2
* Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
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.
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).
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.
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
24
import java.io.IOException;
25
import java.io.PrintWriter;
26
import java.io.StringWriter;
28
import java.nio.file.Files;
29
import java.nio.file.Path;
30
import java.text.BreakIterator;
31
import java.util.ArrayList;
32
import java.util.Arrays;
34
import java.util.Locale;
35
import java.util.regex.Matcher;
36
import java.util.regex.Pattern;
37
import java.util.stream.Collectors;
39
import javax.lang.model.element.Name;
40
import javax.lang.model.util.Elements;
41
import javax.tools.Diagnostic;
42
import javax.tools.JavaFileObject;
43
import javax.tools.StandardJavaFileManager;
45
import com.sun.source.doctree.AttributeTree;
46
import com.sun.source.doctree.AuthorTree;
47
import com.sun.source.doctree.CommentTree;
48
import com.sun.source.doctree.DeprecatedTree;
49
import com.sun.source.doctree.DocCommentTree;
50
import com.sun.source.doctree.DocRootTree;
51
import com.sun.source.doctree.DocTree;
52
import com.sun.source.doctree.DocTreeVisitor;
53
import com.sun.source.doctree.DocTypeTree;
54
import com.sun.source.doctree.EndElementTree;
55
import com.sun.source.doctree.EntityTree;
56
import com.sun.source.doctree.ErroneousTree;
57
import com.sun.source.doctree.EscapeTree;
58
import com.sun.source.doctree.HiddenTree;
59
import com.sun.source.doctree.IdentifierTree;
60
import com.sun.source.doctree.IndexTree;
61
import com.sun.source.doctree.InheritDocTree;
62
import com.sun.source.doctree.LinkTree;
63
import com.sun.source.doctree.LiteralTree;
64
import com.sun.source.doctree.ParamTree;
65
import com.sun.source.doctree.ProvidesTree;
66
import com.sun.source.doctree.RawTextTree;
67
import com.sun.source.doctree.ReferenceTree;
68
import com.sun.source.doctree.ReturnTree;
69
import com.sun.source.doctree.SeeTree;
70
import com.sun.source.doctree.SerialDataTree;
71
import com.sun.source.doctree.SerialFieldTree;
72
import com.sun.source.doctree.SerialTree;
73
import com.sun.source.doctree.SinceTree;
74
import com.sun.source.doctree.SnippetTree;
75
import com.sun.source.doctree.SpecTree;
76
import com.sun.source.doctree.StartElementTree;
77
import com.sun.source.doctree.SummaryTree;
78
import com.sun.source.doctree.SystemPropertyTree;
79
import com.sun.source.doctree.TextTree;
80
import com.sun.source.doctree.ThrowsTree;
81
import com.sun.source.doctree.UnknownBlockTagTree;
82
import com.sun.source.doctree.UnknownInlineTagTree;
83
import com.sun.source.doctree.UsesTree;
84
import com.sun.source.doctree.ValueTree;
85
import com.sun.source.doctree.VersionTree;
86
import com.sun.source.tree.ClassTree;
87
import com.sun.source.tree.CompilationUnitTree;
88
import com.sun.source.tree.MethodTree;
89
import com.sun.source.tree.Tree;
90
import com.sun.source.tree.VariableTree;
91
import com.sun.source.util.DocTreeScanner;
92
import com.sun.source.util.DocTrees;
93
import com.sun.source.util.JavacTask;
94
import com.sun.source.util.TreePath;
95
import com.sun.source.util.TreePathScanner;
96
import com.sun.tools.javac.api.JavacTool;
97
import com.sun.tools.javac.api.JavacTrees;
98
import com.sun.tools.javac.tree.DCTree;
99
import com.sun.tools.javac.tree.DCTree.DCDocComment;
100
import com.sun.tools.javac.tree.DCTree.DCErroneous;
101
import com.sun.tools.javac.tree.DocPretty;
104
* A class to test doc comment trees.
105
* It is normally executed by calling {@code main}, providing a source file to be analyzed.
106
* The file is scanned for top-level declarations, and the comment for any such declarations
107
* is analyzed with a series of "checkers".
109
* @see DocCommentTester.ASTChecker#main(String... args)
111
public class DocCommentTester {
113
public static void main(String... args) throws Exception {
114
ArrayList<String> list = new ArrayList<>(Arrays.asList(args));
115
if (!list.isEmpty() && "-useBreakIterator".equals(list.get(0))) {
117
new DocCommentTester(true, true).run(list);
118
} else if (!list.isEmpty() && "-useStandardTransformer".equals(list.get(0))) {
120
new DocCommentTester(false, false).run(list);
122
new DocCommentTester(false, true).run(list);
126
public static final String BI_MARKER = "BREAK_ITERATOR";
127
public final boolean useBreakIterator;
129
public final boolean useIdentityTransformer;
131
public DocCommentTester(boolean useBreakIterator, boolean useIdentityTtransformer) {
132
this.useBreakIterator = useBreakIterator;
133
this.useIdentityTransformer = useIdentityTtransformer;
136
public void run(List<String> args) throws Exception {
137
String testSrc = System.getProperty("test.src");
139
List<Path> files = args.stream()
140
.map(arg -> Path.of(testSrc, arg))
141
.collect(Collectors.toList());
143
JavacTool javac = JavacTool.create();
144
StandardJavaFileManager fm = javac.getStandardFileManager(null, null, null);
146
Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromPaths(files);
148
JavacTask t = javac.getTask(null, fm, null, null, null, fos);
149
final JavacTrees trees = (JavacTrees) DocTrees.instance(t);
151
if (useIdentityTransformer) {
152
// disable default use of the "standard" transformer, so that we can examine
153
// the trees as created by DocCommentParser.
154
trees.setDocCommentTreeTransformer(new JavacTrees.IdentityTransformer());
157
if (useBreakIterator) {
158
// BreakIterators are locale dependent wrt. behavior
159
trees.setBreakIterator(BreakIterator.getSentenceInstance(Locale.ENGLISH));
162
final Checker[] checkers = {
163
new ASTChecker(this, trees),
164
new PosChecker(this, trees),
165
new PrettyChecker(this, trees),
166
new RangeChecker(this, trees),
167
new StartEndPosChecker(this, trees)
170
DeclScanner d = new DeclScanner() {
172
public Void visitCompilationUnit(CompilationUnitTree tree, Void ignore) {
173
for (Checker c: checkers)
174
c.visitCompilationUnit(tree);
175
return super.visitCompilationUnit(tree, ignore);
179
void visitDecl(Tree tree, Name name) {
180
TreePath path = getCurrentPath();
181
String dc = trees.getDocComment(path);
183
for (Checker c : checkers) {
185
System.err.println(path.getLeaf().getKind()
187
+ " " + c.getClass().getSimpleName());
191
System.err.println();
192
} catch (Exception e) {
193
error("Exception " + e);
194
e.printStackTrace(System.err);
201
Iterable<? extends CompilationUnitTree> units = t.parse();
202
for (CompilationUnitTree unit: units) {
207
throw new Exception(errors + " errors occurred");
210
static abstract class DeclScanner extends TreePathScanner<Void, Void> {
211
abstract void visitDecl(Tree tree, Name name);
214
public Void visitClass(ClassTree tree, Void ignore) {
215
super.visitClass(tree, ignore);
216
visitDecl(tree, tree.getSimpleName());
221
public Void visitMethod(MethodTree tree, Void ignore) {
222
super.visitMethod(tree, ignore);
223
visitDecl(tree, tree.getName());
228
public Void visitVariable(VariableTree tree, Void ignore) {
229
super.visitVariable(tree, ignore);
230
visitDecl(tree, tree.getName());
236
* Base class for checkers to check the doc comment on a declaration
239
abstract class Checker {
240
final DocTrees trees;
242
Checker(DocTrees trees) {
246
void visitCompilationUnit(CompilationUnitTree tree) { }
248
abstract void check(TreePath tree, Name name) throws Exception;
250
void error(String msg) {
251
DocCommentTester.this.error(msg);
255
void error(String msg) {
256
System.err.println("Error: " + msg);
263
* Verifies the structure of the DocTree AST by comparing it against golden text.
265
static class ASTChecker extends Checker {
266
static final String NEWLINE = System.getProperty("line.separator");
267
Printer printer = new Printer();
269
DocCommentTester test;
271
ASTChecker(DocCommentTester test, DocTrees t) {
277
void visitCompilationUnit(CompilationUnitTree tree) {
279
source = tree.getSourceFile().getCharContent(true).toString();
280
} catch (IOException e) {
285
void check(TreePath path, Name name) {
286
StringWriter out = new StringWriter();
287
DocCommentTree dc = trees.getDocCommentTree(path);
288
printer.print(dc, out);
290
String found = out.toString().replace(NEWLINE, "\n");
293
* Look for the first block comment after the first occurrence
294
* of name, noting that, block comments with BI_MARKER may
295
* very well be present.
297
int start = test.useBreakIterator
298
? source.indexOf("\n/*\n" + BI_MARKER + "\n", findName(source, name))
299
: source.indexOf("\n/*\n", findName(source, name));
300
assert start >= 0 : "start of AST comment not found";
301
int end = source.indexOf("\n*/\n", start);
302
assert end >= 0 : "end of AST comment not found";
303
int startlen = start + (test.useBreakIterator ? BI_MARKER.length() + 1 : 0) + 4;
304
String expect = source.substring(startlen, end + 1);
305
if (!found.equals(expect)) {
306
if (test.useBreakIterator) {
307
System.err.println("Using BreakIterator");
309
System.err.println("Expect:\n" + expect);
310
System.err.println("Found:\n" + found);
311
error("AST mismatch for " + name);
316
* This main program is to set up the golden comments used by this
319
* java DocCommentTester$ASTChecker -o dir file...
320
* The given files are written to the output directory with their
321
* golden comments updated. The intent is that the files should
322
* then be compared with the originals, e.g. with meld, and if the
323
* changes are approved, the new files can be used to replace the old.
325
public static void main(String... args) throws Exception {
326
List<Path> files = new ArrayList<>();
328
for (int i = 0; i < args.length; i++) {
329
String arg = args[i];
330
if (arg.equals("-o"))
331
o = Path.of(args[++i]);
332
else if (arg.startsWith("-"))
333
throw new IllegalArgumentException(arg);
335
files.add(Path.of(arg));
340
throw new IllegalArgumentException("no output dir specified");
341
final Path outDir = o;
343
JavacTool javac = JavacTool.create();
344
StandardJavaFileManager fm = javac.getStandardFileManager(null, null, null);
345
Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromPaths(files);
347
JavacTask t = javac.getTask(null, fm, null, null, null, fos);
348
final DocTrees trees = DocTrees.instance(t);
350
DeclScanner d = new DeclScanner() {
351
final Printer p = new Printer();
355
public Void visitCompilationUnit(CompilationUnitTree tree, Void ignore) {
356
System.err.println("processing " + tree.getSourceFile().getName());
358
source = tree.getSourceFile().getCharContent(true).toString();
359
} catch (IOException e) {
362
// remove existing gold by removing all block comments after the first '{'.
363
int start = source.indexOf("{");
364
assert start >= 0 : "cannot find initial '{'";
365
while ((start = source.indexOf("\n/*\n", start)) != -1) {
366
int end = source.indexOf("\n*/\n");
367
assert end >= 0 : "cannot find end of comment";
368
source = source.substring(0, start + 1) + source.substring(end + 4);
371
// process decls in compilation unit
372
super.visitCompilationUnit(tree, ignore);
374
// write the modified source
375
var treeSourceFileName = tree.getSourceFile().getName();
376
var outFile = outDir.resolve(treeSourceFileName);
378
Files.writeString(outFile, source);
379
} catch (IOException e) {
380
System.err.println("Can't write " + treeSourceFileName
381
+ " to " + outFile + ": " + e);
387
void visitDecl(Tree tree, Name name) {
388
DocTree dc = trees.getDocCommentTree(getCurrentPath());
390
StringWriter out = new StringWriter();
392
String found = out.toString();
394
// Look for the empty line after the first occurrence of name
395
int pos = source.indexOf("\n\n", findName(source, name));
397
// Insert the golden comment
398
source = source.substring(0, pos)
402
+ source.substring(pos);
408
Iterable<? extends CompilationUnitTree> units = t.parse();
409
for (CompilationUnitTree unit: units) {
414
static int findName(String source, Name name) {
415
Pattern p = Pattern.compile("\\s" + name + "[(;]");
416
Matcher m = p.matcher(source);
418
throw new Error("cannot find " + name);
422
static class Printer implements DocTreeVisitor<Void, Void> {
425
void print(DocTree tree, Writer out) {
426
this.out = (out instanceof PrintWriter)
427
? (PrintWriter) out : new PrintWriter(out);
428
tree.accept(this, null);
432
public Void visitAttribute(AttributeTree node, Void p) {
435
print("name", node.getName().toString());
436
print("vkind", node.getValueKind().toString());
437
print("value", node.getValue());
444
public Void visitAuthor(AuthorTree node, Void p) {
447
print("name", node.getName());
454
public Void visitComment(CommentTree node, Void p) {
455
header(node, compress(node.getBody()));
459
public Void visitDeprecated(DeprecatedTree node, Void p) {
462
print("body", node.getBody());
469
public Void visitDocComment(DocCommentTree node, Void p) {
472
// Applicable only to html files, print iff non-empty
473
if (!node.getPreamble().isEmpty())
474
print("preamble", node.getPreamble());
476
print("firstSentence", node.getFirstSentence());
477
print("body", node.getBody());
478
print("block tags", node.getBlockTags());
480
// Applicable only to html files, print iff non-empty
481
if (!node.getPostamble().isEmpty())
482
print("postamble", node.getPostamble());
490
public Void visitDocRoot(DocRootTree node, Void p) {
495
public Void visitDocType(DocTypeTree node, Void p) {
496
header(node, compress(node.getText()));
500
public Void visitEndElement(EndElementTree node, Void p) {
501
header(node, node.getName().toString());
505
public Void visitEntity(EntityTree node, Void p) {
506
header(node, node.getName().toString());
510
public Void visitErroneous(ErroneousTree node, Void p) {
513
print("code", ((DCErroneous) node).diag.getCode());
514
print("body", compress(node.getBody()));
521
public Void visitEscape(EscapeTree node, Void p) {
522
header(node, node.getBody());
526
public Void visitHidden(HiddenTree node, Void p) {
529
print("body", node.getBody());
536
public Void visitIdentifier(IdentifierTree node, Void p) {
537
header(node, compress(node.getName().toString()));
542
public Void visitIndex(IndexTree node, Void p) {
545
print("term", node.getSearchTerm());
546
print("description", node.getDescription());
553
public Void visitInheritDoc(InheritDocTree node, Void p) {
556
print("supertype", node.getSupertype());
563
public Void visitLink(LinkTree node, Void p) {
566
print("reference", node.getReference());
567
print("body", node.getLabel());
574
public Void visitLiteral(LiteralTree node, Void p) {
575
header(node, compress(node.getBody().getBody()));
579
public Void visitParam(ParamTree node, Void p) {
582
print("name", node.getName());
583
print("description", node.getDescription());
590
public Void visitProvides(ProvidesTree node, Void p) {
593
print("serviceName", node.getServiceType());
594
print("description", node.getDescription());
601
public Void visitRawText(RawTextTree node, Void p) {
602
header(node, compress(node.getContent()));
606
public Void visitReference(ReferenceTree node, Void p) {
607
header(node, compress(node.getSignature()));
611
public Void visitReturn(ReturnTree node, Void p) {
614
print("description", node.getDescription());
621
public Void visitSee(SeeTree node, Void p) {
624
print("reference", node.getReference());
631
public Void visitSerial(SerialTree node, Void p) {
634
print("description", node.getDescription());
641
public Void visitSerialData(SerialDataTree node, Void p) {
644
print("description", node.getDescription());
651
public Void visitSerialField(SerialFieldTree node, Void p) {
654
print("name", node.getName());
655
print("type", node.getType());
656
print("description", node.getDescription());
663
public Void visitSince(SinceTree node, Void p) {
666
print("body", node.getBody());
674
public Void visitSpec(SpecTree node, Void p) {
677
print("url", node.getURL());
678
print("title", node.getTitle());
686
public Void visitSnippet(SnippetTree node, Void p) {
689
print("attributes", node.getAttributes());
690
print("body", node.getBody());
697
public Void visitStartElement(StartElementTree node, Void p) {
701
out.println("name:" + node.getName());
702
print("attributes", node.getAttributes());
710
public Void visitSummary(SummaryTree node, Void p) {
713
print("summary", node.getSummary());
721
public Void visitSystemProperty(SystemPropertyTree node, Void p) {
724
print("property name", node.getPropertyName().toString());
731
public Void visitText(TextTree node, Void p) {
732
header(node, compress(node.getBody()));
736
public Void visitThrows(ThrowsTree node, Void p) {
739
print("exceptionName", node.getExceptionName());
740
print("description", node.getDescription());
747
public Void visitUnknownBlockTag(UnknownBlockTagTree node, Void p) {
751
out.println("tag:" + node.getTagName());
752
print("content", node.getContent());
759
public Void visitUnknownInlineTag(UnknownInlineTagTree node, Void p) {
763
out.println("tag:" + node.getTagName());
764
print("content", node.getContent());
771
public Void visitUses(UsesTree node, Void p) {
774
print("serviceName", node.getServiceType());
775
print("description", node.getDescription());
782
public Void visitValue(ValueTree node, Void p) {
785
print("format", node.getFormat());
786
print("reference", node.getReference());
793
public Void visitVersion(VersionTree node, Void p) {
796
print("body", node.getBody());
803
public Void visitOther(DocTree node, Void p) {
804
throw new UnsupportedOperationException("Not supported yet.");
808
* Use this method to start printing a multi-line representation of a
809
* DocTree node. The representation should be terminated by calling
812
void header(DocTree node) {
814
var n = (DCTree) node;
815
out.println(simpleClassName(node) + "[" + node.getKind() + ", pos:" + n.pos +
816
(n.getPreferredPosition() != n.pos ? ", prefPos:" + n.getPreferredPosition() : ""));
820
* Use this method to print a single-line representation of a DocTree node.
822
void header(DocTree node, String rest) {
824
out.println(simpleClassName(node) + "[" + node.getKind() + ", pos:" + ((DCTree) node).pos
825
+ (rest.isEmpty() ? "" : ", " + rest)
829
String simpleClassName(DocTree node) {
830
return node.getClass().getSimpleName().replaceAll("DC(.*)", "$1");
833
void print(String name, DocTree item) {
836
out.println(name + ": null");
838
out.println(name + ":");
840
item.accept(this, null);
845
void print(String name, String s) {
847
out.println(name + ": " + s);
850
void print(String name, List<? extends DocTree> list) {
853
out.println(name + ": null");
854
else if (list.isEmpty())
855
out.println(name + ": empty");
857
out.println(name + ": " + list.size());
859
for (DocTree tree: list) {
860
tree.accept(this, null);
869
for (int i = 0; i < indent; i++) {
878
private static final int BEGIN = 32;
879
private static final String ELLIPSIS = "...";
880
private static final int END = 32;
882
String compress(String s) {
883
s = s.replace("\n", "|").replace(" ", "_");
884
return (s.length() < BEGIN + ELLIPSIS.length() + END)
886
: s.substring(0, BEGIN) + ELLIPSIS + s.substring(s.length() - END);
889
String quote(String s) {
890
if (s.contains("\""))
891
return "'" + s + "'";
892
else if (s.contains("'") || s.contains(" "))
893
return '"' + s + '"';
901
* Verifies the reported tree positions by comparing the characters found
902
* at and after the reported position with the beginning of the pretty-
905
static class PosChecker extends Checker {
906
PosChecker(DocCommentTester test, DocTrees t) {
911
void check(TreePath path, Name name) throws Exception {
912
JavaFileObject fo = path.getCompilationUnit().getSourceFile();
913
final CharSequence cs = fo.getCharContent(true);
915
final DCDocComment dc = (DCDocComment) trees.getDocCommentTree(path);
917
DocTreeScanner<Void, Void> scanner = new DocTreeScanner<>() {
919
public Void scan(DocTree node, Void ignore) {
922
DCTree dcTree = (DCTree) node;
923
String expect = getExpectText(node);
924
long startPos = dc.getSourcePosition(dcTree.getStartPosition());
925
String found = getFoundText(cs, (int) startPos, expect.length());
926
if (!found.equals(expect)) {
927
System.err.println("node: " + node.getKind());
928
System.err.println("startPos: " + startPos + " " + showPos(cs, (int) startPos));
929
System.err.println("expect: " + expect);
930
System.err.println("found: " + found);
934
} catch (StringIndexOutOfBoundsException e) {
935
error(node.getClass() + ": " + e);
939
return super.scan(node, ignore);
943
scanner.scan(dc, null);
946
String getExpectText(DocTree t) {
947
StringWriter sw = new StringWriter();
948
DocPretty p = new DocPretty(sw);
949
try { p.print(t); } catch (IOException never) { }
950
String s = sw.toString();
953
int ws = s.replaceAll("\\s+", " ").indexOf(" ");
954
if (ws != -1) s = s.substring(0, ws);
955
return (s.length() < 5) ? s : s.substring(0, 5);
958
String getFoundText(CharSequence cs, int pos, int len) {
959
return (pos == -1) ? "" : cs.subSequence(pos, Math.min(pos + len, cs.length())).toString();
962
String showPos(CharSequence cs, int pos) {
963
String s = cs.toString();
964
return (s.substring(Math.max(0, pos - 10), pos)
968
+ s.substring(pos + 1, Math.min(s.length(), pos + 10)))
975
* Verifies the pretty printed text against a normalized form of the
976
* original doc comment.
978
static class PrettyChecker extends Checker {
980
PrettyChecker(DocCommentTester test, DocTrees t) {
985
void check(TreePath path, Name name) throws Exception {
986
var annos = (path.getLeaf() instanceof MethodTree m)
987
? m.getModifiers().getAnnotations().toString()
989
if (annos.contains("@PrettyCheck(false)")) {
992
boolean normalizeTags = !annos.contains("@NormalizeTags(false)");
994
Elements.DocCommentKind ck = trees.getDocCommentKind(path);
995
boolean isLineComment = ck == Elements.DocCommentKind.END_OF_LINE;
996
String raw = trees.getDocComment(path).stripTrailing();
997
String normRaw = normalize(raw, isLineComment, normalizeTags);
999
StringWriter out = new StringWriter();
1000
DocPretty dp = new DocPretty(out);
1001
dp.print(trees.getDocCommentTree(path));
1002
String pretty = out.toString();
1004
if (!pretty.equals(normRaw)) {
1006
System.err.println("*** raw: (" + raw.length() + ")");
1007
System.err.println(raw.replace(" ", "_"));
1008
System.err.println("*** expected: (" + normRaw.length() + ")");
1009
System.err.println(normRaw.replace(" ", "_"));
1010
System.err.println("*** found: (" + pretty.length() + ")");
1011
System.err.println(pretty.replace(" ", "_"));
1016
* Normalize whitespace in places where the tree does not preserve it.
1017
* Maintain contents of inline tags unless {@code normalizeTags} is
1018
* {@code false}. This should normally be {@code true}, but should be
1019
* set to {@code false} when there is syntactically invalid content
1020
* that might resemble an inline tag, but which is not so.
1022
* @param s the comment text to be normalized
1023
* @param normalizeTags whether to normalize inline tags
1024
* @return the normalized content
1026
String normalize(String s, boolean isLineComment, boolean normalizeTags) {
1027
String s2 = (isLineComment ? s : s.trim())
1028
.replaceFirst("\\.\\s*\\n *@(?![@*])", ".\n@"); // Between block tags
1029
StringBuilder sb = new StringBuilder();
1030
Pattern p = Pattern.compile("(?i)\\{@([a-z][a-z0-9.:-]*)( )?");
1031
Matcher m = p.matcher(s2);
1033
if (normalizeTags) {
1034
while (m.find(start)) {
1035
sb.append(normalizeFragment(s2.substring(start, m.start())));
1036
sb.append(m.group().trim());
1037
start = copyLiteral(s2, m.end(), sb);
1040
sb.append(normalizeFragment(s2.substring(start)));
1041
return sb.toString()
1042
.replaceAll("(?i)\\{@([a-z][a-z0-9.:-]*)\\s+}", "{@$1}")
1043
.replaceAll("(\\{@value\\s+[^}]+)\\s+(})", "$1$2");
1046
String normalizeFragment(String s) {
1047
return s.replaceAll("\n[ \t]+@(?!([@*]|dummy))", "\n@");
1050
int copyLiteral(String s, int start, StringBuilder sb) {
1052
for (int i = start; i < s.length(); i++) {
1053
char ch = s.charAt(i);
1054
if (i == start && !Character.isWhitespace(ch) && ch != '}') {
1077
* Verifies the general "left to right" constraints for the positions of
1078
* nodes in the DocTree AST.
1080
static class RangeChecker extends Checker {
1083
RangeChecker(DocCommentTester test, DocTrees docTrees) {
1084
test.super(docTrees);
1088
void check(TreePath path, Name name) throws Exception {
1089
final DCDocComment dc = (DCDocComment) trees.getDocCommentTree(path);
1091
DocTreeScanner<Void, Void> scanner = new DocTreeScanner<>() {
1093
public Void scan(DocTree node, Void ignore) {
1094
if (node instanceof DCTree dcTree) {
1095
int start = dcTree.getStartPosition();
1096
int pref = dcTree.getPreferredPosition();
1097
int end = dcTree.getEndPosition();
1099
// check within the node, start <= pref <= end
1100
check("start:pref", dcTree, start, pref);
1101
check("pref:end", dcTree, pref, end);
1103
// check cursor <= start
1104
check("cursor:start", dcTree, cursor, start);
1107
// recursively scan any children, updating the cursor
1108
super.scan(node, ignore);
1110
// check cursor <= end
1111
check("cursor:end", dcTree, cursor, end);
1119
scanner.scan(dc, null);
1123
void check(String name, DCTree tree, int first, int second) {
1124
if (!(first <= second)) {
1125
error(name, tree, first, second);
1129
private void error(String name, DCTree tree, int first, int second) {
1130
String t = tree.toString().replaceAll("\\s+", " ");
1131
if (t.length() > 32) {
1132
t = t.substring(0, 15) + "..." + t.substring(t.length() - 15);
1134
error("Checking " + name + " for " + tree.getKind() + " `" + t + "`; first:" + first + ", second:" + second);
1140
* Verifies that the start and end positions of all nodes in a DocCommentTree point to the
1141
* expected characters in the source code.
1143
* The expected characters are derived from the beginning and end of the DocPretty output
1144
* for each node. Note that while the whitespace within the DocPretty output may not exactly
1145
* match the original source code, the first and last characters should match.
1147
static class StartEndPosChecker extends Checker {
1149
StartEndPosChecker(DocCommentTester test, DocTrees docTrees) {
1150
test.super(docTrees);
1154
void check(TreePath path, Name name) throws Exception {
1155
final DCDocComment dc = (DCDocComment) trees.getDocCommentTree(path);
1156
JavaFileObject jfo = path.getCompilationUnit().getSourceFile();
1157
CharSequence content = jfo.getCharContent(true);
1159
DocTreeScanner<Void, Void> scanner = new DocTreeScanner<>() {
1161
public Void scan(DocTree node, Void ignore) {
1162
if (node instanceof DCTree dcTree) {
1163
int start = dc.getSourcePosition(dc.getStartPosition());
1164
int end = dc.getSourcePosition(dcTree.getEndPosition());
1167
StringWriter out = new StringWriter();
1168
DocPretty dp = new DocPretty(out);
1169
dp.print(trees.getDocCommentTree(path));
1170
String pretty = out.toString();
1172
if (pretty.isEmpty()) {
1174
error("Error: expected content is empty, but actual content is not: "
1175
+ dcTree.getKind() + " [" + start + "," + end + ")"
1176
+ ": \"" + content.subSequence(start, end) + "\"" );
1179
check(dcTree, "start", content, start, pretty, 0);
1180
check(dcTree, "end", content, end - 1, pretty, pretty.length() - 1);
1183
} catch (IOException e) {
1184
error("Error generating DocPretty for tree at position " + start + "; " + e);
1191
scanner.scan(dc, null);
1194
void check(DCTree tree, String label, CharSequence content, int contentIndex, String pretty, int prettyIndex) {
1195
if (contentIndex == Diagnostic.NOPOS) {
1196
error("NOPOS for content " + label + ": " + tree.getKind() + " >>" + abbrev(pretty, MAX) + "<<");
1199
char contentChar = content.charAt(contentIndex);
1200
char prettyChar = pretty.charAt(prettyIndex);
1201
if (contentChar != prettyChar) {
1202
error ("Mismatch for content " + label + ": "
1203
+ "expect: '" + prettyChar + "', found: '" + contentChar + "' at position " + contentIndex + ": "
1204
+ tree.getKind() + " >>" + abbrev(pretty, MAX) + "<<");
1208
static final int MAX = 64;
1210
static String abbrev(String s, int max) {
1211
s = s.replaceAll("\\s+", " ");
1212
if (s.length() > max) {
1213
s = s.substring(0, max / 2 - 2) + " ... " + s.substring(max / 2 + 2);