Skip to content

Commit

Permalink
Change how typedef tag is parsed
Browse files Browse the repository at this point in the history
  • Loading branch information
zhengbli committed May 26, 2016
1 parent 5f9fa69 commit 95fd3a1
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 126 deletions.
209 changes: 93 additions & 116 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ namespace ts {
visitNode(cbNode, (<JSDocTypedefTag>node).name) ||
visitNode(cbNode, (<JSDocTypedefTag>node).type);
case SyntaxKind.JSDocTypeLiteral:
return visitNodes(cbNodes, (<JSDocTypeLiteral>node).members);
return visitNodes(cbNodes, (<JSDocTypeLiteral>node).jsDocPropertyTags);
case SyntaxKind.JSDocPropertyTag:
return visitNode(cbNode, (<JSDocPropertyTag>node).typeExpression) ||
visitNode(cbNode, (<JSDocPropertyTag>node).name);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -6066,9 +6066,6 @@ namespace ts {
Debug.assert(end <= content.length);

let tags: NodeArray<JSDocTag>;
let currentParentJSDocTag: JSDocParentTag;
let currentParentJSDocTagEnd: number;

let result: JSDocComment;

// Check for /** (JSDoc opening part)
Expand Down Expand Up @@ -6125,10 +6122,6 @@ namespace ts {
nextJSDocToken();
}

if (currentParentJSDocTag) {
finishCurrentParentTag();
}

result = createJSDocComment();

});
Expand All @@ -6152,40 +6145,6 @@ namespace ts {
}
}

function finishCurrentParentTag(): void {
if (!currentParentJSDocTag) {
return;
}

if (currentParentJSDocTag.kind === SyntaxKind.JSDocTypedefTag) {
const typedefTag = <JSDocTypedefTag>currentParentJSDocTag;
if (typedefTag.jsDocTypeTag) {
if (typedefTag.jsDocTypeTag.typeExpression.type.kind === SyntaxKind.JSDocTypeReference) {
const typeTagtype = <JSDocTypeReference>typedefTag.jsDocTypeTag.typeExpression.type;
if ((typeTagtype.name.kind !== SyntaxKind.Identifier) ||
(<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 = <JSDocTypeLiteral>createNode(SyntaxKind.JSDocTypeLiteral, pos);
jsdocTypeLiteral.members = <NodeArray<JSDocPropertyTag>>[];
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());
Expand All @@ -6198,30 +6157,22 @@ 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":
Expand Down Expand Up @@ -6251,25 +6202,6 @@ namespace ts {
}
}

function addToCurrentParentTag(tag: JSDocTag): void {
if (!currentParentJSDocTag) {
return;
}
switch (tag.kind) {
case SyntaxKind.JSDocPropertyTag:
if (!currentParentJSDocTag.jsDocPropertyTags) {
currentParentJSDocTag.jsDocPropertyTags = <NodeArray<JSDocPropertyTag>>[];
}
currentParentJSDocTag.jsDocPropertyTags.push(<JSDocPropertyTag>tag);
break;
case SyntaxKind.JSDocTypeTag:
if (!currentParentJSDocTag.jsDocTypeTag) {
currentParentJSDocTag.jsDocTypeTag = <JSDocTypeTag>tag;
}
break;
}
}

function tryParseTypeExpression(): JSDocTypeExpression {
if (token !== SyntaxKind.OpenBraceToken) {
return undefined;
Expand Down Expand Up @@ -6349,28 +6281,10 @@ namespace ts {
result.atToken = atToken;
result.tagName = tagName;
result.typeExpression = tryParseTypeExpression();
result = finishNode(result);

if (currentParentJSDocTag && currentParentJSDocTag.kind === SyntaxKind.JSDocTypedefTag) {
const parentTag = <JSDocTypedefTag>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();
Expand All @@ -6384,12 +6298,7 @@ namespace ts {
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 {
Expand All @@ -6413,32 +6322,100 @@ namespace ts {
}
}

const result = <JSDocTypedefTag>createNode(SyntaxKind.JSDocTypedefTag, atToken.pos);
result.atToken = atToken;
result.tagName = tagName;
result.name = name;
result.typeExpression = typeExpression;
const typedefTag = <JSDocTypedefTag>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 = <JSDocTypeReference>typeExpression.type;
if (jsDocTypeReference.name.kind === SyntaxKind.Identifier) {
const name = <Identifier>jsDocTypeReference.name;
if (name.text === "Object") {
currentParentJSDocTag = result;
if (typeExpression) {
if (typeExpression.type.kind === SyntaxKind.JSDocTypeReference) {
const jsDocTypeReference = <JSDocTypeReference>typeExpression.type;
if (jsDocTypeReference.name.kind === SyntaxKind.Identifier) {
const name = <Identifier>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 = <JSDocTypeLiteral>createNode(SyntaxKind.JSDocTypeLiteral, scanner.getStartPos());
let resumePos = scanner.getStartPos();
let canParseTag = true;
let seenAsterisk = false;
let parentTagTerminated = false;

nextJSDocToken();
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);
}
}

currentParentJSDocTagEnd = scanner.getStartPos();
return undefined;
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;
}

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 = <NodeArray<JSDocPropertyTag>>[];
}
const propertyTag = handlePropertyTag(atToken, tagName);
parentTag.jsDocPropertyTags.push(propertyTag);
return true;
}
return false;
}

function handleTemplateTag(atToken: Node, tagName: Identifier): JSDocTemplateTag {
Expand Down
13 changes: 3 additions & 10 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<JSDocPropertyTag>;
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<TypeElement>;
jsDocPropertyTags?: NodeArray<JSDocPropertyTag>;
jsDocTypeTag?: JSDocTypeTag;
}

// @kind(SyntaxKind.JSDocParameterTag)
Expand Down

0 comments on commit 95fd3a1

Please sign in to comment.