Skip to content

Commit fa89a44

Browse files
author
Andy Hanson
committed
Zip @param tags and parameters together instead of looking for a name
1 parent 7959bd0 commit fa89a44

File tree

10 files changed

+113
-91
lines changed

10 files changed

+113
-91
lines changed

src/compiler/binder.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,10 +677,49 @@ namespace ts {
677677
break;
678678
default:
679679
bindEachChild(node);
680+
if (isFunctionLikeDeclaration(node)) {
681+
bindJSDocParameters(node);
682+
}
680683
break;
681684
}
682685
}
683686

687+
function bindJSDocParameters(node: SignatureDeclaration): void {
688+
const { parameters } = node;
689+
if (parameters.length === 0) {
690+
return;
691+
}
692+
693+
// Clear any previous binding
694+
for (const p of parameters) {
695+
p.jsdocParamTags = undefined;
696+
}
697+
698+
let paramIndex = 0;
699+
for (const tag of getJSDocTags(parent)) {
700+
if (!isJSDocParameterTag(tag)) {
701+
continue;
702+
}
703+
704+
// If we run out of parameters, just do nothing.
705+
// Will error in `checkJSDocParameterTag` since it will have no symbol.
706+
const startParamIndex = paramIndex;
707+
do {
708+
const param = parameters[paramIndex];
709+
paramIndex++;
710+
if (paramIndex === parameters.length) {
711+
paramIndex = 0;
712+
}
713+
714+
if (param.name.kind !== SyntaxKind.Identifier || getLeftmostName(tag.name).escapedText === param.name.escapedText) {
715+
tag.symbol = param.symbol;
716+
param.jsdocParamTags = append(param.jsdocParamTags, tag);
717+
break;
718+
}
719+
} while (paramIndex !== startParamIndex);
720+
}
721+
}
722+
684723
function isNarrowingExpression(expr: Expression): boolean {
685724
switch (expr.kind) {
686725
case SyntaxKind.Identifier:

src/compiler/checker.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6268,18 +6268,8 @@ namespace ts {
62686268
if (node.type && node.type.kind === SyntaxKind.JSDocOptionalType) {
62696269
return true;
62706270
}
6271-
const paramTags = getJSDocParameterTags(node);
6272-
if (paramTags) {
6273-
for (const paramTag of paramTags) {
6274-
if (paramTag.isBracketed) {
6275-
return true;
6276-
}
6277-
6278-
if (paramTag.typeExpression) {
6279-
return paramTag.typeExpression.type.kind === SyntaxKind.JSDocOptionalType;
6280-
}
6281-
}
6282-
}
6271+
return some(node.jsdocParamTags, paramTag =>
6272+
paramTag.isBracketed || paramTag.typeExpression && paramTag.typeExpression.type.kind === SyntaxKind.JSDocOptionalType);
62836273
}
62846274
}
62856275

@@ -19794,10 +19784,10 @@ namespace ts {
1979419784

1979519785
function checkJSDocParameterTag(node: JSDocParameterTag) {
1979619786
checkSourceElement(node.typeExpression);
19797-
if (!getParameterSymbolFromJSDoc(node)) {
19787+
if (!node.symbol) {
1979819788
error(node.name,
1979919789
Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name,
19800-
unescapeLeadingUnderscores((node.name.kind === SyntaxKind.QualifiedName ? node.name.right : node.name).escapedText));
19790+
unescapeLeadingUnderscores(getLeftmostName(node.name).escapedText));
1980119791
}
1980219792
}
1980319793

@@ -23019,7 +23009,7 @@ namespace ts {
2301923009
}
2302023010

2302123011
if (entityName.parent!.kind === SyntaxKind.JSDocParameterTag) {
23022-
return getParameterSymbolFromJSDoc(entityName.parent as JSDocParameterTag);
23012+
return entityName.parent.symbol;
2302323013
}
2302423014

2302523015
if (entityName.parent.kind === SyntaxKind.TypeParameter && entityName.parent.parent.kind === SyntaxKind.JSDocTemplateTag) {

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,7 @@ namespace ts {
742742
questionToken?: QuestionToken; // Present on optional parameter
743743
type?: TypeNode; // Optional type annotation
744744
initializer?: Expression; // Optional initializer
745+
/* @internal */ jsdocParamTags?: JSDocParameterTag[];
745746
}
746747

747748
export interface BindingElement extends NamedDeclaration {

src/compiler/utilities.ts

Lines changed: 24 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,11 +1505,17 @@ namespace ts {
15051505
((node as JSDocFunctionType).parameters[0].name as Identifier).escapedText === "new";
15061506
}
15071507

1508-
export function getAllJSDocs(node: Node): (JSDoc | JSDocTag)[] {
1508+
export function forEachJSDoc(node: Node, cb: (doc: JSDoc | JSDocTag) => void): void {
15091509
if (isJSDocTypedefTag(node)) {
1510-
return [node.parent];
1510+
cb(node.parent);
1511+
return;
1512+
}
1513+
if (isParameter(node)) {
1514+
forEach(node.jsdocParamTags, cb);
1515+
}
1516+
for (const tag of getJSDocCommentsAndTags(node)) {
1517+
cb(tag);
15111518
}
1512-
return getJSDocCommentsAndTags(node);
15131519
}
15141520

15151521
export function getJSDocCommentsAndTags(node: Node): (JSDoc | JSDocTag)[] {
@@ -1556,11 +1562,6 @@ namespace ts {
15561562
getJSDocCommentsAndTagsWorker(parent);
15571563
}
15581564

1559-
// Pull parameter comments from declaring function as well
1560-
if (node.kind === SyntaxKind.Parameter) {
1561-
result = addRange(result, getJSDocParameterTags(node as ParameterDeclaration));
1562-
}
1563-
15641565
if (isVariableLike(node) && node.initializer && hasJSDocNodes(node.initializer)) {
15651566
result = addRange(result, node.initializer.jsDoc);
15661567
}
@@ -1571,24 +1572,6 @@ namespace ts {
15711572
}
15721573
}
15731574

1574-
/** Does the opposite of `getJSDocParameterTags`: given a JSDoc parameter, finds the parameter corresponding to it. */
1575-
export function getParameterSymbolFromJSDoc(node: JSDocParameterTag): Symbol | undefined {
1576-
if (node.symbol) {
1577-
return node.symbol;
1578-
}
1579-
if (!isIdentifier(node.name)) {
1580-
return undefined;
1581-
}
1582-
const name = node.name.escapedText;
1583-
const func = getJSDocHost(node);
1584-
if (!isFunctionLike(func)) {
1585-
return undefined;
1586-
}
1587-
const parameter = find(func.parameters, p =>
1588-
p.name.kind === SyntaxKind.Identifier && p.name.escapedText === name);
1589-
return parameter && parameter.symbol;
1590-
}
1591-
15921575
export function getJSDocHost(node: JSDocTag): HasJSDoc {
15931576
Debug.assert(node.parent!.kind === SyntaxKind.JSDocComment);
15941577
return node.parent!.parent!;
@@ -1609,17 +1592,12 @@ namespace ts {
16091592
}
16101593

16111594
export function isRestParameter(node: ParameterDeclaration) {
1612-
if (isInJavaScriptFile(node)) {
1613-
if (node.type && node.type.kind === SyntaxKind.JSDocVariadicType ||
1614-
forEach(getJSDocParameterTags(node),
1615-
t => t.typeExpression && t.typeExpression.type.kind === SyntaxKind.JSDocVariadicType)) {
1616-
return true;
1617-
}
1618-
}
1619-
return isDeclaredRestParam(node);
1595+
return isDeclaredRestParam(node) || isInJavaScriptFile(node) && (
1596+
node.type && node.type.kind === SyntaxKind.JSDocVariadicType ||
1597+
some(node.jsdocParamTags, tag => tag.typeExpression && tag.typeExpression.type.kind === SyntaxKind.JSDocVariadicType));
16201598
}
16211599

1622-
export function isDeclaredRestParam(node: ParameterDeclaration) {
1600+
function isDeclaredRestParam(node: ParameterDeclaration) {
16231601
return node && node.dotDotDotToken !== undefined;
16241602
}
16251603

@@ -3500,6 +3478,10 @@ namespace ts {
35003478
return parent.parent && parent.parent.kind === SyntaxKind.ExpressionStatement ? AccessKind.Write : AccessKind.ReadWrite;
35013479
}
35023480
}
3481+
3482+
export function getLeftmostName(node: EntityName): Identifier {
3483+
return node.kind === SyntaxKind.QualifiedName ? getLeftmostName(node.left) : node;
3484+
}
35033485
}
35043486

35053487
namespace ts {
@@ -4051,17 +4033,9 @@ namespace ts {
40514033
* initializer is the containing function. The tags closest to the
40524034
* node are returned first, so in the previous example, the param
40534035
* tag on the containing function expression would be first.
4054-
*
4055-
* Does not return tags for binding patterns, because JSDoc matches
4056-
* parameters by name and binding patterns do not have a name.
40574036
*/
40584037
export function getJSDocParameterTags(param: ParameterDeclaration): ReadonlyArray<JSDocParameterTag> | undefined {
4059-
if (param.name && isIdentifier(param.name)) {
4060-
const name = param.name.escapedText;
4061-
return getJSDocTags(param.parent).filter((tag): tag is JSDocParameterTag => isJSDocParameterTag(tag) && isIdentifier(tag.name) && tag.name.escapedText === name) as JSDocParameterTag[];
4062-
}
4063-
// a binding pattern doesn't have a name, so it's not possible to match it a JSDoc parameter, which is identified by name
4064-
return undefined;
4038+
return param.jsdocParamTags;
40654039
}
40664040

40674041
/**
@@ -4116,15 +4090,13 @@ namespace ts {
41164090
* tag directly on the node would be returned.
41174091
*/
41184092
export function getJSDocType(node: Node): TypeNode | undefined {
4119-
let tag: JSDocTypeTag | JSDocParameterTag = getFirstJSDocTag(node, SyntaxKind.JSDocTypeTag) as JSDocTypeTag;
4120-
if (!tag && node.kind === SyntaxKind.Parameter) {
4121-
const paramTags = getJSDocParameterTags(node as ParameterDeclaration);
4122-
if (paramTags) {
4123-
tag = find(paramTags, tag => !!tag.typeExpression);
4124-
}
4093+
const tag = getFirstJSDocTag(node, SyntaxKind.JSDocTypeTag) as JSDocTypeTag;
4094+
if (tag) {
4095+
return tag.typeExpression && tag.typeExpression.type;
4096+
}
4097+
else if (node.kind === SyntaxKind.Parameter) {
4098+
return forEach((node as ParameterDeclaration).jsdocParamTags, tag => tag.typeExpression && tag.typeExpression.type);
41254099
}
4126-
4127-
return tag && tag.typeExpression && tag.typeExpression.type;
41284100
}
41294101

41304102
/**

src/services/jsDoc.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ namespace ts.JsDoc {
5353
// from Array<T> - Array<string> and Array<number>
5454
const documentationComment = <SymbolDisplayPart[]>[];
5555
forEachUnique(declarations, declaration => {
56-
forEach(getAllJSDocs(declaration), doc => {
56+
forEachJSDoc(declaration, doc => {
5757
if (doc.comment) {
5858
if (documentationComment.length) {
5959
documentationComment.push(lineBreakPart());
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
=== /a.js ===
2+
/**
3+
* @param {{ x: number, y: string }} p
4+
* @param {number} m
5+
*/
6+
function f({ x, y }, m) {
7+
>f : Symbol(f, Decl(a.js, 0, 0))
8+
>x : Symbol(x, Decl(a.js, 4, 12))
9+
>y : Symbol(y, Decl(a.js, 4, 15))
10+
>m : Symbol(m, Decl(a.js, 4, 20))
11+
}
12+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
=== /a.js ===
2+
/**
3+
* @param {{ x: number, y: string }} p
4+
* @param {number} m
5+
*/
6+
function f({ x, y }, m) {
7+
>f : ({ x, y }: { x: number; y: string; }, m: number) => void
8+
>x : number
9+
>y : string
10+
>m : number
11+
}
12+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// @allowJs: true
2+
// @checkJs: true
3+
// @noEmit: true
4+
// @Filename: /a.js
5+
6+
/**
7+
* @param {{ x: number, y: string }} p
8+
* @param {number} m
9+
*/
10+
function f({ x, y }, m) {
11+
}

tests/cases/fourslash/jsDocFunctionSignatures12.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@
99
////function f1(o) {
1010
//// o/**/;
1111
////}
12-
goTo.marker();
13-
verify.quickInfoIs("(parameter) o: any");
12+
13+
verify.quickInfoAt("", `(parameter) o: any`);
Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,15 @@
11
/// <reference path='fourslash.ts' />
22

3+
// @noLib: true
34
/////**
4-
//// * Returns the substring at the specified location within a String object.
5-
//// * @param start The zero-based index integer indicating the beginning of the substring.
6-
//// * @param end Zero-based index integer indicating the end of the substring. The substring includes the characters up to, but not including, the character indicated by end.
7-
//// * If end is omitted, the characters from start through the end of the original string are returned.
8-
//// */
9-
////function foo(start: number, end?: number) {
5+
//// * @param start START
6+
//// */
7+
////function foo(start: number) {
108
//// return "";
119
////}
1210
////
13-
////fo/*1*/
11+
////foo/*1*/
1412
goTo.marker('1');
1513
verify.not.signatureHelpPresent();
16-
edit.insert("o");
17-
verify.not.signatureHelpPresent();
1814
edit.insert("(");
19-
verify.currentParameterHelpArgumentDocCommentIs("The zero-based index integer indicating the beginning of the substring.");
20-
edit.insert("10,");
21-
verify.currentParameterHelpArgumentDocCommentIs("Zero-based index integer indicating the end of the substring. The substring includes the characters up to, but not including, the character indicated by end.\nIf end is omitted, the characters from start through the end of the original string are returned.");
22-
edit.insert(" ");
23-
verify.currentParameterHelpArgumentDocCommentIs("Zero-based index integer indicating the end of the substring. The substring includes the characters up to, but not including, the character indicated by end.\nIf end is omitted, the characters from start through the end of the original string are returned.");
24-
edit.insert(", ");
25-
edit.backspace(3);
26-
verify.currentParameterHelpArgumentDocCommentIs("Zero-based index integer indicating the end of the substring. The substring includes the characters up to, but not including, the character indicated by end.\nIf end is omitted, the characters from start through the end of the original string are returned.");
27-
edit.insert("12");
28-
verify.currentParameterHelpArgumentDocCommentIs("Zero-based index integer indicating the end of the substring. The substring includes the characters up to, but not including, the character indicated by end.\nIf end is omitted, the characters from start through the end of the original string are returned.");
29-
edit.insert(")");
30-
verify.not.signatureHelpPresent();
15+
verify.currentParameterHelpArgumentDocCommentIs("START");

0 commit comments

Comments
 (0)