Skip to content

Commit

Permalink
GROOVY-6968: Improvements to @asttest
Browse files Browse the repository at this point in the history
  • Loading branch information
melix committed Jul 22, 2014
1 parent 16f77d9 commit e1bed52
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class ASTTestTransformation extends AbstractASTTransformation implements Compila
void visit(final ASTNode[] nodes, final SourceUnit source) {
AnnotationNode annotationNode = nodes[0]
def member = annotationNode.getMember('phase')
def phase = CompilePhase.SEMANTIC_ANALYSIS
def phase = null
if (member) {
if (member instanceof VariableExpression) {
phase = CompilePhase.valueOf(member.text)
Expand All @@ -56,9 +56,11 @@ class ASTTestTransformation extends AbstractASTTransformation implements Compila

def pcallback = compilationUnit.progressCallback
def callback = new CompilationUnit.ProgressCallback() {
Binding binding = new Binding([:].withDefault {null})

@Override
void call(final ProcessingUnit context, final int phaseRef) {
if (phaseRef == phase.phaseNumber) {
if (phase==null || phaseRef == phase.phaseNumber) {
ClosureExpression testClosure = nodes[0].getNodeMetaData(ASTTestTransformation)
StringBuilder sb = new StringBuilder()
for (int i = testClosure.lineNumber; i <= testClosure.lastLineNumber; i++) {
Expand All @@ -69,9 +71,10 @@ class ASTTestTransformation extends AbstractASTTransformation implements Compila
CompilerConfiguration config = new CompilerConfiguration()
def customizer = new ImportCustomizer()
config.addCompilationCustomizers(customizer)
def binding = new Binding()
binding['node'] = nodes[1]
binding['lookup'] = new MethodClosure(LabelFinder, "lookup").curry(nodes[1])
binding['compilationUnit'] = compilationUnit
binding['compilePhase'] = CompilePhase.fromPhaseNumber(phaseRef)

GroovyShell shell = new GroovyShell(binding, config)

Expand Down
27 changes: 27 additions & 0 deletions src/spec/doc/core-metaprogramming.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2083,6 +2083,33 @@ Imagine, for example, that you want to test the declared type of a for loop vari
include::{projectdir}/src/spec/test/TestingASTTransformsTest.groovy[tags=asttest_forloop,indent=0]
----
Since Groovy 2.4.0, `@ASTTest` exposes more variables inside the test closure:
* `node` corresponds to the annotated node, as usual
* `compilationUnit` gives access to the current `org.codehaus.groovy.control.CompilationUnit`
* `compilePhase` returns the current compile phase (`org.codehaus.groovy.control.CompilePhase`)
The latter is interesting if you don't specify the `phase` attribute. In that case, the closure will be executed after
each compile phase after (and including) `SEMANTIC_ANALYSIS`. The context of the transformation is kept after each phase,
giving you a chance to check what changed between two phases.
As an example, here is how you could dump the list of AST transformations registered on a class node:
[source,groovy]
----
include::{projectdir}/src/spec/test/TestingASTTransformsTest.groovy[tags=dump_ast_xforms,indent=0]
----
And here is how you can memorize variables for testing between two phases:
[source,groovy]
----
include::{projectdir}/src/spec/test/TestingASTTransformsTest.groovy[tags=memorize_in_binding,indent=0]
----
<1> if the current compile phase is instruction selection
<2> then we want to make sure `toString` was added at `CANONICALIZATION`
<3> otherwise, if `toString` exists and that the variable from the context, `added` is null
<4> then it means that this compile phase is the one where `toString` was added
==== Grape handling
[[xform-Grab]]
Expand Down
59 changes: 59 additions & 0 deletions src/spec/test/TestingASTTransformsTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,65 @@ class Something {
}
// end::asttest_forloop[]
def p = new Something()
'''
}

void testDumpASTTransforms() {
assertScript '''
// tag::dump_ast_xforms[]
import groovy.transform.ASTTest
import groovy.transform.CompileStatic
import groovy.transform.Immutable
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.control.CompilePhase
@ASTTest(value={
System.err.println "Compile phase: $compilePhase"
ClassNode cn = node
System.err.println "Global AST xforms: ${compilationUnit?.ASTTransformationsContext?.globalTransformNames}"
CompilePhase.values().each {
def transforms = cn.getTransforms(it)
if (transforms) {
System.err.println "Ast xforms for phase $it:"
transforms.each { map ->
System.err.println(map)
}
}
}
})
@CompileStatic
@Immutable
class Foo {
}
// end::dump_ast_xforms[]
Foo
'''
}

void testVariableMemorize() {
assertScript '''
// tag::memorize_in_binding[]
import groovy.transform.ASTTest
import groovy.transform.ToString
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.control.CompilePhase
@ASTTest(value={
if (compilePhase==CompilePhase.INSTRUCTION_SELECTION) { // <1>
println "toString() was added at phase: ${added}"
assert added == CompilePhase.CANONICALIZATION // <2>
} else {
if (node.getDeclaredMethods('toString') && added==null) { // <3>
added = compilePhase // <4>
}
}
})
@ToString
class Foo {
String name
}
// end::memorize_in_binding[]
Foo
'''
}
}

0 comments on commit e1bed52

Please sign in to comment.