Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclint/Checker.java
41161 views
1
/*
2
* Copyright (c) 2012, 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. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
26
package jdk.javadoc.internal.doclint;
27
28
import java.io.IOException;
29
import java.io.StringWriter;
30
import java.net.URI;
31
import java.net.URISyntaxException;
32
import java.util.Deque;
33
import java.util.EnumSet;
34
import java.util.HashMap;
35
import java.util.HashSet;
36
import java.util.LinkedList;
37
import java.util.List;
38
import java.util.Map;
39
import java.util.Objects;
40
import java.util.Set;
41
import java.util.regex.Matcher;
42
import java.util.regex.Pattern;
43
44
import javax.lang.model.element.Element;
45
import javax.lang.model.element.ElementKind;
46
import javax.lang.model.element.ExecutableElement;
47
import javax.lang.model.element.Name;
48
import javax.lang.model.element.VariableElement;
49
import javax.lang.model.type.TypeKind;
50
import javax.lang.model.type.TypeMirror;
51
import javax.tools.Diagnostic.Kind;
52
import javax.tools.JavaFileObject;
53
54
import com.sun.source.doctree.AttributeTree;
55
import com.sun.source.doctree.AuthorTree;
56
import com.sun.source.doctree.DocCommentTree;
57
import com.sun.source.doctree.DocRootTree;
58
import com.sun.source.doctree.DocTree;
59
import com.sun.source.doctree.EndElementTree;
60
import com.sun.source.doctree.EntityTree;
61
import com.sun.source.doctree.ErroneousTree;
62
import com.sun.source.doctree.IdentifierTree;
63
import com.sun.source.doctree.IndexTree;
64
import com.sun.source.doctree.InheritDocTree;
65
import com.sun.source.doctree.LinkTree;
66
import com.sun.source.doctree.LiteralTree;
67
import com.sun.source.doctree.ParamTree;
68
import com.sun.source.doctree.ProvidesTree;
69
import com.sun.source.doctree.ReferenceTree;
70
import com.sun.source.doctree.ReturnTree;
71
import com.sun.source.doctree.SerialDataTree;
72
import com.sun.source.doctree.SerialFieldTree;
73
import com.sun.source.doctree.SinceTree;
74
import com.sun.source.doctree.StartElementTree;
75
import com.sun.source.doctree.SummaryTree;
76
import com.sun.source.doctree.SystemPropertyTree;
77
import com.sun.source.doctree.TextTree;
78
import com.sun.source.doctree.ThrowsTree;
79
import com.sun.source.doctree.UnknownBlockTagTree;
80
import com.sun.source.doctree.UnknownInlineTagTree;
81
import com.sun.source.doctree.UsesTree;
82
import com.sun.source.doctree.ValueTree;
83
import com.sun.source.doctree.VersionTree;
84
import com.sun.source.tree.Tree;
85
import com.sun.source.util.DocTreePath;
86
import com.sun.source.util.DocTreePathScanner;
87
import com.sun.source.util.TreePath;
88
import com.sun.tools.javac.tree.DocPretty;
89
import com.sun.tools.javac.util.Assert;
90
import com.sun.tools.javac.util.DefinedBy;
91
import com.sun.tools.javac.util.DefinedBy.Api;
92
93
import jdk.javadoc.internal.doclint.HtmlTag.AttrKind;
94
import jdk.javadoc.internal.doclint.HtmlTag.ElemKind;
95
import static jdk.javadoc.internal.doclint.Messages.Group.*;
96
97
98
/**
99
* Validate a doc comment.
100
*
101
* <p><b>This is NOT part of any supported API.
102
* If you write code that depends on this, you do so at your own
103
* risk. This code and its internal interfaces are subject to change
104
* or deletion without notice.</b></p>
105
*/
106
public class Checker extends DocTreePathScanner<Void, Void> {
107
final Env env;
108
109
Set<Element> foundParams = new HashSet<>();
110
Set<TypeMirror> foundThrows = new HashSet<>();
111
Map<Element, Set<String>> foundAnchors = new HashMap<>();
112
boolean foundInheritDoc = false;
113
boolean foundReturn = false;
114
boolean hasNonWhitespaceText = false;
115
116
public enum Flag {
117
TABLE_HAS_CAPTION,
118
TABLE_IS_PRESENTATION,
119
HAS_ELEMENT,
120
HAS_HEADING,
121
HAS_INLINE_TAG,
122
HAS_TEXT,
123
REPORTED_BAD_INLINE
124
}
125
126
static class TagStackItem {
127
final DocTree tree; // typically, but not always, StartElementTree
128
final HtmlTag tag;
129
final Set<HtmlTag.Attr> attrs;
130
final Set<Flag> flags;
131
TagStackItem(DocTree tree, HtmlTag tag) {
132
this.tree = tree;
133
this.tag = tag;
134
attrs = EnumSet.noneOf(HtmlTag.Attr.class);
135
flags = EnumSet.noneOf(Flag.class);
136
}
137
@Override
138
public String toString() {
139
return String.valueOf(tag);
140
}
141
}
142
143
private final Deque<TagStackItem> tagStack; // TODO: maybe want to record starting tree as well
144
private HtmlTag currHeadingTag;
145
146
private int implicitHeadingRank;
147
private boolean inIndex;
148
private boolean inLink;
149
private boolean inSummary;
150
151
// <editor-fold defaultstate="collapsed" desc="Top level">
152
153
Checker(Env env) {
154
this.env = Assert.checkNonNull(env);
155
tagStack = new LinkedList<>();
156
}
157
158
public Void scan(DocCommentTree tree, TreePath p) {
159
env.initTypes();
160
env.setCurrent(p, tree);
161
162
boolean isOverridingMethod = !env.currOverriddenMethods.isEmpty();
163
JavaFileObject fo = p.getCompilationUnit().getSourceFile();
164
165
if (p.getLeaf().getKind() == Tree.Kind.PACKAGE) {
166
// If p points to a package, the implied declaration is the
167
// package declaration (if any) for the compilation unit.
168
// Handle this case specially, because doc comments are only
169
// expected in package-info files.
170
boolean isPkgInfo = fo.isNameCompatible("package-info", JavaFileObject.Kind.SOURCE);
171
if (tree == null) {
172
if (isPkgInfo)
173
reportMissing("dc.missing.comment");
174
return null;
175
} else {
176
if (!isPkgInfo)
177
reportReference("dc.unexpected.comment");
178
}
179
} else if (tree != null && fo.isNameCompatible("package", JavaFileObject.Kind.HTML)) {
180
// a package.html file with a DocCommentTree
181
if (tree.getFullBody().isEmpty()) {
182
reportMissing("dc.missing.comment");
183
return null;
184
}
185
} else {
186
if (tree == null) {
187
if (!isSynthetic() && !isOverridingMethod)
188
reportMissing("dc.missing.comment");
189
return null;
190
}
191
}
192
193
tagStack.clear();
194
currHeadingTag = null;
195
196
foundParams.clear();
197
foundThrows.clear();
198
foundInheritDoc = false;
199
foundReturn = false;
200
hasNonWhitespaceText = false;
201
202
switch (p.getLeaf().getKind()) {
203
// the following are for declarations that have their own top-level page,
204
// and so the doc comment comes after the <h1> page title.
205
case MODULE:
206
case PACKAGE:
207
case CLASS:
208
case INTERFACE:
209
case ENUM:
210
case ANNOTATION_TYPE:
211
case RECORD:
212
implicitHeadingRank = 1;
213
break;
214
215
// this is for html files
216
// ... if it is a legacy package.html, the doc comment comes after the <h1> page title
217
// ... otherwise, (e.g. overview file and doc-files/*.html files) no additional headings are inserted
218
case COMPILATION_UNIT:
219
implicitHeadingRank = fo.isNameCompatible("package", JavaFileObject.Kind.HTML) ? 1 : 0;
220
break;
221
222
// the following are for member declarations, which appear in the page
223
// for the enclosing type, and so appear after the <h2> "Members"
224
// aggregate heading and the specific <h3> "Member signature" heading.
225
case METHOD:
226
case VARIABLE:
227
implicitHeadingRank = 3;
228
break;
229
230
default:
231
Assert.error("unexpected tree kind: " + p.getLeaf().getKind() + " " + fo);
232
}
233
234
scan(new DocTreePath(p, tree), null);
235
236
if (!isOverridingMethod) {
237
switch (env.currElement.getKind()) {
238
case METHOD:
239
case CONSTRUCTOR: {
240
ExecutableElement ee = (ExecutableElement) env.currElement;
241
checkParamsDocumented(ee.getTypeParameters());
242
checkParamsDocumented(ee.getParameters());
243
switch (ee.getReturnType().getKind()) {
244
case VOID:
245
case NONE:
246
break;
247
default:
248
if (!foundReturn
249
&& !foundInheritDoc
250
&& !env.types.isSameType(ee.getReturnType(), env.java_lang_Void)) {
251
reportMissing("dc.missing.return");
252
}
253
}
254
checkThrowsDocumented(ee.getThrownTypes());
255
}
256
}
257
}
258
259
return null;
260
}
261
262
private void reportMissing(String code, Object... args) {
263
env.messages.report(MISSING, Kind.WARNING, env.currPath.getLeaf(), code, args);
264
}
265
266
private void reportReference(String code, Object... args) {
267
env.messages.report(REFERENCE, Kind.WARNING, env.currPath.getLeaf(), code, args);
268
}
269
270
@Override @DefinedBy(Api.COMPILER_TREE)
271
public Void visitDocComment(DocCommentTree tree, Void ignore) {
272
scan(tree.getFirstSentence(), ignore);
273
scan(tree.getBody(), ignore);
274
checkTagStack();
275
276
for (DocTree blockTag : tree.getBlockTags()) {
277
tagStack.clear();
278
scan(blockTag, ignore);
279
checkTagStack();
280
}
281
282
return null;
283
}
284
285
private void checkTagStack() {
286
for (TagStackItem tsi: tagStack) {
287
warnIfEmpty(tsi, null);
288
if (tsi.tree.getKind() == DocTree.Kind.START_ELEMENT
289
&& tsi.tag.endKind == HtmlTag.EndKind.REQUIRED) {
290
StartElementTree t = (StartElementTree) tsi.tree;
291
env.messages.error(HTML, t, "dc.tag.not.closed", t.getName());
292
}
293
}
294
}
295
// </editor-fold>
296
297
// <editor-fold defaultstate="collapsed" desc="Text and entities.">
298
299
@Override @DefinedBy(Api.COMPILER_TREE)
300
public Void visitText(TextTree tree, Void ignore) {
301
hasNonWhitespaceText = hasNonWhitespace(tree);
302
if (hasNonWhitespaceText) {
303
checkAllowsText(tree);
304
markEnclosingTag(Flag.HAS_TEXT);
305
}
306
return null;
307
}
308
309
@Override @DefinedBy(Api.COMPILER_TREE)
310
public Void visitEntity(EntityTree tree, Void ignore) {
311
checkAllowsText(tree);
312
markEnclosingTag(Flag.HAS_TEXT);
313
String s = env.trees.getCharacters(tree);
314
if (s == null) {
315
env.messages.error(HTML, tree, "dc.entity.invalid", tree.getName());
316
}
317
return null;
318
319
}
320
321
void checkAllowsText(DocTree tree) {
322
TagStackItem top = tagStack.peek();
323
if (top != null
324
&& top.tree.getKind() == DocTree.Kind.START_ELEMENT
325
&& !top.tag.acceptsText()) {
326
if (top.flags.add(Flag.REPORTED_BAD_INLINE)) {
327
env.messages.error(HTML, tree, "dc.text.not.allowed",
328
((StartElementTree) top.tree).getName());
329
}
330
}
331
}
332
333
// </editor-fold>
334
335
// <editor-fold defaultstate="collapsed" desc="HTML elements">
336
337
@Override @DefinedBy(Api.COMPILER_TREE)
338
public Void visitStartElement(StartElementTree tree, Void ignore) {
339
final Name treeName = tree.getName();
340
final HtmlTag t = HtmlTag.get(treeName);
341
if (t == null) {
342
env.messages.error(HTML, tree, "dc.tag.unknown", treeName);
343
} else if (t.elemKind == ElemKind.HTML4) {
344
env.messages.error(HTML, tree, "dc.tag.not.supported.html5", treeName);
345
} else {
346
boolean done = false;
347
for (TagStackItem tsi: tagStack) {
348
if (tsi.tag.accepts(t)) {
349
while (tagStack.peek() != tsi) {
350
warnIfEmpty(tagStack.peek(), null);
351
tagStack.pop();
352
}
353
done = true;
354
break;
355
} else if (tsi.tag.endKind != HtmlTag.EndKind.OPTIONAL) {
356
done = true;
357
break;
358
}
359
}
360
if (!done && HtmlTag.BODY.accepts(t)) {
361
while (!tagStack.isEmpty()) {
362
warnIfEmpty(tagStack.peek(), null);
363
tagStack.pop();
364
}
365
}
366
367
markEnclosingTag(Flag.HAS_ELEMENT);
368
checkStructure(tree, t);
369
370
// tag specific checks
371
switch (t) {
372
// check for out of sequence headings, such as <h1>...</h1> <h3>...</h3>
373
case H1: case H2: case H3: case H4: case H5: case H6:
374
checkHeading(tree, t);
375
break;
376
}
377
378
if (t.flags.contains(HtmlTag.Flag.NO_NEST)) {
379
for (TagStackItem i: tagStack) {
380
if (t == i.tag) {
381
env.messages.warning(HTML, tree, "dc.tag.nested.not.allowed", treeName);
382
break;
383
}
384
}
385
}
386
}
387
388
// check for self closing tags, such as <a id="name"/>
389
if (tree.isSelfClosing() && !isSelfClosingAllowed(t)) {
390
env.messages.error(HTML, tree, "dc.tag.self.closing", treeName);
391
}
392
393
try {
394
TagStackItem parent = tagStack.peek();
395
TagStackItem top = new TagStackItem(tree, t);
396
tagStack.push(top);
397
398
super.visitStartElement(tree, ignore);
399
400
// handle attributes that may or may not have been found in start element
401
if (t != null) {
402
switch (t) {
403
case CAPTION:
404
if (parent != null && parent.tag == HtmlTag.TABLE)
405
parent.flags.add(Flag.TABLE_HAS_CAPTION);
406
break;
407
408
case H1: case H2: case H3: case H4: case H5: case H6:
409
if (parent != null && (parent.tag == HtmlTag.SECTION || parent.tag == HtmlTag.ARTICLE)) {
410
parent.flags.add(Flag.HAS_HEADING);
411
}
412
break;
413
414
case IMG:
415
if (!top.attrs.contains(HtmlTag.Attr.ALT))
416
env.messages.error(ACCESSIBILITY, tree, "dc.no.alt.attr.for.image");
417
break;
418
}
419
}
420
421
return null;
422
} finally {
423
424
if (t == null || t.endKind == HtmlTag.EndKind.NONE)
425
tagStack.pop();
426
}
427
}
428
429
// so-called "self-closing" tags are only permitted in HTML 5, for void elements
430
// https://html.spec.whatwg.org/multipage/syntax.html#start-tags
431
private boolean isSelfClosingAllowed(HtmlTag tag) {
432
return tag.endKind == HtmlTag.EndKind.NONE;
433
}
434
435
private void checkStructure(StartElementTree tree, HtmlTag t) {
436
Name treeName = tree.getName();
437
TagStackItem top = tagStack.peek();
438
switch (t.blockType) {
439
case BLOCK:
440
if (top == null || top.tag.accepts(t))
441
return;
442
443
switch (top.tree.getKind()) {
444
case START_ELEMENT: {
445
if (top.tag.blockType == HtmlTag.BlockType.INLINE) {
446
Name name = ((StartElementTree) top.tree).getName();
447
env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.element",
448
treeName, name);
449
return;
450
}
451
}
452
break;
453
454
case LINK:
455
case LINK_PLAIN: {
456
String name = top.tree.getKind().tagName;
457
env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.tag",
458
treeName, name);
459
return;
460
}
461
}
462
break;
463
464
case INLINE:
465
if (top == null || top.tag.accepts(t))
466
return;
467
break;
468
469
case LIST_ITEM:
470
case TABLE_ITEM:
471
if (top != null) {
472
// reset this flag so subsequent bad inline content gets reported
473
top.flags.remove(Flag.REPORTED_BAD_INLINE);
474
if (top.tag.accepts(t))
475
return;
476
}
477
break;
478
479
case OTHER:
480
switch (t) {
481
case SCRIPT:
482
// <script> may or may not be allowed, depending on --allow-script-in-comments
483
// but we allow it here, and rely on a separate scanner to detect all uses
484
// of JavaScript, including <script> tags, and use in attributes, etc.
485
break;
486
487
default:
488
env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName);
489
}
490
return;
491
}
492
493
env.messages.error(HTML, tree, "dc.tag.not.allowed.here", treeName);
494
}
495
496
private void checkHeading(StartElementTree tree, HtmlTag tag) {
497
// verify the new tag
498
if (getHeadingRank(tag) > getHeadingRank(currHeadingTag) + 1) {
499
if (currHeadingTag == null) {
500
env.messages.error(ACCESSIBILITY, tree, "dc.tag.heading.sequence.1",
501
tag, implicitHeadingRank);
502
} else {
503
env.messages.error(ACCESSIBILITY, tree, "dc.tag.heading.sequence.2",
504
tag, currHeadingTag);
505
}
506
} else if (getHeadingRank(tag) <= implicitHeadingRank) {
507
env.messages.error(ACCESSIBILITY, tree, "dc.tag.heading.sequence.3",
508
tag, implicitHeadingRank);
509
}
510
511
currHeadingTag = tag;
512
}
513
514
private int getHeadingRank(HtmlTag tag) {
515
if (tag == null)
516
return implicitHeadingRank;
517
switch (tag) {
518
case H1: return 1;
519
case H2: return 2;
520
case H3: return 3;
521
case H4: return 4;
522
case H5: return 5;
523
case H6: return 6;
524
default: throw new IllegalArgumentException();
525
}
526
}
527
528
@Override @DefinedBy(Api.COMPILER_TREE)
529
public Void visitEndElement(EndElementTree tree, Void ignore) {
530
final Name treeName = tree.getName();
531
final HtmlTag t = HtmlTag.get(treeName);
532
if (t == null) {
533
env.messages.error(HTML, tree, "dc.tag.unknown", treeName);
534
} else if (t.endKind == HtmlTag.EndKind.NONE) {
535
env.messages.error(HTML, tree, "dc.tag.end.not.permitted", treeName);
536
} else {
537
boolean done = false;
538
while (!tagStack.isEmpty()) {
539
TagStackItem top = tagStack.peek();
540
if (t == top.tag) {
541
switch (t) {
542
case TABLE:
543
if (!top.flags.contains(Flag.TABLE_IS_PRESENTATION)
544
&& !top.attrs.contains(HtmlTag.Attr.SUMMARY)
545
&& !top.flags.contains(Flag.TABLE_HAS_CAPTION)) {
546
env.messages.error(ACCESSIBILITY, tree,
547
"dc.no.summary.or.caption.for.table");
548
}
549
break;
550
551
case SECTION:
552
case ARTICLE:
553
if (!top.flags.contains(Flag.HAS_HEADING)) {
554
env.messages.error(HTML, tree, "dc.tag.requires.heading", treeName);
555
}
556
break;
557
}
558
warnIfEmpty(top, tree);
559
tagStack.pop();
560
done = true;
561
break;
562
} else if (top.tag == null || top.tag.endKind != HtmlTag.EndKind.REQUIRED) {
563
warnIfEmpty(top, null);
564
tagStack.pop();
565
} else {
566
boolean found = false;
567
for (TagStackItem si: tagStack) {
568
if (si.tag == t) {
569
found = true;
570
break;
571
}
572
}
573
if (found && top.tree.getKind() == DocTree.Kind.START_ELEMENT) {
574
env.messages.error(HTML, top.tree, "dc.tag.start.unmatched",
575
((StartElementTree) top.tree).getName());
576
tagStack.pop();
577
} else {
578
env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName);
579
done = true;
580
break;
581
}
582
}
583
}
584
585
if (!done && tagStack.isEmpty()) {
586
env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName);
587
}
588
}
589
590
return super.visitEndElement(tree, ignore);
591
}
592
593
void warnIfEmpty(TagStackItem tsi, DocTree endTree) {
594
if (tsi.tag != null && tsi.tree instanceof StartElementTree startTree) {
595
if (tsi.tag.flags.contains(HtmlTag.Flag.EXPECT_CONTENT)
596
&& !tsi.flags.contains(Flag.HAS_TEXT)
597
&& !tsi.flags.contains(Flag.HAS_ELEMENT)
598
&& !tsi.flags.contains(Flag.HAS_INLINE_TAG)
599
&& !(tsi.tag.elemKind == ElemKind.HTML4)) {
600
DocTree tree = (endTree != null) ? endTree : startTree;
601
Name treeName = startTree.getName();
602
env.messages.warning(HTML, tree, "dc.tag.empty", treeName);
603
}
604
}
605
}
606
607
// </editor-fold>
608
609
// <editor-fold defaultstate="collapsed" desc="HTML attributes">
610
611
@Override @DefinedBy(Api.COMPILER_TREE) @SuppressWarnings("fallthrough")
612
public Void visitAttribute(AttributeTree tree, Void ignore) {
613
HtmlTag currTag = tagStack.peek().tag;
614
if (currTag != null && currTag.elemKind != ElemKind.HTML4) {
615
Name name = tree.getName();
616
HtmlTag.Attr attr = currTag.getAttr(name);
617
if (attr != null) {
618
boolean first = tagStack.peek().attrs.add(attr);
619
if (!first)
620
env.messages.error(HTML, tree, "dc.attr.repeated", name);
621
}
622
// for now, doclint allows all attribute names beginning with "on" as event handler names,
623
// without checking the validity or applicability of the name
624
if (!name.toString().startsWith("on")) {
625
AttrKind k = currTag.getAttrKind(name);
626
switch (k) {
627
case OK:
628
break;
629
case OBSOLETE:
630
env.messages.warning(HTML, tree, "dc.attr.obsolete", name);
631
break;
632
case HTML4:
633
env.messages.error(HTML, tree, "dc.attr.not.supported.html5", name);
634
break;
635
case INVALID:
636
env.messages.error(HTML, tree, "dc.attr.unknown", name);
637
break;
638
}
639
}
640
641
if (attr != null) {
642
switch (attr) {
643
case ID:
644
String value = getAttrValue(tree);
645
if (value == null) {
646
env.messages.error(HTML, tree, "dc.anchor.value.missing");
647
} else {
648
if (!validId.matcher(value).matches()) {
649
env.messages.error(HTML, tree, "dc.invalid.anchor", value);
650
}
651
if (!checkAnchor(value)) {
652
env.messages.error(HTML, tree, "dc.anchor.already.defined", value);
653
}
654
}
655
break;
656
657
case HREF:
658
if (currTag == HtmlTag.A) {
659
String v = getAttrValue(tree);
660
if (v == null || v.isEmpty()) {
661
env.messages.error(HTML, tree, "dc.attr.lacks.value");
662
} else {
663
Matcher m = docRoot.matcher(v);
664
if (m.matches()) {
665
String rest = m.group(2);
666
if (!rest.isEmpty())
667
checkURI(tree, rest);
668
} else {
669
checkURI(tree, v);
670
}
671
}
672
}
673
break;
674
675
case VALUE:
676
if (currTag == HtmlTag.LI) {
677
String v = getAttrValue(tree);
678
if (v == null || v.isEmpty()) {
679
env.messages.error(HTML, tree, "dc.attr.lacks.value");
680
} else if (!validNumber.matcher(v).matches()) {
681
env.messages.error(HTML, tree, "dc.attr.not.number");
682
}
683
}
684
break;
685
686
case BORDER:
687
if (currTag == HtmlTag.TABLE) {
688
String v = getAttrValue(tree);
689
try {
690
if (v == null || (!v.isEmpty() && Integer.parseInt(v) != 1)) {
691
env.messages.error(HTML, tree, "dc.attr.table.border.not.valid", attr);
692
}
693
} catch (NumberFormatException ex) {
694
env.messages.error(HTML, tree, "dc.attr.table.border.not.number", attr);
695
}
696
} else if (currTag == HtmlTag.IMG) {
697
String v = getAttrValue(tree);
698
try {
699
if (v == null || (!v.isEmpty() && Integer.parseInt(v) != 0)) {
700
env.messages.error(HTML, tree, "dc.attr.img.border.not.valid", attr);
701
}
702
} catch (NumberFormatException ex) {
703
env.messages.error(HTML, tree, "dc.attr.img.border.not.number", attr);
704
}
705
}
706
break;
707
708
case ROLE:
709
if (currTag == HtmlTag.TABLE) {
710
String v = getAttrValue(tree);
711
if (Objects.equals(v, "presentation")) {
712
tagStack.peek().flags.add(Flag.TABLE_IS_PRESENTATION);
713
}
714
}
715
break;
716
}
717
}
718
}
719
720
// TODO: basic check on value
721
722
return null;
723
}
724
725
726
private boolean checkAnchor(String name) {
727
Element e = getEnclosingPackageOrClass(env.currElement);
728
if (e == null)
729
return true;
730
Set<String> set = foundAnchors.get(e);
731
if (set == null)
732
foundAnchors.put(e, set = new HashSet<>());
733
return set.add(name);
734
}
735
736
private Element getEnclosingPackageOrClass(Element e) {
737
while (e != null) {
738
switch (e.getKind()) {
739
case CLASS:
740
case ENUM:
741
case INTERFACE:
742
case PACKAGE:
743
return e;
744
default:
745
e = e.getEnclosingElement();
746
}
747
}
748
return e;
749
}
750
751
// https://html.spec.whatwg.org/#the-id-attribute
752
private static final Pattern validId = Pattern.compile("[^\\s]+");
753
754
private static final Pattern validNumber = Pattern.compile("-?[0-9]+");
755
756
// pattern to remove leading {@docRoot}/?
757
private static final Pattern docRoot = Pattern.compile("(?i)(\\{@docRoot *\\}/?)?(.*)");
758
759
private String getAttrValue(AttributeTree tree) {
760
if (tree.getValue() == null)
761
return null;
762
763
StringWriter sw = new StringWriter();
764
try {
765
new DocPretty(sw).print(tree.getValue());
766
} catch (IOException e) {
767
// cannot happen
768
}
769
// ignore potential use of entities for now
770
return sw.toString();
771
}
772
773
private void checkURI(AttributeTree tree, String uri) {
774
// allow URIs beginning with javascript:, which would otherwise be rejected by the URI API.
775
if (uri.startsWith("javascript:"))
776
return;
777
try {
778
URI u = new URI(uri);
779
} catch (URISyntaxException e) {
780
env.messages.error(HTML, tree, "dc.invalid.uri", uri);
781
}
782
}
783
// </editor-fold>
784
785
// <editor-fold defaultstate="collapsed" desc="javadoc tags">
786
787
@Override @DefinedBy(Api.COMPILER_TREE)
788
public Void visitAuthor(AuthorTree tree, Void ignore) {
789
warnIfEmpty(tree, tree.getName());
790
return super.visitAuthor(tree, ignore);
791
}
792
793
@Override @DefinedBy(Api.COMPILER_TREE)
794
public Void visitDocRoot(DocRootTree tree, Void ignore) {
795
markEnclosingTag(Flag.HAS_INLINE_TAG);
796
return super.visitDocRoot(tree, ignore);
797
}
798
799
@Override @DefinedBy(Api.COMPILER_TREE)
800
public Void visitIndex(IndexTree tree, Void ignore) {
801
markEnclosingTag(Flag.HAS_INLINE_TAG);
802
if (inIndex) {
803
env.messages.warning(HTML, tree, "dc.tag.nested.tag", "@" + tree.getTagName());
804
}
805
for (TagStackItem tsi : tagStack) {
806
if (tsi.tag == HtmlTag.A) {
807
env.messages.warning(HTML, tree, "dc.tag.a.within.a",
808
"{@" + tree.getTagName() + "}");
809
break;
810
}
811
}
812
boolean prevInIndex = inIndex;
813
try {
814
inIndex = true;
815
return super.visitIndex(tree, ignore);
816
} finally {
817
inIndex = prevInIndex;
818
}
819
}
820
821
@Override @DefinedBy(Api.COMPILER_TREE)
822
public Void visitInheritDoc(InheritDocTree tree, Void ignore) {
823
markEnclosingTag(Flag.HAS_INLINE_TAG);
824
// TODO: verify on overridden method
825
foundInheritDoc = true;
826
return super.visitInheritDoc(tree, ignore);
827
}
828
829
@Override @DefinedBy(Api.COMPILER_TREE)
830
public Void visitLink(LinkTree tree, Void ignore) {
831
markEnclosingTag(Flag.HAS_INLINE_TAG);
832
if (inLink) {
833
env.messages.warning(HTML, tree, "dc.tag.nested.tag", "@" + tree.getTagName());
834
}
835
boolean prevInLink = inLink;
836
// simulate inline context on tag stack
837
HtmlTag t = (tree.getKind() == DocTree.Kind.LINK)
838
? HtmlTag.CODE : HtmlTag.SPAN;
839
tagStack.push(new TagStackItem(tree, t));
840
try {
841
inLink = true;
842
return super.visitLink(tree, ignore);
843
} finally {
844
tagStack.pop();
845
inLink = prevInLink;
846
}
847
}
848
849
@Override @DefinedBy(Api.COMPILER_TREE)
850
public Void visitLiteral(LiteralTree tree, Void ignore) {
851
markEnclosingTag(Flag.HAS_INLINE_TAG);
852
if (tree.getKind() == DocTree.Kind.CODE) {
853
for (TagStackItem tsi: tagStack) {
854
if (tsi.tag == HtmlTag.CODE) {
855
env.messages.warning(HTML, tree, "dc.tag.code.within.code");
856
break;
857
}
858
}
859
}
860
return super.visitLiteral(tree, ignore);
861
}
862
863
@Override @DefinedBy(Api.COMPILER_TREE)
864
@SuppressWarnings("fallthrough")
865
public Void visitParam(ParamTree tree, Void ignore) {
866
boolean typaram = tree.isTypeParameter();
867
IdentifierTree nameTree = tree.getName();
868
Element paramElement = nameTree != null ? env.trees.getElement(new DocTreePath(getCurrentPath(), nameTree)) : null;
869
870
if (paramElement == null) {
871
switch (env.currElement.getKind()) {
872
case CLASS: case INTERFACE: {
873
if (!typaram) {
874
env.messages.error(REFERENCE, tree, "dc.invalid.param");
875
break;
876
}
877
}
878
case METHOD: case CONSTRUCTOR: {
879
env.messages.error(REFERENCE, nameTree, "dc.param.name.not.found");
880
break;
881
}
882
883
default:
884
env.messages.error(REFERENCE, tree, "dc.invalid.param");
885
break;
886
}
887
} else {
888
boolean unique = foundParams.add(paramElement);
889
890
if (!unique) {
891
env.messages.warning(REFERENCE, tree, "dc.exists.param", nameTree);
892
}
893
}
894
895
warnIfEmpty(tree, tree.getDescription());
896
return super.visitParam(tree, ignore);
897
}
898
899
private void checkParamsDocumented(List<? extends Element> list) {
900
if (foundInheritDoc)
901
return;
902
903
for (Element e: list) {
904
if (!foundParams.contains(e)) {
905
CharSequence paramName = (e.getKind() == ElementKind.TYPE_PARAMETER)
906
? "<" + e.getSimpleName() + ">"
907
: e.getSimpleName();
908
reportMissing("dc.missing.param", paramName);
909
}
910
}
911
}
912
913
@Override @DefinedBy(Api.COMPILER_TREE)
914
public Void visitProvides(ProvidesTree tree, Void ignore) {
915
Element e = env.trees.getElement(env.currPath);
916
if (e.getKind() != ElementKind.MODULE) {
917
env.messages.error(REFERENCE, tree, "dc.invalid.provides");
918
}
919
ReferenceTree serviceType = tree.getServiceType();
920
Element se = env.trees.getElement(new DocTreePath(getCurrentPath(), serviceType));
921
if (se == null) {
922
env.messages.error(REFERENCE, tree, "dc.service.not.found");
923
}
924
return super.visitProvides(tree, ignore);
925
}
926
927
@Override @DefinedBy(Api.COMPILER_TREE)
928
public Void visitReference(ReferenceTree tree, Void ignore) {
929
Element e = env.trees.getElement(getCurrentPath());
930
if (e == null)
931
env.messages.error(REFERENCE, tree, "dc.ref.not.found");
932
return super.visitReference(tree, ignore);
933
}
934
935
@Override @DefinedBy(Api.COMPILER_TREE)
936
public Void visitReturn(ReturnTree tree, Void ignore) {
937
if (foundReturn) {
938
env.messages.warning(REFERENCE, tree, "dc.exists.return");
939
}
940
if (tree.isInline()) {
941
DocCommentTree dct = getCurrentPath().getDocComment();
942
if (tree != dct.getFirstSentence().get(0)) {
943
env.messages.warning(REFERENCE, tree, "dc.return.not.first");
944
}
945
}
946
947
Element e = env.trees.getElement(env.currPath);
948
if (e.getKind() != ElementKind.METHOD
949
|| ((ExecutableElement) e).getReturnType().getKind() == TypeKind.VOID)
950
env.messages.error(REFERENCE, tree, "dc.invalid.return");
951
foundReturn = true;
952
warnIfEmpty(tree, tree.getDescription());
953
return super.visitReturn(tree, ignore);
954
}
955
956
@Override @DefinedBy(Api.COMPILER_TREE)
957
public Void visitSerialData(SerialDataTree tree, Void ignore) {
958
warnIfEmpty(tree, tree.getDescription());
959
return super.visitSerialData(tree, ignore);
960
}
961
962
@Override @DefinedBy(Api.COMPILER_TREE)
963
public Void visitSerialField(SerialFieldTree tree, Void ignore) {
964
warnIfEmpty(tree, tree.getDescription());
965
return super.visitSerialField(tree, ignore);
966
}
967
968
@Override @DefinedBy(Api.COMPILER_TREE)
969
public Void visitSince(SinceTree tree, Void ignore) {
970
warnIfEmpty(tree, tree.getBody());
971
return super.visitSince(tree, ignore);
972
}
973
974
@Override @DefinedBy(Api.COMPILER_TREE)
975
public Void visitSummary(SummaryTree tree, Void aVoid) {
976
markEnclosingTag(Flag.HAS_INLINE_TAG);
977
if (inSummary) {
978
env.messages.warning(HTML, tree, "dc.tag.nested.tag", "@" + tree.getTagName());
979
}
980
int idx = env.currDocComment.getFullBody().indexOf(tree);
981
// Warn if the node is preceded by non-whitespace characters,
982
// or other non-text nodes.
983
if ((idx == 1 && hasNonWhitespaceText) || idx > 1) {
984
env.messages.warning(SYNTAX, tree, "dc.invalid.summary", tree.getTagName());
985
}
986
boolean prevInSummary = inSummary;
987
try {
988
inSummary = true;
989
return super.visitSummary(tree, aVoid);
990
} finally {
991
inSummary = prevInSummary;
992
}
993
}
994
995
@Override @DefinedBy(Api.COMPILER_TREE)
996
public Void visitSystemProperty(SystemPropertyTree tree, Void ignore) {
997
markEnclosingTag(Flag.HAS_INLINE_TAG);
998
for (TagStackItem tsi : tagStack) {
999
if (tsi.tag == HtmlTag.A) {
1000
env.messages.warning(HTML, tree, "dc.tag.a.within.a",
1001
"{@" + tree.getTagName() + "}");
1002
break;
1003
}
1004
}
1005
return super.visitSystemProperty(tree, ignore);
1006
}
1007
1008
@Override @DefinedBy(Api.COMPILER_TREE)
1009
public Void visitThrows(ThrowsTree tree, Void ignore) {
1010
ReferenceTree exName = tree.getExceptionName();
1011
Element ex = env.trees.getElement(new DocTreePath(getCurrentPath(), exName));
1012
if (ex == null) {
1013
env.messages.error(REFERENCE, tree, "dc.ref.not.found");
1014
} else if (isThrowable(ex.asType())) {
1015
switch (env.currElement.getKind()) {
1016
case CONSTRUCTOR:
1017
case METHOD:
1018
if (isCheckedException(ex.asType())) {
1019
ExecutableElement ee = (ExecutableElement) env.currElement;
1020
checkThrowsDeclared(exName, ex.asType(), ee.getThrownTypes());
1021
}
1022
break;
1023
default:
1024
env.messages.error(REFERENCE, tree, "dc.invalid.throws");
1025
}
1026
} else {
1027
env.messages.error(REFERENCE, tree, "dc.invalid.throws");
1028
}
1029
warnIfEmpty(tree, tree.getDescription());
1030
return scan(tree.getDescription(), ignore);
1031
}
1032
1033
private boolean isThrowable(TypeMirror tm) {
1034
switch (tm.getKind()) {
1035
case DECLARED:
1036
case TYPEVAR:
1037
return env.types.isAssignable(tm, env.java_lang_Throwable);
1038
}
1039
return false;
1040
}
1041
1042
private void checkThrowsDeclared(ReferenceTree tree, TypeMirror t, List<? extends TypeMirror> list) {
1043
boolean found = false;
1044
for (TypeMirror tl : list) {
1045
if (env.types.isAssignable(t, tl)) {
1046
foundThrows.add(tl);
1047
found = true;
1048
}
1049
}
1050
if (!found)
1051
env.messages.error(REFERENCE, tree, "dc.exception.not.thrown", t);
1052
}
1053
1054
private void checkThrowsDocumented(List<? extends TypeMirror> list) {
1055
if (foundInheritDoc)
1056
return;
1057
1058
for (TypeMirror tl: list) {
1059
if (isCheckedException(tl) && !foundThrows.contains(tl))
1060
reportMissing("dc.missing.throws", tl);
1061
}
1062
}
1063
1064
@Override @DefinedBy(Api.COMPILER_TREE)
1065
public Void visitUnknownBlockTag(UnknownBlockTagTree tree, Void ignore) {
1066
checkUnknownTag(tree, tree.getTagName());
1067
return super.visitUnknownBlockTag(tree, ignore);
1068
}
1069
1070
@Override @DefinedBy(Api.COMPILER_TREE)
1071
public Void visitUnknownInlineTag(UnknownInlineTagTree tree, Void ignore) {
1072
markEnclosingTag(Flag.HAS_INLINE_TAG);
1073
checkUnknownTag(tree, tree.getTagName());
1074
return super.visitUnknownInlineTag(tree, ignore);
1075
}
1076
1077
private void checkUnknownTag(DocTree tree, String tagName) {
1078
if (env.customTags != null && !env.customTags.contains(tagName))
1079
env.messages.error(SYNTAX, tree, "dc.tag.unknown", tagName);
1080
}
1081
1082
@Override @DefinedBy(Api.COMPILER_TREE)
1083
public Void visitUses(UsesTree tree, Void ignore) {
1084
Element e = env.trees.getElement(env.currPath);
1085
if (e.getKind() != ElementKind.MODULE) {
1086
env.messages.error(REFERENCE, tree, "dc.invalid.uses");
1087
}
1088
ReferenceTree serviceType = tree.getServiceType();
1089
Element se = env.trees.getElement(new DocTreePath(getCurrentPath(), serviceType));
1090
if (se == null) {
1091
env.messages.error(REFERENCE, tree, "dc.service.not.found");
1092
}
1093
return super.visitUses(tree, ignore);
1094
}
1095
1096
@Override @DefinedBy(Api.COMPILER_TREE)
1097
public Void visitValue(ValueTree tree, Void ignore) {
1098
ReferenceTree ref = tree.getReference();
1099
if (ref == null || ref.getSignature().isEmpty()) {
1100
if (!isConstant(env.currElement))
1101
env.messages.error(REFERENCE, tree, "dc.value.not.allowed.here");
1102
} else {
1103
Element e = env.trees.getElement(new DocTreePath(getCurrentPath(), ref));
1104
if (!isConstant(e))
1105
env.messages.error(REFERENCE, tree, "dc.value.not.a.constant");
1106
}
1107
1108
markEnclosingTag(Flag.HAS_INLINE_TAG);
1109
return super.visitValue(tree, ignore);
1110
}
1111
1112
private boolean isConstant(Element e) {
1113
if (e == null)
1114
return false;
1115
1116
switch (e.getKind()) {
1117
case FIELD:
1118
Object value = ((VariableElement) e).getConstantValue();
1119
return (value != null); // can't distinguish "not a constant" from "constant is null"
1120
default:
1121
return false;
1122
}
1123
}
1124
1125
@Override @DefinedBy(Api.COMPILER_TREE)
1126
public Void visitVersion(VersionTree tree, Void ignore) {
1127
warnIfEmpty(tree, tree.getBody());
1128
return super.visitVersion(tree, ignore);
1129
}
1130
1131
@Override @DefinedBy(Api.COMPILER_TREE)
1132
public Void visitErroneous(ErroneousTree tree, Void ignore) {
1133
env.messages.error(SYNTAX, tree, null, tree.getDiagnostic().getMessage(null));
1134
return null;
1135
}
1136
// </editor-fold>
1137
1138
// <editor-fold defaultstate="collapsed" desc="Utility methods">
1139
1140
private boolean isCheckedException(TypeMirror t) {
1141
return !(env.types.isAssignable(t, env.java_lang_Error)
1142
|| env.types.isAssignable(t, env.java_lang_RuntimeException));
1143
}
1144
1145
private boolean isSynthetic() {
1146
switch (env.currElement.getKind()) {
1147
case CONSTRUCTOR:
1148
// A synthetic default constructor has the same pos as the
1149
// enclosing class
1150
TreePath p = env.currPath;
1151
return env.getPos(p) == env.getPos(p.getParentPath());
1152
}
1153
return false;
1154
}
1155
1156
void markEnclosingTag(Flag flag) {
1157
TagStackItem top = tagStack.peek();
1158
if (top != null)
1159
top.flags.add(flag);
1160
}
1161
1162
String toString(TreePath p) {
1163
StringBuilder sb = new StringBuilder("TreePath[");
1164
toString(p, sb);
1165
sb.append("]");
1166
return sb.toString();
1167
}
1168
1169
void toString(TreePath p, StringBuilder sb) {
1170
TreePath parent = p.getParentPath();
1171
if (parent != null) {
1172
toString(parent, sb);
1173
sb.append(",");
1174
}
1175
sb.append(p.getLeaf().getKind()).append(":").append(env.getPos(p)).append(":S").append(env.getStartPos(p));
1176
}
1177
1178
void warnIfEmpty(DocTree tree, List<? extends DocTree> list) {
1179
for (DocTree d: list) {
1180
switch (d.getKind()) {
1181
case TEXT:
1182
if (hasNonWhitespace((TextTree) d))
1183
return;
1184
break;
1185
default:
1186
return;
1187
}
1188
}
1189
env.messages.warning(MISSING, tree, "dc.empty", tree.getKind().tagName);
1190
}
1191
1192
boolean hasNonWhitespace(TextTree tree) {
1193
String s = tree.getBody();
1194
for (int i = 0; i < s.length(); i++) {
1195
Character c = s.charAt(i);
1196
if (!Character.isWhitespace(s.charAt(i)))
1197
return true;
1198
}
1199
return false;
1200
}
1201
1202
// </editor-fold>
1203
1204
}
1205
1206