mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
IDEA-163510 DevKit inspection to warn when PsiElement string representation is concatenated and passed to factory.createXXXFromText()
This commit is contained in:
@@ -268,7 +268,7 @@ public abstract class LightPlatformTestCase extends UsefulTestCase implements Da
|
||||
ApplicationInfoImpl.setInPerformanceTest(isPerformanceTest());
|
||||
|
||||
ourApplication.setDataProvider(this);
|
||||
LightProjectDescriptor descriptor = new SimpleLightProjectDescriptor(getModuleType(), getProjectJDK());
|
||||
LightProjectDescriptor descriptor = getProjectDescriptor();
|
||||
doSetup(descriptor, configureLocalInspectionTools(), getTestRootDisposable());
|
||||
InjectedLanguageManagerImpl.pushInjectors(getProject());
|
||||
|
||||
@@ -281,6 +281,11 @@ public abstract class LightPlatformTestCase extends UsefulTestCase implements Da
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected LightProjectDescriptor getProjectDescriptor() {
|
||||
return new SimpleLightProjectDescriptor(getModuleType(), getProjectJDK());
|
||||
}
|
||||
|
||||
public static void doSetup(@NotNull LightProjectDescriptor descriptor,
|
||||
@NotNull LocalInspectionTool[] localInspectionTools,
|
||||
@NotNull Disposable parentDisposable) throws Exception {
|
||||
|
||||
@@ -150,6 +150,10 @@
|
||||
enabledByDefault="true"
|
||||
level="WARNING"
|
||||
implementationClass="org.jetbrains.idea.devkit.inspections.QuickFixGetFamilyNameViolationInspection"/>
|
||||
<localInspection language="JAVA" shortName="PsiElementConcatenation"
|
||||
displayName="Using PsiElement string representation to generate new expression is incorrect"
|
||||
groupKey="inspections.group.name" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="org.jetbrains.idea.devkit.inspections.PsiElementConcatenationInspection"/>
|
||||
|
||||
|
||||
<moduleConfigurationEditorProvider implementation="org.jetbrains.idea.devkit.module.PluginModuleEditorsProvider"/>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
Checks that <code>PsiElement</code> is not directly concatenated in the string which will be used for
|
||||
<code>createExpressionFromText</code>, etc. <code>PsiElement.getText()</code> usually should be used instead.
|
||||
<!-- tooltip end -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetbrains.idea.devkit.inspections;
|
||||
|
||||
import com.intellij.codeInsight.FileModificationService;
|
||||
import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.ProblemDescriptor;
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.controlFlow.DefUseUtil;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import com.siyeh.ig.psiutils.MethodUtils;
|
||||
import com.siyeh.ig.psiutils.ParenthesesUtils;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Tagir Valeev
|
||||
*/
|
||||
public class PsiElementConcatenationInspection extends BaseJavaBatchLocalInspectionTool {
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
||||
return new JavaElementVisitor() {
|
||||
@Override
|
||||
public void visitMethodCallExpression(PsiMethodCallExpression call) {
|
||||
String methodName = call.getMethodExpression().getReferenceName();
|
||||
if(methodName == null || !methodName.startsWith("create")) return;
|
||||
PsiExpression[] args = call.getArgumentList().getExpressions();
|
||||
if(args.length == 0) return;
|
||||
PsiExpression arg = args[0];
|
||||
PsiType argType = arg.getType();
|
||||
if(argType == null || !argType.equalsToText(CommonClassNames.JAVA_LANG_STRING)) return;
|
||||
PsiMethod method = call.resolveMethod();
|
||||
if(method == null) return;
|
||||
PsiClass aClass = method.getContainingClass();
|
||||
if(aClass == null || !PsiElementFactory.class.getName().equals(aClass.getQualifiedName())) return;
|
||||
checkOperand(arg, new HashSet<>());
|
||||
}
|
||||
|
||||
@SuppressWarnings("DialogTitleCapitalization")
|
||||
private void checkOperand(@Nullable PsiExpression operand, Set<PsiExpression> visited) {
|
||||
if(operand == null || !visited.add(operand)) return;
|
||||
if(operand instanceof PsiReferenceExpression) {
|
||||
PsiElement element = ((PsiReferenceExpression)operand).resolve();
|
||||
if(element instanceof PsiLocalVariable && ((PsiLocalVariable)element).getType().equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
|
||||
PsiCodeBlock block = PsiTreeUtil.getParentOfType(element, PsiCodeBlock.class);
|
||||
if(block != null) {
|
||||
PsiElement[] defs = DefUseUtil.getDefs(block, (PsiVariable)element, operand);
|
||||
for(PsiElement def : defs) {
|
||||
if(def instanceof PsiLocalVariable) {
|
||||
checkOperand(((PsiLocalVariable)def).getInitializer(), visited);
|
||||
}
|
||||
if(def instanceof PsiReferenceExpression) {
|
||||
PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(def.getParent());
|
||||
if(assignment != null && assignment.getLExpression() == def) {
|
||||
checkOperand(assignment.getRExpression(), visited);
|
||||
}
|
||||
}
|
||||
if(def instanceof PsiExpression) {
|
||||
checkOperand((PsiExpression)def, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(operand instanceof PsiMethodCallExpression) {
|
||||
PsiMethodCallExpression call = (PsiMethodCallExpression)operand;
|
||||
PsiMethod method = call.resolveMethod();
|
||||
if(MethodUtils.isToString(method)) {
|
||||
checkOperand(call.getMethodExpression().getQualifierExpression(), visited);
|
||||
}
|
||||
}
|
||||
PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(operand.getType());
|
||||
if(InheritanceUtil.isInheritor(aClass, false, PsiElement.class.getName())) {
|
||||
holder.registerProblem(operand, "Suspicious conversion of PsiElement to string",
|
||||
new AddGetTextFix("getText"));
|
||||
}
|
||||
if(InheritanceUtil.isInheritor(aClass, false, PsiType.class.getName())) {
|
||||
holder.registerProblem(operand, "Suspicious conversion of PsiType to string",
|
||||
new AddGetTextFix("getCanonicalText"));
|
||||
}
|
||||
if(operand instanceof PsiPolyadicExpression) {
|
||||
PsiPolyadicExpression polyadic = (PsiPolyadicExpression)operand;
|
||||
if(JavaTokenType.PLUS.equals(polyadic.getOperationTokenType())) {
|
||||
for (PsiExpression op : polyadic.getOperands()) {
|
||||
checkOperand(op, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static class AddGetTextFix implements LocalQuickFix {
|
||||
private final String myMethodName;
|
||||
|
||||
private AddGetTextFix(String name) {
|
||||
myMethodName = name;
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Use '"+myMethodName+"' call";
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return "Call text representation retrieval method";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
PsiElement element = descriptor.getStartElement();
|
||||
if(!(element instanceof PsiExpression)) return;
|
||||
PsiExpression expression = (PsiExpression)element;
|
||||
if(!FileModificationService.getInstance().preparePsiElementForWrite(element)) return;
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
|
||||
PsiExpression replacement = factory.createExpressionFromText(ParenthesesUtils.getText(expression, ParenthesesUtils.POSTFIX_PRECEDENCE)
|
||||
+ "." + myMethodName + "()", expression);
|
||||
PsiElement parent = expression.getParent().getParent();
|
||||
if(parent instanceof PsiMethodCallExpression && MethodUtils.isToString(((PsiMethodCallExpression)parent).resolveMethod())) {
|
||||
element = parent;
|
||||
}
|
||||
PsiElement result = element.replace(replacement);
|
||||
CodeStyleManager.getInstance(project).reformat(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Use 'getText' call" "true"
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.*;
|
||||
|
||||
public class PsiElementConcatenationText {
|
||||
public PsiExpression test(PsiElementFactory factory, PsiElement element) {
|
||||
return factory.createExpressionFromText("(Object)"+ element.getText(), element);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// "Use 'getText' call" "true"
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.*;
|
||||
|
||||
public class PsiElementConcatenationText {
|
||||
public PsiExpression test(PsiElementFactory factory, PsiElement element) {
|
||||
String s = "(Object)"+ element.getText();
|
||||
String l = "x -> "+s;
|
||||
return factory.createExpressionFromText("("+l+")", element);
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Use 'getText' call" "true"
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.*;
|
||||
|
||||
public class PsiElementConcatenationText {
|
||||
public PsiExpression test(PsiElementFactory factory, PsiElement element) {
|
||||
return factory.createExpressionFromText(element.getText(), element);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Use 'getCanonicalText' call" "true"
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.*;
|
||||
|
||||
public class PsiElementConcatenationText {
|
||||
public PsiExpression test(PsiElementFactory factory, PsiType type, PsiElement element) {
|
||||
return factory.createExpressionFromText("(Object)"+ type.getCanonicalText(), element);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Use 'getText' call" "true"
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.*;
|
||||
|
||||
public class PsiElementConcatenationText {
|
||||
public PsiExpression test(PsiElementFactory factory, PsiElement element) {
|
||||
return factory.createExpressionFromText("(Object)"+ele<caret>ment, element);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// "Use 'getText' call" "true"
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.*;
|
||||
|
||||
public class PsiElementConcatenationText {
|
||||
public PsiExpression test(PsiElementFactory factory, PsiElement element) {
|
||||
String s = "(Object)"+elem<caret>ent;
|
||||
String l = "x -> "+s;
|
||||
return factory.createExpressionFromText("("+l+")", element);
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Use 'getText' call" "true"
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.*;
|
||||
|
||||
public class PsiElementConcatenationText {
|
||||
public PsiExpression test(PsiElementFactory factory, PsiElement element) {
|
||||
return factory.createExpressionFromText(eleme<caret>nt.toString(), element);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Use 'getCanonicalText' call" "true"
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.*;
|
||||
|
||||
public class PsiElementConcatenationText {
|
||||
public PsiExpression test(PsiElementFactory factory, PsiType type, PsiElement element) {
|
||||
return factory.createExpressionFromText("(Object)"+ty<caret>pe, element);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetbrains.idea.devkit.inspections;
|
||||
|
||||
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.openapi.application.PluginPathManager;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.roots.ContentEntry;
|
||||
import com.intellij.openapi.roots.ModifiableRootModel;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiElementFactory;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.PsiTestUtil;
|
||||
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor;
|
||||
import com.intellij.util.PathUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class PsiElementConcatenationInspectionTest extends LightQuickFixParameterizedTestCase {
|
||||
@NotNull
|
||||
@Override
|
||||
protected LightProjectDescriptor getProjectDescriptor() {
|
||||
return new DefaultLightProjectDescriptor() {
|
||||
@Override
|
||||
public void configureModule(@NotNull Module module, @NotNull ModifiableRootModel model, @NotNull ContentEntry contentEntry) {
|
||||
super.configureModule(module, model, contentEntry);
|
||||
PsiTestUtil.addLibrary(module, model, "core-api", "", PathUtil.getJarPathForClass(PsiElement.class));
|
||||
PsiTestUtil.addLibrary(module, model, "java-psi-api", "", PathUtil.getJarPathForClass(PsiElementFactory.class));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected LocalInspectionTool[] configureLocalInspectionTools() {
|
||||
return new LocalInspectionTool[]{new PsiElementConcatenationInspection()};
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected String getTestDataPath() {
|
||||
return PluginPathManager.getPluginHomePath("devkit") + "/testData";
|
||||
}
|
||||
|
||||
public void test() throws Exception {
|
||||
doAllTests();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return "/inspections/psiElementConcatenation";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user