/******************************************************************************* * Copyright (c) 2000, 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.compiler.parser; import java.util.List; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.compiler.InvalidInputException; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.Javadoc; import org.eclipse.jdt.internal.compiler.ast.JavadocAllocationExpression; import org.eclipse.jdt.internal.compiler.ast.JavadocArgumentExpression; import org.eclipse.jdt.internal.compiler.ast.JavadocArrayQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.JavadocArraySingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.JavadocFieldReference; import org.eclipse.jdt.internal.compiler.ast.JavadocImplicitTypeReference; import org.eclipse.jdt.internal.compiler.ast.JavadocMessageSend; import org.eclipse.jdt.internal.compiler.ast.JavadocQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.JavadocReturnStatement; import org.eclipse.jdt.internal.compiler.ast.JavadocSingleNameReference; import org.eclipse.jdt.internal.compiler.ast.JavadocSingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.util.Util; /** * Parser specialized for decoding javadoc comments */ public class JavadocParser extends AbstractCommentParser { // Public fields public Javadoc docComment; // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600 // Store param references for tag with invalid syntax private int invalidParamReferencesPtr= -1; private ASTNode[] invalidParamReferencesStack; // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=153399 // Store value tag positions private long validValuePositions, invalidValuePositions; // returns whether this JavadocParser should report errors or not (overrides reportProblems) // see "https://bugs.eclipse.org/bugs/show_bug.cgi?id=192449" public boolean shouldReportProblems= true; // flag to let the parser know that the current tag is waiting for a description // see "https://bugs.eclipse.org/bugs/show_bug.cgi?id=222900" private int tagWaitingForDescription; public JavadocParser(Parser sourceParser) { super(sourceParser); this.kind= COMPIL_PARSER | TEXT_VERIF; } /* (non-Javadoc) * Returns true if tag @deprecated is present in javadoc comment. * * If javadoc checking is enabled, will also construct an Javadoc node, which will be stored into Parser.javadoc * slot for being consumed later on. */ public boolean checkDeprecation(int commentPtr) { // Store javadoc positions this.javadocStart= this.sourceParser.scanner.commentStarts[commentPtr]; this.javadocEnd= this.sourceParser.scanner.commentStops[commentPtr] - 1; this.firstTagPosition= this.sourceParser.scanner.commentTagStarts[commentPtr]; this.validValuePositions= -1; this.invalidValuePositions= -1; this.tagWaitingForDescription= NO_TAG_VALUE; // Init javadoc if necessary if (this.checkDocComment) { this.docComment= new Javadoc(this.javadocStart, this.javadocEnd); } else { this.docComment= null; } // If there's no tag in javadoc, return without parsing it if (this.firstTagPosition == 0) { switch (this.kind & PARSER_KIND) { case COMPIL_PARSER: case SOURCE_PARSER: return false; } } // Parse try { this.source= this.sourceParser.scanner.source; if (this.checkDocComment) { // Initialization this.scanner.lineEnds= this.sourceParser.scanner.lineEnds; this.scanner.linePtr= this.sourceParser.scanner.linePtr; this.lineEnds= this.scanner.lineEnds; commentParse(); } else { // Parse comment Scanner sourceScanner= this.sourceParser.scanner; int firstLineNumber= Util.getLineNumber(this.javadocStart, sourceScanner.lineEnds, 0, sourceScanner.linePtr); int lastLineNumber= Util.getLineNumber(this.javadocEnd, sourceScanner.lineEnds, 0, sourceScanner.linePtr); this.index= this.javadocStart + 3; // scan line per line, since tags must be at beginning of lines only this.deprecated= false; nextLine: for (int line= firstLineNumber; line <= lastLineNumber; line++) { int lineStart= line == firstLineNumber ? this.javadocStart + 3 // skip leading /** : this.sourceParser.scanner.getLineStart(line); this.index= lineStart; this.lineEnd= line == lastLineNumber ? this.javadocEnd - 2 // remove trailing * / : this.sourceParser.scanner.getLineEnd(line); nextCharacter: while (this.index < this.lineEnd) { char c= readChar(); // consider unicodes switch (c) { case '*': case '\u000c': /* FORM FEED */ case ' ': /* SPACE */ case '\t': /* HORIZONTAL TABULATION */ case '\n': /* LINE FEED */ case '\r': /* CR */ // do nothing for space or '*' characters continue nextCharacter; case '@': parseSimpleTag(); if (this.tagValue == TAG_DEPRECATED_VALUE) { if (this.abort) break nextCharacter; } } continue nextLine; } } return this.deprecated; } } finally { this.source= null; // release source as soon as finished } return this.deprecated; } /* (non-Javadoc) * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#createArgumentReference(char[], java.lang.Object, int) */ protected Object createArgumentReference(char[] name, int dim, boolean isVarargs, Object typeRef, long[] dimPositions, long argNamePos) throws InvalidInputException { try { TypeReference argTypeRef= (TypeReference)typeRef; if (dim > 0) { long pos= (((long)argTypeRef.sourceStart) << 32) + argTypeRef.sourceEnd; if (typeRef instanceof JavadocSingleTypeReference) { JavadocSingleTypeReference singleRef= (JavadocSingleTypeReference)typeRef; argTypeRef= new JavadocArraySingleTypeReference(singleRef.token, dim, pos); } else { JavadocQualifiedTypeReference qualifRef= (JavadocQualifiedTypeReference)typeRef; argTypeRef= new JavadocArrayQualifiedTypeReference(qualifRef, dim); } } int argEnd= argTypeRef.sourceEnd; if (dim > 0) { argEnd= (int)dimPositions[dim - 1]; if (isVarargs) { argTypeRef.bits|= ASTNode.IsVarArgs; // set isVarArgs } } if (argNamePos >= 0) argEnd= (int)argNamePos; return new JavadocArgumentExpression(name, argTypeRef.sourceStart, argEnd, argTypeRef); } catch (ClassCastException ex) { throw new InvalidInputException(); } } /* (non-Javadoc) * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#createFieldReference() */ protected Object createFieldReference(Object receiver) throws InvalidInputException { try { // Get receiver type TypeReference typeRef= (TypeReference)receiver; if (typeRef == null) { char[] name= this.sourceParser.compilationUnit.getMainTypeName(); typeRef= new JavadocImplicitTypeReference(name, this.memberStart); } // Create field JavadocFieldReference field= new JavadocFieldReference(this.identifierStack[0], this.identifierPositionStack[0]); field.receiver= typeRef; field.tagSourceStart= this.tagSourceStart; field.tagSourceEnd= this.tagSourceEnd; field.tagValue= this.tagValue; return field; } catch (ClassCastException ex) { throw new InvalidInputException(); } } /* (non-Javadoc) * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#createMethodReference(java.lang.Object[]) */ protected Object createMethodReference(Object receiver, List arguments) throws InvalidInputException { try { // Get receiver type TypeReference typeRef= (TypeReference)receiver; // Decide whether we have a constructor or not boolean isConstructor= false; int length= this.identifierLengthStack[0]; // may be > 1 for member class constructor reference if (typeRef == null) { char[] name= this.sourceParser.compilationUnit.getMainTypeName(); TypeDeclaration typeDecl= getParsedTypeDeclaration(); if (typeDecl != null) { name= typeDecl.name; } isConstructor= CharOperation.equals(this.identifierStack[length - 1], name); typeRef= new JavadocImplicitTypeReference(name, this.memberStart); } else { if (typeRef instanceof JavadocSingleTypeReference) { char[] name= ((JavadocSingleTypeReference)typeRef).token; isConstructor= CharOperation.equals(this.identifierStack[length - 1], name); } else if (typeRef instanceof JavadocQualifiedTypeReference) { char[][] tokens= ((JavadocQualifiedTypeReference)typeRef).tokens; int last= tokens.length - 1; isConstructor= CharOperation.equals(this.identifierStack[length - 1], tokens[last]); if (isConstructor) { boolean valid= true; if (valid) { for (int i= 0; i < length - 1 && valid; i++) { valid= CharOperation.equals(this.identifierStack[i], tokens[i]); } } if (!valid) { if (this.reportProblems) { this.sourceParser.problemReporter().javadocInvalidMemberTypeQualification((int)(this.identifierPositionStack[0] >>> 32), (int)this.identifierPositionStack[length - 1], -1); } return null; } } } else { throw new InvalidInputException(); } } // Create node if (arguments == null) { if (isConstructor) { JavadocAllocationExpression allocation= new JavadocAllocationExpression(this.identifierPositionStack[length - 1]); allocation.type= typeRef; allocation.tagValue= this.tagValue; allocation.sourceEnd= this.scanner.getCurrentTokenEndPosition(); if (length == 1) { allocation.qualification= new char[][] { this.identifierStack[0] }; } else { System.arraycopy(this.identifierStack, 0, allocation.qualification= new char[length][], 0, length); allocation.sourceStart= (int)(this.identifierPositionStack[0] >>> 32); } allocation.memberStart= this.memberStart; return allocation; } else { JavadocMessageSend msg= new JavadocMessageSend(this.identifierStack[length - 1], this.identifierPositionStack[length - 1]); msg.receiver= typeRef; msg.tagValue= this.tagValue; msg.sourceEnd= this.scanner.getCurrentTokenEndPosition(); return msg; } } else { JavadocArgumentExpression[] expressions= new JavadocArgumentExpression[arguments.size()]; arguments.toArray(expressions); if (isConstructor) { JavadocAllocationExpression allocation= new JavadocAllocationExpression(this.identifierPositionStack[length - 1]); allocation.arguments= expressions; allocation.type= typeRef; allocation.tagValue= this.tagValue; allocation.sourceEnd= this.scanner.getCurrentTokenEndPosition(); if (length == 1) { allocation.qualification= new char[][] { this.identifierStack[0] }; } else { System.arraycopy(this.identifierStack, 0, allocation.qualification= new char[length][], 0, length); allocation.sourceStart= (int)(this.identifierPositionStack[0] >>> 32); } allocation.memberStart= this.memberStart; return allocation; } else { JavadocMessageSend msg= new JavadocMessageSend(this.identifierStack[length - 1], this.identifierPositionStack[length - 1], expressions); msg.receiver= typeRef; msg.tagValue= this.tagValue; msg.sourceEnd= this.scanner.getCurrentTokenEndPosition(); return msg; } } } catch (ClassCastException ex) { throw new InvalidInputException(); } } /* (non-Javadoc) * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#createReturnStatement() */ protected Object createReturnStatement() { return new JavadocReturnStatement(this.scanner.getCurrentTokenStartPosition(), this.scanner.getCurrentTokenEndPosition()); } /* (non-Javadoc) * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#parseTagName() */ protected void createTag() { this.tagValue= TAG_OTHERS_VALUE; } /* (non-Javadoc) * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#createTypeReference() */ protected Object createTypeReference(int primitiveToken) { TypeReference typeRef= null; int size= this.identifierLengthStack[this.identifierLengthPtr]; if (size == 1) { // Single Type ref typeRef= new JavadocSingleTypeReference( this.identifierStack[this.identifierPtr], this.identifierPositionStack[this.identifierPtr], this.tagSourceStart, this.tagSourceEnd); } else if (size > 1) { // Qualified Type ref char[][] tokens= new char[size][]; System.arraycopy(this.identifierStack, this.identifierPtr - size + 1, tokens, 0, size); long[] positions= new long[size]; System.arraycopy(this.identifierPositionStack, this.identifierPtr - size + 1, positions, 0, size); typeRef= new JavadocQualifiedTypeReference(tokens, positions, this.tagSourceStart, this.tagSourceEnd); } return typeRef; } /* * Get current parsed type declaration. */ protected TypeDeclaration getParsedTypeDeclaration() { int ptr= this.sourceParser.astPtr; while (ptr >= 0) { Object node= this.sourceParser.astStack[ptr]; if (node instanceof TypeDeclaration) { TypeDeclaration typeDecl= (TypeDeclaration)node; if (typeDecl.bodyEnd == 0) { // type declaration currenly parsed return typeDecl; } } ptr--; } return null; } /* * Parse @throws tag declaration and flag missing description if corresponding option is enabled */ protected boolean parseThrows() { boolean valid= super.parseThrows(); this.tagWaitingForDescription= valid && this.reportProblems ? TAG_THROWS_VALUE : NO_TAG_VALUE; return valid; } /* * Parse @return tag declaration */ protected boolean parseReturn() { if (this.returnStatement == null) { this.returnStatement= createReturnStatement(); return true; } if (this.reportProblems) { this.sourceParser.problemReporter().javadocDuplicatedReturnTag( this.scanner.getCurrentTokenStartPosition(), this.scanner.getCurrentTokenEndPosition()); } return false; } protected void parseSimpleTag() { // Read first char // readChar() code is inlined to balance additional method call in checkDeprectation(int) char first= this.source[this.index++]; if (first == '\\' && this.source[this.index] == 'u') { int c1, c2, c3, c4; int pos= this.index; this.index++; while (this.source[this.index] == 'u') this.index++; if (!(((c1= ScannerHelper.getNumericValue(this.source[this.index++])) > 15 || c1 < 0) || ((c2= ScannerHelper.getNumericValue(this.source[this.index++])) > 15 || c2 < 0) || ((c3= ScannerHelper.getNumericValue(this.source[this.index++])) > 15 || c3 < 0) || ((c4= ScannerHelper.getNumericValue(this.source[this.index++])) > 15 || c4 < 0))) { first= (char)(((c1 * 16 + c2) * 16 + c3) * 16 + c4); } else { this.index= pos; } } // switch on first tag char switch (first) { case 'd': if ((readChar() == 'e') && (readChar() == 'p') && (readChar() == 'r') && (readChar() == 'e') && (readChar() == 'c') && (readChar() == 'a') && (readChar() == 't') && (readChar() == 'e') && (readChar() == 'd')) { // ensure the tag is properly ended: either followed by a space, a tab, line end or asterisk. char c= readChar(); if (ScannerHelper.isWhitespace(c) || c == '*') { this.abort= true; this.deprecated= true; this.tagValue= TAG_DEPRECATED_VALUE; } } break; } } protected boolean parseTag(int previousPosition) throws InvalidInputException { // Complain when tag is missing a description // Note that if the parse of an inline tag has already started, consider it // as the expected description, hence do not report any warning switch (this.tagWaitingForDescription) { case TAG_PARAM_VALUE: case TAG_THROWS_VALUE: if (!this.inlineTagStarted) { int start= (int)(this.identifierPositionStack[0] >>> 32); int end= (int)this.identifierPositionStack[this.identifierPtr]; this.sourceParser.problemReporter().javadocMissingTagDescriptionAfterReference(start, end, this.sourceParser.modifiers); } break; case NO_TAG_VALUE: break; default: if (!this.inlineTagStarted) { this.sourceParser.problemReporter().javadocMissingTagDescription(TAG_NAMES[this.tagWaitingForDescription], this.tagSourceStart, this.tagSourceEnd, this.sourceParser.modifiers); } break; } this.tagWaitingForDescription= NO_TAG_VALUE; // Verify first character this.tagSourceStart= this.index; this.tagSourceEnd= previousPosition; this.scanner.startPosition= this.index; int currentPosition= this.index; char firstChar= readChar(); switch (firstChar) { case ' ': case '*': case '}': case '#': // the first character is not valid, hence report invalid empty tag if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidTag(previousPosition, currentPosition); if (this.textStart == -1) this.textStart= currentPosition; this.scanner.currentCharacter= firstChar; return false; default: if (ScannerHelper.isWhitespace(firstChar)) { // the first character is not valid, hence report invalid empty tag if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidTag(previousPosition, currentPosition); if (this.textStart == -1) this.textStart= currentPosition; this.scanner.currentCharacter= firstChar; return false; } break; } // Read tag name char[] tagName= new char[32]; int length= 0; char currentChar= firstChar; int tagNameLength= tagName.length; boolean validTag= true; tagLoop: while (true) { if (length == tagNameLength) { System.arraycopy(tagName, 0, tagName= new char[tagNameLength + 32], 0, tagNameLength); tagNameLength= tagName.length; } tagName[length++]= currentChar; currentPosition= this.index; currentChar= readChar(); switch (currentChar) { case ' ': case '*': case '}': // these characters mark the end of the tag reading break tagLoop; case '#': // invalid tag character, mark the tag as invalid but continue until the end of the tag validTag= false; break; default: if (ScannerHelper.isWhitespace(currentChar)) { // whitespace characters mark the end of the tag reading break tagLoop; } break; } } // Init positions this.tagSourceEnd= currentPosition - 1; this.scanner.currentCharacter= currentChar; this.scanner.currentPosition= currentPosition; this.index= this.tagSourceEnd + 1; // Return if the tag is not valid if (!validTag) { if (this.reportProblems) this.sourceParser.problemReporter().javadocInvalidTag(this.tagSourceStart, this.tagSourceEnd); if (this.textStart == -1) this.textStart= this.index; this.scanner.currentCharacter= currentChar; return false; } // Decide which parse to perform depending on tag name this.tagValue= TAG_OTHERS_VALUE; boolean valid= false; switch (firstChar) { case 'a': if (length == TAG_AUTHOR_LENGTH && CharOperation.equals(TAG_AUTHOR, tagName, 0, length)) { this.tagValue= TAG_AUTHOR_VALUE; this.tagWaitingForDescription= this.tagValue; } break; case 'c': if (length == TAG_CATEGORY_LENGTH && CharOperation.equals(TAG_CATEGORY, tagName, 0, length)) { this.tagValue= TAG_CATEGORY_VALUE; if (!this.inlineTagStarted) { valid= parseIdentifierTag(false); // TODO (frederic) reconsider parameter value when @category will be significant in spec } } else if (length == TAG_CODE_LENGTH && this.inlineTagStarted && CharOperation.equals(TAG_CODE, tagName, 0, length)) { this.tagValue= TAG_CODE_VALUE; this.tagWaitingForDescription= this.tagValue; } break; case 'd': if (length == TAG_DEPRECATED_LENGTH && CharOperation.equals(TAG_DEPRECATED, tagName, 0, length)) { this.deprecated= true; valid= true; this.tagValue= TAG_DEPRECATED_VALUE; this.tagWaitingForDescription= this.tagValue; } else if (length == TAG_DOC_ROOT_LENGTH && CharOperation.equals(TAG_DOC_ROOT, tagName, 0, length)) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=227730 // identify @docRoot tag as a base tag that does not expect any argument valid= true; this.tagValue= TAG_DOC_ROOT_VALUE; } break; case 'e': if (length == TAG_EXCEPTION_LENGTH && CharOperation.equals(TAG_EXCEPTION, tagName, 0, length)) { this.tagValue= TAG_EXCEPTION_VALUE; if (!this.inlineTagStarted) { valid= parseThrows(); } } break; case 'i': if (length == TAG_INHERITDOC_LENGTH && CharOperation.equals(TAG_INHERITDOC, tagName, 0, length)) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=247037, @inheritDoc usage is illegal // outside of few block tags and the main description. switch (this.lastBlockTagValue) { case TAG_RETURN_VALUE: case TAG_THROWS_VALUE: case TAG_EXCEPTION_VALUE: case TAG_PARAM_VALUE: case NO_TAG_VALUE: // Still in main description valid= true; if (this.reportProblems) { recordInheritedPosition((((long)this.tagSourceStart) << 32) + this.tagSourceEnd); } break; default: valid= false; if (this.reportProblems) { this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd); } } this.tagValue= TAG_INHERITDOC_VALUE; } break; case 'l': if (length == TAG_LINK_LENGTH && CharOperation.equals(TAG_LINK, tagName, 0, length)) { this.tagValue= TAG_LINK_VALUE; if (this.inlineTagStarted || (this.kind & COMPLETION_PARSER) != 0) { valid= parseReference(); } } else if (length == TAG_LINKPLAIN_LENGTH && CharOperation.equals(TAG_LINKPLAIN, tagName, 0, length)) { this.tagValue= TAG_LINKPLAIN_VALUE; if (this.inlineTagStarted) { valid= parseReference(); } } else if (length == TAG_LITERAL_LENGTH && this.inlineTagStarted && CharOperation.equals(TAG_LITERAL, tagName, 0, length)) { this.tagValue= TAG_LITERAL_VALUE; this.tagWaitingForDescription= this.tagValue; } break; case 'p': if (length == TAG_PARAM_LENGTH && CharOperation.equals(TAG_PARAM, tagName, 0, length)) { this.tagValue= TAG_PARAM_VALUE; if (!this.inlineTagStarted) { valid= parseParam(); } } break; case 'r': if (length == TAG_RETURN_LENGTH && CharOperation.equals(TAG_RETURN, tagName, 0, length)) { this.tagValue= TAG_RETURN_VALUE; if (!this.inlineTagStarted) { valid= parseReturn(); } } break; case 's': if (length == TAG_SEE_LENGTH && CharOperation.equals(TAG_SEE, tagName, 0, length)) { this.tagValue= TAG_SEE_VALUE; if (!this.inlineTagStarted) { valid= parseReference(); } } else if (length == TAG_SERIAL_LENGTH && CharOperation.equals(TAG_SERIAL, tagName, 0, length)) { this.tagValue= TAG_SERIAL_VALUE; this.tagWaitingForDescription= this.tagValue; } else if (length == TAG_SERIAL_DATA_LENGTH && CharOperation.equals(TAG_SERIAL_DATA, tagName, 0, length)) { this.tagValue= TAG_SERIAL_DATA_VALUE; this.tagWaitingForDescription= this.tagValue; } else if (length == TAG_SERIAL_FIELD_LENGTH && CharOperation.equals(TAG_SERIAL_FIELD, tagName, 0, length)) { this.tagValue= TAG_SERIAL_FIELD_VALUE; this.tagWaitingForDescription= this.tagValue; } else if (length == TAG_SINCE_LENGTH && CharOperation.equals(TAG_SINCE, tagName, 0, length)) { this.tagValue= TAG_SINCE_VALUE; this.tagWaitingForDescription= this.tagValue; } break; case 't': if (length == TAG_THROWS_LENGTH && CharOperation.equals(TAG_THROWS, tagName, 0, length)) { this.tagValue= TAG_THROWS_VALUE; if (!this.inlineTagStarted) { valid= parseThrows(); } } break; case 'v': if (length == TAG_VALUE_LENGTH && CharOperation.equals(TAG_VALUE, tagName, 0, length)) { this.tagValue= TAG_VALUE_VALUE; if (this.sourceLevel >= ClassFileConstants.JDK1_5) { if (this.inlineTagStarted) { valid= parseReference(); } } else { if (this.validValuePositions == -1) { if (this.invalidValuePositions != -1) { if (this.reportProblems) this.sourceParser.problemReporter().javadocUnexpectedTag((int)(this.invalidValuePositions >>> 32), (int)this.invalidValuePositions); } if (valid) { this.validValuePositions= (((long)this.tagSourceStart) << 32) + this.tagSourceEnd; this.invalidValuePositions= -1; } else { this.invalidValuePositions= (((long)this.tagSourceStart) << 32) + this.tagSourceEnd; } } else { if (this.reportProblems) this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd); } } } else if (length == TAG_VERSION_LENGTH && CharOperation.equals(TAG_VERSION, tagName, 0, length)) { this.tagValue= TAG_VERSION_VALUE; this.tagWaitingForDescription= this.tagValue; } else { createTag(); } break; default: createTag(); break; } this.textStart= this.index; if (this.tagValue != TAG_OTHERS_VALUE) { if (!this.inlineTagStarted) { this.lastBlockTagValue= this.tagValue; } // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=267833 // Report a problem if a block tag is being used in the context of an inline tag and vice versa. if ((this.inlineTagStarted && JAVADOC_TAG_TYPE[this.tagValue] == TAG_TYPE_BLOCK) || (!this.inlineTagStarted && JAVADOC_TAG_TYPE[this.tagValue] == TAG_TYPE_INLINE)) { valid= false; this.tagValue= TAG_OTHERS_VALUE; this.tagWaitingForDescription= NO_TAG_VALUE; if (this.reportProblems) { this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd); } } } return valid; } /* * Parse @param tag declaration and flag missing description if corresponding option is enabled */ protected boolean parseParam() throws InvalidInputException { boolean valid= super.parseParam(); this.tagWaitingForDescription= valid && this.reportProblems ? TAG_PARAM_VALUE : NO_TAG_VALUE; return valid; } /* * Push a param name in ast node stack. */ protected boolean pushParamName(boolean isTypeParam) { // Create param reference ASTNode nameRef= null; if (isTypeParam) { JavadocSingleTypeReference ref= new JavadocSingleTypeReference(this.identifierStack[1], this.identifierPositionStack[1], this.tagSourceStart, this.tagSourceEnd); nameRef= ref; } else { JavadocSingleNameReference ref= new JavadocSingleNameReference(this.identifierStack[0], this.identifierPositionStack[0], this.tagSourceStart, this.tagSourceEnd); nameRef= ref; } // Push ref on stack if (this.astLengthPtr == -1) { // First push pushOnAstStack(nameRef, true); } else { // Verify that no @throws has been declared before if (!isTypeParam) { // do not verify for type parameters as @throws may be invalid tag (when declared in class) for (int i= THROWS_TAG_EXPECTED_ORDER; i <= this.astLengthPtr; i+= ORDERED_TAGS_NUMBER) { if (this.astLengthStack[i] != 0) { if (this.reportProblems) this.sourceParser.problemReporter().javadocUnexpectedTag(this.tagSourceStart, this.tagSourceEnd); // bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=51600 // store invalid param references in specific array if (this.invalidParamReferencesPtr == -1l) { this.invalidParamReferencesStack= new JavadocSingleNameReference[10]; } int stackLength= this.invalidParamReferencesStack.length; if (++this.invalidParamReferencesPtr >= stackLength) { System.arraycopy( this.invalidParamReferencesStack, 0, this.invalidParamReferencesStack= new JavadocSingleNameReference[stackLength + AST_STACK_INCREMENT], 0, stackLength); } this.invalidParamReferencesStack[this.invalidParamReferencesPtr]= nameRef; return false; } } } switch (this.astLengthPtr % ORDERED_TAGS_NUMBER) { case PARAM_TAG_EXPECTED_ORDER: // previous push was a @param tag => push another param name pushOnAstStack(nameRef, false); break; case SEE_TAG_EXPECTED_ORDER: // previous push was a @see tag => push new param name pushOnAstStack(nameRef, true); break; default: return false; } } return true; } /* * Push a reference statement in ast node stack. */ protected boolean pushSeeRef(Object statement) { if (this.astLengthPtr == -1) { // First push pushOnAstStack(null, true); pushOnAstStack(null, true); pushOnAstStack(statement, true); } else { switch (this.astLengthPtr % ORDERED_TAGS_NUMBER) { case PARAM_TAG_EXPECTED_ORDER: // previous push was a @param tag => push empty @throws tag and new @see tag pushOnAstStack(null, true); pushOnAstStack(statement, true); break; case THROWS_TAG_EXPECTED_ORDER: // previous push was a @throws tag => push new @see tag pushOnAstStack(statement, true); break; case SEE_TAG_EXPECTED_ORDER: // previous push was a @see tag => push another @see tag pushOnAstStack(statement, false); break; default: return false; } } return true; } /* (non-Javadoc) * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#pushText(int, int) */ protected void pushText(int start, int end) { // The tag gets its description => clear the flag this.tagWaitingForDescription= NO_TAG_VALUE; } /* * Push a throws type ref in ast node stack. */ protected boolean pushThrowName(Object typeRef) { if (this.astLengthPtr == -1) { // First push pushOnAstStack(null, true); pushOnAstStack(typeRef, true); } else { switch (this.astLengthPtr % ORDERED_TAGS_NUMBER) { case PARAM_TAG_EXPECTED_ORDER: // previous push was a @param tag => push new @throws tag pushOnAstStack(typeRef, true); break; case THROWS_TAG_EXPECTED_ORDER: // previous push was a @throws tag => push another @throws tag pushOnAstStack(typeRef, false); break; case SEE_TAG_EXPECTED_ORDER: // previous push was a @see tag => push empty @param and new @throws tags pushOnAstStack(null, true); pushOnAstStack(typeRef, true); break; default: return false; } } return true; } /* (non-Javadoc) * @see org.eclipse.jdt.internal.compiler.parser.AbstractCommentParser#refreshInlineTagPosition(int) */ protected void refreshInlineTagPosition(int previousPosition) { // Signal tag missing description if necessary if (this.tagWaitingForDescription != NO_TAG_VALUE) { this.sourceParser.problemReporter().javadocMissingTagDescription(TAG_NAMES[this.tagWaitingForDescription], this.tagSourceStart, this.tagSourceEnd, this.sourceParser.modifiers); this.tagWaitingForDescription= NO_TAG_VALUE; } } /* * Refresh return statement */ protected void refreshReturnStatement() { ((JavadocReturnStatement)this.returnStatement).bits&= ~ASTNode.Empty; } public String toString() { StringBuffer buffer= new StringBuffer(); buffer.append("check javadoc: ").append(this.checkDocComment).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ buffer.append("javadoc: ").append(this.docComment).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ buffer.append(super.toString()); return buffer.toString(); } /* * Fill associated comment fields with ast nodes information stored in stack. */ protected void updateDocComment() { // Complain when tag is missing a description // Note that if the parse of an inline tag has already started, consider it // as the expected description, hence do not report any warning switch (this.tagWaitingForDescription) { case TAG_PARAM_VALUE: case TAG_THROWS_VALUE: if (!this.inlineTagStarted) { int start= (int)(this.identifierPositionStack[0] >>> 32); int end= (int)this.identifierPositionStack[this.identifierPtr]; this.sourceParser.problemReporter().javadocMissingTagDescriptionAfterReference(start, end, this.sourceParser.modifiers); } break; case NO_TAG_VALUE: break; default: if (!this.inlineTagStarted) { this.sourceParser.problemReporter().javadocMissingTagDescription(TAG_NAMES[this.tagWaitingForDescription], this.tagSourceStart, this.tagSourceEnd, this.sourceParser.modifiers); } break; } this.tagWaitingForDescription= NO_TAG_VALUE; // Set positions if (this.inheritedPositions != null && this.inheritedPositionsPtr != this.inheritedPositions.length) { // Compact array by shrinking. System.arraycopy(this.inheritedPositions, 0, this.inheritedPositions= new long[this.inheritedPositionsPtr], 0, this.inheritedPositionsPtr); } this.docComment.inheritedPositions= this.inheritedPositions; this.docComment.valuePositions= this.validValuePositions != -1 ? this.validValuePositions : this.invalidValuePositions; // Set return node if present if (this.returnStatement != null) { this.docComment.returnStatement= (JavadocReturnStatement)this.returnStatement; } // Copy array of invalid syntax param tags if (this.invalidParamReferencesPtr >= 0) { this.docComment.invalidParameters= new JavadocSingleNameReference[this.invalidParamReferencesPtr + 1]; System.arraycopy(this.invalidParamReferencesStack, 0, this.docComment.invalidParameters, 0, this.invalidParamReferencesPtr + 1); } // If no nodes stored return if (this.astLengthPtr == -1) { return; } // Initialize arrays int[] sizes= new int[ORDERED_TAGS_NUMBER]; for (int i= 0; i <= this.astLengthPtr; i++) { sizes[i % ORDERED_TAGS_NUMBER]+= this.astLengthStack[i]; } this.docComment.seeReferences= new Expression[sizes[SEE_TAG_EXPECTED_ORDER]]; this.docComment.exceptionReferences= new TypeReference[sizes[THROWS_TAG_EXPECTED_ORDER]]; int paramRefPtr= sizes[PARAM_TAG_EXPECTED_ORDER]; this.docComment.paramReferences= new JavadocSingleNameReference[paramRefPtr]; int paramTypeParamPtr= sizes[PARAM_TAG_EXPECTED_ORDER]; this.docComment.paramTypeParameters= new JavadocSingleTypeReference[paramTypeParamPtr]; // Store nodes in arrays while (this.astLengthPtr >= 0) { int ptr= this.astLengthPtr % ORDERED_TAGS_NUMBER; // Starting with the stack top, so get references (Expression) coming from @see declarations switch (ptr) { case SEE_TAG_EXPECTED_ORDER: int size= this.astLengthStack[this.astLengthPtr--]; for (int i= 0; i < size; i++) { this.docComment.seeReferences[--sizes[ptr]]= (Expression)this.astStack[this.astPtr--]; } break; // Then continuing with class names (TypeReference) coming from @throw/@exception declarations case THROWS_TAG_EXPECTED_ORDER: size= this.astLengthStack[this.astLengthPtr--]; for (int i= 0; i < size; i++) { this.docComment.exceptionReferences[--sizes[ptr]]= (TypeReference)this.astStack[this.astPtr--]; } break; // Finally, finishing with parameters names (Argument) coming from @param declaration case PARAM_TAG_EXPECTED_ORDER: size= this.astLengthStack[this.astLengthPtr--]; for (int i= 0; i < size; i++) { Expression reference= (Expression)this.astStack[this.astPtr--]; if (reference instanceof JavadocSingleNameReference) this.docComment.paramReferences[--paramRefPtr]= (JavadocSingleNameReference)reference; else if (reference instanceof JavadocSingleTypeReference) this.docComment.paramTypeParameters[--paramTypeParamPtr]= (JavadocSingleTypeReference)reference; } break; } } // Resize param tag references arrays if (paramRefPtr == 0) { // there's no type parameters references this.docComment.paramTypeParameters= null; } else if (paramTypeParamPtr == 0) { // there's no names references this.docComment.paramReferences= null; } else { // there both of references => resize arrays int size= sizes[PARAM_TAG_EXPECTED_ORDER]; System.arraycopy(this.docComment.paramReferences, paramRefPtr, this.docComment.paramReferences= new JavadocSingleNameReference[size - paramRefPtr], 0, size - paramRefPtr); System.arraycopy(this.docComment.paramTypeParameters, paramTypeParamPtr, this.docComment.paramTypeParameters= new JavadocSingleTypeReference[size - paramTypeParamPtr], 0, size - paramTypeParamPtr); } } }