From 79b508356fed09cd1bd490edefd384e1fbbd8a48 Mon Sep 17 00:00:00 2001 From: Dmitry Avdeev Date: Fri, 20 Jan 2012 14:34:59 +0400 Subject: [PATCH] RegisterInspectionFixProvider --- .../daemon/impl/PostHighlightingPass.java | 11 +- .../UnusedDeclarationFixProvider.java | 35 ++++ plugins/devkit/resources/META-INF/plugin.xml | 3 + plugins/devkit/src/dom/Extension.java | 2 + plugins/devkit/src/dom/Extensions.java | 7 + .../devkit/src/dom/impl/ExtensionImpl.java | 4 +- .../devkit/src/dom/impl/ExtensionsImpl.java | 35 ++++ .../quickfix/RegisterInspectionFix.java | 170 ++++++++++++++++++ .../RegisterInspectionFixProvider.java | 50 ++++++ .../testData/extensions/GlobalInspection.java | 19 ++ .../testData/extensions/LocalInspection.java | 4 + .../testData/extensions/globalInspection.xml | 6 + .../testData/extensions/localInspection.xml | 5 + plugins/devkit/testData/extensions/plugin.xml | 2 + .../codeInsight/CreateExtensionTest.java | 57 ++++++ resources/src/META-INF/IdeaPlugin.xml | 2 + 16 files changed, 410 insertions(+), 2 deletions(-) create mode 100644 platform/lang-api/src/com/intellij/codeInspection/reference/UnusedDeclarationFixProvider.java create mode 100644 plugins/devkit/src/dom/impl/ExtensionsImpl.java create mode 100644 plugins/devkit/src/inspections/quickfix/RegisterInspectionFix.java create mode 100644 plugins/devkit/src/inspections/quickfix/RegisterInspectionFixProvider.java create mode 100644 plugins/devkit/testData/extensions/GlobalInspection.java create mode 100644 plugins/devkit/testData/extensions/LocalInspection.java create mode 100644 plugins/devkit/testData/extensions/globalInspection.xml create mode 100644 plugins/devkit/testData/extensions/localInspection.xml create mode 100644 plugins/devkit/testData/extensions/plugin.xml create mode 100644 plugins/devkit/testSources/codeInsight/CreateExtensionTest.java diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java index 95b91fa3da85..de3dd9049601 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java @@ -37,6 +37,7 @@ import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection; import com.intellij.codeInspection.ex.GlobalInspectionToolWrapper; import com.intellij.codeInspection.ex.InspectionManagerEx; import com.intellij.codeInspection.ex.LocalInspectionToolWrapper; +import com.intellij.codeInspection.reference.UnusedDeclarationFixProvider; import com.intellij.codeInspection.unusedImport.UnusedImportLocalInspection; import com.intellij.codeInspection.unusedParameters.UnusedParametersInspection; import com.intellij.codeInspection.unusedSymbol.UnusedSymbolLocalInspection; @@ -374,7 +375,15 @@ public class PostHighlightingPass extends TextEditorHighlightingPass { } private static HighlightInfo createUnusedSymbolInfo(PsiElement element, String message, final HighlightInfoType highlightInfoType) { - return HighlightInfo.createHighlightInfo(highlightInfoType, element, message); + HighlightInfo info = HighlightInfo.createHighlightInfo(highlightInfoType, element, message); + UnusedDeclarationFixProvider[] fixProviders = Extensions.getExtensions(UnusedDeclarationFixProvider.EP_NAME); + for (UnusedDeclarationFixProvider provider : fixProviders) { + IntentionAction[] fixes = provider.getQuickFixes(element); + for (IntentionAction fix : fixes) { + QuickFixAction.registerQuickFixAction(info, fix); + } + } + return info; } @Nullable diff --git a/platform/lang-api/src/com/intellij/codeInspection/reference/UnusedDeclarationFixProvider.java b/platform/lang-api/src/com/intellij/codeInspection/reference/UnusedDeclarationFixProvider.java new file mode 100644 index 000000000000..d9b88b608559 --- /dev/null +++ b/platform/lang-api/src/com/intellij/codeInspection/reference/UnusedDeclarationFixProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2012 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 com.intellij.codeInspection.reference; + +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; + +/** + * Provides quick fixes for "Unused declaration" inspection + * + * @author Dmitry Avdeev + * Date: 1/19/12 + */ +public interface UnusedDeclarationFixProvider { + + ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.unusedDeclarationFixProvider"); + + @NotNull + IntentionAction[] getQuickFixes(PsiElement unusedElement); +} diff --git a/plugins/devkit/resources/META-INF/plugin.xml b/plugins/devkit/resources/META-INF/plugin.xml index 2eebbbd6fc27..330e83d18fec 100644 --- a/plugins/devkit/resources/META-INF/plugin.xml +++ b/plugins/devkit/resources/META-INF/plugin.xml @@ -24,6 +24,8 @@ implementationClass="org.jetbrains.idea.devkit.dom.impl.IdeaPluginImpl"/> + @@ -73,6 +75,7 @@ + diff --git a/plugins/devkit/src/dom/Extension.java b/plugins/devkit/src/dom/Extension.java index 97b32d1760ff..e7b5d66edc0f 100644 --- a/plugins/devkit/src/dom/Extension.java +++ b/plugins/devkit/src/dom/Extension.java @@ -18,6 +18,7 @@ package org.jetbrains.idea.devkit.dom; import com.intellij.util.xml.DomElement; import com.intellij.util.xml.GenericAttributeValue; import com.intellij.util.xml.NameValue; +import org.jetbrains.annotations.Nullable; /** * @author mike @@ -29,5 +30,6 @@ public interface Extension extends DomElement { GenericAttributeValue getOrder(); + @Nullable ExtensionPoint getExtensionPoint(); } diff --git a/plugins/devkit/src/dom/Extensions.java b/plugins/devkit/src/dom/Extensions.java index e235fd9031e8..6428d351a5b8 100644 --- a/plugins/devkit/src/dom/Extensions.java +++ b/plugins/devkit/src/dom/Extensions.java @@ -26,6 +26,8 @@ import com.intellij.util.xml.GenericAttributeValue; import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.devkit.dom.impl.ExtensionNsConverter; +import java.util.List; + public interface Extensions extends DomElement { @NotNull @Attribute("defaultExtensionNs") @@ -35,4 +37,9 @@ public interface Extensions extends DomElement { @NotNull @Convert(value=ExtensionNsConverter.class, soft=true) GenericAttributeValue getXmlns(); + + List getExtensions(); + Extension addExtension(); + + Extension addExtension(String name); } diff --git a/plugins/devkit/src/dom/impl/ExtensionImpl.java b/plugins/devkit/src/dom/impl/ExtensionImpl.java index 5681f0840294..28ff67f6a504 100644 --- a/plugins/devkit/src/dom/impl/ExtensionImpl.java +++ b/plugins/devkit/src/dom/impl/ExtensionImpl.java @@ -15,6 +15,7 @@ */ package org.jetbrains.idea.devkit.dom.impl; +import com.intellij.pom.PomTarget; import com.intellij.pom.PomTargetPsiElement; import com.intellij.psi.PsiElement; import com.intellij.util.xml.DomTarget; @@ -31,7 +32,8 @@ public abstract class ExtensionImpl implements Extension { public ExtensionPoint getExtensionPoint() { PsiElement declaration = getChildDescription().getDeclaration(getManager().getProject()); if (declaration instanceof PomTargetPsiElement) { - return (ExtensionPoint)((DomTarget)((PomTargetPsiElement)declaration).getTarget()).getDomElement(); + PomTarget target = ((PomTargetPsiElement)declaration).getTarget(); + return target instanceof DomTarget ? (ExtensionPoint)((DomTarget)target).getDomElement() : null; } return null; } diff --git a/plugins/devkit/src/dom/impl/ExtensionsImpl.java b/plugins/devkit/src/dom/impl/ExtensionsImpl.java new file mode 100644 index 000000000000..2de064052e0b --- /dev/null +++ b/plugins/devkit/src/dom/impl/ExtensionsImpl.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2012 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.dom.impl; + +import com.intellij.psi.xml.XmlTag; +import org.jetbrains.idea.devkit.dom.Extension; +import org.jetbrains.idea.devkit.dom.Extensions; + +/** + * @author Dmitry Avdeev + * Date: 1/20/12 + */ +public abstract class ExtensionsImpl implements Extensions { + + @Override + public Extension addExtension(String name) { + Extension extension = addExtension(); + XmlTag tag = extension.getXmlTag(); + tag.setName(name.substring(getDefaultExtensionNs().getStringValue().length() + 1)); + return extension; + } +} diff --git a/plugins/devkit/src/inspections/quickfix/RegisterInspectionFix.java b/plugins/devkit/src/inspections/quickfix/RegisterInspectionFix.java new file mode 100644 index 000000000000..1edffbc87f8e --- /dev/null +++ b/plugins/devkit/src/inspections/quickfix/RegisterInspectionFix.java @@ -0,0 +1,170 @@ +/* + * Copyright 2000-2012 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.quickfix; + +import com.intellij.codeInsight.hint.HintManager; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInspection.InspectionEP; +import com.intellij.ide.TypePresentationService; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.module.impl.ModuleImpl; +import com.intellij.openapi.module.impl.scopes.ModuleWithDependenciesScope; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.PopupStep; +import com.intellij.openapi.ui.popup.util.BaseListPopupStep; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiFile; +import com.intellij.psi.xml.XmlTag; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.PsiNavigateUtil; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.xml.DomFileElement; +import com.intellij.util.xml.DomService; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.devkit.DevKitBundle; +import org.jetbrains.idea.devkit.dom.Extension; +import org.jetbrains.idea.devkit.dom.Extensions; +import org.jetbrains.idea.devkit.dom.IdeaPlugin; + +import javax.swing.*; +import java.util.List; + +/** +* @author Dmitry Avdeev +* Date: 1/20/12 +*/ +class RegisterInspectionFix implements IntentionAction { + + private final PsiClass myPsiClass; + private final ExtensionPointName myEp; + + RegisterInspectionFix(PsiClass psiClass, ExtensionPointName ep) { + myPsiClass = psiClass; + myEp = ep; + } + + @NotNull + @Override + public String getText() { + return "Register inspection"; + } + + @NotNull + @Override + public String getFamilyName() { + return DevKitBundle.message("inspections.component.not.registered.quickfix.family"); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + return true; + } + + @Override + public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException { + Module module = ModuleUtil.findModuleForPsiElement(file); + assert module != null; + List> elements = + DomService.getInstance().getFileElements(IdeaPlugin.class, project, ((ModuleImpl)module).getCachedScope(ModuleWithDependenciesScope.CONTENT | ModuleWithDependenciesScope.MODULES)); + + elements = ContainerUtil.filter(elements, new Condition>() { + @Override + public boolean value(DomFileElement element) { + VirtualFile virtualFile = element.getFile().getVirtualFile(); + return virtualFile != null && ProjectRootManager.getInstance(project).getFileIndex().isInContent(virtualFile); + } + }); + + if (elements.isEmpty()) { + HintManager.getInstance().showErrorHint(editor, "Cannot find plugin descriptor"); + return; + } + + if (elements.size() == 1) { + doFix(elements.get(0), project, file); + return; + } + + final BaseListPopupStep> popupStep = + new BaseListPopupStep>("Choose Plugin Descriptor", elements) { + @Override + public Icon getIconFor(DomFileElement aValue) { + return TypePresentationService.getService().getIcon(aValue); + } + + @NotNull + @Override + public String getTextFor(DomFileElement value) { + return value.getFile().getName(); + } + + @Override + public PopupStep onChosen(DomFileElement selectedValue, boolean finalChoice) { + doFix(selectedValue, project, file); + return FINAL_CHOICE; + } + }; + JBPopupFactory.getInstance().createListPopup(popupStep) + .showInBestPositionFor(editor); + } + + private void doFix(DomFileElement selectedValue, final Project project, final PsiFile file) { + final IdeaPlugin plugin = selectedValue.getRootElement(); + final List extensionsList = plugin.getExtensions(); + Extension extension = new WriteCommandAction(project, file) { + + @Override + protected void run(Result result) throws Throwable { + final Extensions extensions = getExtension(plugin, extensionsList); + Extension extension = extensions.addExtension(myEp.getName()); + XmlTag tag = extension.getXmlTag(); + tag.setAttribute("implementationClass", myPsiClass.getQualifiedName()); + result.setResult(extension); + } + }.execute().throwException().getResultObject(); + PsiNavigateUtil.navigate(extension.getXmlTag()); + } + + private Extensions getExtension(IdeaPlugin plugin, List extensionsList) { + Extensions extensions = null; + for (Extensions e : extensionsList) { + String s = e.getDefaultExtensionNs().getStringValue(); + if (s != null && myEp.getName().startsWith(s)) { + extensions = e; + break; + } + } + if (extensions == null) { + extensions = plugin.addExtensions(); + extensions.getDefaultExtensionNs().setStringValue("com.intellij"); + } + return extensions; + } + + @Override + public boolean startInWriteAction() { + return false; + } +} diff --git a/plugins/devkit/src/inspections/quickfix/RegisterInspectionFixProvider.java b/plugins/devkit/src/inspections/quickfix/RegisterInspectionFixProvider.java new file mode 100644 index 000000000000..c136d2726839 --- /dev/null +++ b/plugins/devkit/src/inspections/quickfix/RegisterInspectionFixProvider.java @@ -0,0 +1,50 @@ +/* + * Copyright 2000-2012 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.quickfix; + +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInspection.GlobalInspectionTool; +import com.intellij.codeInspection.InspectionEP; +import com.intellij.codeInspection.LocalInspectionEP; +import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.codeInspection.reference.UnusedDeclarationFixProvider; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiIdentifier; +import com.intellij.psi.util.InheritanceUtil; +import org.jetbrains.annotations.NotNull; + +/** + * @author Dmitry Avdeev + * Date: 1/19/12 + */ +public class RegisterInspectionFixProvider implements UnusedDeclarationFixProvider { + + @NotNull + @Override + public IntentionAction[] getQuickFixes(PsiElement element) { + if (!(element instanceof PsiIdentifier)) return IntentionAction.EMPTY_ARRAY; + PsiElement parent = element.getParent(); + if (!(parent instanceof PsiClass)) return IntentionAction.EMPTY_ARRAY; + if (InheritanceUtil.isInheritor((PsiClass)parent, LocalInspectionTool.class.getName())) { + return new IntentionAction[] { new RegisterInspectionFix((PsiClass)parent, LocalInspectionEP.LOCAL_INSPECTION) }; + } + if (InheritanceUtil.isInheritor((PsiClass)parent, GlobalInspectionTool.class.getName())) { + return new IntentionAction[] { new RegisterInspectionFix((PsiClass)parent, InspectionEP.GLOBAL_INSPECTION) }; + } + return IntentionAction.EMPTY_ARRAY; + } +} diff --git a/plugins/devkit/testData/extensions/GlobalInspection.java b/plugins/devkit/testData/extensions/GlobalInspection.java new file mode 100644 index 000000000000..9fe0a4ab4742 --- /dev/null +++ b/plugins/devkit/testData/extensions/GlobalInspection.java @@ -0,0 +1,19 @@ +/* + * Copyright 2000-2012 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. + */ +import com.intellij.codeInspection.GlobalInspectionTool; + +public class GlobalInspection extends GlobalInspectionTool { +} \ No newline at end of file diff --git a/plugins/devkit/testData/extensions/LocalInspection.java b/plugins/devkit/testData/extensions/LocalInspection.java new file mode 100644 index 000000000000..a08a6c33e499 --- /dev/null +++ b/plugins/devkit/testData/extensions/LocalInspection.java @@ -0,0 +1,4 @@ +import com.intellij.codeInspection.LocalInspectionTool; + +public class LocalInspection extends LocalInspectionTool { +} \ No newline at end of file diff --git a/plugins/devkit/testData/extensions/globalInspection.xml b/plugins/devkit/testData/extensions/globalInspection.xml new file mode 100644 index 000000000000..9f649860b310 --- /dev/null +++ b/plugins/devkit/testData/extensions/globalInspection.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/plugins/devkit/testData/extensions/localInspection.xml b/plugins/devkit/testData/extensions/localInspection.xml new file mode 100644 index 000000000000..b14a539ad067 --- /dev/null +++ b/plugins/devkit/testData/extensions/localInspection.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/plugins/devkit/testData/extensions/plugin.xml b/plugins/devkit/testData/extensions/plugin.xml new file mode 100644 index 000000000000..6fb76a66265d --- /dev/null +++ b/plugins/devkit/testData/extensions/plugin.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/plugins/devkit/testSources/codeInsight/CreateExtensionTest.java b/plugins/devkit/testSources/codeInsight/CreateExtensionTest.java new file mode 100644 index 000000000000..41d5b96b2706 --- /dev/null +++ b/plugins/devkit/testSources/codeInsight/CreateExtensionTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2000-2012 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.codeInsight; + +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection; +import com.intellij.codeInspection.unusedSymbol.UnusedSymbolLocalInspection; +import com.intellij.openapi.application.PluginPathManager; +import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase; + +/** + * @author Dmitry Avdeev + * Date: 1/20/12 + */ +public class CreateExtensionTest extends JavaCodeInsightFixtureTestCase { + + @Override + public void setUp() throws Exception { + super.setUp(); + myFixture.addClass("package com.intellij.codeInspection; public class LocalInspectionTool {} "); + myFixture.addClass("package com.intellij.codeInspection; public class GlobalInspectionTool {} "); + myFixture.enableInspections(new UnusedDeclarationInspection(), new UnusedSymbolLocalInspection()); + } + + public void testCreateLocalInspectionMapping() throws Exception { + myFixture.testHighlighting("LocalInspection.java", "plugin.xml"); + IntentionAction intention = myFixture.findSingleIntention("Register inspection"); + myFixture.launchAction(intention); + myFixture.checkResultByFile("plugin.xml", "localInspection.xml", true); + } + + public void testCreateGlobalInspectionMapping() throws Exception { + myFixture.testHighlighting("GlobalInspection.java", "localInspection.xml"); + IntentionAction intention = myFixture.findSingleIntention("Register inspection"); + myFixture.launchAction(intention); + myFixture.checkResultByFile("localInspection.xml", "globalInspection.xml", true); + } + + @Override + protected String getBasePath() { + return PluginPathManager.getPluginHomePathRelative("devkit") + "/testData/extensions"; + } + +} diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 270dff6a41b6..bec0cf45f755 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -62,6 +62,8 @@ +