8268420: new Reporter method to report a diagnostic within a DocTree node

Reviewed-by: prappo
This commit is contained in:
Jonathan Gibbons 2021-07-09 16:14:29 +00:00
parent 5a74291013
commit 3588634d54
9 changed files with 581 additions and 17 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -28,7 +28,6 @@ package com.sun.source.util;
import java.io.IOException;
import java.text.BreakIterator;
import java.util.List;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.PackageElement;
@ -195,7 +194,7 @@ public abstract class DocTrees extends Trees {
/**
* Prints a message of the specified kind at the location of the
* tree within the provided compilation unit
* tree within the provided compilation unit.
*
* @param kind the kind of message
* @param msg the message, or an empty string if none

View File

@ -75,6 +75,7 @@ import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Scope.NamedImportScope;
import com.sun.tools.javac.code.Scope.StarImportScope;
import com.sun.tools.javac.code.Scope.WriteableScope;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.ModuleSymbol;
@ -85,10 +86,8 @@ import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Type.ArrayType;
import com.sun.tools.javac.code.Type.ClassType;
import com.sun.tools.javac.code.Type.ErrorType;
import com.sun.tools.javac.code.Type.UnionClassType;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.code.Types.TypeRelation;
import com.sun.tools.javac.comp.Attr;
import com.sun.tools.javac.comp.AttrContext;
import com.sun.tools.javac.comp.Check;
@ -97,7 +96,6 @@ import com.sun.tools.javac.comp.Env;
import com.sun.tools.javac.comp.MemberEnter;
import com.sun.tools.javac.comp.Modules;
import com.sun.tools.javac.comp.Resolve;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.file.BaseFileManager;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.parser.DocCommentParser;
@ -151,7 +149,6 @@ import com.sun.tools.javac.util.Pair;
import com.sun.tools.javac.util.Position;
import static com.sun.tools.javac.code.Kinds.Kind.*;
import static com.sun.tools.javac.code.TypeTag.*;
/**
* Provides an implementation of Trees.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2004, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2004, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -332,7 +332,9 @@ public class JavadocTokenizer extends JavaTokenizer {
private int[] map;
/**
* Logical size of map (number of valid entries.)
* Logical size of map.
* This is the number of occupied positions in {@code map},
* and equals {@code NOFFSETS} multiplied by the number of entries.
*/
private int size;
@ -349,25 +351,25 @@ public class JavadocTokenizer extends JavaTokenizer {
* if there is a change in relative offset.
*
* @param sbOffset comment offset member of pair.
* @param posOffet input offset member of pair.
* @param posOffset input offset member of pair.
*
* @return true if it is worthwhile adding the entry pair.
*/
boolean shouldAdd(int sbOffset, int posOffet) {
return sbOffset - lastSBOffset() != posOffet - lastPosOffset();
boolean shouldAdd(int sbOffset, int posOffset) {
return sbOffset - lastSBOffset() != posOffset - lastPosOffset();
}
/**
* Adds entry pair if worthwhile.
*
* @param sbOffset comment offset member of pair.
* @param posOffet input offset member of pair.
* @param posOffset input offset member of pair.
*/
void add(int sbOffset, int posOffet) {
if (size == 0 || shouldAdd(sbOffset, posOffet)) {
void add(int sbOffset, int posOffset) {
if (size == 0 || shouldAdd(sbOffset, posOffset)) {
ensure(NOFFSETS);
map[size + SB_OFFSET] = sbOffset;
map[size + POS_OFFSET] = posOffet;
map[size + POS_OFFSET] = posOffset;
size += NOFFSETS;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -63,10 +63,26 @@ public abstract class DCTree implements DocTree {
*/
public int pos;
/**
* {@return the source position for this tree node}
*
* @param dc the enclosing doc comment
*/
public long getSourcePosition(DCDocComment dc) {
return dc.comment.getSourcePos(pos);
}
/**
* {@return the source position for position relative to this tree node}
* This is primarily useful for nodes that wrap a single string child.
*
* @param dc the enclosing doc comment
* @param offset the offset
*/
public long getSourcePosition(DCDocComment dc, int offset) {
return dc.comment.getSourcePos(pos + offset);
}
public JCDiagnostic.DiagnosticPosition pos(DCDocComment dc) {
return new SimpleDiagnosticPosition(dc.comment.getSourcePos(pos));
}

View File

@ -31,6 +31,10 @@ import javax.lang.model.element.Element;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import com.sun.source.doctree.CommentTree;
import com.sun.source.doctree.DocTypeTree;
import com.sun.source.doctree.ReferenceTree;
import com.sun.source.doctree.TextTree;
import com.sun.source.util.DocTreePath;
/**
@ -80,6 +84,36 @@ public interface Reporter {
*/
void print(Diagnostic.Kind kind, DocTreePath path, String message);
/**
* Prints a diagnostic message related to a position within a range of characters in a tree node.
*
* Only kinds of {@code DocTree} that wrap a simple string value are supported as leaf nodes
* of the given path. This currently includes
* {@link CommentTree}, {@link DocTypeTree}, {@link ReferenceTree}, and {@link TextTree}.
*
* The positions are all 0-based character offsets from the beginning of string.
* The positions should satisfy the relation {@code start <= pos <= end}.
*
* @implSpec
* This implementation ignores the {@code (start, pos, end)} values and simply calls
* {@link #print(Diagnostic.Kind, DocTreePath, String) print(kind, path, message)}.
*
* @param kind the kind of diagnostic
* @param path the path for the tree node
* @param start the beginning of the enclosing range
* @param pos the position
* @param end the end of the enclosing range
* @param message the message to be printed
*
* @throws IllegalArgumentException if {@code start}, {@code pos} and {@code end} do
* not form a valid range.
*
* @since 18
*/
default void print(Diagnostic.Kind kind, DocTreePath path, int start, int pos, int end, String message) {
print(kind, path, message);
}
/**
* Prints a diagnostic message related to an element.
*

View File

@ -95,6 +95,20 @@ public class Messages {
report(ERROR, path, resources.getText(key, args));
}
/**
* Reports an error message to the doclet's reporter.
*
* @param path a path identifying the position to be included with the message
* @param start the start of a range of characters to be associated with the message
* @param pos the position to be associated with the message
* @param end the end of a range of characters to be associated with the message
* @param key the name of a resource containing the message to be printed
* @param args optional arguments to be replaced in the message
*/
public void error(DocTreePath path, int start, int pos, int end, String key, Object... args) {
report(ERROR, path, start, pos, end, resources.getText(key, args));
}
/**
* Reports an error message to the doclet's reporter.
*
@ -134,6 +148,20 @@ public class Messages {
}
}
/**
* Reports a warning message to the doclet's reporter.
*
* @param path a path identifying the position to be included with the message
* @param start the start of a range of characters to be associated with the message
* @param pos the position to be associated with the message
* @param end the end of a range of characters to be associated with the message
* @param key the name of a resource containing the message to be printed
* @param args optional arguments to be replaced in the message
*/
public void warning(DocTreePath path, int start, int pos, int end, String key, Object... args) {
report(WARNING, path, start, pos, end, resources.getText(key, args));
}
/**
* Reports a warning message to the doclet's reporter.
*
@ -194,4 +222,8 @@ public class Messages {
private void report(Diagnostic.Kind k, FileObject fo, int start, int pos, int end, String msg) {
reporter.print(k, fo, start, pos, end, msg);
}
private void report(Diagnostic.Kind k, DocTreePath path, int start, int pos, int end, String msg) {
reporter.print(k, path, start, pos, end, msg);
}
}

View File

@ -45,6 +45,12 @@ import javax.tools.FileObject;
import javax.tools.ForwardingFileObject;
import javax.tools.JavaFileObject;
import com.sun.source.doctree.CommentTree;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.DocTypeTree;
import com.sun.source.doctree.ReferenceTree;
import com.sun.source.doctree.TextTree;
import com.sun.tools.javac.tree.DCTree;
import jdk.javadoc.doclet.Reporter;
import com.sun.tools.javac.tree.EndPosTable;
@ -247,6 +253,48 @@ public class JavadocLog extends Log implements Reporter {
report(dt, flags, ds, dp, message);
}
@Override // Reporter
public void print(Diagnostic.Kind kind, DocTreePath path, int start, int pos, int end, String message) {
if (!(start <= pos && pos <= end)) {
throw new IllegalArgumentException("start:" + start + ",pos:" + pos + ",end:" + end);
}
DocTree t = path.getLeaf();
String s = switch (t.getKind()) {
case COMMENT -> ((CommentTree) t).getBody();
case DOC_TYPE -> ((DocTypeTree) t).getText();
case REFERENCE -> ((ReferenceTree) t).getSignature();
case TEXT -> ((TextTree) t).getBody();
default -> throw new IllegalArgumentException(t.getKind().toString());
};
if (start < 0 || end > s.length()) {
throw new StringIndexOutOfBoundsException("start:" + start + ",pos:" + pos + ",end:" + end
+ "; string length " + s.length());
}
DiagnosticType dt = getDiagnosticType(kind);
Set<DiagnosticFlag> flags = getDiagnosticFlags(kind);
DiagnosticSource ds = getDiagnosticSource(path);
DCTree.DCDocComment docComment = (DCTree.DCDocComment) path.getDocComment();
DCTree tree = (DCTree) path.getLeaf();
// note: it is important to evaluate the offsets in the context of the position
// within the comment text, and not in the context of the overall source text
int sStart = (int) tree.getSourcePosition(docComment, start);
int sPos = (int) tree.getSourcePosition(docComment, pos);
int sEnd = (int) tree.getSourcePosition(docComment, end);
DiagnosticPosition dp = createDiagnosticPosition(null, sStart, sPos, sEnd);
report(dt, flags, ds, dp, message);
}
private int getSourcePos(DocTreePath path, int offset) {
DCTree.DCDocComment docComment = (DCTree.DCDocComment) path.getDocComment();
DCTree tree = (DCTree) path.getLeaf();
return (int) tree.getSourcePosition(docComment, offset);
}
@Override // Reporter
public void print(Kind kind, Element element, String message) {
DiagnosticType dt = getDiagnosticType(kind);

View File

@ -0,0 +1,169 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.lang.reflect.Method;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import com.sun.source.doctree.CommentTree;
import com.sun.source.doctree.DocCommentTree;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.DocTypeTree;
import com.sun.source.doctree.ReferenceTree;
import com.sun.source.doctree.TextTree;
import com.sun.source.util.DocTreePath;
import com.sun.source.util.DocTreePathScanner;
import com.sun.source.util.DocTrees;
import com.sun.source.util.TreePath;
import jdk.javadoc.doclet.Doclet;
import jdk.javadoc.doclet.DocletEnvironment;
import jdk.javadoc.doclet.Reporter;
import jdk.javadoc.doclet.StandardDoclet;
import jdk.javadoc.doclet.Taglet;
/**
* A taglet to be called in the context of the standard doclet.
* When invoked, it scans the entire enclosing doc comment, and
* reports diagnostics at all instances of selected node kinds,
* so that a test can verify the contents of the diagnostics.
*/
public class MyTaglet implements Taglet {
private DocletEnvironment docEnv;
private Reporter reporter;
@Override
public void init(DocletEnvironment docEnv, Doclet doclet) {
this.docEnv = docEnv;
reporter = ((StandardDoclet) doclet).getReporter();
}
@Override
public Set<Location> getAllowedLocations() {
return EnumSet.allOf(Location.class);
}
@Override
public boolean isInlineTag() {
return false;
}
@Override
public String getName() {
return "scanMe";
}
@Override
public String toString(List<? extends DocTree> tags, Element element) {
DocTrees trees = docEnv.getDocTrees();
Elements elements = docEnv.getElementUtils();
DocTreePath dtp;
// Use reflection to access the file object underlying a doc-files/*.html file
// in order to access a DocCommentTree for the file.
// Note that using reflective access inside javadoc requires the -XDallowInternalAccess option.
// Note also that technically the doc comment tree that is found may be a different instance
// to the current instance, but since we only want to scan it and report diagnostics,
// that should not matter.
if (element.getClass().getSimpleName().equals("DocFileElement")) {
try {
Method getFileObjectMethod = element.getClass().getMethod("getFileObject");
FileObject fo = (FileObject) getFileObjectMethod.invoke(element);
DocCommentTree dct = trees.getDocCommentTree(fo);
dtp = trees.getDocTreePath(fo, elements.getPackageElement("p"));
} catch (ReflectiveOperationException e) {
return "MyTaglet[" + e + "]";
}
} else {
DocCommentTree dct = trees.getDocCommentTree(element);
TreePath tp = trees.getPath(element);
dtp = new DocTreePath(tp, dct);
}
scan(dtp);
return "MyTaglet[" + element + "]";
}
/**
* Scans a DocCommentTree, generating diagnostics for selected nodes.
* Information about the expected position is encoded within the
* text of the diagnostic, surrounded by {@code >>> <<<}.
*
* @param dtp the path to scan
*/
void scan(DocTreePath dtp) {
DocTreePathScanner<Void, Void> s = new DocTreePathScanner<Void, Void>() {
@Override
public Void visitDocComment(DocCommentTree t, Void p) {
// By default, DocTreeScanner does not include the preamble and postamble
scan(t.getPreamble(), p);
super.visitDocComment(t, p);
scan(t.getPostamble(), p);
return null;
}
@Override
public Void visitComment(CommentTree t, Void p) {
report(t, t.getBody());
return super.visitComment(t, p);
}
@Override
public Void visitDocType(DocTypeTree t, Void p) {
report(t, t.getText());
return super.visitDocType(t, p);
}
@Override
public Void visitReference(ReferenceTree t, Void p) {
report(t, t.getSignature());
return super.visitReference(t, p);
}
@Override
public Void visitText(TextTree t, Void p) {
report(t, t.getBody());
return super.visitText(t, p);
}
void report(DocTree t, String s) {
int pad = 3;
assert (s.length() > 2 * pad + 3) : ">>>" + s + "<<<";
int mid = s.length() / 2;
String detail = s.substring(mid - pad, mid) + "[" + s.charAt(mid) + "]" + s.substring(mid + 1, mid + pad + 1);
// The diagnostic is reported at a position in a range of characters
// in the middle of the string; the characters are encoded within the
// message of the diagnostic, with {@code [ ]} surrounding the character
// that should be indicated by the caret.
reporter.print(Diagnostic.Kind.WARNING, getCurrentPath(),
mid - pad, mid, mid + pad + 1,
"length: " + s.length() + " mid: " + mid + " >>>" + detail + "<<<");
}
};
s.scan(dtp, null);
}
}

View File

@ -0,0 +1,267 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8268420
* @summary new Reporter method to report a diagnostic within a DocTree node
* @library /tools/lib ../../lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool
* @build javadoc.tester.* MyTaglet
* @run main TestDocTreeDiags
*/
import java.io.IOException;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.DocumentationTool;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import javadoc.tester.JavadocTester;
import toolbox.ToolBox;
/**
* Tests the ability to write diagnostics related to a (start,pos,end) range in those
* DocTrees that wrap a String value.
*
* Ideally, this would be tested by using a custom doclet which scans all the doc comments,
* generating diagnostics for eligible nodes. However, one of the cases that is tested is
* a DocTypeTree, which only occurs in the context of an HTML file in a doc-files subdirectory,
* which is very specific to the Standard Doclet. Therefore, we use the Standard Doclet
* in conjunction with a non-standard use of a custom taglet, which is used to access and
* scan the doc comments that enclose the tags that trigger the taglet.
*/
public class TestDocTreeDiags extends JavadocTester {
public static void main(String... args) throws Exception {
TestDocTreeDiags tester = new TestDocTreeDiags();
tester.runTests(m -> new Object[] { Path.of(m.getName())} );
}
ToolBox tb = new ToolBox();
Path src;
DocumentationTool tool;
boolean showOutput = false; // set true for to set output written by javadoc
TestDocTreeDiags() throws IOException {
src = Path.of("src");
// Note: the following comments are somewhat stylized, and need to follow some
// simple rules to avoid exceptions and false positives.
// 1. Each fragment must be at least 7 (and preferably 9) characters long,
// in order to contain the range that will be generated in the diagnostic.
// 2. There must be no non-trivial duplication in the fragments, particularly
// in the area where the range of characters will be generated for the
// diagnostic. This is because we use String.indexOf to determine the
// expected values of the range.
tb.writeJavaFiles(src,
"""
package p;
/**
* First sentence. &quot; Second sentence.
* {@link java.lang.String first phrase; &quot; second phrase }
* And now ... <!-- this is a comment --> and so it was.
* @scanMe
*/
public class C {
/**
* Sentence for method m(). More details for the method.
* Embedded {@link java.lang.Object} link.
* And another <!-- unusual comment --> strange comment.
* @scanMe
*/
public void m() { }
}
""");
tb.writeFile(src.resolve("p").resolve("doc-files").resolve("extra.html"),
"""
<!doctype doctype-description>
<html>
<head><title>Document Title</title></head>
<body>
Extra content. More content.
@scanMe
</body>
</html>
"""
);
tool = ToolProvider.getSystemDocumentationTool();
}
/**
* Tests the diagnostics generated to the output stream when there is no
* diagnostic listener in use.
*
* By default, in this context, the start and end of the range of characters are not
* presented. The caret should point at the preferred position for the diagnostic.
*/
@Test
public void testStdout(Path base) throws Exception {
StringWriter outWriter = new StringWriter();
javadoc(outWriter, null, base.resolve("api"));
// analyze and verify the generated diagnostics
List<String> lines = outWriter.toString().lines().toList();
Iterator<String> iter = lines.iterator();
while (iter.hasNext()) {
String l = iter.next();
if (l.startsWith("src")) {
checkDiag(null, l, iter.next(), iter.next());
}
}
}
/**
* Tests the diagnostics received by a DiagnosticListener.
*
* In this context, various detailed coordinate information is available.
*/
@Test
public void testDiagListener(Path base) throws Exception {
StringWriter outWriter = new StringWriter();
DiagnosticListener dl = diagnostic -> {
if (diagnostic.getPosition() != -1) {
List<String> lines = List.of(diagnostic.toString().split("\\R"));
assert lines.size() == 3;
String msgLine = lines.get(0);
String srcLine = lines.get(1);
String caretLine = lines.get(2);
checkDiag(diagnostic, msgLine, srcLine, caretLine);
}
};
javadoc(outWriter, dl, base.resolve("api"));
}
/**
* Runs javadoc on package {@code p} in the {@code src} directory,
* using the specified writer and optional diagnostic listener.
*
* @param writer the writer
* @param dl the diagnostic listener, or {@code null}
* @param outDir the output directory
*
* @throws IOException if an IO error occurs
*/
void javadoc(StringWriter writer, DiagnosticListener dl, Path outDir) throws IOException {
Files.createDirectories(outDir);
try (StandardJavaFileManager fm = tool.getStandardFileManager(null, null, null)) {
fm.setLocationFromPaths(StandardLocation.SOURCE_PATH, List.of(src));
fm.setLocationFromPaths(DocumentationTool.Location.DOCUMENTATION_OUTPUT, List.of(outDir));
fm.setLocationFromPaths(DocumentationTool.Location.TAGLET_PATH, List.of(Path.of(System.getProperty("test.classes"))));
Iterable<? extends JavaFileObject> files = Collections.emptyList();
Iterable<String> options = List.of("-taglet", MyTaglet.class.getName(), "-XDaccessInternalAPI", "p");
DocumentationTool.DocumentationTask t = tool.getTask(writer, fm, dl, null, options, files);
checking("exit");
boolean ok = t.call();
if (showOutput) {
out.println("OUT: >>>" + writer.toString().replace("\n", NL) + "<<<");
}
if (ok) {
passed("javadoc exited OK, as expected");
} else {
failed("javadoc failed");
}
}
}
/**
* Checks the diagnostic output against information encoded in the diagnostics.
*
* The message in the message line contains a string that indicates where the
* caret should be pointing in the source line.
*
* @param diag the diagnostic, or null
* @param msgLine file:line: message >>>detail<<<
* @param srcLine the source line
* @param caretLine the line with the caret
*/
void checkDiag(Diagnostic diag, String msgLine, String srcLine, String caretLine) {
if (diag != null) {
out.printf("DIAG: %d:%d:%d %d:%d vvv%n%s%n^^^%n",
diag.getStartPosition(), diag.getPosition(), diag.getEndPosition(),
diag.getLineNumber(), diag.getColumnNumber(),
diag.toString().replace("\\R", NL) );
}
out.println(msgLine);
out.println(srcLine);
out.println(caretLine);
String srcFileLine = msgLine.substring(0, msgLine.indexOf(": "));
int caretIndex = caretLine.indexOf('^');
Pattern p = Pattern.compile(">>>([^<]*)<<<");
Matcher m = p.matcher(msgLine);
if (!m.find()) {
throw new IllegalArgumentException("detail pattern not found: " + msgLine);
}
String rawDetail = m.group(1);
String detail = rawDetail.replaceAll("[\\[\\]]", "");
if (diag != null) {
checking("coords-column: " + srcFileLine);
int col = (int) diag.getColumnNumber();
// line and column are 1-based, so col should be 1 more than caretIndex
if (col - 1 == caretIndex) {
passed("col: " + col + " caret: " + caretIndex);
} else {
failed("col: " + col + " caret: " + caretIndex);
}
checking("coords-start-end: " + srcFileLine);
String fileStr = readFile(".", msgLine.substring(0, msgLine.indexOf(":")));
int start = (int) diag.getStartPosition();
int end = (int) diag.getEndPosition();
String fileRange = fileStr.substring(start, end);
if (fileRange.equals(detail)) {
passed("file: >>>" + fileRange + "<<< message: >>>" + detail + "<<<");
} else {
failed("file: >>>" + fileRange + "<<< message: >>>" + detail + "<<<");
}
}
checking("message-caret: " + srcFileLine);
int srcIndex = srcLine.indexOf(detail);
int pad = (detail.length() - 1) / 2;
int srcIndexPad = srcIndex + pad;
if (srcIndexPad == caretIndex) {
passed("src: " + srcIndexPad + " caret: " + caretIndex);
} else {
failed("src: " + srcIndexPad + " caret: " + caretIndex);
}
}
}