1 /*******************************************************************************
2 * Copyright (c) 2000, 2004 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package org.eclipse.jdt.internal.compiler.parser;
13 import java.util.List;
15 import org.eclipse.jdt.core.compiler.CharOperation;
16 import org.eclipse.jdt.core.compiler.InvalidInputException;
17 import org.eclipse.jdt.internal.compiler.ast.*;
18 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
19 import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
20 import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
23 * Parser specialized for decoding javadoc comments
25 public class JavadocParser extends AbstractCommentParser {
28 public Javadoc docComment;
30 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
31 // Store param references for tag with invalid syntax
32 private int invalidParamReferencesPtr = -1;
33 private ASTNode[] invalidParamReferencesStack;
35 // Store current tag stack pointer
36 private int currentAstPtr= -2;
38 public JavadocParser(Parser sourceParser) {
40 this.checkDocComment = this.sourceParser.options.docCommentSupport;
41 this.jdk15 = this.sourceParser.options.sourceLevel >= ClassFileConstants.JDK1_5;
42 this.kind = COMPIL_PARSER;
46 * Returns true if tag @deprecated is present in javadoc comment.
48 * If javadoc checking is enabled, will also construct an Javadoc node, which will be stored into Parser.javadoc
49 * slot for being consumed later on.
51 public boolean checkDeprecation(int javadocStart, int javadocEnd) {
54 this.source = this.sourceParser.scanner.source;
55 this.index = javadocStart +3;
56 this.endComment = javadocEnd - 2;
57 if (this.checkDocComment) {
59 this.scanner.lineEnds = this.sourceParser.scanner.lineEnds;
60 this.scanner.linePtr = this.sourceParser.scanner.linePtr;
61 this.lineEnds = this.scanner.lineEnds;
62 this.docComment = new Javadoc(javadocStart, javadocEnd);
63 commentParse(javadocStart, javadocEnd);
65 // Init javadoc if necessary
66 if (this.sourceParser.options.getSeverity(CompilerOptions.MissingJavadocComments) != ProblemSeverities.Ignore) {
67 this.docComment = new Javadoc(javadocStart, javadocEnd);
69 this.docComment = null;
73 int firstLineNumber = this.sourceParser.scanner.getLineNumber(javadocStart);
74 int lastLineNumber = this.sourceParser.scanner.getLineNumber(javadocEnd);
76 // scan line per line, since tags must be at beginning of lines only
77 nextLine : for (int line = firstLineNumber; line <= lastLineNumber; line++) {
78 int lineStart = line == firstLineNumber
79 ? javadocStart + 3 // skip leading /**
80 : this.sourceParser.scanner.getLineStart(line);
81 this.index = lineStart;
82 this.lineEnd = line == lastLineNumber
83 ? javadocEnd - 2 // remove trailing * /
84 : this.sourceParser.scanner.getLineEnd(line);
85 nextCharacter : while (this.index < this.lineEnd) {
86 char c = readChar(); // consider unicodes
89 case '\u000c' : /* FORM FEED */
90 case ' ' : /* SPACE */
91 case '\t' : /* HORIZONTAL TABULATION */
92 case '\n' : /* LINE FEED */
94 // do nothing for space or '*' characters
95 continue nextCharacter;
97 if ((readChar() == 'd') && (readChar() == 'e') &&
98 (readChar() == 'p') && (readChar() == 'r') &&
99 (readChar() == 'e') && (readChar() == 'c') &&
100 (readChar() == 'a') && (readChar() == 't') &&
101 (readChar() == 'e') && (readChar() == 'd')) {
102 // ensure the tag is properly ended: either followed by a space, a tab, line end or asterisk.
104 if (Character.isWhitespace(c) || c == '*') {
115 this.source = null; // release source as soon as finished
117 return this.deprecated;
120 public String toString() {
121 StringBuffer buffer = new StringBuffer();
122 buffer.append("check javadoc: ").append(this.checkDocComment).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
123 buffer.append("javadoc: ").append(this.docComment).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
124 buffer.append(super.toString());
125 return buffer.toString();
129 * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#createArgumentReference(char[], java.lang.Object, int)
131 protected Object createArgumentReference(char[] name, int dim, Object typeRef, long[] dimPositions, long argNamePos) throws InvalidInputException {
133 TypeReference argTypeRef = (TypeReference) typeRef;
135 long pos = (((long) argTypeRef.sourceStart) << 32) + argTypeRef.sourceEnd;
136 if (typeRef instanceof JavadocSingleTypeReference) {
137 JavadocSingleTypeReference singleRef = (JavadocSingleTypeReference) typeRef;
138 argTypeRef = new JavadocArraySingleTypeReference(singleRef.token, dim, pos);
140 JavadocQualifiedTypeReference qualifRef = (JavadocQualifiedTypeReference) typeRef;
141 argTypeRef = new JavadocArrayQualifiedTypeReference(qualifRef, dim);
144 int argEnd = argTypeRef.sourceEnd;
145 if (dim > 0) argEnd = (int) dimPositions[dim-1];
146 if (argNamePos >= 0) argEnd = (int) argNamePos;
147 return new JavadocArgumentExpression(name, argTypeRef.sourceStart, argEnd, argTypeRef);
149 catch (ClassCastException ex) {
150 throw new InvalidInputException();
154 * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#createFieldReference()
156 protected Object createFieldReference(Object receiver) throws InvalidInputException {
159 TypeReference typeRef = (TypeReference) receiver;
160 if (typeRef == null) {
161 char[] name = this.sourceParser.compilationUnit.compilationResult.compilationUnit.getMainTypeName();
162 typeRef = new ImplicitDocTypeReference(name, this.memberStart);
165 JavadocFieldReference field = new JavadocFieldReference(this.identifierStack[0], this.identifierPositionStack[0]);
166 field.receiver = typeRef;
167 field.tagSourceStart = this.tagSourceStart;
168 field.tagSourceEnd = this.tagSourceEnd;
169 field.tagValue = this.tagValue;
172 catch (ClassCastException ex) {
173 throw new InvalidInputException();
177 * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#createMethodReference(java.lang.Object[])
179 protected Object createMethodReference(Object receiver, List arguments) throws InvalidInputException {
182 TypeReference typeRef = (TypeReference) receiver;
183 // Decide whether we have a constructor or not
184 boolean isConstructor = false;
185 if (typeRef == null) {
186 char[] name = this.sourceParser.compilationUnit.compilationResult.compilationUnit.getMainTypeName();
187 isConstructor = CharOperation.equals(this.identifierStack[0], name);
188 typeRef = new ImplicitDocTypeReference(name, this.memberStart);
191 if (typeRef instanceof JavadocSingleTypeReference) {
192 name = ((JavadocSingleTypeReference)typeRef).token;
193 } else if (typeRef instanceof JavadocQualifiedTypeReference) {
194 char[][] tokens = ((JavadocQualifiedTypeReference)typeRef).tokens;
195 name = tokens[tokens.length-1];
197 throw new InvalidInputException();
199 isConstructor = CharOperation.equals(this.identifierStack[0], name);
202 if (arguments == null) {
204 JavadocAllocationExpression alloc = new JavadocAllocationExpression(this.identifierPositionStack[0]);
205 alloc.type = typeRef;
206 alloc.tagValue = this.tagValue;
209 JavadocMessageSend msg = new JavadocMessageSend(this.identifierStack[0], this.identifierPositionStack[0]);
210 msg.receiver = typeRef;
211 msg.tagValue = this.tagValue;
215 JavadocArgumentExpression[] expressions = new JavadocArgumentExpression[arguments.size()];
216 arguments.toArray(expressions);
218 JavadocAllocationExpression alloc = new JavadocAllocationExpression(this.identifierPositionStack[0]);
219 alloc.arguments = expressions;
220 alloc.type = typeRef;
221 alloc.tagValue = this.tagValue;
224 JavadocMessageSend msg = new JavadocMessageSend(this.identifierStack[0], this.identifierPositionStack[0], expressions);
225 msg.receiver = typeRef;
226 msg.tagValue = this.tagValue;
231 catch (ClassCastException ex) {
232 throw new InvalidInputException();
236 * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#createReturnStatement()
238 protected Object createReturnStatement() {
239 return new JavadocReturnStatement(this.scanner.getCurrentTokenStartPosition(),
240 this.scanner.getCurrentTokenEndPosition(),
241 this.scanner.getRawTokenSourceEnd());
244 * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#createTypeReference()
246 protected Object createTypeReference(int primitiveToken) {
247 TypeReference typeRef = null;
248 int size = this.identifierLengthStack[this.identifierLengthPtr--];
249 if (size == 1) { // Single Type ref
250 typeRef = new JavadocSingleTypeReference(
251 this.identifierStack[this.identifierPtr],
252 this.identifierPositionStack[this.identifierPtr],
255 } else if (size > 1) { // Qualified Type ref
256 char[][] tokens = new char[size][];
257 System.arraycopy(this.identifierStack, this.identifierPtr - size + 1, tokens, 0, size);
258 long[] positions = new long[size];
259 System.arraycopy(this.identifierPositionStack, this.identifierPtr - size + 1, positions, 0, size);
260 typeRef = new JavadocQualifiedTypeReference(tokens, positions, this.tagSourceStart, this.tagSourceEnd);
262 this.identifierPtr -= size;
267 * Parse @return tag declaration
269 protected boolean parseReturn() {
270 if (this.returnStatement == null) {
271 this.returnStatement = createReturnStatement();
272 this.currentAstPtr = this.astPtr;
275 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocDuplicatedReturnTag(
276 this.scanner.getCurrentTokenStartPosition(),
277 this.scanner.getCurrentTokenEndPosition());
282 * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#parseTag(int)
284 protected boolean parseTag(int previousPosition) throws InvalidInputException {
285 boolean valid = false;
287 // In case of previous return tag, set it to not empty if parsing an inline tag
288 if (this.currentAstPtr != -2 && this.returnStatement != null) {
289 this.currentAstPtr = -2;
290 JavadocReturnStatement javadocReturn = (JavadocReturnStatement) this.returnStatement;
291 javadocReturn.empty = javadocReturn.empty && !this.inlineTagStarted;
295 int token = readTokenAndConsume();
296 this.tagSourceStart = this.scanner.getCurrentTokenStartPosition();
297 this.tagSourceEnd = this.scanner.getCurrentTokenEndPosition();
298 char[] tag = this.scanner.getCurrentIdentifierSource(); // first token is either an identifier or a keyword
300 // Decide which parse to perform depending on tag name
301 this.tagValue = NO_TAG_VALUE;
303 case TerminalTokens.TokenNameIdentifier :
306 if (CharOperation.equals(tag, TAG_DEPRECATED)) {
307 this.deprecated = true;
309 this.tagValue = TAG_DEPRECATED_VALUE;
313 if (CharOperation.equals(tag, TAG_INHERITDOC)) {
314 // inhibits inherited flag when tags have been already stored
315 // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51606
316 // Note that for DOM_PARSER, nodes stack may be not empty even no '@' tag
317 // was encountered in comment. But it cannot be the case for COMPILER_PARSER
318 // and so is enough as it is only this parser which signals the missing tag warnings...
319 this.inherited = this.astPtr==-1;
321 this.tagValue = TAG_INHERITDOC_VALUE;
325 if (CharOperation.equals(tag, TAG_PARAM)) {
326 this.tagValue = TAG_PARAM_VALUE;
327 valid = parseParam();
331 if (CharOperation.equals(tag, TAG_EXCEPTION)) {
332 this.tagValue = TAG_EXCEPTION_VALUE;
333 valid = parseThrows();
337 if (CharOperation.equals(tag, TAG_SEE)) {
338 if (this.inlineTagStarted) {
339 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290
340 // Cannot have @see inside inline comment
342 if (this.sourceParser != null)
343 this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd);
345 this.tagValue = TAG_SEE_VALUE;
346 valid = parseReference();
351 if (CharOperation.equals(tag, TAG_LINK)) {
352 this.tagValue = TAG_LINK_VALUE;
353 if (this.inlineTagStarted) {
354 valid= parseReference();
356 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=53290
357 // Cannot have @link outside inline comment
359 if (this.sourceParser != null)
360 this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd);
362 } else if (CharOperation.equals(tag, TAG_LINKPLAIN)) {
363 this.tagValue = TAG_LINKPLAIN_VALUE;
364 if (this.inlineTagStarted) {
365 valid = parseReference();
368 if (this.sourceParser != null)
369 this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd);
374 if (this.jdk15 && CharOperation.equals(tag, TAG_VALUE)) {
375 this.tagValue = TAG_VALUE_VALUE;
376 if (this.inlineTagStarted) {
377 valid = parseReference();
380 if (this.sourceParser != null)
381 this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd);
389 case TerminalTokens.TokenNamereturn :
390 this.tagValue = TAG_RETURN_VALUE;
391 valid = parseReturn();
392 /* verify characters after return tag (we're expecting text description)
393 if(!verifyCharsAfterReturnTag(this.index)) {
394 if (this.sourceParser != null) {
395 int end = this.starPosition == -1 || this.lineEnd<this.starPosition ? this.lineEnd : this.starPosition;
396 this.sourceParser.problemReporter().javadocEmptyReturnTag(this.tagSourceStart, end);
401 case TerminalTokens.TokenNamethrows :
402 this.tagValue = TAG_THROWS_VALUE;
403 valid = parseThrows();
406 this.textStart = this.index;
411 * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#parseTagName()
413 protected void createTag() {
414 this.tagValue = TAG_OTHERS_VALUE;
418 * Push a param name in ast node stack.
420 protected boolean pushParamName(boolean isTypeParam) {
421 // Create param reference
422 ASTNode nameRef = null;
424 JavadocSingleTypeReference ref = new JavadocSingleTypeReference(this.identifierStack[1],
425 this.identifierPositionStack[1],
430 JavadocSingleNameReference ref = new JavadocSingleNameReference(this.identifierStack[0],
431 this.identifierPositionStack[0],
437 if (this.astLengthPtr == -1) { // First push
438 pushOnAstStack(nameRef, true);
440 // Verify that no @throws has been declared before
441 if (!isTypeParam) { // do not verify for type parameters as @throws may be invalid tag (when declared in class)
442 for (int i=THROWS_TAG_EXPECTED_ORDER; i<=this.astLengthPtr; i+=ORDERED_TAGS_NUMBER) {
443 if (this.astLengthStack[i] != 0) {
444 if (this.sourceParser != null) this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd);
445 // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600
446 // store invalid param references in specific array
447 if (this.invalidParamReferencesPtr == -1l) {
448 this.invalidParamReferencesStack = new JavadocSingleNameReference[10];
450 int stackLength = this.invalidParamReferencesStack.length;
451 if (++this.invalidParamReferencesPtr >= stackLength) {
453 this.invalidParamReferencesStack, 0,
454 this.invalidParamReferencesStack = new JavadocSingleNameReference[stackLength + AstStackIncrement], 0,
457 this.invalidParamReferencesStack[this.invalidParamReferencesPtr] = nameRef;
462 switch (this.astLengthPtr % ORDERED_TAGS_NUMBER) {
463 case PARAM_TAG_EXPECTED_ORDER :
464 // previous push was a @param tag => push another param name
465 pushOnAstStack(nameRef, false);
467 case SEE_TAG_EXPECTED_ORDER :
468 // previous push was a @see tag => push new param name
469 pushOnAstStack(nameRef, true);
479 * Push a reference statement in ast node stack.
481 protected boolean pushSeeRef(Object statement) {
482 if (this.astLengthPtr == -1) { // First push
483 pushOnAstStack(null, true);
484 pushOnAstStack(null, true);
485 pushOnAstStack(statement, true);
487 switch (this.astLengthPtr % ORDERED_TAGS_NUMBER) {
488 case PARAM_TAG_EXPECTED_ORDER :
489 // previous push was a @param tag => push empty @throws tag and new @see tag
490 pushOnAstStack(null, true);
491 pushOnAstStack(statement, true);
493 case THROWS_TAG_EXPECTED_ORDER :
494 // previous push was a @throws tag => push new @see tag
495 pushOnAstStack(statement, true);
497 case SEE_TAG_EXPECTED_ORDER :
498 // previous push was a @see tag => push another @see tag
499 pushOnAstStack(statement, false);
509 * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#pushText(int, int)
511 protected void pushText(int start, int end) {
512 // In case of previous return tag, verify that text make it not empty
513 if (this.currentAstPtr != -2 && this.returnStatement != null) {
514 int position = this.index;
516 boolean empty = true;
517 boolean star = false;
518 char ch = readChar();
519 // Look for first character other than white or '*'
520 if (Character.isWhitespace(ch) || start>(this.tagSourceEnd+1)) {
521 while (this.index <= end && empty) {
523 empty = Character.isWhitespace(ch) || ch == '*';
525 } else if (ch != '*') {
532 // Store result in previous return tag
533 ((JavadocReturnStatement)this.returnStatement).empty = empty;
534 // Reset position and current ast ptr if we are on a different tag than previous return one
535 this.index = position;
536 if (this.currentAstPtr != this.astPtr) {
537 this.currentAstPtr = -2;
543 * Push a throws type ref in ast node stack.
545 protected boolean pushThrowName(Object typeRef) {
546 if (this.astLengthPtr == -1) { // First push
547 pushOnAstStack(null, true);
548 pushOnAstStack(typeRef, true);
550 switch (this.astLengthPtr % ORDERED_TAGS_NUMBER) {
551 case PARAM_TAG_EXPECTED_ORDER :
552 // previous push was a @param tag => push new @throws tag
553 pushOnAstStack(typeRef, true);
555 case THROWS_TAG_EXPECTED_ORDER :
556 // previous push was a @throws tag => push another @throws tag
557 pushOnAstStack(typeRef, false);
559 case SEE_TAG_EXPECTED_ORDER :
560 // previous push was a @see tag => push empty @param and new @throws tags
561 pushOnAstStack(null, true);
562 pushOnAstStack(typeRef, true);
572 * Fill associated comment fields with ast nodes information stored in stack.
574 protected void updateDocComment() {
576 // Set inherited flag
577 this.docComment.inherited = this.inherited;
579 // Set return node if present
580 if (this.returnStatement != null) {
581 this.docComment.returnStatement = (JavadocReturnStatement) this.returnStatement;
584 // Copy array of invalid syntax param tags
585 if (this.invalidParamReferencesPtr >= 0) {
586 this.docComment.invalidParameters = new JavadocSingleNameReference[this.invalidParamReferencesPtr+1];
587 System.arraycopy(this.invalidParamReferencesStack, 0, this.docComment.invalidParameters, 0, this.invalidParamReferencesPtr+1);
590 // If no nodes stored return
591 if (this.astLengthPtr == -1) {
596 int[] sizes = new int[ORDERED_TAGS_NUMBER];
597 for (int i=0; i<=this.astLengthPtr; i++) {
598 sizes[i%ORDERED_TAGS_NUMBER] += this.astLengthStack[i];
600 this.docComment.seeReferences = new Expression[sizes[SEE_TAG_EXPECTED_ORDER]];
601 this.docComment.exceptionReferences = new TypeReference[sizes[THROWS_TAG_EXPECTED_ORDER]];
602 this.docComment.paramReferences = new JavadocSingleNameReference[sizes[PARAM_TAG_EXPECTED_ORDER]];
603 int paramRefPtr = sizes[PARAM_TAG_EXPECTED_ORDER];
604 this.docComment.paramTypeParameters = new JavadocSingleTypeReference[sizes[PARAM_TAG_EXPECTED_ORDER]];
605 int paramTypeParamPtr = sizes[PARAM_TAG_EXPECTED_ORDER];
607 // Store nodes in arrays
608 while (this.astLengthPtr >= 0) {
609 int ptr = this.astLengthPtr % ORDERED_TAGS_NUMBER;
610 // Starting with the stack top, so get references (eg. Expression) coming from @see declarations
612 case SEE_TAG_EXPECTED_ORDER:
613 int size = this.astLengthStack[this.astLengthPtr--];
614 for (int i=0; i<size; i++) {
615 this.docComment.seeReferences[--sizes[ptr]] = (Expression) this.astStack[this.astPtr--];
619 // Then continuing with class names (eg. TypeReference) coming from @throw/@exception declarations
620 case THROWS_TAG_EXPECTED_ORDER:
621 size = this.astLengthStack[this.astLengthPtr--];
622 for (int i=0; i<size; i++) {
623 this.docComment.exceptionReferences[--sizes[ptr]] = (TypeReference) this.astStack[this.astPtr--];
627 // Finally, finishing with parameters nales (ie. Argument) coming from @param declaration
628 case PARAM_TAG_EXPECTED_ORDER:
629 size = this.astLengthStack[this.astLengthPtr--];
630 for (int i=0; i<size; i++) {
631 Expression reference = (Expression) this.astStack[this.astPtr--];
632 if (reference instanceof JavadocSingleNameReference)
633 this.docComment.paramReferences[--paramRefPtr] = (JavadocSingleNameReference) reference;
634 else if (reference instanceof JavadocSingleTypeReference)
635 this.docComment.paramTypeParameters[--paramTypeParamPtr] = (JavadocSingleTypeReference) reference;
641 // Resize param tag references arrays
642 if (paramRefPtr == 0) { // there's no type parameters references
643 this.docComment.paramTypeParameters = null;
644 } else if (paramTypeParamPtr == 0) { // there's no names references
645 this.docComment.paramReferences = null;
646 } else { // there both of references => resize arrays
647 int size = sizes[PARAM_TAG_EXPECTED_ORDER];
648 System.arraycopy(this.docComment.paramReferences, paramRefPtr, this.docComment.paramReferences = new JavadocSingleNameReference[size - paramRefPtr], 0, size - paramRefPtr);
649 System.arraycopy(this.docComment.paramTypeParameters, paramTypeParamPtr, this.docComment.paramTypeParameters = new JavadocSingleTypeReference[size - paramTypeParamPtr], 0, size - paramTypeParamPtr);