From f71925909ede460a53b2dc4974ca39c4cabb7bb2 Mon Sep 17 00:00:00 2001 From: Daniil Ovchinnikov Date: Wed, 14 Feb 2018 17:25:34 +0300 Subject: [PATCH] [groovy] support constant values via field references (IDEA-186646) --- .../lang/psi/impl/GrAnnotationUtil.java | 49 ++++++--------- .../groovy/lang/psi/impl/grAnnotationUtil.kt | 27 ++++---- .../GroovyConstantExpressionEvaluator.java | 24 +++----- .../resolve/ConstantExpressionTest.groovy | 61 +++++++++++++++++++ .../plugins/groovy/util/TestUtils.java | 24 +++----- 5 files changed, 109 insertions(+), 76 deletions(-) create mode 100644 plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ConstantExpressionTest.groovy diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrAnnotationUtil.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrAnnotationUtil.java index f38f64ab4f1c..8f2414a0c323 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrAnnotationUtil.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GrAnnotationUtil.java @@ -1,21 +1,7 @@ -/* - * Copyright 2000-2017 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-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.plugins.groovy.lang.psi.impl; -import com.intellij.openapi.util.text.StringUtil; +import com.intellij.codeInsight.AnnotationUtil; import com.intellij.psi.*; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -27,6 +13,9 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrRefere import java.util.Collections; import java.util.List; +import static org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtilKt.findDeclaredDetachedValue; +import static org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtilKt.getArrayValue; + /** * @author Max Medvedev */ @@ -146,20 +135,18 @@ public class GrAnnotationUtil { } public static List getStringArrayValue(@NotNull PsiAnnotation annotation, @NotNull String attributeName, boolean declared) { - PsiAnnotationMemberValue value = - declared ? annotation.findDeclaredAttributeValue(attributeName) : annotation.findAttributeValue(attributeName); - if (value instanceof PsiArrayInitializerMemberValue) { - return ContainerUtil.mapNotNull(((PsiArrayInitializerMemberValue)value).getInitializers(), memberValue -> { - String string = getString(memberValue); - return StringUtil.isEmpty(string) ? null : string; - }); - } - else { - String string = getString(value); - if (!StringUtil.isEmpty(string)) { - return Collections.singletonList(string); - } - } - return Collections.emptyList(); + PsiAnnotationMemberValue value = findDetachedAttributeValue(annotation, attributeName, declared); + if (value == null) return Collections.emptyList(); + return getArrayValue(value, AnnotationUtil::getStringAttributeValue); + } + + @Nullable + private static PsiAnnotationMemberValue findDetachedAttributeValue(@NotNull PsiAnnotation annotation, + @Nullable String attributeName, + boolean declared) { + PsiAnnotationMemberValue declaredValue = findDeclaredDetachedValue(annotation, attributeName); + if (declaredValue != null) return declaredValue; + if (declared) return null; + return annotation.findAttributeValue(attributeName); } } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/grAnnotationUtil.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/grAnnotationUtil.kt index ff71eabe3ceb..f467c24a7d27 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/grAnnotationUtil.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/grAnnotationUtil.kt @@ -1,23 +1,10 @@ -/* - * Copyright 2000-2017 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-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.plugins.groovy.lang.psi.impl import com.intellij.codeInsight.AnnotationUtil import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiAnnotationMemberValue +import com.intellij.psi.PsiArrayInitializerMemberValue import com.intellij.psi.PsiLiteral import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList @@ -25,6 +12,16 @@ fun PsiAnnotation.findDeclaredDetachedValue(attributeName: String?): PsiAnnotati return AnnotationUtil.findDeclaredAttribute(this, attributeName)?.detachedValue } +internal fun PsiAnnotationMemberValue.getArrayValue(computeValue: (PsiAnnotationMemberValue) -> T?): List { + if (this is PsiArrayInitializerMemberValue) { + return initializers.mapNotNull(computeValue) + } + else { + val value = computeValue(this) ?: return emptyList() + return listOf(value) + } +} + fun PsiAnnotationMemberValue?.booleanValue() = (this as? PsiLiteral)?.value as? Boolean fun PsiAnnotationMemberValue?.stringValue() = (this as? PsiLiteral)?.value as? String diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/GroovyConstantExpressionEvaluator.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/GroovyConstantExpressionEvaluator.java index 710280959490..b066e3053b18 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/GroovyConstantExpressionEvaluator.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/util/GroovyConstantExpressionEvaluator.java @@ -1,26 +1,14 @@ -/* - * Copyright 2000-2014 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-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.plugins.groovy.lang.psi.util; import com.intellij.psi.PsiConstantEvaluationHelper; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiField; import com.intellij.psi.impl.ConstantExpressionEvaluator; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrParenthesizedExpression; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral; /** @@ -36,6 +24,12 @@ public class GroovyConstantExpressionEvaluator implements ConstantExpressionEval if (expression instanceof GrLiteral) { return ((GrLiteral)expression).getValue(); } + if (expression instanceof GrReferenceExpression) { + PsiElement resolved = ((GrReferenceExpression)expression).resolve(); + if (resolved instanceof PsiField) { + return ((PsiField)resolved).computeConstantValue(); + } + } return null; } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ConstantExpressionTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ConstantExpressionTest.groovy new file mode 100644 index 000000000000..623ee29747e0 --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ConstantExpressionTest.groovy @@ -0,0 +1,61 @@ +// 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.plugins.groovy.lang.resolve + +import com.intellij.testFramework.LightProjectDescriptor +import groovy.transform.CompileStatic +import org.jetbrains.plugins.groovy.GroovyLightProjectDescriptor +import org.jetbrains.plugins.groovy.LightGroovyTestCase + +import static org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtil.getStringArrayValue +import static org.jetbrains.plugins.groovy.util.TestUtils.disableAstLoading + +@CompileStatic +class ConstantExpressionTest extends LightGroovyTestCase { + + final LightProjectDescriptor projectDescriptor = GroovyLightProjectDescriptor.GROOVY_LATEST + + private void addJavaAnnotation() { + fixture.addClass '''\ +package com.foo; + +public @interface MyJavaAnnotation { + String[] stringArrayValue() default {}; + String stringValue() default "default string"; +} +''' + } + + private void addJavaConstants() { + fixture.addClass '''\ +package com.foo; + +public interface Constants { + String HELLO = "java hello"; + String WORLD = "java world"; + String COMPOUND = HELLO + " " + WORLD; +} +''' + } + + void 'test annotation value from java'() { + addJavaAnnotation() + addJavaConstants() + fixture.addFileToProject '_.groovy', '''\ +import com.foo.Constants +import com.foo.MyJavaAnnotation + +@MyJavaAnnotation(stringArrayValue = [ + Constants.HELLO, + Constants.WORLD, + Constants.COMPOUND, + "literal" +]) +class GroovyClass {} +''' + disableAstLoading project, testRootDisposable + def clazz = fixture.findClass 'GroovyClass' + def annotation = clazz.modifierList.findAnnotation 'com.foo.MyJavaAnnotation' + def values = getStringArrayValue annotation, 'stringArrayValue', false + assertOrderedEquals(["java hello", "java world", "java hello java world", "literal"], values) + } +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/util/TestUtils.java b/plugins/groovy/test/org/jetbrains/plugins/groovy/util/TestUtils.java index c2b23f7bdf2a..a3ff579038b9 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/util/TestUtils.java +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/util/TestUtils.java @@ -1,36 +1,26 @@ -/* - * Copyright 2000-2017 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-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.plugins.groovy.util; import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.openapi.Disposable; import com.intellij.openapi.application.PluginPathManager; import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFileFilter; import com.intellij.pom.PomDeclarationSearcher; import com.intellij.pom.PomTarget; import com.intellij.psi.*; +import com.intellij.psi.impl.PsiManagerEx; import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture; import com.intellij.util.CollectConsumer; import com.intellij.util.IncorrectOperationException; import com.intellij.util.LocalTimeCounter; import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.groovy.extensions.NamedArgumentDescriptor; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression; @@ -281,4 +271,8 @@ public abstract class TestUtils { //workaround for IDEA-148973: Groovy static compilation fails to compile calls of overriding methods with covariant type in interfaces return aClass.getMethods(); } + + public static void disableAstLoading(@NotNull Project project, @NotNull Disposable parent) { + PsiManagerEx.getInstanceEx(project).setAssertOnFileLoadingFilter(VirtualFileFilter.ALL, parent); + } }