diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0c577535bc6f6..2237a49a3955a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -294,16 +294,20 @@ namespace ts { } export function createTypeChecker(host: TypeCheckerHost, produceDiagnostics: boolean): TypeChecker { - const getPackagesSet = memoize(() => { - const set = new Set(); + const getPackagesMap = memoize(() => { + // A package name maps to true when we detect it has .d.ts files. + // This is useful as an approximation of whether a package bundles its own types. + // Note: we only look at files already found by module resolution, + // so there may be files we did not consider. + const map = new Map(); host.getSourceFiles().forEach(sf => { if (!sf.resolvedModules) return; forEachEntry(sf.resolvedModules, r => { - if (r && r.packageId) set.add(r.packageId.name); + if (r && r.packageId) map.set(r.packageId.name, r.extension === Extension.Dts || !!map.get(r.packageId.name)); }); }); - return set; + return map; }); // Cancellation that controls whether or not we can cancel in the middle of type checking. @@ -3416,11 +3420,17 @@ namespace ts { /*details*/ undefined, Diagnostics.If_the_0_package_actually_exposes_this_module_consider_sending_a_pull_request_to_amend_https_Colon_Slash_Slashgithub_com_SlashDefinitelyTyped_SlashDefinitelyTyped_Slashtree_Slashmaster_Slashtypes_Slash_1, packageId.name, mangleScopedPackageName(packageId.name)) - : chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.Try_npm_i_save_dev_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0, - moduleReference, - mangleScopedPackageName(packageId.name)) + : packageBundlesTypes(packageId.name) + ? chainDiagnosticMessages( + /*details*/ undefined, + Diagnostics.If_the_0_package_actually_exposes_this_module_try_adding_a_new_declaration_d_ts_file_containing_declare_module_1, + packageId.name, + moduleReference) + : chainDiagnosticMessages( + /*details*/ undefined, + Diagnostics.Try_npm_i_save_dev_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0, + moduleReference, + mangleScopedPackageName(packageId.name)) : undefined; errorOrSuggestion(isError, errorNode, chainDiagnosticMessages( errorInfo, @@ -3429,7 +3439,10 @@ namespace ts { resolvedFileName)); } function typesPackageExists(packageName: string): boolean { - return getPackagesSet().has(getTypesPackageName(packageName)); + return getPackagesMap().has(getTypesPackageName(packageName)); + } + function packageBundlesTypes(packageName: string): boolean { + return !!getPackagesMap().get(packageName); } function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 91780f5309c8d..b4e155d03634f 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -6027,6 +6027,11 @@ "category": "Error", "code": 7057 }, + "If the '{0}' package actually exposes this module, try adding a new declaration (.d.ts) file containing `declare module '{1}';`": { + "category": "Error", + "code": 7058 + }, + "You cannot rename this element.": { "category": "Error", diff --git a/tests/baselines/reference/declarationNotFoundPackageBundlesTypes.errors.txt b/tests/baselines/reference/declarationNotFoundPackageBundlesTypes.errors.txt new file mode 100644 index 0000000000000..352ccd6d0712c --- /dev/null +++ b/tests/baselines/reference/declarationNotFoundPackageBundlesTypes.errors.txt @@ -0,0 +1,27 @@ +tests/cases/conformance/moduleResolution/index.ts(2,24): error TS7016: Could not find a declaration file for module 'foo/other'. 'tests/cases/conformance/moduleResolution/node_modules/foo/other.js' implicitly has an 'any' type. + If the 'foo' package actually exposes this module, try adding a new declaration (.d.ts) file containing `declare module 'foo/other';` + + +==== tests/cases/conformance/moduleResolution/index.ts (1 errors) ==== + import * as Foo from "foo"; + import * as Other from "foo/other"/*1*/; + ~~~~~~~~~~~ +!!! error TS7016: Could not find a declaration file for module 'foo/other'. 'tests/cases/conformance/moduleResolution/node_modules/foo/other.js' implicitly has an 'any' type. +!!! error TS7016: If the 'foo' package actually exposes this module, try adding a new declaration (.d.ts) file containing `declare module 'foo/other';` +==== tests/cases/conformance/moduleResolution/node_modules/foo/package.json (0 errors) ==== + { + "name": "foo", + "version": "1.0.0" + } + +==== tests/cases/conformance/moduleResolution/node_modules/foo/index.js (0 errors) ==== + var foo = 0; + module.exports = foo; + +==== tests/cases/conformance/moduleResolution/node_modules/foo/index.d.ts (0 errors) ==== + declare const foo: any; + export = foo; + +==== tests/cases/conformance/moduleResolution/node_modules/foo/other.js (0 errors) ==== + module.exports = {}; + \ No newline at end of file diff --git a/tests/baselines/reference/declarationNotFoundPackageBundlesTypes.js b/tests/baselines/reference/declarationNotFoundPackageBundlesTypes.js new file mode 100644 index 0000000000000..300c235512cc3 --- /dev/null +++ b/tests/baselines/reference/declarationNotFoundPackageBundlesTypes.js @@ -0,0 +1,26 @@ +//// [tests/cases/conformance/moduleResolution/declarationNotFoundPackageBundlesTypes.ts] //// + +//// [package.json] +{ + "name": "foo", + "version": "1.0.0" +} + +//// [index.js] +var foo = 0; +module.exports = foo; + +//// [index.d.ts] +declare const foo: any; +export = foo; + +//// [other.js] +module.exports = {}; + +//// [index.ts] +import * as Foo from "foo"; +import * as Other from "foo/other"/*1*/; + +//// [index.js] +"use strict"; +exports.__esModule = true; diff --git a/tests/baselines/reference/declarationNotFoundPackageBundlesTypes.symbols b/tests/baselines/reference/declarationNotFoundPackageBundlesTypes.symbols new file mode 100644 index 0000000000000..0727eb72fe3be --- /dev/null +++ b/tests/baselines/reference/declarationNotFoundPackageBundlesTypes.symbols @@ -0,0 +1,14 @@ +=== tests/cases/conformance/moduleResolution/index.ts === +import * as Foo from "foo"; +>Foo : Symbol(Foo, Decl(index.ts, 0, 6)) + +import * as Other from "foo/other"/*1*/; +>Other : Symbol(Other, Decl(index.ts, 1, 6)) + +=== tests/cases/conformance/moduleResolution/node_modules/foo/index.d.ts === +declare const foo: any; +>foo : Symbol(foo, Decl(index.d.ts, 0, 13)) + +export = foo; +>foo : Symbol(foo, Decl(index.d.ts, 0, 13)) + diff --git a/tests/baselines/reference/declarationNotFoundPackageBundlesTypes.types b/tests/baselines/reference/declarationNotFoundPackageBundlesTypes.types new file mode 100644 index 0000000000000..3db6344286db8 --- /dev/null +++ b/tests/baselines/reference/declarationNotFoundPackageBundlesTypes.types @@ -0,0 +1,14 @@ +=== tests/cases/conformance/moduleResolution/index.ts === +import * as Foo from "foo"; +>Foo : any + +import * as Other from "foo/other"/*1*/; +>Other : any + +=== tests/cases/conformance/moduleResolution/node_modules/foo/index.d.ts === +declare const foo: any; +>foo : any + +export = foo; +>foo : any + diff --git a/tests/cases/conformance/moduleResolution/declarationNotFoundPackageBundlesTypes.ts b/tests/cases/conformance/moduleResolution/declarationNotFoundPackageBundlesTypes.ts new file mode 100644 index 0000000000000..75c681daf04a7 --- /dev/null +++ b/tests/cases/conformance/moduleResolution/declarationNotFoundPackageBundlesTypes.ts @@ -0,0 +1,23 @@ +// @noImplicitAny: true +// @noImplicitReferences: true + +// @filename: node_modules/foo/package.json +{ + "name": "foo", + "version": "1.0.0" +} + +// @filename: node_modules/foo/index.js +var foo = 0; +module.exports = foo; + +// @filename: node_modules/foo/index.d.ts +declare const foo: any; +export = foo; + +// @filename: node_modules/foo/other.js +module.exports = {}; + +// @filename: index.ts +import * as Foo from "foo"; +import * as Other from "foo/other"/*1*/; \ No newline at end of file