diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/auxiliary/modifiers/modifiers.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/auxiliary/modifiers/modifiers.kt index 1cfe0b93bc95..42411d26615a 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/auxiliary/modifiers/modifiers.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/auxiliary/modifiers/modifiers.kt @@ -19,26 +19,32 @@ package org.jetbrains.plugins.groovy.lang.psi.impl.auxiliary.modifiers +import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiModifier import com.intellij.psi.util.CachedValueProvider.Result import com.intellij.psi.util.CachedValuesManager import com.intellij.psi.util.PsiModificationTracker -import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.annotations.NonNls import org.jetbrains.plugins.groovy.config.GroovyConfigUtils import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifier.GrModifierConstant import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierFlags.* import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList +import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation +import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationArrayInitializer import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinitionBody import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrEnumConstant import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil import org.jetbrains.plugins.groovy.lang.psi.impl.auxiliary.modifiers.GrModifierListImpl.NAME_TO_MODIFIER_FLAG_MAP +import org.jetbrains.plugins.groovy.lang.psi.impl.findDeclaredDetachedValue import org.jetbrains.plugins.groovy.lang.psi.util.GrTraitUtil private val explicitVisibilityModifiers = PUBLIC_MASK or PRIVATE_MASK or PROTECTED_MASK +private val packageScopeAnno = "groovy.transform.PackageScope" +private val packageScopeTarget = "groovy.transform.PackageScopeTarget" fun Int.hasMaskModifier(@GrModifierConstant @NonNls name: String): Boolean { return and(NAME_TO_MODIFIER_FLAG_MAP[name]) != 0 @@ -66,11 +72,9 @@ private fun GrModifierList.doGetModifierFlags(): Int { doGetTypeDefinitionFlags(owner, this) } else { - val parent = PsiTreeUtil.getStubOrPsiParent(owner) as? GrTypeDefinitionBody - val containingClass = parent?.parent as? GrTypeDefinition when (owner) { - is GrMethod -> doGetMethodFlags(containingClass, this) - is GrVariableDeclaration -> doGetVariableModifierMask(containingClass, this) + is GrMethod -> doGetMethodFlags(owner, this) + is GrVariableDeclaration -> doGetVariableModifierMask(owner, this) else -> 0 } } @@ -94,47 +98,93 @@ private fun doGetTypeDefinitionFlags(clazz: GrTypeDefinition, modifierList: GrMo } } - val containingClass = clazz.containingClass + val containingClass = clazz.containingClass as? GrTypeDefinition if (GrTraitUtil.isInterface(containingClass)) { flags = flags or STATIC_MASK } if (!modifierList.hasExplicitVisibilityModifiers()) { - flags = flags or PUBLIC_MASK + flags = if (modifierList.hasPackageScope(clazz, "CLASS")) { + flags or PACKAGE_LOCAL_MASK + } + else { + flags or PUBLIC_MASK + } } return flags } -private fun doGetMethodFlags(clazz: GrTypeDefinition?, modifierList: GrModifierList): Int { +private fun doGetMethodFlags(method: GrMethod, modifierList: GrModifierList): Int { var flags = 0 - if (clazz != null) { - if (clazz.isInterface) flags = flags or ABSTRACT_MASK // groovy interface or trait - if (GrTraitUtil.isInterface(clazz)) flags = flags or PUBLIC_MASK // groovy interface + val containingClass = method.containingClass as? GrTypeDefinition + if (containingClass != null) { + if (containingClass.isInterface) flags = flags or ABSTRACT_MASK // groovy interface or trait + if (GrTraitUtil.isInterface(containingClass)) flags = flags or PUBLIC_MASK // groovy interface } if (!modifierList.hasExplicitVisibilityModifiers()) { - flags = flags or PUBLIC_MASK + val targetName = if (method.isConstructor) "CONSTRUCTORS" else "METHODS" + flags = if (modifierList.hasPackageScope(containingClass, targetName)) { + flags or PACKAGE_LOCAL_MASK + } + else { + flags or PUBLIC_MASK + } } return flags } -private fun doGetVariableModifierMask(clazz: GrTypeDefinition?, modifierList: GrModifierList): Int { +private fun doGetVariableModifierMask(variableDeclaration: GrVariableDeclaration, modifierList: GrModifierList): Int { var flags = 0 - if (clazz != null) { - if (GrTraitUtil.isInterface(clazz)) { + val containingClass = (variableDeclaration.parent as? GrTypeDefinitionBody)?.parent as? GrTypeDefinition + + if (containingClass != null) { + if (GrTraitUtil.isInterface(containingClass)) { flags = flags or STATIC_MASK or FINAL_MASK or PUBLIC_MASK } else if (!modifierList.hasExplicitVisibilityModifiers()) { - flags = flags or PRIVATE_MASK - if (clazz.modifierList?.let { PsiImplUtil.hasImmutableAnnotation(it) } == true) { + flags = if (modifierList.hasPackageScope(containingClass, "FIELDS")) { + flags or PACKAGE_LOCAL_MASK + } + else { + flags or PRIVATE_MASK + } + + if (containingClass.modifierList?.let { PsiImplUtil.hasImmutableAnnotation(it) } == true) { flags = flags or FINAL_MASK } } } return flags +} + +private fun GrModifierList.hasPackageScope(clazz: GrTypeDefinition?, targetName: String): Boolean { + if (hasOwnEmptyPackageScopeAnnotation()) return true + + val annotation = clazz?.modifierList?.findAnnotation(packageScopeAnno) as? GrAnnotation ?: return false + val value = annotation.findDeclaredDetachedValue(null) ?: return false // annotation without value + + val scopeTargetEnum = JavaPsiFacade.getInstance(project).findClass(packageScopeTarget, resolveScope) ?: return false + val scopeTarget = scopeTargetEnum.findFieldByName(targetName, false) ?: return false + + val resolved = when (value) { + is GrReferenceExpression -> value.resolve()?.let { listOf(it) } ?: emptyList() + is GrAnnotationArrayInitializer -> value.initializers.mapNotNull { + (it as? GrReferenceExpression)?.resolve() + } + else -> emptyList() + } + + return scopeTarget in resolved +} + +private fun GrModifierList.hasOwnEmptyPackageScopeAnnotation(): Boolean { + val annotation = findAnnotation(packageScopeAnno) ?: return false + val value = annotation.findDeclaredDetachedValue(null) ?: return true + return value is GrAnnotationArrayInitializer && value.initializers.isEmpty() } \ No newline at end of file diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/GrReferenceResolveRunner.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/GrReferenceResolveRunner.kt index 1999ddf047a4..fcb9e70d383d 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/GrReferenceResolveRunner.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/GrReferenceResolveRunner.kt @@ -21,6 +21,7 @@ import com.intellij.psi.util.InheritanceUtil import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes import org.jetbrains.plugins.groovy.lang.psi.api.SpreadState +import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationArrayInitializer import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationNameValuePair import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall @@ -35,7 +36,9 @@ import org.jetbrains.plugins.groovy.lang.resolve.processors.ClassHint class GrReferenceResolveRunner(val place: GrReferenceExpression, val processor: PsiScopeProcessor) { fun resolveReferenceExpression(): Boolean { - val processNonCode = PsiTreeUtil.skipParentsOfType(place, GrReferenceExpression::class.java) !is GrAnnotationNameValuePair + val processNonCode = PsiTreeUtil.skipParentsOfType( + place, GrReferenceExpression::class.java, GrAnnotationArrayInitializer::class.java + ) !is GrAnnotationNameValuePair val initialState = initialState(processNonCode) val qualifier = place.qualifier if (qualifier == null) { diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/modifiers/GrClassVisibilityTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/modifiers/GrClassVisibilityTest.groovy new file mode 100644 index 000000000000..8e8aa08ec2b4 --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/modifiers/GrClassVisibilityTest.groovy @@ -0,0 +1,98 @@ +/* + * Copyright 2000-2016 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.plugins.groovy.lang.resolve.modifiers + +import groovy.transform.CompileStatic + +import static com.intellij.psi.PsiModifier.PACKAGE_LOCAL +import static com.intellij.psi.PsiModifier.PUBLIC + +@CompileStatic +class GrClassVisibilityTest extends GrVisibilityTestBase { + + void 'test @PackageScope'() { + def clazz = addClass('''\ +@PackageScope +class A {} +''') + assertVisibility clazz, PACKAGE_LOCAL + } + + void 'test @PackageScope with explicit visibility'() { + def clazz = addClass('''\ +@PackageScope +public class A {} +''') + assertVisibility clazz, PUBLIC + } + + void 'test @PackageScope empty value'() { + def clazz = addClass('''\ +@PackageScope(value = []) +class A {} +''') + assertVisibility clazz, PACKAGE_LOCAL + } + + void 'test @PackageScope CLASS value'() { + def clazz = addClass('''\ +@PackageScope(value = [CLASS]) +class A {} +''') + assertVisibility clazz, PACKAGE_LOCAL + } + + void 'test @PackageScope non CLASS value'() { + def clazz = addClass('''\ +@PackageScope(value = [FIELDS]) +class A {} +''') + assertVisibility clazz, PUBLIC + } + + void 'test @PackageScope inner class'() { + def clazz = addClass('''\ +@PackageScope() +class A { + class Inner {} +} +''') + def inner = clazz.innerClasses.first() + assertVisibility inner, PUBLIC + } + + void 'test @PackageScope CLASS value inner class'() { + def clazz = addClass('''\ +@PackageScope(CLASS) +class A { + class Inner {} +} +''') + def inner = clazz.innerClasses.first() + assertVisibility inner, PUBLIC + } + + void 'test inner class @PackageScope'() { + def clazz = addClass('''\ +class A { + @PackageScope + class Inner {} +} +''') + def inner = clazz.innerClasses.first() + assertVisibility inner, PACKAGE_LOCAL + } +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/modifiers/GrConstructorVisibilityTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/modifiers/GrConstructorVisibilityTest.groovy new file mode 100644 index 000000000000..71c23d80d004 --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/modifiers/GrConstructorVisibilityTest.groovy @@ -0,0 +1,79 @@ +/* + * Copyright 2000-2016 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.plugins.groovy.lang.resolve.modifiers + +import groovy.transform.CompileStatic + +import static com.intellij.psi.PsiModifier.* + +@CompileStatic +class GrConstructorVisibilityTest extends GrVisibilityTestBase { + + void 'test @PackageScope'() { + def clazz = addClass('''\ +class A { + @PackageScope + A() {} +} +''') + def constructor = clazz.methods.first() + assertVisibility constructor, PACKAGE_LOCAL + } + + void 'test @PackageScope with explicit visibility'() { + def clazz = addClass('''\ +class A { + @PackageScope + private A() {} +} +''') + def constructor = clazz.methods.first() + assertVisibility constructor, PRIVATE + } + + void 'test @PackageScope non empty'() { + def clazz = addClass('''\ +class A { + @PackageScope([METHODS]) + A() {} +} +''') + def constructor = clazz.methods.first() + assertVisibility constructor, PUBLIC + } + + void 'test @PackageScope on class'() { + def clazz = addClass('''\ +@PackageScope([CONSTRUCTORS]) +class A { + A() {} +} +''') + def constructor = clazz.methods.first() + assertVisibility constructor, PACKAGE_LOCAL + } + + void 'test @PackageScope on class without CONSTRUCTORS'() { + def clazz = addClass('''\ +@PackageScope +class A { + A() {} +} +''') + def constructor = clazz.methods.first() + assertVisibility constructor, PUBLIC + } +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/modifiers/GrFieldVisibilityTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/modifiers/GrFieldVisibilityTest.groovy new file mode 100644 index 000000000000..4ca0e9edeb7a --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/modifiers/GrFieldVisibilityTest.groovy @@ -0,0 +1,89 @@ +/* + * Copyright 2000-2016 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.plugins.groovy.lang.resolve.modifiers + +import groovy.transform.CompileStatic + +import static com.intellij.psi.PsiModifier.* + +@CompileStatic +class GrFieldVisibilityTest extends GrVisibilityTestBase { + + void 'test property field'() { + def clazz = addClass('''\ +class A { + def foo +} +''') + def field = clazz.fields.first() + assertVisibility field, PRIVATE + } + + void 'test @PackageScope'() { + def clazz = addClass('''\ +class A { + @PackageScope + def foo +} +''') + def field = clazz.fields.first() + assertVisibility field, PACKAGE_LOCAL + } + + void 'test @PackageScope with explicit visibility'() { + def clazz = addClass('''\ +class A { + @PackageScope + protected foo +} +''') + def field = clazz.fields.first() + assertVisibility field, PROTECTED + } + + void 'test @PackageScope non empty'() { + def clazz = addClass('''\ +class A { + @PackageScope([FIELDS]) + def foo +} +''') + def field = clazz.fields.first() + assertVisibility field, PRIVATE + } + + void 'test @PackageScope on class'() { + def clazz = addClass('''\ +@PackageScope([FIELDS]) +class A { + def foo +} +''') + def field = clazz.fields.first() + assertVisibility field, PACKAGE_LOCAL + } + + void 'test @PackageScope on class without FIELDS'() { + def clazz = addClass('''\ +@PackageScope +class A { + def foo +} +''') + def field = clazz.fields.first() + assertVisibility field, PRIVATE + } +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/modifiers/GrMethodVisiblityTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/modifiers/GrMethodVisiblityTest.groovy new file mode 100644 index 000000000000..e01e51f12d16 --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/modifiers/GrMethodVisiblityTest.groovy @@ -0,0 +1,79 @@ +/* + * Copyright 2000-2016 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.plugins.groovy.lang.resolve.modifiers + +import groovy.transform.CompileStatic + +import static com.intellij.psi.PsiModifier.* + +@CompileStatic +class GrMethodVisiblityTest extends GrVisibilityTestBase { + + void 'test @PackageScope'() { + def clazz = addClass('''\ +class A { + @PackageScope + def foo() {} +} +''') + def method = clazz.methods.first() + assertVisibility method, PACKAGE_LOCAL + } + + void 'test @PackageScope with explicit visibility'() { + def clazz = addClass('''\ +class A { + @PackageScope + protected foo() {} +} +''') + def method = clazz.methods.first() + assertVisibility method, PROTECTED + } + + void 'test @PackageScope non empty'() { + def clazz = addClass('''\ +class A { + @PackageScope([METHODS]) + def foo() {} +} +''') + def method = clazz.methods.first() + assertVisibility method, PUBLIC + } + + void 'test @PackageScope on class'() { + def clazz = addClass('''\ +@PackageScope([METHODS]) +class A { + def foo() {} +} +''') + def method = clazz.methods.first() + assertVisibility method, PACKAGE_LOCAL + } + + void 'test @PackageScope on class without METHODS'() { + def clazz = addClass('''\ +@PackageScope +class A { + def foo() {} +} +''') + def method = clazz.methods.first() + assertVisibility method, PUBLIC + } +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/modifiers/GrVisibilityTestBase.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/modifiers/GrVisibilityTestBase.groovy new file mode 100644 index 000000000000..6abfadee5faf --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/modifiers/GrVisibilityTestBase.groovy @@ -0,0 +1,55 @@ +/* + * Copyright 2000-2016 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.plugins.groovy.lang.resolve.modifiers + +import com.intellij.extapi.psi.PsiFileBase +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiModifierListOwner +import com.intellij.testFramework.LightProjectDescriptor +import groovy.transform.CompileStatic +import org.jetbrains.plugins.groovy.GroovyLightProjectDescriptor +import org.jetbrains.plugins.groovy.LightGroovyTestCase +import org.jetbrains.plugins.groovy.lang.psi.GroovyFile + +import static com.intellij.psi.PsiModifier.* + +@CompileStatic +abstract class GrVisibilityTestBase extends LightGroovyTestCase { + + private static final String[] VISIBILITY_MODIFIERS = [PUBLIC, PRIVATE, PROTECTED, PACKAGE_LOCAL] + + final LightProjectDescriptor projectDescriptor = GroovyLightProjectDescriptor.GROOVY_LATEST + + protected PsiClass addClass(String packageName = "pckg", String text) { + def file = fixture.addFileToProject("$packageName/_.groovy", """\ +package $packageName +import groovy.transform.PackageScope +import static groovy.transform.PackageScopeTarget.* + +$text +""") as GroovyFile + file.typeDefinitions.first() + } + + protected static void assertVisibility(PsiModifierListOwner listOwner, String modifier) { + assert listOwner.hasModifierProperty(modifier) + (VISIBILITY_MODIFIERS - modifier).each { + assert listOwner.hasModifierProperty(modifier) + } + def file = listOwner.containingFile as PsiFileBase + assert !file.contentsLoaded + } +}