Skip to content

Commit 4b96edf

Browse files
author
Andy
authored
Treat ... in jsdoc type as creating a synthetic rest parameter -- not as an array type (#19483)
* Treat `...` in jsdoc type as creating a synthetic rest parameter -- not as an array type * Change type parsing so `...T[]` parses as `...(T[])` and not `(...T)[]` * Replace the last parameter with ...args, and make access to it potentially undefined * Code review
1 parent 3d05952 commit 4b96edf

23 files changed

+336
-125
lines changed

src/compiler/checker.ts

Lines changed: 119 additions & 57 deletions
Large diffs are not rendered by default.

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3547,6 +3547,10 @@
35473547
"category": "Error",
35483548
"code": 8027
35493549
},
3550+
"JSDoc '...' may only appear in the last parameter of a signature.": {
3551+
"category": "Error",
3552+
"code": 8028
3553+
},
35503554
"Only identifiers/qualified-names with optional type arguments are currently supported in a class 'extends' clause.": {
35513555
"category": "Error",
35523556
"code": 9002

src/compiler/parser.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2678,8 +2678,6 @@ namespace ts {
26782678
return parseJSDocUnknownOrNullableType();
26792679
case SyntaxKind.FunctionKeyword:
26802680
return parseJSDocFunctionType();
2681-
case SyntaxKind.DotDotDotToken:
2682-
return parseJSDocNodeWithType(SyntaxKind.JSDocVariadicType);
26832681
case SyntaxKind.ExclamationToken:
26842682
return parseJSDocNodeWithType(SyntaxKind.JSDocNonNullableType);
26852683
case SyntaxKind.NoSubstitutionTemplateLiteral:
@@ -2819,6 +2817,12 @@ namespace ts {
28192817
switch (token()) {
28202818
case SyntaxKind.KeyOfKeyword:
28212819
return parseTypeOperator(SyntaxKind.KeyOfKeyword);
2820+
case SyntaxKind.DotDotDotToken: {
2821+
const result = createNode(SyntaxKind.JSDocVariadicType) as JSDocVariadicType;
2822+
nextToken();
2823+
result.type = parsePostfixTypeOrHigher();
2824+
return finishNode(result);
2825+
}
28222826
}
28232827
return parsePostfixTypeOrHigher();
28242828
}

src/compiler/utilities.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,16 +1626,22 @@ namespace ts {
16261626
return undefined;
16271627
}
16281628
const name = node.name.escapedText;
1629+
const decl = getHostSignatureFromJSDoc(node);
1630+
if (!decl) {
1631+
return undefined;
1632+
}
1633+
const parameter = find(decl.parameters, p => p.name.kind === SyntaxKind.Identifier && p.name.escapedText === name);
1634+
return parameter && parameter.symbol;
1635+
}
1636+
1637+
export function getHostSignatureFromJSDoc(node: JSDocParameterTag): FunctionLike | undefined {
16291638
const host = getJSDocHost(node);
16301639
const decl = getSourceOfAssignment(host) ||
16311640
getSingleInitializerOfVariableStatement(host) ||
16321641
getSingleVariableOfVariableStatement(host) ||
16331642
getNestedModuleDeclaration(host) ||
16341643
host;
1635-
if (decl && isFunctionLike(decl)) {
1636-
const parameter = find(decl.parameters, p => p.name.kind === SyntaxKind.Identifier && p.name.escapedText === name);
1637-
return parameter && parameter.symbol;
1638-
}
1644+
return decl && isFunctionLike(decl) ? decl : undefined;
16391645
}
16401646

16411647
export function getJSDocHost(node: JSDocTag): HasJSDoc {

tests/baselines/reference/jsFileCompilationRestParamJsDocFunction.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* @param {...*} args The arguments to invoke `func` with.
1010
* @returns {*} Returns the result of `func`.
1111
*/
12-
function apply(func, thisArg, args) {
12+
function apply(func, thisArg, ...args) {
1313
var length = args.length;
1414
switch (length) {
1515
case 0: return func.call(thisArg);
@@ -36,7 +36,11 @@ define("_apply", ["require", "exports"], function (require, exports) {
3636
* @param {...*} args The arguments to invoke `func` with.
3737
* @returns {*} Returns the result of `func`.
3838
*/
39-
function apply(func, thisArg, args) {
39+
function apply(func, thisArg) {
40+
var args = [];
41+
for (var _i = 2; _i < arguments.length; _i++) {
42+
args[_i - 2] = arguments[_i];
43+
}
4044
var length = args.length;
4145
switch (length) {
4246
case 0: return func.call(thisArg);

tests/baselines/reference/jsFileCompilationRestParamJsDocFunction.symbols

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* @param {...*} args The arguments to invoke `func` with.
1010
* @returns {*} Returns the result of `func`.
1111
*/
12-
function apply(func, thisArg, args) {
12+
function apply(func, thisArg, ...args) {
1313
>apply : Symbol(apply, Decl(_apply.js, 0, 0))
1414
>func : Symbol(func, Decl(_apply.js, 10, 15))
1515
>thisArg : Symbol(thisArg, Decl(_apply.js, 10, 20))

tests/baselines/reference/jsFileCompilationRestParamJsDocFunction.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
* @param {...*} args The arguments to invoke `func` with.
1010
* @returns {*} Returns the result of `func`.
1111
*/
12-
function apply(func, thisArg, args) {
13-
>apply : (func: Function, thisArg: any, args: any[]) => any
12+
function apply(func, thisArg, ...args) {
13+
>apply : (func: Function, thisArg: any, ...args: any[]) => any
1414
>func : Function
1515
>thisArg : any
1616
>args : any[]
@@ -84,5 +84,5 @@ function apply(func, thisArg, args) {
8484
}
8585

8686
export default apply;
87-
>apply : (func: Function, thisArg: any, args: any[]) => any
87+
>apply : (func: Function, thisArg: any, ...args: any[]) => any
8888

tests/baselines/reference/jsdocDisallowedInTypescript.errors.txt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts(11,12): error TS255
77
tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts(13,14): error TS8020: JSDoc types can only be used inside documentation comments.
88
tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts(14,11): error TS8020: JSDoc types can only be used inside documentation comments.
99
tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts(15,8): error TS8020: JSDoc types can only be used inside documentation comments.
10+
tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts(16,5): error TS2322: Type 'boolean[]' is not assignable to type 'boolean | undefined'.
11+
Type 'boolean[]' is not assignable to type 'false'.
1012
tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts(16,15): error TS8020: JSDoc types can only be used inside documentation comments.
13+
tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts(16,15): error TS8028: JSDoc '...' may only appear in the last parameter of a signature.
1114
tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts(17,11): error TS8020: JSDoc types can only be used inside documentation comments.
1215
tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts(18,17): error TS8020: JSDoc types can only be used inside documentation comments.
1316
tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts(19,5): error TS2322: Type 'undefined' is not assignable to type 'number | null'.
@@ -16,9 +19,10 @@ tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts(21,16): error TS802
1619
tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts(22,16): error TS8020: JSDoc types can only be used inside documentation comments.
1720
tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts(23,17): error TS8020: JSDoc types can only be used inside documentation comments.
1821
tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts(24,17): error TS8020: JSDoc types can only be used inside documentation comments.
22+
tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts(24,17): error TS8028: JSDoc '...' may only appear in the last parameter of a signature.
1923

2024

21-
==== tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts (18 errors) ====
25+
==== tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts (21 errors) ====
2226
// grammar error from checker
2327
var ara: Array.<number> = [1,2,3];
2428
~
@@ -53,8 +57,13 @@ tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts(24,17): error TS802
5357
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5458
!!! error TS8020: JSDoc types can only be used inside documentation comments.
5559
var variadic: ...boolean = [true, false, true];
60+
~~~~~~~~
61+
!!! error TS2322: Type 'boolean[]' is not assignable to type 'boolean | undefined'.
62+
!!! error TS2322: Type 'boolean[]' is not assignable to type 'false'.
5663
~~~~~~~~~~
5764
!!! error TS8020: JSDoc types can only be used inside documentation comments.
65+
~~~~~~~~~~
66+
!!! error TS8028: JSDoc '...' may only appear in the last parameter of a signature.
5867
var most: !string = 'definite';
5968
~~~~~~~
6069
!!! error TS8020: JSDoc types can only be used inside documentation comments.
@@ -79,5 +88,7 @@ tests/cases/conformance/jsdoc/jsdocDisallowedInTypescript.ts(24,17): error TS802
7988
var vars: Array<...number>;
8089
~~~~~~~~~
8190
!!! error TS8020: JSDoc types can only be used inside documentation comments.
91+
~~~~~~~~~
92+
!!! error TS8028: JSDoc '...' may only appear in the last parameter of a signature.
8293

8394

tests/baselines/reference/jsdocDisallowedInTypescript.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ var g: function(number, number): number = (n,m) => n + m;
6565
>m : number
6666

6767
var variadic: ...boolean = [true, false, true];
68-
>variadic : boolean[]
68+
>variadic : boolean | undefined
6969
>[true, false, true] : boolean[]
7070
>true : true
7171
>false : false
@@ -96,7 +96,7 @@ var anys: Array<*>;
9696
>Array : T[]
9797

9898
var vars: Array<...number>;
99-
>vars : number[][]
99+
>vars : (number | undefined)[]
100100
>Array : T[]
101101

102102

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
tests/cases/conformance/jsdoc/prefixPostfix.js(8,13): error TS8028: JSDoc '...' may only appear in the last parameter of a signature.
2+
tests/cases/conformance/jsdoc/prefixPostfix.js(9,12): error TS1014: A rest parameter must be last in a parameter list.
3+
tests/cases/conformance/jsdoc/prefixPostfix.js(10,12): error TS1014: A rest parameter must be last in a parameter list.
4+
tests/cases/conformance/jsdoc/prefixPostfix.js(11,12): error TS1014: A rest parameter must be last in a parameter list.
5+
tests/cases/conformance/jsdoc/prefixPostfix.js(12,12): error TS1014: A rest parameter must be last in a parameter list.
6+
tests/cases/conformance/jsdoc/prefixPostfix.js(13,12): error TS1014: A rest parameter must be last in a parameter list.
7+
tests/cases/conformance/jsdoc/prefixPostfix.js(14,12): error TS1014: A rest parameter must be last in a parameter list.
8+
9+
10+
==== tests/cases/conformance/jsdoc/prefixPostfix.js (7 errors) ====
11+
/**
12+
* @param {number![]} x - number[]
13+
* @param {!number[]} y - number[]
14+
* @param {(number[])!} z - number[]
15+
* @param {number?[]} a - (number | null)[]
16+
* @param {?number[]} b - number[] | null
17+
* @param {(number[])?} c - number[] | null
18+
* @param {?...number} d - number[] | null
19+
~~~~~~~~~
20+
!!! error TS8028: JSDoc '...' may only appear in the last parameter of a signature.
21+
* @param {...?number} e - (number | null)[]
22+
~~~~~~~~~~
23+
!!! error TS1014: A rest parameter must be last in a parameter list.
24+
* @param {...number?} f - number[] | null
25+
~~~~~~~~~~
26+
!!! error TS1014: A rest parameter must be last in a parameter list.
27+
* @param {...number!?} g - number[] | null
28+
~~~~~~~~~~~
29+
!!! error TS1014: A rest parameter must be last in a parameter list.
30+
* @param {...number?!} h - number[] | null
31+
~~~~~~~~~~~
32+
!!! error TS1014: A rest parameter must be last in a parameter list.
33+
* @param {...number[]} i - number[][]
34+
~~~~~~~~~~~
35+
!!! error TS1014: A rest parameter must be last in a parameter list.
36+
* @param {...number![]?} j - number[][] | null
37+
~~~~~~~~~~~~~
38+
!!! error TS1014: A rest parameter must be last in a parameter list.
39+
* @param {...number?[]!} k - (number[] | null)[]
40+
*/
41+
function f(x, y, z, a, b, c, d, e, f, g, h, i, j, k) {
42+
}
43+

tests/baselines/reference/jsdocPrefixPostfixParsing.types

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,20 @@
1616
* @param {...number?[]!} k - (number[] | null)[]
1717
*/
1818
function f(x, y, z, a, b, c, d, e, f, g, h, i, j, k) {
19-
>f : (x: number[], y: number[], z: number[], a: (number | null)[], b: number[] | null, c: number[] | null, d: number[] | null, e: (number | null)[], f: number[] | null, g: number[] | null, h: number[] | null, i: number[][], j: number[][] | null, k: (number[] | null)[]) => void
19+
>f : (x: number[], y: number[], z: number[], a: (number | null)[], b: number[] | null, c: number[] | null, d: number | null | undefined, e: number | null | undefined, f: number | null | undefined, g: number | null | undefined, h: number | null | undefined, i: number[] | undefined, j: number[] | null | undefined, ...args: (number | null)[][]) => void
2020
>x : number[]
2121
>y : number[]
2222
>z : number[]
2323
>a : (number | null)[]
2424
>b : number[] | null
2525
>c : number[] | null
26-
>d : number[] | null
27-
>e : (number | null)[]
28-
>f : number[] | null
29-
>g : number[] | null
30-
>h : number[] | null
31-
>i : number[][]
32-
>j : number[][] | null
33-
>k : (number[] | null)[]
26+
>d : number | null | undefined
27+
>e : number | null | undefined
28+
>f : number | null | undefined
29+
>g : number | null | undefined
30+
>h : number | null | undefined
31+
>i : number[] | undefined
32+
>j : number[] | null | undefined
33+
>k : (number | null)[] | undefined
3434
}
3535

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/a.js(7,3): error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'number'.
2+
/a.js(8,6): error TS2345: Argument of type '"2"' is not assignable to parameter of type 'number'.
3+
4+
5+
==== /a.js (2 errors) ====
6+
/** @param {...number} a */
7+
function f(a) {
8+
a; // number | undefined
9+
// Ideally this would be a number. But currently checker.ts has only one `argumentsSymbol`, so it's `any`.
10+
arguments[0];
11+
}
12+
f([1, 2]); // Error
13+
~~~~~~
14+
!!! error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'number'.
15+
f(1, "2"); // Error
16+
~~~
17+
!!! error TS2345: Argument of type '"2"' is not assignable to parameter of type 'number'.
18+
f(1, 2);
19+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
=== /a.js ===
2+
/** @param {...number} a */
3+
function f(a) {
4+
>f : Symbol(f, Decl(a.js, 0, 0))
5+
>a : Symbol(a, Decl(a.js, 1, 11))
6+
7+
a; // number | undefined
8+
>a : Symbol(a, Decl(a.js, 1, 11))
9+
10+
// Ideally this would be a number. But currently checker.ts has only one `argumentsSymbol`, so it's `any`.
11+
arguments[0];
12+
>arguments : Symbol(arguments)
13+
}
14+
f([1, 2]); // Error
15+
>f : Symbol(f, Decl(a.js, 0, 0))
16+
17+
f(1, "2"); // Error
18+
>f : Symbol(f, Decl(a.js, 0, 0))
19+
20+
f(1, 2);
21+
>f : Symbol(f, Decl(a.js, 0, 0))
22+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
=== /a.js ===
2+
/** @param {...number} a */
3+
function f(a) {
4+
>f : (...args: number[]) => void
5+
>a : number | undefined
6+
7+
a; // number | undefined
8+
>a : number | undefined
9+
10+
// Ideally this would be a number. But currently checker.ts has only one `argumentsSymbol`, so it's `any`.
11+
arguments[0];
12+
>arguments[0] : any
13+
>arguments : IArguments
14+
>0 : 0
15+
}
16+
f([1, 2]); // Error
17+
>f([1, 2]) : void
18+
>f : (...args: number[]) => void
19+
>[1, 2] : number[]
20+
>1 : 1
21+
>2 : 2
22+
23+
f(1, "2"); // Error
24+
>f(1, "2") : void
25+
>f : (...args: number[]) => void
26+
>1 : 1
27+
>"2" : "2"
28+
29+
f(1, 2);
30+
>f(1, 2) : void
31+
>f : (...args: number[]) => void
32+
>1 : 1
33+
>2 : 2
34+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
=== /a.js ===
2+
/** @param {...number} a */
3+
function f(...a) {
4+
>f : Symbol(f, Decl(a.js, 0, 0))
5+
>a : Symbol(a, Decl(a.js, 1, 11))
6+
7+
a; // number[]
8+
>a : Symbol(a, Decl(a.js, 1, 11))
9+
}
10+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
=== /a.js ===
2+
/** @param {...number} a */
3+
function f(...a) {
4+
>f : (...a: number[]) => void
5+
>a : number[]
6+
7+
a; // number[]
8+
>a : number[]
9+
}
10+

tests/cases/compiler/jsFileCompilationRestParamJsDocFunction.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* @param {...*} args The arguments to invoke `func` with.
1414
* @returns {*} Returns the result of `func`.
1515
*/
16-
function apply(func, thisArg, args) {
16+
function apply(func, thisArg, ...args) {
1717
var length = args.length;
1818
switch (length) {
1919
case 0: return func.call(thisArg);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// @allowJs: true
2+
// @checkJs: true
3+
// @strict: true
4+
// @noEmit: true
5+
6+
// @Filename: /a.js
7+
/** @param {...number} a */
8+
function f(a) {
9+
a; // number | undefined
10+
// Ideally this would be a number. But currently checker.ts has only one `argumentsSymbol`, so it's `any`.
11+
arguments[0];
12+
}
13+
f([1, 2]); // Error
14+
f(1, "2"); // Error
15+
f(1, 2);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// @allowJs: true
2+
// @checkJs: true
3+
// @strict: true
4+
// @noEmit: true
5+
6+
// @Filename: /a.js
7+
/** @param {...number} a */
8+
function f(...a) {
9+
a; // number[]
10+
}

tests/cases/fourslash/codeFixChangeJSDocSyntax27.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)