2 * Copyright 2003-2007 Dave Griffith, Bas Leijdekkers
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com
.siyeh
.ig
.dataflow
;
18 import com
.intellij
.codeInspection
.ProblemDescriptor
;
19 import com
.intellij
.openapi
.project
.Project
;
20 import com
.intellij
.psi
.*;
21 import com
.intellij
.psi
.codeStyle
.CodeStyleManager
;
22 import com
.intellij
.psi
.search
.searches
.ReferencesSearch
;
23 import com
.intellij
.psi
.util
.PsiTreeUtil
;
24 import com
.intellij
.psi
.util
.PsiUtil
;
25 import com
.intellij
.util
.IncorrectOperationException
;
26 import com
.intellij
.util
.Query
;
27 import com
.siyeh
.InspectionGadgetsBundle
;
28 import com
.siyeh
.ig
.BaseInspection
;
29 import com
.siyeh
.ig
.BaseInspectionVisitor
;
30 import com
.siyeh
.ig
.InspectionGadgetsFix
;
31 import com
.siyeh
.ig
.psiutils
.ClassUtils
;
32 import com
.siyeh
.ig
.psiutils
.ExpressionUtils
;
33 import com
.siyeh
.ig
.psiutils
.HighlightUtil
;
34 import com
.siyeh
.ig
.ui
.MultipleCheckboxOptionsPanel
;
35 import org
.jetbrains
.annotations
.NotNull
;
36 import org
.jetbrains
.annotations
.Nullable
;
38 import javax
.swing
.JComponent
;
39 import java
.util
.Collection
;
41 public class TooBroadScopeInspection
extends BaseInspection
44 /** @noinspection PublicField for externalization*/
45 public boolean m_allowConstructorAsInitializer
= false;
47 /** @noinspection PublicField for externalization*/
48 public boolean m_onlyLookAtBlocks
= false;
51 public String
getDisplayName()
53 return InspectionGadgetsBundle
.message("too.broad.scope.display.name");
59 return "TooBroadScope";
63 public JComponent
createOptionsPanel()
65 // html allows text to wrap
66 final MultipleCheckboxOptionsPanel checkboxOptionsPanel
=
67 new MultipleCheckboxOptionsPanel(this);
68 checkboxOptionsPanel
.addCheckbox(InspectionGadgetsBundle
.message(
69 "too.broad.scope.only.blocks.option"),
70 "m_onlyLookAtBlocks");
71 checkboxOptionsPanel
.addCheckbox(InspectionGadgetsBundle
.message(
72 "too.broad.scope.allow.option"),
73 "m_allowConstructorAsInitializer");
74 return checkboxOptionsPanel
;
78 protected String
buildErrorString(Object
... infos
)
80 return InspectionGadgetsBundle
.message(
81 "too.broad.scope.problem.descriptor");
84 public InspectionGadgetsFix
buildFix(PsiElement location
)
86 return new TooBroadScopeInspectionFix(location
.getText());
89 private class TooBroadScopeInspectionFix
extends InspectionGadgetsFix
91 private String m_name
;
93 TooBroadScopeInspectionFix(String name
)
99 public String
getName()
101 return InspectionGadgetsBundle
.message(
102 "too.broad.scope.narrow.quickfix", m_name
);
105 protected void doFix(@NotNull Project project
,
106 ProblemDescriptor descriptor
)
107 throws IncorrectOperationException
109 final PsiElement variableIdentifier
=
110 descriptor
.getPsiElement();
111 final PsiVariable variable
=
112 (PsiVariable
)variableIdentifier
.getParent();
113 assert variable
!= null;
114 final Query
<PsiReference
> query
=
115 ReferencesSearch
.search(variable
, variable
.getUseScope());
116 final Collection
<PsiReference
> referenceCollection
=
118 final PsiElement
[] referenceElements
=
119 new PsiElement
[referenceCollection
.size()];
121 for (PsiReference reference
: referenceCollection
)
123 final PsiElement referenceElement
= reference
.getElement();
124 referenceElements
[index
] = referenceElement
;
127 PsiElement commonParent
=
128 ScopeUtils
.getCommonParent(referenceElements
);
129 assert commonParent
!= null;
130 final PsiExpression initializer
= variable
.getInitializer();
131 if (initializer
!= null)
133 final PsiElement variableScope
=
134 PsiTreeUtil
.getParentOfType(variable
,
135 PsiCodeBlock
.class, PsiForStatement
.class);
136 assert variableScope
!= null;
137 commonParent
= ScopeUtils
.moveOutOfLoops(
138 commonParent
, variableScope
);
139 if (commonParent
== null)
144 final PsiElement referenceElement
= referenceElements
[0];
145 final PsiElement firstReferenceScope
=
146 PsiTreeUtil
.getParentOfType(referenceElement
,
147 PsiCodeBlock
.class, PsiForStatement
.class);
148 assert firstReferenceScope
!= null;
149 PsiDeclarationStatement newDeclaration
;
150 if (firstReferenceScope
.equals(commonParent
))
152 newDeclaration
= moveDeclarationToLocation(variable
,
157 final PsiElement commonParentChild
=
158 ScopeUtils
.getChildWhichContainsElement(
159 commonParent
, referenceElement
);
160 assert commonParentChild
!= null;
161 final PsiElement location
= commonParentChild
.getPrevSibling();
162 newDeclaration
= createNewDeclaration(variable
, initializer
);
163 newDeclaration
= (PsiDeclarationStatement
)
164 commonParent
.addAfter(newDeclaration
, location
);
166 final CodeStyleManager codeStyleManager
=
167 CodeStyleManager
.getInstance(project
);
168 newDeclaration
= (PsiDeclarationStatement
)
169 codeStyleManager
.reformat(newDeclaration
);
170 removeOldVariable(variable
);
173 HighlightUtil
.highlightElement(newDeclaration
);
177 private void removeOldVariable(@NotNull PsiVariable variable
)
178 throws IncorrectOperationException
180 final PsiDeclarationStatement declaration
=
181 (PsiDeclarationStatement
)variable
.getParent();
182 assert declaration
!= null;
183 final PsiElement
[] declaredElements
=
184 declaration
.getDeclaredElements();
185 if (declaredElements
.length
== 1)
187 declaration
.delete();
195 private PsiDeclarationStatement
createNewDeclaration(
196 @NotNull PsiVariable variable
,
197 @Nullable PsiExpression initializer
)
198 throws IncorrectOperationException
200 final PsiManager manager
= variable
.getManager();
201 final PsiElementFactory factory
= manager
.getElementFactory();
202 String name
= variable
.getName();
207 final String comment
= getCommentText(variable
);
208 final PsiType type
= variable
.getType();
209 final String statementText
;
210 final String typeText
= type
.getCanonicalText();
211 if (initializer
== null)
213 statementText
= typeText
+ ' ' + name
+
218 final String initializerText
= initializer
.getText();
219 statementText
= typeText
+ ' ' + name
+
220 '=' + initializerText
+ ';' + comment
;
222 final PsiDeclarationStatement newDeclaration
=
223 (PsiDeclarationStatement
)factory
.createStatementFromText(
224 statementText
, variable
);
225 final PsiLocalVariable newVariable
=
226 (PsiLocalVariable
)newDeclaration
.getDeclaredElements()[0];
227 final PsiModifierList newModifierList
=
228 newVariable
.getModifierList();
229 final PsiModifierList modifierList
= variable
.getModifierList();
230 if (newModifierList
!= null && modifierList
!= null)
232 // remove final when PsiDeclarationFactory adds one by mistake
233 newModifierList
.setModifierProperty(PsiModifier
.FINAL
,
234 variable
.hasModifierProperty(PsiModifier
.FINAL
));
235 final PsiAnnotation
[] annotations
=
236 modifierList
.getAnnotations();
237 for (PsiAnnotation annotation
: annotations
)
239 newModifierList
.add(annotation
);
242 return newDeclaration
;
245 private String
getCommentText(PsiVariable variable
) {
246 final PsiDeclarationStatement parentDeclaration
=
247 (PsiDeclarationStatement
)variable
.getParent();
248 final PsiElement
[] declaredElements
=
249 parentDeclaration
.getDeclaredElements();
250 if (declaredElements
.length
!= 1) {
253 final PsiElement lastChild
= parentDeclaration
.getLastChild();
254 if (!(lastChild
instanceof PsiComment
)) {
257 final PsiElement prevSibling
= lastChild
.getPrevSibling();
258 if (prevSibling
instanceof PsiWhiteSpace
) {
259 return prevSibling
.getText() + lastChild
.getText();
261 return lastChild
.getText();
264 private PsiDeclarationStatement
moveDeclarationToLocation(
265 @NotNull PsiVariable variable
, @NotNull PsiElement location
)
266 throws IncorrectOperationException
268 PsiStatement statement
= PsiTreeUtil
.getParentOfType(location
,
269 PsiStatement
.class, false);
270 assert statement
!= null;
271 PsiElement statementParent
= statement
.getParent();
272 while (statementParent
instanceof PsiStatement
&&
273 !(statementParent
instanceof PsiForStatement
))
275 statement
= (PsiStatement
)statementParent
;
276 statementParent
= statement
.getParent();
278 assert statementParent
!= null;
279 final PsiExpression initializer
= variable
.getInitializer();
280 if (isMoveable(initializer
) &&
281 statement
instanceof PsiExpressionStatement
)
283 final PsiExpressionStatement expressionStatement
=
284 (PsiExpressionStatement
)statement
;
285 final PsiExpression expression
=
286 expressionStatement
.getExpression();
287 if (expression
instanceof PsiAssignmentExpression
)
289 final PsiAssignmentExpression assignmentExpression
=
290 (PsiAssignmentExpression
)expression
;
291 final PsiExpression lExpression
=
292 assignmentExpression
.getLExpression();
293 if (location
.equals(lExpression
))
295 PsiDeclarationStatement newDeclaration
=
296 createNewDeclaration(variable
,
297 assignmentExpression
.getRExpression());
298 newDeclaration
= (PsiDeclarationStatement
)
299 statementParent
.addBefore(newDeclaration
,
301 final PsiElement parent
=
302 assignmentExpression
.getParent();
303 assert parent
!= null;
305 return newDeclaration
;
309 PsiDeclarationStatement newDeclaration
=
310 createNewDeclaration(variable
, initializer
);
311 if (statement
instanceof PsiForStatement
)
313 final PsiForStatement forStatement
= (PsiForStatement
)statement
;
314 final PsiStatement initialization
=
315 forStatement
.getInitialization();
316 newDeclaration
= (PsiDeclarationStatement
)
317 forStatement
.addBefore(newDeclaration
, initialization
);
318 if (initialization
!= null)
320 initialization
.delete();
322 return newDeclaration
;
326 return (PsiDeclarationStatement
)
327 statementParent
.addBefore(newDeclaration
, statement
);
332 private boolean isMoveable(PsiExpression expression
)
334 if (expression
== null)
338 if (PsiUtil
.isConstantExpression(expression
) ||
339 ExpressionUtils
.isNullLiteral(expression
)) {
342 if (expression
instanceof PsiNewExpression
)
344 final PsiNewExpression newExpression
=
345 (PsiNewExpression
)expression
;
346 final PsiExpression
[] arrayDimensions
=
347 newExpression
.getArrayDimensions();
348 if (arrayDimensions
.length
> 0)
350 for (PsiExpression arrayDimension
: arrayDimensions
)
352 if (!isMoveable(arrayDimension
))
359 final PsiArrayInitializerExpression arrayInitializer
=
360 newExpression
.getArrayInitializer();
361 boolean result
= true;
362 if (arrayInitializer
!= null)
364 final PsiExpression
[] initializers
=
365 arrayInitializer
.getInitializers();
366 for (final PsiExpression initializerExpression
:
369 result
&= isMoveable(initializerExpression
);
372 else if (!m_allowConstructorAsInitializer
)
374 final PsiType type
= newExpression
.getType();
375 if (!ClassUtils
.isImmutable(type
)) {
379 final PsiExpressionList argumentList
=
380 newExpression
.getArgumentList();
381 if (argumentList
== null)
385 final PsiExpression
[] expressions
=
386 argumentList
.getExpressions();
387 for (final PsiExpression argumentExpression
: expressions
)
389 result
&= isMoveable(argumentExpression
);
393 if (expression
instanceof PsiReferenceExpression
) {
394 final PsiReferenceExpression referenceExpression
=
395 (PsiReferenceExpression
)expression
;
396 final PsiElement target
= referenceExpression
.resolve();
397 if (!(target
instanceof PsiField
)) {
400 final PsiField field
= (PsiField
)target
;
401 if (ExpressionUtils
.isConstant(field
)) {
408 public BaseInspectionVisitor
buildVisitor()
410 return new TooBroadScopeVisitor();
413 private class TooBroadScopeVisitor
extends BaseInspectionVisitor
416 public void visitVariable(@NotNull PsiVariable variable
)
418 super.visitVariable(variable
);
419 if (!(variable
instanceof PsiLocalVariable
))
423 final PsiExpression initializer
= variable
.getInitializer();
424 if (!isMoveable(initializer
))
428 final PsiElement variableScope
=
429 PsiTreeUtil
.getParentOfType(variable
,
430 PsiCodeBlock
.class, PsiForStatement
.class);
431 if (variableScope
== null)
435 final Query
<PsiReference
> query
=
436 ReferencesSearch
.search(variable
, variable
.getUseScope());
437 final Collection
<PsiReference
> referencesCollection
=
439 final int size
= referencesCollection
.size();
444 final PsiElement
[] referenceElements
=
445 new PsiElement
[referencesCollection
.size()];
447 for (PsiReference reference
: referencesCollection
)
449 final PsiElement referenceElement
= reference
.getElement();
450 referenceElements
[index
] = referenceElement
;
453 PsiElement commonParent
=
454 ScopeUtils
.getCommonParent(referenceElements
);
455 if (commonParent
== null)
459 if (initializer
!= null)
462 ScopeUtils
.moveOutOfLoops(commonParent
, variableScope
);
463 if (commonParent
== null)
468 if (PsiTreeUtil
.isAncestor(variableScope
, commonParent
, true))
470 registerVariableError(variable
);
473 if (m_onlyLookAtBlocks
)
477 if (commonParent
instanceof PsiForStatement
)
481 final PsiElement referenceElement
= referenceElements
[0];
482 final PsiElement blockChild
=
483 ScopeUtils
.getChildWhichContainsElement(variableScope
,
485 if (blockChild
== null)
489 final PsiElement insertionPoint
=
490 ScopeUtils
.findTighterDeclarationLocation(
491 blockChild
, variable
);
492 if (insertionPoint
== null)
494 if (!(blockChild
instanceof PsiExpressionStatement
))
498 final PsiExpressionStatement expressionStatement
=
499 (PsiExpressionStatement
)blockChild
;
500 final PsiExpression expression
=
501 expressionStatement
.getExpression();
502 if (!(expression
instanceof PsiAssignmentExpression
))
506 final PsiAssignmentExpression assignmentExpression
=
507 (PsiAssignmentExpression
)expression
;
508 final PsiExpression lExpression
=
509 assignmentExpression
.getLExpression();
510 if (!lExpression
.equals(referenceElement
))
515 if (insertionPoint
!= null && PsiUtil
.isInJspFile(insertionPoint
))
517 PsiElement elementBefore
= insertionPoint
.getPrevSibling();
518 elementBefore
= PsiTreeUtil
.skipSiblingsBackward(elementBefore
,
519 PsiWhiteSpace
.class);
520 if (elementBefore
instanceof PsiDeclarationStatement
)
522 final PsiElement variableParent
= variable
.getParent();
523 if (elementBefore
.equals(variableParent
))
529 registerVariableError(variable
);