IDEADEV-14834
[fedora-idea.git] / plugins / InspectionGadgets / src / com / siyeh / ig / dataflow / TooBroadScopeInspection.java
blobafd434fffa4ec27fdcf9d507318b68f1c933eae5
1 /*
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;
50 @NotNull
51 public String getDisplayName()
53 return InspectionGadgetsBundle.message("too.broad.scope.display.name");
56 @NotNull
57 public String getID()
59 return "TooBroadScope";
62 @Nullable
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;
77 @NotNull
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)
95 m_name = name;
98 @NotNull
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 =
117 query.findAll();
118 final PsiElement[] referenceElements =
119 new PsiElement[referenceCollection.size()];
120 int index = 0;
121 for (PsiReference reference : referenceCollection)
123 final PsiElement referenceElement = reference.getElement();
124 referenceElements[index] = referenceElement;
125 index++;
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)
141 return;
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,
153 referenceElement);
155 else
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);
171 if (isOnTheFly())
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();
189 else
191 variable.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();
203 if (name == null)
205 name = "";
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 +
214 ';' + comment;
216 else
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) {
251 return "";
253 final PsiElement lastChild = parentDeclaration.getLastChild();
254 if (!(lastChild instanceof PsiComment)) {
255 return "";
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,
300 statement);
301 final PsiElement parent =
302 assignmentExpression.getParent();
303 assert parent != null;
304 parent.delete();
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;
324 else
326 return (PsiDeclarationStatement)
327 statementParent.addBefore(newDeclaration, statement);
332 private boolean isMoveable(PsiExpression expression)
334 if (expression == null)
336 return true;
338 if (PsiUtil.isConstantExpression(expression) ||
339 ExpressionUtils.isNullLiteral(expression)) {
340 return true;
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))
354 return false;
357 return true;
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 :
367 initializers)
369 result &= isMoveable(initializerExpression);
372 else if (!m_allowConstructorAsInitializer)
374 final PsiType type = newExpression.getType();
375 if (!ClassUtils.isImmutable(type)) {
376 return false;
379 final PsiExpressionList argumentList =
380 newExpression.getArgumentList();
381 if (argumentList == null)
383 return result;
385 final PsiExpression[] expressions =
386 argumentList.getExpressions();
387 for (final PsiExpression argumentExpression : expressions)
389 result &= isMoveable(argumentExpression);
391 return result;
393 if (expression instanceof PsiReferenceExpression) {
394 final PsiReferenceExpression referenceExpression =
395 (PsiReferenceExpression)expression;
396 final PsiElement target = referenceExpression.resolve();
397 if (!(target instanceof PsiField)) {
398 return false;
400 final PsiField field = (PsiField)target;
401 if (ExpressionUtils.isConstant(field)) {
402 return true;
405 return false;
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))
421 return;
423 final PsiExpression initializer = variable.getInitializer();
424 if (!isMoveable(initializer))
426 return;
428 final PsiElement variableScope =
429 PsiTreeUtil.getParentOfType(variable,
430 PsiCodeBlock.class, PsiForStatement.class);
431 if (variableScope == null)
433 return;
435 final Query<PsiReference> query =
436 ReferencesSearch.search(variable, variable.getUseScope());
437 final Collection<PsiReference> referencesCollection =
438 query.findAll();
439 final int size = referencesCollection.size();
440 if (size == 0)
442 return;
444 final PsiElement[] referenceElements =
445 new PsiElement[referencesCollection.size()];
446 int index = 0;
447 for (PsiReference reference : referencesCollection)
449 final PsiElement referenceElement = reference.getElement();
450 referenceElements[index] = referenceElement;
451 index++;
453 PsiElement commonParent =
454 ScopeUtils.getCommonParent(referenceElements);
455 if (commonParent == null)
457 return;
459 if (initializer != null)
461 commonParent =
462 ScopeUtils.moveOutOfLoops(commonParent, variableScope);
463 if (commonParent == null)
465 return;
468 if (PsiTreeUtil.isAncestor(variableScope, commonParent, true))
470 registerVariableError(variable);
471 return;
473 if (m_onlyLookAtBlocks)
475 return;
477 if (commonParent instanceof PsiForStatement)
479 return;
481 final PsiElement referenceElement = referenceElements[0];
482 final PsiElement blockChild =
483 ScopeUtils.getChildWhichContainsElement(variableScope,
484 referenceElement);
485 if (blockChild == null)
487 return;
489 final PsiElement insertionPoint =
490 ScopeUtils.findTighterDeclarationLocation(
491 blockChild, variable);
492 if (insertionPoint == null)
494 if (!(blockChild instanceof PsiExpressionStatement))
496 return;
498 final PsiExpressionStatement expressionStatement =
499 (PsiExpressionStatement)blockChild;
500 final PsiExpression expression =
501 expressionStatement.getExpression();
502 if (!(expression instanceof PsiAssignmentExpression))
504 return;
506 final PsiAssignmentExpression assignmentExpression =
507 (PsiAssignmentExpression)expression;
508 final PsiExpression lExpression =
509 assignmentExpression.getLExpression();
510 if (!lExpression.equals(referenceElement))
512 return;
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))
525 return;
529 registerVariableError(variable);