From 5b64637c62357288fb916771537c2dedd4dbb897 Mon Sep 17 00:00:00 2001 From: MaheshDesu Date: Mon, 12 Oct 2026 10:49:16 +0530 Subject: [PATCH] Fix missing syntax errors for optional property access in plain JS Plain JS files filter checker diagnostics through an allowlist, so the grammar errors for optional property access in assignment positions (TS2777-TS2781) were silently dropped even though they are runtime SyntaxErrors. Add the five codes to the allowlist and extend the plainJSGrammarErrors conformance test to cover them. Fixes #57640. Signed-off-by: MaheshDesu --- tsc/internal/compiler/program.go | 5 ++ .../plainJSGrammarErrors.errors.txt | 36 ++++++++- .../conformance/plainJSGrammarErrors.js | 28 +++++++ .../conformance/plainJSGrammarErrors.symbols | 52 +++++++++++++ .../conformance/plainJSGrammarErrors.types | 73 +++++++++++++++++++ .../conformance/salsa/plainJSGrammarErrors.ts | 13 ++++ 6 files changed, 206 insertions(+), 1 deletion(-) diff --git a/tsc/internal/compiler/program.go b/tsc/internal/compiler/program.go index 2c7aa08030d8a..ce275e56528a7 100644 --- a/tsc/internal/compiler/program.go +++ b/tsc/internal/compiler/program.go @@ -2475,7 +2475,12 @@ var plainJSErrors = collections.NewSetFromItems( diagnostics.Source_phase_imports_are_not_allowed_on_statements_that_compile_to_CommonJS_require_calls.Code(), diagnostics.Source_phase_imports_are_only_supported_when_the_module_option_is_set_to_esnext_nodenext_or_preserve.Code(), diagnostics.Tagged_template_expressions_are_not_permitted_in_an_optional_chain.Code(), + diagnostics.The_left_hand_side_of_a_for_in_statement_may_not_be_an_optional_property_access.Code(), + diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_an_optional_property_access.Code(), diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_async.Code(), + diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access.Code(), + diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access.Code(), + diagnostics.The_target_of_an_object_rest_assignment_may_not_be_an_optional_property_access.Code(), diagnostics.The_variable_declaration_of_a_for_in_statement_cannot_have_an_initializer.Code(), diagnostics.The_variable_declaration_of_a_for_of_statement_cannot_have_an_initializer.Code(), diagnostics.Trailing_comma_not_allowed.Code(), diff --git a/tsc/testdata/baselines/reference/conformance/plainJSGrammarErrors.errors.txt b/tsc/testdata/baselines/reference/conformance/plainJSGrammarErrors.errors.txt index d4de4272f915c..753e7f23e0d5c 100644 --- a/tsc/testdata/baselines/reference/conformance/plainJSGrammarErrors.errors.txt +++ b/tsc/testdata/baselines/reference/conformance/plainJSGrammarErrors.errors.txt @@ -99,9 +99,16 @@ plainJSGrammarErrors.js(202,22): error TS17012: 'targe' is not a valid meta-prop plainJSGrammarErrors.js(203,30): message TS1450: Dynamic imports can only accept a module specifier and an optional set of attributes as arguments plainJSGrammarErrors.js(204,30): message TS1450: Dynamic imports can only accept a module specifier and an optional set of attributes as arguments plainJSGrammarErrors.js(205,36): error TS1325: Argument of dynamic import cannot be spread element. +plainJSGrammarErrors.js(211,1): error TS2779: The left-hand side of an assignment expression may not be an optional property access. +plainJSGrammarErrors.js(212,1): error TS2779: The left-hand side of an assignment expression may not be an optional property access. +plainJSGrammarErrors.js(213,1): error TS2777: The operand of an increment or decrement operator may not be an optional property access. +plainJSGrammarErrors.js(214,7): error TS2778: The target of an object rest assignment may not be an optional property access. +plainJSGrammarErrors.js(215,6): error TS2780: The left-hand side of a 'for...in' statement may not be an optional property access. +plainJSGrammarErrors.js(216,6): error TS2781: The left-hand side of a 'for...of' statement may not be an optional property access. +plainJSGrammarErrors.js(218,6): error TS2780: The left-hand side of a 'for...in' statement may not be an optional property access. -==== plainJSGrammarErrors.js (101 errors) ==== +==== plainJSGrammarErrors.js (108 errors) ==== class C { // #private mistakes q = #unbound @@ -511,4 +518,31 @@ plainJSGrammarErrors.js(205,36): error TS1325: Argument of dynamic import cannot !!! error TS1325: Argument of dynamic import cannot be spread element. return + + // optional property access in assignment positions + let optObj = { a: 1, s: "x" }; + optObj?.a = 1; + ~~~~~~~~~ +!!! error TS2779: The left-hand side of an assignment expression may not be an optional property access. + optObj.a?.b = 1; + ~~~~~~~~~~~ +!!! error TS2779: The left-hand side of an assignment expression may not be an optional property access. + optObj?.a++; + ~~~~~~~~~ +!!! error TS2777: The operand of an increment or decrement operator may not be an optional property access. + ({ ...optObj?.a } = {}); + ~~~~~~~~~ +!!! error TS2778: The target of an object rest assignment may not be an optional property access. + for (optObj?.s in {}); + ~~~~~~~~~ +!!! error TS2780: The left-hand side of a 'for...in' statement may not be an optional property access. + for (optObj?.a of []); + ~~~~~~~~~ +!!! error TS2781: The left-hand side of a 'for...of' statement may not be an optional property access. + /** @type {any} */ let anyObj; + for (anyObj?.a in {}); + ~~~~~~~~~ +!!! error TS2780: The left-hand side of a 'for...in' statement may not be an optional property access. + optObj.a = 2; + optObj?.a; \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/conformance/plainJSGrammarErrors.js b/tsc/testdata/baselines/reference/conformance/plainJSGrammarErrors.js index 5de8565506b1d..f19ad4da0fc1b 100644 --- a/tsc/testdata/baselines/reference/conformance/plainJSGrammarErrors.js +++ b/tsc/testdata/baselines/reference/conformance/plainJSGrammarErrors.js @@ -208,6 +208,19 @@ const trinaryDynamicImport = import('1', '2', '3') const spreadDynamicImport = import(...[]) return + +// optional property access in assignment positions +let optObj = { a: 1, s: "x" }; +optObj?.a = 1; +optObj.a?.b = 1; +optObj?.a++; +({ ...optObj?.a } = {}); +for (optObj?.s in {}); +for (optObj?.a of []); +/** @type {any} */ let anyObj; +for (anyObj?.a in {}); +optObj.a = 2; +optObj?.a; //// [plainJSGrammarErrors.js] @@ -410,3 +423,18 @@ const nullaryDynamicImport = import(); const trinaryDynamicImport = import('1', '2', '3'); const spreadDynamicImport = import(...[]); return; +// optional property access in assignment positions +let optObj = { a: 1, s: "x" }; +optObj?.a = 1; +optObj.a?.b = 1; +optObj?.a++; +({ ...optObj?.a } = {}); +for (optObj?.s in {}) + ; +for (optObj?.a of []) + ; +/** @type {any} */ let anyObj; +for (anyObj?.a in {}) + ; +optObj.a = 2; +optObj?.a; diff --git a/tsc/testdata/baselines/reference/conformance/plainJSGrammarErrors.symbols b/tsc/testdata/baselines/reference/conformance/plainJSGrammarErrors.symbols index 38eb2a9e796d3..9d9851df5f9cf 100644 --- a/tsc/testdata/baselines/reference/conformance/plainJSGrammarErrors.symbols +++ b/tsc/testdata/baselines/reference/conformance/plainJSGrammarErrors.symbols @@ -469,3 +469,55 @@ const spreadDynamicImport = import(...[]) return +// optional property access in assignment positions +let optObj = { a: 1, s: "x" }; +>optObj : Symbol(optObj, Decl(plainJSGrammarErrors.js, 209, 3)) +>a : Symbol(a, Decl(plainJSGrammarErrors.js, 209, 14)) +>s : Symbol(s, Decl(plainJSGrammarErrors.js, 209, 20)) + +optObj?.a = 1; +>optObj?.a : Symbol(a, Decl(plainJSGrammarErrors.js, 209, 14)) +>optObj : Symbol(optObj, Decl(plainJSGrammarErrors.js, 209, 3)) +>a : Symbol(a, Decl(plainJSGrammarErrors.js, 209, 14)) + +optObj.a?.b = 1; +>optObj.a : Symbol(a, Decl(plainJSGrammarErrors.js, 209, 14)) +>optObj : Symbol(optObj, Decl(plainJSGrammarErrors.js, 209, 3)) +>a : Symbol(a, Decl(plainJSGrammarErrors.js, 209, 14)) + +optObj?.a++; +>optObj?.a : Symbol(a, Decl(plainJSGrammarErrors.js, 209, 14)) +>optObj : Symbol(optObj, Decl(plainJSGrammarErrors.js, 209, 3)) +>a : Symbol(a, Decl(plainJSGrammarErrors.js, 209, 14)) + +({ ...optObj?.a } = {}); +>optObj?.a : Symbol(a, Decl(plainJSGrammarErrors.js, 209, 14)) +>optObj : Symbol(optObj, Decl(plainJSGrammarErrors.js, 209, 3)) +>a : Symbol(a, Decl(plainJSGrammarErrors.js, 209, 14)) + +for (optObj?.s in {}); +>optObj?.s : Symbol(s, Decl(plainJSGrammarErrors.js, 209, 20)) +>optObj : Symbol(optObj, Decl(plainJSGrammarErrors.js, 209, 3)) +>s : Symbol(s, Decl(plainJSGrammarErrors.js, 209, 20)) + +for (optObj?.a of []); +>optObj?.a : Symbol(a, Decl(plainJSGrammarErrors.js, 209, 14)) +>optObj : Symbol(optObj, Decl(plainJSGrammarErrors.js, 209, 3)) +>a : Symbol(a, Decl(plainJSGrammarErrors.js, 209, 14)) + +/** @type {any} */ let anyObj; +>anyObj : Symbol(anyObj, Decl(plainJSGrammarErrors.js, 216, 22)) + +for (anyObj?.a in {}); +>anyObj : Symbol(anyObj, Decl(plainJSGrammarErrors.js, 216, 22)) + +optObj.a = 2; +>optObj.a : Symbol(a, Decl(plainJSGrammarErrors.js, 209, 14)) +>optObj : Symbol(optObj, Decl(plainJSGrammarErrors.js, 209, 3)) +>a : Symbol(a, Decl(plainJSGrammarErrors.js, 209, 14)) + +optObj?.a; +>optObj?.a : Symbol(a, Decl(plainJSGrammarErrors.js, 209, 14)) +>optObj : Symbol(optObj, Decl(plainJSGrammarErrors.js, 209, 3)) +>a : Symbol(a, Decl(plainJSGrammarErrors.js, 209, 14)) + diff --git a/tsc/testdata/baselines/reference/conformance/plainJSGrammarErrors.types b/tsc/testdata/baselines/reference/conformance/plainJSGrammarErrors.types index b944f7ee03b06..07c8fd513d293 100644 --- a/tsc/testdata/baselines/reference/conformance/plainJSGrammarErrors.types +++ b/tsc/testdata/baselines/reference/conformance/plainJSGrammarErrors.types @@ -627,3 +627,76 @@ const spreadDynamicImport = import(...[]) return +// optional property access in assignment positions +let optObj = { a: 1, s: "x" }; +>optObj : { a: number; s: string; } +>{ a: 1, s: "x" } : { a: number; s: string; } +>a : number +>1 : 1 +>s : string +>"x" : "x" + +optObj?.a = 1; +>optObj?.a = 1 : 1 +>optObj?.a : number +>optObj : { a: number; s: string; } +>a : number +>1 : 1 + +optObj.a?.b = 1; +>optObj.a?.b = 1 : 1 +>optObj.a?.b : any +>optObj.a : number +>optObj : { a: number; s: string; } +>a : number +>b : any +>1 : 1 + +optObj?.a++; +>optObj?.a++ : number +>optObj?.a : number +>optObj : { a: number; s: string; } +>a : number + +({ ...optObj?.a } = {}); +>({ ...optObj?.a } = {}) : {} +>{ ...optObj?.a } = {} : {} +>{ ...optObj?.a } : any +>optObj?.a : number +>optObj : { a: number; s: string; } +>a : number +>{} : {} + +for (optObj?.s in {}); +>optObj?.s : string +>optObj : { a: number; s: string; } +>s : string +>{} : {} + +for (optObj?.a of []); +>optObj?.a : number +>optObj : { a: number; s: string; } +>a : number +>[] : never[] + +/** @type {any} */ let anyObj; +>anyObj : any + +for (anyObj?.a in {}); +>anyObj?.a : any +>anyObj : any +>a : any +>{} : {} + +optObj.a = 2; +>optObj.a = 2 : 2 +>optObj.a : number +>optObj : { a: number; s: string; } +>a : number +>2 : 2 + +optObj?.a; +>optObj?.a : number +>optObj : { a: number; s: string; } +>a : number + diff --git a/tsc/testdata/tests/cases/conformance/salsa/plainJSGrammarErrors.ts b/tsc/testdata/tests/cases/conformance/salsa/plainJSGrammarErrors.ts index 9697e9f8ac386..b429bbba76153 100644 --- a/tsc/testdata/tests/cases/conformance/salsa/plainJSGrammarErrors.ts +++ b/tsc/testdata/tests/cases/conformance/salsa/plainJSGrammarErrors.ts @@ -210,3 +210,16 @@ const trinaryDynamicImport = import('1', '2', '3') const spreadDynamicImport = import(...[]) return + +// optional property access in assignment positions +let optObj = { a: 1, s: "x" }; +optObj?.a = 1; +optObj.a?.b = 1; +optObj?.a++; +({ ...optObj?.a } = {}); +for (optObj?.s in {}); +for (optObj?.a of []); +/** @type {any} */ let anyObj; +for (anyObj?.a in {}); +optObj.a = 2; +optObj?.a;