diff --git a/src/compiler/program.ts b/src/compiler/program.ts index fb74f1dcaab33..2e4492efa7b14 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -779,13 +779,18 @@ namespace ts { while (true) { const baseName = getBaseFileName(directory); if (baseName !== "node_modules") { - const result = - // first: try to load module as-is - loadModuleFromNodeModulesFolder(moduleName, directory, failedLookupLocations, state) || - // second: try to load module from the scope '@types' - loadModuleFromNodeModulesFolder(combinePaths("@types", moduleName), directory, failedLookupLocations, state); - if (result) { - return result; + // Try to load source from the package + const packageResult = loadModuleFromNodeModulesFolder(moduleName, directory, failedLookupLocations, state); + if (packageResult && hasTypeScriptFileExtension(packageResult)) { + // Always prefer a TypeScript (.ts, .tsx, .d.ts) file shipped with the package + return packageResult; + } + else { + // Else prefer a types package over non-TypeScript results (e.g. JavaScript files) + const typesResult = loadModuleFromNodeModulesFolder(combinePaths("@types", moduleName), directory, failedLookupLocations, state); + if (typesResult || packageResult) { + return typesResult || packageResult; + } } } @@ -1097,7 +1102,7 @@ namespace ts { const modulesWithElidedImports: Map = {}; // Track source files that are JavaScript files found by searching under node_modules, as these shouldn't be compiled. - const jsFilesFoundSearchingNodeModules: Map = {}; + const sourceFilesFoundSearchingNodeModules: Map = {}; const start = new Date().getTime(); @@ -1378,7 +1383,7 @@ namespace ts { getSourceFile: program.getSourceFile, getSourceFileByPath: program.getSourceFileByPath, getSourceFiles: program.getSourceFiles, - getFilesFromNodeModules: () => jsFilesFoundSearchingNodeModules, + isSourceFileFromExternalLibrary: (file: SourceFile) => !!lookUp(sourceFilesFoundSearchingNodeModules, file.path), writeFile: writeFileCallback || ( (fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)), isEmitBlocked, @@ -2066,15 +2071,17 @@ namespace ts { // - noResolve is falsy // - module name comes from the list of imports // - it's not a top level JavaScript module that exceeded the search max - const isJsFileUnderNodeModules = resolution && resolution.isExternalLibraryImport && - hasJavaScriptFileExtension(resolution.resolvedFileName); + const isFromNodeModulesSearch = resolution && resolution.isExternalLibraryImport; + const isJsFileFromNodeModules = isFromNodeModulesSearch && hasJavaScriptFileExtension(resolution.resolvedFileName); - if (isJsFileUnderNodeModules) { - jsFilesFoundSearchingNodeModules[resolvedPath] = true; + if (isFromNodeModulesSearch) { + sourceFilesFoundSearchingNodeModules[resolvedPath] = true; + } + if (isJsFileFromNodeModules) { currentNodeModulesJsDepth++; } - const elideImport = isJsFileUnderNodeModules && currentNodeModulesJsDepth > maxNodeModulesJsDepth; + const elideImport = isJsFileFromNodeModules && currentNodeModulesJsDepth > maxNodeModulesJsDepth; const shouldAddFile = resolution && !options.noResolve && i < file.imports.length && !elideImport; if (elideImport) { @@ -2089,7 +2096,7 @@ namespace ts { file.imports[i].end); } - if (isJsFileUnderNodeModules) { + if (isJsFileFromNodeModules) { currentNodeModulesJsDepth--; } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index e8f2832cd592e..7892af6624f3c 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -36,7 +36,7 @@ namespace ts { getSourceFiles(): SourceFile[]; /* @internal */ - getFilesFromNodeModules(): Map; + isSourceFileFromExternalLibrary(file: SourceFile): boolean; getCommonSourceDirectory(): string; getCanonicalFileName(fileName: string): string; @@ -2277,10 +2277,9 @@ namespace ts { } else { const sourceFiles = targetSourceFile === undefined ? host.getSourceFiles() : [targetSourceFile]; - const nodeModulesFiles = host.getFilesFromNodeModules(); for (const sourceFile of sourceFiles) { // Don't emit if source file is a declaration file, or was located under node_modules - if (!isDeclarationFile(sourceFile) && !lookUp(nodeModulesFiles, sourceFile.path)) { + if (!isDeclarationFile(sourceFile) && !host.isSourceFileFromExternalLibrary(sourceFile)) { onSingleFileEmit(host, sourceFile); } } @@ -2314,10 +2313,9 @@ namespace ts { function onBundledEmit(host: EmitHost) { // Can emit only sources that are not declaration file and are either non module code or module with // --module or --target es6 specified. Files included by searching under node_modules are also not emitted. - const nodeModulesFiles = host.getFilesFromNodeModules(); const bundledSources = filter(host.getSourceFiles(), sourceFile => !isDeclarationFile(sourceFile) && - !lookUp(nodeModulesFiles, sourceFile.path) && + !host.isSourceFileFromExternalLibrary(sourceFile) && (!isExternalModule(sourceFile) || !!getEmitModuleKind(options))); if (bundledSources.length) { diff --git a/tests/baselines/reference/moduleAugmentationInDependency2.js b/tests/baselines/reference/moduleAugmentationInDependency2.js index 381f1e72d8f31..0a5d83695a34f 100644 --- a/tests/baselines/reference/moduleAugmentationInDependency2.js +++ b/tests/baselines/reference/moduleAugmentationInDependency2.js @@ -8,8 +8,6 @@ export {}; //// [app.ts] import "A" -//// [index.js] -"use strict"; //// [app.js] "use strict"; require("A"); diff --git a/tests/baselines/reference/pathMappingBasedModuleResolution5_node.js b/tests/baselines/reference/pathMappingBasedModuleResolution5_node.js index 1958800f91893..e4440299cc75c 100644 --- a/tests/baselines/reference/pathMappingBasedModuleResolution5_node.js +++ b/tests/baselines/reference/pathMappingBasedModuleResolution5_node.js @@ -31,9 +31,6 @@ exports.x = 1; //// [file2.js] "use strict"; exports.y = 1; -//// [file4.js] -"use strict"; -exports.z1 = 1; //// [file1.js] "use strict"; var file1_1 = require("folder2/file1"); diff --git a/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/amd/maxDepthIncreased/root.js b/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/amd/maxDepthIncreased/root.js index 77951a4889da7..9ef3915c85125 100644 --- a/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/amd/maxDepthIncreased/root.js +++ b/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/amd/maxDepthIncreased/root.js @@ -1,6 +1,7 @@ -define(["require", "exports", "m1"], function (require, exports, m1) { +define(["require", "exports", "m1", "m4"], function (require, exports, m1, m4) { "use strict"; m1.f1("test"); m1.f2.a = 10; - m1.f2.person.age = "10"; // Error: Should be number + m1.f2.person.age = "10"; // Should error if loaded the .js files correctly + var r2 = 3 + m4.foo; // Should be OK if correctly using the @types .d.ts file }); diff --git a/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/amd/nodeModulesMaxDepthIncreased.errors.txt b/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/amd/nodeModulesMaxDepthIncreased.errors.txt index f63c2a789e8c4..f511000d5ac4a 100644 --- a/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/amd/nodeModulesMaxDepthIncreased.errors.txt +++ b/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/amd/nodeModulesMaxDepthIncreased.errors.txt @@ -1,4 +1,4 @@ -maxDepthIncreased/root.ts(4,1): error TS2322: Type 'string' is not assignable to type 'number'. +maxDepthIncreased/root.ts(7,1): error TS2322: Type 'string' is not assignable to type 'number'. ==== index.js (0 errors) ==== @@ -28,11 +28,19 @@ maxDepthIncreased/root.ts(4,1): error TS2322: Type 'string' is not assignable to exports.f2 = m2; +==== entry.d.ts (0 errors) ==== + export declare var foo: number; + ==== maxDepthIncreased/root.ts (1 errors) ==== import * as m1 from "m1"; + import * as m4 from "m4"; + m1.f1("test"); m1.f2.a = 10; - m1.f2.person.age = "10"; // Error: Should be number + + m1.f2.person.age = "10"; // Should error if loaded the .js files correctly ~~~~~~~~~~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'number'. + + let r2 = 3 + m4.foo; // Should be OK if correctly using the @types .d.ts file \ No newline at end of file diff --git a/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/amd/nodeModulesMaxDepthIncreased.json b/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/amd/nodeModulesMaxDepthIncreased.json index 1350bf6441e25..bb3234f1db227 100644 --- a/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/amd/nodeModulesMaxDepthIncreased.json +++ b/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/amd/nodeModulesMaxDepthIncreased.json @@ -10,6 +10,7 @@ "maxDepthIncreased/node_modules/m2/node_modules/m3/index.js", "maxDepthIncreased/node_modules/m2/entry.js", "maxDepthIncreased/node_modules/m1/index.js", + "maxDepthIncreased/node_modules/@types/m4/entry.d.ts", "maxDepthIncreased/root.ts" ], "emittedFiles": [ diff --git a/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/node/maxDepthIncreased/root.js b/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/node/maxDepthIncreased/root.js index 3a0a96991b058..f6783cda79b04 100644 --- a/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/node/maxDepthIncreased/root.js +++ b/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/node/maxDepthIncreased/root.js @@ -1,5 +1,7 @@ "use strict"; var m1 = require("m1"); +var m4 = require("m4"); m1.f1("test"); m1.f2.a = 10; -m1.f2.person.age = "10"; // Error: Should be number +m1.f2.person.age = "10"; // Should error if loaded the .js files correctly +var r2 = 3 + m4.foo; // Should be OK if correctly using the @types .d.ts file diff --git a/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/node/nodeModulesMaxDepthIncreased.errors.txt b/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/node/nodeModulesMaxDepthIncreased.errors.txt index f63c2a789e8c4..f511000d5ac4a 100644 --- a/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/node/nodeModulesMaxDepthIncreased.errors.txt +++ b/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/node/nodeModulesMaxDepthIncreased.errors.txt @@ -1,4 +1,4 @@ -maxDepthIncreased/root.ts(4,1): error TS2322: Type 'string' is not assignable to type 'number'. +maxDepthIncreased/root.ts(7,1): error TS2322: Type 'string' is not assignable to type 'number'. ==== index.js (0 errors) ==== @@ -28,11 +28,19 @@ maxDepthIncreased/root.ts(4,1): error TS2322: Type 'string' is not assignable to exports.f2 = m2; +==== entry.d.ts (0 errors) ==== + export declare var foo: number; + ==== maxDepthIncreased/root.ts (1 errors) ==== import * as m1 from "m1"; + import * as m4 from "m4"; + m1.f1("test"); m1.f2.a = 10; - m1.f2.person.age = "10"; // Error: Should be number + + m1.f2.person.age = "10"; // Should error if loaded the .js files correctly ~~~~~~~~~~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'number'. + + let r2 = 3 + m4.foo; // Should be OK if correctly using the @types .d.ts file \ No newline at end of file diff --git a/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/node/nodeModulesMaxDepthIncreased.json b/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/node/nodeModulesMaxDepthIncreased.json index 1350bf6441e25..bb3234f1db227 100644 --- a/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/node/nodeModulesMaxDepthIncreased.json +++ b/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/node/nodeModulesMaxDepthIncreased.json @@ -10,6 +10,7 @@ "maxDepthIncreased/node_modules/m2/node_modules/m3/index.js", "maxDepthIncreased/node_modules/m2/entry.js", "maxDepthIncreased/node_modules/m1/index.js", + "maxDepthIncreased/node_modules/@types/m4/entry.d.ts", "maxDepthIncreased/root.ts" ], "emittedFiles": [ diff --git a/tests/cases/projects/NodeModulesSearch/maxDepthIncreased/node_modules/@types/m4/entry.d.ts b/tests/cases/projects/NodeModulesSearch/maxDepthIncreased/node_modules/@types/m4/entry.d.ts new file mode 100644 index 0000000000000..f0fc245910a1a --- /dev/null +++ b/tests/cases/projects/NodeModulesSearch/maxDepthIncreased/node_modules/@types/m4/entry.d.ts @@ -0,0 +1 @@ +export declare var foo: number; diff --git a/tests/cases/projects/NodeModulesSearch/maxDepthIncreased/node_modules/@types/m4/package.json b/tests/cases/projects/NodeModulesSearch/maxDepthIncreased/node_modules/@types/m4/package.json new file mode 100644 index 0000000000000..53ac1558a23ce --- /dev/null +++ b/tests/cases/projects/NodeModulesSearch/maxDepthIncreased/node_modules/@types/m4/package.json @@ -0,0 +1,5 @@ +{ + "types": "entry.d.ts", + "name": "m4", + "version": "1.0.0" +} \ No newline at end of file diff --git a/tests/cases/projects/NodeModulesSearch/maxDepthIncreased/node_modules/m4/entry.js b/tests/cases/projects/NodeModulesSearch/maxDepthIncreased/node_modules/m4/entry.js new file mode 100644 index 0000000000000..a1cb557b18d8a --- /dev/null +++ b/tests/cases/projects/NodeModulesSearch/maxDepthIncreased/node_modules/m4/entry.js @@ -0,0 +1 @@ +exports.test = "hello, world"; diff --git a/tests/cases/projects/NodeModulesSearch/maxDepthIncreased/node_modules/m4/package.json b/tests/cases/projects/NodeModulesSearch/maxDepthIncreased/node_modules/m4/package.json new file mode 100644 index 0000000000000..4ae99f312fc0f --- /dev/null +++ b/tests/cases/projects/NodeModulesSearch/maxDepthIncreased/node_modules/m4/package.json @@ -0,0 +1,5 @@ +{ + "name": "m4", + "version": "1.0.0", + "main": "entry.js" +} \ No newline at end of file diff --git a/tests/cases/projects/NodeModulesSearch/maxDepthIncreased/root.ts b/tests/cases/projects/NodeModulesSearch/maxDepthIncreased/root.ts index 9ed943bebba36..2b80564ae26b9 100644 --- a/tests/cases/projects/NodeModulesSearch/maxDepthIncreased/root.ts +++ b/tests/cases/projects/NodeModulesSearch/maxDepthIncreased/root.ts @@ -1,4 +1,9 @@ import * as m1 from "m1"; +import * as m4 from "m4"; + m1.f1("test"); m1.f2.a = 10; -m1.f2.person.age = "10"; // Error: Should be number + +m1.f2.person.age = "10"; // Should error if loaded the .js files correctly + +let r2 = 3 + m4.foo; // Should be OK if correctly using the @types .d.ts file