[groovy] @Singleton support:

- implement as AstTransformationSupport instead of gdsl
- add proper members
- support `property` parameter
- support `lazy` parameter
This commit is contained in:
Daniil Ovchinnikov
2016-11-08 18:27:40 +03:00
parent cf125e30ec
commit 8e5f004aed
7 changed files with 198 additions and 19 deletions
@@ -0,0 +1,54 @@
/*
* 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.transformations.singleton
import com.intellij.util.text.nullize
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierFlags.*
import org.jetbrains.plugins.groovy.lang.psi.impl.booleanValue
import org.jetbrains.plugins.groovy.lang.psi.impl.findDeclaredDetachedValue
import org.jetbrains.plugins.groovy.lang.psi.impl.stringValue
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightField
import org.jetbrains.plugins.groovy.transformations.AstTransformationSupport
import org.jetbrains.plugins.groovy.transformations.TransformationContext
import org.jetbrains.plugins.groovy.transformations.plusAssign
class SingletonTransformationSupport : AstTransformationSupport {
override fun applyTransformation(context: TransformationContext) {
val annotation = context.getAnnotation(singletonFqn) ?: return
val name = annotation.findDeclaredDetachedValue("property").stringValue().nullize(true) ?: "instance"
val lazy = annotation.findDeclaredDetachedValue("lazy").booleanValue() ?: false
context += GrLightField(context.codeClass, name, context.classType, annotation).apply {
val modifiers = STATIC_MASK or if (lazy) PRIVATE_MASK else PUBLIC_MASK or FINAL_MASK
modifierList.setModifiers(modifiers)
navigationElement = annotation
originInfo = singletonOriginInfo
}
context += context.memberBuilder.constructor {
setModifiers(PRIVATE_MASK)
originInfo = singletonOriginInfo
}
context += context.memberBuilder.method("get${name.capitalize()}") {
setModifiers(PUBLIC_MASK or STATIC_MASK)
returnType = context.classType
navigationElement = annotation
originInfo = singletonOriginInfo
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* 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.
@@ -13,12 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package standardDsls
package org.jetbrains.plugins.groovy.transformations.singleton
contributor(ctype:hasAnnotation("groovy.lang.Singleton")) {
if (psiClass?.hasAnnotation("groovy.lang.Singleton")) {
property name: "instance",
type: psiClass?.getQualifiedName() ?: "java.lang.Object",
isStatic: true
}
}
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames
internal val singletonFqn = GroovyCommonClassNames.GROOVY_LANG_SINGLETON
internal val singletonOriginInfo = "by @Singleton"
+2
View File
@@ -145,6 +145,8 @@
<astTransformationSupport implementation="org.jetbrains.plugins.groovy.transformations.listenerList.ListenerListTransformationSupport"/>
<customAnnotationChecker implementation="org.jetbrains.plugins.groovy.transformations.listenerList.ListenerListAnnotationChecker"/>
<astTransformationSupport implementation="org.jetbrains.plugins.groovy.transformations.singleton.SingletonTransformationSupport"/>
<positionManagerDelegate implementation="org.jetbrains.plugins.groovy.gant.GantPositionManagerHelper"/>
<methodDescriptor lightMethodKey="SwingBuilder_builder_method"
@@ -54,10 +54,6 @@ class GroovyTransformationsTest extends LightCodeInsightFixtureTestCase {
void testDelegateAnnotation() throws Throwable { doPlainTest() }
void testSingletonTransform() throws Throwable {
doVariantsTest('instance', 'newInstance', 'newInstance', 'isInstance', 'getInstance', 'setInstance')
}
void testCategoryTransform() throws Throwable { doVariantsTest('name', 'getName') }
void testMixinTransform() throws Throwable { doPlainTest() }
@@ -0,0 +1,136 @@
/*
* 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.transformations.singleton
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.codeInspection.untypedUnresolvedAccess.GrUnresolvedAccessInspection
import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyFileImpl
@CompileStatic
class SingletonTransformationSupportTest extends LightGroovyTestCase {
final LightProjectDescriptor projectDescriptor = GroovyLightProjectDescriptor.GROOVY_LATEST
void 'test highlighting'() {
fixture.with {
def file = addFileToProject('singletons.groovy', '''\
@Singleton
class Simple {}
@Singleton(property = "coolInstance")
class CustomName {}
@Singleton(strict = false)
class DefaultConstructor {
DefaultConstructor() {}
}
@Singleton(strict = false)
class CustomConstructor {
CustomConstructor(int a, String b) {}
}
@Singleton(lazy = true)
class Lazy {}
@Singleton(strict = false, lazy = true)
class LazyDefaultConstructor {
LazyDefaultConstructor() {}
}
''') as GroovyFileImpl
configureByText '_.groovy', '''\
Simple.<warning descr="Cannot resolve symbol 'setInstance'">setInstance</warning>(null)
Simple simple
simple = Simple.getInstance()
simple = Simple.instance
simple = new Simple()
CustomName.<warning descr="Cannot resolve symbol 'setCoolInstance'">setCoolInstance</warning>(null)
CustomName customName
customName = CustomName.getCoolInstance()
customName = CustomName.coolInstance
customName = new CustomName()
DefaultConstructor.<warning descr="Cannot resolve symbol 'setInstance'">setInstance</warning>(null)
DefaultConstructor defaultConstructor
defaultConstructor = DefaultConstructor.getInstance()
defaultConstructor = DefaultConstructor.instance
defaultConstructor = new DefaultConstructor()
CustomConstructor customConstructor
customConstructor = CustomConstructor.getInstance()
customConstructor = CustomConstructor.instance
customConstructor = new CustomConstructor()
customConstructor = new CustomConstructor(1, "")
Lazy lazy
lazy = Lazy.getInstance()
lazy = Lazy.instance
lazy = new Lazy()
LazyDefaultConstructor lazyDefaultConstructor
lazyDefaultConstructor = LazyDefaultConstructor.getInstance()
lazyDefaultConstructor = LazyDefaultConstructor.instance
lazyDefaultConstructor = new LazyDefaultConstructor()
'''
enableInspections(GrUnresolvedAccessInspection)
checkHighlighting()
configureByText 'Main.java', '''\
public class Main {
public static void main(String[] args) {
Simple simple;
simple = Simple.getInstance();
simple = Simple.instance;
simple = new <error descr="'Simple()' has private access in 'Simple'">Simple</error>();
CustomName customName;
customName = CustomName.getCoolInstance();
customName = CustomName.coolInstance;
customName = new <error descr="'CustomName()' has private access in 'CustomName'">CustomName</error>();
DefaultConstructor defaultConstructor;
defaultConstructor = DefaultConstructor.getInstance();
defaultConstructor = DefaultConstructor.instance;
defaultConstructor = new DefaultConstructor();
CustomConstructor customConstructor;
customConstructor = CustomConstructor.getInstance();
customConstructor = CustomConstructor.instance;
customConstructor = new <error descr="'CustomConstructor()' has private access in 'CustomConstructor'">CustomConstructor</error>();
customConstructor = new CustomConstructor(1, "");
Lazy lazy;
lazy = Lazy.getInstance();
lazy = Lazy.<error descr="'instance' has private access in 'Lazy'">instance</error>;
lazy = new <error descr="'Lazy()' has private access in 'Lazy'">Lazy</error>();
LazyDefaultConstructor lazyDefaultConstructor;
lazyDefaultConstructor = LazyDefaultConstructor.getInstance();
lazyDefaultConstructor = LazyDefaultConstructor.<error descr="'instance' has private access in 'LazyDefaultConstructor'">instance</error>;
lazyDefaultConstructor = new LazyDefaultConstructor();
}
}
'''
checkHighlighting()
assert !file.contentsLoaded
}
}
}
@@ -1,3 +0,0 @@
@Singleton class T {}
T.inst<caret>
@@ -1,3 +0,0 @@
@Singleton class T {}
T.instance