Skip to content

Commit

Permalink
Merge pull request #10671 from Microsoft/new-jsdoc-parser
Browse files Browse the repository at this point in the history
Remove service's jsdoc parser and enhance parser's jsdoc parser
  • Loading branch information
sandersn authored Sep 15, 2016
2 parents 9d8d2b6 + f8f244f commit 955f2f2
Show file tree
Hide file tree
Showing 68 changed files with 1,288 additions and 912 deletions.
6 changes: 2 additions & 4 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ namespace ts {
Debug.assert(node.parent.kind === SyntaxKind.JSDocFunctionType);
let functionType = <JSDocFunctionType>node.parent;
let index = indexOf(functionType.parameters, node);
return "p" + index;
return "arg" + index;
case SyntaxKind.JSDocTypedefTag:
const parentNode = node.parent && node.parent.parent;
let nameFromParentNode: string;
Expand Down Expand Up @@ -540,9 +540,7 @@ namespace ts {
// because the scope of JsDocComment should not be affected by whether the current node is a
// container or not.
if (isInJavaScriptFile(node) && node.jsDocComments) {
for (const jsDocComment of node.jsDocComments) {
bind(jsDocComment);
}
forEach(node.jsDocComments, bind);
}
if (checkUnreachable(node)) {
forEachChild(node, bind);
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5653,12 +5653,13 @@ namespace ts {
case SyntaxKind.JSDocThisType:
case SyntaxKind.JSDocOptionalType:
return getTypeFromTypeNode((<ParenthesizedTypeNode | JSDocTypeReferencingNode>node).type);
case SyntaxKind.JSDocRecordType:
return getTypeFromTypeNode((node as JSDocRecordType).literal);
case SyntaxKind.FunctionType:
case SyntaxKind.ConstructorType:
case SyntaxKind.TypeLiteral:
case SyntaxKind.JSDocTypeLiteral:
case SyntaxKind.JSDocFunctionType:
case SyntaxKind.JSDocRecordType:
return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node, aliasSymbol, aliasTypeArguments);
// This function assumes that an identifier or qualified name is a type expression
// Callers should first ensure this by calling isTypeNode
Expand Down
412 changes: 271 additions & 141 deletions src/compiler/parser.ts

Large diffs are not rendered by default.

48 changes: 27 additions & 21 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1759,40 +1759,46 @@ namespace ts {
}

startPos = pos;

// Eat leading whitespace
let ch = text.charCodeAt(pos);
while (pos < end) {
ch = text.charCodeAt(pos);
if (isWhiteSpaceSingleLine(ch)) {
pos++;
}
else {
break;
}
}
tokenPos = pos;

const ch = text.charCodeAt(pos);
switch (ch) {
case CharacterCodes.tab:
case CharacterCodes.verticalTab:
case CharacterCodes.formFeed:
case CharacterCodes.space:
while (pos < end && isWhiteSpaceSingleLine(text.charCodeAt(pos))) {
pos++;
}
return token = SyntaxKind.WhitespaceTrivia;
case CharacterCodes.at:
return pos += 1, token = SyntaxKind.AtToken;
pos++;
return token = SyntaxKind.AtToken;
case CharacterCodes.lineFeed:
case CharacterCodes.carriageReturn:
return pos += 1, token = SyntaxKind.NewLineTrivia;
pos++;
return token = SyntaxKind.NewLineTrivia;
case CharacterCodes.asterisk:
return pos += 1, token = SyntaxKind.AsteriskToken;
pos++;
return token = SyntaxKind.AsteriskToken;
case CharacterCodes.openBrace:
return pos += 1, token = SyntaxKind.OpenBraceToken;
pos++;
return token = SyntaxKind.OpenBraceToken;
case CharacterCodes.closeBrace:
return pos += 1, token = SyntaxKind.CloseBraceToken;
pos++;
return token = SyntaxKind.CloseBraceToken;
case CharacterCodes.openBracket:
return pos += 1, token = SyntaxKind.OpenBracketToken;
pos++;
return token = SyntaxKind.OpenBracketToken;
case CharacterCodes.closeBracket:
return pos += 1, token = SyntaxKind.CloseBracketToken;
pos++;
return token = SyntaxKind.CloseBracketToken;
case CharacterCodes.equals:
return pos += 1, token = SyntaxKind.EqualsToken;
pos++;
return token = SyntaxKind.EqualsToken;
case CharacterCodes.comma:
return pos += 1, token = SyntaxKind.CommaToken;
pos++;
return token = SyntaxKind.CommaToken;
}

if (isIdentifierStart(ch, ScriptTarget.Latest)) {
Expand Down
12 changes: 9 additions & 3 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ namespace ts {
parent?: Node; // Parent node (initialized by binding)
/* @internal */ original?: Node; // The original node if this is an updated node.
/* @internal */ startsOnNewLine?: boolean; // Whether a synthesized node should start on a new line (used by transforms).
/* @internal */ jsDocComments?: JSDocComment[]; // JSDoc for the node, if it has any. Only for .js files.
/* @internal */ jsDocComments?: JSDoc[]; // JSDoc for the node, if it has any.
/* @internal */ symbol?: Symbol; // Symbol declared by node (initialized by binding)
/* @internal */ locals?: SymbolTable; // Locals associated with node (initialized by binding)
/* @internal */ nextContainer?: Node; // Next container in declaration order (initialized by binding)
Expand Down Expand Up @@ -1555,7 +1555,7 @@ namespace ts {

// @kind(SyntaxKind.JSDocRecordType)
export interface JSDocRecordType extends JSDocType, TypeLiteralNode {
members: NodeArray<JSDocRecordMember>;
literal: TypeLiteralNode;
}

// @kind(SyntaxKind.JSDocTypeReference)
Expand Down Expand Up @@ -1603,14 +1603,16 @@ namespace ts {
}

// @kind(SyntaxKind.JSDocComment)
export interface JSDocComment extends Node {
export interface JSDoc extends Node {
tags: NodeArray<JSDocTag>;
comment: string | undefined;
}

// @kind(SyntaxKind.JSDocTag)
export interface JSDocTag extends Node {
atToken: Node;
tagName: Identifier;
comment: string | undefined;
}

// @kind(SyntaxKind.JSDocTemplateTag)
Expand Down Expand Up @@ -1649,9 +1651,13 @@ namespace ts {

// @kind(SyntaxKind.JSDocParameterTag)
export interface JSDocParameterTag extends JSDocTag {
/** the parameter name, if provided *before* the type (TypeScript-style) */
preParameterName?: Identifier;
typeExpression?: JSDocTypeExpression;
/** the parameter name, if provided *after* the type (JSDoc-standard) */
postParameterName?: Identifier;
/** the parameter name, regardless of the location it was provided */
parameterName: Identifier;
isBracketed: boolean;
}

Expand Down
150 changes: 115 additions & 35 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1426,39 +1426,75 @@ namespace ts {
return undefined;
}

const jsDocComments = getJSDocComments(node, checkParentVariableStatement);
if (!jsDocComments) {
const jsDocTags = getJSDocTags(node, checkParentVariableStatement);
if (!jsDocTags) {
return undefined;
}

for (const jsDocComment of jsDocComments) {
for (const tag of jsDocComment.tags) {
if (tag.kind === kind) {
return tag;
}
for (const tag of jsDocTags) {
if (tag.kind === kind) {
return tag;
}
}
}

function getJSDocComments(node: Node, checkParentVariableStatement: boolean): JSDocComment[] {
if (node.jsDocComments) {
return node.jsDocComments;
}
// Try to recognize this pattern when node is initializer of variable declaration and JSDoc comments are on containing variable statement.
// /**
// * @param {number} name
// * @returns {number}
// */
// var x = function(name) { return name.length; }
function append<T>(previous: T[] | undefined, additional: T[] | undefined): T[] | undefined {
if (additional) {
if (!previous) {
previous = [];
}
for (const x of additional) {
previous.push(x);
}
}
return previous;
}

export function getJSDocComments(node: Node, checkParentVariableStatement: boolean): string[] {
return getJSDocs(node, checkParentVariableStatement, docs => map(docs, doc => doc.comment), tags => map(tags, tag => tag.comment));
}

function getJSDocTags(node: Node, checkParentVariableStatement: boolean): JSDocTag[] {
return getJSDocs(node, checkParentVariableStatement, docs => {
const result: JSDocTag[] = [];
for (const doc of docs) {
if (doc.tags) {
result.push(...doc.tags);
}
}
return result;
}, tags => tags);
}

function getJSDocs<T>(node: Node, checkParentVariableStatement: boolean, getDocs: (docs: JSDoc[]) => T[], getTags: (tags: JSDocTag[]) => T[]): T[] {
// TODO: Get rid of getJsDocComments and friends (note the lowercase 's' in Js)
// TODO: A lot of this work should be cached, maybe. I guess it's only used in services right now...
let result: T[] = undefined;
// prepend documentation from parent sources
if (checkParentVariableStatement) {
// Try to recognize this pattern when node is initializer of variable declaration and JSDoc comments are on containing variable statement.
// /**
// * @param {number} name
// * @returns {number}
// */
// var x = function(name) { return name.length; }
const isInitializerOfVariableDeclarationInStatement =
node.parent.kind === SyntaxKind.VariableDeclaration &&
(<VariableDeclaration>node.parent).initializer === node &&
isVariableLike(node.parent) &&
(node.parent).initializer === node &&
node.parent.parent.parent.kind === SyntaxKind.VariableStatement;
const isVariableOfVariableDeclarationStatement = isVariableLike(node) &&
node.parent.parent.kind === SyntaxKind.VariableStatement;

const variableStatementNode = isInitializerOfVariableDeclarationInStatement ? node.parent.parent.parent : undefined;
const variableStatementNode =
isInitializerOfVariableDeclarationInStatement ? node.parent.parent.parent :
isVariableOfVariableDeclarationStatement ? node.parent.parent :
undefined;
if (variableStatementNode) {
return variableStatementNode.jsDocComments;
result = append(result, getJSDocs(variableStatementNode, checkParentVariableStatement, getDocs, getTags));
}
if (node.kind === SyntaxKind.ModuleDeclaration &&
node.parent && node.parent.kind === SyntaxKind.ModuleDeclaration) {
result = append(result, getJSDocs(node.parent, checkParentVariableStatement, getDocs, getTags));
}

// Also recognize when the node is the RHS of an assignment expression
Expand All @@ -1469,16 +1505,62 @@ namespace ts {
(parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken &&
parent.parent.kind === SyntaxKind.ExpressionStatement;
if (isSourceOfAssignmentExpressionStatement) {
return parent.parent.jsDocComments;
result = append(result, getJSDocs(parent.parent, checkParentVariableStatement, getDocs, getTags));
}

const isPropertyAssignmentExpression = parent && parent.kind === SyntaxKind.PropertyAssignment;
if (isPropertyAssignmentExpression) {
return parent.jsDocComments;
result = append(result, getJSDocs(parent, checkParentVariableStatement, getDocs, getTags));
}

// Pull parameter comments from declaring function as well
if (node.kind === SyntaxKind.Parameter) {
const paramTags = getJSDocParameterTag(node as ParameterDeclaration, checkParentVariableStatement);
if (paramTags) {
result = append(result, getTags(paramTags));
}
}
}

return undefined;
if (isVariableLike(node) && node.initializer) {
result = append(result, getJSDocs(node.initializer, /*checkParentVariableStatement*/ false, getDocs, getTags));
}

if (node.jsDocComments) {
if (result) {
result = append(result, getDocs(node.jsDocComments));
}
else {
return getDocs(node.jsDocComments);
}
}

return result;
}

function getJSDocParameterTag(param: ParameterDeclaration, checkParentVariableStatement: boolean): JSDocTag[] {
const func = param.parent as FunctionLikeDeclaration;
const tags = getJSDocTags(func, checkParentVariableStatement);
if (!param.name) {
// this is an anonymous jsdoc param from a `function(type1, type2): type3` specification
const i = func.parameters.indexOf(param);
const paramTags = filter(tags, tag => tag.kind === SyntaxKind.JSDocParameterTag);
if (paramTags && 0 <= i && i < paramTags.length) {
return [paramTags[i]];
}
}
else if (param.name.kind === SyntaxKind.Identifier) {
const name = (param.name as Identifier).text;
const paramTags = filter(tags, tag => tag.kind === SyntaxKind.JSDocParameterTag && (tag as JSDocParameterTag).parameterName.text === name);
if (paramTags) {
return paramTags;
}
}
else {
// TODO: it's a destructured parameter, so it should look up an "object type" series of multiple lines
// But multi-line object types aren't supported yet either
return undefined;
}
}

export function getJSDocTypeTag(node: Node): JSDocTypeTag {
Expand All @@ -1499,17 +1581,15 @@ namespace ts {
// annotation.
const parameterName = (<Identifier>parameter.name).text;

const jsDocComments = getJSDocComments(parameter.parent, /*checkParentVariableStatement*/ true);
if (jsDocComments) {
for (const jsDocComment of jsDocComments) {
for (const tag of jsDocComment.tags) {
if (tag.kind === SyntaxKind.JSDocParameterTag) {
const parameterTag = <JSDocParameterTag>tag;
const name = parameterTag.preParameterName || parameterTag.postParameterName;
if (name.text === parameterName) {
return parameterTag;
}
}
const jsDocTags = getJSDocTags(parameter.parent, /*checkParentVariableStatement*/ true);
if (!jsDocTags) {
return undefined;
}
for (const tag of jsDocTags) {
if (tag.kind === SyntaxKind.JSDocParameterTag) {
const parameterTag = <JSDocParameterTag>tag;
if (parameterTag.parameterName.text === parameterName) {
return parameterTag;
}
}
}
Expand Down
Loading

0 comments on commit 955f2f2

Please sign in to comment.