don't let "'StringBuffer' can be replaced with 'String'" inspection report problems...
[idea/community.git] / plugins / InspectionGadgets / src / com / siyeh / ig / style / StringBufferReplaceableByStringInspection.java
1 /*
2  * Copyright 2003-2012 Dave Griffith, Bas Leijdekkers
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.siyeh.ig.style;
17
18 import com.intellij.codeInspection.ProblemDescriptor;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.psi.*;
21 import com.intellij.psi.util.PsiTreeUtil;
22 import com.intellij.util.IncorrectOperationException;
23 import com.siyeh.InspectionGadgetsBundle;
24 import com.siyeh.ig.BaseInspection;
25 import com.siyeh.ig.BaseInspectionVisitor;
26 import com.siyeh.ig.InspectionGadgetsFix;
27 import com.siyeh.ig.psiutils.ParenthesesUtils;
28 import com.siyeh.ig.psiutils.TypeUtils;
29 import com.siyeh.ig.psiutils.VariableAccessUtils;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
32
33 public class StringBufferReplaceableByStringInspection extends BaseInspection {
34
35   @Override
36   @NotNull
37   public String getDisplayName() {
38     return InspectionGadgetsBundle.message("string.buffer.replaceable.by.string.display.name");
39   }
40
41   @Override
42   @NotNull
43   public String buildErrorString(Object... infos) {
44     final PsiElement element = (PsiElement)infos[0];
45     if (element instanceof PsiNewExpression) {
46       return InspectionGadgetsBundle.message("new.string.buffer.replaceable.by.string.problem.descriptor");
47     }
48     final String typeText = ((PsiType)infos[1]).getPresentableText();
49     return InspectionGadgetsBundle.message("string.buffer.replaceable.by.string.problem.descriptor", typeText);
50   }
51
52   @Override
53   protected InspectionGadgetsFix buildFix(Object... infos) {
54     final String typeText = ((PsiType)infos[1]).getCanonicalText();
55     return new StringBufferReplaceableByStringFix(CommonClassNames.JAVA_LANG_STRING_BUILDER.equals(typeText));
56   }
57
58   private static class StringBufferReplaceableByStringFix extends InspectionGadgetsFix {
59
60     private final boolean isStringBuilder;
61
62     private StringBufferReplaceableByStringFix(boolean isStringBuilder) {
63       this.isStringBuilder = isStringBuilder;
64     }
65
66     @NotNull
67     @Override
68     public String getName() {
69       if (isStringBuilder) {
70         return InspectionGadgetsBundle.message("string.builder.replaceable.by.string.quickfix");
71       }
72       else {
73         return InspectionGadgetsBundle.message("string.buffer.replaceable.by.string.quickfix");
74       }
75     }
76
77     @Override
78     protected void doFix(Project project, ProblemDescriptor descriptor) throws IncorrectOperationException {
79       final PsiElement element = descriptor.getPsiElement();
80       final PsiElement parent = element.getParent();
81       if (!(parent instanceof PsiVariable)) {
82         if (parent instanceof PsiNewExpression) {
83           final PsiNewExpression newExpression = (PsiNewExpression)parent;
84           final PsiExpression stringBuilderExpression = getCompleteExpression(newExpression);
85           final StringBuilder stringExpression = buildStringExpression(stringBuilderExpression, new StringBuilder());
86           if (stringExpression != null && stringBuilderExpression != null) {
87             replaceExpression(stringBuilderExpression, stringExpression.toString());
88           }
89           return;
90         }
91         return;
92       }
93       final PsiVariable variable = (PsiVariable)parent;
94       final PsiTypeElement originalTypeElement = variable.getTypeElement();
95       if (originalTypeElement == null) {
96         return;
97       }
98       final PsiExpression initializer = variable.getInitializer();
99       if (initializer == null) {
100         return;
101       }
102       final StringBuilder stringExpression = buildStringExpression(initializer, new StringBuilder());
103       if (stringExpression == null) {
104         return;
105       }
106       final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
107       final PsiClassType javaLangString = factory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_STRING, variable.getResolveScope());
108       final PsiTypeElement typeElement = factory.createTypeElement(javaLangString);
109       replaceExpression(initializer, stringExpression.toString());
110       originalTypeElement.replace(typeElement);
111     }
112
113     @Nullable
114     private static StringBuilder buildStringExpression(PsiExpression initializer, StringBuilder result) {
115       if (initializer instanceof PsiNewExpression) {
116         final PsiNewExpression newExpression = (PsiNewExpression)initializer;
117         final PsiExpressionList argumentList = newExpression.getArgumentList();
118         if (argumentList ==  null) {
119           return null;
120         }
121         final PsiExpression[] arguments = argumentList.getExpressions();
122         if (arguments.length == 1) {
123           final PsiExpression argument = arguments[0];
124           final PsiType type = argument.getType();
125           if (!PsiType.INT.equals(type)) {
126             result.append(argument.getText());
127           }
128         }
129         return result;
130       }
131       else if (initializer instanceof PsiMethodCallExpression) {
132         final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)initializer;
133         final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
134         final PsiExpression qualifier = methodExpression.getQualifierExpression();
135         result = buildStringExpression(qualifier, result);
136         if (result == null) {
137           return null;
138         }
139         if ("toString".equals(methodExpression.getReferenceName())) {
140           if (result.length() == 0) {
141             result.append("\"\"");
142           }
143         }
144         else {
145           final PsiExpressionList argumentList = methodCallExpression.getArgumentList();
146           final PsiExpression[] arguments = argumentList.getExpressions();
147           if (arguments.length != 1) {
148             return null;
149           }
150           final PsiExpression argument = arguments[0];
151           if (result.length() != 0) {
152             result.append('+');
153             if (ParenthesesUtils.getPrecedence(argument) > ParenthesesUtils.ADDITIVE_PRECEDENCE) {
154               result.append('(').append(argument.getText()).append(')');
155             }
156             else {
157               result.append(argument.getText());
158             }
159           }
160           else {
161             final PsiType type = argument.getType();
162             if (type instanceof PsiPrimitiveType) {
163               result.append("String.valueOf(").append(argument.getText()).append(")");
164             }
165             else {
166               if (ParenthesesUtils.getPrecedence(argument) > ParenthesesUtils.ADDITIVE_PRECEDENCE) {
167                 result.append('(').append(argument.getText()).append(')');
168               }
169               else {
170                 result.append(argument.getText());
171               }
172             }
173           }
174         }
175         return result;
176       }
177       return null;
178     }
179   }
180
181   @Override
182   public BaseInspectionVisitor buildVisitor() {
183     return new StringBufferReplaceableByStringVisitor();
184   }
185
186   private static class StringBufferReplaceableByStringVisitor extends BaseInspectionVisitor {
187
188     @Override
189     public void visitLocalVariable(@NotNull PsiLocalVariable variable) {
190       super.visitLocalVariable(variable);
191       final PsiCodeBlock codeBlock = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class);
192       if (codeBlock == null) {
193         return;
194       }
195       final PsiType type = variable.getType();
196       if (!TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING_BUFFER, type) &&
197           !TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING_BUILDER, type)) {
198         return;
199       }
200       final PsiExpression initializer = variable.getInitializer();
201       if (initializer == null) {
202         return;
203       }
204       if (!isNewStringBufferOrStringBuilder(initializer)) {
205         return;
206       }
207       if (VariableAccessUtils.variableIsAssigned(variable, codeBlock)) {
208         return;
209       }
210       if (VariableAccessUtils.variableIsAssignedFrom(variable, codeBlock)) {
211         return;
212       }
213       if (VariableAccessUtils.variableIsReturned(variable, codeBlock)) {
214         return;
215       }
216       if (VariableAccessUtils.variableIsPassedAsMethodArgument(variable, codeBlock)) {
217         return;
218       }
219       if (variableIsModified(variable, codeBlock)) {
220         return;
221       }
222       registerVariableError(variable, variable, type);
223     }
224
225     @Override
226     public void visitNewExpression(PsiNewExpression expression) {
227       super.visitNewExpression(expression);
228       final PsiType type = expression.getType();
229       if (!TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING_BUFFER, type) &&
230           !TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING_BUILDER, type)) {
231         return;
232       }
233       final PsiExpression completeExpression = getCompleteExpression(expression);
234       if (completeExpression == null) {
235         return;
236       }
237       registerNewExpressionError(expression, expression, type);
238     }
239
240     public static boolean variableIsModified(PsiVariable variable, PsiElement context) {
241       final VariableIsModifiedVisitor visitor = new VariableIsModifiedVisitor(variable);
242       context.accept(visitor);
243       return visitor.isModified();
244     }
245
246     private static boolean isNewStringBufferOrStringBuilder(PsiExpression expression) {
247       if (expression == null) {
248         return false;
249       }
250       else if (
251       expression instanceof PsiNewExpression) {
252         if (!(expression.getParent() instanceof PsiVariable)) {
253           return true;
254         }
255         final PsiNewExpression newExpression = (PsiNewExpression)expression;
256         final PsiExpressionList argumentList = newExpression.getArgumentList();
257         if (argumentList == null) {
258           return false;
259         }
260         final PsiExpression[] arguments = argumentList.getExpressions();
261         return arguments.length == 1;
262       }
263       else if (expression instanceof PsiMethodCallExpression) {
264         final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression;
265         if (!isAppend(methodCallExpression)) {
266           return false;
267         }
268         final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
269         final PsiExpression qualifier = methodExpression.getQualifierExpression();
270         return isNewStringBufferOrStringBuilder(qualifier);
271       }
272       return false;
273     }
274
275     public static boolean isAppend(PsiMethodCallExpression methodCallExpression) {
276       final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
277       final String methodName = methodExpression.getReferenceName();
278       return "append".equals(methodName);
279     }
280   }
281
282   @Nullable
283   private static PsiExpression getCompleteExpression(PsiNewExpression expression) {
284     PsiElement completeExpression = expression;
285     boolean found = false;
286     while (true) {
287       final PsiElement parent = completeExpression.getParent();
288       if (!(parent instanceof PsiReferenceExpression)) {
289         break;
290       }
291       final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)parent;
292       final PsiElement grandParent = parent.getParent();
293       if (!(grandParent instanceof PsiMethodCallExpression)) {
294         break;
295       }
296       final String name = referenceExpression.getReferenceName();
297       if ("append".equals(name)) {
298         final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)grandParent;
299         final PsiExpressionList argumentList = methodCallExpression.getArgumentList();
300         final PsiExpression[] arguments = argumentList.getExpressions();
301         if (arguments.length != 1) {
302           return null;
303         }
304       }
305       else {
306         if (!"toString".equals(name)) {
307           return null;
308         }
309         found = true;
310       }
311       completeExpression = grandParent;
312       if (found) {
313         return (PsiExpression)completeExpression;
314       }
315     }
316     return null;
317   }
318 }