diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 9b421ed34c..377c5d3f46 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1702,8 +1702,9 @@ namespace ts { case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.JSDocRecordMember: - case SyntaxKind.JSDocPropertyTag: return bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property | ((node).questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes); + case SyntaxKind.JSDocPropertyTag: + return bindJSDocProperty(node); case SyntaxKind.PropertyAssignment: case SyntaxKind.ShorthandPropertyAssignment: return bindPropertyOrMethodOrAccessor(node, SymbolFlags.Property, SymbolFlags.PropertyExcludes); @@ -2085,6 +2086,10 @@ namespace ts { : declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes); } + function bindJSDocProperty(node: JSDocPropertyTag) { + return declareSymbolAndAddToSymbolTable(node, SymbolFlags.Property, SymbolFlags.PropertyExcludes); + } + // reachability checks function shouldReportErrorOnModuleDeclaration(node: ModuleDeclaration): boolean { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 9c26fdeb17..07509d0afc 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -406,7 +406,7 @@ namespace ts { visitNode(cbNode, (node).name) || visitNode(cbNode, (node).type); case SyntaxKind.JSDocTypeLiteral: - return visitNodes(cbNodes, (node).members); + return visitNodes(cbNodes, (node).jsDocPropertyTags); case SyntaxKind.JSDocPropertyTag: return visitNode(cbNode, (node).typeExpression) || visitNode(cbNode, (node).name); @@ -4113,9 +4113,9 @@ namespace ts { const isAsync = !!(node.flags & NodeFlags.Async); node.name = isGenerator && isAsync ? doInYieldAndAwaitContext(parseOptionalIdentifier) : - isGenerator ? doInYieldContext(parseOptionalIdentifier) : - isAsync ? doInAwaitContext(parseOptionalIdentifier) : - parseOptionalIdentifier(); + isGenerator ? doInYieldContext(parseOptionalIdentifier) : + isAsync ? doInAwaitContext(parseOptionalIdentifier) : + parseOptionalIdentifier(); fillSignature(SyntaxKind.ColonToken, /*yieldContext*/ isGenerator, /*awaitContext*/ isAsync, /*requireCompleteParameterList*/ false, node); node.body = parseFunctionBlock(/*allowYield*/ isGenerator, /*allowAwait*/ isAsync, /*ignoreMissingOpenBrace*/ false); @@ -6066,9 +6066,6 @@ namespace ts { Debug.assert(end <= content.length); let tags: NodeArray; - let currentParentJSDocTag: JSDocParentTag; - let currentParentJSDocTagEnd: number; - let result: JSDocComment; // Check for /** (JSDoc opening part) @@ -6125,10 +6122,6 @@ namespace ts { nextJSDocToken(); } - if (currentParentJSDocTag) { - finishCurrentParentTag(); - } - result = createJSDocComment(); }); @@ -6152,40 +6145,6 @@ namespace ts { } } - function finishCurrentParentTag(): void { - if (!currentParentJSDocTag) { - return; - } - - if (currentParentJSDocTag.kind === SyntaxKind.JSDocTypedefTag) { - const typedefTag = currentParentJSDocTag; - if (typedefTag.jsDocTypeTag) { - if (typedefTag.jsDocTypeTag.typeExpression.type.kind === SyntaxKind.JSDocTypeReference) { - const typeTagtype = typedefTag.jsDocTypeTag.typeExpression.type; - if ((typeTagtype.name.kind !== SyntaxKind.Identifier) || - (typeTagtype.name).text !== "Object") { - typedefTag.type = typedefTag.jsDocTypeTag.typeExpression.type; - } - } - else { - typedefTag.type = typedefTag.jsDocTypeTag.typeExpression.type; - } - } - if (!typedefTag.type && typedefTag.jsDocPropertyTags && typedefTag.jsDocPropertyTags.length > 0) { - const pos = typedefTag.jsDocPropertyTags[0].pos; - const end = typedefTag.jsDocPropertyTags[typedefTag.jsDocPropertyTags.length - 1].end; - const jsdocTypeLiteral = createNode(SyntaxKind.JSDocTypeLiteral, pos); - jsdocTypeLiteral.members = >[]; - addRange(jsdocTypeLiteral.members, typedefTag.jsDocPropertyTags); - typedefTag.type = finishNode(jsdocTypeLiteral, end); - } - } - - addTag(finishNode(currentParentJSDocTag, currentParentJSDocTagEnd)); - currentParentJSDocTag = undefined; - currentParentJSDocTagEnd = undefined; - } - function parseTag(): void { Debug.assert(token === SyntaxKind.AtToken); const atToken = createNode(SyntaxKind.AtToken, scanner.getTokenPos()); @@ -6198,34 +6157,23 @@ namespace ts { } const tag = handleTag(atToken, tagName) || handleUnknownTag(atToken, tagName); - if (!currentParentJSDocTag) { - addTag(tag); - } + addTag(tag); } function handleTag(atToken: Node, tagName: Identifier): JSDocTag { if (tagName) { switch (tagName.text) { case "param": - finishCurrentParentTag(); return handleParamTag(atToken, tagName); case "return": case "returns": - finishCurrentParentTag(); return handleReturnTag(atToken, tagName); case "template": - finishCurrentParentTag(); return handleTemplateTag(atToken, tagName); case "type": - // @typedef tag is allowed to have one @type tag, therefore seeing - // a @type tag may not indicate the end of the current parent tag. return handleTypeTag(atToken, tagName); case "typedef": - finishCurrentParentTag(); return handleTypedefTag(atToken, tagName); - case "property": - case "prop": - return handlePropertyTag(atToken, tagName); } } @@ -6251,25 +6199,6 @@ namespace ts { } } - function addToCurrentParentTag(tag: JSDocTag): void { - if (!currentParentJSDocTag) { - return; - } - switch (tag.kind) { - case SyntaxKind.JSDocPropertyTag: - if (!currentParentJSDocTag.jsDocPropertyTags) { - currentParentJSDocTag.jsDocPropertyTags = >[]; - } - currentParentJSDocTag.jsDocPropertyTags.push(tag); - break; - case SyntaxKind.JSDocTypeTag: - if (!currentParentJSDocTag.jsDocTypeTag) { - currentParentJSDocTag.jsDocTypeTag = tag; - } - break; - } - } - function tryParseTypeExpression(): JSDocTypeExpression { if (token !== SyntaxKind.OpenBraceToken) { return undefined; @@ -6345,32 +6274,14 @@ namespace ts { parseErrorAtPosition(tagName.pos, scanner.getTokenPos() - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text); } - let result = createNode(SyntaxKind.JSDocTypeTag, atToken.pos); + const result = createNode(SyntaxKind.JSDocTypeTag, atToken.pos); result.atToken = atToken; result.tagName = tagName; result.typeExpression = tryParseTypeExpression(); - result = finishNode(result); - - if (currentParentJSDocTag && currentParentJSDocTag.kind === SyntaxKind.JSDocTypedefTag) { - const parentTag = currentParentJSDocTag; - if (!parentTag.typeExpression && !parentTag.jsDocTypeTag) { - parentTag.jsDocTypeTag = result; - currentParentJSDocTagEnd = scanner.getStartPos(); - return result; - } - } - // If this @type tag is not part of the current parent tag, then - // it denotes the end of the current parent tag. - finishCurrentParentTag(); - return result; + return finishNode(result); } function handlePropertyTag(atToken: Node, tagName: Identifier): JSDocPropertyTag { - if (!currentParentJSDocTag) { - parseErrorAtPosition(tagName.pos, scanner.getTokenPos() - tagName.pos, Diagnostics._0_tag_cannot_be_used_independently_as_a_top_level_JSDoc_tag, tagName.text); - return undefined; - } - const typeExpression = tryParseTypeExpression(); skipWhitespace(); const name = parseJSDocIdentifierName(); @@ -6379,17 +6290,12 @@ namespace ts { return undefined; } - let result = createNode(SyntaxKind.JSDocPropertyTag, atToken.pos); + const result = createNode(SyntaxKind.JSDocPropertyTag, atToken.pos); result.atToken = atToken; result.tagName = tagName; result.name = name; result.typeExpression = typeExpression; - result.type = typeExpression.type; - result = finishNode(result); - - addToCurrentParentTag(result); - currentParentJSDocTagEnd = scanner.getStartPos(); - return undefined; + return finishNode(result); } function handleTypedefTag(atToken: Node, tagName: Identifier): JSDocTypedefTag { @@ -6413,32 +6319,99 @@ namespace ts { } } - const result = createNode(SyntaxKind.JSDocTypedefTag, atToken.pos); - result.atToken = atToken; - result.tagName = tagName; - result.name = name; - result.typeExpression = typeExpression; + const typedefTag = createNode(SyntaxKind.JSDocTypedefTag, atToken.pos); + typedefTag.atToken = atToken; + typedefTag.tagName = tagName; + typedefTag.name = name; + typedefTag.typeExpression = typeExpression; - if (typeExpression && typeExpression.type.kind === SyntaxKind.JSDocTypeReference) { - const jsDocTypeReference = typeExpression.type; - if (jsDocTypeReference.name.kind === SyntaxKind.Identifier) { - const name = jsDocTypeReference.name; - if (name.text === "Object") { - currentParentJSDocTag = result; + if (typeExpression) { + if (typeExpression.type.kind === SyntaxKind.JSDocTypeReference) { + const jsDocTypeReference = typeExpression.type; + if (jsDocTypeReference.name.kind === SyntaxKind.Identifier) { + const name = jsDocTypeReference.name; + if (name.text === "Object") { + typedefTag.type = scanChildTags(); + } } } + if (!typedefTag.type) { + typedefTag.type = typeExpression.type; + } } - else if (!typeExpression) { - currentParentJSDocTag = result; + else { + typedefTag.type = scanChildTags(); } - if (!currentParentJSDocTag) { - result.type = result.typeExpression.type; - return finishNode(result); + return finishNode(typedefTag); + + function scanChildTags(): JSDocTypeLiteral { + const jsDocTypeLiteral = createNode(SyntaxKind.JSDocTypeLiteral, scanner.getStartPos()); + let resumePos = scanner.getStartPos(); + let canParseTag = true; + let seenAsterisk = false; + let parentTagTerminated = false; + + while (token !== SyntaxKind.EndOfFileToken && !parentTagTerminated) { + nextJSDocToken(); + switch (token) { + case SyntaxKind.AtToken: + if (canParseTag) { + parentTagTerminated = !tryParseChildTag(jsDocTypeLiteral); + } + seenAsterisk = false; + break; + case SyntaxKind.NewLineTrivia: + resumePos = scanner.getStartPos() - 1; + canParseTag = true; + seenAsterisk = false; + break; + case SyntaxKind.AsteriskToken: + if (seenAsterisk) { + canParseTag = false; + } + seenAsterisk = true; + break; + case SyntaxKind.Identifier: + canParseTag = false; + case SyntaxKind.EndOfFileToken: + break; + } + } + scanner.setTextPos(resumePos); + return finishNode(jsDocTypeLiteral); + } + } + + function tryParseChildTag(parentTag: JSDocTypeLiteral): boolean { + Debug.assert(token === SyntaxKind.AtToken); + const atToken = createNode(SyntaxKind.AtToken, scanner.getStartPos()); + atToken.end = scanner.getTextPos(); + nextJSDocToken(); + + const tagName = parseJSDocIdentifierName(); + if (!tagName) { + return false; } - currentParentJSDocTagEnd = scanner.getStartPos(); - return undefined; + switch (tagName.text) { + case "type": + if (parentTag.jsDocTypeTag) { + // already has a @type tag, terminate the parent tag now. + return false; + } + parentTag.jsDocTypeTag = handleTypeTag(atToken, tagName); + return true; + case "prop": + case "property": + if (!parentTag.jsDocPropertyTags) { + parentTag.jsDocPropertyTags = >[]; + } + const propertyTag = handlePropertyTag(atToken, tagName); + parentTag.jsDocPropertyTags.push(propertyTag); + return true; + } + return false; } function handleTemplateTag(atToken: Node, tagName: Identifier): JSDocTemplateTag { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b472aa92e6..1b9af02a8d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1517,29 +1517,22 @@ namespace ts { } // @kind(SyntaxKind.JSDocTypedefTag) - export interface JSDocTypedefTag extends JSDocTag, Declaration, JSDocParentTag { + export interface JSDocTypedefTag extends JSDocTag, Declaration { name: Identifier; typeExpression?: JSDocTypeExpression; type: JSDocType; } - export interface JSDocParentTag extends JSDocTag { - jsDocPropertyTags?: NodeArray; - jsDocTypeTag?: JSDocTypeTag; - } - // @kind(SyntaxKind.JSDocPropertyTag) export interface JSDocPropertyTag extends JSDocTag, TypeElement { name: Identifier; typeExpression: JSDocTypeExpression; - // Add a "type" property here to make the interface compatible - // with the "VariableLikeDeclaration" definition - type: TypeNode; } // @kind(SyntaxKind.JSDocTypeLiteral) export interface JSDocTypeLiteral extends JSDocType { - members: NodeArray; + jsDocPropertyTags?: NodeArray; + jsDocTypeTag?: JSDocTypeTag; } // @kind(SyntaxKind.JSDocParameterTag)