Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/openjdk-multiarch-jdk8u
Path: blob/aarch64-shenandoah-jdk8u272-b10/langtools/src/share/classes/com/sun/tools/doclint/Checker.java
38899 views
1
/*
2
* Copyright (c) 2012, 2016, 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 com.sun.tools.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.Set;
40
import java.util.regex.Matcher;
41
import java.util.regex.Pattern;
42
43
import javax.lang.model.element.Element;
44
import javax.lang.model.element.ElementKind;
45
import javax.lang.model.element.ExecutableElement;
46
import javax.lang.model.element.Name;
47
import javax.lang.model.element.VariableElement;
48
import javax.lang.model.type.TypeKind;
49
import javax.lang.model.type.TypeMirror;
50
import javax.tools.Diagnostic.Kind;
51
import javax.tools.JavaFileObject;
52
53
import com.sun.source.doctree.AttributeTree;
54
import com.sun.source.doctree.AuthorTree;
55
import com.sun.source.doctree.DocCommentTree;
56
import com.sun.source.doctree.DocRootTree;
57
import com.sun.source.doctree.DocTree;
58
import com.sun.source.doctree.EndElementTree;
59
import com.sun.source.doctree.EntityTree;
60
import com.sun.source.doctree.ErroneousTree;
61
import com.sun.source.doctree.IdentifierTree;
62
import com.sun.source.doctree.InheritDocTree;
63
import com.sun.source.doctree.LinkTree;
64
import com.sun.source.doctree.LiteralTree;
65
import com.sun.source.doctree.ParamTree;
66
import com.sun.source.doctree.ReferenceTree;
67
import com.sun.source.doctree.ReturnTree;
68
import com.sun.source.doctree.SerialDataTree;
69
import com.sun.source.doctree.SerialFieldTree;
70
import com.sun.source.doctree.SinceTree;
71
import com.sun.source.doctree.StartElementTree;
72
import com.sun.source.doctree.TextTree;
73
import com.sun.source.doctree.ThrowsTree;
74
import com.sun.source.doctree.UnknownBlockTagTree;
75
import com.sun.source.doctree.UnknownInlineTagTree;
76
import com.sun.source.doctree.ValueTree;
77
import com.sun.source.doctree.VersionTree;
78
import com.sun.source.util.DocTreePath;
79
import com.sun.source.util.DocTreePathScanner;
80
import com.sun.source.util.TreePath;
81
import com.sun.tools.doclint.HtmlTag.AttrKind;
82
import com.sun.tools.javac.tree.DocPretty;
83
import com.sun.tools.javac.util.StringUtils;
84
import static com.sun.tools.doclint.Messages.Group.*;
85
86
87
/**
88
* Validate a doc comment.
89
*
90
* <p><b>This is NOT part of any supported API.
91
* If you write code that depends on this, you do so at your own
92
* risk. This code and its internal interfaces are subject to change
93
* or deletion without notice.</b></p>
94
*/
95
public class Checker extends DocTreePathScanner<Void, Void> {
96
final Env env;
97
98
Set<Element> foundParams = new HashSet<>();
99
Set<TypeMirror> foundThrows = new HashSet<>();
100
Map<Element, Set<String>> foundAnchors = new HashMap<>();
101
boolean foundInheritDoc = false;
102
boolean foundReturn = false;
103
104
public enum Flag {
105
TABLE_HAS_CAPTION,
106
HAS_ELEMENT,
107
HAS_INLINE_TAG,
108
HAS_TEXT,
109
REPORTED_BAD_INLINE
110
}
111
112
static class TagStackItem {
113
final DocTree tree; // typically, but not always, StartElementTree
114
final HtmlTag tag;
115
final Set<HtmlTag.Attr> attrs;
116
final Set<Flag> flags;
117
TagStackItem(DocTree tree, HtmlTag tag) {
118
this.tree = tree;
119
this.tag = tag;
120
attrs = EnumSet.noneOf(HtmlTag.Attr.class);
121
flags = EnumSet.noneOf(Flag.class);
122
}
123
@Override
124
public String toString() {
125
return String.valueOf(tag);
126
}
127
}
128
129
private final Deque<TagStackItem> tagStack; // TODO: maybe want to record starting tree as well
130
private HtmlTag currHeaderTag;
131
132
private final int implicitHeaderLevel;
133
134
// <editor-fold defaultstate="collapsed" desc="Top level">
135
136
Checker(Env env) {
137
env.getClass();
138
this.env = env;
139
tagStack = new LinkedList<>();
140
implicitHeaderLevel = env.implicitHeaderLevel;
141
}
142
143
public Void scan(DocCommentTree tree, TreePath p) {
144
env.setCurrent(p, tree);
145
146
boolean isOverridingMethod = !env.currOverriddenMethods.isEmpty();
147
148
if (p.getLeaf() == p.getCompilationUnit()) {
149
// If p points to a compilation unit, the implied declaration is the
150
// package declaration (if any) for the compilation unit.
151
// Handle this case specially, because doc comments are only
152
// expected in package-info files.
153
JavaFileObject fo = p.getCompilationUnit().getSourceFile();
154
boolean isPkgInfo = fo.isNameCompatible("package-info", JavaFileObject.Kind.SOURCE);
155
if (tree == null) {
156
if (isPkgInfo)
157
reportMissing("dc.missing.comment");
158
return null;
159
} else {
160
if (!isPkgInfo)
161
reportReference("dc.unexpected.comment");
162
}
163
} else {
164
if (tree == null) {
165
if (!isSynthetic() && !isOverridingMethod)
166
reportMissing("dc.missing.comment");
167
return null;
168
}
169
}
170
171
tagStack.clear();
172
currHeaderTag = null;
173
174
foundParams.clear();
175
foundThrows.clear();
176
foundInheritDoc = false;
177
foundReturn = false;
178
179
scan(new DocTreePath(p, tree), null);
180
181
if (!isOverridingMethod) {
182
switch (env.currElement.getKind()) {
183
case METHOD:
184
case CONSTRUCTOR: {
185
ExecutableElement ee = (ExecutableElement) env.currElement;
186
checkParamsDocumented(ee.getTypeParameters());
187
checkParamsDocumented(ee.getParameters());
188
switch (ee.getReturnType().getKind()) {
189
case VOID:
190
case NONE:
191
break;
192
default:
193
if (!foundReturn
194
&& !foundInheritDoc
195
&& !env.types.isSameType(ee.getReturnType(), env.java_lang_Void)) {
196
reportMissing("dc.missing.return");
197
}
198
}
199
checkThrowsDocumented(ee.getThrownTypes());
200
}
201
}
202
}
203
204
return null;
205
}
206
207
private void reportMissing(String code, Object... args) {
208
env.messages.report(MISSING, Kind.WARNING, env.currPath.getLeaf(), code, args);
209
}
210
211
private void reportReference(String code, Object... args) {
212
env.messages.report(REFERENCE, Kind.WARNING, env.currPath.getLeaf(), code, args);
213
}
214
215
@Override
216
public Void visitDocComment(DocCommentTree tree, Void ignore) {
217
super.visitDocComment(tree, ignore);
218
for (TagStackItem tsi: tagStack) {
219
warnIfEmpty(tsi, null);
220
if (tsi.tree.getKind() == DocTree.Kind.START_ELEMENT
221
&& tsi.tag.endKind == HtmlTag.EndKind.REQUIRED) {
222
StartElementTree t = (StartElementTree) tsi.tree;
223
env.messages.error(HTML, t, "dc.tag.not.closed", t.getName());
224
}
225
}
226
return null;
227
}
228
// </editor-fold>
229
230
// <editor-fold defaultstate="collapsed" desc="Text and entities.">
231
232
@Override
233
public Void visitText(TextTree tree, Void ignore) {
234
if (hasNonWhitespace(tree)) {
235
checkAllowsText(tree);
236
markEnclosingTag(Flag.HAS_TEXT);
237
}
238
return null;
239
}
240
241
@Override
242
public Void visitEntity(EntityTree tree, Void ignore) {
243
checkAllowsText(tree);
244
markEnclosingTag(Flag.HAS_TEXT);
245
String name = tree.getName().toString();
246
if (name.startsWith("#")) {
247
int v = StringUtils.toLowerCase(name).startsWith("#x")
248
? Integer.parseInt(name.substring(2), 16)
249
: Integer.parseInt(name.substring(1), 10);
250
if (!Entity.isValid(v)) {
251
env.messages.error(HTML, tree, "dc.entity.invalid", name);
252
}
253
} else if (!Entity.isValid(name)) {
254
env.messages.error(HTML, tree, "dc.entity.invalid", name);
255
}
256
return null;
257
}
258
259
void checkAllowsText(DocTree tree) {
260
TagStackItem top = tagStack.peek();
261
if (top != null
262
&& top.tree.getKind() == DocTree.Kind.START_ELEMENT
263
&& !top.tag.acceptsText()) {
264
if (top.flags.add(Flag.REPORTED_BAD_INLINE)) {
265
env.messages.error(HTML, tree, "dc.text.not.allowed",
266
((StartElementTree) top.tree).getName());
267
}
268
}
269
}
270
271
// </editor-fold>
272
273
// <editor-fold defaultstate="collapsed" desc="HTML elements">
274
275
@Override
276
public Void visitStartElement(StartElementTree tree, Void ignore) {
277
final Name treeName = tree.getName();
278
final HtmlTag t = HtmlTag.get(treeName);
279
if (t == null) {
280
env.messages.error(HTML, tree, "dc.tag.unknown", treeName);
281
} else {
282
boolean done = false;
283
for (TagStackItem tsi: tagStack) {
284
if (tsi.tag.accepts(t)) {
285
while (tagStack.peek() != tsi) {
286
warnIfEmpty(tagStack.peek(), null);
287
tagStack.pop();
288
}
289
done = true;
290
break;
291
} else if (tsi.tag.endKind != HtmlTag.EndKind.OPTIONAL) {
292
done = true;
293
break;
294
}
295
}
296
if (!done && HtmlTag.BODY.accepts(t)) {
297
while (!tagStack.isEmpty()) {
298
warnIfEmpty(tagStack.peek(), null);
299
tagStack.pop();
300
}
301
}
302
303
markEnclosingTag(Flag.HAS_ELEMENT);
304
checkStructure(tree, t);
305
306
// tag specific checks
307
switch (t) {
308
// check for out of sequence headers, such as <h1>...</h1> <h3>...</h3>
309
case H1: case H2: case H3: case H4: case H5: case H6:
310
checkHeader(tree, t);
311
break;
312
}
313
314
if (t.flags.contains(HtmlTag.Flag.NO_NEST)) {
315
for (TagStackItem i: tagStack) {
316
if (t == i.tag) {
317
env.messages.warning(HTML, tree, "dc.tag.nested.not.allowed", treeName);
318
break;
319
}
320
}
321
}
322
}
323
324
// check for self closing tags, such as <a id="name"/>
325
if (tree.isSelfClosing()) {
326
env.messages.error(HTML, tree, "dc.tag.self.closing", treeName);
327
}
328
329
try {
330
TagStackItem parent = tagStack.peek();
331
TagStackItem top = new TagStackItem(tree, t);
332
tagStack.push(top);
333
334
super.visitStartElement(tree, ignore);
335
336
// handle attributes that may or may not have been found in start element
337
if (t != null) {
338
switch (t) {
339
case CAPTION:
340
if (parent != null && parent.tag == HtmlTag.TABLE)
341
parent.flags.add(Flag.TABLE_HAS_CAPTION);
342
break;
343
344
case IMG:
345
if (!top.attrs.contains(HtmlTag.Attr.ALT))
346
env.messages.error(ACCESSIBILITY, tree, "dc.no.alt.attr.for.image");
347
break;
348
}
349
}
350
351
return null;
352
} finally {
353
354
if (t == null || t.endKind == HtmlTag.EndKind.NONE)
355
tagStack.pop();
356
}
357
}
358
359
private void checkStructure(StartElementTree tree, HtmlTag t) {
360
Name treeName = tree.getName();
361
TagStackItem top = tagStack.peek();
362
switch (t.blockType) {
363
case BLOCK:
364
if (top == null || top.tag.accepts(t))
365
return;
366
367
switch (top.tree.getKind()) {
368
case START_ELEMENT: {
369
if (top.tag.blockType == HtmlTag.BlockType.INLINE) {
370
Name name = ((StartElementTree) top.tree).getName();
371
env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.element",
372
treeName, name);
373
return;
374
}
375
}
376
break;
377
378
case LINK:
379
case LINK_PLAIN: {
380
String name = top.tree.getKind().tagName;
381
env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.tag",
382
treeName, name);
383
return;
384
}
385
}
386
break;
387
388
case INLINE:
389
if (top == null || top.tag.accepts(t))
390
return;
391
break;
392
393
case LIST_ITEM:
394
case TABLE_ITEM:
395
if (top != null) {
396
// reset this flag so subsequent bad inline content gets reported
397
top.flags.remove(Flag.REPORTED_BAD_INLINE);
398
if (top.tag.accepts(t))
399
return;
400
}
401
break;
402
403
case OTHER:
404
switch (t) {
405
case SCRIPT:
406
// <script> may or may not be allowed, depending on --allow-script-in-comments
407
// but we allow it here, and rely on a separate scanner to detect all uses
408
// of JavaScript, including <script> tags, and use in attributes, etc.
409
break;
410
411
default:
412
env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName);
413
}
414
return;
415
}
416
417
env.messages.error(HTML, tree, "dc.tag.not.allowed.here", treeName);
418
}
419
420
private void checkHeader(StartElementTree tree, HtmlTag tag) {
421
// verify the new tag
422
if (getHeaderLevel(tag) > getHeaderLevel(currHeaderTag) + 1) {
423
if (currHeaderTag == null) {
424
env.messages.error(ACCESSIBILITY, tree, "dc.tag.header.sequence.1", tag);
425
} else {
426
env.messages.error(ACCESSIBILITY, tree, "dc.tag.header.sequence.2",
427
tag, currHeaderTag);
428
}
429
}
430
431
currHeaderTag = tag;
432
}
433
434
private int getHeaderLevel(HtmlTag tag) {
435
if (tag == null)
436
return implicitHeaderLevel;
437
switch (tag) {
438
case H1: return 1;
439
case H2: return 2;
440
case H3: return 3;
441
case H4: return 4;
442
case H5: return 5;
443
case H6: return 6;
444
default: throw new IllegalArgumentException();
445
}
446
}
447
448
@Override
449
public Void visitEndElement(EndElementTree tree, Void ignore) {
450
final Name treeName = tree.getName();
451
final HtmlTag t = HtmlTag.get(treeName);
452
if (t == null) {
453
env.messages.error(HTML, tree, "dc.tag.unknown", treeName);
454
} else if (t.endKind == HtmlTag.EndKind.NONE) {
455
env.messages.error(HTML, tree, "dc.tag.end.not.permitted", treeName);
456
} else {
457
boolean done = false;
458
while (!tagStack.isEmpty()) {
459
TagStackItem top = tagStack.peek();
460
if (t == top.tag) {
461
switch (t) {
462
case TABLE:
463
if (!top.attrs.contains(HtmlTag.Attr.SUMMARY)
464
&& !top.flags.contains(Flag.TABLE_HAS_CAPTION)) {
465
env.messages.error(ACCESSIBILITY, tree,
466
"dc.no.summary.or.caption.for.table");
467
}
468
}
469
warnIfEmpty(top, tree);
470
tagStack.pop();
471
done = true;
472
break;
473
} else if (top.tag == null || top.tag.endKind != HtmlTag.EndKind.REQUIRED) {
474
tagStack.pop();
475
} else {
476
boolean found = false;
477
for (TagStackItem si: tagStack) {
478
if (si.tag == t) {
479
found = true;
480
break;
481
}
482
}
483
if (found && top.tree.getKind() == DocTree.Kind.START_ELEMENT) {
484
env.messages.error(HTML, top.tree, "dc.tag.start.unmatched",
485
((StartElementTree) top.tree).getName());
486
tagStack.pop();
487
} else {
488
env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName);
489
done = true;
490
break;
491
}
492
}
493
}
494
495
if (!done && tagStack.isEmpty()) {
496
env.messages.error(HTML, tree, "dc.tag.end.unexpected", treeName);
497
}
498
}
499
500
return super.visitEndElement(tree, ignore);
501
}
502
503
void warnIfEmpty(TagStackItem tsi, DocTree endTree) {
504
if (tsi.tag != null && tsi.tree instanceof StartElementTree) {
505
if (tsi.tag.flags.contains(HtmlTag.Flag.EXPECT_CONTENT)
506
&& !tsi.flags.contains(Flag.HAS_TEXT)
507
&& !tsi.flags.contains(Flag.HAS_ELEMENT)
508
&& !tsi.flags.contains(Flag.HAS_INLINE_TAG)) {
509
DocTree tree = (endTree != null) ? endTree : tsi.tree;
510
Name treeName = ((StartElementTree) tsi.tree).getName();
511
env.messages.warning(HTML, tree, "dc.tag.empty", treeName);
512
}
513
}
514
}
515
516
// </editor-fold>
517
518
// <editor-fold defaultstate="collapsed" desc="HTML attributes">
519
520
@Override @SuppressWarnings("fallthrough")
521
public Void visitAttribute(AttributeTree tree, Void ignore) {
522
HtmlTag currTag = tagStack.peek().tag;
523
if (currTag != null) {
524
Name name = tree.getName();
525
HtmlTag.Attr attr = currTag.getAttr(name);
526
if (attr != null) {
527
boolean first = tagStack.peek().attrs.add(attr);
528
if (!first)
529
env.messages.error(HTML, tree, "dc.attr.repeated", name);
530
}
531
532
// for now, doclint allows all attribute names beginning with "on" as event handler names,
533
// without checking the validity or applicability of the name
534
if (!name.toString().startsWith("on")) {
535
AttrKind k = currTag.getAttrKind(name);
536
switch (k) {
537
case OK:
538
break;
539
540
case INVALID:
541
env.messages.error(HTML, tree, "dc.attr.unknown", name);
542
break;
543
544
case OBSOLETE:
545
env.messages.warning(ACCESSIBILITY, tree, "dc.attr.obsolete", name);
546
break;
547
548
case USE_CSS:
549
env.messages.warning(ACCESSIBILITY, tree, "dc.attr.obsolete.use.css", name);
550
break;
551
}
552
}
553
554
if (attr != null) {
555
switch (attr) {
556
case NAME:
557
if (currTag != HtmlTag.A) {
558
break;
559
}
560
// fallthrough
561
case ID:
562
String value = getAttrValue(tree);
563
if (value == null) {
564
env.messages.error(HTML, tree, "dc.anchor.value.missing");
565
} else {
566
if (!validName.matcher(value).matches()) {
567
env.messages.error(HTML, tree, "dc.invalid.anchor", value);
568
}
569
if (!checkAnchor(value)) {
570
env.messages.error(HTML, tree, "dc.anchor.already.defined", value);
571
}
572
}
573
break;
574
575
case HREF:
576
if (currTag == HtmlTag.A) {
577
String v = getAttrValue(tree);
578
if (v == null || v.isEmpty()) {
579
env.messages.error(HTML, tree, "dc.attr.lacks.value");
580
} else {
581
Matcher m = docRoot.matcher(v);
582
if (m.matches()) {
583
String rest = m.group(2);
584
if (!rest.isEmpty())
585
checkURI(tree, rest);
586
} else {
587
checkURI(tree, v);
588
}
589
}
590
}
591
break;
592
593
case VALUE:
594
if (currTag == HtmlTag.LI) {
595
String v = getAttrValue(tree);
596
if (v == null || v.isEmpty()) {
597
env.messages.error(HTML, tree, "dc.attr.lacks.value");
598
} else if (!validNumber.matcher(v).matches()) {
599
env.messages.error(HTML, tree, "dc.attr.not.number");
600
}
601
}
602
break;
603
}
604
}
605
}
606
607
// TODO: basic check on value
608
609
return super.visitAttribute(tree, ignore);
610
}
611
612
private boolean checkAnchor(String name) {
613
Element e = getEnclosingPackageOrClass(env.currElement);
614
if (e == null)
615
return true;
616
Set<String> set = foundAnchors.get(e);
617
if (set == null)
618
foundAnchors.put(e, set = new HashSet<>());
619
return set.add(name);
620
}
621
622
private Element getEnclosingPackageOrClass(Element e) {
623
while (e != null) {
624
switch (e.getKind()) {
625
case CLASS:
626
case ENUM:
627
case INTERFACE:
628
case PACKAGE:
629
return e;
630
default:
631
e = e.getEnclosingElement();
632
}
633
}
634
return e;
635
}
636
637
// http://www.w3.org/TR/html401/types.html#type-name
638
private static final Pattern validName = Pattern.compile("[A-Za-z][A-Za-z0-9-_:.]*");
639
640
private static final Pattern validNumber = Pattern.compile("-?[0-9]+");
641
642
// pattern to remove leading {@docRoot}/?
643
private static final Pattern docRoot = Pattern.compile("(?i)(\\{@docRoot *\\}/?)?(.*)");
644
645
private String getAttrValue(AttributeTree tree) {
646
if (tree.getValue() == null)
647
return null;
648
649
StringWriter sw = new StringWriter();
650
try {
651
new DocPretty(sw).print(tree.getValue());
652
} catch (IOException e) {
653
// cannot happen
654
}
655
// ignore potential use of entities for now
656
return sw.toString();
657
}
658
659
private void checkURI(AttributeTree tree, String uri) {
660
// allow URIs beginning with javascript:, which would otherwise be rejected by the URI API.
661
if (uri.startsWith("javascript:"))
662
return;
663
try {
664
URI u = new URI(uri);
665
} catch (URISyntaxException e) {
666
env.messages.error(HTML, tree, "dc.invalid.uri", uri);
667
}
668
}
669
// </editor-fold>
670
671
// <editor-fold defaultstate="collapsed" desc="javadoc tags">
672
673
@Override
674
public Void visitAuthor(AuthorTree tree, Void ignore) {
675
warnIfEmpty(tree, tree.getName());
676
return super.visitAuthor(tree, ignore);
677
}
678
679
@Override
680
public Void visitDocRoot(DocRootTree tree, Void ignore) {
681
markEnclosingTag(Flag.HAS_INLINE_TAG);
682
return super.visitDocRoot(tree, ignore);
683
}
684
685
@Override
686
public Void visitInheritDoc(InheritDocTree tree, Void ignore) {
687
markEnclosingTag(Flag.HAS_INLINE_TAG);
688
// TODO: verify on overridden method
689
foundInheritDoc = true;
690
return super.visitInheritDoc(tree, ignore);
691
}
692
693
@Override
694
public Void visitLink(LinkTree tree, Void ignore) {
695
markEnclosingTag(Flag.HAS_INLINE_TAG);
696
// simulate inline context on tag stack
697
HtmlTag t = (tree.getKind() == DocTree.Kind.LINK)
698
? HtmlTag.CODE : HtmlTag.SPAN;
699
tagStack.push(new TagStackItem(tree, t));
700
try {
701
return super.visitLink(tree, ignore);
702
} finally {
703
tagStack.pop();
704
}
705
}
706
707
@Override
708
public Void visitLiteral(LiteralTree tree, Void ignore) {
709
markEnclosingTag(Flag.HAS_INLINE_TAG);
710
if (tree.getKind() == DocTree.Kind.CODE) {
711
for (TagStackItem tsi: tagStack) {
712
if (tsi.tag == HtmlTag.CODE) {
713
env.messages.warning(HTML, tree, "dc.tag.code.within.code");
714
break;
715
}
716
}
717
}
718
return super.visitLiteral(tree, ignore);
719
}
720
721
@Override
722
@SuppressWarnings("fallthrough")
723
public Void visitParam(ParamTree tree, Void ignore) {
724
boolean typaram = tree.isTypeParameter();
725
IdentifierTree nameTree = tree.getName();
726
Element paramElement = nameTree != null ? env.trees.getElement(new DocTreePath(getCurrentPath(), nameTree)) : null;
727
728
if (paramElement == null) {
729
switch (env.currElement.getKind()) {
730
case CLASS: case INTERFACE: {
731
if (!typaram) {
732
env.messages.error(REFERENCE, tree, "dc.invalid.param");
733
break;
734
}
735
}
736
case METHOD: case CONSTRUCTOR: {
737
env.messages.error(REFERENCE, nameTree, "dc.param.name.not.found");
738
break;
739
}
740
741
default:
742
env.messages.error(REFERENCE, tree, "dc.invalid.param");
743
break;
744
}
745
} else {
746
foundParams.add(paramElement);
747
}
748
749
warnIfEmpty(tree, tree.getDescription());
750
return super.visitParam(tree, ignore);
751
}
752
753
private void checkParamsDocumented(List<? extends Element> list) {
754
if (foundInheritDoc)
755
return;
756
757
for (Element e: list) {
758
if (!foundParams.contains(e)) {
759
CharSequence paramName = (e.getKind() == ElementKind.TYPE_PARAMETER)
760
? "<" + e.getSimpleName() + ">"
761
: e.getSimpleName();
762
reportMissing("dc.missing.param", paramName);
763
}
764
}
765
}
766
767
@Override
768
public Void visitReference(ReferenceTree tree, Void ignore) {
769
String sig = tree.getSignature();
770
if (sig.contains("<") || sig.contains(">"))
771
env.messages.error(REFERENCE, tree, "dc.type.arg.not.allowed");
772
773
Element e = env.trees.getElement(getCurrentPath());
774
if (e == null)
775
env.messages.error(REFERENCE, tree, "dc.ref.not.found");
776
return super.visitReference(tree, ignore);
777
}
778
779
@Override
780
public Void visitReturn(ReturnTree tree, Void ignore) {
781
Element e = env.trees.getElement(env.currPath);
782
if (e.getKind() != ElementKind.METHOD
783
|| ((ExecutableElement) e).getReturnType().getKind() == TypeKind.VOID)
784
env.messages.error(REFERENCE, tree, "dc.invalid.return");
785
foundReturn = true;
786
warnIfEmpty(tree, tree.getDescription());
787
return super.visitReturn(tree, ignore);
788
}
789
790
@Override
791
public Void visitSerialData(SerialDataTree tree, Void ignore) {
792
warnIfEmpty(tree, tree.getDescription());
793
return super.visitSerialData(tree, ignore);
794
}
795
796
@Override
797
public Void visitSerialField(SerialFieldTree tree, Void ignore) {
798
warnIfEmpty(tree, tree.getDescription());
799
return super.visitSerialField(tree, ignore);
800
}
801
802
@Override
803
public Void visitSince(SinceTree tree, Void ignore) {
804
warnIfEmpty(tree, tree.getBody());
805
return super.visitSince(tree, ignore);
806
}
807
808
@Override
809
public Void visitThrows(ThrowsTree tree, Void ignore) {
810
ReferenceTree exName = tree.getExceptionName();
811
Element ex = env.trees.getElement(new DocTreePath(getCurrentPath(), exName));
812
if (ex == null) {
813
env.messages.error(REFERENCE, tree, "dc.ref.not.found");
814
} else if (isThrowable(ex.asType())) {
815
switch (env.currElement.getKind()) {
816
case CONSTRUCTOR:
817
case METHOD:
818
if (isCheckedException(ex.asType())) {
819
ExecutableElement ee = (ExecutableElement) env.currElement;
820
checkThrowsDeclared(exName, ex.asType(), ee.getThrownTypes());
821
}
822
break;
823
default:
824
env.messages.error(REFERENCE, tree, "dc.invalid.throws");
825
}
826
} else {
827
env.messages.error(REFERENCE, tree, "dc.invalid.throws");
828
}
829
warnIfEmpty(tree, tree.getDescription());
830
return scan(tree.getDescription(), ignore);
831
}
832
833
private boolean isThrowable(TypeMirror tm) {
834
switch (tm.getKind()) {
835
case DECLARED:
836
case TYPEVAR:
837
return env.types.isAssignable(tm, env.java_lang_Throwable);
838
}
839
return false;
840
}
841
842
private void checkThrowsDeclared(ReferenceTree tree, TypeMirror t, List<? extends TypeMirror> list) {
843
boolean found = false;
844
for (TypeMirror tl : list) {
845
if (env.types.isAssignable(t, tl)) {
846
foundThrows.add(tl);
847
found = true;
848
}
849
}
850
if (!found)
851
env.messages.error(REFERENCE, tree, "dc.exception.not.thrown", t);
852
}
853
854
private void checkThrowsDocumented(List<? extends TypeMirror> list) {
855
if (foundInheritDoc)
856
return;
857
858
for (TypeMirror tl: list) {
859
if (isCheckedException(tl) && !foundThrows.contains(tl))
860
reportMissing("dc.missing.throws", tl);
861
}
862
}
863
864
@Override
865
public Void visitUnknownBlockTag(UnknownBlockTagTree tree, Void ignore) {
866
checkUnknownTag(tree, tree.getTagName());
867
return super.visitUnknownBlockTag(tree, ignore);
868
}
869
870
@Override
871
public Void visitUnknownInlineTag(UnknownInlineTagTree tree, Void ignore) {
872
checkUnknownTag(tree, tree.getTagName());
873
return super.visitUnknownInlineTag(tree, ignore);
874
}
875
876
private void checkUnknownTag(DocTree tree, String tagName) {
877
if (env.customTags != null && !env.customTags.contains(tagName))
878
env.messages.error(SYNTAX, tree, "dc.tag.unknown", tagName);
879
}
880
881
@Override
882
public Void visitValue(ValueTree tree, Void ignore) {
883
ReferenceTree ref = tree.getReference();
884
if (ref == null || ref.getSignature().isEmpty()) {
885
if (!isConstant(env.currElement))
886
env.messages.error(REFERENCE, tree, "dc.value.not.allowed.here");
887
} else {
888
Element e = env.trees.getElement(new DocTreePath(getCurrentPath(), ref));
889
if (!isConstant(e))
890
env.messages.error(REFERENCE, tree, "dc.value.not.a.constant");
891
}
892
893
markEnclosingTag(Flag.HAS_INLINE_TAG);
894
return super.visitValue(tree, ignore);
895
}
896
897
private boolean isConstant(Element e) {
898
if (e == null)
899
return false;
900
901
switch (e.getKind()) {
902
case FIELD:
903
Object value = ((VariableElement) e).getConstantValue();
904
return (value != null); // can't distinguish "not a constant" from "constant is null"
905
default:
906
return false;
907
}
908
}
909
910
@Override
911
public Void visitVersion(VersionTree tree, Void ignore) {
912
warnIfEmpty(tree, tree.getBody());
913
return super.visitVersion(tree, ignore);
914
}
915
916
@Override
917
public Void visitErroneous(ErroneousTree tree, Void ignore) {
918
env.messages.error(SYNTAX, tree, null, tree.getDiagnostic().getMessage(null));
919
return null;
920
}
921
// </editor-fold>
922
923
// <editor-fold defaultstate="collapsed" desc="Utility methods">
924
925
private boolean isCheckedException(TypeMirror t) {
926
return !(env.types.isAssignable(t, env.java_lang_Error)
927
|| env.types.isAssignable(t, env.java_lang_RuntimeException));
928
}
929
930
private boolean isSynthetic() {
931
switch (env.currElement.getKind()) {
932
case CONSTRUCTOR:
933
// A synthetic default constructor has the same pos as the
934
// enclosing class
935
TreePath p = env.currPath;
936
return env.getPos(p) == env.getPos(p.getParentPath());
937
}
938
return false;
939
}
940
941
void markEnclosingTag(Flag flag) {
942
TagStackItem top = tagStack.peek();
943
if (top != null)
944
top.flags.add(flag);
945
}
946
947
String toString(TreePath p) {
948
StringBuilder sb = new StringBuilder("TreePath[");
949
toString(p, sb);
950
sb.append("]");
951
return sb.toString();
952
}
953
954
void toString(TreePath p, StringBuilder sb) {
955
TreePath parent = p.getParentPath();
956
if (parent != null) {
957
toString(parent, sb);
958
sb.append(",");
959
}
960
sb.append(p.getLeaf().getKind()).append(":").append(env.getPos(p)).append(":S").append(env.getStartPos(p));
961
}
962
963
void warnIfEmpty(DocTree tree, List<? extends DocTree> list) {
964
for (DocTree d: list) {
965
switch (d.getKind()) {
966
case TEXT:
967
if (hasNonWhitespace((TextTree) d))
968
return;
969
break;
970
default:
971
return;
972
}
973
}
974
env.messages.warning(SYNTAX, tree, "dc.empty", tree.getKind().tagName);
975
}
976
977
boolean hasNonWhitespace(TextTree tree) {
978
String s = tree.getBody();
979
for (int i = 0; i < s.length(); i++) {
980
if (!Character.isWhitespace(s.charAt(i)))
981
return true;
982
}
983
return false;
984
}
985
986
// </editor-fold>
987
988
}
989
990