Skip to content

Commit 02493de

Browse files
author
Andy
authored
Merge pull request #11147 from Microsoft/classic_at_types
Have classic module resolution use @types as a fallback
2 parents 32eddcf + 4907fd1 commit 02493de

File tree

6 files changed

+167
-25
lines changed

6 files changed

+167
-25
lines changed

src/compiler/moduleNameResolver.ts

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ namespace ts {
9595
currentDirectory = host.getCurrentDirectory();
9696
}
9797

98-
return currentDirectory && getDefaultTypeRoots(currentDirectory, host);
98+
return currentDirectory !== undefined && getDefaultTypeRoots(currentDirectory, host);
9999
}
100100

101101
/**
@@ -675,23 +675,33 @@ namespace ts {
675675

676676
/* @internal */
677677
export function loadModuleFromNodeModules(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState, checkOneLevel: boolean): string {
678+
return loadModuleFromNodeModulesWorker(moduleName, directory, failedLookupLocations, state, checkOneLevel, /*typesOnly*/ false);
679+
}
680+
681+
function loadModuleFromNodeModulesAtTypes(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState): string {
682+
return loadModuleFromNodeModulesWorker(moduleName, directory, failedLookupLocations, state, /*checkOneLevel*/ false, /*typesOnly*/ true);
683+
}
684+
685+
function loadModuleFromNodeModulesWorker(moduleName: string, directory: string, failedLookupLocations: string[], state: ModuleResolutionState, checkOneLevel: boolean, typesOnly: boolean): string {
678686
directory = normalizeSlashes(directory);
679687
while (true) {
680688
const baseName = getBaseFileName(directory);
681689
if (baseName !== "node_modules") {
682-
// Try to load source from the package
683-
const packageResult = loadModuleFromNodeModulesFolder(moduleName, directory, failedLookupLocations, state);
684-
if (packageResult && hasTypeScriptFileExtension(packageResult)) {
685-
// Always prefer a TypeScript (.ts, .tsx, .d.ts) file shipped with the package
686-
return packageResult;
687-
}
688-
else {
689-
// Else prefer a types package over non-TypeScript results (e.g. JavaScript files)
690-
const typesResult = loadModuleFromNodeModulesFolder(combinePaths("@types", moduleName), directory, failedLookupLocations, state);
691-
if (typesResult || packageResult) {
692-
return typesResult || packageResult;
690+
let packageResult: string | undefined;
691+
if (!typesOnly) {
692+
// Try to load source from the package
693+
packageResult = loadModuleFromNodeModulesFolder(moduleName, directory, failedLookupLocations, state);
694+
if (packageResult && hasTypeScriptFileExtension(packageResult)) {
695+
// Always prefer a TypeScript (.ts, .tsx, .d.ts) file shipped with the package
696+
return packageResult;
693697
}
694698
}
699+
700+
// Else prefer a types package over non-TypeScript results (e.g. JavaScript files)
701+
const typesResult = loadModuleFromNodeModulesFolder(combinePaths("@types", moduleName), directory, failedLookupLocations, state);
702+
if (typesResult || packageResult) {
703+
return typesResult || packageResult;
704+
}
695705
}
696706

697707
const parentPath = getDirectoryPath(directory);
@@ -709,7 +719,7 @@ namespace ts {
709719
const state = { compilerOptions, host, traceEnabled, skipTsx: !compilerOptions.jsx };
710720
const failedLookupLocations: string[] = [];
711721
const supportedExtensions = getSupportedExtensions(compilerOptions);
712-
let containingDirectory = getDirectoryPath(containingFile);
722+
const containingDirectory = getDirectoryPath(containingFile);
713723

714724
const resolvedFileName = tryLoadModuleUsingOptionalResolutionSettings(moduleName, containingDirectory, loadModuleFromFile, failedLookupLocations, supportedExtensions, state);
715725
if (resolvedFileName) {
@@ -718,18 +728,9 @@ namespace ts {
718728

719729
let referencedSourceFile: string;
720730
if (moduleHasNonRelativeName(moduleName)) {
721-
while (true) {
722-
const searchName = normalizePath(combinePaths(containingDirectory, moduleName));
723-
referencedSourceFile = loadModuleFromFile(searchName, supportedExtensions, failedLookupLocations, /*onlyRecordFailures*/ false, state);
724-
if (referencedSourceFile) {
725-
break;
726-
}
727-
const parentPath = getDirectoryPath(containingDirectory);
728-
if (parentPath === containingDirectory) {
729-
break;
730-
}
731-
containingDirectory = parentPath;
732-
}
731+
referencedSourceFile = referencedSourceFile = loadModuleFromAncestorDirectories(moduleName, containingDirectory, supportedExtensions, failedLookupLocations, state) ||
732+
// If we didn't find the file normally, look it up in @types.
733+
loadModuleFromNodeModulesAtTypes(moduleName, containingDirectory, failedLookupLocations, state);
733734
}
734735
else {
735736
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));
@@ -741,4 +742,20 @@ namespace ts {
741742
? { resolvedModule: { resolvedFileName: referencedSourceFile }, failedLookupLocations }
742743
: { resolvedModule: undefined, failedLookupLocations };
743744
}
745+
746+
/** Climb up parent directories looking for a module. */
747+
function loadModuleFromAncestorDirectories(moduleName: string, containingDirectory: string, supportedExtensions: string[], failedLookupLocations: string[], state: ModuleResolutionState): string | undefined {
748+
while (true) {
749+
const searchName = normalizePath(combinePaths(containingDirectory, moduleName));
750+
const referencedSourceFile = loadModuleFromFile(searchName, supportedExtensions, failedLookupLocations, /*onlyRecordFailures*/ false, state);
751+
if (referencedSourceFile) {
752+
return referencedSourceFile;
753+
}
754+
const parentPath = getDirectoryPath(containingDirectory);
755+
if (parentPath === containingDirectory) {
756+
return undefined;
757+
}
758+
containingDirectory = parentPath;
759+
}
760+
}
744761
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//// [tests/cases/conformance/typings/typingsLookupAmd.ts] ////
2+
3+
//// [index.d.ts]
4+
5+
export declare class A {}
6+
7+
//// [index.d.ts]
8+
import {A} from "a";
9+
export declare class B extends A {}
10+
11+
//// [foo.ts]
12+
import {B} from "b";
13+
14+
15+
//// [foo.js]
16+
define(["require", "exports"], function (require, exports) {
17+
"use strict";
18+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
=== /x/y/foo.ts ===
2+
import {B} from "b";
3+
>B : Symbol(B, Decl(foo.ts, 0, 8))
4+
5+
=== /node_modules/@types/a/index.d.ts ===
6+
7+
export declare class A {}
8+
>A : Symbol(A, Decl(index.d.ts, 0, 0))
9+
10+
=== /x/node_modules/@types/b/index.d.ts ===
11+
import {A} from "a";
12+
>A : Symbol(A, Decl(index.d.ts, 0, 8))
13+
14+
export declare class B extends A {}
15+
>B : Symbol(B, Decl(index.d.ts, 0, 20))
16+
>A : Symbol(A, Decl(index.d.ts, 0, 8))
17+
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
[
2+
"======== Resolving module 'b' from '/x/y/foo.ts'. ========",
3+
"Module resolution kind is not specified, using 'Classic'.",
4+
"File '/x/y/b.ts' does not exist.",
5+
"File '/x/y/b.d.ts' does not exist.",
6+
"File '/x/b.ts' does not exist.",
7+
"File '/x/b.d.ts' does not exist.",
8+
"File '/b.ts' does not exist.",
9+
"File '/b.d.ts' does not exist.",
10+
"File '/x/y/node_modules/@types/b.ts' does not exist.",
11+
"File '/x/y/node_modules/@types/b.d.ts' does not exist.",
12+
"File '/x/y/node_modules/@types/b/package.json' does not exist.",
13+
"File '/x/y/node_modules/@types/b/index.ts' does not exist.",
14+
"File '/x/y/node_modules/@types/b/index.d.ts' does not exist.",
15+
"File '/x/node_modules/@types/b.ts' does not exist.",
16+
"File '/x/node_modules/@types/b.d.ts' does not exist.",
17+
"File '/x/node_modules/@types/b/package.json' does not exist.",
18+
"File '/x/node_modules/@types/b/index.ts' does not exist.",
19+
"File '/x/node_modules/@types/b/index.d.ts' exist - use it as a name resolution result.",
20+
"======== Module name 'b' was successfully resolved to '/x/node_modules/@types/b/index.d.ts'. ========",
21+
"======== Resolving module 'a' from '/x/node_modules/@types/b/index.d.ts'. ========",
22+
"Module resolution kind is not specified, using 'Classic'.",
23+
"File '/x/node_modules/@types/b/a.ts' does not exist.",
24+
"File '/x/node_modules/@types/b/a.d.ts' does not exist.",
25+
"File '/x/node_modules/@types/a.ts' does not exist.",
26+
"File '/x/node_modules/@types/a.d.ts' does not exist.",
27+
"File '/x/node_modules/a.ts' does not exist.",
28+
"File '/x/node_modules/a.d.ts' does not exist.",
29+
"File '/x/a.ts' does not exist.",
30+
"File '/x/a.d.ts' does not exist.",
31+
"File '/a.ts' does not exist.",
32+
"File '/a.d.ts' does not exist.",
33+
"File '/x/node_modules/@types/b/node_modules/@types/a.ts' does not exist.",
34+
"File '/x/node_modules/@types/b/node_modules/@types/a.d.ts' does not exist.",
35+
"File '/x/node_modules/@types/b/node_modules/@types/a/package.json' does not exist.",
36+
"File '/x/node_modules/@types/b/node_modules/@types/a/index.ts' does not exist.",
37+
"File '/x/node_modules/@types/b/node_modules/@types/a/index.d.ts' does not exist.",
38+
"File '/x/node_modules/@types/node_modules/@types/a.ts' does not exist.",
39+
"File '/x/node_modules/@types/node_modules/@types/a.d.ts' does not exist.",
40+
"File '/x/node_modules/@types/node_modules/@types/a/package.json' does not exist.",
41+
"File '/x/node_modules/@types/node_modules/@types/a/index.ts' does not exist.",
42+
"File '/x/node_modules/@types/node_modules/@types/a/index.d.ts' does not exist.",
43+
"File '/x/node_modules/@types/a.ts' does not exist.",
44+
"File '/x/node_modules/@types/a.d.ts' does not exist.",
45+
"File '/x/node_modules/@types/a/package.json' does not exist.",
46+
"File '/x/node_modules/@types/a/index.ts' does not exist.",
47+
"File '/x/node_modules/@types/a/index.d.ts' does not exist.",
48+
"File '/node_modules/@types/a.ts' does not exist.",
49+
"File '/node_modules/@types/a.d.ts' does not exist.",
50+
"File '/node_modules/@types/a/package.json' does not exist.",
51+
"File '/node_modules/@types/a/index.ts' does not exist.",
52+
"File '/node_modules/@types/a/index.d.ts' exist - use it as a name resolution result.",
53+
"======== Module name 'a' was successfully resolved to '/node_modules/@types/a/index.d.ts'. ========",
54+
"======== Resolving type reference directive 'a', containing file '/__inferred type names__.ts', root directory '/node_modules/@types'. ========",
55+
"Resolving with primary search path '/node_modules/@types'",
56+
"File '/node_modules/@types/a/package.json' does not exist.",
57+
"File '/node_modules/@types/a/index.d.ts' exist - use it as a name resolution result.",
58+
"======== Type reference directive 'a' was successfully resolved to '/node_modules/@types/a/index.d.ts', primary: true. ========"
59+
]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
=== /x/y/foo.ts ===
2+
import {B} from "b";
3+
>B : typeof B
4+
5+
=== /node_modules/@types/a/index.d.ts ===
6+
7+
export declare class A {}
8+
>A : A
9+
10+
=== /x/node_modules/@types/b/index.d.ts ===
11+
import {A} from "a";
12+
>A : typeof A
13+
14+
export declare class B extends A {}
15+
>B : B
16+
>A : A
17+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// @traceResolution: true
2+
// @noImplicitReferences: true
3+
// @currentDirectory: /
4+
// @module: amd
5+
6+
// @filename: /node_modules/@types/a/index.d.ts
7+
export declare class A {}
8+
9+
// @filename: /x/node_modules/@types/b/index.d.ts
10+
import {A} from "a";
11+
export declare class B extends A {}
12+
13+
// @filename: /x/y/foo.ts
14+
import {B} from "b";

0 commit comments

Comments
 (0)