dev-kit inspection: detect QuickFix's getFamilyName() implementation which are depend on context

This commit is contained in:
Dmitry Batkovich
2016-03-02 11:40:21 +03:00
parent d28fcd74af
commit a33da9a3bd
10 changed files with 257 additions and 0 deletions
@@ -147,6 +147,12 @@
groupKey="inspections.group.name"
enabledByDefault="false" isInternal="true"
level="ERROR" implementationClass="org.jetbrains.idea.devkit.inspections.internal.HighlightVisitorInternalInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="QuickFixGetFamilyNameViolation"
displayName="QuickFix's getFamilyName() implementation must not depend on a specific context"
groupKey="inspections.group.name"
enabledByDefault="true"
level="WARNING"
implementationClass="org.jetbrains.idea.devkit.inspections.QuickFixGetFamilyNameViolationInspection"/>
<moduleConfigurationEditorProvider implementation="org.jetbrains.idea.devkit.module.PluginModuleEditorsProvider"/>
@@ -0,0 +1,6 @@
<html>
<body>
Inspection reports cases when <strong>QuickFix#getFamilyName()</strong>
implementation uses non-static fields, methods, containing method local variables variables etc.
</body>
</html>
@@ -0,0 +1,79 @@
/*
* 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.codeInspection.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* @author Dmitry Batkovich
*/
public class QuickFixGetFamilyNameViolationInspection extends DevKitInspectionBase {
private final static Logger LOG = Logger.getInstance(QuickFixGetFamilyNameViolationInspection.class);
@Nullable
@Override
public ProblemDescriptor[] checkMethod(@NotNull PsiMethod method, @NotNull InspectionManager manager, boolean isOnTheFly) {
if ("getFamilyName".equals(method.getName()) && !method.hasModifierProperty(PsiModifier.ABSTRACT)) {
final PsiClass aClass = method.getContainingClass();
if (InheritanceUtil.isInheritor(aClass, QuickFix.class.getName()) && !isPureMethod(method)) {
final PsiIdentifier identifier = method.getNameIdentifier();
LOG.assertTrue(identifier != null);
//noinspection DialogTitleCapitalization
return new ProblemDescriptor[]{
manager.createProblemDescriptor(identifier, "QuickFix's getFamilyName() implementation must not depend on a specific context", (LocalQuickFix) null, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, true)};
}
}
return null;
}
private static boolean isPureMethod(final PsiMethod method) {
if (method.hasModifierProperty(PsiModifier.STATIC)) return true;
final PsiCodeBlock body = method.getBody();
if (body == null) return true;
final Collection<PsiJavaCodeReferenceElement> referenceIterator =
PsiTreeUtil.findChildrenOfType(body, PsiJavaCodeReferenceElement.class);
for (PsiJavaCodeReferenceElement reference : referenceIterator) {
final PsiElement resolved = reference.resolve();
if (resolved instanceof PsiVariable) {
if (resolved instanceof PsiParameter || (resolved instanceof PsiLocalVariable && !PsiTreeUtil.isAncestor(body, resolved, false))) {
return false;
}
if (resolved instanceof PsiField && !((PsiField)resolved).hasModifierProperty(PsiModifier.STATIC)) {
return false;
}
}
if (resolved instanceof PsiMethod && !((PsiMethod)resolved).hasModifierProperty(PsiModifier.STATIC)) {
final PsiClass resolvedContainingClass = ((PsiMethod)resolved).getContainingClass();
if (resolvedContainingClass == method.getContainingClass()) {
if (!isPureMethod((PsiMethod)resolved)) {
return false;
}
}
}
}
return true;
}
}
@@ -0,0 +1,14 @@
import com.intellij.codeInspection.QuickFix;
class MyQuickFix implements QuickFix {
public String getName() {
return "that fix do some fix";
};
public String getFamilyName() {
return getName();
};
}
@@ -0,0 +1,16 @@
import com.intellij.codeInspection.QuickFix;
class MyQuickFix implements QuickFix {
static String someField = "Hello";
public String getName() {
return "some name";
};
public String getFamilyName() {
return someField + getName() + "123";
};
}
@@ -0,0 +1,20 @@
import com.intellij.codeInspection.QuickFix;
class MyQuickFix implements QuickFix {
String someField;
public String getName() {
return someField;
};
public String getFamilyName() {
return getNameStatic() + "123";
};
static String getNameStatic() {
return "Static";
}
}
@@ -0,0 +1,17 @@
import com.intellij.codeInspection.QuickFix;
class A {
QuickFix getFix(final String someParameter) {
return new QuickFix() {
public String getName() {
return "some name";
};
public String <warning descr="QuickFix's getFamilyName() implementation must not depend on a specific context">getFamilyName</warning>() {
return someParameter + "123";
};
};
}
}
@@ -0,0 +1,16 @@
import com.intellij.codeInspection.QuickFix;
class MyQuickFix implements QuickFix {
String someField;
public String getName() {
return "some name";
};
public String <warning descr="QuickFix's getFamilyName() implementation must not depend on a specific context">getFamilyName</warning>() {
return someField + getName() + "123";
};
}
@@ -0,0 +1,16 @@
import com.intellij.codeInspection.QuickFix;
class MyQuickFix implements QuickFix {
String someField;
public String getName() {
return someField;
};
public String <warning descr="QuickFix's getFamilyName() implementation must not depend on a specific context">getFamilyName</warning>() {
return getName() + "123";
};
}
@@ -0,0 +1,67 @@
/*
* 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.openapi.application.PluginPathManager;
import com.intellij.testFramework.TestDataPath;
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
/**
* @author Dmitry Batkovich
*/
@TestDataPath("$CONTENT_ROOT/testData/inspections/getFamilyNameViolation")
public class QuickFixGetFamilyNameViolationInspectionTest extends JavaCodeInsightFixtureTestCase {
@Override
protected String getBasePath() {
return PluginPathManager.getPluginHomePathRelative("devkit") + "/testData/inspections/getFamilyNameViolation";
}
@Override
protected void setUp() throws Exception {
super.setUp();
myFixture.enableInspections(QuickFixGetFamilyNameViolationInspection.class);
myFixture.addClass("package com.intellij.codeInspection;" +
"public interface QuickFix {" +
" String getName();" +
" String getFamilyName();" +
"}");
}
public void testViolationByField() {
myFixture.testHighlighting(getTestName(false) + ".java");
}
public void testViolationByGetName() {
myFixture.testHighlighting(getTestName(false) + ".java");
}
public void testViolationByExternalParameter() {
myFixture.testHighlighting(getTestName(false) + ".java");
}
public void testNotViolatedStaticField() {
myFixture.testHighlighting(getTestName(false) + ".java");
}
public void testNotViolatedStaticMethod() {
myFixture.testHighlighting(getTestName(false) + ".java");
}
public void testNotViolatedGetNameMethod() {
myFixture.testHighlighting(getTestName(false) + ".java");
}
}