diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 345ea00ce1437..fef9b230133cd 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -15422,6 +15422,25 @@ func (c *Checker) getCannotResolveModuleNameErrorForSpecificModule(moduleName *a return nil } +// Returns a more specific error when a failed non-relative module import matches a global +// namespace declaration, e.g. `declare module foo { }`, which declares a namespace rather +// than an ambient module. See https://github.com/microsoft/TypeScript/issues/23185. +func (c *Checker) getCannotResolveModuleNameErrorForGlobalNamespace(moduleReference string) *diagnostics.Message { + if tspath.IsExternalModuleNameRelative(moduleReference) { + return nil + } + symbol := c.getGlobalSymbol(moduleReference, ast.SymbolFlagsModule, nil /*diagnostic*/) + if symbol == nil { + return nil + } + for _, declaration := range symbol.Declarations() { + if ast.IsModuleDeclaration(declaration) && !ast.IsStringLiteral(declaration.Name()) { + return diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations_Did_you_mean_to_use_the_global_namespace_0_If_you_meant_to_declare_an_ambient_module_the_name_must_be_quoted_Colon_declare_module_0 + } + } + return nil +} + func (c *Checker) resolveExternalModuleNameWorker(location *ast.Node, moduleReferenceExpression *ast.Node, moduleNotFoundError *diagnostics.Message, ignoreErrors bool, isForAugmentation bool, importAttributesType *Type) *ast.Symbol { if ast.IsStringLiteralLike(moduleReferenceExpression) { if ast.IsSourcePhaseImport(moduleReferenceExpression.Parent) { @@ -15716,6 +15735,11 @@ func (c *Checker) resolveExternalModule( } if moduleNotFoundError != nil { + if moduleNotFoundError == diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations { + if betterError := c.getCannotResolveModuleNameErrorForGlobalNamespace(moduleReference); betterError != nil { + moduleNotFoundError = betterError + } + } // See if this was possibly a projectReference redirect if resolvedModule.IsResolved() { redirect := c.program.GetProjectReferenceFromSource( @@ -15873,6 +15897,13 @@ func (c *Checker) errorOnImplicitAnyModule(isError bool, errorNode *ast.Node, mo return } + if isError { + if globalNamespaceError := c.getCannotResolveModuleNameErrorForGlobalNamespace(moduleReference); globalNamespaceError != nil { + c.error(errorNode, globalNamespaceError, moduleReference) + return + } + } + var errorInfo *ast.Diagnostic if !tspath.IsExternalModuleNameRelative(moduleReference) && resolvedModule.PackageId.Name != "" { errorInfo = c.createModuleNotFoundChain(resolvedModule, errorNode, moduleReference, mode, resolvedModule.PackageId.Name) diff --git a/tsc/internal/diagnostics/diagnosticMessages.generated.json b/tsc/internal/diagnostics/diagnosticMessages.generated.json index 52c3ab10ac45e..3d9ad196baf04 100644 --- a/tsc/internal/diagnostics/diagnosticMessages.generated.json +++ b/tsc/internal/diagnostics/diagnosticMessages.generated.json @@ -1951,6 +1951,7 @@ "Source_phase_imports_are_not_allowed_on_statements_that_compile_to_CommonJS_require_calls_18115": "Source phase imports are not allowed on statements that compile to CommonJS 'require' calls.", "This_initializer_has_a_Symbol_asyncDispose_method_Did_you_mean_to_use_await_using_18116": "This initializer has a '[Symbol.asyncDispose]()' method. Did you mean to use 'await using'?", "A_deferred_import_must_specify_a_namespace_binding_18117": "A deferred import must specify a namespace binding.", + "Cannot_find_module_0_or_its_corresponding_type_declarations_Did_you_mean_to_use_the_global_namespace_18118": "Cannot find module '{0}' or its corresponding type declarations. Did you mean to use the global namespace '{0}'? If you meant to declare an ambient module, the name must be quoted: `declare module '{0}';`.", "nodenext_if_module_is_nodenext_node16_if_module_is_node16_or_node18_otherwise_bundler_69010": "`nodenext` if `module` is `nodenext`; `node16` if `module` is `node16` or `node18`; otherwise, `bundler`.", "File_is_a_CommonJS_module_it_may_be_converted_to_an_ES_module_80001": "File is a CommonJS module; it may be converted to an ES module.", "This_constructor_function_may_be_converted_to_a_class_declaration_80002": "This constructor function may be converted to a class declaration.", diff --git a/tsc/internal/diagnostics/diagnosticMessages.json b/tsc/internal/diagnostics/diagnosticMessages.json index 18e7048874771..e7af9b8248671 100644 --- a/tsc/internal/diagnostics/diagnosticMessages.json +++ b/tsc/internal/diagnostics/diagnosticMessages.json @@ -8923,5 +8923,9 @@ "A deferred import must specify a namespace binding.": { "category": "Error", "code": 18117 + }, + "Cannot find module '{0}' or its corresponding type declarations. Did you mean to use the global namespace '{0}'? If you meant to declare an ambient module, the name must be quoted: `declare module '{0}';`.": { + "category": "Error", + "code": 18118 } } diff --git a/tsc/internal/diagnostics/diagnostics_generated.go b/tsc/internal/diagnostics/diagnostics_generated.go index 9bca165033942..2293e14b172d0 100644 --- a/tsc/internal/diagnostics/diagnostics_generated.go +++ b/tsc/internal/diagnostics/diagnostics_generated.go @@ -3906,6 +3906,8 @@ var This_initializer_has_a_Symbol_asyncDispose_method_Did_you_mean_to_use_await_ var A_deferred_import_must_specify_a_namespace_binding = &Message{code: 18117, category: CategoryError, key: "A_deferred_import_must_specify_a_namespace_binding_18117", text: "A deferred import must specify a namespace binding."} +var Cannot_find_module_0_or_its_corresponding_type_declarations_Did_you_mean_to_use_the_global_namespace_0_If_you_meant_to_declare_an_ambient_module_the_name_must_be_quoted_Colon_declare_module_0 = &Message{code: 18118, category: CategoryError, key: "Cannot_find_module_0_or_its_corresponding_type_declarations_Did_you_mean_to_use_the_global_namespace_18118", text: "Cannot find module '{0}' or its corresponding type declarations. Did you mean to use the global namespace '{0}'? If you meant to declare an ambient module, the name must be quoted: `declare module '{0}';`."} + var X_nodenext_if_module_is_nodenext_node16_if_module_is_node16_or_node18_otherwise_bundler = &Message{code: 69010, category: CategoryMessage, key: "nodenext_if_module_is_nodenext_node16_if_module_is_node16_or_node18_otherwise_bundler_69010", text: "`nodenext` if `module` is `nodenext`; `node16` if `module` is `node16` or `node18`; otherwise, `bundler`."} var File_is_a_CommonJS_module_it_may_be_converted_to_an_ES_module = &Message{code: 80001, category: CategorySuggestion, key: "File_is_a_CommonJS_module_it_may_be_converted_to_an_ES_module_80001", text: "File is a CommonJS module; it may be converted to an ES module."} @@ -6399,6 +6401,7 @@ var allMessages = [...]**Message{ &Source_phase_imports_are_not_allowed_on_statements_that_compile_to_CommonJS_require_calls, &This_initializer_has_a_Symbol_asyncDispose_method_Did_you_mean_to_use_await_using, &A_deferred_import_must_specify_a_namespace_binding, + &Cannot_find_module_0_or_its_corresponding_type_declarations_Did_you_mean_to_use_the_global_namespace_0_If_you_meant_to_declare_an_ambient_module_the_name_must_be_quoted_Colon_declare_module_0, &X_nodenext_if_module_is_nodenext_node16_if_module_is_node16_or_node18_otherwise_bundler, &File_is_a_CommonJS_module_it_may_be_converted_to_an_ES_module, &This_constructor_function_may_be_converted_to_a_class_declaration, diff --git a/tsc/testdata/baselines/reference/compiler/cannotFindModuleWithGlobalNamespace.errors.txt b/tsc/testdata/baselines/reference/compiler/cannotFindModuleWithGlobalNamespace.errors.txt new file mode 100644 index 0000000000000..d5d94842e536f --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/cannotFindModuleWithGlobalNamespace.errors.txt @@ -0,0 +1,41 @@ +globals.d.ts(1,16): error TS1540: A 'namespace' declaration should not be declared using the 'module' keyword. Please use the 'namespace' keyword instead. +globals.d.ts(4,16): error TS1540: A 'namespace' declaration should not be declared using the 'module' keyword. Please use the 'namespace' keyword instead. +main.ts(2,27): error TS18118: Cannot find module 'messageformat' or its corresponding type declarations. Did you mean to use the global namespace 'messageformat'? If you meant to declare an ambient module, the name must be quoted: `declare module 'messageformat';`. +main.ts(3,19): error TS18118: Cannot find module 'otherlib' or its corresponding type declarations. Did you mean to use the global namespace 'otherlib'? If you meant to declare an ambient module, the name must be quoted: `declare module 'otherlib';`. +main.ts(4,27): error TS2307: Cannot find module 'not-declared-globally' or its corresponding type declarations. + + +==== main.ts (3 errors) ==== + /// + import MessageFormat from "messageformat"; + ~~~~~~~~~~~~~~~ +!!! error TS18118: Cannot find module 'messageformat' or its corresponding type declarations. Did you mean to use the global namespace 'messageformat'? If you meant to declare an ambient module, the name must be quoted: `declare module 'messageformat';`. + import Other from "otherlib"; + ~~~~~~~~~~ +!!! error TS18118: Cannot find module 'otherlib' or its corresponding type declarations. Did you mean to use the global namespace 'otherlib'? If you meant to declare an ambient module, the name must be quoted: `declare module 'otherlib';`. + import { something } from "not-declared-globally"; + ~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2307: Cannot find module 'not-declared-globally' or its corresponding type declarations. + +==== /node_modules/messageformat/package.json (0 errors) ==== + { + "name": "messageformat", + "version": "1.0.0", + "main": "lib/messageformat.js" + } + +==== /node_modules/messageformat/lib/messageformat.js (0 errors) ==== + module.exports = function messageformat() {}; + +==== globals.d.ts (2 errors) ==== + declare module messageformat { + ~~~~~~~~~~~~~ +!!! error TS1540: A 'namespace' declaration should not be declared using the 'module' keyword. Please use the 'namespace' keyword instead. + export type Msg = (params: {}) => string; + } + declare module otherlib { + ~~~~~~~~ +!!! error TS1540: A 'namespace' declaration should not be declared using the 'module' keyword. Please use the 'namespace' keyword instead. + export type Other = string; + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/cannotFindModuleWithGlobalNamespace.js b/tsc/testdata/baselines/reference/compiler/cannotFindModuleWithGlobalNamespace.js new file mode 100644 index 0000000000000..f906ff915a0a8 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/cannotFindModuleWithGlobalNamespace.js @@ -0,0 +1,29 @@ +//// [tests/cases/compiler/cannotFindModuleWithGlobalNamespace.ts] //// + +//// [package.json] +{ + "name": "messageformat", + "version": "1.0.0", + "main": "lib/messageformat.js" +} + +//// [messageformat.js] +module.exports = function messageformat() {}; + +//// [globals.d.ts] +declare module messageformat { + export type Msg = (params: {}) => string; +} +declare module otherlib { + export type Other = string; +} + +//// [main.ts] +/// +import MessageFormat from "messageformat"; +import Other from "otherlib"; +import { something } from "not-declared-globally"; + + +//// [main.js] +export {}; diff --git a/tsc/testdata/baselines/reference/compiler/cannotFindModuleWithGlobalNamespace.symbols b/tsc/testdata/baselines/reference/compiler/cannotFindModuleWithGlobalNamespace.symbols new file mode 100644 index 0000000000000..4da84a9866fbd --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/cannotFindModuleWithGlobalNamespace.symbols @@ -0,0 +1,28 @@ +//// [tests/cases/compiler/cannotFindModuleWithGlobalNamespace.ts] //// + +=== main.ts === +/// +import MessageFormat from "messageformat"; +>MessageFormat : Symbol(MessageFormat, Decl(main.ts, 1, 6)) + +import Other from "otherlib"; +>Other : Symbol(Other, Decl(main.ts, 2, 6)) + +import { something } from "not-declared-globally"; +>something : Symbol(something, Decl(main.ts, 3, 8)) + +=== globals.d.ts === +declare module messageformat { +>messageformat : Symbol(messageformat, Decl(globals.d.ts, 0, 0)) + + export type Msg = (params: {}) => string; +>Msg : Symbol(Msg, Decl(globals.d.ts, 0, 30)) +>params : Symbol(params, Decl(globals.d.ts, 1, 23)) +} +declare module otherlib { +>otherlib : Symbol(otherlib, Decl(globals.d.ts, 2, 1)) + + export type Other = string; +>Other : Symbol(Other, Decl(globals.d.ts, 3, 25)) +} + diff --git a/tsc/testdata/baselines/reference/compiler/cannotFindModuleWithGlobalNamespace.types b/tsc/testdata/baselines/reference/compiler/cannotFindModuleWithGlobalNamespace.types new file mode 100644 index 0000000000000..ee64aed5d1dec --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/cannotFindModuleWithGlobalNamespace.types @@ -0,0 +1,24 @@ +//// [tests/cases/compiler/cannotFindModuleWithGlobalNamespace.ts] //// + +=== main.ts === +/// +import MessageFormat from "messageformat"; +>MessageFormat : any + +import Other from "otherlib"; +>Other : any + +import { something } from "not-declared-globally"; +>something : any + +=== globals.d.ts === +declare module messageformat { + export type Msg = (params: {}) => string; +>Msg : Msg +>params : {} +} +declare module otherlib { + export type Other = string; +>Other : string +} + diff --git a/tsc/testdata/tests/cases/compiler/cannotFindModuleWithGlobalNamespace.ts b/tsc/testdata/tests/cases/compiler/cannotFindModuleWithGlobalNamespace.ts new file mode 100644 index 0000000000000..946ebd09304bf --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/cannotFindModuleWithGlobalNamespace.ts @@ -0,0 +1,26 @@ +// @strict: true +// @noImplicitReferences: true + +// @Filename: /node_modules/messageformat/package.json +{ + "name": "messageformat", + "version": "1.0.0", + "main": "lib/messageformat.js" +} + +// @Filename: /node_modules/messageformat/lib/messageformat.js +module.exports = function messageformat() {}; + +// @Filename: globals.d.ts +declare module messageformat { + export type Msg = (params: {}) => string; +} +declare module otherlib { + export type Other = string; +} + +// @Filename: main.ts +/// +import MessageFormat from "messageformat"; +import Other from "otherlib"; +import { something } from "not-declared-globally";