[groovy] add support for @PackageScope (IDEA-165816)

This commit is contained in:
Daniil Ovchinnikov
2016-12-24 01:03:06 +03:00
parent a811557816
commit a49857848d
7 changed files with 471 additions and 18 deletions
@@ -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()
}
@@ -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) {
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}