IDEA-179541 Propose to rename inspection description file and short name on inspection rename

This commit is contained in:
Yaroslav Pankratyev
2018-01-15 17:17:28 +07:00
parent 328bc1b1be
commit d8d24ab2fe
17 changed files with 415 additions and 3 deletions
@@ -28,6 +28,8 @@
<psi.referenceContributor language="JAVA" implementation="org.jetbrains.idea.devkit.testAssistant.TestDataReferenceContributor"/>
<getDataRule key="Location" implementationClass="org.jetbrains.idea.devkit.testAssistant.TestLocationDataRule"/>
<automaticRenamerFactory implementation="org.jetbrains.idea.devkit.refactoring.InspectionAutomaticRenamerFactory"/>
<junitPatcher implementation="org.jetbrains.idea.devkit.run.JUnitDevKitPatcher"/>
<antBuildGen implementation="org.jetbrains.idea.devkit.build.ant.ChunkBuildPluginExtension"/>
<compiler.buildTargetScopeProvider implementation="org.jetbrains.idea.devkit.build.PluginModuleBuildScopeProvider"/>
@@ -197,3 +197,8 @@ implemented.at.runtime.jamElement=JAM Element implemented at runtime
line.marker.tooltip.extension.declaration=<a href="#navigation/{0}:{1}">{2}</a> declaration in {3}
line.marker.tooltip.extension.point.declaration=<a href="#navigation/{0}:{1}">{2}</a> EP declaration in {3}
inspections.usage.uelement.as.psi=Usage of UElement as PsiElement is not recommended
inspection.renamer.dialog.title=Rename Inspection Description File and Change the Short Name
inspection.renamer.option.name=Rename inspection description file and change the short name
inspection.renamer.dialog.description=<html>Do you want to also rename the inspection description file and change the short name?<br/><b>Warning: it may break existing inspection profiles and suppression comments.</b></html>
inspection.renamer.entity.name=Element
@@ -52,11 +52,14 @@ public class InspectionDescriptionInfo {
private final String myFilename;
private final PsiMethod myMethod;
private final PsiFile myDescriptionFile;
private final boolean myShortNameInXml;
private InspectionDescriptionInfo(String filename, @Nullable PsiMethod method, @Nullable PsiFile descriptionFile) {
private InspectionDescriptionInfo(String filename, @Nullable PsiMethod method,
@Nullable PsiFile descriptionFile, boolean shortNameInXml) {
myFilename = filename;
myMethod = method;
myDescriptionFile = descriptionFile;
myShortNameInXml = shortNameInXml;
}
public static InspectionDescriptionInfo create(Module module, PsiClass psiClass) {
@@ -64,8 +67,10 @@ public class InspectionDescriptionInfo {
if (method != null && method.getContainingClass().hasModifierProperty(PsiModifier.ABSTRACT)) {
method = null;
}
boolean shortNameInXml;
String filename = null;
if (method == null) {
shortNameInXml = true;
String className = psiClass.getQualifiedName();
if(className != null) {
Extension extension = findExtension(module, psiClass);
@@ -75,6 +80,7 @@ public class InspectionDescriptionInfo {
}
}
else {
shortNameInXml = false;
filename = PsiUtil.getReturnedLiteral(method, psiClass);
}
@@ -85,11 +91,11 @@ public class InspectionDescriptionInfo {
}
PsiFile descriptionFile = resolveInspectionDescriptionFile(module, filename);
return new InspectionDescriptionInfo(filename, method, descriptionFile);
return new InspectionDescriptionInfo(filename, method, descriptionFile, shortNameInXml);
}
@Nullable
static Extension findExtension(Module module, PsiClass psiClass) {
public static Extension findExtension(Module module, PsiClass psiClass) {
return CachedValuesManager.getCachedValue(psiClass, () -> {
Extension extension = doFindExtension(module, psiClass);
return CachedValueProvider.Result
@@ -168,4 +174,8 @@ public class InspectionDescriptionInfo {
public boolean hasDescriptionFile() {
return getDescriptionFile() != null;
}
public boolean isShortNameInXml() {
return myShortNameInXml;
}
}
@@ -0,0 +1,231 @@
/*
* Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
package org.jetbrains.idea.devkit.refactoring;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlTag;
import com.intellij.refactoring.rename.UnresolvableCollisionUsageInfo;
import com.intellij.refactoring.rename.naming.AutomaticRenamer;
import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory;
import com.intellij.refactoring.util.NonCodeUsageInfo;
import com.intellij.usageView.UsageInfo;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.devkit.DevKitBundle;
import org.jetbrains.idea.devkit.dom.Extension;
import org.jetbrains.idea.devkit.inspections.DescriptionType;
import org.jetbrains.idea.devkit.inspections.InspectionDescriptionInfo;
import org.jetbrains.idea.devkit.util.PsiUtil;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class InspectionAutomaticRenamerFactory implements AutomaticRenamerFactory {
private static final String PROPERTY_RENAME_DESCRIPTION_AND_SHORT_NAME = "rename.inspection.description.and.short.name";
private static final String INSPECTION_CLASS_SUFFIX = "Inspection";
@Override
public boolean isApplicable(@NotNull PsiElement element) {
if (!(element instanceof PsiClass)) {
return false;
}
if (!PsiUtil.isPluginProject(element.getProject())) {
return false;
}
PsiClass inspectionClass = (PsiClass)element;
String inspectionClassName = inspectionClass.getName();
return inspectionClassName != null &&
inspectionClassName.endsWith(INSPECTION_CLASS_SUFFIX) &&
!isGetShortNameMethodOverridden(inspectionClass) &&
InheritanceUtil.isInheritor(inspectionClass, DescriptionType.INSPECTION.getClassName());
}
@Nls
@Nullable
@Override
public String getOptionName() {
return DevKitBundle.message("inspection.renamer.option.name");
}
@Override
public boolean isEnabled() {
return PropertiesComponent.getInstance().getBoolean(PROPERTY_RENAME_DESCRIPTION_AND_SHORT_NAME, true);
}
@Override
public void setEnabled(boolean enabled) {
PropertiesComponent.getInstance().setValue(PROPERTY_RENAME_DESCRIPTION_AND_SHORT_NAME, enabled);
}
@NotNull
@Override
public AutomaticRenamer createRenamer(PsiElement element, String newName, Collection<UsageInfo> usages) {
return new InspectionAutomaticRenamer((PsiClass)element, newName);
}
/**
* @return inspection description file name without extension.
*/
@NotNull
private static String getDescriptionFileName(String inspectionClassName) {
return StringUtil.trimEnd(inspectionClassName, INSPECTION_CLASS_SUFFIX);
}
@Nullable
private static XmlAttribute getInspectionShortNameAttribute(Module module, PsiClass inspectionClass) {
Extension extension = InspectionDescriptionInfo.findExtension(module, inspectionClass);
if (extension == null) {
return null;
}
XmlTag tag = extension.getXmlTag();
return tag == null ? null : tag.getAttribute("shortName");
}
private static boolean isGetShortNameMethodOverridden(PsiClass inspectionClass) {
Module module = ModuleUtilCore.findModuleForPsiElement(inspectionClass);
if (module == null) {
return false;
}
return InspectionDescriptionInfo.create(module, inspectionClass).getShortNameMethod() != null;
}
private static class InspectionAutomaticRenamer extends AutomaticRenamer {
public InspectionAutomaticRenamer(PsiClass inspectionClass, String newName) {
super();
Module module = ModuleUtilCore.findModuleForPsiElement(inspectionClass);
if (module == null) {
return;
}
InspectionDescriptionInfo descriptionInfo = InspectionDescriptionInfo.create(module, inspectionClass);
if (!descriptionInfo.isShortNameInXml() || descriptionInfo.getShortNameMethod() != null) {
return;
}
PsiFile descriptionFile = descriptionInfo.getDescriptionFile();
if (descriptionFile == null) {
return;
}
String inspectionClassName = inspectionClass.getName();
if (inspectionClassName == null) {
return;
}
String descriptionFileName = descriptionFile.getName();
String defaultShortName = getDescriptionFileName(inspectionClassName);
if (!newName.endsWith(INSPECTION_CLASS_SUFFIX) || !defaultShortName.equals(FileUtil.getNameWithoutExtension(descriptionFileName))) {
return;
}
XmlAttribute shortNameAttribute = getInspectionShortNameAttribute(module, inspectionClass);
if (shortNameAttribute == null) {
return;
}
if (!defaultShortName.equals(shortNameAttribute.getValue())) {
return;
}
VirtualFile descriptionVirtualFile = descriptionFile.getVirtualFile();
if (descriptionVirtualFile == null) {
return;
}
String newDescriptionFileName = getDescriptionFileName(newName);
if (newDescriptionFileName.equals(descriptionFileName)) {
return;
}
String descriptionFileExtension = StringUtil.notNullize(descriptionVirtualFile.getExtension());
myElements.add(descriptionFile);
suggestAllNames(descriptionFileName, newDescriptionFileName + "." + descriptionFileExtension);
}
@Nls
@Override
public String getDialogTitle() {
return DevKitBundle.message("inspection.renamer.dialog.title");
}
@Nls
@Override
public String getDialogDescription() {
return DevKitBundle.message("inspection.renamer.dialog.description");
}
@Override
public String entityName() {
return DevKitBundle.message("inspection.renamer.entity.name");
}
@Override
public void findUsages(List<UsageInfo> result,
boolean searchInStringsAndComments,
boolean searchInNonJavaFiles,
List<UnresolvableCollisionUsageInfo> unresolvedUsages,
Map<PsiElement, String> allRenames) {
super.findUsages(result, searchInStringsAndComments, searchInNonJavaFiles, unresolvedUsages, allRenames);
if (allRenames == null) {
return;
}
for (Map.Entry<PsiElement, String> entry : allRenames.entrySet()) {
PsiElement element = entry.getKey();
if (!(element instanceof PsiClass)) {
continue;
}
Module module = ModuleUtilCore.findModuleForPsiElement(element);
PsiClass inspectionClass = (PsiClass)element;
InspectionDescriptionInfo descriptionInfo = InspectionDescriptionInfo.create(module, inspectionClass);
PsiFile descriptionFile = descriptionInfo.getDescriptionFile();
if (descriptionFile == null) {
continue;
}
XmlAttribute shortNameAttribute = getInspectionShortNameAttribute(module, inspectionClass);
if (shortNameAttribute == null) {
continue;
}
XmlAttributeValue shortNameValue = shortNameAttribute.getValueElement();
if (shortNameValue == null) {
continue;
}
PsiFile pluginXmlFile = shortNameAttribute.getContainingFile();
if (pluginXmlFile == null) {
continue;
}
String newName = getDescriptionFileName(entry.getValue());
TextRange range = shortNameValue.getTextRange();
result.add(NonCodeUsageInfo.create(pluginXmlFile,
range.getStartOffset() + 1, // quotes
range.getEndOffset() - 1,
shortNameValue,
newName));
break;
}
}
}
}
@@ -0,0 +1,2 @@
public class MyInspectionWithSpecificName extends com.intellij.codeInspection.LocalInspectionTool {
}
@@ -0,0 +1,11 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<localInspection language="XML"
shortName="someShortName"
displayName="My Inspection With Specific Name"
groupName="RenameInspectionTestGroupName"
enabledByDefault="true"
level="ERROR"
implementationClass="MyInspectionWithSpecificName"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,11 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<localInspection language="XML"
shortName="someShortName"
displayName="My Inspection With Specific Name"
groupName="RenameInspectionTestGroupName"
enabledByDefault="true"
level="ERROR"
implementationClass="NewMyInspectionWithSpecificName"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,11 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<localInspection language="XML"
shortName="someSpecificShortName"
displayName="My Inspection With Get Short Name"
groupName="RenameInspectionTestGroupName"
enabledByDefault="true"
level="ERROR"
implementationClass="MyInspectionWithGetShortNameInspection"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,6 @@
public class MyInspectionWithGetShortNameInspection extends com.intellij.codeInspection.LocalInspectionTool {
@Override
public String getShortName() {
return "someSpecificShortName";
}
}
@@ -0,0 +1,11 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<localInspection language="XML"
shortName="someSpecificShortName"
displayName="My Inspection With Get Short Name"
groupName="RenameInspectionTestGroupName"
enabledByDefault="true"
level="ERROR"
implementationClass="NewMyInspectionWithGetShortNameInspection"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,11 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<localInspection language="XML"
shortName="MyInspectionWithoutGetShortName"
displayName="My Inspection Without Get Short Name"
groupName="RenameInspectionTestGroupName"
enabledByDefault="true"
level="ERROR"
implementationClass="MyInspectionWithoutGetShortNameInspection"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,3 @@
public class MyInspectionWithoutGetShortNameInspection extends com.intellij.codeInspection.LocalInspectionTool {
}
@@ -0,0 +1,11 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<localInspection language="XML"
shortName="NewMyInspectionWithoutGetShortName"
displayName="My Inspection Without Get Short Name"
groupName="RenameInspectionTestGroupName"
enabledByDefault="true"
level="ERROR"
implementationClass="NewMyInspectionWithoutGetShortNameInspection"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,84 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
package org.jetbrains.idea.devkit.refactoring;
import com.intellij.codeInspection.LocalInspectionEP;
import com.intellij.lang.LanguageExtensionPoint;
import com.intellij.openapi.application.PluginPathManager;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.impl.JavaPsiFacadeEx;
import com.intellij.refactoring.rename.RenameProcessor;
import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory;
import com.intellij.testFramework.TestDataPath;
import com.intellij.testFramework.builders.JavaModuleFixtureBuilder;
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
import com.intellij.ui.components.JBList;
import com.intellij.util.PathUtil;
import com.intellij.util.xmlb.annotations.Attribute;
import java.nio.file.Paths;
@TestDataPath("$CONTENT_ROOT/testData/refactoring/renameInspection")
public class InspectionRenameTest extends JavaCodeInsightFixtureTestCase {
@Override
protected String getBasePath() {
return PluginPathManager.getPluginHomePathRelative("devkit") + "/testData/refactoring/renameInspection";
}
@Override
protected void tuneFixture(JavaModuleFixtureBuilder moduleBuilder) throws Exception {
super.tuneFixture(moduleBuilder);
moduleBuilder.addLibrary("core-api", PathUtil.getJarPathForClass(LanguageExtensionPoint.class));
moduleBuilder.addLibrary("analysis-api", PathUtil.getJarPathForClass(LocalInspectionEP.class));
moduleBuilder.addLibrary("platform-resources", Paths.get(PathUtil.getJarPathForClass(LocalInspectionEP.class))
.resolveSibling("platform-resources").toString());
moduleBuilder.addLibrary("platform-api", PathUtil.getJarPathForClass(JBList.class));
moduleBuilder.addLibrary("util", PathUtil.getJarPathForClass(Attribute.class));
}
public void testRenameInspectionWithoutGetShortName() {
doTestRenameInspection("MyInspectionWithoutGetShortName", "/withoutGetShortName/", "NewMyInspectionWithoutGetShortName.html");
}
public void testRenameInspectionWithGetShortName() {
doTestRenameInspection("MyInspectionWithGetShortName", "/withGetShortName/", "someSpecificShortName.html");
}
public void testRenameNonStandardNamedInspection() {
doTestRenameInspection("MyInspectionWithSpecificName", "MyInspectionWithSpecificName", "/nonStandardNamed/", "someShortName.html");
}
private void doTestRenameInspection(String name, String testDataSubPath, String expectedDescriptionFileName) {
doTestRenameInspection(name, name + "Inspection", testDataSubPath, expectedDescriptionFileName);
}
private void doTestRenameInspection(String name, String inspectionName, String testDataSubPath,
String expectedDescriptionFileName) {
myFixture.configureByFile(testDataSubPath + name + ".xml");
myFixture.copyFileToProject(testDataSubPath + inspectionName + ".java");
myFixture.copyDirectoryToProject(testDataSubPath + "inspectionDescriptions", "inspectionDescriptions");
JavaPsiFacadeEx javaFacade = myFixture.getJavaFacade();
PsiClass inspectionClass = javaFacade.findClass(inspectionName);
assertNotNull(inspectionClass);
RenameProcessor processor = new RenameProcessor(getProject(), inspectionClass, "New" + inspectionName, true, true);
for (AutomaticRenamerFactory factory : Extensions.getExtensions(AutomaticRenamerFactory.EP_NAME)) {
processor.addRenamerFactory(factory);
}
processor.run();
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
FileDocumentManager.getInstance().saveAllDocuments();
myFixture.checkResultByFile(testDataSubPath + name + ".xml", testDataSubPath + name + "_after.xml", false);
VirtualFile descriptionFile = myFixture.findFileInTempDir("inspectionDescriptions/" + expectedDescriptionFileName);
assertNotNull(descriptionFile);
}
}