Skip to content

Commit bffde58

Browse files
author
Andy
authored
Improve performance of JSDoc tag utilities (microsoft#16836)
* Improve performance of JSDoc tag utilities * Use emptyArray instead of null, and address PR comments
1 parent 8c3f5e2 commit bffde58

File tree

5 files changed

+52
-59
lines changed

5 files changed

+52
-59
lines changed

src/compiler/checker.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ namespace ts {
5656
let enumCount = 0;
5757
let symbolInstantiationDepth = 0;
5858

59-
const emptyArray: any[] = [];
6059
const emptySymbols = createSymbolTable();
6160

6261
const compilerOptions = host.getCompilerOptions();

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ namespace ts {
516516
/* @internal */ original?: Node; // The original node if this is an updated node.
517517
/* @internal */ startsOnNewLine?: boolean; // Whether a synthesized node should start on a new line (used by transforms).
518518
/* @internal */ jsDoc?: JSDoc[]; // JSDoc that directly precedes this node
519-
/* @internal */ jsDocCache?: (JSDoc | JSDocTag)[]; // All JSDoc that applies to the node, including parent docs and @param tags
519+
/* @internal */ jsDocCache?: JSDocTag[]; // Cache for getJSDocTags
520520
/* @internal */ symbol?: Symbol; // Symbol declared by node (initialized by binding)
521521
/* @internal */ locals?: SymbolTable; // Locals associated with node (initialized by binding)
522522
/* @internal */ nextContainer?: Node; // Next container in declaration order (initialized by binding)

src/compiler/utilities.ts

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
/* @internal */
44
namespace ts {
5+
export const emptyArray: never[] = [] as never[];
6+
57
export const externalHelpersModuleNameText = "tslib";
68

79
export interface ReferencePathMatchResult {
@@ -1445,39 +1447,37 @@ namespace ts {
14451447
(<JSDocFunctionType>node).parameters[0].type.kind === SyntaxKind.JSDocConstructorType;
14461448
}
14471449

1448-
export function getCommentsFromJSDoc(node: Node): string[] {
1449-
return map(getJSDocs(node), doc => doc.comment);
1450-
}
1451-
1452-
export function hasJSDocParameterTags(node: FunctionLikeDeclaration | SignatureDeclaration) {
1453-
const parameterTags = getJSDocTags(node, SyntaxKind.JSDocParameterTag);
1454-
return parameterTags && parameterTags.length > 0;
1450+
export function hasJSDocParameterTags(node: FunctionLikeDeclaration | SignatureDeclaration): boolean {
1451+
return !!getFirstJSDocTag(node, SyntaxKind.JSDocParameterTag);
14551452
}
14561453

1457-
function getJSDocTags(node: Node, kind: SyntaxKind): JSDocTag[] {
1458-
return flatMap(getJSDocs(node), doc =>
1459-
doc.kind === SyntaxKind.JSDocComment
1460-
? filter((doc as JSDoc).tags, tag => tag.kind === kind)
1461-
: doc.kind === kind && doc);
1454+
function getFirstJSDocTag(node: Node, kind: SyntaxKind): JSDocTag | undefined {
1455+
const tags = getJSDocTags(node);
1456+
return find(tags, doc => doc.kind === kind);
14621457
}
14631458

1464-
function getFirstJSDocTag(node: Node, kind: SyntaxKind): JSDocTag {
1465-
return node && firstOrUndefined(getJSDocTags(node, kind));
1466-
}
1467-
1468-
export function getJSDocs(node: Node): (JSDoc | JSDocTag)[] {
1459+
export function getAllJSDocs(node: Node): (JSDoc | JSDocTag)[] {
14691460
if (isJSDocTypedefTag(node)) {
14701461
return [node.parent];
14711462
}
1463+
return getJSDocCommentsAndTags(node);
1464+
}
14721465

1473-
let cache: (JSDoc | JSDocTag)[] = node.jsDocCache;
1474-
if (!cache) {
1475-
getJSDocsWorker(node);
1476-
node.jsDocCache = cache;
1466+
export function getJSDocTags(node: Node): JSDocTag[] | undefined {
1467+
let tags = node.jsDocCache;
1468+
// If cache is 'null', that means we did the work of searching for JSDoc tags and came up with nothing.
1469+
if (tags === undefined) {
1470+
node.jsDocCache = tags = flatMap(getJSDocCommentsAndTags(node), j => isJSDoc(j) ? j.tags : j);
14771471
}
1478-
return cache;
1472+
return tags;
1473+
}
1474+
1475+
function getJSDocCommentsAndTags(node: Node): (JSDoc | JSDocTag)[] {
1476+
let result: Array<JSDoc | JSDocTag> | undefined;
1477+
getJSDocCommentsAndTagsWorker(node);
1478+
return result || emptyArray;
14791479

1480-
function getJSDocsWorker(node: Node) {
1480+
function getJSDocCommentsAndTagsWorker(node: Node): void {
14811481
const parent = node.parent;
14821482
// Try to recognize this pattern when node is initializer of variable declaration and JSDoc comments are on containing variable statement.
14831483
// /**
@@ -1496,7 +1496,7 @@ namespace ts {
14961496
isVariableOfVariableDeclarationStatement ? parent.parent :
14971497
undefined;
14981498
if (variableStatementNode) {
1499-
getJSDocsWorker(variableStatementNode);
1499+
getJSDocCommentsAndTagsWorker(variableStatementNode);
15001500
}
15011501

15021502
// Also recognize when the node is the RHS of an assignment expression
@@ -1506,43 +1506,51 @@ namespace ts {
15061506
(parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken &&
15071507
parent.parent.kind === SyntaxKind.ExpressionStatement;
15081508
if (isSourceOfAssignmentExpressionStatement) {
1509-
getJSDocsWorker(parent.parent);
1509+
getJSDocCommentsAndTagsWorker(parent.parent);
15101510
}
15111511

15121512
const isModuleDeclaration = node.kind === SyntaxKind.ModuleDeclaration &&
15131513
parent && parent.kind === SyntaxKind.ModuleDeclaration;
15141514
const isPropertyAssignmentExpression = parent && parent.kind === SyntaxKind.PropertyAssignment;
15151515
if (isModuleDeclaration || isPropertyAssignmentExpression) {
1516-
getJSDocsWorker(parent);
1516+
getJSDocCommentsAndTagsWorker(parent);
15171517
}
15181518

15191519
// Pull parameter comments from declaring function as well
15201520
if (node.kind === SyntaxKind.Parameter) {
1521-
cache = concatenate(cache, getJSDocParameterTags(node as ParameterDeclaration));
1521+
result = addRange(result, getJSDocParameterTags(node as ParameterDeclaration));
15221522
}
15231523

15241524
if (isVariableLike(node) && node.initializer) {
1525-
cache = concatenate(cache, node.initializer.jsDoc);
1525+
result = addRange(result, node.initializer.jsDoc);
15261526
}
15271527

1528-
cache = concatenate(cache, node.jsDoc);
1528+
result = addRange(result, node.jsDoc);
15291529
}
15301530
}
15311531

1532-
export function getJSDocParameterTags(param: ParameterDeclaration): JSDocParameterTag[] {
1532+
export function getJSDocParameterTags(param: ParameterDeclaration): JSDocParameterTag[] | undefined {
15331533
const func = param.parent;
1534-
const tags = getJSDocTags(func, SyntaxKind.JSDocParameterTag) as JSDocParameterTag[];
1534+
const tags = getJSDocTags(func);
1535+
if (!tags) return undefined;
1536+
15351537
if (!param.name) {
15361538
// this is an anonymous jsdoc param from a `function(type1, type2): type3` specification
1537-
const i = func.parameters.indexOf(param);
1538-
const paramTags = filter(tags, tag => tag.kind === SyntaxKind.JSDocParameterTag);
1539-
if (paramTags && 0 <= i && i < paramTags.length) {
1540-
return [paramTags[i]];
1539+
const paramIndex = func.parameters.indexOf(param);
1540+
Debug.assert(paramIndex !== -1);
1541+
let curParamIndex = 0;
1542+
for (const tag of tags) {
1543+
if (isJSDocParameterTag(tag)) {
1544+
if (curParamIndex === paramIndex) {
1545+
return [tag];
1546+
}
1547+
curParamIndex++;
1548+
}
15411549
}
15421550
}
15431551
else if (param.name.kind === SyntaxKind.Identifier) {
15441552
const name = (param.name as Identifier).text;
1545-
return filter(tags, tag => tag.kind === SyntaxKind.JSDocParameterTag && tag.name.text === name);
1553+
return tags.filter((tag): tag is JSDocParameterTag => isJSDocParameterTag(tag) && tag.name.text === name) as JSDocParameterTag[];
15461554
}
15471555
else {
15481556
// TODO: it's a destructured parameter, so it should look up an "object type" series of multiple lines

src/services/jsDoc.ts

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,14 @@ namespace ts.JsDoc {
5353
// from Array<T> - Array<string> and Array<number>
5454
const documentationComment = <SymbolDisplayPart[]>[];
5555
forEachUnique(declarations, declaration => {
56-
const comments = getCommentsFromJSDoc(declaration);
57-
if (!comments) {
58-
return;
59-
}
60-
for (const comment of comments) {
61-
if (comment) {
56+
forEach(getAllJSDocs(declaration), doc => {
57+
if (doc.comment) {
6258
if (documentationComment.length) {
6359
documentationComment.push(lineBreakPart());
6460
}
65-
documentationComment.push(textPart(comment));
61+
documentationComment.push(textPart(doc.comment));
6662
}
67-
}
63+
});
6864
});
6965
return documentationComment;
7066
}
@@ -73,18 +69,9 @@ namespace ts.JsDoc {
7369
// Only collect doc comments from duplicate declarations once.
7470
const tags: JSDocTagInfo[] = [];
7571
forEachUnique(declarations, declaration => {
76-
const jsDocs = getJSDocs(declaration);
77-
if (!jsDocs) {
78-
return;
79-
}
80-
for (const doc of jsDocs) {
81-
const tagsForDoc = (doc as JSDoc).tags;
82-
if (tagsForDoc) {
83-
tags.push(...tagsForDoc.filter(tag => tag.kind === SyntaxKind.JSDocTag).map(jsDocTag => {
84-
return {
85-
name: unescapeLeadingUnderscores(jsDocTag.tagName.text),
86-
text: jsDocTag.comment
87-
}; }));
72+
for (const tag of getJSDocTags(declaration)) {
73+
if (tag.kind === SyntaxKind.JSDocTag) {
74+
tags.push({ name: unescapeLeadingUnderscores(tag.tagName.text), text: tag.comment });
8875
}
8976
}
9077
});

src/services/utilities.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
/* @internal */
33
namespace ts {
44
export const scanner: Scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true);
5-
export const emptyArray: any[] = [];
65

76
export const enum SemanticMeaning {
87
None = 0x0,

0 commit comments

Comments
 (0)