All Downloads are FREE. Search and download functionalities are using the official Maven repository.

spock.util.SourceToAstNodeAndSourceTranspiler.groovy Maven / Gradle / Ivy

package spock.util

import org.spockframework.compat.groovy2.GroovyCodeVisitorCompat

import java.lang.reflect.Modifier
import java.security.CodeSource

import groovy.transform.*
import org.apache.groovy.io.StringBuilderWriter
import org.codehaus.groovy.ast.*
import org.codehaus.groovy.ast.expr.*
import org.codehaus.groovy.ast.stmt.*
import org.codehaus.groovy.classgen.*
import org.codehaus.groovy.control.*
import org.codehaus.groovy.control.customizers.ImportCustomizer
import org.codehaus.groovy.syntax.Types

import static java.util.Collections.disjoint

/*
 * This has been adapted from https://github.com/apache/groovy/blob/5d2944523f198d96b6515e85a24d2b4e43ce665f/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstNodeToScriptAdapter.groovy
 * and made backwards compatible with groovy 2.5 via GroovyCodeVisitorCompat.
 *
 * - Replaced getter access by property access
 * - Removed trailing whitespaces when directly followed by printLineBreak()
 * - Special handling of AnnotationConstantExpression in visitConstantExpression
 * - Fix AnnotationNode rendering
 * - Improve GString rendering
 */

/**
 * This class takes Groovy source code, compiles it to a specific compile phase, and then decompiles it
 * back to the groovy source.
 */
@CompileStatic
class SourceToAstNodeAndSourceTranspiler {

  /**
   * This method takes source code, compiles it, then reverses it back to source.
   *
   * @param script
   *    the source code to be compiled. If invalid, a compile error occurs
   * @param compilePhase
   *    the CompilePhase. Must be an int mapped in {@link CompilePhase}
   * @param showSet
   *    the set of code to include in the rendering
   * @param classLoader
   *    (optional) the classloader to use. If missing/null then the current is used.
   *    This parameter enables things like ASTBrowser to invoke this with the correct classpath
   * @param config
   *    optional compiler configuration
   * @returns the source code from the AST state and the captured ast nodes
   */

  TranspileResult compileScript(String script, int compilePhase, Set showSet, ClassLoader classLoader = null, CompilerConfiguration config = null) {

    def writer = new StringBuilderWriter()

    classLoader = classLoader ?: new GroovyClassLoader(getClass().classLoader)

    def scriptName = 'script.groovy'
    GroovyCodeSource codeSource = new GroovyCodeSource(script, scriptName, '/groovy/script')
    CompilationUnit cu = new CompilationUnit((CompilerConfiguration)(config ?: CompilerConfiguration.DEFAULT), (CodeSource)codeSource.codeSource, (GroovyClassLoader)classLoader)
    def captureVisitor = new AstNodeCaptureVisitor()
    cu.addPhaseOperation(captureVisitor, compilePhase)
    cu.addPhaseOperation(new AstNodeToScriptVisitor(writer, showSet), compilePhase)
    cu.addSource(codeSource.name, script)
    try {
      cu.compile(compilePhase)
    } catch (CompilationFailedException cfe) {

      writer.println 'Unable to produce AST for this phase due to earlier compilation error:'
      cfe.message.eachLine {
        writer.println it
      }
    }
    return new TranspileResult(writer.toString(), captureVisitor.nodeCaptures)
  }
}

@CompileStatic
enum Show {
  /**
   * Show the package declaration portion of the source code
   */
  PACKAGE,
  /**
   * Show the imports declaration portion of the source code
   */
  IMPORTS,
  /**
   * Show the script portion of the source code
   */
  SCRIPT,
  /**
   * Show the Script class from the source code
   */
  CLASS,
  /**
   * Show the declared constructors of a class
   *
   * Implicit constructors are not rendered
   */
  CONSTRUCTORS,
  /**
   * Show the object initializers of a class
   */
  OBJECT_INITIALIZERS,
  /**
   * Show the methods of a class
   */
  METHODS,
  /**
   * Show the fields of a class
   */
  FIELDS,
  /**
   * Show the properties of a class
   */
  PROPERTIES,
  /**
   * Show the Annotations
   *
   * Is present, annotations will be rendered for the other selected elements.
   * If nothing else is selected, nothing will be rendered.
   */
  ANNOTATIONS;

  static EnumSet all() {
    EnumSet.allOf(Show)
  }
}

@TupleConstructor
@CompileStatic
class TranspileResult {
  final String source
  final List nodeCaptures
}


@TupleConstructor
@CompileStatic
class NodeCapture {
  final SourceUnit source
  final ClassNode classNode
}

@CompileStatic
class AstNodeCaptureVisitor extends CompilationUnit.PrimaryClassNodeOperation {

  final List nodeCaptures = []

  @Override
  void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
    nodeCaptures << new NodeCapture(source, classNode)
  }
}


/**
 * An adapter from ASTNode tree to source code.
 */
@CompileStatic
class AstNodeToScriptVisitor extends CompilationUnit.PrimaryClassNodeOperation implements GroovyClassVisitor, GroovyCodeVisitor, GroovyCodeVisitorCompat {

  public static final EnumSet CLASS_AND_MEMBERS = EnumSet.of(Show.CLASS, Show.METHODS, Show.FIELDS)
  private static final char SPACE = ' '
  private static final char NEW_LINE = '\n'
  private final StringBuilderWriter _out
  private final Set show
  private Stack classNameStack = new Stack()
  private String _indent = ''
  private boolean readyToIndent = true
  private boolean scriptHasBeenVisited

  AstNodeToScriptVisitor(StringBuilderWriter writer, Set show) {
    this._out = writer
    this.show = show
    this.scriptHasBeenVisited = false
  }

  void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {

    showOn(Show.PACKAGE) {
      visitPackage(source?.AST?.package)
    }

    showOn(Show.PACKAGE) {
      visitAllImports(source)
    }

    if (show.contains(Show.SCRIPT) && !scriptHasBeenVisited) {
      scriptHasBeenVisited = true
      source?.AST?.statementBlock?.visit(this)
    }
    if (showAny(CLASS_AND_MEMBERS) || !classNode.script) {
      visitClass classNode
    }
  }

  private def visitAllImports(SourceUnit source) {
    boolean staticImportsPresent = false
    boolean importsPresent = false

    source?.AST?.staticImports?.values()?.each {
      visitImport(it)
      staticImportsPresent = true
    }
    source?.AST?.staticStarImports?.values()?.each {
      visitImport(it)
      staticImportsPresent = true
    }

    if (staticImportsPresent) {
      printDoubleBreak()
    }

    source?.AST?.imports?.each {
      visitImport(it)
      importsPresent = true
    }
    source?.AST?.starImports?.each {
      visitImport(it)
      importsPresent = true
    }
    if (importsPresent) {
      printDoubleBreak()
    }
  }


  void print(parameter) {
    String output = parameter.toString()

    if (readyToIndent) {
      _out.print _indent
      readyToIndent = false
      while (output.startsWith(' ')) {
        output = output[1..-1]  // trim left
      }
    }
    if (outEndsWith(SPACE)) {
      if (output.startsWith(' ')) {
        output = output[1..-1]
      }
    }
    _out.print output
  }

  // slightly more complicated than the original code, but avoids creating strings just to check for the end
  boolean outEndsWith(Character character) {
    def builder = _out.builder
    return (builder.length() > 0 && builder.charAt(builder.length() - 1) == character)
  }

  boolean outEndsWith(String  str) {
    def builder = _out.builder
    return (builder.length() > str.size() && builder.substring(builder.length() - str.size()) == str)
  }

  void trimSpaceRight() {
    while (outEndsWith(SPACE)) {
      _out.builder.length = _out.builder.length() - 1
    }
  }

  def println(parameter) {
    throw new UnsupportedOperationException('Wrong API')
  }

  private boolean showAny(EnumSet set) {
    !disjoint(show, set)
  }

  def showOn(Show s, Closure block) {
    if (show.contains(s)) {
      block()
    }
  }

  def showOnAll(Set s, Closure block) {
    if (show.containsAll(s)) {
      block()
    }
  }

  def indented(Closure block) {
    String startingIndent = _indent
    _indent = _indent + '    '
    block()
    _indent = startingIndent
  }

  def printLineBreak() {
    if (!outEndsWith(NEW_LINE)) {
      trimSpaceRight()
      _out.print '\n'
    }
    readyToIndent = true
  }

  def printDoubleBreak() {
    if (outEndsWith('\n\n')) {
      // do nothing
    } else if (outEndsWith(NEW_LINE)) {
      _out.print '\n'
    } else {
      trimSpaceRight()
      _out.print '\n'
      _out.print '\n'
    }
    readyToIndent = true
  }

  void visitPackage(PackageNode packageNode) {

    if (packageNode) {

      showOn(Show.ANNOTATIONS) {
        packageNode.annotations?.each {
          visitAnnotationNode(it)
          printLineBreak()
        }
      }

      if (packageNode.text.endsWith('.')) {
        print packageNode.text[0..-2]
      } else {
        print packageNode.text
      }
      printDoubleBreak()
    }
  }

  void visitImport(ImportNode node) {
    if (node) {
      showOn(Show.ANNOTATIONS) {
        node.annotations?.each {
          visitAnnotationNode(it)
          printLineBreak()
        }
      }
      print node.text
      printLineBreak()
    }
  }

  @Override
  void visitClass(ClassNode node) {

    classNameStack.push(node.name)

    showOnAll(EnumSet.of(Show.ANNOTATIONS, Show.CLASS)) {
      node?.annotations?.each {
        visitAnnotationNode(it)
        printLineBreak()
      }
    }


    showOn(Show.CLASS) {
      visitModifiers(node.modifiers)
      if (node.interface) print node.name
      else print "class $node.name"
      visitGenerics node?.genericsTypes
      print ' extends '
      visitType node.unresolvedSuperClass
      boolean first = true
      node.unresolvedInterfaces?.each {
        if (first) {
          print ' implements '
        } else {
          print ', '
        }
        first = false
        visitType it
      }
      print ' {'
      printDoubleBreak()
    }

    readyToIndent = true

    indented {
      showOn(Show.PROPERTIES) {
        node?.properties?.each { visitProperty(it) }
        printLineBreak()
      }
      showOn(Show.FIELDS) {
        node?.fields?.each { visitField(it) }
        printDoubleBreak()
      }
      showOn(Show.CONSTRUCTORS) {
        node?.declaredConstructors?.each { visitConstructor(it) }
        printLineBreak()
      }
      showOn(Show.OBJECT_INITIALIZERS) {
        visitObjectInitializerBlocks(node)
        printLineBreak()
      }
      showOn(Show.METHODS) {
        node?.methods?.each { visitMethod(it) }
      }
    }
    showOn(Show.CLASS) {
      print '}'
      printLineBreak()
      classNameStack.pop()
    }
  }

  private void visitObjectInitializerBlocks(ClassNode node) {
    for (Statement stmt : (node.objectInitializerStatements)) {
      print '{'
      printLineBreak()
      indented {
        stmt.visit(this)
      }
      printLineBreak()
      print '}'
      printDoubleBreak()
    }
  }

  private void visitGenerics(GenericsType[] generics) {

    if (generics != null) {
      print '<'
      boolean first = true
      generics.each { GenericsType it ->
        if (!first) {
          print ', '
        }
        first = false
        print it.name
        if (it.upperBounds) {
          print ' extends '
          boolean innerFirst = true
          it.upperBounds.each { ClassNode upperBound ->
            if (!innerFirst) {
              print ' & '
            }
            innerFirst = false
            visitType upperBound
          }
        }
        if (it.lowerBound) {
          print ' super '
          visitType it.lowerBound
        }
      }
      print '>'
    }
  }

  @Override
  void visitConstructor(ConstructorNode node) {
    visitMethod(node)
  }

  private String visitParameters(parameters) {
    boolean first = true

    parameters.each { Parameter p ->
      if (!first) {
        print ', '
      }
      first = false

      showOn(Show.ANNOTATIONS) {
        p.annotations?.each {
          visitAnnotationNode(it)
          print(' ')
        }
      }

      visitModifiers(p.modifiers)
      visitType p.type
      print ' ' + p.name
      if (p.initialExpression && !(p.initialExpression instanceof EmptyExpression)) {
        print ' = '
        p.initialExpression.visit this
      }
    }
  }

  @Override
  void visitMethod(MethodNode node) {
    showOn(Show.ANNOTATIONS) {
      node?.annotations?.each {
        visitAnnotationNode(it)
        printLineBreak()
      }
    }

    visitModifiers(node.modifiers)
    if (node.name == '') {
      print "${classNameStack.peek()}("
      visitParameters(node.parameters)
      print ') {'
      printLineBreak()
    } else if (node.name == '') {
      print '{ ' // will already have 'static' from modifiers
      printLineBreak()
    } else {
      if (node.genericsTypes) {
        visitGenerics node.genericsTypes
        print ' '
      }
      visitType node.returnType
      print " $node.name("
      visitParameters(node.parameters)
      print ')'
      if (node.exceptions) {
        boolean first = true
        print ' throws '
        node.exceptions.each {
          if (!first) {
            print ', '
          }
          first = false
          visitType it
        }
      }
      print ' {'
      printLineBreak()
    }

    indented {
      node?.code?.visit(this)
    }
    printLineBreak()
    print '}'
    printDoubleBreak()
  }

  private void visitModifiers(int modifiers) {
    String mods = Modifier.toString(modifiers)
    mods = mods ? mods + ' ' : mods
    print mods
  }

  @Override
  void visitField(FieldNode node) {

    showOn(Show.ANNOTATIONS) {
      node?.annotations?.each {
        visitAnnotationNode(it)
        printLineBreak()
      }
    }
    visitModifiers(node.modifiers)
    visitType node.type
    print " $node.name "
    // Groovy's version didn't render initialValueExpression with this explanation:
    // > do not print initial expression, as this is executed as part of the constructor, unless on static constant
    // but since we want to see what is going on, we do render it
    Expression exp = node.initialValueExpression
    if (exp) {
      print ' = '
      exp.visit(this)
    }
    printLineBreak()
  }

  void visitAnnotationNode(AnnotationNode node) {
    print '@' + node?.classNode?.name
    if (node?.members) {
      print '('
      boolean first = true
      node.members.each { String name, Expression value ->
        if (first) {
          first = false
        } else {
          print ', '
        }
        print name + ' = '
        value.visit(this)
      }
      print ')'
    }

  }

  @Override
  void visitProperty(PropertyNode node) {
    // is a FieldNode, avoid double dispatch
  }

  @Override
  void visitBlockStatement(BlockStatement block) {
    if (printStatementLabels(block)) {
      print '{'
      printLineBreak()
      indented {
        block?.statements?.each {
          it.visit(this)
          printLineBreak()
        }
      }
      print '}'
      printLineBreak()
    } else {
      block?.statements?.each {
        it.visit(this)
        printLineBreak()
      }
    }
    if (!_out.toString().endsWith('\n')) {
      printLineBreak()
    }
  }

  @Override
  void visitForLoop(ForStatement statement) {
    printStatementLabels(statement)
    print 'for ('
    if (statement?.variable != ForStatement.FOR_LOOP_DUMMY) {
      visitParameters([statement.variable])
      print ' : '
    }

    statement?.collectionExpression?.visit this
    print ') {'
    printLineBreak()
    indented {
      statement?.loopBlock?.visit this
    }
    print '}'
    printLineBreak()
  }

  @Override
  void visitIfElse(IfStatement ifElse) {
    printStatementLabels(ifElse)
    print 'if ('
    ifElse?.booleanExpression?.visit this
    print ') {'
    printLineBreak()
    indented {
      ifElse?.ifBlock?.visit this
    }
    printLineBreak()
    if (ifElse?.elseBlock && !(ifElse.elseBlock instanceof EmptyStatement)) {
      print '} else {'
      printLineBreak()
      indented {
        ifElse?.elseBlock?.visit this
      }
      printLineBreak()
    }
    print '}'
    printLineBreak()
  }

  @Override
  void visitExpressionStatement(ExpressionStatement statement) {
    statement.expression.visit this
  }

  @Override
  void visitReturnStatement(ReturnStatement statement) {
    printLineBreak()
    print 'return '
    statement.expression.visit(this)
    printLineBreak()
  }

  @Override
  void visitSwitch(SwitchStatement statement) {
    printStatementLabels(statement)
    print 'switch ('
    statement?.expression?.visit this
    print ') {'
    printLineBreak()
    indented {
      statement?.caseStatements?.each {
        visitCaseStatement it
      }
      if (statement?.defaultStatement) {
        print 'default: '
        printLineBreak()
        statement?.defaultStatement?.visit this
      }
    }
    print '}'
    printLineBreak()
  }

  @Override
  void visitCaseStatement(CaseStatement statement) {
    print 'case '
    statement?.expression?.visit this
    print ':'
    printLineBreak()
    indented {
      statement?.code?.visit this
    }
  }

  @Override
  void visitBreakStatement(BreakStatement statement) {
    print 'break'
    if (statement?.label) {
      print ' ' + statement.label
    }
    printLineBreak()
  }

  @Override
  void visitContinueStatement(ContinueStatement statement) {
    print 'continue'
    if (statement?.label) {
      print ' ' + statement.label
    }
    printLineBreak()
  }

  @Override
  void visitMethodCallExpression(MethodCallExpression expression) {

    Expression objectExp = expression.objectExpression
    if (objectExp instanceof VariableExpression) {
      visitVariableExpression(objectExp, false)
    } else {
      objectExp.visit(this)
    }
    if (expression.spreadSafe) {
      print '*'
    }
    if (expression.safe) {
      print '?'
    }
    print '.'
    Expression method = expression.method
    if (method instanceof ConstantExpression) {
      visitConstantExpression(method, true)
    } else {
      method.visit(this)
    }
    expression.arguments.visit(this)
  }

  @Override
  void visitStaticMethodCallExpression(StaticMethodCallExpression expression) {
    print expression?.ownerType?.name + '.' + expression?.method
    expression?.arguments?.visit this
  }

  @Override
  void visitConstructorCallExpression(ConstructorCallExpression expression) {
    if (expression?.superCall) {
      print 'super'
    } else if (expression?.thisCall) {
      print 'this '
    } else {
      print 'new '
      visitType expression?.type
    }
    expression?.arguments?.visit this
  }

  @Override
  void visitBinaryExpression(BinaryExpression expression) {
    expression?.leftExpression?.visit this
    if (!(expression.rightExpression instanceof EmptyExpression) || expression.operation.type != Types.ASSIGN) {
      print " $expression.operation.text "
      expression.rightExpression.visit this

      if (expression?.operation?.text == '[') {
        print ']'
      }
    }
  }

  @Override
  void visitPostfixExpression(PostfixExpression expression) {
    print '('
    expression?.expression?.visit this
    print ')'
    print expression?.operation?.text
  }

  @Override
  void visitPrefixExpression(PrefixExpression expression) {
    print expression?.operation?.text
    print '('
    expression?.expression?.visit this
    print ')'
  }


  @Override
  void visitClosureExpression(ClosureExpression expression) {
    print '{ '
    if (expression?.parameters) {
      visitParameters(expression?.parameters)
    }
    print ' ->'
    printLineBreak()
    indented {
      expression?.code?.visit this
    }
    print '}'
  }

  @Override
  void visitLambdaExpression(LambdaExpression expression) {
    print '( '
    if (expression?.parameters) {
      visitParameters(expression?.parameters)
    }
    print ') -> {'
    printLineBreak()
    indented {
      expression?.code?.visit this
    }
    print '}'
  }

  @Override
  void visitTupleExpression(TupleExpression expression) {
    print '('
    visitExpressionsAndCommaSeparate(expression?.expressions)
    print ')'
  }

  @Override
  void visitRangeExpression(RangeExpression expression) {
    print '('
    expression?.from?.visit this
    print '..'
    if (!expression?.inclusive) {
      print '<'
    }
    expression?.to?.visit this
    print ')'
  }

  @Override
  void visitPropertyExpression(PropertyExpression expression) {
    expression?.objectExpression?.visit this
    trimSpaceRight() // remove space inserted by previous expression
    if (expression?.spreadSafe) {
      print '*'
    } else if (expression?.safe) {
      print '?'
    }
    print '.'
    if (expression?.property instanceof ConstantExpression) {
      visitConstantExpression((ConstantExpression)expression?.property, true)
    } else {
      expression?.property?.visit this
    }
  }

  @Override
  void visitAttributeExpression(AttributeExpression attributeExpression) {
    visitPropertyExpression attributeExpression
  }

  @Override
  void visitFieldExpression(FieldExpression expression) {
    print expression?.field?.name
  }

  private static String escapeCharacters(String input) {
    input
      ?.replaceAll('\\\\', '\\\\\\\\')
      ?.replaceAll('\b', '\\\\b')
      ?.replaceAll('\f', '\\\\f')
      ?.replaceAll('\n', '\\\\n')
      ?.replaceAll('\r', '\\\\r')
      ?.replaceAll('\t', '\\\\t')
      ?.replaceAll("'", "\\\\'")
      ?.replaceAll('"', '\\\\"')
      ?.replaceAll('\\$', '\\\\\\$')
  }

  void visitConstantExpression(ConstantExpression expression, boolean unwrapQuotes = false, Boolean escapeChars = null) {
    if (expression instanceof AnnotationConstantExpression) {
      visitAnnotationNode (expression.value as AnnotationNode)
      return
    }
    if ((expression.value instanceof String || expression.value instanceof Character) && !unwrapQuotes) {
      //noinspection GroovyPointlessBoolean
      String value = escapeChars == false ? expression.value : escapeCharacters(expression.value as String)
      print "'$value'"
    } else {
      //noinspection GroovyPointlessBoolean
      print escapeChars == true ? escapeCharacters(expression.value as String) : expression.value
    }
  }

  @Override
  void visitClassExpression(ClassExpression expression) {
    print expression.text
  }

  void visitVariableExpression(VariableExpression expression, boolean spacePad = true) {

    if (spacePad) {
      print ' ' + expression.name + ' '
    } else {
      print expression.name
    }
  }

  @Override
  void visitDeclarationExpression(DeclarationExpression expression) {
    // handle multiple assignment expressions
    if (expression?.leftExpression instanceof ArgumentListExpression) {
      print 'def '
      visitArgumentlistExpression((ArgumentListExpression)expression?.leftExpression, true)
      print " $expression.operation.text "
      expression.rightExpression.visit this
    } else {
      visitType expression?.leftExpression?.type
      visitBinaryExpression expression // is a BinaryExpression
    }
  }

  @Override
  void visitGStringExpression(GStringExpression expression) {
    print '"'
    ((expression.strings as Collection) + expression.values)
      .sort { left, right ->
        left.lineNumber <=> right.lineNumber
          ?: left.columnNumber <=> right.columnNumber
      }
      .each {
        if (it in expression.values) {
          if (it instanceof ClosureExpression) {
            print '$'
            it.visit(this)
          } else {
            print '${'
            if (it instanceof VariableExpression) {
              visitVariableExpression(it, false)
            } else {
              it.visit(this)
            }
            print '}'
          }
        } else if (it instanceof ConstantExpression) {
          visitConstantExpression(it, true, true)
        }
      }
    print '"'
  }

  @Override
  void visitSpreadExpression(SpreadExpression expression) {
    print '*'
    expression?.expression?.visit this
  }

  @Override
  void visitNotExpression(NotExpression expression) {
    print '!('
    expression?.expression?.visit this
    print ')'
  }

  @Override
  void visitUnaryMinusExpression(UnaryMinusExpression expression) {
    print '-('
    expression?.expression?.visit this
    print ')'
  }

  @Override
  void visitUnaryPlusExpression(UnaryPlusExpression expression) {
    print '+('
    expression?.expression?.visit this
    print ')'
  }

  @Override
  void visitCastExpression(CastExpression expression) {
    if (expression.coerce) {
      print '(('
      expression?.expression?.visit this
      print ') as '
      visitType(expression?.type)
      print ')'
    } else {
      print '(('
      visitType(expression?.type)
      print ') '
      expression?.expression?.visit this
      print ')'
    }
  }

  /**
   * Prints out the type, safely handling arrays.
   * @param classNode
   */
  void visitType(ClassNode classNode) {
    if (classNode.array || classNode.genericsPlaceHolder) {
      print classNode.toString(false)
    } else {
      print classNode.name
      visitGenerics classNode?.genericsTypes
    }
  }

  void visitArgumentlistExpression(ArgumentListExpression expression, boolean showTypes = false) {
    print '('
    int count = expression?.expressions?.size()
    expression.expressions.each {
      if (showTypes) {
        visitType it.type
        print ' '
      }
      if (it instanceof VariableExpression) {
        visitVariableExpression it, false
      } else if (it instanceof ConstantExpression) {
        visitConstantExpression it, false
      } else {
        it.visit this
      }
      count--
      if (count) print ', '
    }
    print ')'
  }

  @Override
  void visitBytecodeExpression(BytecodeExpression expression) {
    print '/*BytecodeExpression*/'
    printLineBreak()
  }


  @Override
  void visitMapExpression(MapExpression expression) {
    print '['
    if (expression?.mapEntryExpressions?.size() == 0) {
      print ':'
    } else {
      visitExpressionsAndCommaSeparate((List)expression?.mapEntryExpressions)
    }
    print ']'
  }

  @Override
  void visitMapEntryExpression(MapEntryExpression expression) {
    if (expression?.keyExpression instanceof SpreadMapExpression) {
      expression?.keyExpression?.visit this
    } else {
      expression?.keyExpression?.visit this
      print ': '
      expression?.valueExpression?.visit this
    }
  }

  @Override
  void visitListExpression(ListExpression expression) {
    print '['
    visitExpressionsAndCommaSeparate(expression?.expressions)
    print ']'
  }

  @Override
  void visitTryCatchFinally(TryCatchStatement statement) {
    printStatementLabels(statement)
    print 'try {'
    printLineBreak()
    indented {
      statement?.tryStatement?.visit this
    }
    printLineBreak()
    print '}'
    printLineBreak()
    statement?.catchStatements?.each { CatchStatement catchStatement ->
      visitCatchStatement(catchStatement)
    }
    print 'finally {'
    printLineBreak()
    indented {
      statement?.finallyStatement?.visit this
    }
    print '}'
    printLineBreak()
  }

  @Override
  void visitThrowStatement(ThrowStatement statement) {
    print 'throw '
    statement?.expression?.visit this
    printLineBreak()
  }

  @Override
  void visitSynchronizedStatement(SynchronizedStatement statement) {
    printStatementLabels(statement)
    print 'synchronized ('
    statement?.expression?.visit this
    print ') {'
    printLineBreak()
    indented {
      statement?.code?.visit this
    }
    print '}'
  }

  @Override
  void visitTernaryExpression(TernaryExpression expression) {
    expression?.booleanExpression?.visit this
    print ' ? '
    expression?.trueExpression?.visit this
    print ' : '
    expression?.falseExpression?.visit this
  }

  @Override
  void visitShortTernaryExpression(ElvisOperatorExpression expression) {
    visitTernaryExpression(expression)
  }

  @Override
  void visitBooleanExpression(BooleanExpression expression) {
    expression?.expression?.visit this
  }

  @Override
  void visitWhileLoop(WhileStatement statement) {
    printStatementLabels(statement)
    print 'while ('
    statement?.booleanExpression?.visit this
    print ') {'
    printLineBreak()
    indented {
      statement?.loopBlock?.visit this
    }
    printLineBreak()
    print '}'
    printLineBreak()
  }

  @Override
  void visitDoWhileLoop(DoWhileStatement statement) {
    printStatementLabels(statement)
    print 'do {'
    printLineBreak()
    indented {
      statement?.loopBlock?.visit this
    }
    print '} while ('
    statement?.booleanExpression?.visit this
    print ')'
    printLineBreak()
  }

  @Override
  void visitCatchStatement(CatchStatement statement) {
    print 'catch ('
    visitParameters([statement.variable])
    print ') {'
    printLineBreak()
    indented {
      statement.code?.visit this
    }
    print '}'
    printLineBreak()
  }

  @Override
  void visitBitwiseNegationExpression(BitwiseNegationExpression expression) {
    print '~('
    expression?.expression?.visit this
    print ') '
  }

  @Override
  void visitAssertStatement(AssertStatement statement) {
    print 'assert '
    statement?.booleanExpression?.visit this
    print ' : '
    statement?.messageExpression?.visit this
  }

  @Override
  void visitClosureListExpression(ClosureListExpression expression) {
    boolean first = true
    expression?.expressions?.each {
      if (!first) {
        print ';'
      }
      first = false
      it.visit this
    }
  }

  @Override
  void visitMethodPointerExpression(MethodPointerExpression expression) {
    expression?.expression?.visit this
    trimSpaceRight() // remove space inserted by previous expression
    print '.&'
    expression?.methodName?.visit this
  }

  @Override
  void visitMethodReferenceExpression(MethodReferenceExpression expression) {
    expression?.expression?.visit this
    print '::'
    expression?.methodName?.visit this
  }

  @Override
  void visitArrayExpression(ArrayExpression expression) {
    print 'new '
    visitType expression?.elementType
    print '['
    visitExpressionsAndCommaSeparate(expression?.sizeExpression)
    print ']'
    if (expression?.expressions) { // print array initializer
      print '{'
      visitExpressionsAndCommaSeparate(expression?.expressions)
      print '}'
    }
  }

  private void visitExpressionsAndCommaSeparate(List expressions) {
    boolean first = true
    expressions?.each {
      if (!first) {
        print ', '
      }
      first = false
      /*
      I have no idea why the AnnotationConstantExpression does it,
      but it first visits the values of the members before it visits itself.
      That's why you got org.spockframework.runtime.model.BlockKind.SETUP followed by [] followed by the actual correct representation.
      https://issues.apache.org/jira/browse/GROOVY-9980
       */
      if (it instanceof AnnotationConstantExpression) {
        visitConstantExpression(it)
      } else {
        (it as ASTNode).visit this
      }
    }
  }

  @Override
  void visitSpreadMapExpression(SpreadMapExpression expression) {
    print '*:'
    expression?.expression?.visit this
  }

  /**
   * Prints all labels for the given statement.  The labels will be printed on a single
   * line and line break will be added.
   *
   * @param statement for which to print labels
   * @return {@code true} if the statement had labels to print, else {@code false}
   */
  private boolean printStatementLabels(Statement statement) {
    List labels = statement?.statementLabels
    if (labels == null || labels.empty) {
      return false
    }
    for (String label : labels) {
      print label + ':'
      printLineBreak()
    }
    return true
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy