Skip to content

Commit d3bd1a3

Browse files
[ts] Fix restrictions for optional parameters (#15414)
Fixes #15396
1 parent 9112b8f commit d3bd1a3

23 files changed

Lines changed: 249 additions & 89 deletions

File tree

packages/babel-parser/src/parser/expression.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,7 @@ export default abstract class ExpressionParser extends LValParser {
9595
allowExpressionBody?: boolean,
9696
isAsync?: boolean,
9797
): T;
98-
abstract parseFunctionParams(
99-
node: N.Function,
100-
allowModifiers?: boolean,
101-
): void;
98+
abstract parseFunctionParams(node: N.Function, isConstructor?: boolean): void;
10299
abstract parseBlockOrModuleBlockBody(
103100
body: N.Statement[],
104101
directives: N.Directive[] | null | undefined,
@@ -2427,15 +2424,14 @@ export default abstract class ExpressionParser extends LValParser {
24272424
): T {
24282425
this.initFunction(node, isAsync);
24292426
node.generator = isGenerator;
2430-
const allowModifiers = isConstructor; // For TypeScript parameter properties
24312427
this.scope.enter(
24322428
SCOPE_FUNCTION |
24332429
SCOPE_SUPER |
24342430
(inClassScope ? SCOPE_CLASS : 0) |
24352431
(allowDirectSuper ? SCOPE_DIRECT_SUPER : 0),
24362432
);
24372433
this.prodParam.enter(functionFlags(isAsync, node.generator));
2438-
this.parseFunctionParams(node, allowModifiers);
2434+
this.parseFunctionParams(node, isConstructor);
24392435
const finishedNode = this.parseFunctionBodyAndFinish(node, type, true);
24402436
this.prodParam.exit();
24412437
this.scope.exit();

packages/babel-parser/src/parser/lval.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ const unwrapParenthesizedExpression = (node: Node): Node => {
4444
: node;
4545
};
4646

47+
export const enum ParseBindingListFlags {
48+
ALLOW_EMPTY = 1 << 0,
49+
IS_FUNCTION_PARAMS = 1 << 1,
50+
IS_CONSTRUCTOR_PARAMS = 1 << 2,
51+
}
52+
4753
export default abstract class LValParser extends NodeUtils {
4854
// Forward-declaration: defined in expression.js
4955
abstract parseIdentifier(liberal?: boolean): Identifier;
@@ -366,7 +372,7 @@ export default abstract class LValParser extends NodeUtils {
366372
node.elements = this.parseBindingList(
367373
tt.bracketR,
368374
charCodes.rightSquareBracket,
369-
true,
375+
ParseBindingListFlags.ALLOW_EMPTY,
370376
);
371377
return this.finishNode(node, "ArrayPattern");
372378
}
@@ -384,9 +390,10 @@ export default abstract class LValParser extends NodeUtils {
384390
this: Parser,
385391
close: TokenType,
386392
closeCharCode: typeof charCodes[keyof typeof charCodes],
387-
allowEmpty?: boolean,
388-
allowModifiers?: boolean,
393+
flags: ParseBindingListFlags,
389394
): Array<Pattern | TSParameterProperty> {
395+
const allowEmpty = flags & ParseBindingListFlags.ALLOW_EMPTY;
396+
390397
const elts: Array<Pattern | TSParameterProperty> = [];
391398
let first = true;
392399
while (!this.eat(close)) {
@@ -400,7 +407,9 @@ export default abstract class LValParser extends NodeUtils {
400407
} else if (this.eat(close)) {
401408
break;
402409
} else if (this.match(tt.ellipsis)) {
403-
elts.push(this.parseAssignableListItemTypes(this.parseRestBinding()));
410+
elts.push(
411+
this.parseAssignableListItemTypes(this.parseRestBinding(), flags),
412+
);
404413
if (!this.checkCommaAfterRest(closeCharCode)) {
405414
this.expect(close);
406415
break;
@@ -416,7 +425,7 @@ export default abstract class LValParser extends NodeUtils {
416425
while (this.match(tt.at)) {
417426
decorators.push(this.parseDecorator());
418427
}
419-
elts.push(this.parseAssignableListItem(allowModifiers, decorators));
428+
elts.push(this.parseAssignableListItem(flags, decorators));
420429
}
421430
}
422431
return elts;
@@ -460,11 +469,11 @@ export default abstract class LValParser extends NodeUtils {
460469

461470
parseAssignableListItem(
462471
this: Parser,
463-
allowModifiers: boolean | undefined | null,
472+
flags: ParseBindingListFlags,
464473
decorators: Decorator[],
465474
): Pattern | TSParameterProperty {
466475
const left = this.parseMaybeDefault();
467-
this.parseAssignableListItemTypes(left);
476+
this.parseAssignableListItemTypes(left, flags);
468477
const elt = this.parseMaybeDefault(left.loc.start, left);
469478
if (decorators.length) {
470479
left.decorators = decorators;
@@ -473,7 +482,11 @@ export default abstract class LValParser extends NodeUtils {
473482
}
474483

475484
// Used by flow/typescript plugin to add type annotations to binding elements
476-
parseAssignableListItemTypes(param: Pattern): Pattern {
485+
parseAssignableListItemTypes(
486+
param: Pattern,
487+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
488+
flags: ParseBindingListFlags,
489+
): Pattern {
477490
return param;
478491
}
479492

packages/babel-parser/src/parser/statement.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import type { Position } from "../util/location";
4343
import { createPositionWithColumnOffset } from "../util/location";
4444
import { cloneStringLiteral, cloneIdentifier, type Undone } from "./node";
4545
import type Parser from "./index";
46+
import { ParseBindingListFlags } from "./lval";
4647

4748
const loopLabel = { kind: "loop" } as const,
4849
switchLabel = { kind: "switch" } as const;
@@ -1566,7 +1567,7 @@ export default abstract class StatementParser extends ExpressionParser {
15661567
node.id = this.parseFunctionId();
15671568
}
15681569

1569-
this.parseFunctionParams(node, /* allowModifiers */ false);
1570+
this.parseFunctionParams(node, /* isConstructor */ false);
15701571

15711572
// For the smartPipelines plugin: Disable topic references from outer
15721573
// contexts within the function body. They are permitted in function
@@ -1602,15 +1603,15 @@ export default abstract class StatementParser extends ExpressionParser {
16021603
parseFunctionParams(
16031604
this: Parser,
16041605
node: Undone<N.Function>,
1605-
allowModifiers?: boolean,
1606+
isConstructor?: boolean,
16061607
): void {
16071608
this.expect(tt.parenL);
16081609
this.expressionScope.enter(newParameterDeclarationScope());
16091610
node.params = this.parseBindingList(
16101611
tt.parenR,
16111612
charCodes.rightParenthesis,
1612-
/* allowEmpty */ false,
1613-
allowModifiers,
1613+
ParseBindingListFlags.IS_FUNCTION_PARAMS |
1614+
(isConstructor ? ParseBindingListFlags.IS_CONSTRUCTOR_PARAMS : 0),
16141615
);
16151616

16161617
this.expressionScope.exit();

packages/babel-parser/src/plugins/flow/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2887,14 +2887,14 @@ export default (superClass: typeof Parser) =>
28872887
// parse function type parameters - function foo<T>() {}
28882888
parseFunctionParams(
28892889
node: Undone<N.Function>,
2890-
allowModifiers?: boolean,
2890+
isConstructor: boolean,
28912891
): void {
28922892
// @ts-expect-error kind may not index node
28932893
const kind = node.kind;
28942894
if (kind !== "get" && kind !== "set" && this.match(tt.lt)) {
28952895
node.typeParameters = this.flowParseTypeParameterDeclaration();
28962896
}
2897-
super.parseFunctionParams(node, allowModifiers);
2897+
super.parseFunctionParams(node, isConstructor);
28982898
}
28992899

29002900
// parse flow type annotations on variable declarator heads - let foo: string = bar
@@ -3291,7 +3291,7 @@ export default (superClass: typeof Parser) =>
32913291
startLoc: Position,
32923292
): N.ArrowFunctionExpression | undefined | null {
32933293
const node = this.startNodeAt<N.ArrowFunctionExpression>(startLoc);
3294-
this.parseFunctionParams(node);
3294+
this.parseFunctionParams(node, false);
32953295
if (!this.parseArrow(node)) return;
32963296
return super.parseArrowExpression(
32973297
node,

packages/babel-parser/src/plugins/typescript/index.ts

Lines changed: 58 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { cloneIdentifier, type Undone } from "../../parser/node";
4242
import type { Pattern } from "../../types";
4343
import type { Expression } from "../../types";
4444
import type { IJSXParserMixin } from "../jsx";
45+
import { ParseBindingListFlags } from "../../parser/lval";
4546

4647
const getOwn = <T extends {}>(object: T, key: keyof T) =>
4748
Object.hasOwnProperty.call(object, key) && object[key];
@@ -755,7 +756,11 @@ export default (superClass: ClassWithMixin<typeof Parser, IJSXParserMixin>) =>
755756
N.Identifier | N.RestElement | N.ObjectPattern | N.ArrayPattern
756757
> {
757758
return super
758-
.parseBindingList(tt.parenR, charCodes.rightParenthesis)
759+
.parseBindingList(
760+
tt.parenR,
761+
charCodes.rightParenthesis,
762+
ParseBindingListFlags.IS_FUNCTION_PARAMS,
763+
)
759764
.map(pattern => {
760765
if (
761766
pattern.type !== "Identifier" &&
@@ -1422,7 +1427,7 @@ export default (superClass: ClassWithMixin<typeof Parser, IJSXParserMixin>) =>
14221427
super.parseBindingList(
14231428
tt.bracketR,
14241429
charCodes.rightSquareBracket,
1425-
true,
1430+
ParseBindingListFlags.ALLOW_EMPTY,
14261431
);
14271432
return errors.length === previousErrorCount;
14281433
} catch {
@@ -2249,40 +2254,35 @@ export default (superClass: ClassWithMixin<typeof Parser, IJSXParserMixin>) =>
22492254
}
22502255

22512256
parseAssignableListItem(
2252-
allowModifiers: boolean | undefined | null,
2257+
flags: ParseBindingListFlags,
22532258
decorators: N.Decorator[],
22542259
): N.Pattern | N.TSParameterProperty {
22552260
// Store original location to include modifiers in range
22562261
const startLoc = this.state.startLoc;
22572262

2258-
let accessibility: N.Accessibility | undefined | null;
2259-
let readonly = false;
2260-
let override = false;
2261-
if (allowModifiers !== undefined) {
2262-
const modified: ModifierBase = {};
2263-
this.tsParseModifiers({
2264-
modified,
2265-
allowedModifiers: [
2266-
"public",
2267-
"private",
2268-
"protected",
2269-
"override",
2270-
"readonly",
2271-
],
2272-
});
2273-
accessibility = modified.accessibility;
2274-
override = modified.override;
2275-
readonly = modified.readonly;
2276-
if (
2277-
allowModifiers === false &&
2278-
(accessibility || readonly || override)
2279-
) {
2280-
this.raise(TSErrors.UnexpectedParameterModifier, { at: startLoc });
2281-
}
2263+
const modified: ModifierBase = {};
2264+
this.tsParseModifiers({
2265+
modified,
2266+
allowedModifiers: [
2267+
"public",
2268+
"private",
2269+
"protected",
2270+
"override",
2271+
"readonly",
2272+
],
2273+
});
2274+
const accessibility = modified.accessibility;
2275+
const override = modified.override;
2276+
const readonly = modified.readonly;
2277+
if (
2278+
!(flags & ParseBindingListFlags.IS_CONSTRUCTOR_PARAMS) &&
2279+
(accessibility || readonly || override)
2280+
) {
2281+
this.raise(TSErrors.UnexpectedParameterModifier, { at: startLoc });
22822282
}
22832283

22842284
const left = this.parseMaybeDefault();
2285-
this.parseAssignableListItemTypes(left);
2285+
this.parseAssignableListItemTypes(left, flags);
22862286
const elt = this.parseMaybeDefault(left.loc.start, left);
22872287
if (accessibility || readonly || override) {
22882288
const pp = this.startNodeAt<N.TSParameterProperty>(startLoc);
@@ -2314,6 +2314,27 @@ export default (superClass: ClassWithMixin<typeof Parser, IJSXParserMixin>) =>
23142314
);
23152315
}
23162316

2317+
tsDisallowOptionalPattern(node: Undone<N.Function>) {
2318+
for (const param of node.params) {
2319+
if (
2320+
param.type !== "Identifier" &&
2321+
(param as any).optional &&
2322+
!this.state.isAmbientContext
2323+
) {
2324+
this.raise(TSErrors.PatternIsOptional, { at: param });
2325+
}
2326+
}
2327+
}
2328+
2329+
setArrowFunctionParameters(
2330+
node: Undone<N.ArrowFunctionExpression>,
2331+
params: N.Expression[],
2332+
trailingCommaLoc?: Position | null,
2333+
): void {
2334+
super.setArrowFunctionParameters(node, params, trailingCommaLoc);
2335+
this.tsDisallowOptionalPattern(node);
2336+
}
2337+
23172338
parseFunctionBodyAndFinish<
23182339
T extends
23192340
| N.Function
@@ -2340,6 +2361,7 @@ export default (superClass: ClassWithMixin<typeof Parser, IJSXParserMixin>) =>
23402361
return super.parseFunctionBodyAndFinish(node, bodilessType, isMethod);
23412362
}
23422363
}
2364+
this.tsDisallowOptionalPattern(node);
23432365

23442366
return super.parseFunctionBodyAndFinish(node, type, isMethod);
23452367
}
@@ -3271,10 +3293,10 @@ export default (superClass: ClassWithMixin<typeof Parser, IJSXParserMixin>) =>
32713293
);
32723294
}
32733295

3274-
parseFunctionParams(node: N.Function, allowModifiers?: boolean): void {
3296+
parseFunctionParams(node: N.Function, isConstructor: boolean): void {
32753297
const typeParameters = this.tsTryParseTypeParameters();
32763298
if (typeParameters) node.typeParameters = typeParameters;
3277-
super.parseFunctionParams(node, allowModifiers);
3299+
super.parseFunctionParams(node, isConstructor);
32783300
}
32793301

32803302
// `let x: number;`
@@ -3504,16 +3526,13 @@ export default (superClass: ClassWithMixin<typeof Parser, IJSXParserMixin>) =>
35043526
}
35053527

35063528
// Allow type annotations inside of a parameter list.
3507-
parseAssignableListItemTypes(param: N.Pattern) {
3508-
if (this.eat(tt.question)) {
3509-
if (
3510-
param.type !== "Identifier" &&
3511-
!this.state.isAmbientContext &&
3512-
!this.state.inType
3513-
) {
3514-
this.raise(TSErrors.PatternIsOptional, { at: param });
3515-
}
3529+
parseAssignableListItemTypes(
3530+
param: N.Pattern,
3531+
flags: ParseBindingListFlags,
3532+
) {
3533+
if (!(flags & ParseBindingListFlags.IS_FUNCTION_PARAMS)) return param;
35163534

3535+
if (this.eat(tt.question)) {
35173536
(param as any as N.Identifier).optional = true;
35183537
}
35193538
const type = this.tsTryParseTypeAnnotation();

packages/babel-parser/test/fixtures/typescript/arrow-function/async-rest-optional-parameter/output.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
{
22
"type": "File",
33
"start":0,"end":34,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":34,"index":34}},
4+
"errors": [
5+
"SyntaxError: A binding pattern parameter cannot be optional in an implementation signature. (1:6)"
6+
],
47
"program": {
58
"type": "Program",
69
"start":0,"end":34,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":34,"index":34}},
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
([]?, {}) => {}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"type": "File",
3+
"start":0,"end":15,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":15,"index":15}},
4+
"errors": [
5+
"SyntaxError: A binding pattern parameter cannot be optional in an implementation signature. (1:1)"
6+
],
7+
"program": {
8+
"type": "Program",
9+
"start":0,"end":15,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":15,"index":15}},
10+
"sourceType": "module",
11+
"interpreter": null,
12+
"body": [
13+
{
14+
"type": "ExpressionStatement",
15+
"start":0,"end":15,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":15,"index":15}},
16+
"expression": {
17+
"type": "ArrowFunctionExpression",
18+
"start":0,"end":15,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":15,"index":15}},
19+
"id": null,
20+
"generator": false,
21+
"async": false,
22+
"params": [
23+
{
24+
"type": "ArrayPattern",
25+
"start":1,"end":4,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":4,"index":4}},
26+
"elements": [],
27+
"optional": true
28+
},
29+
{
30+
"type": "ObjectPattern",
31+
"start":6,"end":8,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":8,"index":8}},
32+
"properties": []
33+
}
34+
],
35+
"body": {
36+
"type": "BlockStatement",
37+
"start":13,"end":15,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":15,"index":15}},
38+
"body": [],
39+
"directives": []
40+
}
41+
}
42+
}
43+
],
44+
"directives": []
45+
}
46+
}

packages/babel-parser/test/fixtures/typescript/function/pattern-parameters/input.ts renamed to packages/babel-parser/test/fixtures/typescript/function/pattern-optional-parameters/input.ts

File renamed without changes.

packages/babel-parser/test/fixtures/typescript/function/pattern-parameters/output.json renamed to packages/babel-parser/test/fixtures/typescript/function/pattern-optional-parameters/output.json

File renamed without changes.

0 commit comments

Comments
 (0)