From cd3cb064d0a70a598ab184f6adda5958e2829730 Mon Sep 17 00:00:00 2001 From: vaibhavsrv Date: Tue, 8 Sep 2026 22:07:24 +0530 Subject: [PATCH 1/2] Fix: disallow optional call chaining on import.defer expressions (#63679) --- tsc/internal/checker/grammarchecks.go | 2 +- tsc/internal/parser/parser.go | 2 +- .../conformance/importDeferOptionalChain.errors.txt | 11 +++++++++++ .../reference/conformance/importDeferOptionalChain.js | 9 +++++++++ .../conformance/importDeferOptionalChain.symbols | 6 ++++++ .../conformance/importDeferOptionalChain.types | 8 ++++++++ .../importDefer/importDeferOptionalChain.ts | 4 ++++ 7 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.errors.txt create mode 100644 tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.js create mode 100644 tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.symbols create mode 100644 tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.types create mode 100644 tsc/testdata/tests/cases/conformance/importDefer/importDeferOptionalChain.ts diff --git a/tsc/internal/checker/grammarchecks.go b/tsc/internal/checker/grammarchecks.go index cc274d8b2fd4d..8275da757bbba 100644 --- a/tsc/internal/checker/grammarchecks.go +++ b/tsc/internal/checker/grammarchecks.go @@ -2168,7 +2168,7 @@ func (c *Checker) checkGrammarImportCallExpression(node *ast.Node) bool { } nodeAsCall := node.AsCallExpression() - if nodeAsCall.TypeArguments != nil { + if nodeAsCall.TypeArguments != nil || nodeAsCall.QuestionDotToken != nil { return c.grammarErrorOnNode(node, diagnostics.This_use_of_import_is_invalid_import_calls_can_be_written_but_they_must_have_parentheses_and_cannot_have_type_arguments) } diff --git a/tsc/internal/parser/parser.go b/tsc/internal/parser/parser.go index 8eef8b7a7da8d..a33bd55654412 100644 --- a/tsc/internal/parser/parser.go +++ b/tsc/internal/parser/parser.go @@ -5245,7 +5245,7 @@ func (p *Parser) parseLeftHandSideExpressionOrHigher() *ast.Expression { p.nextToken() // advance past the dot expression = p.finishNode(p.factory.NewMetaProperty(ast.KindImportKeyword, p.parseIdentifierName()), pos) if expression.Text() == "defer" { - if p.token == ast.KindOpenParenToken || p.token == ast.KindLessThanToken { + if p.token == ast.KindOpenParenToken || p.token == ast.KindLessThanToken || p.token == ast.KindQuestionDotToken { p.sourceFlags |= ast.NodeFlagsPossiblyContainsDynamicImport } } else { diff --git a/tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.errors.txt b/tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.errors.txt new file mode 100644 index 0000000000000..9b49845bb1909 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.errors.txt @@ -0,0 +1,11 @@ +importDeferOptionalChain.ts(1,1): error TS1326: This use of 'import' is invalid. 'import()' calls can be written, but they must have parentheses and cannot have type arguments. +importDeferOptionalChain.ts(1,16): error TS2307: Cannot find module './a' or its corresponding type declarations. + + +==== importDeferOptionalChain.ts (2 errors) ==== + import.defer?.("./a"); + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS1326: This use of 'import' is invalid. 'import()' calls can be written, but they must have parentheses and cannot have type arguments. + ~~~~~ +!!! error TS2307: Cannot find module './a' or its corresponding type declarations. + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.js b/tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.js new file mode 100644 index 0000000000000..b53bd6104d5b6 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.js @@ -0,0 +1,9 @@ +//// [tests/cases/conformance/importDefer/importDeferOptionalChain.ts] //// + +//// [importDeferOptionalChain.ts] +import.defer?.("./a"); + + +//// [importDeferOptionalChain.js] +"use strict"; +import.defer?.("./a"); diff --git a/tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.symbols b/tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.symbols new file mode 100644 index 0000000000000..3fb2655dc86b0 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.symbols @@ -0,0 +1,6 @@ +//// [tests/cases/conformance/importDefer/importDeferOptionalChain.ts] //// + +=== importDeferOptionalChain.ts === + +import.defer?.("./a"); + diff --git a/tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.types b/tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.types new file mode 100644 index 0000000000000..e4fbf942fbaff --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.types @@ -0,0 +1,8 @@ +//// [tests/cases/conformance/importDefer/importDeferOptionalChain.ts] //// + +=== importDeferOptionalChain.ts === +import.defer?.("./a"); +>import.defer?.("./a") : Promise +>defer : any +>"./a" : "./a" + diff --git a/tsc/testdata/tests/cases/conformance/importDefer/importDeferOptionalChain.ts b/tsc/testdata/tests/cases/conformance/importDefer/importDeferOptionalChain.ts new file mode 100644 index 0000000000000..b13867189205e --- /dev/null +++ b/tsc/testdata/tests/cases/conformance/importDefer/importDeferOptionalChain.ts @@ -0,0 +1,4 @@ +// @target: esnext +// @module: esnext + +import.defer?.("./a"); From 1c9c21b175b851e044b65ef7e85f8ccb26e6e7b2 Mon Sep 17 00:00:00 2001 From: vaibhavsrv Date: Wed, 9 Sep 2026 10:15:07 +0530 Subject: [PATCH 2/2] checker: emit distinct diagnostic for import() in optional chains --- tsc/internal/checker/grammarchecks.go | 5 ++++- tsc/internal/diagnostics/diagnostics_generated.go | 4 ++++ tsc/internal/diagnostics/extraDiagnosticMessages.json | 4 ++++ tsc/internal/diagnostics/loc_generated.go | 5 ++--- .../conformance/importDeferOptionalChain.errors.txt | 4 ++-- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/tsc/internal/checker/grammarchecks.go b/tsc/internal/checker/grammarchecks.go index 8275da757bbba..8d038ebec092d 100644 --- a/tsc/internal/checker/grammarchecks.go +++ b/tsc/internal/checker/grammarchecks.go @@ -2168,9 +2168,12 @@ func (c *Checker) checkGrammarImportCallExpression(node *ast.Node) bool { } nodeAsCall := node.AsCallExpression() - if nodeAsCall.TypeArguments != nil || nodeAsCall.QuestionDotToken != nil { + if nodeAsCall.TypeArguments != nil { return c.grammarErrorOnNode(node, diagnostics.This_use_of_import_is_invalid_import_calls_can_be_written_but_they_must_have_parentheses_and_cannot_have_type_arguments) } + if nodeAsCall.QuestionDotToken != nil { + return c.grammarErrorOnNode(node, diagnostics.Calls_to_import_are_not_permitted_in_an_optional_chain) + } nodeArguments := nodeAsCall.Arguments argumentNodes := nodeArguments.Nodes diff --git a/tsc/internal/diagnostics/diagnostics_generated.go b/tsc/internal/diagnostics/diagnostics_generated.go index e323564b5d377..89e818db5d51a 100644 --- a/tsc/internal/diagnostics/diagnostics_generated.go +++ b/tsc/internal/diagnostics/diagnostics_generated.go @@ -4424,6 +4424,8 @@ var The_invalid_diagnostic_directive_is_in_supplemental_output_0_returned_by_the var Diagnostic_directive_0_returned_by_the_content_mapper_has_an_invalid_unusedExpectDirectiveIndex = &Message{code: 100068, category: CategoryMessage, key: "Diagnostic_directive_0_returned_by_the_content_mapper_has_an_invalid_unusedExpectDirectiveIndex_100068", text: "Diagnostic directive {0} returned by the content mapper has an invalid 'unusedExpectDirectiveIndex'."} +var Calls_to_import_are_not_permitted_in_an_optional_chain = &Message{code: 100069, category: CategoryError, key: "Calls_to_import_are_not_permitted_in_an_optional_chain_100069", text: "Calls to 'import()' are not permitted in an optional chain."} + func keyToMessage(key Key) *Message { switch key { case "Unterminated_string_literal_1002": @@ -8848,6 +8850,8 @@ func keyToMessage(key Key) *Message { return The_invalid_diagnostic_directive_is_in_supplemental_output_0_returned_by_the_content_mapper case "Diagnostic_directive_0_returned_by_the_content_mapper_has_an_invalid_unusedExpectDirectiveIndex_100068": return Diagnostic_directive_0_returned_by_the_content_mapper_has_an_invalid_unusedExpectDirectiveIndex + case "Calls_to_import_are_not_permitted_in_an_optional_chain_100069": + return Calls_to_import_are_not_permitted_in_an_optional_chain default: return nil } diff --git a/tsc/internal/diagnostics/extraDiagnosticMessages.json b/tsc/internal/diagnostics/extraDiagnosticMessages.json index 41c7b8d8abc35..e8e9db7e82578 100644 --- a/tsc/internal/diagnostics/extraDiagnosticMessages.json +++ b/tsc/internal/diagnostics/extraDiagnosticMessages.json @@ -338,5 +338,9 @@ "Diagnostic directive {0} returned by the content mapper has an invalid 'unusedExpectDirectiveIndex'.": { "category": "Message", "code": 100068 + }, + "Calls to 'import()' are not permitted in an optional chain.": { + "category": "Error", + "code": 100069 } } diff --git a/tsc/internal/diagnostics/loc_generated.go b/tsc/internal/diagnostics/loc_generated.go index 769956b1762a2..c57838c45276b 100644 --- a/tsc/internal/diagnostics/loc_generated.go +++ b/tsc/internal/diagnostics/loc_generated.go @@ -5,11 +5,10 @@ package diagnostics import ( "compress/gzip" _ "embed" - "strings" - "sync" - "github.com/microsoft/TypeScript/tsc/internal/json" "golang.org/x/text/language" + "strings" + "sync" ) var matcher = language.NewMatcher([]language.Tag{ diff --git a/tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.errors.txt b/tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.errors.txt index 9b49845bb1909..5dce7fa4638ff 100644 --- a/tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.errors.txt +++ b/tsc/testdata/baselines/reference/conformance/importDeferOptionalChain.errors.txt @@ -1,11 +1,11 @@ -importDeferOptionalChain.ts(1,1): error TS1326: This use of 'import' is invalid. 'import()' calls can be written, but they must have parentheses and cannot have type arguments. +importDeferOptionalChain.ts(1,1): error TS100069: Calls to 'import()' are not permitted in an optional chain. importDeferOptionalChain.ts(1,16): error TS2307: Cannot find module './a' or its corresponding type declarations. ==== importDeferOptionalChain.ts (2 errors) ==== import.defer?.("./a"); ~~~~~~~~~~~~~~~~~~~~~ -!!! error TS1326: This use of 'import' is invalid. 'import()' calls can be written, but they must have parentheses and cannot have type arguments. +!!! error TS100069: Calls to 'import()' are not permitted in an optional chain. ~~~~~ !!! error TS2307: Cannot find module './a' or its corresponding type declarations. \ No newline at end of file