Skip to content

Commit 22919d5

Browse files
authored
JSDoc:positional matching of destructured params (#23307)
* JSDoc:positional matching of destructured params 1. When looking up tags for a parameter whose name is a binding pattern, use the index of the parameter to get the type. 2. When reporting errors for `@param` tags with no matching parameter name, do not report the error for tags whose index in the `@param` tag list matches the index of a parameter whose name is a binding pattern. * Change to an assert * Improve comment text
1 parent 4b706fc commit 22919d5

File tree

6 files changed

+405
-4
lines changed

6 files changed

+405
-4
lines changed

src/compiler/checker.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21871,6 +21871,10 @@ namespace ts {
2187121871
// and give a better error message when the host function mentions `arguments`
2187221872
// but the tag doesn't have an array type
2187321873
if (decl) {
21874+
const i = getJSDocTags(decl).filter(isJSDocParameterTag).indexOf(node);
21875+
if (i > -1 && i < decl.parameters.length && isBindingPattern(decl.parameters[i].name)) {
21876+
return;
21877+
}
2187421878
if (!containsArgumentsReference(decl)) {
2187521879
error(node.name,
2187621880
Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name,

src/compiler/utilities.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4632,11 +4632,21 @@ namespace ts {
46324632
* parameters by name and binding patterns do not have a name.
46334633
*/
46344634
export function getJSDocParameterTags(param: ParameterDeclaration): ReadonlyArray<JSDocParameterTag> {
4635-
if (param.name && isIdentifier(param.name)) {
4636-
const name = param.name.escapedText;
4637-
return getJSDocTags(param.parent).filter((tag): tag is JSDocParameterTag => isJSDocParameterTag(tag) && isIdentifier(tag.name) && tag.name.escapedText === name);
4635+
if (param.name) {
4636+
if (isIdentifier(param.name)) {
4637+
const name = param.name.escapedText;
4638+
return getJSDocTags(param.parent).filter((tag): tag is JSDocParameterTag => isJSDocParameterTag(tag) && isIdentifier(tag.name) && tag.name.escapedText === name);
4639+
}
4640+
else {
4641+
const i = param.parent.parameters.indexOf(param);
4642+
Debug.assert(i > -1, "Parameters should always be in their parents' parameter list");
4643+
const paramTags = getJSDocTags(param.parent).filter(isJSDocParameterTag);
4644+
if (i < paramTags.length) {
4645+
return [paramTags[i]];
4646+
}
4647+
}
46384648
}
4639-
// a binding pattern doesn't have a name, so it's not possible to match it a JSDoc parameter, which is identified by name
4649+
// return empty array for: out-of-order binding patterns and JSDoc function syntax, which has un-named parameters
46404650
return emptyArray;
46414651
}
46424652

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
tests/cases/conformance/jsdoc/0.js(56,20): error TS8024: JSDoc '@param' tag has name 'obj', but there is no parameter with that name.
2+
tests/cases/conformance/jsdoc/0.js(61,19): error TS2459: Type 'string' has no property 'a' and no string index signature.
3+
tests/cases/conformance/jsdoc/0.js(61,22): error TS2459: Type 'string' has no property 'b' and no string index signature.
4+
tests/cases/conformance/jsdoc/0.js(63,20): error TS8024: JSDoc '@param' tag has name 'y', but there is no parameter with that name.
5+
6+
7+
==== tests/cases/conformance/jsdoc/0.js (4 errors) ====
8+
// Object literal syntax
9+
/**
10+
* @param {{a: string, b: string}} obj
11+
* @param {string} x
12+
*/
13+
function good1({a, b}, x) {}
14+
/**
15+
* @param {{a: string, b: string}} obj
16+
* @param {{c: number, d: number}} OBJECTION
17+
*/
18+
function good2({a, b}, {c, d}) {}
19+
/**
20+
* @param {number} x
21+
* @param {{a: string, b: string}} obj
22+
* @param {string} y
23+
*/
24+
function good3(x, {a, b}, y) {}
25+
/**
26+
* @param {{a: string, b: string}} obj
27+
*/
28+
function good4({a, b}) {}
29+
30+
// nested object syntax
31+
/**
32+
* @param {Object} obj
33+
* @param {string} obj.a - this is like the saddest way to specify a type
34+
* @param {string} obj.b - but it sure does allow a lot of documentation
35+
* @param {string} x
36+
*/
37+
function good5({a, b}, x) {}
38+
/**
39+
* @param {Object} obj
40+
* @param {string} obj.a
41+
* @param {string} obj.b - but it sure does allow a lot of documentation
42+
* @param {Object} OBJECTION - documentation here too
43+
* @param {string} OBJECTION.c
44+
* @param {string} OBJECTION.d - meh
45+
*/
46+
function good6({a, b}, {c, d}) {}
47+
/**
48+
* @param {number} x
49+
* @param {Object} obj
50+
* @param {string} obj.a
51+
* @param {string} obj.b
52+
* @param {string} y
53+
*/
54+
function good7(x, {a, b}, y) {}
55+
/**
56+
* @param {Object} obj
57+
* @param {string} obj.a
58+
* @param {string} obj.b
59+
*/
60+
function good8({a, b}) {}
61+
62+
/**
63+
* @param {object} obj - this type gets ignored
64+
~~~
65+
!!! error TS8024: JSDoc '@param' tag has name 'obj', but there is no parameter with that name.
66+
* @param {string} obj.a
67+
* @param {string} obj.b - and x's type gets used for both parameters
68+
* @param {string} x
69+
*/
70+
function bad1(x, {a, b}) {}
71+
~
72+
!!! error TS2459: Type 'string' has no property 'a' and no string index signature.
73+
~
74+
!!! error TS2459: Type 'string' has no property 'b' and no string index signature.
75+
/**
76+
* @param {string} y - here, y's type gets ignored but obj's is fine
77+
~
78+
!!! error TS8024: JSDoc '@param' tag has name 'y', but there is no parameter with that name.
79+
* @param {{a: string, b: string}} obj
80+
*/
81+
function bad2(x, {a, b}) {}
82+
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
=== tests/cases/conformance/jsdoc/0.js ===
2+
// Object literal syntax
3+
/**
4+
* @param {{a: string, b: string}} obj
5+
* @param {string} x
6+
*/
7+
function good1({a, b}, x) {}
8+
>good1 : Symbol(good1, Decl(0.js, 0, 0))
9+
>a : Symbol(a, Decl(0.js, 5, 16))
10+
>b : Symbol(b, Decl(0.js, 5, 18))
11+
>x : Symbol(x, Decl(0.js, 5, 22))
12+
13+
/**
14+
* @param {{a: string, b: string}} obj
15+
* @param {{c: number, d: number}} OBJECTION
16+
*/
17+
function good2({a, b}, {c, d}) {}
18+
>good2 : Symbol(good2, Decl(0.js, 5, 28))
19+
>a : Symbol(a, Decl(0.js, 10, 16))
20+
>b : Symbol(b, Decl(0.js, 10, 18))
21+
>c : Symbol(c, Decl(0.js, 10, 24))
22+
>d : Symbol(d, Decl(0.js, 10, 26))
23+
24+
/**
25+
* @param {number} x
26+
* @param {{a: string, b: string}} obj
27+
* @param {string} y
28+
*/
29+
function good3(x, {a, b}, y) {}
30+
>good3 : Symbol(good3, Decl(0.js, 10, 33))
31+
>x : Symbol(x, Decl(0.js, 16, 15))
32+
>a : Symbol(a, Decl(0.js, 16, 19))
33+
>b : Symbol(b, Decl(0.js, 16, 21))
34+
>y : Symbol(y, Decl(0.js, 16, 25))
35+
36+
/**
37+
* @param {{a: string, b: string}} obj
38+
*/
39+
function good4({a, b}) {}
40+
>good4 : Symbol(good4, Decl(0.js, 16, 31))
41+
>a : Symbol(a, Decl(0.js, 20, 16))
42+
>b : Symbol(b, Decl(0.js, 20, 18))
43+
44+
// nested object syntax
45+
/**
46+
* @param {Object} obj
47+
* @param {string} obj.a - this is like the saddest way to specify a type
48+
* @param {string} obj.b - but it sure does allow a lot of documentation
49+
* @param {string} x
50+
*/
51+
function good5({a, b}, x) {}
52+
>good5 : Symbol(good5, Decl(0.js, 20, 25))
53+
>a : Symbol(a, Decl(0.js, 29, 16))
54+
>b : Symbol(b, Decl(0.js, 29, 18))
55+
>x : Symbol(x, Decl(0.js, 29, 22))
56+
57+
/**
58+
* @param {Object} obj
59+
* @param {string} obj.a
60+
* @param {string} obj.b - but it sure does allow a lot of documentation
61+
* @param {Object} OBJECTION - documentation here too
62+
* @param {string} OBJECTION.c
63+
* @param {string} OBJECTION.d - meh
64+
*/
65+
function good6({a, b}, {c, d}) {}
66+
>good6 : Symbol(good6, Decl(0.js, 29, 28))
67+
>a : Symbol(a, Decl(0.js, 38, 16))
68+
>b : Symbol(b, Decl(0.js, 38, 18))
69+
>c : Symbol(c, Decl(0.js, 38, 24))
70+
>d : Symbol(d, Decl(0.js, 38, 26))
71+
72+
/**
73+
* @param {number} x
74+
* @param {Object} obj
75+
* @param {string} obj.a
76+
* @param {string} obj.b
77+
* @param {string} y
78+
*/
79+
function good7(x, {a, b}, y) {}
80+
>good7 : Symbol(good7, Decl(0.js, 38, 33))
81+
>x : Symbol(x, Decl(0.js, 46, 15))
82+
>a : Symbol(a, Decl(0.js, 46, 19))
83+
>b : Symbol(b, Decl(0.js, 46, 21))
84+
>y : Symbol(y, Decl(0.js, 46, 25))
85+
86+
/**
87+
* @param {Object} obj
88+
* @param {string} obj.a
89+
* @param {string} obj.b
90+
*/
91+
function good8({a, b}) {}
92+
>good8 : Symbol(good8, Decl(0.js, 46, 31))
93+
>a : Symbol(a, Decl(0.js, 52, 16))
94+
>b : Symbol(b, Decl(0.js, 52, 18))
95+
96+
/**
97+
* @param {object} obj - this type gets ignored
98+
* @param {string} obj.a
99+
* @param {string} obj.b - and x's type gets used for both parameters
100+
* @param {string} x
101+
*/
102+
function bad1(x, {a, b}) {}
103+
>bad1 : Symbol(bad1, Decl(0.js, 52, 25))
104+
>x : Symbol(x, Decl(0.js, 60, 14))
105+
>a : Symbol(a, Decl(0.js, 60, 18))
106+
>b : Symbol(b, Decl(0.js, 60, 20))
107+
108+
/**
109+
* @param {string} y - here, y's type gets ignored but obj's is fine
110+
* @param {{a: string, b: string}} obj
111+
*/
112+
function bad2(x, {a, b}) {}
113+
>bad2 : Symbol(bad2, Decl(0.js, 60, 27))
114+
>x : Symbol(x, Decl(0.js, 65, 14))
115+
>a : Symbol(a, Decl(0.js, 65, 18))
116+
>b : Symbol(b, Decl(0.js, 65, 20))
117+
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
=== tests/cases/conformance/jsdoc/0.js ===
2+
// Object literal syntax
3+
/**
4+
* @param {{a: string, b: string}} obj
5+
* @param {string} x
6+
*/
7+
function good1({a, b}, x) {}
8+
>good1 : ({ a, b }: { a: string; b: string; }, x: string) => void
9+
>a : string
10+
>b : string
11+
>x : string
12+
13+
/**
14+
* @param {{a: string, b: string}} obj
15+
* @param {{c: number, d: number}} OBJECTION
16+
*/
17+
function good2({a, b}, {c, d}) {}
18+
>good2 : ({ a, b }: { a: string; b: string; }, { c, d }: { c: number; d: number; }) => void
19+
>a : string
20+
>b : string
21+
>c : number
22+
>d : number
23+
24+
/**
25+
* @param {number} x
26+
* @param {{a: string, b: string}} obj
27+
* @param {string} y
28+
*/
29+
function good3(x, {a, b}, y) {}
30+
>good3 : (x: number, { a, b }: { a: string; b: string; }, y: string) => void
31+
>x : number
32+
>a : string
33+
>b : string
34+
>y : string
35+
36+
/**
37+
* @param {{a: string, b: string}} obj
38+
*/
39+
function good4({a, b}) {}
40+
>good4 : ({ a, b }: { a: string; b: string; }) => void
41+
>a : string
42+
>b : string
43+
44+
// nested object syntax
45+
/**
46+
* @param {Object} obj
47+
* @param {string} obj.a - this is like the saddest way to specify a type
48+
* @param {string} obj.b - but it sure does allow a lot of documentation
49+
* @param {string} x
50+
*/
51+
function good5({a, b}, x) {}
52+
>good5 : ({ a, b }: { a: string; b: string; }, x: string) => void
53+
>a : string
54+
>b : string
55+
>x : string
56+
57+
/**
58+
* @param {Object} obj
59+
* @param {string} obj.a
60+
* @param {string} obj.b - but it sure does allow a lot of documentation
61+
* @param {Object} OBJECTION - documentation here too
62+
* @param {string} OBJECTION.c
63+
* @param {string} OBJECTION.d - meh
64+
*/
65+
function good6({a, b}, {c, d}) {}
66+
>good6 : ({ a, b }: { a: string; b: string; }, { c, d }: { c: string; d: string; }) => void
67+
>a : string
68+
>b : string
69+
>c : string
70+
>d : string
71+
72+
/**
73+
* @param {number} x
74+
* @param {Object} obj
75+
* @param {string} obj.a
76+
* @param {string} obj.b
77+
* @param {string} y
78+
*/
79+
function good7(x, {a, b}, y) {}
80+
>good7 : (x: number, { a, b }: { a: string; b: string; }, y: string) => void
81+
>x : number
82+
>a : string
83+
>b : string
84+
>y : string
85+
86+
/**
87+
* @param {Object} obj
88+
* @param {string} obj.a
89+
* @param {string} obj.b
90+
*/
91+
function good8({a, b}) {}
92+
>good8 : ({ a, b }: { a: string; b: string; }) => void
93+
>a : string
94+
>b : string
95+
96+
/**
97+
* @param {object} obj - this type gets ignored
98+
* @param {string} obj.a
99+
* @param {string} obj.b - and x's type gets used for both parameters
100+
* @param {string} x
101+
*/
102+
function bad1(x, {a, b}) {}
103+
>bad1 : (x: string, { a, b }: string) => void
104+
>x : string
105+
>a : any
106+
>b : any
107+
108+
/**
109+
* @param {string} y - here, y's type gets ignored but obj's is fine
110+
* @param {{a: string, b: string}} obj
111+
*/
112+
function bad2(x, {a, b}) {}
113+
>bad2 : (x: any, { a, b }: { a: string; b: string; }) => void
114+
>x : any
115+
>a : string
116+
>b : string
117+

0 commit comments

Comments
 (0)