IDEA-242833 EP attribute marked with com.intellij.openapi.extensions.RequiredElement needs ability to specify nonEmpty=false

GitOrigin-RevId: 7a1bf8484fc6069041e34d20d3c4245d3eb4eda2
This commit is contained in:
Yann Cébron
2020-09-23 09:11:48 +00:00
committed by intellij-monorepo-bot
parent 16344fc69a
commit 1f38e71591
10 changed files with 86 additions and 19 deletions
@@ -17,7 +17,7 @@ public final class PsiSymbolReferenceProviderBean extends CustomLoadingExtension
* The references will be provided for the specified language and its {@linkplain Language#getBaseLanguage() base languages}.
*/
@Attribute
@RequiredElement
@RequiredElement(allowEmpty = true)
public String hostLanguage;
/**
@@ -1,4 +1,4 @@
// 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.
// Copyright 2000-2020 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 com.intellij.openapi.extensions;
import com.intellij.util.xmlb.annotations.Attribute;
@@ -16,4 +16,9 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
public @interface RequiredElement {
/**
* @return {@code true} if the specified property value can be empty.
*/
boolean allowEmpty() default false;
}
@@ -550,6 +550,7 @@ inspections.IncorrectParentDisposableInspection.do.not.use.as.disposable=Don''t
inspections.IncorrectParentDisposableInspection.documentation.link.title=Choosing a Disposable Parent
extension.point.documentation.field.required.suffix=(required)
extension.point.documentation.field.required.can.be.empty.suffix=(required, empty allowed)
extension.point.documentation.implementation.section=Extension Point Implementation
gutter.related.option.description=Description
@@ -77,7 +77,7 @@ public class ExtensionDomExtender extends DomExtender<Extension> {
binding.visit(new ExtensionPointBinding.BindingVisitor() {
@Override
public void visitAttribute(@NotNull PsiField field, @NotNull @NonNls String attributeName, boolean required) {
public void visitAttribute(@NotNull PsiField field, @NotNull @NonNls String attributeName, RequiredFlag required) {
final With withElement = findWithElement(elements, field);
final PsiType fieldType = field.getType();
Class<?> clazz = String.class;
@@ -122,7 +122,7 @@ public class ExtensionDomExtender extends DomExtender<Extension> {
}
@Override
public void visitTagOrProperty(@NotNull PsiField field, @NotNull String tagName, boolean required) {
public void visitTagOrProperty(@NotNull PsiField field, @NotNull String tagName, RequiredFlag required) {
final DomExtension extension =
registrar.registerFixedNumberChildExtension(new XmlName(tagName), SimpleTagValue.class)
.setDeclaringElement(field);
@@ -139,7 +139,7 @@ public class ExtensionDomExtender extends DomExtender<Extension> {
public void visitXCollection(@NotNull PsiField field,
@Nullable String tagName,
@NotNull PsiAnnotation collectionAnnotation,
boolean required) {
RequiredFlag required) {
if (tagName == null) {
registerCollectionBinding(field, registrar, collectionAnnotation, required);
return;
@@ -181,14 +181,19 @@ public class ExtensionDomExtender extends DomExtender<Extension> {
}
}
private static void markAsRequired(DomExtension extension, boolean required) {
if (required) extension.addCustomAnnotation(MyRequired.INSTANCE);
private static void markAsRequired(DomExtension extension, ExtensionPointBinding.BindingVisitor.RequiredFlag required) {
if (required == ExtensionPointBinding.BindingVisitor.RequiredFlag.REQUIRED) {
extension.addCustomAnnotation(MyRequired.INSTANCE);
}
else if (required == ExtensionPointBinding.BindingVisitor.RequiredFlag.REQUIRED_ALLOW_EMPTY) {
extension.addCustomAnnotation(MyRequiredCanBeEmpty.INSTANCE);
}
}
private static void registerCollectionBinding(PsiField field,
DomExtensionsRegistrar registrar,
PsiAnnotation collectionAnnotation,
boolean required) {
ExtensionPointBinding.BindingVisitor.RequiredFlag required) {
final boolean surroundWithTag = PsiUtil.getAnnotationBooleanAttribute(collectionAnnotation, "surroundWithTag");
if (surroundWithTag) return; // todo Set, List, Array
@@ -292,6 +297,32 @@ public class ExtensionDomExtender extends DomExtender<Extension> {
}
}
@SuppressWarnings("ClassExplicitlyAnnotation")
private static class MyRequiredCanBeEmpty implements Required {
private static final MyRequiredCanBeEmpty INSTANCE = new MyRequiredCanBeEmpty();
@Override
public boolean value() {
return true;
}
@Override
public boolean nonEmpty() {
return false;
}
@Override
public boolean identifier() {
return false;
}
@Override
public Class<? extends Annotation> annotationType() {
return Required.class;
}
}
private static final class MyImplementationExtendClass extends ExtendClassImpl {
private final String myInterfaceName;
@@ -40,7 +40,13 @@ public class ExtensionPointBinding {
final PsiMethod setter = PropertyUtilBase.findSetterForField(field);
if ((getter == null || setter == null) && !field.hasModifierProperty(PsiModifier.PUBLIC)) continue;
boolean required = PsiUtil.findAnnotation(RequiredElement.class, field, getter, setter) != null;
final PsiAnnotation requiredAnnotation = PsiUtil.findAnnotation(RequiredElement.class, field, getter, setter);
BindingVisitor.RequiredFlag required = BindingVisitor.RequiredFlag.NOT_REQUIRED;
if (requiredAnnotation != null) {
required = PsiUtil.getAnnotationBooleanAttribute(requiredAnnotation, "allowEmpty") ?
BindingVisitor.RequiredFlag.REQUIRED_ALLOW_EMPTY : BindingVisitor.RequiredFlag.REQUIRED;
}
final PsiAnnotation attributeAnnotation = PsiUtil.findAnnotation(Attribute.class, field, getter, setter);
if (attributeAnnotation != null) {
String fieldName = PsiUtil.getAnnotationStringAttribute(attributeAnnotation, "value", field.getName());
@@ -75,10 +81,19 @@ public class ExtensionPointBinding {
public interface BindingVisitor {
void visitAttribute(@NotNull PsiField field, @NotNull @NonNls String attributeName, boolean required);
enum RequiredFlag {
NOT_REQUIRED,
REQUIRED,
REQUIRED_ALLOW_EMPTY
}
void visitTagOrProperty(@NotNull PsiField field, @NotNull @NonNls String tagName, boolean required);
void visitAttribute(@NotNull PsiField field, @NotNull @NonNls String attributeName, RequiredFlag required);
void visitXCollection(@NotNull PsiField field, @Nullable @NonNls String tagName, @NotNull PsiAnnotation collectionAnnotation, boolean required);
void visitTagOrProperty(@NotNull PsiField field, @NotNull @NonNls String tagName, RequiredFlag required);
void visitXCollection(@NotNull PsiField field,
@Nullable @NonNls String tagName,
@NotNull PsiAnnotation collectionAnnotation,
RequiredFlag required);
}
}
@@ -75,12 +75,12 @@ public class ExtensionPointDocumentationProvider implements DocumentationProvide
new ExtensionPointBinding(beanClass).visit(new ExtensionPointBinding.BindingVisitor() {
@Override
public void visitAttribute(@NotNull PsiField field, @NotNull String attributeName, boolean required) {
public void visitAttribute(@NotNull PsiField field, @NotNull String attributeName, RequiredFlag required) {
appendFieldBindingText(field, attributeName, required);
}
@Override
public void visitTagOrProperty(@NotNull PsiField field, @NotNull String tagName, boolean required) {
public void visitTagOrProperty(@NotNull PsiField field, @NotNull String tagName, RequiredFlag required) {
visitAttribute(field, "<" + tagName + ">", required);
}
@@ -88,15 +88,21 @@ public class ExtensionPointDocumentationProvider implements DocumentationProvide
public void visitXCollection(@NotNull PsiField field,
@Nullable String tagName,
@NotNull PsiAnnotation collectionAnnotation,
boolean required) {
RequiredFlag required) {
visitAttribute(field, "<" + tagName + ">...", required);
}
private void appendFieldBindingText(@NotNull PsiField field, @NotNull @NlsSafe String displayName, boolean required) {
private void appendFieldBindingText(@NotNull PsiField field, @NotNull @NlsSafe String displayName, RequiredFlag required) {
HtmlChunk hyperLink = createLink(JavaDocUtil.getReferenceText(field.getProject(), field), displayName);
final String typeText = field.getType().getPresentableText();
final String requiredText = required ? " " + DevKitBundle.message("extension.point.documentation.field.required.suffix") : "";
String requiredText = "";
if (required == RequiredFlag.REQUIRED) {
requiredText = " " + DevKitBundle.message("extension.point.documentation.field.required.suffix");
}
else if (required == RequiredFlag.REQUIRED_ALLOW_EMPTY) {
requiredText = " " + DevKitBundle.message("extension.point.documentation.field.required.can.be.empty.suffix");
}
final String initializer = field.getInitializer() != null ? " = " + field.getInitializer().getText() : "";
bindingRows.append(createSectionRow(hyperLink, typeText + requiredText + initializer));
}
@@ -46,6 +46,9 @@
forClass="<error descr="Cannot resolve symbol 'INVALID_VALUE'">INVALID_VALUE</error>"
/>
<myService serviceImplementation="foo.MyRunnable"
canBeEmptyString=""/>
<myPlugin.foo.bar implementation="java.util.ArrayList" os="<error descr="Unknown enum value 'INVALID_VALUE'">INVALID_VALUE</error>"/>
<completion.contributor/>
<custom.custom/>
@@ -13,6 +13,10 @@ public class MyExtensionPoint {
@Attribute
public String implementationClass;
@RequiredElement(allowEmpty = true)
@Attribute
public String stringCanBeEmpty;
@Tag
public Integer intValue;
}
@@ -1,5 +1,5 @@
/*
* 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.
* Copyright 2000-2020 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
@@ -143,11 +143,13 @@ class PluginXmlFunctionalTest extends JavaCodeInsightFixtureTestCase {
"}")
myFixture.addClass("package foo; " +
"import com.intellij.util.xmlb.annotations.Attribute; " +
"import com.intellij.openapi.extensions.RequiredElement; " +
"public class MyServiceDescriptor { " +
" @Attribute public String serviceImplementation; " +
" @Attribute public java.util.concurrent.TimeUnit timeUnit; " +
" @Attribute public java.lang.Integer integerNullable; " +
" @Attribute public int intPropertyForClass; " +
" @Attribute @RequiredElement(allowEmpty=true) public String canBeEmptyString; " +
" @Attribute public boolean forClass; " +
"}")
@@ -52,7 +52,7 @@ public class ExtensionPointDocumentationProviderTest extends LightJavaCodeInsigh
"<div class='definition'><pre>bar<br>public class <b>MyExtensionPoint</b>\n" +
"extends <a href=\"psi_element://java.lang.Object\"><code>Object</code></a></pre></div><div class='content'>\n" +
" MyExtensionPoint JavaDoc.\n" +
" </div><table class='sections'><p></table><table class=\"sections\"><tr><td class=\"section\" valign=\"top\"><p><a href=\"psi_element://bar.MyExtensionPoint#implementationClass\"><code>implementationClass</code></a></p></td><td valign=\"top\">String (required)</td></tr><tr><td class=\"section\" valign=\"top\"><p><a href=\"psi_element://bar.MyExtensionPoint#intValue\"><code>&lt;intValue&gt;</code></a></p></td><td valign=\"top\">Integer</td></tr><br/></table></pre></div><div class=\"content\"><h2>Extension Point Implementation</h2><div class='definition'><pre>bar<br>public interface <b>MyExtension</b></pre></div><div class='content'>\n" +
" </div><table class='sections'><p></table><table class=\"sections\"><tr><td class=\"section\" valign=\"top\"><p><a href=\"psi_element://bar.MyExtensionPoint#implementationClass\"><code>implementationClass</code></a></p></td><td valign=\"top\">String (required)</td></tr><tr><td class=\"section\" valign=\"top\"><p><a href=\"psi_element://bar.MyExtensionPoint#stringCanBeEmpty\"><code>stringCanBeEmpty</code></a></p></td><td valign=\"top\">String (required, empty allowed)</td></tr><tr><td class=\"section\" valign=\"top\"><p><a href=\"psi_element://bar.MyExtensionPoint#intValue\"><code>&lt;intValue&gt;</code></a></p></td><td valign=\"top\">Integer</td></tr><br/></table></pre></div><div class=\"content\"><h2>Extension Point Implementation</h2><div class='definition'><pre>bar<br>public interface <b>MyExtension</b></pre></div><div class='content'>\n" +
" My Extension Javadoc.\n" +
" </div><table class='sections'><p></table></div>",
provider.generateDoc(docElement, getOriginalElement()));