diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go
index 10ddb3c0c88b4..81b1583771e00 100644
--- a/tsc/internal/checker/checker.go
+++ b/tsc/internal/checker/checker.go
@@ -15418,6 +15418,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) {
@@ -15729,6 +15748,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(
@@ -15886,6 +15910,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 bf7f327e371c0..024e38b08974c 100644
--- a/tsc/internal/diagnostics/diagnosticMessages.generated.json
+++ b/tsc/internal/diagnostics/diagnosticMessages.generated.json
@@ -1959,6 +1959,7 @@
"The_typescript_contentMapper_outputExtensions_of_the_content_mapper_package_0_must_be_an_object_with_18122": "The 'typescript.contentMapper.outputExtensions' of the content mapper package '{0}' must be an object with string values.",
"Content_mapper_extension_0_must_not_contain_path_separators_18123": "Content mapper extension '{0}' must not contain path separators.",
"The_content_mapper_package_0_is_outside_the_open_workspace_and_will_not_be_loaded_18124": "The content mapper package '{0}' is outside the open workspace and will not be loaded.",
+ "Cannot_find_module_0_or_its_corresponding_type_declarations_Did_you_mean_to_use_the_global_namespace_18125": "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 895ede2e4fa92..ea51cdbb2f24e 100644
--- a/tsc/internal/diagnostics/diagnosticMessages.json
+++ b/tsc/internal/diagnostics/diagnosticMessages.json
@@ -8955,5 +8955,9 @@
"Content mapper extension '{0}' must not contain path separators.": {
"category": "Error",
"code": 18123
+ },
+ "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": 18125
}
}
diff --git a/tsc/internal/diagnostics/diagnostics_generated.go b/tsc/internal/diagnostics/diagnostics_generated.go
index fbd75c71bbe08..791820011ef03 100644
--- a/tsc/internal/diagnostics/diagnostics_generated.go
+++ b/tsc/internal/diagnostics/diagnostics_generated.go
@@ -3922,6 +3922,8 @@ var Content_mapper_extension_0_must_not_contain_path_separators = &Message{code:
var The_content_mapper_package_0_is_outside_the_open_workspace_and_will_not_be_loaded = &Message{code: 18124, category: CategoryError, key: "The_content_mapper_package_0_is_outside_the_open_workspace_and_will_not_be_loaded_18124", text: "The content mapper package '{0}' is outside the open workspace and will not be loaded."}
+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: 18125, category: CategoryError, key: "Cannot_find_module_0_or_its_corresponding_type_declarations_Did_you_mean_to_use_the_global_namespace_18125", 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."}
@@ -6423,6 +6425,7 @@ var allMessages = [...]**Message{
&The_typescript_contentMapper_outputExtensions_of_the_content_mapper_package_0_must_be_an_object_with_string_values,
&Content_mapper_extension_0_must_not_contain_path_separators,
&The_content_mapper_package_0_is_outside_the_open_workspace_and_will_not_be_loaded,
+ &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..b739729ec07de
--- /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 TS18125: 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 TS18125: 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 TS18125: 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 TS18125: 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";