IDEA-297798 DevKit: new inspection to highlight missing "language" declaration in extension point registration, use exceptions for "any language" value for select EPs

GitOrigin-RevId: 88cf39c413f79b9c5df5779ab7793f0504b3af36
This commit is contained in:
Yann Cébron
2022-09-14 11:15:38 +00:00
committed by intellij-monorepo-bot
parent 181c26a8b8
commit 241ea1120e
7 changed files with 82 additions and 46 deletions
@@ -3,12 +3,15 @@
Reports problems with extension registration in <code>plugin.xml</code>.
<p>
The following problems are reported:
<ul>
<li>Missing <code>language</code> declaration.
If the extension does not target a specific language, specify
<code>language=""</code> or <code>&lt;language>&lt;/language></code> explicitly for "any language".</li>
<li><code>com.intellij.stubElementTypeHolder</code> without <code>externalIdPrefix</code>, see <a href='https://plugins.jetbrains.com/docs/intellij/stub-indexes.html?from=?from=DevkitPluginXmlInspectionDescription'>Stub Indexes</a></li>
</ul>
<ul>
<li>Missing <code>language</code> declaration.
If the extension does not target a specific language, use quick fix to create an
explicit declaration for "any language".
</li>
<li><code>com.intellij.stubElementTypeHolder</code> without <code>externalIdPrefix</code>, see <a
href='https://plugins.jetbrains.com/docs/intellij/stub-indexes.html?from=?from=DevkitPluginXmlInspectionDescription'>Stub Indexes</a>
</li>
</ul>
</p>
<p><small>New in 2022.3</small>
</body>
@@ -43,7 +43,7 @@ import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.StreamSupport;
final class LanguageResolvingUtil {
public final class LanguageResolvingUtil {
private static final String ANY_LANGUAGE_DEFAULT_ID = Language.ANY.getID();
static Collection<LanguageDefinition> getAllLanguageDefinitions(ConvertContext context) {
@@ -333,6 +333,14 @@ final class LanguageResolvingUtil {
return ANY_LANGUAGE_DEFAULT_ID;
}
return getAnyLanguageValue(extensionPoint);
}
/**
* @return value for "any language" for given EP {@code language} declaration, with few exceptions it's always {@link Language#ANY} ID.
*/
@NotNull
public static String getAnyLanguageValue(ExtensionPoint extensionPoint) {
final GenericAttributeValue<PsiClass> epBeanClass = extensionPoint.getBeanClass();
if (EP_WITH_ANY_LANGUAGE_ID.contains(epBeanClass.getStringValue())) {
return "any";
@@ -26,6 +26,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.devkit.DevKitBundle;
import org.jetbrains.idea.devkit.dom.Extension;
import org.jetbrains.idea.devkit.dom.ExtensionPoint;
import org.jetbrains.idea.devkit.dom.impl.LanguageResolvingUtil;
public class PluginXmlExtensionRegistrationInspection extends DevKitPluginXmlInspectionBase {
@@ -59,7 +60,7 @@ public class PluginXmlExtensionRegistrationInspection extends DevKitPluginXmlIns
holder.createProblem(extension,
DevKitBundle.message("inspection.plugin.xml.extension.registration.should.define.language.attribute",
extensionPoint.getEffectiveQualifiedName()),
new DefineAttributeQuickFix("language"));
new DefineAttributeQuickFix("language", "", LanguageResolvingUtil.getAnyLanguageValue(extensionPoint)));
}
return;
}
@@ -72,27 +73,7 @@ public class PluginXmlExtensionRegistrationInspection extends DevKitPluginXmlIns
holder.createProblem(extension,
DevKitBundle.message("inspection.plugin.xml.extension.registration.should.define.language.tag",
extensionPoint.getEffectiveQualifiedName()),
new LocalQuickFix() {
@Override
public @NotNull String getFamilyName() {
return DevKitBundle.message("inspection.plugin.xml.extension.registration.should.define.language.tag.family.name");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
Extension fixExtension = DomUtil.findDomElement(descriptor.getPsiElement(), Extension.class, false);
if (fixExtension == null) return;
XmlTag xmlTag = fixExtension.getXmlTag();
XmlTag languageTag = xmlTag.createChildTag("language", null, "", false);
XmlTag addedLanguageTag = xmlTag.addSubTag(languageTag, true);
if (!IntentionPreviewUtils.isPreviewElement(addedLanguageTag)) {
PsiNavigationSupport.getInstance()
.createNavigatable(project, addedLanguageTag.getContainingFile().getVirtualFile(),
addedLanguageTag.getValue().getTextRange().getEndOffset()).navigate(true);
}
}
});
new AddLanguageTagQuickFix(LanguageResolvingUtil.getAnyLanguageValue(extensionPoint)));
}
}
}
@@ -105,4 +86,33 @@ public class PluginXmlExtensionRegistrationInspection extends DevKitPluginXmlIns
IntentionActionBean.class.getName().equals(extensionBeanClass) ||
InheritanceUtil.isInheritor(extensionPoint.getBeanClass().getValue(), LanguageExtensionPoint.class.getName());
}
private static class AddLanguageTagQuickFix implements LocalQuickFix {
private final String myAnyLanguageID;
private AddLanguageTagQuickFix(String anyLanguageId) {
myAnyLanguageID = anyLanguageId;
}
@Override
public @NotNull String getFamilyName() {
return DevKitBundle.message("inspection.plugin.xml.extension.registration.should.define.language.tag.family.name");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
Extension fixExtension = DomUtil.findDomElement(descriptor.getPsiElement(), Extension.class, false);
if (fixExtension == null) return;
XmlTag xmlTag = fixExtension.getXmlTag();
XmlTag languageTag = xmlTag.createChildTag("language", null, myAnyLanguageID, false);
XmlTag addedLanguageTag = xmlTag.addSubTag(languageTag, true);
if (!IntentionPreviewUtils.isPreviewElement(addedLanguageTag)) {
PsiNavigationSupport.getInstance()
.createNavigatable(project, addedLanguageTag.getContainingFile().getVirtualFile(),
addedLanguageTag.getValue().getTextRange().getEndOffset()).navigate(true);
}
}
}
}
@@ -0,0 +1,6 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<completion.<caret>contributor />
</extensions>
</idea-plugin>
@@ -0,0 +1,6 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<completion.contributor language="<caret>any"/>
</extensions>
</idea-plugin>
@@ -54,6 +54,17 @@ public class PluginXmlExtensionRegistrationInspectionTest extends JavaCodeInsigh
myFixture.checkResultByFile("languageAddLanguageTagFix_after.xml");
}
public void testLanguageAddLanguageAttributeForCompletionContributorEPFix() {
IntentionAction action =
myFixture.getAvailableIntention("Define language attribute",
"addLanguageAttributeForCompletionContributorEPFix.xml");
assertNotNull(action);
myFixture.checkPreviewAndLaunchAction(action);
myFixture.checkResultByFile("addLanguageAttributeForCompletionContributorEPFix_after.xml");
}
public void testStubElementTypeHolder() {
myFixture.testHighlighting("stubElementTypeHolder.xml");
}
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2013 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.
*/
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.util.xml.highlighting;
import com.intellij.codeInspection.LocalQuickFix;
@@ -30,15 +16,21 @@ import org.jetbrains.annotations.NotNull;
*/
public class DefineAttributeQuickFix implements LocalQuickFix {
private final String myAttrName;
private final String myAttrValue;
private final String myNamespace;
public DefineAttributeQuickFix(String attrName) {
this(attrName, "");
this(attrName, "", "");
}
public DefineAttributeQuickFix(@NotNull final String attrName, @NotNull String namespace) {
this(attrName, namespace, "");
}
public DefineAttributeQuickFix(@NotNull final String attrName, @NotNull String namespace, @NotNull String attrValue) {
myAttrName = attrName;
myNamespace = namespace;
myAttrValue = attrValue;
}
@Override
@@ -56,7 +48,7 @@ public class DefineAttributeQuickFix implements LocalQuickFix {
@Override
public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
XmlTag tag = (XmlTag)descriptor.getPsiElement();
XmlAttribute attribute = tag.setAttribute(myAttrName, myNamespace.equals(tag.getNamespace())? "": myNamespace, "");
XmlAttribute attribute = tag.setAttribute(myAttrName, myNamespace.equals(tag.getNamespace()) ? "" : myNamespace, myAttrValue);
VirtualFile virtualFile = tag.getContainingFile().getVirtualFile();
if (virtualFile != null) {
PsiNavigationSupport.getInstance().createNavigatable(project, virtualFile,