Skip to content

Commit 3588634

Browse files
committedJul 9, 2021
8268420: new Reporter method to report a diagnostic within a DocTree node
Reviewed-by: prappo
1 parent 5a74291 commit 3588634

File tree

9 files changed

+581
-17
lines changed

9 files changed

+581
-17
lines changed
 

‎src/jdk.compiler/share/classes/com/sun/source/util/DocTrees.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -28,7 +28,6 @@
2828
import java.io.IOException;
2929
import java.text.BreakIterator;
3030
import java.util.List;
31-
3231
import javax.annotation.processing.ProcessingEnvironment;
3332
import javax.lang.model.element.Element;
3433
import javax.lang.model.element.PackageElement;
@@ -195,7 +194,7 @@ public static DocTrees instance(ProcessingEnvironment env) {
195194

196195
/**
197196
* Prints a message of the specified kind at the location of the
198-
* tree within the provided compilation unit
197+
* tree within the provided compilation unit.
199198
*
200199
* @param kind the kind of message
201200
* @param msg the message, or an empty string if none

‎src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
import com.sun.tools.javac.code.Scope.NamedImportScope;
7676
import com.sun.tools.javac.code.Scope.StarImportScope;
7777
import com.sun.tools.javac.code.Scope.WriteableScope;
78+
import com.sun.tools.javac.code.Symbol;
7879
import com.sun.tools.javac.code.Symbol.ClassSymbol;
7980
import com.sun.tools.javac.code.Symbol.MethodSymbol;
8081
import com.sun.tools.javac.code.Symbol.ModuleSymbol;
@@ -85,10 +86,8 @@
8586
import com.sun.tools.javac.code.Type;
8687
import com.sun.tools.javac.code.Type.ArrayType;
8788
import com.sun.tools.javac.code.Type.ClassType;
88-
import com.sun.tools.javac.code.Type.ErrorType;
8989
import com.sun.tools.javac.code.Type.UnionClassType;
9090
import com.sun.tools.javac.code.Types;
91-
import com.sun.tools.javac.code.Types.TypeRelation;
9291
import com.sun.tools.javac.comp.Attr;
9392
import com.sun.tools.javac.comp.AttrContext;
9493
import com.sun.tools.javac.comp.Check;
@@ -97,7 +96,6 @@
9796
import com.sun.tools.javac.comp.MemberEnter;
9897
import com.sun.tools.javac.comp.Modules;
9998
import com.sun.tools.javac.comp.Resolve;
100-
import com.sun.tools.javac.code.Symbol;
10199
import com.sun.tools.javac.file.BaseFileManager;
102100
import com.sun.tools.javac.model.JavacElements;
103101
import com.sun.tools.javac.parser.DocCommentParser;
@@ -151,7 +149,6 @@
151149
import com.sun.tools.javac.util.Position;
152150

153151
import static com.sun.tools.javac.code.Kinds.Kind.*;
154-
import static com.sun.tools.javac.code.TypeTag.*;
155152

156153
/**
157154
* Provides an implementation of Trees.

‎src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavadocTokenizer.java

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2004, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2004, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -332,7 +332,9 @@ static class OffsetMap {
332332
private int[] map;
333333

334334
/**
335-
* Logical size of map (number of valid entries.)
335+
* Logical size of map.
336+
* This is the number of occupied positions in {@code map},
337+
* and equals {@code NOFFSETS} multiplied by the number of entries.
336338
*/
337339
private int size;
338340

@@ -349,25 +351,25 @@ static class OffsetMap {
349351
* if there is a change in relative offset.
350352
*
351353
* @param sbOffset comment offset member of pair.
352-
* @param posOffet input offset member of pair.
354+
* @param posOffset input offset member of pair.
353355
*
354356
* @return true if it is worthwhile adding the entry pair.
355357
*/
356-
boolean shouldAdd(int sbOffset, int posOffet) {
357-
return sbOffset - lastSBOffset() != posOffet - lastPosOffset();
358+
boolean shouldAdd(int sbOffset, int posOffset) {
359+
return sbOffset - lastSBOffset() != posOffset - lastPosOffset();
358360
}
359361

360362
/**
361363
* Adds entry pair if worthwhile.
362364
*
363365
* @param sbOffset comment offset member of pair.
364-
* @param posOffet input offset member of pair.
366+
* @param posOffset input offset member of pair.
365367
*/
366-
void add(int sbOffset, int posOffet) {
367-
if (size == 0 || shouldAdd(sbOffset, posOffet)) {
368+
void add(int sbOffset, int posOffset) {
369+
if (size == 0 || shouldAdd(sbOffset, posOffset)) {
368370
ensure(NOFFSETS);
369371
map[size + SB_OFFSET] = sbOffset;
370-
map[size + POS_OFFSET] = posOffet;
372+
map[size + POS_OFFSET] = posOffset;
371373
size += NOFFSETS;
372374
}
373375
}

‎src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -63,10 +63,26 @@ public abstract class DCTree implements DocTree {
6363
*/
6464
public int pos;
6565

66+
/**
67+
* {@return the source position for this tree node}
68+
*
69+
* @param dc the enclosing doc comment
70+
*/
6671
public long getSourcePosition(DCDocComment dc) {
6772
return dc.comment.getSourcePos(pos);
6873
}
6974

75+
/**
76+
* {@return the source position for position relative to this tree node}
77+
* This is primarily useful for nodes that wrap a single string child.
78+
*
79+
* @param dc the enclosing doc comment
80+
* @param offset the offset
81+
*/
82+
public long getSourcePosition(DCDocComment dc, int offset) {
83+
return dc.comment.getSourcePos(pos + offset);
84+
}
85+
7086
public JCDiagnostic.DiagnosticPosition pos(DCDocComment dc) {
7187
return new SimpleDiagnosticPosition(dc.comment.getSourcePos(pos));
7288
}

‎src/jdk.javadoc/share/classes/jdk/javadoc/doclet/Reporter.java

+34
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
import javax.tools.Diagnostic;
3232
import javax.tools.FileObject;
3333

34+
import com.sun.source.doctree.CommentTree;
35+
import com.sun.source.doctree.DocTypeTree;
36+
import com.sun.source.doctree.ReferenceTree;
37+
import com.sun.source.doctree.TextTree;
3438
import com.sun.source.util.DocTreePath;
3539

3640
/**
@@ -80,6 +84,36 @@ public interface Reporter {
8084
*/
8185
void print(Diagnostic.Kind kind, DocTreePath path, String message);
8286

87+
/**
88+
* Prints a diagnostic message related to a position within a range of characters in a tree node.
89+
*
90+
* Only kinds of {@code DocTree} that wrap a simple string value are supported as leaf nodes
91+
* of the given path. This currently includes
92+
* {@link CommentTree}, {@link DocTypeTree}, {@link ReferenceTree}, and {@link TextTree}.
93+
*
94+
* The positions are all 0-based character offsets from the beginning of string.
95+
* The positions should satisfy the relation {@code start <= pos <= end}.
96+
*
97+
* @implSpec
98+
* This implementation ignores the {@code (start, pos, end)} values and simply calls
99+
* {@link #print(Diagnostic.Kind, DocTreePath, String) print(kind, path, message)}.
100+
*
101+
* @param kind the kind of diagnostic
102+
* @param path the path for the tree node
103+
* @param start the beginning of the enclosing range
104+
* @param pos the position
105+
* @param end the end of the enclosing range
106+
* @param message the message to be printed
107+
*
108+
* @throws IllegalArgumentException if {@code start}, {@code pos} and {@code end} do
109+
* not form a valid range.
110+
*
111+
* @since 18
112+
*/
113+
default void print(Diagnostic.Kind kind, DocTreePath path, int start, int pos, int end, String message) {
114+
print(kind, path, message);
115+
}
116+
83117
/**
84118
* Prints a diagnostic message related to an element.
85119
*

‎src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/Messages.java

+32
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,20 @@ public void error(DocTreePath path, String key, Object... args) {
9595
report(ERROR, path, resources.getText(key, args));
9696
}
9797

98+
/**
99+
* Reports an error message to the doclet's reporter.
100+
*
101+
* @param path a path identifying the position to be included with the message
102+
* @param start the start of a range of characters to be associated with the message
103+
* @param pos the position to be associated with the message
104+
* @param end the end of a range of characters to be associated with the message
105+
* @param key the name of a resource containing the message to be printed
106+
* @param args optional arguments to be replaced in the message
107+
*/
108+
public void error(DocTreePath path, int start, int pos, int end, String key, Object... args) {
109+
report(ERROR, path, start, pos, end, resources.getText(key, args));
110+
}
111+
98112
/**
99113
* Reports an error message to the doclet's reporter.
100114
*
@@ -134,6 +148,20 @@ public void warning(DocTreePath path, String key, Object... args) {
134148
}
135149
}
136150

151+
/**
152+
* Reports a warning message to the doclet's reporter.
153+
*
154+
* @param path a path identifying the position to be included with the message
155+
* @param start the start of a range of characters to be associated with the message
156+
* @param pos the position to be associated with the message
157+
* @param end the end of a range of characters to be associated with the message
158+
* @param key the name of a resource containing the message to be printed
159+
* @param args optional arguments to be replaced in the message
160+
*/
161+
public void warning(DocTreePath path, int start, int pos, int end, String key, Object... args) {
162+
report(WARNING, path, start, pos, end, resources.getText(key, args));
163+
}
164+
137165
/**
138166
* Reports a warning message to the doclet's reporter.
139167
*
@@ -194,4 +222,8 @@ private void report(Diagnostic.Kind k, Element e, String msg) {
194222
private void report(Diagnostic.Kind k, FileObject fo, int start, int pos, int end, String msg) {
195223
reporter.print(k, fo, start, pos, end, msg);
196224
}
225+
226+
private void report(Diagnostic.Kind k, DocTreePath path, int start, int pos, int end, String msg) {
227+
reporter.print(k, path, start, pos, end, msg);
228+
}
197229
}

‎src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/JavadocLog.java

+48
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@
4545
import javax.tools.ForwardingFileObject;
4646
import javax.tools.JavaFileObject;
4747

48+
import com.sun.source.doctree.CommentTree;
49+
import com.sun.source.doctree.DocTree;
50+
import com.sun.source.doctree.DocTypeTree;
51+
import com.sun.source.doctree.ReferenceTree;
52+
import com.sun.source.doctree.TextTree;
53+
import com.sun.tools.javac.tree.DCTree;
4854
import jdk.javadoc.doclet.Reporter;
4955

5056
import com.sun.tools.javac.tree.EndPosTable;
@@ -247,6 +253,48 @@ public void print(Diagnostic.Kind kind, DocTreePath path, String message) {
247253
report(dt, flags, ds, dp, message);
248254
}
249255

256+
@Override // Reporter
257+
public void print(Diagnostic.Kind kind, DocTreePath path, int start, int pos, int end, String message) {
258+
if (!(start <= pos && pos <= end)) {
259+
throw new IllegalArgumentException("start:" + start + ",pos:" + pos + ",end:" + end);
260+
}
261+
262+
DocTree t = path.getLeaf();
263+
String s = switch (t.getKind()) {
264+
case COMMENT -> ((CommentTree) t).getBody();
265+
case DOC_TYPE -> ((DocTypeTree) t).getText();
266+
case REFERENCE -> ((ReferenceTree) t).getSignature();
267+
case TEXT -> ((TextTree) t).getBody();
268+
default -> throw new IllegalArgumentException(t.getKind().toString());
269+
};
270+
271+
if (start < 0 || end > s.length()) {
272+
throw new StringIndexOutOfBoundsException("start:" + start + ",pos:" + pos + ",end:" + end
273+
+ "; string length " + s.length());
274+
}
275+
276+
DiagnosticType dt = getDiagnosticType(kind);
277+
Set<DiagnosticFlag> flags = getDiagnosticFlags(kind);
278+
DiagnosticSource ds = getDiagnosticSource(path);
279+
280+
DCTree.DCDocComment docComment = (DCTree.DCDocComment) path.getDocComment();
281+
DCTree tree = (DCTree) path.getLeaf();
282+
// note: it is important to evaluate the offsets in the context of the position
283+
// within the comment text, and not in the context of the overall source text
284+
int sStart = (int) tree.getSourcePosition(docComment, start);
285+
int sPos = (int) tree.getSourcePosition(docComment, pos);
286+
int sEnd = (int) tree.getSourcePosition(docComment, end);
287+
DiagnosticPosition dp = createDiagnosticPosition(null, sStart, sPos, sEnd);
288+
289+
report(dt, flags, ds, dp, message);
290+
}
291+
292+
private int getSourcePos(DocTreePath path, int offset) {
293+
DCTree.DCDocComment docComment = (DCTree.DCDocComment) path.getDocComment();
294+
DCTree tree = (DCTree) path.getLeaf();
295+
return (int) tree.getSourcePosition(docComment, offset);
296+
}
297+
250298
@Override // Reporter
251299
public void print(Kind kind, Element element, String message) {
252300
DiagnosticType dt = getDiagnosticType(kind);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
* Copyright (c) 2021, 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+
24+
import java.lang.reflect.Method;
25+
import java.util.EnumSet;
26+
import java.util.List;
27+
import java.util.Set;
28+
import javax.lang.model.element.Element;
29+
import javax.lang.model.util.Elements;
30+
import javax.tools.Diagnostic;
31+
import javax.tools.FileObject;
32+
33+
import com.sun.source.doctree.CommentTree;
34+
import com.sun.source.doctree.DocCommentTree;
35+
import com.sun.source.doctree.DocTree;
36+
import com.sun.source.doctree.DocTypeTree;
37+
import com.sun.source.doctree.ReferenceTree;
38+
import com.sun.source.doctree.TextTree;
39+
import com.sun.source.util.DocTreePath;
40+
import com.sun.source.util.DocTreePathScanner;
41+
import com.sun.source.util.DocTrees;
42+
import com.sun.source.util.TreePath;
43+
import jdk.javadoc.doclet.Doclet;
44+
import jdk.javadoc.doclet.DocletEnvironment;
45+
import jdk.javadoc.doclet.Reporter;
46+
import jdk.javadoc.doclet.StandardDoclet;
47+
import jdk.javadoc.doclet.Taglet;
48+
49+
/**
50+
* A taglet to be called in the context of the standard doclet.
51+
* When invoked, it scans the entire enclosing doc comment, and
52+
* reports diagnostics at all instances of selected node kinds,
53+
* so that a test can verify the contents of the diagnostics.
54+
*/
55+
public class MyTaglet implements Taglet {
56+
private DocletEnvironment docEnv;
57+
private Reporter reporter;
58+
59+
@Override
60+
public void init(DocletEnvironment docEnv, Doclet doclet) {
61+
this.docEnv = docEnv;
62+
reporter = ((StandardDoclet) doclet).getReporter();
63+
}
64+
65+
@Override
66+
public Set<Location> getAllowedLocations() {
67+
return EnumSet.allOf(Location.class);
68+
}
69+
70+
@Override
71+
public boolean isInlineTag() {
72+
return false;
73+
}
74+
75+
@Override
76+
public String getName() {
77+
return "scanMe";
78+
}
79+
80+
@Override
81+
public String toString(List<? extends DocTree> tags, Element element) {
82+
DocTrees trees = docEnv.getDocTrees();
83+
Elements elements = docEnv.getElementUtils();
84+
DocTreePath dtp;
85+
// Use reflection to access the file object underlying a doc-files/*.html file
86+
// in order to access a DocCommentTree for the file.
87+
// Note that using reflective access inside javadoc requires the -XDallowInternalAccess option.
88+
// Note also that technically the doc comment tree that is found may be a different instance
89+
// to the current instance, but since we only want to scan it and report diagnostics,
90+
// that should not matter.
91+
if (element.getClass().getSimpleName().equals("DocFileElement")) {
92+
try {
93+
Method getFileObjectMethod = element.getClass().getMethod("getFileObject");
94+
FileObject fo = (FileObject) getFileObjectMethod.invoke(element);
95+
DocCommentTree dct = trees.getDocCommentTree(fo);
96+
dtp = trees.getDocTreePath(fo, elements.getPackageElement("p"));
97+
} catch (ReflectiveOperationException e) {
98+
return "MyTaglet[" + e + "]";
99+
}
100+
} else {
101+
DocCommentTree dct = trees.getDocCommentTree(element);
102+
TreePath tp = trees.getPath(element);
103+
dtp = new DocTreePath(tp, dct);
104+
}
105+
106+
scan(dtp);
107+
108+
return "MyTaglet[" + element + "]";
109+
}
110+
111+
/**
112+
* Scans a DocCommentTree, generating diagnostics for selected nodes.
113+
* Information about the expected position is encoded within the
114+
* text of the diagnostic, surrounded by {@code >>> <<<}.
115+
*
116+
* @param dtp the path to scan
117+
*/
118+
void scan(DocTreePath dtp) {
119+
DocTreePathScanner<Void, Void> s = new DocTreePathScanner<Void, Void>() {
120+
@Override
121+
public Void visitDocComment(DocCommentTree t, Void p) {
122+
// By default, DocTreeScanner does not include the preamble and postamble
123+
scan(t.getPreamble(), p);
124+
super.visitDocComment(t, p);
125+
scan(t.getPostamble(), p);
126+
return null;
127+
}
128+
129+
@Override
130+
public Void visitComment(CommentTree t, Void p) {
131+
report(t, t.getBody());
132+
return super.visitComment(t, p);
133+
}
134+
135+
@Override
136+
public Void visitDocType(DocTypeTree t, Void p) {
137+
report(t, t.getText());
138+
return super.visitDocType(t, p);
139+
}
140+
141+
@Override
142+
public Void visitReference(ReferenceTree t, Void p) {
143+
report(t, t.getSignature());
144+
return super.visitReference(t, p);
145+
}
146+
147+
@Override
148+
public Void visitText(TextTree t, Void p) {
149+
report(t, t.getBody());
150+
return super.visitText(t, p);
151+
}
152+
153+
void report(DocTree t, String s) {
154+
int pad = 3;
155+
assert (s.length() > 2 * pad + 3) : ">>>" + s + "<<<";
156+
int mid = s.length() / 2;
157+
String detail = s.substring(mid - pad, mid) + "[" + s.charAt(mid) + "]" + s.substring(mid + 1, mid + pad + 1);
158+
// The diagnostic is reported at a position in a range of characters
159+
// in the middle of the string; the characters are encoded within the
160+
// message of the diagnostic, with {@code [ ]} surrounding the character
161+
// that should be indicated by the caret.
162+
reporter.print(Diagnostic.Kind.WARNING, getCurrentPath(),
163+
mid - pad, mid, mid + pad + 1,
164+
"length: " + s.length() + " mid: " + mid + " >>>" + detail + "<<<");
165+
}
166+
};
167+
s.scan(dtp, null);
168+
}
169+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
/*
2+
* Copyright (c) 2021, 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+
24+
/*
25+
* @test
26+
* @bug 8268420
27+
* @summary new Reporter method to report a diagnostic within a DocTree node
28+
* @library /tools/lib ../../lib
29+
* @modules jdk.javadoc/jdk.javadoc.internal.tool
30+
* @build javadoc.tester.* MyTaglet
31+
* @run main TestDocTreeDiags
32+
*/
33+
34+
import java.io.IOException;
35+
import java.io.StringWriter;
36+
import java.nio.file.Files;
37+
import java.nio.file.Path;
38+
import java.util.Collections;
39+
import java.util.Iterator;
40+
import java.util.List;
41+
import java.util.regex.Matcher;
42+
import java.util.regex.Pattern;
43+
import javax.tools.Diagnostic;
44+
import javax.tools.DiagnosticListener;
45+
import javax.tools.DocumentationTool;
46+
import javax.tools.JavaFileObject;
47+
import javax.tools.StandardJavaFileManager;
48+
import javax.tools.StandardLocation;
49+
import javax.tools.ToolProvider;
50+
51+
import javadoc.tester.JavadocTester;
52+
import toolbox.ToolBox;
53+
54+
/**
55+
* Tests the ability to write diagnostics related to a (start,pos,end) range in those
56+
* DocTrees that wrap a String value.
57+
*
58+
* Ideally, this would be tested by using a custom doclet which scans all the doc comments,
59+
* generating diagnostics for eligible nodes. However, one of the cases that is tested is
60+
* a DocTypeTree, which only occurs in the context of an HTML file in a doc-files subdirectory,
61+
* which is very specific to the Standard Doclet. Therefore, we use the Standard Doclet
62+
* in conjunction with a non-standard use of a custom taglet, which is used to access and
63+
* scan the doc comments that enclose the tags that trigger the taglet.
64+
*/
65+
public class TestDocTreeDiags extends JavadocTester {
66+
67+
public static void main(String... args) throws Exception {
68+
TestDocTreeDiags tester = new TestDocTreeDiags();
69+
tester.runTests(m -> new Object[] { Path.of(m.getName())} );
70+
}
71+
72+
ToolBox tb = new ToolBox();
73+
Path src;
74+
DocumentationTool tool;
75+
76+
boolean showOutput = false; // set true for to set output written by javadoc
77+
78+
TestDocTreeDiags() throws IOException {
79+
src = Path.of("src");
80+
// Note: the following comments are somewhat stylized, and need to follow some
81+
// simple rules to avoid exceptions and false positives.
82+
// 1. Each fragment must be at least 7 (and preferably 9) characters long,
83+
// in order to contain the range that will be generated in the diagnostic.
84+
// 2. There must be no non-trivial duplication in the fragments, particularly
85+
// in the area where the range of characters will be generated for the
86+
// diagnostic. This is because we use String.indexOf to determine the
87+
// expected values of the range.
88+
tb.writeJavaFiles(src,
89+
"""
90+
package p;
91+
/**
92+
* First sentence. &quot; Second sentence.
93+
* {@link java.lang.String first phrase; &quot; second phrase }
94+
* And now ... <!-- this is a comment --> and so it was.
95+
* @scanMe
96+
*/
97+
public class C {
98+
/**
99+
* Sentence for method m(). More details for the method.
100+
* Embedded {@link java.lang.Object} link.
101+
* And another <!-- unusual comment --> strange comment.
102+
* @scanMe
103+
*/
104+
public void m() { }
105+
}
106+
""");
107+
tb.writeFile(src.resolve("p").resolve("doc-files").resolve("extra.html"),
108+
"""
109+
<!doctype doctype-description>
110+
<html>
111+
<head><title>Document Title</title></head>
112+
<body>
113+
Extra content. More content.
114+
@scanMe
115+
</body>
116+
</html>
117+
"""
118+
);
119+
120+
tool = ToolProvider.getSystemDocumentationTool();
121+
}
122+
123+
/**
124+
* Tests the diagnostics generated to the output stream when there is no
125+
* diagnostic listener in use.
126+
*
127+
* By default, in this context, the start and end of the range of characters are not
128+
* presented. The caret should point at the preferred position for the diagnostic.
129+
*/
130+
@Test
131+
public void testStdout(Path base) throws Exception {
132+
StringWriter outWriter = new StringWriter();
133+
javadoc(outWriter, null, base.resolve("api"));
134+
135+
// analyze and verify the generated diagnostics
136+
List<String> lines = outWriter.toString().lines().toList();
137+
Iterator<String> iter = lines.iterator();
138+
while (iter.hasNext()) {
139+
String l = iter.next();
140+
if (l.startsWith("src")) {
141+
checkDiag(null, l, iter.next(), iter.next());
142+
}
143+
}
144+
}
145+
146+
/**
147+
* Tests the diagnostics received by a DiagnosticListener.
148+
*
149+
* In this context, various detailed coordinate information is available.
150+
*/
151+
@Test
152+
public void testDiagListener(Path base) throws Exception {
153+
StringWriter outWriter = new StringWriter();
154+
DiagnosticListener dl = diagnostic -> {
155+
if (diagnostic.getPosition() != -1) {
156+
List<String> lines = List.of(diagnostic.toString().split("\\R"));
157+
assert lines.size() == 3;
158+
String msgLine = lines.get(0);
159+
String srcLine = lines.get(1);
160+
String caretLine = lines.get(2);
161+
checkDiag(diagnostic, msgLine, srcLine, caretLine);
162+
}
163+
};
164+
javadoc(outWriter, dl, base.resolve("api"));
165+
}
166+
167+
/**
168+
* Runs javadoc on package {@code p} in the {@code src} directory,
169+
* using the specified writer and optional diagnostic listener.
170+
*
171+
* @param writer the writer
172+
* @param dl the diagnostic listener, or {@code null}
173+
* @param outDir the output directory
174+
*
175+
* @throws IOException if an IO error occurs
176+
*/
177+
void javadoc(StringWriter writer, DiagnosticListener dl, Path outDir) throws IOException {
178+
Files.createDirectories(outDir);
179+
try (StandardJavaFileManager fm = tool.getStandardFileManager(null, null, null)) {
180+
fm.setLocationFromPaths(StandardLocation.SOURCE_PATH, List.of(src));
181+
fm.setLocationFromPaths(DocumentationTool.Location.DOCUMENTATION_OUTPUT, List.of(outDir));
182+
fm.setLocationFromPaths(DocumentationTool.Location.TAGLET_PATH, List.of(Path.of(System.getProperty("test.classes"))));
183+
Iterable<? extends JavaFileObject> files = Collections.emptyList();
184+
Iterable<String> options = List.of("-taglet", MyTaglet.class.getName(), "-XDaccessInternalAPI", "p");
185+
DocumentationTool.DocumentationTask t = tool.getTask(writer, fm, dl, null, options, files);
186+
187+
checking("exit");
188+
boolean ok = t.call();
189+
190+
if (showOutput) {
191+
out.println("OUT: >>>" + writer.toString().replace("\n", NL) + "<<<");
192+
}
193+
194+
if (ok) {
195+
passed("javadoc exited OK, as expected");
196+
} else {
197+
failed("javadoc failed");
198+
}
199+
}
200+
}
201+
202+
/**
203+
* Checks the diagnostic output against information encoded in the diagnostics.
204+
*
205+
* The message in the message line contains a string that indicates where the
206+
* caret should be pointing in the source line.
207+
*
208+
* @param diag the diagnostic, or null
209+
* @param msgLine file:line: message >>>detail<<<
210+
* @param srcLine the source line
211+
* @param caretLine the line with the caret
212+
*/
213+
void checkDiag(Diagnostic diag, String msgLine, String srcLine, String caretLine) {
214+
if (diag != null) {
215+
out.printf("DIAG: %d:%d:%d %d:%d vvv%n%s%n^^^%n",
216+
diag.getStartPosition(), diag.getPosition(), diag.getEndPosition(),
217+
diag.getLineNumber(), diag.getColumnNumber(),
218+
diag.toString().replace("\\R", NL) );
219+
}
220+
out.println(msgLine);
221+
out.println(srcLine);
222+
out.println(caretLine);
223+
224+
String srcFileLine = msgLine.substring(0, msgLine.indexOf(": "));
225+
int caretIndex = caretLine.indexOf('^');
226+
Pattern p = Pattern.compile(">>>([^<]*)<<<");
227+
Matcher m = p.matcher(msgLine);
228+
if (!m.find()) {
229+
throw new IllegalArgumentException("detail pattern not found: " + msgLine);
230+
}
231+
String rawDetail = m.group(1);
232+
String detail = rawDetail.replaceAll("[\\[\\]]", "");
233+
234+
if (diag != null) {
235+
checking("coords-column: " + srcFileLine);
236+
int col = (int) diag.getColumnNumber();
237+
// line and column are 1-based, so col should be 1 more than caretIndex
238+
if (col - 1 == caretIndex) {
239+
passed("col: " + col + " caret: " + caretIndex);
240+
} else {
241+
failed("col: " + col + " caret: " + caretIndex);
242+
}
243+
244+
checking("coords-start-end: " + srcFileLine);
245+
String fileStr = readFile(".", msgLine.substring(0, msgLine.indexOf(":")));
246+
int start = (int) diag.getStartPosition();
247+
int end = (int) diag.getEndPosition();
248+
String fileRange = fileStr.substring(start, end);
249+
if (fileRange.equals(detail)) {
250+
passed("file: >>>" + fileRange + "<<< message: >>>" + detail + "<<<");
251+
} else {
252+
failed("file: >>>" + fileRange + "<<< message: >>>" + detail + "<<<");
253+
}
254+
}
255+
256+
checking("message-caret: " + srcFileLine);
257+
int srcIndex = srcLine.indexOf(detail);
258+
int pad = (detail.length() - 1) / 2;
259+
int srcIndexPad = srcIndex + pad;
260+
if (srcIndexPad == caretIndex) {
261+
passed("src: " + srcIndexPad + " caret: " + caretIndex);
262+
} else {
263+
failed("src: " + srcIndexPad + " caret: " + caretIndex);
264+
}
265+
}
266+
}
267+

0 commit comments

Comments
 (0)
Please sign in to comment.