Skip to content

Commit 0649c22

Browse files
authored
cache per-folder module resolutions during construction of the program (#13030)
1 parent c05b733 commit 0649c22

10 files changed

+175
-23
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2945,6 +2945,10 @@
29452945
"category": "Message",
29462946
"code": 6146
29472947
},
2948+
"Resolution for module '{0}' was found in cache": {
2949+
"category": "Message",
2950+
"code": 6147
2951+
},
29482952
"Variable '{0}' implicitly has an '{1}' type.": {
29492953
"category": "Error",
29502954
"code": 7005

src/compiler/moduleNameResolver.ts

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -293,33 +293,69 @@ namespace ts {
293293
return result;
294294
}
295295

296-
export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
296+
/**
297+
* Cached module resolutions per containing directory.
298+
* This assumes that any module id will have the same resolution for sibling files located in the same folder.
299+
*/
300+
export interface ModuleResolutionCache {
301+
getOrCreateCacheForDirectory(directoryName: string): Map<ResolvedModuleWithFailedLookupLocations>;
302+
}
303+
304+
export function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string) {
305+
const map = createFileMap<Map<ResolvedModuleWithFailedLookupLocations>>();
306+
307+
return { getOrCreateCacheForDirectory };
308+
309+
function getOrCreateCacheForDirectory(directoryName: string) {
310+
const path = toPath(directoryName, currentDirectory, getCanonicalFileName);
311+
let perFolderCache = map.get(path);
312+
if (!perFolderCache) {
313+
perFolderCache = createMap<ResolvedModuleWithFailedLookupLocations>();
314+
map.set(path, perFolderCache);
315+
}
316+
return perFolderCache;
317+
}
318+
}
319+
320+
export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations {
297321
const traceEnabled = isTraceEnabled(compilerOptions, host);
298322
if (traceEnabled) {
299323
trace(host, Diagnostics.Resolving_module_0_from_1, moduleName, containingFile);
300324
}
325+
let perFolderCache = cache && cache.getOrCreateCacheForDirectory(getDirectoryPath(containingFile));
326+
let result = perFolderCache && perFolderCache[moduleName];
301327

302-
let moduleResolution = compilerOptions.moduleResolution;
303-
if (moduleResolution === undefined) {
304-
moduleResolution = getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS ? ModuleResolutionKind.NodeJs : ModuleResolutionKind.Classic;
328+
if (result) {
305329
if (traceEnabled) {
306-
trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]);
330+
trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache, moduleName);
307331
}
308332
}
309333
else {
310-
if (traceEnabled) {
311-
trace(host, Diagnostics.Explicitly_specified_module_resolution_kind_Colon_0, ModuleResolutionKind[moduleResolution]);
334+
let moduleResolution = compilerOptions.moduleResolution;
335+
if (moduleResolution === undefined) {
336+
moduleResolution = getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS ? ModuleResolutionKind.NodeJs : ModuleResolutionKind.Classic;
337+
if (traceEnabled) {
338+
trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]);
339+
}
340+
}
341+
else {
342+
if (traceEnabled) {
343+
trace(host, Diagnostics.Explicitly_specified_module_resolution_kind_Colon_0, ModuleResolutionKind[moduleResolution]);
344+
}
312345
}
313-
}
314346

315-
let result: ResolvedModuleWithFailedLookupLocations;
316-
switch (moduleResolution) {
317-
case ModuleResolutionKind.NodeJs:
318-
result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host);
319-
break;
320-
case ModuleResolutionKind.Classic:
321-
result = classicNameResolver(moduleName, containingFile, compilerOptions, host);
322-
break;
347+
switch (moduleResolution) {
348+
case ModuleResolutionKind.NodeJs:
349+
result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host);
350+
break;
351+
case ModuleResolutionKind.Classic:
352+
result = classicNameResolver(moduleName, containingFile, compilerOptions, host);
353+
break;
354+
}
355+
356+
if (perFolderCache) {
357+
perFolderCache[moduleName] = result;
358+
}
323359
}
324360

325361
if (traceEnabled) {

src/compiler/program.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ namespace ts {
325325
// Map storing if there is emit blocking diagnostics for given input
326326
const hasEmitBlockingDiagnostics = createFileMap<boolean>(getCanonicalFileName);
327327

328+
let moduleResolutionCache: ModuleResolutionCache;
328329
let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string) => ResolvedModuleFull[];
329330
if (host.resolveModuleNames) {
330331
resolveModuleNamesWorker = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile).map(resolved => {
@@ -338,7 +339,8 @@ namespace ts {
338339
});
339340
}
340341
else {
341-
const loader = (moduleName: string, containingFile: string) => resolveModuleName(moduleName, containingFile, options, host).resolvedModule;
342+
moduleResolutionCache = createModuleResolutionCache(currentDirectory, x => host.getCanonicalFileName(x));
343+
const loader = (moduleName: string, containingFile: string) => resolveModuleName(moduleName, containingFile, options, host, moduleResolutionCache).resolvedModule;
342344
resolveModuleNamesWorker = (moduleNames, containingFile) => loadWithLocalCache(moduleNames, containingFile, loader);
343345
}
344346

@@ -391,6 +393,9 @@ namespace ts {
391393
}
392394
}
393395

396+
// unconditionally set moduleResolutionCache to undefined to avoid unnecessary leaks
397+
moduleResolutionCache = undefined;
398+
394399
// unconditionally set oldProgram to undefined to prevent it from being captured in closure
395400
oldProgram = undefined;
396401

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//// [tests/cases/compiler/cacheResolutions.ts] ////
2+
3+
//// [app.ts]
4+
5+
export let x = 1;
6+
7+
//// [lib1.ts]
8+
export let x = 1;
9+
10+
//// [lib2.ts]
11+
export let x = 1;
12+
13+
//// [app.js]
14+
define(["require", "exports"], function (require, exports) {
15+
"use strict";
16+
exports.x = 1;
17+
});
18+
//// [lib1.js]
19+
define(["require", "exports"], function (require, exports) {
20+
"use strict";
21+
exports.x = 1;
22+
});
23+
//// [lib2.js]
24+
define(["require", "exports"], function (require, exports) {
25+
"use strict";
26+
exports.x = 1;
27+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
=== /a/b/c/app.ts ===
2+
3+
export let x = 1;
4+
>x : Symbol(x, Decl(app.ts, 1, 10))
5+
6+
=== /a/b/c/lib1.ts ===
7+
export let x = 1;
8+
>x : Symbol(x, Decl(lib1.ts, 0, 10))
9+
10+
=== /a/b/c/lib2.ts ===
11+
export let x = 1;
12+
>x : Symbol(x, Decl(lib2.ts, 0, 10))
13+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
[
2+
"======== Resolving module 'tslib' from '/a/b/c/app.ts'. ========",
3+
"Module resolution kind is not specified, using 'Classic'.",
4+
"File '/a/b/c/tslib.ts' does not exist.",
5+
"File '/a/b/c/tslib.tsx' does not exist.",
6+
"File '/a/b/c/tslib.d.ts' does not exist.",
7+
"File '/a/b/tslib.ts' does not exist.",
8+
"File '/a/b/tslib.tsx' does not exist.",
9+
"File '/a/b/tslib.d.ts' does not exist.",
10+
"File '/a/tslib.ts' does not exist.",
11+
"File '/a/tslib.tsx' does not exist.",
12+
"File '/a/tslib.d.ts' does not exist.",
13+
"File '/tslib.ts' does not exist.",
14+
"File '/tslib.tsx' does not exist.",
15+
"File '/tslib.d.ts' does not exist.",
16+
"File '/a/b/c/node_modules/@types/tslib.d.ts' does not exist.",
17+
"File '/a/b/c/node_modules/@types/tslib/package.json' does not exist.",
18+
"File '/a/b/c/node_modules/@types/tslib/index.d.ts' does not exist.",
19+
"File '/a/b/node_modules/@types/tslib.d.ts' does not exist.",
20+
"File '/a/b/node_modules/@types/tslib/package.json' does not exist.",
21+
"File '/a/b/node_modules/@types/tslib/index.d.ts' does not exist.",
22+
"File '/a/node_modules/@types/tslib.d.ts' does not exist.",
23+
"File '/a/node_modules/@types/tslib/package.json' does not exist.",
24+
"File '/a/node_modules/@types/tslib/index.d.ts' does not exist.",
25+
"File '/node_modules/@types/tslib.d.ts' does not exist.",
26+
"File '/node_modules/@types/tslib/package.json' does not exist.",
27+
"File '/node_modules/@types/tslib/index.d.ts' does not exist.",
28+
"File '/a/b/c/tslib.js' does not exist.",
29+
"File '/a/b/c/tslib.jsx' does not exist.",
30+
"File '/a/b/tslib.js' does not exist.",
31+
"File '/a/b/tslib.jsx' does not exist.",
32+
"File '/a/tslib.js' does not exist.",
33+
"File '/a/tslib.jsx' does not exist.",
34+
"File '/tslib.js' does not exist.",
35+
"File '/tslib.jsx' does not exist.",
36+
"======== Module name 'tslib' was not resolved. ========",
37+
"======== Resolving module 'tslib' from '/a/b/c/lib1.ts'. ========",
38+
"Resolution for module 'tslib' was found in cache",
39+
"======== Module name 'tslib' was not resolved. ========",
40+
"======== Resolving module 'tslib' from '/a/b/c/lib2.ts'. ========",
41+
"Resolution for module 'tslib' was found in cache",
42+
"======== Module name 'tslib' was not resolved. ========"
43+
]
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
=== /a/b/c/app.ts ===
2+
3+
export let x = 1;
4+
>x : number
5+
>1 : 1
6+
7+
=== /a/b/c/lib1.ts ===
8+
export let x = 1;
9+
>x : number
10+
>1 : 1
11+
12+
=== /a/b/c/lib2.ts ===
13+
export let x = 1;
14+
>x : number
15+
>1 : 1
16+

tests/baselines/reference/typeReferenceDirectives12.trace.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616
"Resolving real path for '/types/lib/index.d.ts', result '/types/lib/index.d.ts'",
1717
"======== Type reference directive 'lib' was successfully resolved to '/types/lib/index.d.ts', primary: true. ========",
1818
"======== Resolving module './main' from '/mod1.ts'. ========",
19-
"Module resolution kind is not specified, using 'NodeJs'.",
20-
"Loading module as file / folder, candidate module location '/main'.",
21-
"File '/main.ts' exist - use it as a name resolution result.",
19+
"Resolution for module './main' was found in cache",
2220
"======== Module name './main' was successfully resolved to '/main.ts'. ========",
2321
"======== Resolving type reference directive 'lib', containing file '/__inferred type names__.ts', root directory '/types'. ========",
2422
"Resolving with primary search path '/types'",

tests/baselines/reference/typeReferenceDirectives9.trace.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616
"Resolving real path for '/types/lib/index.d.ts', result '/types/lib/index.d.ts'",
1717
"======== Type reference directive 'lib' was successfully resolved to '/types/lib/index.d.ts', primary: true. ========",
1818
"======== Resolving module './main' from '/mod1.ts'. ========",
19-
"Module resolution kind is not specified, using 'NodeJs'.",
20-
"Loading module as file / folder, candidate module location '/main'.",
21-
"File '/main.ts' exist - use it as a name resolution result.",
19+
"Resolution for module './main' was found in cache",
2220
"======== Module name './main' was successfully resolved to '/main.ts'. ========",
2321
"======== Resolving type reference directive 'lib', containing file '/__inferred type names__.ts', root directory '/types'. ========",
2422
"Resolving with primary search path '/types'",
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// @module: amd
2+
// @importHelpers: true
3+
// @traceResolution: true
4+
5+
// @filename: /a/b/c/app.ts
6+
export let x = 1;
7+
8+
// @filename: /a/b/c/lib1.ts
9+
export let x = 1;
10+
11+
// @filename: /a/b/c/lib2.ts
12+
export let x = 1;

0 commit comments

Comments
 (0)