Skip to content

Commit

Permalink
Fix for #1155: compilation, coloration, navigation, etc. for @asttest
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-milles committed Aug 24, 2020
1 parent 4ca4939 commit 6385b4b
Show file tree
Hide file tree
Showing 12 changed files with 998 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -513,19 +513,31 @@ public void testCompileStatic20() {
//@formatter:off
String[] sources = {
"Script.groovy",
"@groovy.transform.CompileStatic\n" +
"import groovy.transform.*\n" +
"import static org.codehaus.groovy.transform.stc.StaticTypesMarker.*\n" +
"\n" +
"@CompileStatic\n" +
"class C {\n" +
" void m() {\n" +
" C that = this;\n" +
" { ->\n" +
" @ASTTest(phase=INSTRUCTION_SELECTION, value={\n" +
" assert node.getNodeMetaData(INFERRED_TYPE)?.name == 'C'\n" +
" })\n" +
" def ref = getThisObject()\n" +
" assert ref == that\n" +
" }();\n" +
" { ->\n" +
" @ASTTest(phase=INSTRUCTION_SELECTION, value={\n" +
" assert node.getNodeMetaData(INFERRED_TYPE)?.name == 'C'\n" +
" })\n" +
" def ref = getDelegate()\n" +
" assert ref == that\n" +
" }();\n" +
" { ->\n" +
" @ASTTest(phase=INSTRUCTION_SELECTION, value={\n" +
" assert node.getNodeMetaData(INFERRED_TYPE)?.name == 'C'\n" +
" })\n" +
" def ref = getOwner()\n" +
" assert ref == that\n" +
" }();\n" +
Expand Down
1 change: 1 addition & 0 deletions base/org.codehaus.groovy24/.checkstyle
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
<file-match-pattern match-pattern="groovy/control/messages/LocatedMessage.java" include-pattern="false" />
<file-match-pattern match-pattern="groovy/tools/GroovyClass.java" include-pattern="false" />
<file-match-pattern match-pattern="groovy/transform/AnnotationCollectorTransform.java" include-pattern="false" />
<file-match-pattern match-pattern="groovy/transform/ASTTestTransformation.groovy" include-pattern="false" />
<file-match-pattern match-pattern="groovy/transform/ASTTransformationCollectorCodeVisitor.java" include-pattern="false" />
<file-match-pattern match-pattern="groovy/transform/ASTTransformationVisitor.java" include-pattern="false" />
<file-match-pattern match-pattern="groovy/transform/FieldASTTransformation.java" include-pattern="false" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.codehaus.groovy.transform

import groovy.transform.CompilationUnitAware
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.AnnotationNode
import org.codehaus.groovy.ast.ClassCodeVisitorSupport
import org.codehaus.groovy.ast.ClassHelper
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.ast.Parameter
import org.codehaus.groovy.ast.expr.ClosureExpression
import org.codehaus.groovy.ast.expr.PropertyExpression
import org.codehaus.groovy.ast.expr.VariableExpression
import org.codehaus.groovy.ast.stmt.EmptyStatement
import org.codehaus.groovy.ast.stmt.Statement
import org.codehaus.groovy.control.CompilationUnit
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.ErrorCollector
import org.codehaus.groovy.control.Janitor
import org.codehaus.groovy.control.ProcessingUnit
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.control.customizers.ImportCustomizer
import org.codehaus.groovy.control.io.ReaderSource
import org.codehaus.groovy.control.messages.SyntaxErrorMessage
import org.codehaus.groovy.runtime.MethodClosure
import org.codehaus.groovy.syntax.SyntaxException
import org.codehaus.groovy.tools.Utilities

import static org.codehaus.groovy.ast.tools.GeneralUtils.classX
import static org.codehaus.groovy.ast.tools.GeneralUtils.propX

@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
class ASTTestTransformation extends AbstractASTTransformation implements CompilationUnitAware {
private CompilationUnit compilationUnit

void visit(final ASTNode[] nodes, final SourceUnit source) {
AnnotationNode annotationNode = nodes[0]
def member = annotationNode.getMember('phase')
def phase = null
if (member) {
if (member instanceof VariableExpression) {
phase = CompilePhase.valueOf(member.text)
} else if (member instanceof PropertyExpression) {
phase = CompilePhase.valueOf(member.propertyAsString)
}
annotationNode.setMember('phase', propX(classX(ClassHelper.make(CompilePhase)), phase.toString()))
// GRECLIPSE add
PropertyExpression p = annotationNode.getMember('phase')
if (member instanceof VariableExpression) {
p.getProperty().setSourcePosition(member)
p.setStart(member.getStart())
p.setEnd(member.getEnd())
} else if (member instanceof PropertyExpression) {
ClassNode type = ClassHelper.makeWithoutCaching(CompilePhase.name)
type.setRedirect(p.getObjectExpression().getType())
p.getObjectExpression().setType(type)

p.getObjectExpression().setSourcePosition(member.objectExpression)
p.getProperty().setSourcePosition(member.property)
p.setSourcePosition(member)
}
// GRECLIPSE end
}
member = annotationNode.getMember('value')
if (member && !(member instanceof ClosureExpression)) {
throw new SyntaxException("ASTTest value must be a closure", member.getLineNumber(), member.getColumnNumber())
}
if (!member && !annotationNode.getNodeMetaData(ASTTestTransformation)) {
throw new SyntaxException("Missing test expression", annotationNode.getLineNumber(), annotationNode.getColumnNumber())
}
// convert value into node metadata so that the expression doesn't mix up with other AST xforms like type checking
annotationNode.setNodeMetaData(ASTTestTransformation, member)
/* GRECLIPSE edit
annotationNode.getMembers().remove('value')
*/
new ClosureExpression(Parameter.EMPTY_ARRAY, EmptyStatement.INSTANCE).with {
annotationNode.setMember('value', it)
setStart(member.getStart())
setEnd(member.getEnd())
}
member.variableScope.@parent = null
// GRECLIPSE end

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 (phase==null || phaseRef == phase.phaseNumber) {
ClosureExpression testClosure = nodes[0].getNodeMetaData(ASTTestTransformation)
StringBuilder sb = new StringBuilder()
for (int i = testClosure.lineNumber; i <= testClosure.lastLineNumber; i++) {
sb.append(source.source.getLine(i, new Janitor())).append('\n')
}
def testSource = sb.substring(testClosure.columnNumber, sb.length())
testSource = testSource.substring(0, testSource.lastIndexOf('}'))
CompilerConfiguration config = new CompilerConfiguration()
def customizer = new ImportCustomizer()
config.addCompilationCustomizers(customizer)
binding['sourceUnit'] = source
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)

source.AST.imports.each {
customizer.addImport(it.alias, it.type.name)
}
source.AST.starImports.each {
customizer.addStarImports(it.packageName)
}
source.AST.staticImports.each {
customizer.addStaticImport(it.value.alias, it.value.type.name, it.value.fieldName)
}
source.AST.staticStarImports.each {
customizer.addStaticStars(it.value.className)
}
// GRECLIPSE add
try {
// GRECLIPSE end
shell.evaluate(testSource)
// GRECLIPSE add
} catch (Throwable t) {
source.errorCollector.addErrorAndContinue(new SyntaxErrorMessage(new SyntaxException(" ASTTest exception: $t.message", t,
annotationNode.lineNumber, annotationNode.columnNumber, annotationNode.classNode.lastLineNumber, annotationNode.classNode.lastColumnNumber
), source))
}
// GRECLIPSE end
}
}
}

if (pcallback!=null) {
if (pcallback instanceof ProgressCallbackChain) {
pcallback.addCallback(callback)
} else {
pcallback = new ProgressCallbackChain(pcallback, callback)
}
callback = pcallback
}

compilationUnit.setProgressCallback(callback)

}

void setCompilationUnit(final CompilationUnit unit) {
this.compilationUnit = unit
}

private static class AssertionSourceDelegatingSourceUnit extends SourceUnit {
private final ReaderSource delegate

AssertionSourceDelegatingSourceUnit(final String name, final ReaderSource source, final CompilerConfiguration flags, final GroovyClassLoader loader, final ErrorCollector er) {
super(name, '', flags, loader, er)
delegate = source
}

@Override
String getSample(final int line, final int column, final Janitor janitor) {
String sample = null;
String text = delegate.getLine(line, janitor);

if (text != null) {
if (column > 0) {
String marker = Utilities.repeatString(" ", column - 1) + "^";

if (column > 40) {
int start = column - 30 - 1;
int end = (column + 10 > text.length() ? text.length() : column + 10 - 1);
sample = " " + text.substring(start, end) + Utilities.eol() + " " +
marker.substring(start, marker.length());
} else {
sample = " " + text + Utilities.eol() + " " + marker;
}
} else {
sample = text;
}
}

return sample;

}

}

private static class ProgressCallbackChain extends CompilationUnit.ProgressCallback {

private final List<CompilationUnit.ProgressCallback> chain = new LinkedList<CompilationUnit.ProgressCallback>()

ProgressCallbackChain(CompilationUnit.ProgressCallback... callbacks) {
if (callbacks!=null) {
callbacks.each { addCallback(it) }
}
}

public void addCallback(CompilationUnit.ProgressCallback callback) {
chain << callback
}

@Override
void call(final ProcessingUnit context, final int phase) {
chain*.call(context, phase)
}
}

public static class LabelFinder extends ClassCodeVisitorSupport {

public static List<Statement> lookup(MethodNode node, String label) {
LabelFinder finder = new LabelFinder(label, null)
node.code.visit(finder)

finder.targets
}

public static List<Statement> lookup(ClassNode node, String label) {
LabelFinder finder = new LabelFinder(label, null)
node.methods*.code*.visit(finder)
node.declaredConstructors*.code*.visit(finder)

finder.targets
}

private final String label
private final SourceUnit unit

private final List<Statement> targets = new LinkedList<Statement>();

LabelFinder(final String label, final SourceUnit unit) {
this.label = label
this.unit = unit;
}

@Override
protected SourceUnit getSourceUnit() {
unit
}

@Override
protected void visitStatement(final Statement statement) {
super.visitStatement(statement)
if (statement.statementLabel==label) targets << statement
}

List<Statement> getTargets() {
return Collections.unmodifiableList(targets)
}
}

}
1 change: 1 addition & 0 deletions base/org.codehaus.groovy25/.checkstyle
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
<file-match-pattern match-pattern="groovy/control/messages/LocatedMessage.java" include-pattern="false" />
<file-match-pattern match-pattern="groovy/tools/GroovyClass.java" include-pattern="false" />
<file-match-pattern match-pattern="groovy/transform/AnnotationCollectorTransform.java" include-pattern="false" />
<file-match-pattern match-pattern="groovy/transform/ASTTestTransformation.groovy" include-pattern="false" />
<file-match-pattern match-pattern="groovy/transform/ASTTransformationCollectorCodeVisitor.java" include-pattern="false" />
<file-match-pattern match-pattern="groovy/transform/ASTTransformationVisitor.java" include-pattern="false" />
<file-match-pattern match-pattern="groovy/transform/FieldASTTransformation.java" include-pattern="false" />
Expand Down
Loading

0 comments on commit 6385b4b

Please sign in to comment.