src/share/classes/com/sun/tools/doclint/Checker.java

changeset 1507
967052c425a1
parent 1506
4a3cfc970c6f
child 1552
153d20d0cac5
equal deleted inserted replaced
1506:4a3cfc970c6f 1507:967052c425a1
93 boolean foundReturn = false; 93 boolean foundReturn = false;
94 94
95 public enum Flag { 95 public enum Flag {
96 TABLE_HAS_CAPTION, 96 TABLE_HAS_CAPTION,
97 HAS_ELEMENT, 97 HAS_ELEMENT,
98 HAS_TEXT 98 HAS_TEXT,
99 REPORTED_BAD_INLINE
99 } 100 }
100 101
101 static class TagStackItem { 102 static class TagStackItem {
102 final DocTree tree; // typically, but not always, StartElementTree 103 final DocTree tree; // typically, but not always, StartElementTree
103 final HtmlTag tag; 104 final HtmlTag tag;
193 194
194 // <editor-fold defaultstate="collapsed" desc="Text and entities."> 195 // <editor-fold defaultstate="collapsed" desc="Text and entities.">
195 196
196 @Override 197 @Override
197 public Void visitText(TextTree tree, Void ignore) { 198 public Void visitText(TextTree tree, Void ignore) {
198 if (!tree.getBody().trim().isEmpty()) { 199 if (hasNonWhitespace(tree)) {
200 checkAllowsText(tree);
199 markEnclosingTag(Flag.HAS_TEXT); 201 markEnclosingTag(Flag.HAS_TEXT);
200 } 202 }
201 return null; 203 return null;
202 } 204 }
203 205
204 @Override 206 @Override
205 public Void visitEntity(EntityTree tree, Void ignore) { 207 public Void visitEntity(EntityTree tree, Void ignore) {
208 checkAllowsText(tree);
206 markEnclosingTag(Flag.HAS_TEXT); 209 markEnclosingTag(Flag.HAS_TEXT);
207 String name = tree.getName().toString(); 210 String name = tree.getName().toString();
208 if (name.startsWith("#")) { 211 if (name.startsWith("#")) {
209 int v = name.toLowerCase().startsWith("#x") 212 int v = name.toLowerCase().startsWith("#x")
210 ? Integer.parseInt(name.substring(2), 16) 213 ? Integer.parseInt(name.substring(2), 16)
216 env.messages.error(HTML, tree, "dc.entity.invalid", name); 219 env.messages.error(HTML, tree, "dc.entity.invalid", name);
217 } 220 }
218 return null; 221 return null;
219 } 222 }
220 223
224 void checkAllowsText(DocTree tree) {
225 TagStackItem top = tagStack.peek();
226 if (top != null
227 && top.tree.getKind() == DocTree.Kind.START_ELEMENT
228 && !top.tag.acceptsText()) {
229 if (top.flags.add(Flag.REPORTED_BAD_INLINE)) {
230 env.messages.error(HTML, tree, "dc.text.not.allowed",
231 ((StartElementTree) top.tree).getName());
232 }
233 }
234 }
235
221 // </editor-fold> 236 // </editor-fold>
222 237
223 // <editor-fold defaultstate="collapsed" desc="HTML elements"> 238 // <editor-fold defaultstate="collapsed" desc="HTML elements">
224 239
225 @Override 240 @Override
228 final Name treeName = tree.getName(); 243 final Name treeName = tree.getName();
229 final HtmlTag t = HtmlTag.get(treeName); 244 final HtmlTag t = HtmlTag.get(treeName);
230 if (t == null) { 245 if (t == null) {
231 env.messages.error(HTML, tree, "dc.tag.unknown", treeName); 246 env.messages.error(HTML, tree, "dc.tag.unknown", treeName);
232 } else { 247 } else {
248 for (TagStackItem tsi: tagStack) {
249 if (tsi.tag.accepts(t)) {
250 while (tagStack.peek() != tsi) tagStack.pop();
251 break;
252 } else if (tsi.tag.endKind != HtmlTag.EndKind.OPTIONAL)
253 break;
254 }
255
256 checkStructure(tree, t);
257
233 // tag specific checks 258 // tag specific checks
234 switch (t) { 259 switch (t) {
235 // check for out of sequence headers, such as <h1>...</h1> <h3>...</h3> 260 // check for out of sequence headers, such as <h1>...</h1> <h3>...</h3>
236 case H1: case H2: case H3: case H4: case H5: case H6: 261 case H1: case H2: case H3: case H4: case H5: case H6:
237 checkHeader(tree, t); 262 checkHeader(tree, t);
238 break; 263 break;
239 // <p> inside <pre>
240 case P:
241 TagStackItem top = tagStack.peek();
242 if (top != null && top.tag == HtmlTag.PRE)
243 env.messages.warning(HTML, tree, "dc.tag.p.in.pre");
244 break;
245 }
246
247 // check that only block tags and inline tags are used,
248 // and that blocks tags are not used within inline tags
249 switch (t.blockType) {
250 case INLINE:
251 break;
252 case BLOCK:
253 TagStackItem top = tagStack.peek();
254 if (top != null && top.tag != null && top.tag.blockType == HtmlTag.BlockType.INLINE) {
255 switch (top.tree.getKind()) {
256 case START_ELEMENT: {
257 Name name = ((StartElementTree) top.tree).getName();
258 env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.element",
259 treeName, name);
260 break;
261 }
262 case LINK:
263 case LINK_PLAIN: {
264 String name = top.tree.getKind().tagName;
265 env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.tag",
266 treeName, name);
267 break;
268 }
269 default:
270 env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.other",
271 treeName);
272 }
273 }
274 break;
275 case OTHER:
276 env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName);
277 break;
278 default:
279 throw new AssertionError();
280 } 264 }
281 265
282 if (t.flags.contains(HtmlTag.Flag.NO_NEST)) { 266 if (t.flags.contains(HtmlTag.Flag.NO_NEST)) {
283 for (TagStackItem i: tagStack) { 267 for (TagStackItem i: tagStack) {
284 if (t == i.tag) { 268 if (t == i.tag) {
320 } finally { 304 } finally {
321 305
322 if (t == null || t.endKind == HtmlTag.EndKind.NONE) 306 if (t == null || t.endKind == HtmlTag.EndKind.NONE)
323 tagStack.pop(); 307 tagStack.pop();
324 } 308 }
309 }
310
311 private void checkStructure(StartElementTree tree, HtmlTag t) {
312 Name treeName = tree.getName();
313 TagStackItem top = tagStack.peek();
314 switch (t.blockType) {
315 case BLOCK:
316 if (top == null || top.tag.accepts(t))
317 return;
318
319 switch (top.tree.getKind()) {
320 case START_ELEMENT: {
321 if (top.tag.blockType == HtmlTag.BlockType.INLINE) {
322 Name name = ((StartElementTree) top.tree).getName();
323 env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.element",
324 treeName, name);
325 return;
326 }
327 }
328 break;
329
330 case LINK:
331 case LINK_PLAIN: {
332 String name = top.tree.getKind().tagName;
333 env.messages.error(HTML, tree, "dc.tag.not.allowed.inline.tag",
334 treeName, name);
335 return;
336 }
337 }
338 break;
339
340 case INLINE:
341 if (top == null || top.tag.accepts(t))
342 return;
343 break;
344
345 case LIST_ITEM:
346 case TABLE_ITEM:
347 if (top != null) {
348 // reset this flag so subsequent bad inline content gets reported
349 top.flags.remove(Flag.REPORTED_BAD_INLINE);
350 if (top.tag.accepts(t))
351 return;
352 }
353 break;
354
355 case OTHER:
356 env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName);
357 return;
358 }
359
360 env.messages.error(HTML, tree, "dc.tag.not.allowed.here", treeName);
325 } 361 }
326 362
327 private void checkHeader(StartElementTree tree, HtmlTag tag) { 363 private void checkHeader(StartElementTree tree, HtmlTag tag) {
328 // verify the new tag 364 // verify the new tag
329 if (getHeaderLevel(tag) > getHeaderLevel(currHeaderTag) + 1) { 365 if (getHeaderLevel(tag) > getHeaderLevel(currHeaderTag) + 1) {
376 if (t.flags.contains(HtmlTag.Flag.EXPECT_CONTENT) 412 if (t.flags.contains(HtmlTag.Flag.EXPECT_CONTENT)
377 && !top.flags.contains(Flag.HAS_TEXT) 413 && !top.flags.contains(Flag.HAS_TEXT)
378 && !top.flags.contains(Flag.HAS_ELEMENT)) { 414 && !top.flags.contains(Flag.HAS_ELEMENT)) {
379 env.messages.warning(HTML, tree, "dc.tag.empty", treeName); 415 env.messages.warning(HTML, tree, "dc.tag.empty", treeName);
380 } 416 }
381 if (t.flags.contains(HtmlTag.Flag.NO_TEXT)
382 && top.flags.contains(Flag.HAS_TEXT)) {
383 env.messages.error(HTML, tree, "dc.text.not.allowed", treeName);
384 }
385 tagStack.pop(); 417 tagStack.pop();
386 done = true; 418 done = true;
387 break; 419 break;
388 } else if (top.tag == null || top.tag.endKind != HtmlTag.EndKind.REQUIRED) { 420 } else if (top.tag == null || top.tag.endKind != HtmlTag.EndKind.REQUIRED) {
389 tagStack.pop(); 421 tagStack.pop();
761 793
762 void warnIfEmpty(DocTree tree, List<? extends DocTree> list) { 794 void warnIfEmpty(DocTree tree, List<? extends DocTree> list) {
763 for (DocTree d: list) { 795 for (DocTree d: list) {
764 switch (d.getKind()) { 796 switch (d.getKind()) {
765 case TEXT: 797 case TEXT:
766 if (!((TextTree) d).getBody().trim().isEmpty()) 798 if (hasNonWhitespace((TextTree) d))
767 return; 799 return;
768 break; 800 break;
769 default: 801 default:
770 return; 802 return;
771 } 803 }
772 } 804 }
773 env.messages.warning(SYNTAX, tree, "dc.empty", tree.getKind().tagName); 805 env.messages.warning(SYNTAX, tree, "dc.empty", tree.getKind().tagName);
774 } 806 }
807
808 boolean hasNonWhitespace(TextTree tree) {
809 String s = tree.getBody();
810 for (int i = 0; i < s.length(); i++) {
811 if (!Character.isWhitespace(s.charAt(i)))
812 return true;
813 }
814 return false;
815 }
816
775 // </editor-fold> 817 // </editor-fold>
776 818
777 } 819 }

mercurial