diff --git a/plugins/devkit/resources/META-INF/plugin.xml b/plugins/devkit/resources/META-INF/plugin.xml index 79c035c854df..ba3f11941f2b 100644 --- a/plugins/devkit/resources/META-INF/plugin.xml +++ b/plugins/devkit/resources/META-INF/plugin.xml @@ -28,6 +28,8 @@ + + diff --git a/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties b/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties index 9f5083dd896e..1a0c957ee972 100644 --- a/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties +++ b/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties @@ -197,3 +197,8 @@ implemented.at.runtime.jamElement=JAM Element implemented at runtime line.marker.tooltip.extension.declaration={2} declaration in {3} line.marker.tooltip.extension.point.declaration={2} 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=Do you want to also rename the inspection description file and change the short name?
Warning: it may break existing inspection profiles and suppression comments. +inspection.renamer.entity.name=Element diff --git a/plugins/devkit/src/inspections/InspectionDescriptionInfo.java b/plugins/devkit/src/inspections/InspectionDescriptionInfo.java index 77c1f34c890d..d91098d7cbf4 100644 --- a/plugins/devkit/src/inspections/InspectionDescriptionInfo.java +++ b/plugins/devkit/src/inspections/InspectionDescriptionInfo.java @@ -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; + } } diff --git a/plugins/devkit/src/refactoring/InspectionAutomaticRenamerFactory.java b/plugins/devkit/src/refactoring/InspectionAutomaticRenamerFactory.java new file mode 100644 index 000000000000..37beda28ebb5 --- /dev/null +++ b/plugins/devkit/src/refactoring/InspectionAutomaticRenamerFactory.java @@ -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 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 result, + boolean searchInStringsAndComments, + boolean searchInNonJavaFiles, + List unresolvedUsages, + Map allRenames) { + super.findUsages(result, searchInStringsAndComments, searchInNonJavaFiles, unresolvedUsages, allRenames); + if (allRenames == null) { + return; + } + + for (Map.Entry 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; + } + } + } +} diff --git a/plugins/devkit/testData/refactoring/renameInspection/nonStandardNamed/MyInspectionWithSpecificName.java b/plugins/devkit/testData/refactoring/renameInspection/nonStandardNamed/MyInspectionWithSpecificName.java new file mode 100644 index 000000000000..f30a8c7ad37c --- /dev/null +++ b/plugins/devkit/testData/refactoring/renameInspection/nonStandardNamed/MyInspectionWithSpecificName.java @@ -0,0 +1,2 @@ +public class MyInspectionWithSpecificName extends com.intellij.codeInspection.LocalInspectionTool { +} \ No newline at end of file diff --git a/plugins/devkit/testData/refactoring/renameInspection/nonStandardNamed/MyInspectionWithSpecificName.xml b/plugins/devkit/testData/refactoring/renameInspection/nonStandardNamed/MyInspectionWithSpecificName.xml new file mode 100644 index 000000000000..220101664031 --- /dev/null +++ b/plugins/devkit/testData/refactoring/renameInspection/nonStandardNamed/MyInspectionWithSpecificName.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/plugins/devkit/testData/refactoring/renameInspection/nonStandardNamed/MyInspectionWithSpecificName_after.xml b/plugins/devkit/testData/refactoring/renameInspection/nonStandardNamed/MyInspectionWithSpecificName_after.xml new file mode 100644 index 000000000000..f0eeeae9176d --- /dev/null +++ b/plugins/devkit/testData/refactoring/renameInspection/nonStandardNamed/MyInspectionWithSpecificName_after.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/plugins/devkit/testData/refactoring/renameInspection/nonStandardNamed/inspectionDescriptions/someShortName.html b/plugins/devkit/testData/refactoring/renameInspection/nonStandardNamed/inspectionDescriptions/someShortName.html new file mode 100644 index 000000000000..ded32df72d9b --- /dev/null +++ b/plugins/devkit/testData/refactoring/renameInspection/nonStandardNamed/inspectionDescriptions/someShortName.html @@ -0,0 +1 @@ +Some description \ No newline at end of file diff --git a/plugins/devkit/testData/refactoring/renameInspection/withGetShortName/MyInspectionWithGetShortName.xml b/plugins/devkit/testData/refactoring/renameInspection/withGetShortName/MyInspectionWithGetShortName.xml new file mode 100644 index 000000000000..4317254b30fd --- /dev/null +++ b/plugins/devkit/testData/refactoring/renameInspection/withGetShortName/MyInspectionWithGetShortName.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/plugins/devkit/testData/refactoring/renameInspection/withGetShortName/MyInspectionWithGetShortNameInspection.java b/plugins/devkit/testData/refactoring/renameInspection/withGetShortName/MyInspectionWithGetShortNameInspection.java new file mode 100644 index 000000000000..a56050c31b1f --- /dev/null +++ b/plugins/devkit/testData/refactoring/renameInspection/withGetShortName/MyInspectionWithGetShortNameInspection.java @@ -0,0 +1,6 @@ +public class MyInspectionWithGetShortNameInspection extends com.intellij.codeInspection.LocalInspectionTool { + @Override + public String getShortName() { + return "someSpecificShortName"; + } +} \ No newline at end of file diff --git a/plugins/devkit/testData/refactoring/renameInspection/withGetShortName/MyInspectionWithGetShortName_after.xml b/plugins/devkit/testData/refactoring/renameInspection/withGetShortName/MyInspectionWithGetShortName_after.xml new file mode 100644 index 000000000000..50c56bdb0c09 --- /dev/null +++ b/plugins/devkit/testData/refactoring/renameInspection/withGetShortName/MyInspectionWithGetShortName_after.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/plugins/devkit/testData/refactoring/renameInspection/withGetShortName/inspectionDescriptions/someSpecificShortName.html b/plugins/devkit/testData/refactoring/renameInspection/withGetShortName/inspectionDescriptions/someSpecificShortName.html new file mode 100644 index 000000000000..ded32df72d9b --- /dev/null +++ b/plugins/devkit/testData/refactoring/renameInspection/withGetShortName/inspectionDescriptions/someSpecificShortName.html @@ -0,0 +1 @@ +Some description \ No newline at end of file diff --git a/plugins/devkit/testData/refactoring/renameInspection/withoutGetShortName/MyInspectionWithoutGetShortName.xml b/plugins/devkit/testData/refactoring/renameInspection/withoutGetShortName/MyInspectionWithoutGetShortName.xml new file mode 100644 index 000000000000..280d396fb5f5 --- /dev/null +++ b/plugins/devkit/testData/refactoring/renameInspection/withoutGetShortName/MyInspectionWithoutGetShortName.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/plugins/devkit/testData/refactoring/renameInspection/withoutGetShortName/MyInspectionWithoutGetShortNameInspection.java b/plugins/devkit/testData/refactoring/renameInspection/withoutGetShortName/MyInspectionWithoutGetShortNameInspection.java new file mode 100644 index 000000000000..dd48468b095b --- /dev/null +++ b/plugins/devkit/testData/refactoring/renameInspection/withoutGetShortName/MyInspectionWithoutGetShortNameInspection.java @@ -0,0 +1,3 @@ +public class MyInspectionWithoutGetShortNameInspection extends com.intellij.codeInspection.LocalInspectionTool { + +} \ No newline at end of file diff --git a/plugins/devkit/testData/refactoring/renameInspection/withoutGetShortName/MyInspectionWithoutGetShortName_after.xml b/plugins/devkit/testData/refactoring/renameInspection/withoutGetShortName/MyInspectionWithoutGetShortName_after.xml new file mode 100644 index 000000000000..30a9fbd98734 --- /dev/null +++ b/plugins/devkit/testData/refactoring/renameInspection/withoutGetShortName/MyInspectionWithoutGetShortName_after.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/plugins/devkit/testData/refactoring/renameInspection/withoutGetShortName/inspectionDescriptions/MyInspectionWithoutGetShortName.html b/plugins/devkit/testData/refactoring/renameInspection/withoutGetShortName/inspectionDescriptions/MyInspectionWithoutGetShortName.html new file mode 100644 index 000000000000..ded32df72d9b --- /dev/null +++ b/plugins/devkit/testData/refactoring/renameInspection/withoutGetShortName/inspectionDescriptions/MyInspectionWithoutGetShortName.html @@ -0,0 +1 @@ +Some description \ No newline at end of file diff --git a/plugins/devkit/testSources/refactoring/InspectionRenameTest.java b/plugins/devkit/testSources/refactoring/InspectionRenameTest.java new file mode 100644 index 000000000000..2a114ed4f3ab --- /dev/null +++ b/plugins/devkit/testSources/refactoring/InspectionRenameTest.java @@ -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); + } +}