Utilities.parseAndAttribute should be able to return positions of (sub)trees of the pattern and correcting locations of parse errors.
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 }