DocCommentTester.java revision 3060:23f76aadbb36
1/* 2 * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24import java.io.File; 25import java.io.FileWriter; 26import java.io.IOException; 27import java.io.PrintWriter; 28import java.io.StringWriter; 29import java.io.Writer; 30import java.util.ArrayList; 31import java.util.List; 32import java.util.regex.Matcher; 33import java.util.regex.Pattern; 34 35import javax.lang.model.element.Name; 36import javax.tools.JavaFileObject; 37import javax.tools.StandardJavaFileManager; 38 39import com.sun.source.doctree.*; 40import com.sun.source.tree.ClassTree; 41import com.sun.source.tree.CompilationUnitTree; 42import com.sun.source.tree.MethodTree; 43import com.sun.source.tree.Tree; 44import com.sun.source.tree.VariableTree; 45import com.sun.source.util.DocTreeScanner; 46import com.sun.source.util.DocTrees; 47import com.sun.source.util.JavacTask; 48import com.sun.source.util.TreePath; 49import com.sun.source.util.TreePathScanner; 50import com.sun.tools.javac.api.JavacTool; 51import com.sun.tools.javac.tree.DCTree; 52import com.sun.tools.javac.tree.DCTree.DCDocComment; 53import com.sun.tools.javac.tree.DCTree.DCErroneous; 54import com.sun.tools.javac.tree.DocPretty; 55 56public class DocCommentTester { 57 58 public static void main(String... args) throws Exception { 59 new DocCommentTester().run(args); 60 } 61 62 public void run(String... args) throws Exception { 63 String testSrc = System.getProperty("test.src"); 64 65 List<File> files = new ArrayList<File>(); 66 for (String arg: args) 67 files.add(new File(testSrc, arg)); 68 69 JavacTool javac = JavacTool.create(); 70 StandardJavaFileManager fm = javac.getStandardFileManager(null, null, null); 71 72 Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromFiles(files); 73 74 JavacTask t = javac.getTask(null, fm, null, null, null, fos); 75 final DocTrees trees = DocTrees.instance(t); 76 77 final Checker[] checkers = { 78 new ASTChecker(this, trees), 79 new PosChecker(this, trees), 80 new PrettyChecker(this, trees) 81 }; 82 83 DeclScanner d = new DeclScanner() { 84 @Override 85 public Void visitCompilationUnit(CompilationUnitTree tree, Void ignore) { 86 for (Checker c: checkers) 87 c.visitCompilationUnit(tree); 88 return super.visitCompilationUnit(tree, ignore); 89 } 90 91 @Override 92 void visitDecl(Tree tree, Name name) { 93 TreePath path = getCurrentPath(); 94 String dc = trees.getDocComment(path); 95 if (dc != null) { 96 for (Checker c : checkers) { 97 try { 98 System.err.println(path.getLeaf().getKind() 99 + " " + name 100 + " " + c.getClass().getSimpleName()); 101 102 c.check(path, name); 103 104 System.err.println(); 105 } catch (Exception e) { 106 error("Exception " + e); 107 e.printStackTrace(System.err); 108 } 109 } 110 } 111 } 112 }; 113 114 Iterable<? extends CompilationUnitTree> units = t.parse(); 115 for (CompilationUnitTree unit: units) { 116 d.scan(unit, null); 117 } 118 119 if (errors > 0) 120 throw new Exception(errors + " errors occurred"); 121 } 122 123 static abstract class DeclScanner extends TreePathScanner<Void, Void> { 124 abstract void visitDecl(Tree tree, Name name); 125 126 @Override 127 public Void visitClass(ClassTree tree, Void ignore) { 128 super.visitClass(tree, ignore); 129 visitDecl(tree, tree.getSimpleName()); 130 return null; 131 } 132 133 @Override 134 public Void visitMethod(MethodTree tree, Void ignore) { 135 super.visitMethod(tree, ignore); 136 visitDecl(tree, tree.getName()); 137 return null; 138 } 139 140 @Override 141 public Void visitVariable(VariableTree tree, Void ignore) { 142 super.visitVariable(tree, ignore); 143 visitDecl(tree, tree.getName()); 144 return null; 145 } 146 } 147 148 /** 149 * Base class for checkers to check the doc comment on a declaration 150 * (when present.) 151 */ 152 abstract class Checker { 153 final DocTrees trees; 154 155 Checker(DocTrees trees) { 156 this.trees = trees; 157 } 158 159 void visitCompilationUnit(CompilationUnitTree tree) { } 160 161 abstract void check(TreePath tree, Name name) throws Exception; 162 163 void error(String msg) { 164 DocCommentTester.this.error(msg); 165 } 166 } 167 168 void error(String msg) { 169 System.err.println("Error: " + msg); 170 errors++; 171 } 172 173 int errors; 174 175 /** 176 * Verify the structure of the DocTree AST by comparing it against golden text. 177 */ 178 static class ASTChecker extends Checker { 179 static final String NEWLINE = System.getProperty("line.separator"); 180 Printer printer = new Printer(); 181 String source; 182 183 ASTChecker(DocCommentTester test, DocTrees t) { 184 test.super(t); 185 } 186 187 @Override 188 void visitCompilationUnit(CompilationUnitTree tree) { 189 try { 190 source = tree.getSourceFile().getCharContent(true).toString(); 191 } catch (IOException e) { 192 source = ""; 193 } 194 } 195 196 void check(TreePath path, Name name) { 197 StringWriter out = new StringWriter(); 198 DocCommentTree dc = trees.getDocCommentTree(path); 199 printer.print(dc, out); 200 out.flush(); 201 String found = out.toString().replace(NEWLINE, "\n"); 202 203 // Look for the first block comment after the first occurrence of name 204 int start = source.indexOf("\n/*\n", findName(source, name)); 205 int end = source.indexOf("\n*/\n", start); 206 String expect = source.substring(start + 4, end + 1); 207 if (!found.equals(expect)) { 208 System.err.println("Expect:\n" + expect); 209 System.err.println("Found:\n" + found); 210 error("AST mismatch for " + name); 211 } 212 } 213 214 /** 215 * This main program is to set up the golden comments used by this 216 * checker. 217 * Usage: 218 * java DocCommentTester$ASTChecker -o dir file... 219 * The given files are written to the output directory with their 220 * golden comments updated. The intent is that the files should 221 * then be compared with the originals, e.g. with meld, and if the 222 * changes are approved, the new files can be used to replace the old. 223 */ 224 public static void main(String... args) throws Exception { 225 List<File> files = new ArrayList<File>(); 226 File o = null; 227 for (int i = 0; i < args.length; i++) { 228 String arg = args[i]; 229 if (arg.equals("-o")) 230 o = new File(args[++i]); 231 else if (arg.startsWith("-")) 232 throw new IllegalArgumentException(arg); 233 else { 234 files.add(new File(arg)); 235 } 236 } 237 238 if (o == null) 239 throw new IllegalArgumentException("no output dir specified"); 240 final File outDir = o; 241 242 JavacTool javac = JavacTool.create(); 243 StandardJavaFileManager fm = javac.getStandardFileManager(null, null, null); 244 Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromFiles(files); 245 246 JavacTask t = javac.getTask(null, fm, null, null, null, fos); 247 final DocTrees trees = DocTrees.instance(t); 248 249 DeclScanner d = new DeclScanner() { 250 Printer p = new Printer(); 251 String source; 252 253 @Override 254 public Void visitCompilationUnit(CompilationUnitTree tree, Void ignore) { 255 System.err.println("processing " + tree.getSourceFile().getName()); 256 try { 257 source = tree.getSourceFile().getCharContent(true).toString(); 258 } catch (IOException e) { 259 source = ""; 260 } 261 // remove existing gold by removing all block comments after the first '{'. 262 int start = source.indexOf("{"); 263 while ((start = source.indexOf("\n/*\n", start)) != -1) { 264 int end = source.indexOf("\n*/\n"); 265 source = source.substring(0, start + 1) + source.substring(end + 4); 266 } 267 268 // process decls in compilation unit 269 super.visitCompilationUnit(tree, ignore); 270 271 // write the modified source 272 File f = new File(tree.getSourceFile().getName()); 273 File outFile = new File(outDir, f.getName()); 274 try { 275 FileWriter out = new FileWriter(outFile); 276 try { 277 out.write(source); 278 } finally { 279 out.close(); 280 } 281 } catch (IOException e) { 282 System.err.println("Can't write " + tree.getSourceFile().getName() 283 + " to " + outFile + ": " + e); 284 } 285 return null; 286 } 287 288 @Override 289 void visitDecl(Tree tree, Name name) { 290 DocTree dc = trees.getDocCommentTree(getCurrentPath()); 291 if (dc != null) { 292 StringWriter out = new StringWriter(); 293 p.print(dc, out); 294 String found = out.toString(); 295 296 // Look for the empty line after the first occurrence of name 297 int pos = source.indexOf("\n\n", findName(source, name)); 298 299 // Insert the golden comment 300 source = source.substring(0, pos) 301 + "\n/*\n" 302 + found 303 + "*/" 304 + source.substring(pos); 305 } 306 } 307 308 }; 309 310 Iterable<? extends CompilationUnitTree> units = t.parse(); 311 for (CompilationUnitTree unit: units) { 312 d.scan(unit, null); 313 } 314 } 315 316 static int findName(String source, Name name) { 317 Pattern p = Pattern.compile("\\s" + name + "[(;]"); 318 Matcher m = p.matcher(source); 319 if (!m.find()) 320 throw new Error("cannot find " + name); 321 return m.start(); 322 } 323 324 static class Printer implements DocTreeVisitor<Void, Void> { 325 PrintWriter out; 326 327 void print(DocTree tree, Writer out) { 328 this.out = (out instanceof PrintWriter) 329 ? (PrintWriter) out : new PrintWriter(out); 330 tree.accept(this, null); 331 this.out.flush(); 332 } 333 334 public Void visitAttribute(AttributeTree node, Void p) { 335 header(node); 336 indent(+1); 337 print("name", node.getName().toString()); 338 print("vkind", node.getValueKind().toString()); 339 print("value", node.getValue()); 340 indent(-1); 341 indent(); 342 out.println("]"); 343 return null; 344 } 345 346 public Void visitAuthor(AuthorTree node, Void p) { 347 header(node); 348 indent(+1); 349 print("name", node.getName()); 350 indent(-1); 351 indent(); 352 out.println("]"); 353 return null; 354 } 355 356 public Void visitComment(CommentTree node, Void p) { 357 header(node, compress(node.getBody())); 358 return null; 359 } 360 361 public Void visitDeprecated(DeprecatedTree node, Void p) { 362 header(node); 363 indent(+1); 364 print("body", node.getBody()); 365 indent(-1); 366 indent(); 367 out.println("]"); 368 return null; 369 } 370 371 public Void visitDocComment(DocCommentTree node, Void p) { 372 header(node); 373 indent(+1); 374 print("firstSentence", node.getFirstSentence()); 375 print("body", node.getBody()); 376 print("block tags", node.getBlockTags()); 377 indent(-1); 378 indent(); 379 out.println("]"); 380 return null; 381 } 382 383 public Void visitDocRoot(DocRootTree node, Void p) { 384 header(node, ""); 385 return null; 386 } 387 388 public Void visitEndElement(EndElementTree node, Void p) { 389 header(node, node.getName().toString()); 390 return null; 391 } 392 393 public Void visitEntity(EntityTree node, Void p) { 394 header(node, node.getName().toString()); 395 return null; 396 } 397 398 public Void visitErroneous(ErroneousTree node, Void p) { 399 header(node); 400 indent(+1); 401 print("code", ((DCErroneous) node).diag.getCode()); 402 print("body", compress(node.getBody())); 403 indent(-1); 404 indent(); 405 out.println("]"); 406 return null; 407 } 408 409 public Void visitIdentifier(IdentifierTree node, Void p) { 410 header(node, compress(node.getName().toString())); 411 return null; 412 } 413 414 public Void visitInheritDoc(InheritDocTree node, Void p) { 415 header(node, ""); 416 return null; 417 } 418 419 public Void visitLink(LinkTree node, Void p) { 420 header(node); 421 indent(+1); 422 print("reference", node.getReference()); 423 print("body", node.getLabel()); 424 indent(-1); 425 indent(); 426 out.println("]"); 427 return null; 428 } 429 430 public Void visitLiteral(LiteralTree node, Void p) { 431 header(node, compress(node.getBody().getBody())); 432 return null; 433 } 434 435 public Void visitParam(ParamTree node, Void p) { 436 header(node); 437 indent(+1); 438 print("name", node.getName()); 439 print("description", node.getDescription()); 440 indent(-1); 441 indent(); 442 out.println("]"); 443 return null; 444 } 445 446 public Void visitReference(ReferenceTree node, Void p) { 447 header(node, compress(node.getSignature())); 448 return null; 449 } 450 451 public Void visitReturn(ReturnTree node, Void p) { 452 header(node); 453 indent(+1); 454 print("description", node.getDescription()); 455 indent(-1); 456 indent(); 457 out.println("]"); 458 return null; 459 } 460 461 public Void visitSee(SeeTree node, Void p) { 462 header(node); 463 indent(+1); 464 print("reference", node.getReference()); 465 indent(-1); 466 indent(); 467 out.println("]"); 468 return null; 469 } 470 471 public Void visitSerial(SerialTree node, Void p) { 472 header(node); 473 indent(+1); 474 print("description", node.getDescription()); 475 indent(-1); 476 indent(); 477 out.println("]"); 478 return null; 479 } 480 481 public Void visitSerialData(SerialDataTree node, Void p) { 482 header(node); 483 indent(+1); 484 print("description", node.getDescription()); 485 indent(-1); 486 indent(); 487 out.println("]"); 488 return null; 489 } 490 491 public Void visitSerialField(SerialFieldTree node, Void p) { 492 header(node); 493 indent(+1); 494 print("name", node.getName()); 495 print("type", node.getType()); 496 print("description", node.getDescription()); 497 indent(-1); 498 indent(); 499 out.println("]"); 500 return null; 501 } 502 503 public Void visitSince(SinceTree node, Void p) { 504 header(node); 505 indent(+1); 506 print("body", node.getBody()); 507 indent(-1); 508 indent(); 509 out.println("]"); 510 return null; 511 } 512 513 public Void visitStartElement(StartElementTree node, Void p) { 514 header(node); 515 indent(+1); 516 indent(); 517 out.println("name:" + node.getName()); 518 print("attributes", node.getAttributes()); 519 indent(-1); 520 indent(); 521 out.println("]"); 522 return null; 523 } 524 525 public Void visitText(TextTree node, Void p) { 526 header(node, compress(node.getBody())); 527 return null; 528 } 529 530 public Void visitThrows(ThrowsTree node, Void p) { 531 header(node); 532 indent(+1); 533 print("exceptionName", node.getExceptionName()); 534 print("description", node.getDescription()); 535 indent(-1); 536 indent(); 537 out.println("]"); 538 return null; 539 } 540 541 public Void visitUnknownBlockTag(UnknownBlockTagTree node, Void p) { 542 header(node); 543 indent(+1); 544 indent(); 545 out.println("tag:" + node.getTagName()); 546 print("content", node.getContent()); 547 indent(-1); 548 indent(); 549 out.println("]"); 550 return null; 551 } 552 553 public Void visitUnknownInlineTag(UnknownInlineTagTree node, Void p) { 554 header(node); 555 indent(+1); 556 indent(); 557 out.println("tag:" + node.getTagName()); 558 print("content", node.getContent()); 559 indent(-1); 560 indent(); 561 out.println("]"); 562 return null; 563 } 564 565 public Void visitValue(ValueTree node, Void p) { 566 header(node); 567 indent(+1); 568 print("reference", node.getReference()); 569 indent(-1); 570 indent(); 571 out.println("]"); 572 return null; 573 } 574 575 public Void visitVersion(VersionTree node, Void p) { 576 header(node); 577 indent(+1); 578 print("body", node.getBody()); 579 indent(-1); 580 indent(); 581 out.println("]"); 582 return null; 583 } 584 585 public Void visitOther(DocTree node, Void p) { 586 throw new UnsupportedOperationException("Not supported yet."); 587 } 588 589 void header(DocTree node) { 590 indent(); 591 out.println(simpleClassName(node) + "[" + node.getKind() + ", pos:" + ((DCTree) node).pos); 592 } 593 594 void header(DocTree node, String rest) { 595 indent(); 596 out.println(simpleClassName(node) + "[" + node.getKind() + ", pos:" + ((DCTree) node).pos 597 + (rest.isEmpty() ? "" : ", " + rest) 598 + "]"); 599 } 600 601 String simpleClassName(DocTree node) { 602 return node.getClass().getSimpleName().replaceAll("DC(.*)", "$1"); 603 } 604 605 void print(String name, DocTree item) { 606 indent(); 607 if (item == null) 608 out.println(name + ": null"); 609 else { 610 out.println(name + ":"); 611 indent(+1); 612 item.accept(this, null); 613 indent(-1); 614 } 615 } 616 617 void print(String name, String s) { 618 indent(); 619 out.println(name + ": " + s); 620 } 621 622 void print(String name, List<? extends DocTree> list) { 623 indent(); 624 if (list == null) 625 out.println(name + ": null"); 626 else if (list.isEmpty()) 627 out.println(name + ": empty"); 628 else { 629 out.println(name + ": " + list.size()); 630 indent(+1); 631 for (DocTree tree: list) { 632 tree.accept(this, null); 633 } 634 indent(-1); 635 } 636 } 637 638 int indent = 0; 639 640 void indent() { 641 for (int i = 0; i < indent; i++) { 642 out.print(" "); 643 } 644 } 645 646 void indent(int n) { 647 indent += n; 648 } 649 650 String compress(String s) { 651 s = s.replace("\n", "|").replace(" ", "_"); 652 return (s.length() < 32) 653 ? s 654 : s.substring(0, 16) + "..." + s.substring(16); 655 } 656 657 String quote(String s) { 658 if (s.contains("\"")) 659 return "'" + s + "'"; 660 else if (s.contains("'") || s.contains(" ")) 661 return '"' + s + '"'; 662 else 663 return s; 664 } 665 } 666 } 667 668 /** 669 * Verify the reported tree positions by comparing the characters found 670 * at and after the reported position with the beginning of the pretty- 671 * printed text. 672 */ 673 static class PosChecker extends Checker { 674 PosChecker(DocCommentTester test, DocTrees t) { 675 test.super(t); 676 } 677 678 @Override 679 void check(TreePath path, Name name) throws Exception { 680 JavaFileObject fo = path.getCompilationUnit().getSourceFile(); 681 final CharSequence cs = fo.getCharContent(true); 682 683 final DCDocComment dc = (DCDocComment) trees.getDocCommentTree(path); 684 DCTree t = (DCTree) trees.getDocCommentTree(path); 685 686 DocTreeScanner scanner = new DocTreeScanner<Void,Void>() { 687 @Override 688 public Void scan(DocTree node, Void ignore) { 689 if (node != null) { 690 try { 691 String expect = getExpectText(node); 692 long pos = ((DCTree) node).getSourcePosition(dc); 693 String found = getFoundText(cs, (int) pos, expect.length()); 694 if (!found.equals(expect)) { 695 System.err.println("expect: " + expect); 696 System.err.println("found: " + found); 697 error("mismatch"); 698 } 699 700 } catch (StringIndexOutOfBoundsException e) { 701 error(node.getClass() + ": " + e.toString()); 702 e.printStackTrace(); 703 } 704 } 705 return super.scan(node, ignore); 706 } 707 }; 708 709 scanner.scan(t, null); 710 } 711 712 String getExpectText(DocTree t) { 713 StringWriter sw = new StringWriter(); 714 DocPretty p = new DocPretty(sw); 715 try { p.print(t); } catch (IOException never) { } 716 String s = sw.toString(); 717 if (s.length() <= 1) 718 return s; 719 int ws = s.replaceAll("\\s+", " ").indexOf(" "); 720 if (ws != -1) s = s.substring(0, ws); 721 return (s.length() < 5) ? s : s.substring(0, 5); 722 } 723 724 String getFoundText(CharSequence cs, int pos, int len) { 725 return (pos == -1) ? "" : cs.subSequence(pos, Math.min(pos + len, cs.length())).toString(); 726 } 727 } 728 729 /** 730 * Verify the pretty printed text against a normalized form of the 731 * original doc comment. 732 */ 733 static class PrettyChecker extends Checker { 734 735 PrettyChecker(DocCommentTester test, DocTrees t) { 736 test.super(t); 737 } 738 739 @Override 740 void check(TreePath path, Name name) throws Exception { 741 String raw = trees.getDocComment(path); 742 String normRaw = normalize(raw); 743 744 StringWriter out = new StringWriter(); 745 DocPretty dp = new DocPretty(out); 746 dp.print(trees.getDocCommentTree(path)); 747 String pretty = out.toString(); 748 749 if (!pretty.equals(normRaw)) { 750 error("mismatch"); 751 System.err.println("*** expected:"); 752 System.err.println(normRaw.replace(" ", "_")); 753 System.err.println("*** found:"); 754 System.err.println(pretty.replace(" ", "_")); 755 // throw new Error(); 756 } 757 } 758 759 /** 760 * Normalize white space in places where the tree does not preserve it. 761 */ 762 String normalize(String s) { 763 s = s.trim() 764 .replaceFirst("\\.\\s*\\n *@", ".\n@") 765 .replaceAll("\\{@docRoot\\s+\\}", "{@docRoot}") 766 .replaceAll("\\{@inheritDoc\\s+\\}", "{@inheritDoc}") 767 .replaceAll("(\\{@value\\s+[^}]+)\\s+(\\})", "$1$2") 768 .replaceAll("\n[ \t]+@", "\n@") 769 .replaceAll("(\\{@code)(\\x20)(\\s+.*)", "$1$3"); 770 return s; 771 } 772 } 773} 774 775