From 1199cee3ae9a5c95019cd83e23d3e45ec6fe7a81 Mon Sep 17 00:00:00 2001 From: camc314 <18101008+camc314@users.noreply.github.com> Date: Tue, 25 Nov 2025 09:43:09 +0000 Subject: [PATCH] fix(parser): reject invalid modifiers on parameter properties with binding patterns (#16083) Fixes #16077 --- crates/oxc_parser/src/diagnostics.rs | 6 ++ crates/oxc_parser/src/js/function.rs | 19 ++++ tasks/coverage/misc/fail/oxc-16077.ts | 8 ++ tasks/coverage/misc/pass/oxc-16077.ts | 7 ++ tasks/coverage/snapshots/codegen_misc.snap | 4 +- tasks/coverage/snapshots/formatter_misc.snap | 4 +- tasks/coverage/snapshots/parser_babel.snap | 20 +++- tasks/coverage/snapshots/parser_misc.snap | 68 +++++++++++++- .../coverage/snapshots/parser_typescript.snap | 94 ++++++++++++++++--- tasks/coverage/snapshots/semantic_misc.snap | 4 +- .../coverage/snapshots/transformer_misc.snap | 4 +- 11 files changed, 211 insertions(+), 27 deletions(-) create mode 100644 tasks/coverage/misc/fail/oxc-16077.ts create mode 100644 tasks/coverage/misc/pass/oxc-16077.ts diff --git a/crates/oxc_parser/src/diagnostics.rs b/crates/oxc_parser/src/diagnostics.rs index e2d4c67d6778d..2f5836bb6adec 100644 --- a/crates/oxc_parser/src/diagnostics.rs +++ b/crates/oxc_parser/src/diagnostics.rs @@ -813,6 +813,12 @@ pub fn cannot_appear_on_a_parameter( .with_allowed_modifier_help(allowed) } +#[cold] +pub fn parameter_property_cannot_be_binding_pattern(span: Span) -> OxcDiagnostic { + ts_error("1187", "A parameter property may not be declared using a binding pattern.") + .with_label(span) +} + pub fn cannot_appear_on_an_index_signature( modifier: &Modifier, allowed: Option, diff --git a/crates/oxc_parser/src/js/function.rs b/crates/oxc_parser/src/js/function.rs index ccbe2d85e6563..7d6d8719eeb72 100644 --- a/crates/oxc_parser/src/js/function.rs +++ b/crates/oxc_parser/src/js/function.rs @@ -93,6 +93,25 @@ impl<'a> ParserImpl<'a> { ); } let pattern = self.parse_binding_pattern_with_initializer(); + + if modifiers.accessibility().is_some() + || modifiers.contains_readonly() + || modifiers.contains_override() + { + let is_valid_pattern = match &pattern.kind { + BindingPatternKind::BindingIdentifier(_) => true, + BindingPatternKind::AssignmentPattern(assignment) => { + assignment.left.kind.is_binding_identifier() + } + _ => false, + }; + if !is_valid_pattern { + self.error(diagnostics::parameter_property_cannot_be_binding_pattern(Span::new( + span, + self.prev_token_end, + ))); + } + } let are_decorators_allowed = matches!(func_kind, FunctionKind::ClassMethod | FunctionKind::Constructor) && self.is_ts; diff --git a/tasks/coverage/misc/fail/oxc-16077.ts b/tasks/coverage/misc/fail/oxc-16077.ts new file mode 100644 index 0000000000000..9c0f485f20694 --- /dev/null +++ b/tasks/coverage/misc/fail/oxc-16077.ts @@ -0,0 +1,8 @@ +class A {constructor(override []){}} +class B {constructor(private []){}} +class C {constructor(protected []){}} +class D {constructor(public []){}} +class E {constructor(readonly []){}} +class F {constructor(private {}){}} +class G {constructor(public {a} = {}){}} +class H {constructor(readonly [b] = []){}} diff --git a/tasks/coverage/misc/pass/oxc-16077.ts b/tasks/coverage/misc/pass/oxc-16077.ts new file mode 100644 index 0000000000000..11e7d60702a96 --- /dev/null +++ b/tasks/coverage/misc/pass/oxc-16077.ts @@ -0,0 +1,7 @@ +// Valid parameter properties with simple identifiers +class ValidA {constructor(private x){}} +class ValidB {constructor(public y){}} +class ValidC {constructor(protected z){}} +class ValidD {constructor(readonly w){}} +class ValidF {constructor(private x = 1){}} +class ValidG {constructor(public y: number = 2){}} diff --git a/tasks/coverage/snapshots/codegen_misc.snap b/tasks/coverage/snapshots/codegen_misc.snap index 8feb2c6d0afa2..ad58b4c01b10a 100644 --- a/tasks/coverage/snapshots/codegen_misc.snap +++ b/tasks/coverage/snapshots/codegen_misc.snap @@ -1,3 +1,3 @@ codegen_misc Summary: -AST Parsed : 51/51 (100.00%) -Positive Passed: 51/51 (100.00%) +AST Parsed : 52/52 (100.00%) +Positive Passed: 52/52 (100.00%) diff --git a/tasks/coverage/snapshots/formatter_misc.snap b/tasks/coverage/snapshots/formatter_misc.snap index 8bbe756a122b5..332be836603a7 100644 --- a/tasks/coverage/snapshots/formatter_misc.snap +++ b/tasks/coverage/snapshots/formatter_misc.snap @@ -1,3 +1,3 @@ formatter_misc Summary: -AST Parsed : 51/51 (100.00%) -Positive Passed: 51/51 (100.00%) +AST Parsed : 52/52 (100.00%) +Positive Passed: 52/52 (100.00%) diff --git a/tasks/coverage/snapshots/parser_babel.snap b/tasks/coverage/snapshots/parser_babel.snap index 2c74f39732303..36efe74ce8192 100644 --- a/tasks/coverage/snapshots/parser_babel.snap +++ b/tasks/coverage/snapshots/parser_babel.snap @@ -3,7 +3,7 @@ commit: 99dcba5e parser_babel Summary: AST Parsed : 2422/2440 (99.26%) Positive Passed: 2396/2440 (98.20%) -Negative Passed: 1689/1752 (96.40%) +Negative Passed: 1690/1752 (96.46%) Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2022/private-in/invalid-private-followed-by-in-2/input.js Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2026/async-explicit-resource-management/invalid-script-top-level-using-binding/input.js @@ -40,8 +40,6 @@ Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/ty Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/modifiers-invalid-order/input.ts -Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/parameter-properties-binding-patterns/input.ts - Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/private-fields-modifier-abstract/input.ts Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/declare/invalid-namespace-var/input.ts @@ -13407,6 +13405,22 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc 9 │ readonly *[e?.e]?() { } ╰──── + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[babel/packages/babel-parser/test/fixtures/typescript/class/parameter-properties-binding-patterns/input.ts:2:17] + 1 │ class C { + 2 │ constructor(public []) {} + · ───────── + 3 │ } + ╰──── + + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[babel/packages/babel-parser/test/fixtures/typescript/class/parameter-properties-binding-patterns/input.ts:5:17] + 4 │ class D { + 5 │ constructor(public {}) {} + · ───────── + 6 │ } + ╰──── + × TS(1090): 'readonly' modifier cannot appear on a parameter. ╭─[babel/packages/babel-parser/test/fixtures/typescript/class/parameter-properties-not-constructor/input.ts:3:9] 2 │ not_constructor( diff --git a/tasks/coverage/snapshots/parser_misc.snap b/tasks/coverage/snapshots/parser_misc.snap index e5d9e5d5f6406..9811dbe427931 100644 --- a/tasks/coverage/snapshots/parser_misc.snap +++ b/tasks/coverage/snapshots/parser_misc.snap @@ -1,7 +1,7 @@ parser_misc Summary: -AST Parsed : 51/51 (100.00%) -Positive Passed: 51/51 (100.00%) -Negative Passed: 118/118 (100.00%) +AST Parsed : 52/52 (100.00%) +Positive Passed: 52/52 (100.00%) +Negative Passed: 119/119 (100.00%) × Cannot assign to 'arguments' in strict mode ╭─[misc/fail/arguments-eval.ts:1:10] @@ -3143,6 +3143,68 @@ Negative Passed: 118/118 (100.00%) · ╰── `{` expected ╰──── + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[misc/fail/oxc-16077.ts:1:22] + 1 │ class A {constructor(override []){}} + · ─────────── + 2 │ class B {constructor(private []){}} + ╰──── + + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[misc/fail/oxc-16077.ts:2:22] + 1 │ class A {constructor(override []){}} + 2 │ class B {constructor(private []){}} + · ────────── + 3 │ class C {constructor(protected []){}} + ╰──── + + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[misc/fail/oxc-16077.ts:3:22] + 2 │ class B {constructor(private []){}} + 3 │ class C {constructor(protected []){}} + · ──────────── + 4 │ class D {constructor(public []){}} + ╰──── + + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[misc/fail/oxc-16077.ts:4:22] + 3 │ class C {constructor(protected []){}} + 4 │ class D {constructor(public []){}} + · ───────── + 5 │ class E {constructor(readonly []){}} + ╰──── + + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[misc/fail/oxc-16077.ts:5:22] + 4 │ class D {constructor(public []){}} + 5 │ class E {constructor(readonly []){}} + · ─────────── + 6 │ class F {constructor(private {}){}} + ╰──── + + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[misc/fail/oxc-16077.ts:6:22] + 5 │ class E {constructor(readonly []){}} + 6 │ class F {constructor(private {}){}} + · ────────── + 7 │ class G {constructor(public {a} = {}){}} + ╰──── + + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[misc/fail/oxc-16077.ts:7:22] + 6 │ class F {constructor(private {}){}} + 7 │ class G {constructor(public {a} = {}){}} + · ─────────────── + 8 │ class H {constructor(readonly [b] = []){}} + ╰──── + + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[misc/fail/oxc-16077.ts:8:22] + 7 │ class G {constructor(public {a} = {}){}} + 8 │ class H {constructor(readonly [b] = []){}} + · ───────────────── + ╰──── + × Unexpected token ╭─[misc/fail/oxc-169.js:2:1] 1 │ 1<(V=82< diff --git a/tasks/coverage/snapshots/parser_typescript.snap b/tasks/coverage/snapshots/parser_typescript.snap index bd0e49b9fe5dc..9b426457fe098 100644 --- a/tasks/coverage/snapshots/parser_typescript.snap +++ b/tasks/coverage/snapshots/parser_typescript.snap @@ -3,7 +3,7 @@ commit: 669c25c0 parser_typescript Summary: AST Parsed : 9821/9822 (99.99%) Positive Passed: 9812/9822 (99.90%) -Negative Passed: 1457/2547 (57.20%) +Negative Passed: 1463/2547 (57.44%) Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ExportAssignment7.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/ExportAssignment8.ts @@ -218,8 +218,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/declFileWith Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/declarationEmitComputedPropertyNameEnum3.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/declarationEmitDestructuringParameterProperties.ts - Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/declarationEmitDestructuringWithOptionalBindingParameters.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/declarationEmitIndexTypeNotFound.ts @@ -1308,16 +1306,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/es6/destr Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/es6/destructuring/destructuringParameterDeclaration1ES5iterable.ts -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/es6/destructuring/destructuringParameterProperties1.ts - -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/es6/destructuring/destructuringParameterProperties2.ts - -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/es6/destructuring/destructuringParameterProperties3.ts - -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/es6/destructuring/destructuringParameterProperties4.ts - -Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/es6/destructuring/destructuringParameterProperties5.ts - Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/es6/destructuring/destructuringSpread.ts Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/es6/destructuring/missingAndExcessProperties.ts @@ -4914,6 +4902,30 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/parser/ecmasc 4 │ function h1([a, [b], [[c]], {x = 10, y = [1, 2, 3], z: {a1, b1}}]){ } ╰──── + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[typescript/tests/cases/compiler/declarationEmitDestructuringParameterProperties.ts:2:17] + 1 │ class C1 { + 2 │ constructor(public [x, y, z]: string[]) { + · ────────────────────────── + 3 │ } + ╰──── + + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[typescript/tests/cases/compiler/declarationEmitDestructuringParameterProperties.ts:8:17] + 7 │ class C2 { + 8 │ constructor(public [x, y, z]: TupleType1) { + · ──────────────────────────── + 9 │ } + ╰──── + + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[typescript/tests/cases/compiler/declarationEmitDestructuringParameterProperties.ts:14:17] + 13 │ class C3 { + 14 │ constructor(public { x, y, z }: ObjType1) { + · ──────────────────────────── + 15 │ } + ╰──── + × TS(2499): An interface can only extend an identifier/qualified-name with optional type arguments. ╭─[typescript/tests/cases/compiler/declarationEmitInterfaceWithNonEntityNameExpressionHeritage.ts:2:25] 1 │ class A { } @@ -15746,6 +15758,62 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/parser/ecmasc 8 │ function a1({public}) { } ╰──── + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[typescript/tests/cases/conformance/es6/destructuring/destructuringParameterProperties1.ts:2:17] + 1 │ class C1 { + 2 │ constructor(public [x, y, z]: string[]) { + · ────────────────────────── + 3 │ } + ╰──── + + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[typescript/tests/cases/conformance/es6/destructuring/destructuringParameterProperties1.ts:9:17] + 8 │ class C2 { + 9 │ constructor(public [x, y, z]: TupleType1) { + · ──────────────────────────── + 10 │ } + ╰──── + + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[typescript/tests/cases/conformance/es6/destructuring/destructuringParameterProperties1.ts:16:17] + 15 │ class C3 { + 16 │ constructor(public { x, y, z }: ObjType1) { + · ──────────────────────────── + 17 │ } + ╰──── + + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[typescript/tests/cases/conformance/es6/destructuring/destructuringParameterProperties2.ts:2:36] + 1 │ class C1 { + 2 │ constructor(private k: number, private [a, b, c]: [number, string, boolean]) { + · ──────────────────────────────────────────── + 3 │ if ((b === undefined && c === undefined) || (this.b === undefined && this.c === undefined)) { + ╰──── + + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[typescript/tests/cases/conformance/es6/destructuring/destructuringParameterProperties3.ts:2:31] + 1 │ class C1 { + 2 │ constructor(private k: T, private [a, b, c]: [T,U,V]) { + · ────────────────────────── + 3 │ if ((b === undefined && c === undefined) || (this.b === undefined && this.c === undefined)) { + ╰──── + + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[typescript/tests/cases/conformance/es6/destructuring/destructuringParameterProperties4.ts:2:31] + 1 │ class C1 { + 2 │ constructor(private k: T, protected [a, b, c]: [T,U,V]) { + · ──────────────────────────── + 3 │ if ((b === undefined && c === undefined) || (this.b === undefined && this.c === undefined)) { + ╰──── + + × TS(1187): A parameter property may not be declared using a binding pattern. + ╭─[typescript/tests/cases/conformance/es6/destructuring/destructuringParameterProperties5.ts:5:17] + 4 │ class C1 { + 5 │ constructor(public [{ x1, x2, x3 }, y, z]: TupleType1) { + · ───────────────────────────────────────── + 6 │ var foo: any = x1 || x2 || x3 || y || z; + ╰──── + × Identifier `foo1` has already been declared ╭─[typescript/tests/cases/conformance/es6/destructuring/destructuringSameNames.ts:21:7] 20 │ diff --git a/tasks/coverage/snapshots/semantic_misc.snap b/tasks/coverage/snapshots/semantic_misc.snap index 647dbd803fd94..bc75641651cda 100644 --- a/tasks/coverage/snapshots/semantic_misc.snap +++ b/tasks/coverage/snapshots/semantic_misc.snap @@ -1,6 +1,6 @@ semantic_misc Summary: -AST Parsed : 51/51 (100.00%) -Positive Passed: 32/51 (62.75%) +AST Parsed : 52/52 (100.00%) +Positive Passed: 33/52 (63.46%) semantic Error: tasks/coverage/misc/pass/declare-let-private.ts Bindings mismatch: after transform: ScopeId(0): ["private"] diff --git a/tasks/coverage/snapshots/transformer_misc.snap b/tasks/coverage/snapshots/transformer_misc.snap index 955d1ad269bd5..3efc821b94367 100644 --- a/tasks/coverage/snapshots/transformer_misc.snap +++ b/tasks/coverage/snapshots/transformer_misc.snap @@ -1,3 +1,3 @@ transformer_misc Summary: -AST Parsed : 51/51 (100.00%) -Positive Passed: 51/51 (100.00%) +AST Parsed : 52/52 (100.00%) +Positive Passed: 52/52 (100.00%)