Skip to content

Commit d7c8e52

Browse files
committed
In JSDoc, parse postfix-? below conditional types/tuple types
Outside of JSDoc comments, postfix-? is parsed at lower precedence than the `?` of conditional types, and a postfix-? inside a tuple type results in the type being marked optional. This PR changes JSDoc parsing to behave the same way, which means that 1. Conditional types are allowed in JSDoc. Fixes #37166. 2. Tuple types' postfix-? syntax is interpreted correctly in JSDoc. Fixes #38747. The breaking change is that a postfix-? type followed by another postfix type, like `[]` or `!`, is parsed as a conditional type. [Postfix-? is not common](#37166 (comment)), so this is an acceptable breaking change. A postfix-? type `T?` is still parsed everywhere else and treated as `T | null`.
1 parent 0bc29ac commit d7c8e52

File tree

5 files changed

+75
-39
lines changed

5 files changed

+75
-39
lines changed

src/compiler/parser.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3115,7 +3115,7 @@ namespace ts {
31153115
return finishNode(factory.createRestTypeNode(parseType()), pos);
31163116
}
31173117
const type = parseType();
3118-
if (!(contextFlags & NodeFlags.JSDoc) && isJSDocNullableType(type) && type.pos === type.type.pos) {
3118+
if (isJSDocNullableType(type) && type.pos === type.type.pos) {
31193119
const node = factory.createOptionalTypeNode(type.type);
31203120
setTextRange(node, type);
31213121
(node as Mutable<Node>).flags = type.flags;
@@ -3356,7 +3356,7 @@ namespace ts {
33563356
break;
33573357
case SyntaxKind.QuestionToken:
33583358
// If not in JSDoc and next token is start of a type we have a conditional type
3359-
if (!(contextFlags & NodeFlags.JSDoc) && lookAhead(nextTokenIsStartOfType)) {
3359+
if (lookAhead(nextTokenIsStartOfType)) {
33603360
return type;
33613361
}
33623362
nextToken();
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
1+
tests/cases/conformance/jsdoc/prefixPostfix.js(5,18): error TS8024: JSDoc '@param' tag has name '', but there is no parameter with that name.
2+
tests/cases/conformance/jsdoc/prefixPostfix.js(5,18): error TS1005: '}' expected.
13
tests/cases/conformance/jsdoc/prefixPostfix.js(8,12): error TS1014: A rest parameter must be last in a parameter list.
24
tests/cases/conformance/jsdoc/prefixPostfix.js(9,12): error TS1014: A rest parameter must be last in a parameter list.
35
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.
6+
tests/cases/conformance/jsdoc/prefixPostfix.js(11,21): error TS8024: JSDoc '@param' tag has name '', but there is no parameter with that name.
7+
tests/cases/conformance/jsdoc/prefixPostfix.js(11,21): error TS1005: '}' expected.
58
tests/cases/conformance/jsdoc/prefixPostfix.js(12,12): error TS1014: A rest parameter must be last in a parameter list.
69
tests/cases/conformance/jsdoc/prefixPostfix.js(13,12): error TS1014: A rest parameter must be last in a parameter list.
10+
tests/cases/conformance/jsdoc/prefixPostfix.js(14,21): error TS8024: JSDoc '@param' tag has name '', but there is no parameter with that name.
11+
tests/cases/conformance/jsdoc/prefixPostfix.js(14,21): error TS1005: '}' expected.
12+
tests/cases/conformance/jsdoc/prefixPostfix.js(18,21): error TS7006: Parameter 'a' implicitly has an 'any' type.
13+
tests/cases/conformance/jsdoc/prefixPostfix.js(18,39): error TS7006: Parameter 'h' implicitly has an 'any' type.
14+
tests/cases/conformance/jsdoc/prefixPostfix.js(18,48): error TS7006: Parameter 'k' implicitly has an 'any' type.
715

816

9-
==== tests/cases/conformance/jsdoc/prefixPostfix.js (6 errors) ====
17+
==== tests/cases/conformance/jsdoc/prefixPostfix.js (14 errors) ====
1018
/**
1119
* @param {number![]} x - number[]
1220
* @param {!number[]} y - number[]
1321
* @param {(number[])!} z - number[]
14-
* @param {number?[]} a - (number | null)[]
22+
* @param {number?[]} a - parse error without parentheses
23+
24+
!!! error TS8024: JSDoc '@param' tag has name '', but there is no parameter with that name.
25+
~
26+
!!! error TS1005: '}' expected.
1527
* @param {?number[]} b - number[] | null
1628
* @param {(number[])?} c - number[] | null
1729
* @param {...?number} e - (number | null)[]
@@ -23,17 +35,31 @@ tests/cases/conformance/jsdoc/prefixPostfix.js(13,12): error TS1014: A rest para
2335
* @param {...number!?} g - number[] | null
2436
~~~~~~~~~~~
2537
!!! error TS1014: A rest parameter must be last in a parameter list.
26-
* @param {...number?!} h - number[] | null
27-
~~~~~~~~~~~
28-
!!! error TS1014: A rest parameter must be last in a parameter list.
38+
* @param {...number?!} h - parse error without parentheses (also nonsensical)
39+
40+
!!! error TS8024: JSDoc '@param' tag has name '', but there is no parameter with that name.
41+
~
42+
!!! error TS1005: '}' expected.
2943
* @param {...number[]} i - number[][]
3044
~~~~~~~~~~~
3145
!!! error TS1014: A rest parameter must be last in a parameter list.
3246
* @param {...number![]?} j - number[][] | null
3347
~~~~~~~~~~~~~
3448
!!! error TS1014: A rest parameter must be last in a parameter list.
35-
* @param {...number?[]!} k - (number[] | null)[]
49+
* @param {...number?[]!} k - parse error without parentheses
50+
51+
!!! error TS8024: JSDoc '@param' tag has name '', but there is no parameter with that name.
52+
~
53+
!!! error TS1005: '}' expected.
54+
* @param {number extends number ? true : false} l - conditional types work
55+
* @param {[number, number?]} m - [number, (number | undefined)?]
3656
*/
37-
function f(x, y, z, a, b, c, e, f, g, h, i, j, k) {
57+
function f(x, y, z, a, b, c, e, f, g, h, i, j, k, l, m) {
58+
~
59+
!!! error TS7006: Parameter 'a' implicitly has an 'any' type.
60+
~
61+
!!! error TS7006: Parameter 'h' implicitly has an 'any' type.
62+
~
63+
!!! error TS7006: Parameter 'k' implicitly has an 'any' type.
3864
}
3965

tests/baselines/reference/jsdocPrefixPostfixParsing.symbols

+21-17
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,35 @@
33
* @param {number![]} x - number[]
44
* @param {!number[]} y - number[]
55
* @param {(number[])!} z - number[]
6-
* @param {number?[]} a - (number | null)[]
6+
* @param {number?[]} a - parse error without parentheses
77
* @param {?number[]} b - number[] | null
88
* @param {(number[])?} c - number[] | null
99
* @param {...?number} e - (number | null)[]
1010
* @param {...number?} f - number[] | null
1111
* @param {...number!?} g - number[] | null
12-
* @param {...number?!} h - number[] | null
12+
* @param {...number?!} h - parse error without parentheses (also nonsensical)
1313
* @param {...number[]} i - number[][]
1414
* @param {...number![]?} j - number[][] | null
15-
* @param {...number?[]!} k - (number[] | null)[]
15+
* @param {...number?[]!} k - parse error without parentheses
16+
* @param {number extends number ? true : false} l - conditional types work
17+
* @param {[number, number?]} m - [number, (number | undefined)?]
1618
*/
17-
function f(x, y, z, a, b, c, e, f, g, h, i, j, k) {
19+
function f(x, y, z, a, b, c, e, f, g, h, i, j, k, l, m) {
1820
>f : Symbol(f, Decl(prefixPostfix.js, 0, 0))
19-
>x : Symbol(x, Decl(prefixPostfix.js, 15, 11))
20-
>y : Symbol(y, Decl(prefixPostfix.js, 15, 13))
21-
>z : Symbol(z, Decl(prefixPostfix.js, 15, 16))
22-
>a : Symbol(a, Decl(prefixPostfix.js, 15, 19))
23-
>b : Symbol(b, Decl(prefixPostfix.js, 15, 22))
24-
>c : Symbol(c, Decl(prefixPostfix.js, 15, 25))
25-
>e : Symbol(e, Decl(prefixPostfix.js, 15, 28))
26-
>f : Symbol(f, Decl(prefixPostfix.js, 15, 31))
27-
>g : Symbol(g, Decl(prefixPostfix.js, 15, 34))
28-
>h : Symbol(h, Decl(prefixPostfix.js, 15, 37))
29-
>i : Symbol(i, Decl(prefixPostfix.js, 15, 40))
30-
>j : Symbol(j, Decl(prefixPostfix.js, 15, 43))
31-
>k : Symbol(k, Decl(prefixPostfix.js, 15, 46))
21+
>x : Symbol(x, Decl(prefixPostfix.js, 17, 11))
22+
>y : Symbol(y, Decl(prefixPostfix.js, 17, 13))
23+
>z : Symbol(z, Decl(prefixPostfix.js, 17, 16))
24+
>a : Symbol(a, Decl(prefixPostfix.js, 17, 19))
25+
>b : Symbol(b, Decl(prefixPostfix.js, 17, 22))
26+
>c : Symbol(c, Decl(prefixPostfix.js, 17, 25))
27+
>e : Symbol(e, Decl(prefixPostfix.js, 17, 28))
28+
>f : Symbol(f, Decl(prefixPostfix.js, 17, 31))
29+
>g : Symbol(g, Decl(prefixPostfix.js, 17, 34))
30+
>h : Symbol(h, Decl(prefixPostfix.js, 17, 37))
31+
>i : Symbol(i, Decl(prefixPostfix.js, 17, 40))
32+
>j : Symbol(j, Decl(prefixPostfix.js, 17, 43))
33+
>k : Symbol(k, Decl(prefixPostfix.js, 17, 46))
34+
>l : Symbol(l, Decl(prefixPostfix.js, 17, 49))
35+
>m : Symbol(m, Decl(prefixPostfix.js, 17, 52))
3236
}
3337

tests/baselines/reference/jsdocPrefixPostfixParsing.types

+12-8
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,35 @@
33
* @param {number![]} x - number[]
44
* @param {!number[]} y - number[]
55
* @param {(number[])!} z - number[]
6-
* @param {number?[]} a - (number | null)[]
6+
* @param {number?[]} a - parse error without parentheses
77
* @param {?number[]} b - number[] | null
88
* @param {(number[])?} c - number[] | null
99
* @param {...?number} e - (number | null)[]
1010
* @param {...number?} f - number[] | null
1111
* @param {...number!?} g - number[] | null
12-
* @param {...number?!} h - number[] | null
12+
* @param {...number?!} h - parse error without parentheses (also nonsensical)
1313
* @param {...number[]} i - number[][]
1414
* @param {...number![]?} j - number[][] | null
15-
* @param {...number?[]!} k - (number[] | null)[]
15+
* @param {...number?[]!} k - parse error without parentheses
16+
* @param {number extends number ? true : false} l - conditional types work
17+
* @param {[number, number?]} m - [number, (number | undefined)?]
1618
*/
17-
function f(x, y, z, a, b, c, e, f, g, h, i, j, k) {
18-
>f : (x: number[], y: number[], z: (number[]), a: (number | null)[], b: number[] | null, c: (number[]) | null, e: (number | null)[], f: (number | null)[], g: (number | null)[], h: (number | null)[], i: number[][], j: (number[] | null)[], k: (number | null)[][]) => void
19+
function f(x, y, z, a, b, c, e, f, g, h, i, j, k, l, m) {
20+
>f : (x: number[], y: number[], z: (number[]), a: any, b: number[] | null, c: (number[]) | null, e: (number | null)[], f: (number | null)[], g: (number | null)[], h: any, i: number[][], j: (number[] | null)[], k: any, l: number extends number ? true : false, m: [number, number?]) => void
1921
>x : number[]
2022
>y : number[]
2123
>z : number[]
22-
>a : (number | null)[]
24+
>a : any
2325
>b : number[] | null
2426
>c : number[] | null
2527
>e : number | null | undefined
2628
>f : number | null | undefined
2729
>g : number | null | undefined
28-
>h : number | null | undefined
30+
>h : any
2931
>i : number[] | undefined
3032
>j : number[] | null | undefined
31-
>k : (number | null)[] | undefined
33+
>k : any
34+
>l : true
35+
>m : [number, (number | undefined)?]
3236
}
3337

tests/cases/conformance/jsdoc/jsdocPrefixPostfixParsing.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@
1010
* @param {number![]} x - number[]
1111
* @param {!number[]} y - number[]
1212
* @param {(number[])!} z - number[]
13-
* @param {number?[]} a - (number | null)[]
13+
* @param {number?[]} a - parse error without parentheses
1414
* @param {?number[]} b - number[] | null
1515
* @param {(number[])?} c - number[] | null
1616
* @param {...?number} e - (number | null)[]
1717
* @param {...number?} f - number[] | null
1818
* @param {...number!?} g - number[] | null
19-
* @param {...number?!} h - number[] | null
19+
* @param {...number?!} h - parse error without parentheses (also nonsensical)
2020
* @param {...number[]} i - number[][]
2121
* @param {...number![]?} j - number[][] | null
22-
* @param {...number?[]!} k - (number[] | null)[]
22+
* @param {...number?[]!} k - parse error without parentheses
23+
* @param {number extends number ? true : false} l - conditional types work
24+
* @param {[number, number?]} m - [number, (number | undefined)?]
2325
*/
24-
function f(x, y, z, a, b, c, e, f, g, h, i, j, k) {
26+
function f(x, y, z, a, b, c, e, f, g, h, i, j, k, l, m) {
2527
}

0 commit comments

Comments
 (0)