Skip to content

Commit

Permalink
Fix for #1528: DSLD: excludes, includes, deprecated, interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-milles committed Jan 8, 2024
1 parent e6ea331 commit 1f1716d
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.codehaus.groovy.ast.expr.*

import static org.apache.groovy.util.BeanUtils.capitalize
import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName
import static org.codehaus.groovy.transform.AbstractASTTransformation.getMemberStringList
import static org.codehaus.groovy.transform.NamedVariantASTTransformation.NAMED_PARAM_TYPE

@Field static final ClassNode NAMED_PARAMS_TYPE = ClassHelper.makeWithoutCaching(NamedParams)
Expand All @@ -45,8 +46,21 @@ contribute(currentType(annos: annotatedBy(Category))) {
//@groovy.lang.Delegate
contribute(currentType(fields: fields(annotatedBy(Delegate)))) {
provider = '{@link groovy.lang.Delegate Delegate} AST transform'
for (FieldNode field : fields) {
delegatesTo type: field.type
for (FieldNode field : fields) { AnnotationNode node = field.getAnnotations(new ClassNode(Delegate))[0]
List<String> excludes = getMemberStringList(node, 'excludes')
List<String> includes = getMemberStringList(node, 'includes')
if (!excludes && includes) {
excludes = field.type.methods*.name - includes // inverse
}

Boolean deprecated = node.getMember('deprecated')?.value
Boolean interfaces = node.getMember('interfaces')?.value
if (!deprecated && (!field.type.isInterface() || Boolean.FALSE.equals(interfaces))) {
def names = field.type.methods.findAll(org.eclipse.jdt.groovy.core.util.GroovyUtils.&isDeprecated)*.name
if (names) excludes = (excludes ? excludes + names : names) // TODO: What if name is deprecated and not?
}

delegatesTo type: field.type, except: excludes
}
}

Expand Down Expand Up @@ -106,7 +120,7 @@ contribute(enclosingClass(annos: annotatedBy(Newify)) | enclosingField(annos: an
def resolveNewifyType = { String name ->
if (name.split('\\.')[-1].matches(expr.text)) {
ClassNode type = currentType.module.context.resolver.resolve(name)
if (type != ClassHelper.DYNAMIC_TYPE) {
if (!ClassHelper.isDynamicTyped(type)) {
addNewifyMethods(type)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.codehaus.groovy.ast.expr.*

import static org.apache.groovy.util.BeanUtils.capitalize
import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName
import static org.codehaus.groovy.transform.AbstractASTTransformation.getMemberStringList
import static org.codehaus.groovy.transform.NamedVariantASTTransformation.NAMED_PARAM_TYPE

@Field static final ClassNode NAMED_PARAMS_TYPE = ClassHelper.makeWithoutCaching(NamedParams)
Expand All @@ -45,8 +46,21 @@ contribute(currentType(annos: annotatedBy(Category))) {
//@groovy.lang.Delegate
contribute(currentType(fields: fields(annotatedBy(Delegate)))) {
provider = '{@link groovy.lang.Delegate Delegate} AST transform'
for (FieldNode field : fields) {
delegatesTo type: field.type
for (FieldNode field : fields) { AnnotationNode node = field.getAnnotations(new ClassNode(Delegate))[0]
List<String> excludes = getMemberStringList(node, 'excludes')
List<String> includes = getMemberStringList(node, 'includes')
if (!excludes && includes) {
excludes = field.type.methods*.name - includes // inverse
}

Boolean deprecated = node.getMember('deprecated')?.value
Boolean interfaces = node.getMember('interfaces')?.value
if (!deprecated && (!field.type.isInterface() || Boolean.FALSE.equals(interfaces))) {
def names = field.type.methods.findAll(org.eclipse.jdt.groovy.core.util.GroovyUtils.&isDeprecated)*.name
if (names) excludes = (excludes ? excludes + names : names) // TODO: What if name is deprecated and not?
}

delegatesTo type: field.type, except: excludes
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.codehaus.groovy.ast.expr.*

import static org.apache.groovy.util.BeanUtils.capitalize
import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName
import static org.codehaus.groovy.transform.AbstractASTTransformation.getMemberStringList

// http://groovy-lang.org/metaprogramming.html#_available_ast_transformations

Expand All @@ -42,8 +43,21 @@ contribute(currentType(annos: annotatedBy(Category))) {
//@groovy.lang.Delegate
contribute(currentType(fields: fields(annotatedBy(Delegate)))) {
provider = '{@link groovy.lang.Delegate Delegate} AST transform'
for (FieldNode field : fields) {
delegatesTo type: field.type
for (FieldNode field : fields) { AnnotationNode node = field.getAnnotations(new ClassNode(Delegate))[0]
List<String> excludes = getMemberStringList(node, 'excludes')
List<String> includes = getMemberStringList(node, 'includes')
if (!excludes && includes) {
excludes = field.type.methods*.name - includes // inverse
}

Boolean deprecated = node.getMember('deprecated')?.value
Boolean interfaces = node.getMember('interfaces')?.value
if (!deprecated && (!field.type.isInterface() || Boolean.FALSE.equals(interfaces))) {
def names = field.type.methods.findAll(org.eclipse.jdt.groovy.core.util.GroovyUtils.&isDeprecated)*.name
if (names) excludes = (excludes ? excludes + names : names) // TODO: What if name is deprecated and not?
}

delegatesTo type: field.type, except: excludes
}
}

Expand Down Expand Up @@ -218,8 +232,8 @@ contribute(currentType(meths: methods(hasArgument(annotatedBy(NamedParam))) | ha
}

Map<String, ?> requiredParams = [:], optionalParams = [:]
for (AnnotationNode anno : parm.getAnnotations(ClassHelper.make(NamedParam)) ?:
parm.getAnnotations(ClassHelper.make(NamedParams)).collectMany { it.getMember('value').expressions*.value }) {
for (AnnotationNode anno : parm.getAnnotations(new ClassNode(NamedParam)) ?:
parm.getAnnotations(new ClassNode(NamedParams)).collectMany { it.getMember('value').expressions*.value }) {
String name = anno.getMember('value').value
def type = anno.getMember('type')?.type
if (type == null) NamedParam.getMethod('type').defaultValue
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2009-2022 the original author or authors.
* Copyright 2009-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -61,8 +61,7 @@ final class BuiltInDSLInferencingTests extends DSLInferencingTestSuite {
}

assert pluginEntry != null : "Did not find the Plugin DSLD classpath entry. Exsting resolved roots: [\n${ -> elements.join(', ')}\n]\nOther DSLD fragments: [\n${ -> possibleFrags.join('\n')}\n]"
assert root != null : "Plugin DSLD classpath entry should exist. Exsting resolved roots: [\n${ -> elements.join(', ')}\n]\nOther DSLD fragments: [\n${ -> possibleFrags.join('\n')}\n]"
assert root.exists() : 'Plugin DSLD classpath entry should exist'
assert root != null && root.exists() : "Plugin DSLD classpath entry should exist. Exsting resolved roots: [\n${ -> elements.join(', ')}\n]\nOther DSLD fragments: [\n${ -> possibleFrags.join('\n')}\n]"

root.resource().refreshLocal(IResource.DEPTH_INFINITE, null)
root.close(); root.open(null)
Expand Down Expand Up @@ -90,10 +89,57 @@ final class BuiltInDSLInferencingTests extends DSLInferencingTestSuite {
@Test
void testDelegate2() {
String contents = '''\
|class Bar {
|class Foo {
| @Delegate List<Integer> list
|}
|new Foo().spliterator() // default method of List
|'''.stripMargin()

inferType(contents, 'spliterator').with {
assert result.extraDoc?.replace('}', '') =~ 'Delegate AST transform'
assert declaringTypeName == 'java.util.List<java.lang.Integer>'
assert typeName == 'java.util.Spliterator<java.lang.Integer>'
}
}

@Test
void testDelegate3() {
String contents = '''\
|class Foo {
| @Delegate List<Integer> list
|}
|new Foo().stream() // default method of Collection
|'''.stripMargin()

inferType(contents, 'stream').with {
assert result.extraDoc?.replace('}', '') =~ 'Delegate AST transform'
assert declaringTypeName == 'java.util.List<java.lang.Integer>'
assert typeName == 'java.util.stream.Stream<java.lang.Integer>'
}
}

@Test
void testDelegate4() {
String contents = '''\
|class Foo {
| @Delegate List<Integer> list
|}
|new Foo()./**/equals(null) // method of List and Object
|'''.stripMargin()

inferType(contents, 'equals').with {
assert declaringTypeName == 'java.lang.Object'
assert result.extraDoc == null
}
}

@Test
void testDelegate5() {
String contents = '''\
|class Foo {
| @Delegate URL url
|}
|new Bar().file
|new Foo().file // getFile() as property
|'''.stripMargin()

inferType(contents, 'file').with {
Expand All @@ -104,7 +150,7 @@ final class BuiltInDSLInferencingTests extends DSLInferencingTestSuite {
}

@Test // GROOVY-5204
void testDelegate3() {
void testDelegate6() {
String contents = '''\
|class Bar {
| def baz() {}
Expand All @@ -123,7 +169,7 @@ final class BuiltInDSLInferencingTests extends DSLInferencingTestSuite {
}

@Test // GROOVY-5204
void testDelegate4() {
void testDelegate7() {
String contents = '''\
|class Bar {
| def baz() {}
Expand All @@ -143,14 +189,14 @@ final class BuiltInDSLInferencingTests extends DSLInferencingTestSuite {
}

@Test // GROOVY-3917
void testDelegate5() {
void testDelegate8() {
String contents = '''\
|class Bar {
|}
|class Foo {
| @Delegate Bar bar = new Bar()
|}
|new Foo().getProperty('baz')
|new Foo().getProperty('baz') // method of GroovyObject
|'''.stripMargin()

inferType(contents, 'getProperty').with {
Expand All @@ -159,6 +205,109 @@ final class BuiltInDSLInferencingTests extends DSLInferencingTestSuite {
}
}

@Test // GROOVY-8164
void testDelegate9() {
String contents = '''\
|class Bar {
| def baz
|}
|class Foo {
| @Delegate Comparator<Bar> cmp
|}
|new Foo().comparing(Bar.&getBaz) // static method of Comparator
|'''.stripMargin()

inferType(contents, 'comparing').with {
assert result.confidence.name() == 'UNKNOWN'
}
}

@Test
void testDelegate10() {
String contents = '''\
|class Bar {
| def baz
|}
|class Foo {
| @Delegate(excludes=['compare','equals']) Comparator<Bar> cmp
|}
|new Foo().compare(null, null)
|'''.stripMargin()

inferType(contents, 'compare').with {
assert result.confidence.name() == 'UNKNOWN'
}
}

@Test
void testDelegate11() {
String contents = '''\
|class Bar {
| def baz
|}
|class Foo {
| @Delegate(includes='compare') Comparator<Bar> cmp
|}
|new Foo().compare(null, null)
|'''.stripMargin()

inferType(contents, 'compare').with {
assert result.extraDoc?.replace('}', '') =~ 'Delegate AST transform'
assert declaringTypeName == 'java.util.Comparator<Bar>'
assert typeName == 'java.lang.Integer'
}
}

@Test
void testDelegate12() {
addGroovySource '''\
|class Bar {
| @Deprecated
| int baz(){}
|}
|'''.stripMargin(), 'Bar'

String contents = '''\
|class Foo {
| @Delegate Bar bar
|}
|new Foo().baz()
|'''.stripMargin()

inferType(contents, 'baz').with {
assert result.confidence.name() == 'UNKNOWN'
}

contents = contents.replace('@Delegate', '@Delegate(deprecated=true)')

inferType(contents, 'baz').with {
assert result.extraDoc?.replace('}', '') =~ 'Delegate AST transform'
assert declaringTypeName == 'Bar'
assert typeName == 'java.lang.Integer'
}
}

@Test
void testDelegate13() {
addGroovySource '''\
|interface Bar {
| @Deprecated
| int baz()
|}
|'''.stripMargin(), 'Bar'

String contents = '''\
|class Foo {
| @Delegate(interfaces=false) Bar bar
|}
|new Foo().baz()
|'''.stripMargin()

inferType(contents, 'baz').with {
assert result.confidence.name() == 'UNKNOWN'
}
}

@Test
void testField1() {
String contents = '''\
Expand Down Expand Up @@ -382,7 +531,7 @@ final class BuiltInDSLInferencingTests extends DSLInferencingTestSuite {
|class E {
| String value
|}
|new E().compareTo(null)
|new E()./**/compareTo(null)
|'''.stripMargin()

inferType(contents, 'compareTo').with {
Expand Down
Loading

0 comments on commit 1f1716d

Please sign in to comment.