Utilities.parseAndAttribute should be able to return positions of (sub)trees of the pattern and correcting locations of parse errors.
authorJan Lahoda <jlahoda@netbeans.org>
Wed, 15 Dec 2010 19:27:30 +0100
changeset 4999452058454f2
parent 498 a0fff8b53334
child 500 69445b855fd2
Utilities.parseAndAttribute should be able to return positions of (sub)trees of the pattern and correcting locations of parse errors.
api/src/org/netbeans/modules/jackpot30/impl/Utilities.java
api/test/unit/src/org/netbeans/modules/jackpot30/impl/UtilitiesTest.java
     1.1 --- a/api/src/org/netbeans/modules/jackpot30/impl/Utilities.java	Wed Dec 15 19:11:13 2010 +0100
     1.2 +++ b/api/src/org/netbeans/modules/jackpot30/impl/Utilities.java	Wed Dec 15 19:27:30 2010 +0100
     1.3 @@ -57,6 +57,7 @@
     1.4  import com.sun.source.tree.NewClassTree;
     1.5  import com.sun.source.tree.Scope;
     1.6  import com.sun.source.tree.StatementTree;
     1.7 +import com.sun.source.tree.SwitchTree;
     1.8  import com.sun.source.tree.Tree;
     1.9  import com.sun.source.tree.Tree.Kind;
    1.10  import com.sun.source.tree.TypeParameterTree;
    1.11 @@ -114,6 +115,7 @@
    1.12  import java.util.Iterator;
    1.13  import java.util.LinkedList;
    1.14  import java.util.List;
    1.15 +import java.util.Locale;
    1.16  import java.util.Map;
    1.17  import java.util.Map.Entry;
    1.18  import java.util.Set;
    1.19 @@ -313,6 +315,10 @@
    1.20          return parseAndAttribute(info, JavaSourceAccessor.getINSTANCE().getJavacTask(info), pattern, scope, errors);
    1.21      }
    1.22  
    1.23 +    public static Tree parseAndAttribute(CompilationInfo info, String pattern, Scope scope, SourcePositions[] sourcePositions, Collection<Diagnostic<? extends JavaFileObject>> errors) {
    1.24 +        return parseAndAttribute(info, JavaSourceAccessor.getINSTANCE().getJavacTask(info), pattern, scope, sourcePositions, errors);
    1.25 +    }
    1.26 +
    1.27      public static Tree parseAndAttribute(JavacTaskImpl jti, String pattern) {
    1.28          return parseAndAttribute(jti, pattern, null);
    1.29      }
    1.30 @@ -322,16 +328,31 @@
    1.31      }
    1.32  
    1.33      private static Tree parseAndAttribute(CompilationInfo info, JavacTaskImpl jti, String pattern, Scope scope, Collection<Diagnostic<? extends JavaFileObject>> errors) {
    1.34 +        return parseAndAttribute(info, jti, pattern, scope, new SourcePositions[1], errors);
    1.35 +    }
    1.36 +
    1.37 +    private static Tree parseAndAttribute(CompilationInfo info, JavacTaskImpl jti, String pattern, Scope scope, SourcePositions[] sourcePositions, Collection<Diagnostic<? extends JavaFileObject>> errors) {
    1.38          Context c = jti.getContext();
    1.39          TreeFactory make = TreeFactory.instance(c);
    1.40          List<Diagnostic<? extends JavaFileObject>> patternTreeErrors = new LinkedList<Diagnostic<? extends JavaFileObject>>();
    1.41 -        Tree patternTree = !isStatement(pattern) ? parseExpression(c, pattern, true, new SourcePositions[1], patternTreeErrors) : null;
    1.42 +        Tree patternTree = !isStatement(pattern) ? parseExpression(c, pattern, true, sourcePositions, patternTreeErrors) : null;
    1.43 +        int offset = 0;
    1.44          boolean expression = true;
    1.45          boolean classMember = false;
    1.46  
    1.47 +        if (pattern.startsWith("case ")) {//XXX: should be a lexer token
    1.48 +            List<Diagnostic<? extends JavaFileObject>> currentPatternTreeErrors = new LinkedList<Diagnostic<? extends JavaFileObject>>();
    1.49 +            Tree switchTree = parseStatement(c, "switch ($$foo) {" + pattern + "}", sourcePositions, currentPatternTreeErrors);
    1.50 +
    1.51 +            offset = "switch ($$foo) {".length();
    1.52 +            patternTreeErrors = currentPatternTreeErrors;
    1.53 +            patternTree = ((SwitchTree) switchTree).getCases().get(0);
    1.54 +        }
    1.55 +
    1.56          if (patternTree == null || isErrorTree(patternTree)) {
    1.57 +            SourcePositions[] currentPatternTreePositions = new SourcePositions[1];
    1.58              List<Diagnostic<? extends JavaFileObject>> currentPatternTreeErrors = new LinkedList<Diagnostic<? extends JavaFileObject>>();
    1.59 -            Tree currentPatternTree = parseStatement(c, "{" + pattern + "}", new SourcePositions[1], currentPatternTreeErrors);
    1.60 +            Tree currentPatternTree = parseStatement(c, "{" + pattern + "}", currentPatternTreePositions, currentPatternTreeErrors);
    1.61  
    1.62              assert currentPatternTree.getKind() == Kind.BLOCK : currentPatternTree.getKind();
    1.63  
    1.64 @@ -351,18 +372,25 @@
    1.65  
    1.66              if (!currentPatternTreeErrors.isEmpty() || containsError(currentPatternTree)) {
    1.67                  //maybe a class member?
    1.68 +                SourcePositions[] classPatternTreePositions = new SourcePositions[1];
    1.69                  List<Diagnostic<? extends JavaFileObject>> classPatternTreeErrors = new LinkedList<Diagnostic<? extends JavaFileObject>>();
    1.70 -                Tree classPatternTree = parseExpression(c, "new Object() {" + pattern + "}", false, new SourcePositions[1], classPatternTreeErrors);
    1.71 +                Tree classPatternTree = parseExpression(c, "new Object() {" + pattern + "}", false, classPatternTreePositions, classPatternTreeErrors);
    1.72  
    1.73                  if (!containsError(classPatternTree)) {
    1.74 +                    sourcePositions[0] = classPatternTreePositions[0];
    1.75 +                    offset = "new Object() {".length();
    1.76                      patternTreeErrors = classPatternTreeErrors;
    1.77                      patternTree = classPatternTree;
    1.78                      classMember = true;
    1.79                  } else {
    1.80 +                    sourcePositions[0] = currentPatternTreePositions[0];
    1.81 +                    offset = 1;
    1.82                      patternTreeErrors = currentPatternTreeErrors;
    1.83                      patternTree = currentPatternTree;
    1.84                  }
    1.85              } else {
    1.86 +                sourcePositions[0] = currentPatternTreePositions[0];
    1.87 +                offset = 1;
    1.88                  patternTreeErrors = currentPatternTreeErrors;
    1.89                  patternTree = currentPatternTree;
    1.90              }
    1.91 @@ -379,8 +407,9 @@
    1.92                  //maybe type?
    1.93                  Elements el = jti.getElements();
    1.94                  if (Utilities.isPureMemberSelect(patternTree, false)) {
    1.95 +                    SourcePositions[] varPositions = new SourcePositions[1];
    1.96                      List<Diagnostic<? extends JavaFileObject>> varErrors = new LinkedList<Diagnostic<? extends JavaFileObject>>();
    1.97 -                    Tree var = parseExpression(c, pattern + ".class;", false, new SourcePositions[1], varErrors);
    1.98 +                    Tree var = parseExpression(c, pattern + ".class;", false, varPositions, varErrors);
    1.99  
   1.100                      type = attributeTree(jti, var, scope, varErrors);
   1.101  
   1.102 @@ -389,6 +418,8 @@
   1.103                      CompilationUnitTree cut = ((JavacScope) scope).getEnv().toplevel;
   1.104  
   1.105                      if (!isError(trees.getElement(new TreePath(new TreePath(cut), typeTree)))) {
   1.106 +                        sourcePositions[0] = varPositions[0];
   1.107 +                        offset = 0;
   1.108                          patternTreeErrors = varErrors;
   1.109                          patternTree = typeTree;
   1.110                      }
   1.111 @@ -415,9 +446,13 @@
   1.112          }
   1.113  
   1.114          if (errors != null) {
   1.115 -            errors.addAll(patternTreeErrors);
   1.116 +            for (Diagnostic<? extends JavaFileObject> d : patternTreeErrors) {
   1.117 +                errors.add(new OffsetDiagnostic<JavaFileObject>(d, -offset));
   1.118 +            }
   1.119          }
   1.120  
   1.121 +        sourcePositions[0] = new OffsetSourcePositions(sourcePositions[0], -offset);
   1.122 +        
   1.123          return patternTree;
   1.124      }
   1.125  
   1.126 @@ -472,8 +507,8 @@
   1.127              Names names = Names.instance(context);
   1.128              Parser parser = new JackpotJavacParser(context, factory, scannerFactory.newScanner(buf, false), false, false, CancelService.instance(context), names);
   1.129              if (parser instanceof JavacParser) {
   1.130 -//                if (pos != null)
   1.131 -//                    pos[0] = new ParserSourcePositions((JavacParser)parser);
   1.132 +                if (pos != null)
   1.133 +                    pos[0] = new ParserSourcePositions((JavacParser)parser);
   1.134                  return parser.parseStatement();
   1.135              }
   1.136              return null;
   1.137 @@ -506,8 +541,8 @@
   1.138              Scanner scanner = scannerFactory.newScanner(buf, false);
   1.139              Parser parser = new JackpotJavacParser(context, factory, scanner, false, false, CancelService.instance(context), names);
   1.140              if (parser instanceof JavacParser) {
   1.141 -//                if (pos != null)
   1.142 -//                    pos[0] = new ParserSourcePositions((JavacParser)parser);
   1.143 +                if (pos != null)
   1.144 +                    pos[0] = new ParserSourcePositions((JavacParser)parser);
   1.145                  JCExpression result = parser.parseExpression();
   1.146  
   1.147                  if (!onlyFullInput || scanner.token() == Token.EOF) {
   1.148 @@ -1128,6 +1163,7 @@
   1.149          @Override
   1.150          protected JCCatch catchClause() {
   1.151              if (S.token() == Token.CATCH) {
   1.152 +                int origPos = S.pos();
   1.153  //                S.pushState();
   1.154                  
   1.155                  Token peeked;
   1.156 @@ -1152,8 +1188,8 @@
   1.157  
   1.158                      return new CatchWildcard(ctx, name, F.Ident(name));
   1.159                  } else {
   1.160 -                    ((PushbackLexer) S).add(Token.CATCH, null);
   1.161 -                    ((PushbackLexer) S).add(null, null);
   1.162 +                    ((PushbackLexer) S).add(Token.CATCH, origPos, null);
   1.163 +                    ((PushbackLexer) S).add(null, -1, null);
   1.164                      S.nextToken();
   1.165                  }
   1.166              }
   1.167 @@ -1163,6 +1199,7 @@
   1.168          @Override
   1.169          public com.sun.tools.javac.util.List<JCTree> classOrInterfaceBodyDeclaration(com.sun.tools.javac.util.Name className, boolean isInterface) {
   1.170              if (S.token() == Token.IDENTIFIER) {
   1.171 +                int identPos = S.pos();
   1.172                  String ident = S.stringVal();
   1.173  
   1.174                  if (ident.startsWith("$")) {
   1.175 @@ -1176,8 +1213,8 @@
   1.176                          return com.sun.tools.javac.util.List.<JCTree>of(F.Ident(name));
   1.177                      }
   1.178                      
   1.179 -                    ((PushbackLexer) S).add(Token.IDENTIFIER, name);
   1.180 -                    ((PushbackLexer) S).add(null, null);
   1.181 +                    ((PushbackLexer) S).add(Token.IDENTIFIER, identPos, name);
   1.182 +                    ((PushbackLexer) S).add(null, -1, null);
   1.183                      S.nextToken();
   1.184                  }
   1.185              }
   1.186 @@ -1197,6 +1234,8 @@
   1.187          @Override
   1.188          protected JCCase switchBlockStatementGroup() {
   1.189              if (S.token() == Token.CASE) {
   1.190 +                int origPos = S.pos();
   1.191 +
   1.192                  S.nextToken();
   1.193  
   1.194                  if (S.token() == Token.IDENTIFIER) {
   1.195 @@ -1216,8 +1255,8 @@
   1.196                      }
   1.197                  }
   1.198  
   1.199 -                ((PushbackLexer) S).add(Token.CASE, null);
   1.200 -                ((PushbackLexer) S).add(null, null);
   1.201 +                ((PushbackLexer) S).add(Token.CASE, origPos, null);
   1.202 +                ((PushbackLexer) S).add(null, -1, null);
   1.203                  S.nextToken();
   1.204              }
   1.205  
   1.206 @@ -1230,18 +1269,22 @@
   1.207  
   1.208          private final Lexer delegate;
   1.209          private final List<Token> tokenBuffer;
   1.210 +        private final List<Integer> posBuffer;
   1.211          private final List<com.sun.tools.javac.util.Name> nameBuffer;
   1.212          private Token currentBufferToken;
   1.213 +        private int   currentBufferPos;
   1.214          private com.sun.tools.javac.util.Name currentBufferName;
   1.215  
   1.216          public PushbackLexer(Lexer delegate) {
   1.217              this.delegate = delegate;
   1.218              this.tokenBuffer = new LinkedList<Token>();
   1.219 +            this.posBuffer = new LinkedList<Integer>();
   1.220              this.nameBuffer = new LinkedList<com.sun.tools.javac.util.Name>();
   1.221          }
   1.222  
   1.223 -        public void add(Token token, com.sun.tools.javac.util.Name name) {
   1.224 +        public void add(Token token, int pos, com.sun.tools.javac.util.Name name) {
   1.225              tokenBuffer.add(token);
   1.226 +            posBuffer.add(pos);
   1.227              nameBuffer.add(name);
   1.228          }
   1.229          
   1.230 @@ -1272,12 +1315,14 @@
   1.231          }
   1.232  
   1.233          public int pos() {
   1.234 +            if (currentBufferToken != null) return currentBufferPos;
   1.235              return delegate.pos();
   1.236          }
   1.237  
   1.238          public void nextToken() {
   1.239              if (!tokenBuffer.isEmpty()) {
   1.240                  currentBufferToken = tokenBuffer.remove(0);
   1.241 +                currentBufferPos = posBuffer.remove(0);
   1.242                  currentBufferName  = nameBuffer.remove(0);
   1.243              }
   1.244              else delegate.nextToken();
   1.245 @@ -1390,4 +1435,88 @@
   1.246              errors.add(diagnostic);
   1.247          }
   1.248      }
   1.249 +
   1.250 +    private static final class OffsetSourcePositions implements SourcePositions {
   1.251 +
   1.252 +        private final SourcePositions delegate;
   1.253 +        private final long offset;
   1.254 +
   1.255 +        public OffsetSourcePositions(SourcePositions delegate, long offset) {
   1.256 +            this.delegate = delegate;
   1.257 +            this.offset = offset;
   1.258 +        }
   1.259 +
   1.260 +        public long getStartPosition(CompilationUnitTree cut, Tree tree) {
   1.261 +            return delegate.getStartPosition(cut, tree) + offset;
   1.262 +        }
   1.263 +
   1.264 +        public long getEndPosition(CompilationUnitTree cut, Tree tree) {
   1.265 +            return delegate.getEndPosition(cut, tree) + offset;
   1.266 +        }
   1.267 +
   1.268 +    }
   1.269 +
   1.270 +    private static final class OffsetDiagnostic<S> implements Diagnostic<S> {
   1.271 +        private final Diagnostic<? extends S> delegate;
   1.272 +        private final long offset;
   1.273 +
   1.274 +        public OffsetDiagnostic(Diagnostic<? extends S> delegate, long offset) {
   1.275 +            this.delegate = delegate;
   1.276 +            this.offset = offset;
   1.277 +        }
   1.278 +
   1.279 +        public Diagnostic.Kind getKind() {
   1.280 +            return delegate.getKind();
   1.281 +        }
   1.282 +
   1.283 +        public S getSource() {
   1.284 +            return delegate.getSource();
   1.285 +        }
   1.286 +
   1.287 +        public long getPosition() {
   1.288 +            return delegate.getPosition() + offset;
   1.289 +        }
   1.290 +
   1.291 +        public long getStartPosition() {
   1.292 +            return delegate.getStartPosition() + offset;
   1.293 +        }
   1.294 +
   1.295 +        public long getEndPosition() {
   1.296 +            return delegate.getEndPosition() + offset;
   1.297 +        }
   1.298 +
   1.299 +        public long getLineNumber() {
   1.300 +            throw new UnsupportedOperationException("Not supported yet.");
   1.301 +        }
   1.302 +
   1.303 +        public long getColumnNumber() {
   1.304 +            throw new UnsupportedOperationException("Not supported yet.");
   1.305 +        }
   1.306 +
   1.307 +        public String getCode() {
   1.308 +            return delegate.getCode();
   1.309 +        }
   1.310 +
   1.311 +        public String getMessage(Locale locale) {
   1.312 +            return delegate.getMessage(locale);
   1.313 +        }
   1.314 +
   1.315 +    }
   1.316 +
   1.317 +    private static class ParserSourcePositions implements SourcePositions {
   1.318 +
   1.319 +        private JavacParser parser;
   1.320 +
   1.321 +        private ParserSourcePositions(JavacParser parser) {
   1.322 +            this.parser = parser;
   1.323 +        }
   1.324 +
   1.325 +        public long getStartPosition(CompilationUnitTree file, Tree tree) {
   1.326 +            return parser.getStartPos((JCTree)tree);
   1.327 +        }
   1.328 +
   1.329 +        public long getEndPosition(CompilationUnitTree file, Tree tree) {
   1.330 +            return parser.getEndPos((JCTree)tree);
   1.331 +        }
   1.332 +    }
   1.333  }
     2.1 --- a/api/test/unit/src/org/netbeans/modules/jackpot30/impl/UtilitiesTest.java	Wed Dec 15 19:11:13 2010 +0100
     2.2 +++ b/api/test/unit/src/org/netbeans/modules/jackpot30/impl/UtilitiesTest.java	Wed Dec 15 19:27:30 2010 +0100
     2.3 @@ -43,8 +43,11 @@
     2.4  import com.sun.source.tree.Scope;
     2.5  import com.sun.source.tree.Tree;
     2.6  import com.sun.source.tree.Tree.Kind;
     2.7 +import com.sun.source.util.SourcePositions;
     2.8  import com.sun.source.util.TreePath;
     2.9 +import com.sun.source.util.TreeScanner;
    2.10  import com.sun.tools.javac.tree.JCTree;
    2.11 +import java.util.ArrayList;
    2.12  import java.util.Arrays;
    2.13  import java.util.Collection;
    2.14  import java.util.Collections;
    2.15 @@ -282,30 +285,66 @@
    2.16      public void testErrorsForPatterns1() throws Exception {
    2.17          prepareTest("test/Test.java", "package test; public class Test{}");
    2.18  
    2.19 +        SourcePositions[] positions = new SourcePositions[1];
    2.20          Collection<Diagnostic<? extends JavaFileObject>> errors = new LinkedList<Diagnostic<? extends JavaFileObject>>();
    2.21 -        Tree result = Utilities.parseAndAttribute(info, "foo bar", null, errors);
    2.22 -        List<String> errorStrings = new LinkedList<String>();
    2.23 +        String code = "foo bar";
    2.24 +        Tree result = Utilities.parseAndAttribute(info, code, null, positions, errors);
    2.25  
    2.26 -        for (Diagnostic<? extends JavaFileObject> d : errors) {
    2.27 -            errorStrings.add(d.getCode());
    2.28 -        }
    2.29 -
    2.30 -        assertEquals(Arrays.asList("compiler.err.expected"), errorStrings);
    2.31 +        assertDiagnostics(errors, "7-7:compiler.err.expected");
    2.32 +        assertPositions(result, positions[0], code, "foo", "foo bar");
    2.33      }
    2.34  
    2.35      public void testErrorsForPatterns2() throws Exception {
    2.36          prepareTest("test/Test.java", "package test; public class Test{}");
    2.37  
    2.38 +        SourcePositions[] positions = new SourcePositions[1];
    2.39          Scope s = Utilities.constructScope(info, Collections.<String, TypeMirror>emptyMap());
    2.40          Collection<Diagnostic<? extends JavaFileObject>> errors = new LinkedList<Diagnostic<? extends JavaFileObject>>();
    2.41 -        Tree result = Utilities.parseAndAttribute(info, "$1.isDirectory()", s, errors);
    2.42 -        List<String> errorStrings = new LinkedList<String>();
    2.43 +        String code = "$1.isDirectory()";
    2.44 +        Tree result = Utilities.parseAndAttribute(info, code, s, positions, errors);
    2.45  
    2.46 -        for (Diagnostic<? extends JavaFileObject> d : errors) {
    2.47 -            errorStrings.add(d.getCode());
    2.48 -        }
    2.49 +        assertDiagnostics(errors, "0-0:compiler.err.cant.resolve.location");
    2.50 +        assertPositions(result, positions[0], code, "$1", "$1.isDirectory", "$1.isDirectory()");
    2.51 +    }
    2.52  
    2.53 -        assertEquals(Arrays.asList("compiler.err.cant.resolve.location"), errorStrings);
    2.54 +    public void testErrorsForPatterns3() throws Exception {
    2.55 +        prepareTest("test/Test.java", "package test; public class Test{}");
    2.56 +
    2.57 +        SourcePositions[] positions = new SourcePositions[1];
    2.58 +        Collection<Diagnostic<? extends JavaFileObject>> errors = new LinkedList<Diagnostic<? extends JavaFileObject>>();
    2.59 +        String code = "if ($cond) { foo() } else $else;";
    2.60 +        Tree result = Utilities.parseAndAttribute(info, code, null, positions, errors);
    2.61 +
    2.62 +        assertDiagnostics(errors, "18-18:compiler.err.expected");
    2.63 +        assertPositions(result, positions[0], code, "$cond", "$else", "$else;", "($cond)", "foo", "foo()", "foo() ", "if ($cond) { foo() } else $else;", "{ foo() }");
    2.64 +    }
    2.65 +
    2.66 +    public void testPositionsForCorrectStatement() throws Exception {
    2.67 +        prepareTest("test/Test.java", "package test; public class Test{}");
    2.68 +
    2.69 +        SourcePositions[] positions = new SourcePositions[1];
    2.70 +        Collection<Diagnostic<? extends JavaFileObject>> errors = new LinkedList<Diagnostic<? extends JavaFileObject>>();
    2.71 +        String code = "assert true;";
    2.72 +        Tree result = Utilities.parseAndAttribute(info, code, null, positions, errors);
    2.73 +
    2.74 +        assertTrue(errors.isEmpty());
    2.75 +        assertPositions(result, positions[0], code, "assert true;", "true");
    2.76 +    }
    2.77 +
    2.78 +    public void testCasePattern() throws Exception {
    2.79 +        prepareTest("test/Test.java", "package test; public class Test{}");
    2.80 +
    2.81 +        SourcePositions[] positions = new SourcePositions[1];
    2.82 +        Collection<Diagnostic<? extends JavaFileObject>> errors = new LinkedList<Diagnostic<? extends JavaFileObject>>();
    2.83 +        String code = "case $expr: foo bar $stmts$;\n";
    2.84 +        Tree result = Utilities.parseAndAttribute(info, code, null, positions, errors);
    2.85 +
    2.86 +        assertTrue(result.getKind().name(), result.getKind() == Kind.CASE);
    2.87 +
    2.88 +        String golden = "case $expr: foo bar; $stmts$; ";
    2.89 +        assertEquals(golden.replaceAll("[ \n\r]+", " "), result.toString().replaceAll("[ \n\r]+", " "));
    2.90 +        assertDiagnostics(errors, "19-19:compiler.err.expected");
    2.91 +        assertPositions(result, positions[0], code, "$expr", "$stmts$", "$stmts$;", "case $expr: foo bar $stmts$;", "foo", "foo bar ");
    2.92      }
    2.93  
    2.94      public void testLambdaPattern() throws Exception {
    2.95 @@ -374,4 +413,41 @@
    2.96                       repr.replaceAll("[ \n\t]+", " "));
    2.97      }
    2.98  
    2.99 +    private void assertDiagnostics(Collection<Diagnostic<? extends JavaFileObject>> errors, String... golden) {
   2.100 +        List<String> actual = new ArrayList<String>(errors.size());
   2.101 +
   2.102 +        for (Diagnostic<? extends JavaFileObject> d : errors) {
   2.103 +            actual.add(d.getStartPosition() + "-" + d.getEndPosition() + ":" + d.getCode());
   2.104 +        }
   2.105 +
   2.106 +        assertEquals(Arrays.asList(golden), actual);
   2.107 +    }
   2.108 +
   2.109 +    private void assertPositions(Tree t, final SourcePositions sp, final String code, String... golden) {
   2.110 +        final List<String> actual = new ArrayList<String>(golden.length);
   2.111 +
   2.112 +        new TreeScanner<Void, Void>() {
   2.113 +            @Override
   2.114 +            public Void scan(Tree node, Void p) {
   2.115 +                if (node != null) {
   2.116 +                    int start = (int) sp.getStartPosition(null, node);
   2.117 +                    int end = (int) sp.getEndPosition(null, node);
   2.118 +
   2.119 +                    if (start >= 0 && end >= 0) {
   2.120 +                        actual.add(code.substring(start, end));
   2.121 +                    }
   2.122 +                }
   2.123 +                return super.scan(node, p);
   2.124 +            }
   2.125 +        }.scan(t, null);
   2.126 +
   2.127 +        Collections.sort(actual);
   2.128 +
   2.129 +        List<String> goldenList = new ArrayList<String>(Arrays.asList(golden));
   2.130 +
   2.131 +        Collections.sort(goldenList);
   2.132 +
   2.133 +        assertEquals(goldenList, actual);
   2.134 +    }
   2.135 +
   2.136  }