[devkit] plugin.xml: new inspection "Dynamic Plugin verification" (IDEA-228484)

GitOrigin-RevId: d4ef7c4de7348af14435eeee93929d6ce5defd56
This commit is contained in:
Yann Cébron
2019-12-06 15:18:06 +01:00
committed by intellij-monorepo-bot
parent 1566d4df15
commit d603ec12fe
6 changed files with 221 additions and 0 deletions

View File

@@ -92,6 +92,11 @@
groupPath="Plugin DevKit" groupKey="inspections.group.descriptor"
enabledByDefault="true" level="WARNING"
implementationClass="org.jetbrains.idea.devkit.inspections.PluginXmlCapitalizationInspection"/>
<localInspection language="XML" displayName="Plugin.xml dynamic plugin verification" applyToDialects="false"
groupPath="Plugin DevKit" groupKey="inspections.group.descriptor"
enabledByDefault="false" level="WARNING"
implementationClass="org.jetbrains.idea.devkit.inspections.PluginXmlDynamicPluginInspection"/>
<localInspection language="JVM" shortName="ComponentNotRegistered"
groupPath="Plugin DevKit"
key="inspections.component.not.registered.name" groupKey="inspections.group.code" enabledByDefault="true"

View File

@@ -0,0 +1,9 @@
<html>
<body>
Reports problems preventing dynamic plugin.
<p>
Dynamic plugins can be installed, updated and uninstalled without restarting the IDE (supported in 2020.1 and later).
</p>
<p><small>New in 2020.1</small>
</body>
</html>

View File

@@ -0,0 +1,126 @@
// Copyright 2000-2019 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.inspections;
import com.intellij.codeInspection.IntentionAndQuickFixAction;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile;
import com.intellij.util.xml.DomElement;
import com.intellij.util.xml.DomUtil;
import com.intellij.util.xml.highlighting.AddDomElementQuickFix;
import com.intellij.util.xml.highlighting.BasicDomElementsInspection;
import com.intellij.util.xml.highlighting.DomElementAnnotationHolder;
import com.intellij.util.xml.highlighting.DomHighlightingHelper;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.devkit.dom.*;
import javax.swing.*;
import java.util.Objects;
public class PluginXmlDynamicPluginInspection extends BasicDomElementsInspection<IdeaPlugin> {
public boolean highlightNonDynamicEPUsages = false;
public PluginXmlDynamicPluginInspection() {
super(IdeaPlugin.class);
}
@Nullable
@Override
public JComponent createOptionsPanel() {
return new SingleCheckboxOptionsPanel("Highlight usage of non-dynamic extension points", this, "highlightNonDynamicEPUsages");
}
@Override
protected void checkDomElement(DomElement element, DomElementAnnotationHolder holder, DomHighlightingHelper helper) {
if (element instanceof ApplicationComponents ||
element instanceof ProjectComponents ||
element instanceof ModuleComponents) {
highlightComponents(holder, element);
}
else if (element instanceof ExtensionPoint) {
highlightExtensionPoint(holder, ((ExtensionPoint)element));
}
else if (element instanceof Group) {
highlightGroup(holder, (Group)element);
}
else if (highlightNonDynamicEPUsages && element instanceof Extension) {
highlightExtension(holder, ((Extension)element));
}
}
private static void highlightComponents(DomElementAnnotationHolder holder, DomElement component) {
holder.createProblem(component, ProblemHighlightType.LIKE_DEPRECATED,
"<html>Replace Components with " +
"<a href=\"http://www.jetbrains.org/intellij/sdk/docs/basics/plugin_structure/plugin_components.html\">alternatives</a></html>",
null).highlightWholeElement();
}
private static void highlightExtensionPoint(DomElementAnnotationHolder holder, ExtensionPoint extensionPoint) {
if (!DomUtil.hasXml(extensionPoint.getDynamic())) {
final LocalQuickFix[] fixes = holder.isOnTheFly() ? new LocalQuickFix[]{
createAnalyzeEPFix("AnalyzeEPUsage", extensionPoint),
createAnalyzeEPFix("AnalyzeEPUsageIgnoreSafeClasses", extensionPoint)
} : LocalQuickFix.EMPTY_ARRAY;
holder.createProblem(extensionPoint, "Non-dynamic extension point '" + extensionPoint.getEffectiveQualifiedName() + "'",
fixes);
}
else if (Boolean.FALSE == extensionPoint.getDynamic().getValue()) {
holder.createProblem(extensionPoint, "Explicit non-dynamic extension point '" + extensionPoint.getEffectiveQualifiedName() + "'");
}
}
private static IntentionAndQuickFixAction createAnalyzeEPFix(String actionId, ExtensionPoint extensionPoint) {
final AnAction action = ActionManager.getInstance().getAction(actionId);
assert action != null : actionId;
return new IntentionAndQuickFixAction() {
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getName() {
return action.getTemplateText() + " for '" + extensionPoint.getEffectiveQualifiedName() + "'";
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getFamilyName() {
return Objects.requireNonNull(action.getTemplateText());
}
@Override
public void applyFix(@NotNull Project project, PsiFile file, @Nullable Editor editor) {
assert editor != null;
action.actionPerformed(AnActionEvent.createFromDataContext(ActionPlaces.UNKNOWN, null, ((EditorEx)editor).getDataContext()));
}
};
}
private static void highlightGroup(DomElementAnnotationHolder holder, Group group) {
if (!DomUtil.hasXml(group.getId())) {
holder.createProblem(group, "'id' must be specified for <group>", new AddDomElementQuickFix<>(group.getId()));
}
}
private static void highlightExtension(DomElementAnnotationHolder holder, Extension extension) {
final ExtensionPoint extensionPoint = extension.getExtensionPoint();
if (extensionPoint != null && Boolean.TRUE != extensionPoint.getDynamic().getValue()) {
holder.createProblem(extension, "Usage of non-dynamic extension point '" + extensionPoint.getEffectiveQualifiedName() + "'");
}
}
}

View File

@@ -0,0 +1,36 @@
<idea-plugin>
<id>myPlugin</id>
<extensionPoints>
<extensionPoint name="dynamicEP" dynamic="true"/>
<<warning descr="Non-dynamic extension point 'myPlugin.nonDynamicEP'">extensionPoint</warning> name="nonDynamicEP"/>
<<warning descr="Explicit non-dynamic extension point 'myPlugin.explicitNonDynamicEP'">extensionPoint</warning> name="explicitNonDynamicEP" dynamic="false"/>
</extensionPoints>
<extensions defaultExtensionNs="myPlugin">
<dynamicEP/>
<nonDynamicEP/>
<explicitNonDynamicEP/>
</extensions>
<actions>
<group id="requiredIdIsPresent"/>
<<warning descr="'id' must be specified for <group>">group</warning>>
<separator/>
<reference ref="requiredIdIsPresent"/>
</group>
</actions>
<warning descr="Replace Components with alternatives"><application-components>
</application-components></warning>
<warning descr="Replace Components with alternatives"><project-components>
</project-components></warning>
<warning descr="Replace Components with alternatives"><module-components>
</module-components></warning>
</idea-plugin>

View File

@@ -0,0 +1,17 @@
<idea-plugin>
<id>myPlugin</id>
<extensionPoints>
<extensionPoint name="dynamicEP" dynamic="true"/>
<<warning descr="Non-dynamic extension point 'myPlugin.nonDynamicEP'">extensionPoint</warning> name="nonDynamicEP"/>
<<warning descr="Explicit non-dynamic extension point 'myPlugin.explicitNonDynamicEP'">extensionPoint</warning> name="explicitNonDynamicEP" dynamic="false"/>
</extensionPoints>
<extensions defaultExtensionNs="myPlugin">
<dynamicEP/>
<<warning descr="Usage of non-dynamic extension point 'myPlugin.nonDynamicEP'">nonDynamicEP</warning>/>
<<warning descr="Usage of non-dynamic extension point 'myPlugin.explicitNonDynamicEP'">explicitNonDynamicEP</warning>/>
</extensions>
</idea-plugin>

View File

@@ -0,0 +1,28 @@
// Copyright 2000-2019 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.codeInsight;
import com.intellij.testFramework.TestDataPath;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.jetbrains.idea.devkit.DevkitJavaTestsUtil;
import org.jetbrains.idea.devkit.inspections.PluginXmlDynamicPluginInspection;
@TestDataPath("$CONTENT_ROOT/testData/codeInsight/pluginXmlDynamicPluginInspection")
public class PluginXmlDynamicPluginInspectionTest extends LightJavaCodeInsightFixtureTestCase {
@Override
protected String getBasePath() {
return DevkitJavaTestsUtil.TESTDATA_PATH + "codeInsight/pluginXmlDynamicPluginInspection";
}
public void testHighlighting() {
myFixture.enableInspections(new PluginXmlDynamicPluginInspection());
myFixture.testHighlighting("pluginXmlDynamicPluginInspection-highlighting.xml");
}
public void testUsingExtensionPoints() {
final PluginXmlDynamicPluginInspection inspection = new PluginXmlDynamicPluginInspection();
inspection.highlightNonDynamicEPUsages = true;
myFixture.enableInspections(inspection);
myFixture.testHighlighting("pluginXmlDynamicPluginInspection-usingExtensionPoints.xml");
}
}