From 47bcdf35af65f6492c28a47a4a298ea530b0147f Mon Sep 17 00:00:00 2001 From: Gabriel Date: Wed, 21 Oct 2026 19:12:38 +0100 Subject: [PATCH] fix(jsx): diagnose automatic JSX in script files Refs microsoft/TypeScript#64438 --- tsc/internal/checker/jsx.go | 5 +++++ .../diagnosticMessages.generated.json | 1 + .../diagnostics/diagnosticMessages.json | 4 ++++ .../diagnostics/diagnostics_generated.go | 3 +++ ...commonjs,moduledetection=legacy).errors.txt | 4 ++-- ...commonjs,moduledetection=legacy).errors.txt | 4 ++-- .../compiler/jsxScriptFile.errors.txt | 18 ++++++++++++++++++ .../reference/compiler/jsxScriptFile.js | 16 ++++++++++++++++ .../reference/compiler/jsxScriptFile.symbols | 18 ++++++++++++++++++ .../reference/compiler/jsxScriptFile.types | 16 ++++++++++++++++ .../tests/cases/compiler/jsxScriptFile.tsx | 13 +++++++++++++ 11 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 tsc/testdata/baselines/reference/compiler/jsxScriptFile.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/jsxScriptFile.js create mode 100644 tsc/testdata/baselines/reference/compiler/jsxScriptFile.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/jsxScriptFile.types create mode 100644 tsc/testdata/tests/cases/compiler/jsxScriptFile.tsx diff --git a/tsc/internal/checker/jsx.go b/tsc/internal/checker/jsx.go index 7f35f796957cf..a4a651e4c9e9d 100644 --- a/tsc/internal/checker/jsx.go +++ b/tsc/internal/checker/jsx.go @@ -1473,6 +1473,11 @@ func (c *Checker) getJsxNamespaceContainerForImplicitImport(location *ast.Node) if moduleReference == "" { return nil } + if !ast.IsExternalOrCommonJSModule(file) { + c.error(canonicalErrorTag, diagnostics.This_JSX_tag_cannot_be_used_in_a_script_file_Consider_adding_an_empty_export_statement_to_make_this_file_a_module) + links.jsxImplicitImportContainer = c.unknownSymbol + return nil + } errorMessage := diagnostics.This_JSX_tag_requires_the_module_path_0_to_exist_but_none_could_be_found_Make_sure_you_have_types_for_the_appropriate_package_installed mod := c.resolveExternalModule(core.OrElse(specifier, canonicalErrorTag), moduleReference, errorMessage, canonicalErrorTag, false, nil /*importAttributesType*/) var result *ast.Symbol diff --git a/tsc/internal/diagnostics/diagnosticMessages.generated.json b/tsc/internal/diagnostics/diagnosticMessages.generated.json index bf7f327e371c0..011e9af060421 100644 --- a/tsc/internal/diagnostics/diagnosticMessages.generated.json +++ b/tsc/internal/diagnostics/diagnosticMessages.generated.json @@ -1010,6 +1010,7 @@ "This_expression_is_never_nullish_2881": "This expression is never nullish.", "Cannot_find_module_or_type_declarations_for_side_effect_import_of_0_2882": "Cannot find module or type declarations for side-effect import of '{0}'.", "The_inferred_type_of_0_cannot_be_named_without_a_reference_to_2_from_1_This_is_likely_not_portable_A_2883": "The inferred type of '{0}' cannot be named without a reference to '{2}' from '{1}'. This is likely not portable. A type annotation is necessary.", + "This_JSX_tag_cannot_be_used_in_a_script_file_Consider_adding_an_empty_export_statement_to_make_this__2884": "This JSX tag cannot be used in a script file. Consider adding an empty 'export {}' statement to make this file a module.", "Import_declaration_0_is_using_private_name_1_4000": "Import declaration '{0}' is using private name '{1}'.", "Type_parameter_0_of_exported_class_has_or_is_using_private_name_1_4002": "Type parameter '{0}' of exported class has or is using private name '{1}'.", "Type_parameter_0_of_exported_interface_has_or_is_using_private_name_1_4004": "Type parameter '{0}' of exported interface has or is using private name '{1}'.", diff --git a/tsc/internal/diagnostics/diagnosticMessages.json b/tsc/internal/diagnostics/diagnosticMessages.json index 895ede2e4fa92..d99dd9e5dcaf2 100644 --- a/tsc/internal/diagnostics/diagnosticMessages.json +++ b/tsc/internal/diagnostics/diagnosticMessages.json @@ -4022,6 +4022,10 @@ "category": "Error", "code": 2875 }, + "This JSX tag cannot be used in a script file. Consider adding an empty 'export {}' statement to make this file a module.": { + "category": "Error", + "code": 2884 + }, "This relative import path is unsafe to rewrite because it looks like a file name, but actually resolves to \"{0}\".": { "category": "Error", "code": 2876 diff --git a/tsc/internal/diagnostics/diagnostics_generated.go b/tsc/internal/diagnostics/diagnostics_generated.go index fbd75c71bbe08..b5e07587bd763 100644 --- a/tsc/internal/diagnostics/diagnostics_generated.go +++ b/tsc/internal/diagnostics/diagnostics_generated.go @@ -2024,6 +2024,8 @@ var Cannot_find_module_or_type_declarations_for_side_effect_import_of_0 = &Messa var The_inferred_type_of_0_cannot_be_named_without_a_reference_to_2_from_1_This_is_likely_not_portable_A_type_annotation_is_necessary = &Message{code: 2883, category: CategoryError, key: "The_inferred_type_of_0_cannot_be_named_without_a_reference_to_2_from_1_This_is_likely_not_portable_A_2883", text: "The inferred type of '{0}' cannot be named without a reference to '{2}' from '{1}'. This is likely not portable. A type annotation is necessary."} +var This_JSX_tag_cannot_be_used_in_a_script_file_Consider_adding_an_empty_export_statement_to_make_this_file_a_module = &Message{code: 2884, category: CategoryError, key: "This_JSX_tag_cannot_be_used_in_a_script_file_Consider_adding_an_empty_export_statement_to_make_this__2884", text: "This JSX tag cannot be used in a script file. Consider adding an empty 'export {}' statement to make this file a module."} + var Import_declaration_0_is_using_private_name_1 = &Message{code: 4000, category: CategoryError, key: "Import_declaration_0_is_using_private_name_1_4000", text: "Import declaration '{0}' is using private name '{1}'."} var Type_parameter_0_of_exported_class_has_or_is_using_private_name_1 = &Message{code: 4002, category: CategoryError, key: "Type_parameter_0_of_exported_class_has_or_is_using_private_name_1_4002", text: "Type parameter '{0}' of exported class has or is using private name '{1}'."} @@ -5474,6 +5476,7 @@ var allMessages = [...]**Message{ &This_expression_is_never_nullish, &Cannot_find_module_or_type_declarations_for_side_effect_import_of_0, &The_inferred_type_of_0_cannot_be_named_without_a_reference_to_2_from_1_This_is_likely_not_portable_A_type_annotation_is_necessary, + &This_JSX_tag_cannot_be_used_in_a_script_file_Consider_adding_an_empty_export_statement_to_make_this_file_a_module, &Import_declaration_0_is_using_private_name_1, &Type_parameter_0_of_exported_class_has_or_is_using_private_name_1, &Type_parameter_0_of_exported_interface_has_or_is_using_private_name_1, diff --git a/tsc/testdata/baselines/reference/compiler/commentsOnJSXExpressionsArePreserved(jsx=react-jsx,module=commonjs,moduledetection=legacy).errors.txt b/tsc/testdata/baselines/reference/compiler/commentsOnJSXExpressionsArePreserved(jsx=react-jsx,module=commonjs,moduledetection=legacy).errors.txt index f710481bd1f17..1db934efaebe0 100644 --- a/tsc/testdata/baselines/reference/compiler/commentsOnJSXExpressionsArePreserved(jsx=react-jsx,module=commonjs,moduledetection=legacy).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/commentsOnJSXExpressionsArePreserved(jsx=react-jsx,module=commonjs,moduledetection=legacy).errors.txt @@ -1,5 +1,5 @@ commentsOnJSXExpressionsArePreserved.tsx(5,16): error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. -commentsOnJSXExpressionsArePreserved.tsx(5,16): error TS2875: This JSX tag requires the module path 'react/jsx-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed. +commentsOnJSXExpressionsArePreserved.tsx(5,16): error TS2884: This JSX tag cannot be used in a script file. Consider adding an empty 'export {}' statement to make this file a module. commentsOnJSXExpressionsArePreserved.tsx(18,9): error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. @@ -38,7 +38,7 @@ commentsOnJSXExpressionsArePreserved.tsx(18,9): error TS7026: JSX element implic ~~~~~~~~~~~~~~~~~~~~~~~~ ; ~~~~~~~~~~~~~~ -!!! error TS2875: This JSX tag requires the module path 'react/jsx-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed. +!!! error TS2884: This JSX tag cannot be used in a script file. Consider adding an empty 'export {}' statement to make this file a module. ~~~~~~ !!! error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. } diff --git a/tsc/testdata/baselines/reference/compiler/commentsOnJSXExpressionsArePreserved(jsx=react-jsxdev,module=commonjs,moduledetection=legacy).errors.txt b/tsc/testdata/baselines/reference/compiler/commentsOnJSXExpressionsArePreserved(jsx=react-jsxdev,module=commonjs,moduledetection=legacy).errors.txt index 164f9da0004b7..1db934efaebe0 100644 --- a/tsc/testdata/baselines/reference/compiler/commentsOnJSXExpressionsArePreserved(jsx=react-jsxdev,module=commonjs,moduledetection=legacy).errors.txt +++ b/tsc/testdata/baselines/reference/compiler/commentsOnJSXExpressionsArePreserved(jsx=react-jsxdev,module=commonjs,moduledetection=legacy).errors.txt @@ -1,5 +1,5 @@ commentsOnJSXExpressionsArePreserved.tsx(5,16): error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. -commentsOnJSXExpressionsArePreserved.tsx(5,16): error TS2875: This JSX tag requires the module path 'react/jsx-dev-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed. +commentsOnJSXExpressionsArePreserved.tsx(5,16): error TS2884: This JSX tag cannot be used in a script file. Consider adding an empty 'export {}' statement to make this file a module. commentsOnJSXExpressionsArePreserved.tsx(18,9): error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. @@ -38,7 +38,7 @@ commentsOnJSXExpressionsArePreserved.tsx(18,9): error TS7026: JSX element implic ~~~~~~~~~~~~~~~~~~~~~~~~ ; ~~~~~~~~~~~~~~ -!!! error TS2875: This JSX tag requires the module path 'react/jsx-dev-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed. +!!! error TS2884: This JSX tag cannot be used in a script file. Consider adding an empty 'export {}' statement to make this file a module. ~~~~~~ !!! error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. } diff --git a/tsc/testdata/baselines/reference/compiler/jsxScriptFile.errors.txt b/tsc/testdata/baselines/reference/compiler/jsxScriptFile.errors.txt new file mode 100644 index 0000000000000..20726c7fb2d6a --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/jsxScriptFile.errors.txt @@ -0,0 +1,18 @@ +index.tsx(1,11): error TS2884: This JSX tag cannot be used in a script file. Consider adding an empty 'export {}' statement to make this file a module. +index.tsx(1,11): error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. + + +==== node_modules/solid-js/jsx-runtime.d.ts (0 errors) ==== + export namespace JSX { + interface IntrinsicElements { + div: {}; + } + } + +==== index.tsx (2 errors) ==== + const x =
; + ~~~~~~~ +!!! error TS2884: This JSX tag cannot be used in a script file. Consider adding an empty 'export {}' statement to make this file a module. + ~~~~~~~ +!!! error TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists. + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/jsxScriptFile.js b/tsc/testdata/baselines/reference/compiler/jsxScriptFile.js new file mode 100644 index 0000000000000..b77ca17a17666 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/jsxScriptFile.js @@ -0,0 +1,16 @@ +//// [tests/cases/compiler/jsxScriptFile.tsx] //// + +//// [jsx-runtime.d.ts] +export namespace JSX { + interface IntrinsicElements { + div: {}; + } +} + +//// [index.tsx] +const x =
; + + +//// [index.js] +"use strict"; +const x = _jsx("div", {}); diff --git a/tsc/testdata/baselines/reference/compiler/jsxScriptFile.symbols b/tsc/testdata/baselines/reference/compiler/jsxScriptFile.symbols new file mode 100644 index 0000000000000..2b288424db451 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/jsxScriptFile.symbols @@ -0,0 +1,18 @@ +//// [tests/cases/compiler/jsxScriptFile.tsx] //// + +=== node_modules/solid-js/jsx-runtime.d.ts === +export namespace JSX { +>JSX : Symbol(JSX, Decl(jsx-runtime.d.ts, 0, 0)) + + interface IntrinsicElements { +>IntrinsicElements : Symbol(IntrinsicElements, Decl(jsx-runtime.d.ts, 0, 22)) + + div: {}; +>div : Symbol(IntrinsicElements.div, Decl(jsx-runtime.d.ts, 1, 33)) + } +} + +=== index.tsx === +const x =
; +>x : Symbol(x, Decl(index.tsx, 0, 5)) + diff --git a/tsc/testdata/baselines/reference/compiler/jsxScriptFile.types b/tsc/testdata/baselines/reference/compiler/jsxScriptFile.types new file mode 100644 index 0000000000000..5f6175a8e3bde --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/jsxScriptFile.types @@ -0,0 +1,16 @@ +//// [tests/cases/compiler/jsxScriptFile.tsx] //// + +=== node_modules/solid-js/jsx-runtime.d.ts === +export namespace JSX { + interface IntrinsicElements { + div: {}; +>div : {} + } +} + +=== index.tsx === +const x =
; +>x : any +>
: any +>div : any + diff --git a/tsc/testdata/tests/cases/compiler/jsxScriptFile.tsx b/tsc/testdata/tests/cases/compiler/jsxScriptFile.tsx new file mode 100644 index 0000000000000..20c894c8e7702 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/jsxScriptFile.tsx @@ -0,0 +1,13 @@ +// @jsx: react-jsx +// @jsxImportSource: solid-js +// @moduleDetection: legacy + +// @filename: node_modules/solid-js/jsx-runtime.d.ts +export namespace JSX { + interface IntrinsicElements { + div: {}; + } +} + +// @filename: index.tsx +const x =
;