Code with Finding: |
class JsDocInfoParser {
/**
* Parses a {@link JSDocInfo} object. This parsing method reads all tokens
* returned by the {@link JsDocTokenStream#getJsDocToken()} method until the
* {@link JsDocToken#EOC} is returned.
*
* @return {@code true} if JSDoc information was correctly parsed,
* {@code false} otherwise
*/
boolean parse() {
int lineno;
int charno;
// JSTypes are represented as Rhino AST nodes, and then resolved later.
JSTypeExpression type;
state = State.SEARCHING_ANNOTATION;
skipEOLs();
JsDocToken token = next();
// Always record that we have a comment.
if (jsdocBuilder.shouldParseDocumentation()) {
ExtractionInfo blockInfo = extractBlockComment(token);
token = blockInfo.token;
if (!blockInfo.string.isEmpty()) {
jsdocBuilder.recordBlockDescription(blockInfo.string);
}
} else {
if (token != JsDocToken.ANNOTATION &&
token != JsDocToken.EOC) {
// Mark that there was a description, but don't bother marking
// what it was.
jsdocBuilder.recordBlockDescription("");
}
}
// Parse the actual JsDoc.
retry: for (;;) {
switch (token) {
case ANNOTATION:
if (state == State.SEARCHING_ANNOTATION) {
state = State.SEARCHING_NEWLINE;
lineno = stream.getLineno();
charno = stream.getCharno();
String annotationName = stream.getString();
Annotation annotation = annotationNames.get(annotationName);
if (annotation == null) {
parser.addWarning("msg.bad.jsdoc.tag", annotationName,
stream.getLineno(), stream.getCharno());
} else {
// Mark the beginning of the annotation.
jsdocBuilder.markAnnotation(annotationName, lineno, charno);
switch (annotation) {
case AUTHOR:
if (jsdocBuilder.shouldParseDocumentation()) {
ExtractionInfo authorInfo = extractSingleLineBlock();
String author = authorInfo.string;
if (author.length() == 0) {
parser.addWarning("msg.jsdoc.authormissing",
stream.getLineno(), stream.getCharno());
} else {
jsdocBuilder.addAuthor(author);
}
token = authorInfo.token;
} else {
token = eatTokensUntilEOL(token);
}
continue retry;
case CONSTANT:
if (!jsdocBuilder.recordConstancy()) {
parser.addWarning("msg.jsdoc.const",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case CONSTRUCTOR:
if (!jsdocBuilder.recordConstructor()) {
if (jsdocBuilder.isInterfaceRecorded()) {
parser.addWarning("msg.jsdoc.interface.constructor",
stream.getLineno(), stream.getCharno());
} else {
parser.addWarning("msg.jsdoc.incompat.type",
stream.getLineno(), stream.getCharno());
}
}
token = eatTokensUntilEOL();
continue retry;
case DEPRECATED:
if (!jsdocBuilder.recordDeprecated()) {
parser.addWarning("msg.jsdoc.deprecated",
stream.getLineno(), stream.getCharno());
}
// Find the reason/description, if any.
ExtractionInfo reasonInfo =
extractMultilineTextualBlock(token);
String reason = reasonInfo.string;
if (reason.length() > 0) {
jsdocBuilder.recordDeprecationReason(reason);
}
token = reasonInfo.token;
continue retry;
case INTERFACE:
if (!jsdocBuilder.recordInterface()) {
if (jsdocBuilder.isConstructorRecorded()) {
parser.addWarning("msg.jsdoc.interface.constructor",
stream.getLineno(), stream.getCharno());
} else {
parser.addWarning("msg.jsdoc.incompat.type",
stream.getLineno(), stream.getCharno());
}
}
token = eatTokensUntilEOL();
continue retry;
case DESC:
if (jsdocBuilder.isDescriptionRecorded()) {
parser.addWarning("msg.jsdoc.desc.extra",
stream.getLineno(), stream.getCharno());
token = eatTokensUntilEOL();
continue retry;
} else {
ExtractionInfo descriptionInfo =
extractMultilineTextualBlock(token);
String description = descriptionInfo.string;
jsdocBuilder.recordDescription(description);
token = descriptionInfo.token;
continue retry;
}
case FILE_OVERVIEW:
String fileOverview = "";
if (jsdocBuilder.shouldParseDocumentation()) {
ExtractionInfo fileOverviewInfo =
extractMultilineTextualBlock(token,
WhitespaceOption.TRIM);
fileOverview = fileOverviewInfo.string;
token = fileOverviewInfo.token;
} else {
token = eatTokensUntilEOL(token);
}
if (!jsdocBuilder.recordFileOverview(fileOverview) ||
fileOverviewJSDocInfo != null) {
parser.addWarning("msg.jsdoc.fileoverview.extra",
stream.getLineno(), stream.getCharno());
}
continue retry;
case LICENSE:
case PRESERVE:
ExtractionInfo preserveInfo =
extractMultilineTextualBlock(token,
WhitespaceOption.PRESERVE);
String preserve = preserveInfo.string;
if (preserve.length() > 0) {
if (fileLevelJsDocBuilder != null) {
fileLevelJsDocBuilder.append(preserve);
}
}
token = preserveInfo.token;
continue retry;
case ENUM:
token = next();
lineno = stream.getLineno();
charno = stream.getCharno();
type = null;
if (token != JsDocToken.EOL && token != JsDocToken.EOC) {
type = createJSTypeExpression(
parseAndRecordTypeNode(token));
}
if (type == null) {
type = createJSTypeExpression(newStringNode("number"));
}
if (!jsdocBuilder.recordEnumParameterType(type)) {
parser.addWarning("msg.jsdoc.incompat.type", lineno, charno);
}
token = eatTokensUntilEOL(token);
continue retry;
case EXPORT:
if (!jsdocBuilder.recordExport()) {
parser.addWarning("msg.jsdoc.export",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case EXTERNS:
if (!jsdocBuilder.recordExterns()) {
parser.addWarning("msg.jsdoc.externs",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case JAVA_DISPATCH:
if (!jsdocBuilder.recordJavaDispatch()) {
parser.addWarning("msg.jsdoc.javadispatch",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case EXTENDS:
case IMPLEMENTS:
skipEOLs();
token = next();
lineno = stream.getLineno();
charno = stream.getCharno();
boolean matchingRc = false;
if (token == JsDocToken.LC) {
token = next();
matchingRc = true;
}
if (token == JsDocToken.STRING) {
Node typeNode = parseAndRecordTypeNameNode(
token, lineno, charno, matchingRc);
lineno = stream.getLineno();
charno = stream.getCharno();
typeNode = wrapNode(Token.BANG, typeNode);
if (typeNode != null && !matchingRc) {
typeNode.putBooleanProp(Node.BRACELESS_TYPE, true);
}
type = createJSTypeExpression(typeNode);
if (annotation == Annotation.EXTENDS) {
if (!jsdocBuilder.recordBaseType(type)) {
parser.addWarning(
"msg.jsdoc.incompat.type", lineno, charno);
}
} else {
Preconditions.checkState(
annotation == Annotation.IMPLEMENTS);
if (!jsdocBuilder.recordImplementedInterface(type)) {
parser.addWarning("msg.jsdoc.implements.duplicate",
lineno, charno);
}
}
token = next();
if (matchingRc) {
if (token != JsDocToken.RC) {
parser.addWarning("msg.jsdoc.missing.rc",
stream.getLineno(), stream.getCharno());
}
} else if (token != JsDocToken.EOL &&
token != JsDocToken.EOF && token != JsDocToken.EOC) {
parser.addWarning("msg.end.annotation.expected",
stream.getLineno(), stream.getCharno());
}
} else {
parser.addWarning("msg.no.type.name", lineno, charno);
}
token = eatTokensUntilEOL(token);
continue retry;
case HIDDEN:
if (!jsdocBuilder.recordHiddenness()) {
parser.addWarning("msg.jsdoc.hidden",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case LENDS:
skipEOLs();
matchingRc = false;
if (match(JsDocToken.LC)) {
token = next();
matchingRc = true;
}
if (match(JsDocToken.STRING)) {
token = next();
if (!jsdocBuilder.recordLends(stream.getString())) {
parser.addWarning("msg.jsdoc.lends.incompatible",
stream.getLineno(), stream.getCharno());
}
} else {
parser.addWarning("msg.jsdoc.lends.missing",
stream.getLineno(), stream.getCharno());
}
if (matchingRc && !match(JsDocToken.RC)) {
parser.addWarning("msg.jsdoc.missing.rc",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case MEANING:
ExtractionInfo meaningInfo =
extractMultilineTextualBlock(token);
String meaning = meaningInfo.string;
token = meaningInfo.token;
if (!jsdocBuilder.recordMeaning(meaning)) {
parser.addWarning("msg.jsdoc.meaning.extra",
stream.getLineno(), stream.getCharno());
}
continue retry;
case NO_ALIAS:
if (!jsdocBuilder.recordNoAlias()) {
parser.addWarning("msg.jsdoc.noalias",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case NO_COMPILE:
if (!jsdocBuilder.recordNoCompile()) {
parser.addWarning("msg.jsdoc.nocompile",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case NO_TYPE_CHECK:
if (!jsdocBuilder.recordNoTypeCheck()) {
parser.addWarning("msg.jsdoc.nocheck",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case NOT_IMPLEMENTED:
token = eatTokensUntilEOL();
continue retry;
case INHERIT_DOC:
case OVERRIDE:
if (!jsdocBuilder.recordOverride()) {
parser.addWarning("msg.jsdoc.override",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case THROWS:
skipEOLs();
token = next();
lineno = stream.getLineno();
charno = stream.getCharno();
type = null;
if (token == JsDocToken.LC) {
type = createJSTypeExpression(
parseAndRecordTypeNode(token));
if (type == null) {
// parsing error reported during recursive descent
// recovering parsing
token = eatTokensUntilEOL();
continue retry;
}
}
// *Update* the token to that after the type annotation.
token = current();
// Save the throw type.
jsdocBuilder.recordThrowType(type);
// Find the throw's description (if applicable).
if (jsdocBuilder.shouldParseDocumentation()) {
ExtractionInfo descriptionInfo =
extractMultilineTextualBlock(token);
String description = descriptionInfo.string;
if (description.length() > 0) {
jsdocBuilder.recordThrowDescription(type, description);
}
token = descriptionInfo.token;
} else {
token = eatTokensUntilEOL(token);
}
continue retry;
case PARAM:
skipEOLs();
token = next();
lineno = stream.getLineno();
charno = stream.getCharno();
type = null;
if (token == JsDocToken.LC) {
type = createJSTypeExpression(
parseAndRecordParamTypeNode(token));
if (type == null) {
// parsing error reported during recursive descent
// recovering parsing
token = eatTokensUntilEOL();
continue retry;
}
skipEOLs();
token = next();
lineno = stream.getLineno();
charno = stream.getCharno();
}
String name = null;
boolean isBracketedParam = JsDocToken.LB == token;
if (isBracketedParam) {
token = next();
}
if (JsDocToken.STRING != token) {
parser.addWarning("msg.missing.variable.name",
lineno, charno);
} else {
name = stream.getString();
if (isBracketedParam) {
token = next();
// Throw out JsDocToolkit's "default" parameter annotation.
// It makes no sense under our type system.
if (JsDocToken.EQUALS == token) {
token = next();
if (JsDocToken.STRING == token) {
token = next();
}
}
if (JsDocToken.RB != token) {
reportTypeSyntaxWarning("msg.jsdoc.missing.rb");
} else if (type != null) {
// Make the type expression optional, if it isn't
// already.
type = JSTypeExpression.makeOptionalArg(type);
}
}
// If the param name has a DOT in it, just throw it out
// quietly. We do not handle the JsDocToolkit method
// for handling properties of params.
if (name.indexOf('.') > -1) {
name = null;
} else if (!jsdocBuilder.recordParameter(name, type)) {
if (jsdocBuilder.hasParameter(name)) {
parser.addWarning("msg.dup.variable.name", name,
lineno, charno);
} else {
parser.addWarning("msg.jsdoc.incompat.type", name,
lineno, charno);
}
}
}
if (name == null) {
token = eatTokensUntilEOL(token);
continue retry;
}
jsdocBuilder.markName(name, lineno, charno);
// Find the parameter's description (if applicable).
if (jsdocBuilder.shouldParseDocumentation()) {
ExtractionInfo paramDescriptionInfo =
extractMultilineTextualBlock(token);
String paramDescription = paramDescriptionInfo.string;
if (paramDescription.length() > 0) {
jsdocBuilder.recordParameterDescription(name,
paramDescription);
}
token = paramDescriptionInfo.token;
} else {
token = eatTokensUntilEOL(token);
}
continue retry;
case PRESERVE_TRY:
if (!jsdocBuilder.recordPreserveTry()) {
parser.addWarning("msg.jsdoc.preservertry",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case PRIVATE:
if (!jsdocBuilder.recordVisibility(Visibility.PRIVATE)) {
parser.addWarning("msg.jsdoc.visibility.private",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case PROTECTED:
if (!jsdocBuilder.recordVisibility(Visibility.PROTECTED)) {
parser.addWarning("msg.jsdoc.visibility.protected",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case PUBLIC:
if (!jsdocBuilder.recordVisibility(Visibility.PUBLIC)) {
parser.addWarning("msg.jsdoc.visibility.public",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case NO_SHADOW:
if (!jsdocBuilder.recordNoShadow()) {
parser.addWarning("msg.jsdoc.noshadow",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case NO_SIDE_EFFECTS:
if (!jsdocBuilder.recordNoSideEffects()) {
parser.addWarning("msg.jsdoc.nosideeffects",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case MODIFIES:
token = parseModifiesTag(next());
continue retry;
case IMPLICIT_CAST:
if (!jsdocBuilder.recordImplicitCast()) {
parser.addWarning("msg.jsdoc.implicitcast",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case SEE:
if (jsdocBuilder.shouldParseDocumentation()) {
ExtractionInfo referenceInfo = extractSingleLineBlock();
String reference = referenceInfo.string;
if (reference.length() == 0) {
parser.addWarning("msg.jsdoc.seemissing",
stream.getLineno(), stream.getCharno());
} else {
jsdocBuilder.addReference(reference);
}
token = referenceInfo.token;
} else {
token = eatTokensUntilEOL(token);
}
continue retry;
case SUPPRESS:
token = parseSuppressTag(next());
continue retry;
case TEMPLATE:
ExtractionInfo templateInfo = extractSingleLineBlock();
String templateTypeName = templateInfo.string;
if (templateTypeName.length() == 0) {
parser.addWarning("msg.jsdoc.templatemissing",
stream.getLineno(), stream.getCharno());
} else if (!jsdocBuilder.recordTemplateTypeName(
templateTypeName)) {
parser.addWarning("msg.jsdoc.template.at.most.once",
stream.getLineno(), stream.getCharno());
}
token = templateInfo.token;
continue retry;
case VERSION:
ExtractionInfo versionInfo = extractSingleLineBlock();
String version = versionInfo.string;
if (version.length() == 0) {
parser.addWarning("msg.jsdoc.versionmissing",
stream.getLineno(), stream.getCharno());
} else {
if (!jsdocBuilder.recordVersion(version)) {
parser.addWarning("msg.jsdoc.extraversion",
stream.getLineno(), stream.getCharno());
}
}
token = versionInfo.token;
continue retry;
case DEFINE:
case RETURN:
case THIS:
case TYPE:
case TYPEDEF:
skipEOLs();
lineno = stream.getLineno();
charno = stream.getCharno();
token = next();
Node typeNode = parseAndRecordTypeNode(token, lineno, charno);
if (annotation == Annotation.THIS) {
typeNode = wrapNode(Token.BANG, typeNode);
if (typeNode != null && token != JsDocToken.LC) {
typeNode.putBooleanProp(Node.BRACELESS_TYPE, true);
}
}
type = createJSTypeExpression(typeNode);
if (type == null) {
// error reported during recursive descent
// recovering parsing
} else {
switch (annotation) {
case DEFINE:
if (!jsdocBuilder.recordDefineType(type)) {
parser.addWarning("msg.jsdoc.define",
lineno, charno);
}
break;
case RETURN:
if (!jsdocBuilder.recordReturnType(type)) {
parser.addWarning(
"msg.jsdoc.incompat.type", lineno, charno);
break;
}
// *Update* the token to that after the type annotation.
token = current();
// Find the return's description (if applicable).
if (jsdocBuilder.shouldParseDocumentation()) {
ExtractionInfo returnDescriptionInfo =
extractMultilineTextualBlock(token);
String returnDescription =
returnDescriptionInfo.string;
if (returnDescription.length() > 0) {
jsdocBuilder.recordReturnDescription(
returnDescription);
}
token = returnDescriptionInfo.token;
} else {
token = eatTokensUntilEOL(token);
}
continue retry;
case THIS:
if (!jsdocBuilder.recordThisType(type)) {
parser.addWarning(
"msg.jsdoc.incompat.type", lineno, charno);
}
break;
case TYPE:
if (!jsdocBuilder.recordType(type)) {
parser.addWarning(
"msg.jsdoc.incompat.type", lineno, charno);
}
break;
case TYPEDEF:
if (!jsdocBuilder.recordTypedef(type)) {
parser.addWarning(
"msg.jsdoc.incompat.type", lineno, charno);
}
break;
}
token = eatTokensUntilEOL();
}
continue retry;
}
}
}
break;
case EOC:
if (hasParsedFileOverviewDocInfo()) {
fileOverviewJSDocInfo = retrieveAndResetParsedJSDocInfo();
}
return true;
case EOF:
// discard any accumulated information
jsdocBuilder.build(null);
parser.addWarning("msg.unexpected.eof",
stream.getLineno(), stream.getCharno());
return false;
case EOL:
if (state == State.SEARCHING_NEWLINE) {
state = State.SEARCHING_ANNOTATION;
}
token = next();
continue retry;
default:
if (token == JsDocToken.STAR && state == State.SEARCHING_ANNOTATION) {
token = next();
continue retry;
} else {
state = State.SEARCHING_NEWLINE;
token = eatTokensUntilEOL();
continue retry;
}
}
// next token
token = next();
}
}
}
|