Skip to content

Commit bdba8a3

Browse files
author
Andy Hanson
committed
Zip @param tags and parameters together instead of looking for a name
1 parent 683d6c7 commit bdba8a3

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

@@ -19789,10 +19779,10 @@ namespace ts {
1978919779

1979019780
function checkJSDocParameterTag(node: JSDocParameterTag) {
1979119781
checkSourceElement(node.typeExpression);
19792-
if (!getParameterSymbolFromJSDoc(node)) {
19782+
if (!node.symbol) {
1979319783
error(node.name,
1979419784
Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name,
19795-
unescapeLeadingUnderscores((node.name.kind === SyntaxKind.QualifiedName ? node.name.right : node.name).escapedText));
19785+
idText(getLeftmostName(node.name)));
1979619786
}
1979719787
}
1979819788

@@ -23014,7 +23004,7 @@ namespace ts {
2301423004
}
2301523005

2301623006
if (entityName.parent!.kind === SyntaxKind.JSDocParameterTag) {
23017-
return getParameterSymbolFromJSDoc(entityName.parent as JSDocParameterTag);
23007+
return entityName.parent.symbol;
2301823008
}
2301923009

2302023010
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

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

35043486
namespace ts {
@@ -4057,17 +4039,9 @@ namespace ts {
40574039
* initializer is the containing function. The tags closest to the
40584040
* node are returned first, so in the previous example, the param
40594041
* tag on the containing function expression would be first.
4060-
*
4061-
* Does not return tags for binding patterns, because JSDoc matches
4062-
* parameters by name and binding patterns do not have a name.
40634042
*/
40644043
export function getJSDocParameterTags(param: ParameterDeclaration): ReadonlyArray<JSDocParameterTag> | undefined {
4065-
if (param.name && isIdentifier(param.name)) {
4066-
const name = param.name.escapedText;
4067-
return getJSDocTags(param.parent).filter((tag): tag is JSDocParameterTag => isJSDocParameterTag(tag) && isIdentifier(tag.name) && tag.name.escapedText === name) as JSDocParameterTag[];
4068-
}
4069-
// a binding pattern doesn't have a name, so it's not possible to match it a JSDoc parameter, which is identified by name
4070-
return undefined;
4044+
return param.jsdocParamTags;
40714045
}
40724046

40734047
/**
@@ -4122,15 +4096,13 @@ namespace ts {
41224096
* tag directly on the node would be returned.
41234097
*/
41244098
export function getJSDocType(node: Node): TypeNode | undefined {
4125-
let tag: JSDocTypeTag | JSDocParameterTag = getFirstJSDocTag(node, SyntaxKind.JSDocTypeTag) as JSDocTypeTag;
4126-
if (!tag && node.kind === SyntaxKind.Parameter) {
4127-
const paramTags = getJSDocParameterTags(node as ParameterDeclaration);
4128-
if (paramTags) {
4129-
tag = find(paramTags, tag => !!tag.typeExpression);
4130-
}
4099+
const tag = getFirstJSDocTag(node, SyntaxKind.JSDocTypeTag) as JSDocTypeTag;
4100+
if (tag) {
4101+
return tag.typeExpression && tag.typeExpression.type;
4102+
}
4103+
else if (node.kind === SyntaxKind.Parameter) {
4104+
return forEach((node as ParameterDeclaration).jsdocParamTags, tag => tag.typeExpression && tag.typeExpression.type);
41314105
}
4132-
4133-
return tag && tag.typeExpression && tag.typeExpression.type;
41344106
}
41354107

41364108
/**

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)