diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index ca702173e79..4ebdc59e6dd 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -95,7 +95,7 @@ namespace ts { currentDirectory = host.getCurrentDirectory(); } - return currentDirectory && getDefaultTypeRoots(currentDirectory, host); + return currentDirectory !== undefined && getDefaultTypeRoots(currentDirectory, host); } /** @@ -675,23 +675,33 @@ namespace ts { /* @internal */ export function loadModuleFromNodeModules(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState, checkOneLevel: boolean): string { + return loadModuleFromNodeModulesWorker(moduleName, directory, failedLookupLocations, state, checkOneLevel, /*typesOnly*/ false); + } + + function loadModuleFromNodeModulesAtTypes(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState): string { + return loadModuleFromNodeModulesWorker(moduleName, directory, failedLookupLocations, state, /*checkOneLevel*/ false, /*typesOnly*/ true); + } + + function loadModuleFromNodeModulesWorker(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState, checkOneLevel: boolean, typesOnly: boolean): string { directory = normalizeSlashes(directory); while (true) { const baseName = getBaseFileName(directory); if (baseName !== "node_modules") { - // 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; + let packageResult: string | undefined; + if (!typesOnly) { + // Try to load source from the package + 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 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; + } } const parentPath = getDirectoryPath(directory); @@ -709,7 +719,7 @@ namespace ts { const state = { compilerOptions, host, traceEnabled, skipTsx: !compilerOptions.jsx }; const failedLookupLocations: string[] = []; const supportedExtensions = getSupportedExtensions(compilerOptions); - let containingDirectory = getDirectoryPath(containingFile); + const containingDirectory = getDirectoryPath(containingFile); const resolvedFileName = tryLoadModuleUsingOptionalResolutionSettings(moduleName, containingDirectory, loadModuleFromFile, failedLookupLocations, supportedExtensions, state); if (resolvedFileName) { @@ -718,18 +728,9 @@ namespace ts { let referencedSourceFile: string; if (moduleHasNonRelativeName(moduleName)) { - while (true) { - const searchName = normalizePath(combinePaths(containingDirectory, moduleName)); - referencedSourceFile = loadModuleFromFile(searchName, supportedExtensions, failedLookupLocations, /*onlyRecordFailures*/ false, state); - if (referencedSourceFile) { - break; - } - const parentPath = getDirectoryPath(containingDirectory); - if (parentPath === containingDirectory) { - break; - } - containingDirectory = parentPath; - } + referencedSourceFile = referencedSourceFile = loadModuleFromAncestorDirectories(moduleName, containingDirectory, supportedExtensions, failedLookupLocations, state) || + // If we didn't find the file normally, look it up in @types. + loadModuleFromNodeModulesAtTypes(moduleName, containingDirectory, failedLookupLocations, state); } else { const candidate = normalizePath(combinePaths(containingDirectory, moduleName)); @@ -741,4 +742,20 @@ namespace ts { ? { resolvedModule: { resolvedFileName: referencedSourceFile }, failedLookupLocations } : { resolvedModule: undefined, failedLookupLocations }; } + + /** Climb up parent directories looking for a module. */ + function loadModuleFromAncestorDirectories(moduleName: string, containingDirectory: string, supportedExtensions: string[], failedLookupLocations: string[], state: ModuleResolutionState): string | undefined { + while (true) { + const searchName = normalizePath(combinePaths(containingDirectory, moduleName)); + const referencedSourceFile = loadModuleFromFile(searchName, supportedExtensions, failedLookupLocations, /*onlyRecordFailures*/ false, state); + if (referencedSourceFile) { + return referencedSourceFile; + } + const parentPath = getDirectoryPath(containingDirectory); + if (parentPath === containingDirectory) { + return undefined; + } + containingDirectory = parentPath; + } + } } \ No newline at end of file diff --git a/tests/baselines/reference/typingsLookupAmd.js b/tests/baselines/reference/typingsLookupAmd.js new file mode 100644 index 00000000000..1e99644d489 --- /dev/null +++ b/tests/baselines/reference/typingsLookupAmd.js @@ -0,0 +1,18 @@ +//// [tests/cases/conformance/typings/typingsLookupAmd.ts] //// + +//// [index.d.ts] + +export declare class A {} + +//// [index.d.ts] +import {A} from "a"; +export declare class B extends A {} + +//// [foo.ts] +import {B} from "b"; + + +//// [foo.js] +define(["require", "exports"], function (require, exports) { + "use strict"; +}); diff --git a/tests/baselines/reference/typingsLookupAmd.symbols b/tests/baselines/reference/typingsLookupAmd.symbols new file mode 100644 index 00000000000..8235467daf8 --- /dev/null +++ b/tests/baselines/reference/typingsLookupAmd.symbols @@ -0,0 +1,17 @@ +=== /x/y/foo.ts === +import {B} from "b"; +>B : Symbol(B, Decl(foo.ts, 0, 8)) + +=== /node_modules/@types/a/index.d.ts === + +export declare class A {} +>A : Symbol(A, Decl(index.d.ts, 0, 0)) + +=== /x/node_modules/@types/b/index.d.ts === +import {A} from "a"; +>A : Symbol(A, Decl(index.d.ts, 0, 8)) + +export declare class B extends A {} +>B : Symbol(B, Decl(index.d.ts, 0, 20)) +>A : Symbol(A, Decl(index.d.ts, 0, 8)) + diff --git a/tests/baselines/reference/typingsLookupAmd.trace.json b/tests/baselines/reference/typingsLookupAmd.trace.json new file mode 100644 index 00000000000..5c83757454c --- /dev/null +++ b/tests/baselines/reference/typingsLookupAmd.trace.json @@ -0,0 +1,59 @@ +[ + "======== Resolving module 'b' from '/x/y/foo.ts'. ========", + "Module resolution kind is not specified, using 'Classic'.", + "File '/x/y/b.ts' does not exist.", + "File '/x/y/b.d.ts' does not exist.", + "File '/x/b.ts' does not exist.", + "File '/x/b.d.ts' does not exist.", + "File '/b.ts' does not exist.", + "File '/b.d.ts' does not exist.", + "File '/x/y/node_modules/@types/b.ts' does not exist.", + "File '/x/y/node_modules/@types/b.d.ts' does not exist.", + "File '/x/y/node_modules/@types/b/package.json' does not exist.", + "File '/x/y/node_modules/@types/b/index.ts' does not exist.", + "File '/x/y/node_modules/@types/b/index.d.ts' does not exist.", + "File '/x/node_modules/@types/b.ts' does not exist.", + "File '/x/node_modules/@types/b.d.ts' does not exist.", + "File '/x/node_modules/@types/b/package.json' does not exist.", + "File '/x/node_modules/@types/b/index.ts' does not exist.", + "File '/x/node_modules/@types/b/index.d.ts' exist - use it as a name resolution result.", + "======== Module name 'b' was successfully resolved to '/x/node_modules/@types/b/index.d.ts'. ========", + "======== Resolving module 'a' from '/x/node_modules/@types/b/index.d.ts'. ========", + "Module resolution kind is not specified, using 'Classic'.", + "File '/x/node_modules/@types/b/a.ts' does not exist.", + "File '/x/node_modules/@types/b/a.d.ts' does not exist.", + "File '/x/node_modules/@types/a.ts' does not exist.", + "File '/x/node_modules/@types/a.d.ts' does not exist.", + "File '/x/node_modules/a.ts' does not exist.", + "File '/x/node_modules/a.d.ts' does not exist.", + "File '/x/a.ts' does not exist.", + "File '/x/a.d.ts' does not exist.", + "File '/a.ts' does not exist.", + "File '/a.d.ts' does not exist.", + "File '/x/node_modules/@types/b/node_modules/@types/a.ts' does not exist.", + "File '/x/node_modules/@types/b/node_modules/@types/a.d.ts' does not exist.", + "File '/x/node_modules/@types/b/node_modules/@types/a/package.json' does not exist.", + "File '/x/node_modules/@types/b/node_modules/@types/a/index.ts' does not exist.", + "File '/x/node_modules/@types/b/node_modules/@types/a/index.d.ts' does not exist.", + "File '/x/node_modules/@types/node_modules/@types/a.ts' does not exist.", + "File '/x/node_modules/@types/node_modules/@types/a.d.ts' does not exist.", + "File '/x/node_modules/@types/node_modules/@types/a/package.json' does not exist.", + "File '/x/node_modules/@types/node_modules/@types/a/index.ts' does not exist.", + "File '/x/node_modules/@types/node_modules/@types/a/index.d.ts' does not exist.", + "File '/x/node_modules/@types/a.ts' does not exist.", + "File '/x/node_modules/@types/a.d.ts' does not exist.", + "File '/x/node_modules/@types/a/package.json' does not exist.", + "File '/x/node_modules/@types/a/index.ts' does not exist.", + "File '/x/node_modules/@types/a/index.d.ts' does not exist.", + "File '/node_modules/@types/a.ts' does not exist.", + "File '/node_modules/@types/a.d.ts' does not exist.", + "File '/node_modules/@types/a/package.json' does not exist.", + "File '/node_modules/@types/a/index.ts' does not exist.", + "File '/node_modules/@types/a/index.d.ts' exist - use it as a name resolution result.", + "======== Module name 'a' was successfully resolved to '/node_modules/@types/a/index.d.ts'. ========", + "======== Resolving type reference directive 'a', containing file '/__inferred type names__.ts', root directory '/node_modules/@types'. ========", + "Resolving with primary search path '/node_modules/@types'", + "File '/node_modules/@types/a/package.json' does not exist.", + "File '/node_modules/@types/a/index.d.ts' exist - use it as a name resolution result.", + "======== Type reference directive 'a' was successfully resolved to '/node_modules/@types/a/index.d.ts', primary: true. ========" +] \ No newline at end of file diff --git a/tests/baselines/reference/typingsLookupAmd.types b/tests/baselines/reference/typingsLookupAmd.types new file mode 100644 index 00000000000..44273c03f80 --- /dev/null +++ b/tests/baselines/reference/typingsLookupAmd.types @@ -0,0 +1,17 @@ +=== /x/y/foo.ts === +import {B} from "b"; +>B : typeof B + +=== /node_modules/@types/a/index.d.ts === + +export declare class A {} +>A : A + +=== /x/node_modules/@types/b/index.d.ts === +import {A} from "a"; +>A : typeof A + +export declare class B extends A {} +>B : B +>A : A + diff --git a/tests/cases/conformance/typings/typingsLookupAmd.ts b/tests/cases/conformance/typings/typingsLookupAmd.ts new file mode 100644 index 00000000000..e452894e9b4 --- /dev/null +++ b/tests/cases/conformance/typings/typingsLookupAmd.ts @@ -0,0 +1,14 @@ +// @traceResolution: true +// @noImplicitReferences: true +// @currentDirectory: / +// @module: amd + +// @filename: /node_modules/@types/a/index.d.ts +export declare class A {} + +// @filename: /x/node_modules/@types/b/index.d.ts +import {A} from "a"; +export declare class B extends A {} + +// @filename: /x/y/foo.ts +import {B} from "b";