jdk

Форк
0
/
DocCommentTester.java 
1219 строк · 44.3 Кб
1
/*
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.
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.io.IOException;
25
import java.io.PrintWriter;
26
import java.io.StringWriter;
27
import java.io.Writer;
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;
33
import java.util.List;
34
import java.util.Locale;
35
import java.util.regex.Matcher;
36
import java.util.regex.Pattern;
37
import java.util.stream.Collectors;
38

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;
44

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;
102

103
/**
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".
108
 *
109
 * @see DocCommentTester.ASTChecker#main(String... args)
110
 */
111
public class DocCommentTester {
112

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))) {
116
            list.remove(0);
117
            new DocCommentTester(true, true).run(list);
118
        } else if (!list.isEmpty() && "-useStandardTransformer".equals(list.get(0))) {
119
            list.remove(0);
120
            new DocCommentTester(false, false).run(list);
121
        } else {
122
            new DocCommentTester(false, true).run(list);
123
        }
124
    }
125

126
    public static final String BI_MARKER = "BREAK_ITERATOR";
127
    public final boolean useBreakIterator;
128

129
    public final boolean useIdentityTransformer;
130

131
    public DocCommentTester(boolean useBreakIterator, boolean useIdentityTtransformer) {
132
        this.useBreakIterator = useBreakIterator;
133
        this.useIdentityTransformer = useIdentityTtransformer;
134
    }
135

136
    public void run(List<String> args) throws Exception {
137
        String testSrc = System.getProperty("test.src");
138

139
        List<Path> files = args.stream()
140
                .map(arg -> Path.of(testSrc, arg))
141
                .collect(Collectors.toList());
142

143
        JavacTool javac = JavacTool.create();
144
        StandardJavaFileManager fm = javac.getStandardFileManager(null, null, null);
145

146
        Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromPaths(files);
147

148
        JavacTask t = javac.getTask(null, fm, null, null, null, fos);
149
        final JavacTrees trees = (JavacTrees) DocTrees.instance(t);
150

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());
155
        }
156

157
        if (useBreakIterator) {
158
            // BreakIterators are locale dependent wrt. behavior
159
            trees.setBreakIterator(BreakIterator.getSentenceInstance(Locale.ENGLISH));
160
        }
161

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)
168
        };
169

170
        DeclScanner d = new DeclScanner() {
171
            @Override
172
            public Void visitCompilationUnit(CompilationUnitTree tree, Void ignore) {
173
                for (Checker c: checkers)
174
                    c.visitCompilationUnit(tree);
175
                return super.visitCompilationUnit(tree, ignore);
176
            }
177

178
            @Override
179
            void visitDecl(Tree tree, Name name) {
180
                TreePath path = getCurrentPath();
181
                String dc = trees.getDocComment(path);
182
                if (dc != null) {
183
                    for (Checker c : checkers) {
184
                        try {
185
                            System.err.println(path.getLeaf().getKind()
186
                                    + " " + name
187
                                    + " " + c.getClass().getSimpleName());
188

189
                            c.check(path, name);
190

191
                            System.err.println();
192
                        } catch (Exception e) {
193
                            error("Exception " + e);
194
                            e.printStackTrace(System.err);
195
                        }
196
                    }
197
                }
198
            }
199
        };
200

201
        Iterable<? extends CompilationUnitTree> units = t.parse();
202
        for (CompilationUnitTree unit: units) {
203
            d.scan(unit, null);
204
        }
205

206
        if (errors > 0)
207
            throw new Exception(errors + " errors occurred");
208
    }
209

210
    static abstract class DeclScanner extends TreePathScanner<Void, Void> {
211
        abstract void visitDecl(Tree tree, Name name);
212

213
        @Override
214
        public Void visitClass(ClassTree tree, Void ignore) {
215
            super.visitClass(tree, ignore);
216
            visitDecl(tree, tree.getSimpleName());
217
            return null;
218
        }
219

220
        @Override
221
        public Void visitMethod(MethodTree tree, Void ignore) {
222
            super.visitMethod(tree, ignore);
223
            visitDecl(tree, tree.getName());
224
            return null;
225
        }
226

227
        @Override
228
        public Void visitVariable(VariableTree tree, Void ignore) {
229
            super.visitVariable(tree, ignore);
230
            visitDecl(tree, tree.getName());
231
            return null;
232
        }
233
    }
234

235
    /**
236
     * Base class for checkers to check the doc comment on a declaration
237
     * (when present.)
238
     */
239
    abstract class Checker {
240
        final DocTrees trees;
241

242
        Checker(DocTrees trees) {
243
            this.trees = trees;
244
        }
245

246
        void visitCompilationUnit(CompilationUnitTree tree) { }
247

248
        abstract void check(TreePath tree, Name name) throws Exception;
249

250
        void error(String msg) {
251
            DocCommentTester.this.error(msg);
252
        }
253
    }
254

255
    void error(String msg) {
256
        System.err.println("Error: " + msg);
257
        errors++;
258
    }
259

260
    int errors;
261

262
    /**
263
     * Verifies the structure of the DocTree AST by comparing it against golden text.
264
     */
265
    static class ASTChecker extends Checker {
266
        static final String NEWLINE = System.getProperty("line.separator");
267
        Printer printer = new Printer();
268
        String source;
269
        DocCommentTester test;
270

271
        ASTChecker(DocCommentTester test, DocTrees t) {
272
            test.super(t);
273
            this.test = test;
274
        }
275

276
        @Override
277
        void visitCompilationUnit(CompilationUnitTree tree) {
278
            try {
279
                source = tree.getSourceFile().getCharContent(true).toString();
280
            } catch (IOException e) {
281
                source = "";
282
            }
283
        }
284

285
        void check(TreePath path, Name name) {
286
            StringWriter out = new StringWriter();
287
            DocCommentTree dc = trees.getDocCommentTree(path);
288
            printer.print(dc, out);
289
            out.flush();
290
            String found = out.toString().replace(NEWLINE, "\n");
291

292
            /*
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.
296
             */
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");
308
                }
309
                System.err.println("Expect:\n" + expect);
310
                System.err.println("Found:\n" + found);
311
                error("AST mismatch for " + name);
312
            }
313
        }
314

315
        /**
316
         * This main program is to set up the golden comments used by this
317
         * checker.
318
         * Usage:
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.
324
         */
325
        public static void main(String... args) throws Exception {
326
            List<Path> files = new ArrayList<>();
327
            Path o = null;
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);
334
                else {
335
                    files.add(Path.of(arg));
336
                }
337
            }
338

339
            if (o == null)
340
                throw new IllegalArgumentException("no output dir specified");
341
            final Path outDir = o;
342

343
            JavacTool javac = JavacTool.create();
344
            StandardJavaFileManager fm = javac.getStandardFileManager(null, null, null);
345
            Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromPaths(files);
346

347
            JavacTask t = javac.getTask(null, fm, null, null, null, fos);
348
            final DocTrees trees = DocTrees.instance(t);
349

350
            DeclScanner d = new DeclScanner() {
351
                final Printer p = new Printer();
352
                String source;
353

354
                @Override
355
                public Void visitCompilationUnit(CompilationUnitTree tree, Void ignore) {
356
                    System.err.println("processing " + tree.getSourceFile().getName());
357
                    try {
358
                        source = tree.getSourceFile().getCharContent(true).toString();
359
                    } catch (IOException e) {
360
                        source = "";
361
                    }
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);
369
                    }
370

371
                    // process decls in compilation unit
372
                    super.visitCompilationUnit(tree, ignore);
373

374
                    // write the modified source
375
                    var treeSourceFileName = tree.getSourceFile().getName();
376
                    var outFile = outDir.resolve(treeSourceFileName);
377
                    try {
378
                        Files.writeString(outFile, source);
379
                    } catch (IOException e) {
380
                        System.err.println("Can't write " + treeSourceFileName
381
                                + " to " + outFile + ": " + e);
382
                    }
383
                    return null;
384
                }
385

386
                @Override
387
                void visitDecl(Tree tree, Name name) {
388
                    DocTree dc = trees.getDocCommentTree(getCurrentPath());
389
                    if (dc != null) {
390
                        StringWriter out = new StringWriter();
391
                        p.print(dc, out);
392
                        String found = out.toString();
393

394
                        // Look for the empty line after the first occurrence of name
395
                        int pos = source.indexOf("\n\n", findName(source, name));
396

397
                        // Insert the golden comment
398
                        source = source.substring(0, pos)
399
                                + "\n/*\n"
400
                                + found
401
                                + "*/"
402
                                + source.substring(pos);
403
                    }
404
                }
405

406
            };
407

408
            Iterable<? extends CompilationUnitTree> units = t.parse();
409
            for (CompilationUnitTree unit: units) {
410
                d.scan(unit, null);
411
            }
412
        }
413

414
        static int findName(String source, Name name) {
415
            Pattern p = Pattern.compile("\\s" + name + "[(;]");
416
            Matcher m = p.matcher(source);
417
            if (!m.find())
418
                throw new Error("cannot find " + name);
419
            return m.start();
420
        }
421

422
        static class Printer implements DocTreeVisitor<Void, Void> {
423
            PrintWriter out;
424

425
            void print(DocTree tree, Writer out) {
426
                this.out = (out instanceof PrintWriter)
427
                        ? (PrintWriter) out : new PrintWriter(out);
428
                tree.accept(this, null);
429
                this.out.flush();
430
            }
431

432
            public Void visitAttribute(AttributeTree node, Void p) {
433
                header(node);
434
                indent(+1);
435
                print("name", node.getName().toString());
436
                print("vkind", node.getValueKind().toString());
437
                print("value", node.getValue());
438
                indent(-1);
439
                indent();
440
                out.println("]");
441
                return null;
442
            }
443

444
            public Void visitAuthor(AuthorTree node, Void p) {
445
                header(node);
446
                indent(+1);
447
                print("name", node.getName());
448
                indent(-1);
449
                indent();
450
                out.println("]");
451
                return null;
452
            }
453

454
            public Void visitComment(CommentTree node, Void p) {
455
                header(node, compress(node.getBody()));
456
                return null;
457
            }
458

459
            public Void visitDeprecated(DeprecatedTree node, Void p) {
460
                header(node);
461
                indent(+1);
462
                print("body", node.getBody());
463
                indent(-1);
464
                indent();
465
                out.println("]");
466
                return null;
467
            }
468

469
            public Void visitDocComment(DocCommentTree node, Void p) {
470
                header(node);
471
                indent(+1);
472
                // Applicable only to html files, print iff non-empty
473
                if (!node.getPreamble().isEmpty())
474
                    print("preamble", node.getPreamble());
475

476
                print("firstSentence", node.getFirstSentence());
477
                print("body", node.getBody());
478
                print("block tags", node.getBlockTags());
479

480
                // Applicable only to html files, print iff non-empty
481
                if (!node.getPostamble().isEmpty())
482
                    print("postamble", node.getPostamble());
483

484
                indent(-1);
485
                indent();
486
                out.println("]");
487
                return null;
488
            }
489

490
            public Void visitDocRoot(DocRootTree node, Void p) {
491
                header(node, "");
492
                return null;
493
            }
494

495
            public Void visitDocType(DocTypeTree node, Void p) {
496
                header(node, compress(node.getText()));
497
                return null;
498
            }
499

500
            public Void visitEndElement(EndElementTree node, Void p) {
501
                header(node, node.getName().toString());
502
                return null;
503
            }
504

505
            public Void visitEntity(EntityTree node, Void p) {
506
                header(node, node.getName().toString());
507
                return null;
508
            }
509

510
            public Void visitErroneous(ErroneousTree node, Void p) {
511
                header(node);
512
                indent(+1);
513
                print("code", ((DCErroneous) node).diag.getCode());
514
                print("body", compress(node.getBody()));
515
                indent(-1);
516
                indent();
517
                out.println("]");
518
                return null;
519
            }
520

521
            public Void visitEscape(EscapeTree node, Void p) {
522
                header(node, node.getBody());
523
                return null;
524
            }
525

526
            public Void visitHidden(HiddenTree node, Void p) {
527
                header(node);
528
                indent(+1);
529
                print("body", node.getBody());
530
                indent(-1);
531
                indent();
532
                out.println("]");
533
                return null;
534
            }
535

536
            public Void visitIdentifier(IdentifierTree node, Void p) {
537
                header(node, compress(node.getName().toString()));
538
                return null;
539
            }
540

541
            @Override
542
            public Void visitIndex(IndexTree node, Void p) {
543
                header(node);
544
                indent(+1);
545
                print("term", node.getSearchTerm());
546
                print("description", node.getDescription());
547
                indent(-1);
548
                indent();
549
                out.println("]");
550
                return null;
551
            }
552

553
            public Void visitInheritDoc(InheritDocTree node, Void p) {
554
                header(node);
555
                indent(+1);
556
                print("supertype", node.getSupertype());
557
                indent(-1);
558
                indent();
559
                out.println("]");
560
                return null;
561
            }
562

563
            public Void visitLink(LinkTree node, Void p) {
564
                header(node);
565
                indent(+1);
566
                print("reference", node.getReference());
567
                print("body", node.getLabel());
568
                indent(-1);
569
                indent();
570
                out.println("]");
571
                return null;
572
            }
573

574
            public Void visitLiteral(LiteralTree node, Void p) {
575
                header(node, compress(node.getBody().getBody()));
576
                return null;
577
            }
578

579
            public Void visitParam(ParamTree node, Void p) {
580
                header(node);
581
                indent(+1);
582
                print("name", node.getName());
583
                print("description", node.getDescription());
584
                indent(-1);
585
                indent();
586
                out.println("]");
587
                return null;
588
            }
589

590
            public Void visitProvides(ProvidesTree node, Void p) {
591
                header(node);
592
                indent(+1);
593
                print("serviceName", node.getServiceType());
594
                print("description", node.getDescription());
595
                indent(-1);
596
                indent();
597
                out.println("]");
598
                return null;
599
            }
600

601
            public Void visitRawText(RawTextTree node, Void p) {
602
                header(node, compress(node.getContent()));
603
                return null;
604
            }
605

606
            public Void visitReference(ReferenceTree node, Void p) {
607
                header(node, compress(node.getSignature()));
608
                return null;
609
            }
610

611
            public Void visitReturn(ReturnTree node, Void p) {
612
                header(node);
613
                indent(+1);
614
                print("description", node.getDescription());
615
                indent(-1);
616
                indent();
617
                out.println("]");
618
                return null;
619
            }
620

621
            public Void visitSee(SeeTree node, Void p) {
622
                header(node);
623
                indent(+1);
624
                print("reference", node.getReference());
625
                indent(-1);
626
                indent();
627
                out.println("]");
628
                return null;
629
            }
630

631
            public Void visitSerial(SerialTree node, Void p) {
632
                header(node);
633
                indent(+1);
634
                print("description", node.getDescription());
635
                indent(-1);
636
                indent();
637
                out.println("]");
638
                return null;
639
            }
640

641
            public Void visitSerialData(SerialDataTree node, Void p) {
642
                header(node);
643
                indent(+1);
644
                print("description", node.getDescription());
645
                indent(-1);
646
                indent();
647
                out.println("]");
648
                return null;
649
            }
650

651
            public Void visitSerialField(SerialFieldTree node, Void p) {
652
                header(node);
653
                indent(+1);
654
                print("name", node.getName());
655
                print("type", node.getType());
656
                print("description", node.getDescription());
657
                indent(-1);
658
                indent();
659
                out.println("]");
660
                return null;
661
            }
662

663
            public Void visitSince(SinceTree node, Void p) {
664
                header(node);
665
                indent(+1);
666
                print("body", node.getBody());
667
                indent(-1);
668
                indent();
669
                out.println("]");
670
                return null;
671
            }
672

673
            @Override
674
            public Void visitSpec(SpecTree node, Void p) {
675
                header(node);
676
                indent(+1);
677
                print("url", node.getURL());
678
                print("title", node.getTitle());
679
                indent(-1);
680
                indent();
681
                out.println("]");
682
                return null;
683
            }
684

685
            @Override
686
            public Void visitSnippet(SnippetTree node, Void p) {
687
                header(node);
688
                indent(+1);
689
                print("attributes", node.getAttributes());
690
                print("body", node.getBody());
691
                indent(-1);
692
                indent();
693
                out.println("]");
694
                return null;
695
            }
696

697
            public Void visitStartElement(StartElementTree node, Void p) {
698
                header(node);
699
                indent(+1);
700
                indent();
701
                out.println("name:" + node.getName());
702
                print("attributes", node.getAttributes());
703
                indent(-1);
704
                indent();
705
                out.println("]");
706
                return null;
707
            }
708

709
            @Override
710
            public Void visitSummary(SummaryTree node, Void p) {
711
                header(node);
712
                indent(+1);
713
                print("summary", node.getSummary());
714
                indent(-1);
715
                indent();
716
                out.println("]");
717
                return null;
718
            }
719

720
            @Override
721
            public Void visitSystemProperty(SystemPropertyTree node, Void p) {
722
                header(node);
723
                indent(+1);
724
                print("property name", node.getPropertyName().toString());
725
                indent(-1);
726
                indent();
727
                out.println("]");
728
                return null;
729
            }
730

731
            public Void visitText(TextTree node, Void p) {
732
                header(node, compress(node.getBody()));
733
                return null;
734
            }
735

736
            public Void visitThrows(ThrowsTree node, Void p) {
737
                header(node);
738
                indent(+1);
739
                print("exceptionName", node.getExceptionName());
740
                print("description", node.getDescription());
741
                indent(-1);
742
                indent();
743
                out.println("]");
744
                return null;
745
            }
746

747
            public Void visitUnknownBlockTag(UnknownBlockTagTree node, Void p) {
748
                header(node);
749
                indent(+1);
750
                indent();
751
                out.println("tag:" + node.getTagName());
752
                print("content", node.getContent());
753
                indent(-1);
754
                indent();
755
                out.println("]");
756
                return null;
757
            }
758

759
            public Void visitUnknownInlineTag(UnknownInlineTagTree node, Void p) {
760
                header(node);
761
                indent(+1);
762
                indent();
763
                out.println("tag:" + node.getTagName());
764
                print("content", node.getContent());
765
                indent(-1);
766
                indent();
767
                out.println("]");
768
                return null;
769
            }
770

771
            public Void visitUses(UsesTree node, Void p) {
772
                header(node);
773
                indent(+1);
774
                print("serviceName", node.getServiceType());
775
                print("description", node.getDescription());
776
                indent(-1);
777
                indent();
778
                out.println("]");
779
                return null;
780
            }
781

782
            public Void visitValue(ValueTree node, Void p) {
783
                header(node);
784
                indent(+1);
785
                print("format", node.getFormat());
786
                print("reference", node.getReference());
787
                indent(-1);
788
                indent();
789
                out.println("]");
790
                return null;
791
            }
792

793
            public Void visitVersion(VersionTree node, Void p) {
794
                header(node);
795
                indent(+1);
796
                print("body", node.getBody());
797
                indent(-1);
798
                indent();
799
                out.println("]");
800
                return null;
801
            }
802

803
            public Void visitOther(DocTree node, Void p) {
804
                throw new UnsupportedOperationException("Not supported yet.");
805
            }
806

807
            /*
808
             * Use this method to start printing a multi-line representation of a
809
             * DocTree node. The representation should be terminated by calling
810
             * out.println("]").
811
             */
812
            void header(DocTree node) {
813
                indent();
814
                var n = (DCTree) node;
815
                out.println(simpleClassName(node) + "[" + node.getKind() + ", pos:" + n.pos +
816
                        (n.getPreferredPosition() != n.pos ? ", prefPos:" + n.getPreferredPosition() : ""));
817
            }
818

819
            /*
820
             * Use this method to print a single-line representation of a DocTree node.
821
             */
822
            void header(DocTree node, String rest) {
823
                indent();
824
                out.println(simpleClassName(node) + "[" + node.getKind() + ", pos:" + ((DCTree) node).pos
825
                        + (rest.isEmpty() ? "" : ", " + rest)
826
                        + "]");
827
            }
828

829
            String simpleClassName(DocTree node) {
830
                return node.getClass().getSimpleName().replaceAll("DC(.*)", "$1");
831
            }
832

833
            void print(String name, DocTree item) {
834
                indent();
835
                if (item == null)
836
                    out.println(name + ": null");
837
                else {
838
                    out.println(name + ":");
839
                    indent(+1);
840
                    item.accept(this, null);
841
                    indent(-1);
842
                }
843
            }
844

845
            void print(String name, String s) {
846
                indent();
847
                out.println(name + ": " + s);
848
            }
849

850
            void print(String name, List<? extends DocTree> list) {
851
                indent();
852
                if (list == null)
853
                    out.println(name + ": null");
854
                else if (list.isEmpty())
855
                    out.println(name + ": empty");
856
                else {
857
                    out.println(name + ": " + list.size());
858
                    indent(+1);
859
                    for (DocTree tree: list) {
860
                        tree.accept(this, null);
861
                    }
862
                    indent(-1);
863
                }
864
            }
865

866
            int indent = 0;
867

868
            void indent() {
869
                for (int i = 0; i < indent; i++) {
870
                    out.print("  ");
871
                }
872
            }
873

874
            void indent(int n) {
875
                indent += n;
876
            }
877

878
            private static final int BEGIN = 32;
879
            private static final String ELLIPSIS = "...";
880
            private static final int END = 32;
881

882
            String compress(String s) {
883
                s = s.replace("\n", "|").replace(" ", "_");
884
                return (s.length() < BEGIN + ELLIPSIS.length() + END)
885
                        ? s
886
                        : s.substring(0, BEGIN) + ELLIPSIS + s.substring(s.length() - END);
887
            }
888

889
            String quote(String s) {
890
                if (s.contains("\""))
891
                    return "'" + s + "'";
892
                else if (s.contains("'") || s.contains(" "))
893
                    return '"' + s + '"';
894
                else
895
                    return s;
896
            }
897
        }
898
    }
899

900
    /**
901
     * Verifies the reported tree positions by comparing the characters found
902
     * at and after the reported position with the beginning of the pretty-
903
     * printed text.
904
     */
905
    static class PosChecker extends Checker {
906
        PosChecker(DocCommentTester test, DocTrees t) {
907
            test.super(t);
908
        }
909

910
        @Override
911
        void check(TreePath path, Name name) throws Exception {
912
            JavaFileObject fo = path.getCompilationUnit().getSourceFile();
913
            final CharSequence cs = fo.getCharContent(true);
914

915
            final DCDocComment dc = (DCDocComment) trees.getDocCommentTree(path);
916

917
            DocTreeScanner<Void, Void> scanner = new DocTreeScanner<>() {
918
                @Override
919
                public Void scan(DocTree node, Void ignore) {
920
                    if (node != null) {
921
                        try {
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);
931
                                error("mismatch");
932
                            }
933

934
                        } catch (StringIndexOutOfBoundsException e) {
935
                            error(node.getClass() + ": " + e);
936
                                e.printStackTrace();
937
                        }
938
                    }
939
                    return super.scan(node, ignore);
940
                }
941
            };
942

943
            scanner.scan(dc, null);
944
        }
945

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();
951
            if (s.length() <= 1)
952
                return s;
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);
956
        }
957

958
        String getFoundText(CharSequence cs, int pos, int len) {
959
            return (pos == -1) ? "" : cs.subSequence(pos, Math.min(pos + len, cs.length())).toString();
960
        }
961

962
        String showPos(CharSequence cs, int pos) {
963
            String s = cs.toString();
964
            return (s.substring(Math.max(0, pos - 10), pos)
965
                    + "["
966
                    + s.charAt(pos)
967
                    + "]"
968
                    + s.substring(pos + 1, Math.min(s.length(), pos + 10)))
969
                    .replace('\n', '|')
970
                    .replace(' ', '_');
971
        }
972
    }
973

974
    /**
975
     * Verifies the pretty printed text against a normalized form of the
976
     * original doc comment.
977
     */
978
    static class PrettyChecker extends Checker {
979

980
        PrettyChecker(DocCommentTester test, DocTrees t) {
981
            test.super(t);
982
        }
983

984
        @Override
985
        void check(TreePath path, Name name) throws Exception {
986
            var annos = (path.getLeaf() instanceof MethodTree m)
987
                    ? m.getModifiers().getAnnotations().toString()
988
                    : "";
989
            if (annos.contains("@PrettyCheck(false)")) {
990
                return;
991
            }
992
            boolean normalizeTags = !annos.contains("@NormalizeTags(false)");
993

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);
998

999
            StringWriter out = new StringWriter();
1000
            DocPretty dp = new DocPretty(out);
1001
            dp.print(trees.getDocCommentTree(path));
1002
            String pretty = out.toString();
1003

1004
            if (!pretty.equals(normRaw)) {
1005
                error("mismatch");
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(" ", "_"));
1012
            }
1013
        }
1014

1015
        /**
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.
1021
         *
1022
         * @param s the comment text to be normalized
1023
         * @param normalizeTags whether to normalize inline tags
1024
         * @return the normalized content
1025
         */
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);
1032
            int start = 0;
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);
1038
                }
1039
            }
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");
1044
        }
1045

1046
        String normalizeFragment(String s) {
1047
            return s.replaceAll("\n[ \t]+@(?!([@*]|dummy))", "\n@");
1048
        }
1049

1050
        int copyLiteral(String s, int start, StringBuilder sb) {
1051
            int depth = 0;
1052
            for (int i = start; i < s.length(); i++) {
1053
                char ch = s.charAt(i);
1054
                if (i == start && !Character.isWhitespace(ch) && ch != '}') {
1055
                    sb.append(' ');
1056
                }
1057
                switch (ch) {
1058
                    case '{' ->
1059
                        depth++;
1060

1061
                    case '}' -> {
1062
                        depth--;
1063
                        if (depth < 0) {
1064
                            sb.append(ch);
1065
                            return i + 1;
1066
                        }
1067
                    }
1068
                }
1069
                sb.append(ch);
1070
            }
1071
            return s.length();
1072
        }
1073
    }
1074

1075

1076
    /**
1077
     * Verifies the general "left to right" constraints for the positions of
1078
     * nodes in the DocTree AST.
1079
     */
1080
    static class RangeChecker extends Checker {
1081
        int cursor = 0;
1082

1083
        RangeChecker(DocCommentTester test, DocTrees docTrees) {
1084
            test.super(docTrees);
1085
        }
1086

1087
        @Override
1088
        void check(TreePath path, Name name) throws Exception {
1089
            final DCDocComment dc = (DCDocComment) trees.getDocCommentTree(path);
1090

1091
            DocTreeScanner<Void, Void> scanner = new DocTreeScanner<>() {
1092
                @Override
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();
1098

1099
                        // check within the node, start <= pref <= end
1100
                        check("start:pref", dcTree, start, pref);
1101
                        check("pref:end", dcTree, pref, end);
1102

1103
                        // check cursor <= start
1104
                        check("cursor:start", dcTree, cursor, start);
1105
                        cursor = start;
1106

1107
                        // recursively scan any children, updating the cursor
1108
                        super.scan(node, ignore);
1109

1110
                        // check cursor <= end
1111
                        check("cursor:end", dcTree, cursor, end);
1112
                        cursor = end;
1113
                    }
1114
                    return null;
1115
                }
1116
            };
1117

1118
            cursor = 0;
1119
            scanner.scan(dc, null);
1120

1121
        }
1122

1123
        void check(String name, DCTree tree, int first, int second) {
1124
            if (!(first <= second)) {
1125
                error(name, tree, first, second);
1126
            }
1127
        }
1128

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);
1133
            }
1134
            error("Checking " + name + " for " + tree.getKind() + " `" + t + "`;  first:" + first + ", second:" + second);
1135

1136
        }
1137
    }
1138

1139
    /**
1140
     * Verifies that the start and end positions of all nodes in a DocCommentTree point to the
1141
     * expected characters in the source code.
1142
     *
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.
1146
     */
1147
    static class StartEndPosChecker extends Checker {
1148

1149
        StartEndPosChecker(DocCommentTester test, DocTrees docTrees) {
1150
            test.super(docTrees);
1151
        }
1152

1153
        @Override
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);
1158

1159
            DocTreeScanner<Void, Void> scanner = new DocTreeScanner<>() {
1160
                @Override
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());
1165

1166
                        try {
1167
                            StringWriter out = new StringWriter();
1168
                            DocPretty dp = new DocPretty(out);
1169
                            dp.print(trees.getDocCommentTree(path));
1170
                            String pretty = out.toString();
1171

1172
                            if (pretty.isEmpty()) {
1173
                                if (start != end) {
1174
                                    error("Error: expected content is empty, but actual content is not: "
1175
                                            + dcTree.getKind() + " [" + start + "," + end + ")"
1176
                                            + ": \"" + content.subSequence(start, end) + "\"" );
1177
                                }
1178
                            } else {
1179
                                check(dcTree, "start", content, start, pretty, 0);
1180
                                check(dcTree, "end", content, end - 1, pretty, pretty.length() - 1);
1181
                            }
1182

1183
                        } catch (IOException e) {
1184
                            error("Error generating DocPretty for tree at position " + start + "; " + e);
1185
                        }
1186
                    }
1187
                    return null;
1188
                }
1189
            };
1190

1191
            scanner.scan(dc, null);
1192
        }
1193

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) + "<<");
1197
            }
1198

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) + "<<");
1205
            }
1206
        }
1207

1208
        static final int MAX = 64;
1209

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);
1214
            }
1215
            return s;
1216
        }
1217

1218
    }
1219
}
1220

1221

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.