Skip to content

Commit feea116

Browse files
committed
Add incremental test where cache should have same resolutions as whats in the program
This shows cache is holding onto resolutions that are no longer needed by program because either those modules arent present in file or is determined to be ambient resolution
1 parent 665c6ca commit feea116

File tree

4 files changed

+128
-30
lines changed

4 files changed

+128
-30
lines changed

src/compiler/program.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -947,7 +947,8 @@ export function getResolutionModeOverride(node: ImportAttributes | undefined, gr
947947
return elem.value.text === "import" ? ModuleKind.ESNext : ModuleKind.CommonJS;
948948
}
949949

950-
const emptyResolution: ResolvedModuleWithFailedLookupLocations & ResolvedTypeReferenceDirectiveWithFailedLookupLocations = {
950+
/** @internal */
951+
export const emptyResolution: ResolvedModuleWithFailedLookupLocations & ResolvedTypeReferenceDirectiveWithFailedLookupLocations = {
951952
resolvedModule: undefined,
952953
resolvedTypeReferenceDirective: undefined,
953954
};
@@ -1112,6 +1113,13 @@ function forEachProjectReference<T>(
11121113
/** @internal */
11131114
export const inferredTypesContainingFile = "__inferred type names__.ts";
11141115

1116+
/** @internal */
1117+
export function getAutomaticTypeDirectiveContainingFile(options: CompilerOptions, currentDirectory: string) {
1118+
// This containingFilename needs to match with the one used in managed-side
1119+
const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : currentDirectory;
1120+
return combinePaths(containingDirectory, inferredTypesContainingFile);
1121+
}
1122+
11151123
/** @internal */
11161124
export function getInferredLibraryNameResolveFrom(options: CompilerOptions, currentDirectory: string, libFileName: string) {
11171125
const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : currentDirectory;
@@ -1762,10 +1770,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
17621770
automaticTypeDirectiveResolutions = createModeAwareCache();
17631771
if (automaticTypeDirectiveNames.length) {
17641772
tracing?.push(tracing.Phase.Program, "processTypeReferences", { count: automaticTypeDirectiveNames.length });
1765-
// This containingFilename needs to match with the one used in managed-side
1766-
const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : currentDirectory;
1767-
const containingFilename = combinePaths(containingDirectory, inferredTypesContainingFile);
1768-
const resolutions = resolveTypeReferenceDirectiveNamesReusingOldState(automaticTypeDirectiveNames, containingFilename);
1773+
const resolutions = resolveTypeReferenceDirectiveNamesReusingOldState(automaticTypeDirectiveNames, getAutomaticTypeDirectiveContainingFile(options, currentDirectory));
17691774
for (let i = 0; i < automaticTypeDirectiveNames.length; i++) {
17701775
// under node16/nodenext module resolution, load `types`/ata include names as cjs resolution results by passing an `undefined` mode
17711776
automaticTypeDirectiveResolutions.set(automaticTypeDirectiveNames[i], /*mode*/ undefined, resolutions[i]);
@@ -2153,8 +2158,6 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
21532158
*/
21542159
let result: ResolvedModuleWithFailedLookupLocations[] | undefined;
21552160
let reusedNames: StringLiteralLike[] | undefined;
2156-
/** A transient placeholder used to mark predicted resolution in the result list. */
2157-
const predictedToResolveToAmbientModuleMarker: ResolvedModuleWithFailedLookupLocations = emptyResolution;
21582161
const oldSourceFile = oldProgram && oldProgram.getSourceFile(file.fileName);
21592162

21602163
for (let i = 0; i < moduleNames.length; i++) {
@@ -2197,7 +2200,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
21972200
}
21982201

21992202
if (resolvesToAmbientModuleInNonModifiedFile) {
2200-
(result || (result = new Array(moduleNames.length)))[i] = predictedToResolveToAmbientModuleMarker;
2203+
(result || (result = new Array(moduleNames.length)))[i] = emptyResolution;
22012204
}
22022205
else {
22032206
// Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the result.

src/harness/incrementalUtils.ts

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ export function verifyResolutionCache(
204204
actualProgram: ts.Program,
205205
resolutionHostCacheHost: ts.ResolutionCacheHost,
206206
projectName: string,
207+
userResolvedModuleNames?: true,
207208
) {
208209
const currentDirectory = resolutionHostCacheHost.getCurrentDirectory!();
209210
const expected = ts.createResolutionCache(resolutionHostCacheHost, actual.rootDirForResolution);
@@ -214,6 +215,7 @@ export function verifyResolutionCache(
214215
const expectedToResolution = new Map<ExpectedResolution, ts.ResolutionWithFailedLookupLocations>();
215216
const resolutionToExpected = new Map<ts.ResolutionWithFailedLookupLocations, ExpectedResolution>();
216217
const resolutionToRefs = new Map<ts.ResolutionWithFailedLookupLocations, ResolutionInfo[]>();
218+
const inferredTypesPath = resolutionHostCacheHost.toPath(ts.getAutomaticTypeDirectiveContainingFile(actualProgram.getCompilerOptions(), currentDirectory));
217219
actual.resolvedModuleNames.forEach((resolutions, path) =>
218220
collectResolutionToRefFromCache(
219221
"Modules",
@@ -222,6 +224,7 @@ export function verifyResolutionCache(
222224
getResolvedModuleFileName,
223225
/*deferWatchingNonRelativeResolution*/ true,
224226
expected.resolvedModuleNames,
227+
(name, mode) => actualProgram.getResolvedModule(actualProgram.getSourceFileByPath(path)!, name, mode),
225228
)
226229
);
227230
actual.resolvedTypeReferenceDirectives.forEach((resolutions, path) =>
@@ -232,6 +235,10 @@ export function verifyResolutionCache(
232235
getResolvedTypeRefFileName,
233236
/*deferWatchingNonRelativeResolution*/ false,
234237
expected.resolvedTypeReferenceDirectives,
238+
(name, mode) =>
239+
path !== inferredTypesPath ?
240+
actualProgram.getResolvedTypeReferenceDirective(actualProgram.getSourceFileByPath(path)!, name, mode) :
241+
actualProgram.getAutomaticTypeDirectiveResolutions().get(name, mode),
235242
)
236243
);
237244
actual.resolvedLibraries.forEach((resolved, libFileName) => {
@@ -248,6 +255,39 @@ export function verifyResolutionCache(
248255
);
249256
expected.resolvedLibraries.set(libFileName, expectedResolution);
250257
});
258+
// Check for resolutions in program but not in cache to empty resolutions
259+
if (!userResolvedModuleNames) {
260+
actualProgram.forEachResolvedModule((resolution, name, mode, filePath) =>
261+
verifyResolutionIsInCache(
262+
"Modules",
263+
actual.resolvedModuleNames.get(filePath),
264+
resolution,
265+
name,
266+
mode,
267+
filePath,
268+
)
269+
);
270+
}
271+
actualProgram.forEachResolvedTypeReferenceDirective((resolution, name, mode, filePath) =>
272+
verifyResolutionIsInCache(
273+
"TypeRefs",
274+
actual.resolvedTypeReferenceDirectives.get(filePath),
275+
resolution,
276+
name,
277+
mode,
278+
filePath,
279+
)
280+
);
281+
actualProgram.getAutomaticTypeDirectiveResolutions().forEach((resolution, name, mode) =>
282+
verifyResolutionIsInCache(
283+
"AutoTypeRefs",
284+
actual.resolvedTypeReferenceDirectives.get(inferredTypesPath),
285+
resolution,
286+
name,
287+
mode,
288+
inferredTypesPath,
289+
)
290+
);
251291

252292
expected.finishCachingPerDirectoryResolution(actualProgram, /*oldProgram*/ undefined);
253293

@@ -260,6 +300,10 @@ export function verifyResolutionCache(
260300
`Expected from:: ${JSON.stringify(info, undefined, " ")}` +
261301
`Actual from: ${resolution.refCount}`,
262302
);
303+
ts.Debug.assert(
304+
!resolution.isInvalidated,
305+
`${projectName}:: Resolution should not be invalidated`,
306+
);
263307
ts.Debug.assert(
264308
resolutionToExpected.get(resolution)!.refCount === resolution.refCount,
265309
`${projectName}:: Expected Resolution ref count ${resolutionToExpected.get(resolution)!.refCount} but got ${resolution.refCount}`,
@@ -298,24 +342,54 @@ export function verifyResolutionCache(
298342
ts.Debug.assert(expected.countResolutionsResolvedWithGlobalCache() === 0, `${projectName}:: ResolutionsResolvedWithGlobalCache should be cleared`);
299343
ts.Debug.assert(expected.countResolutionsResolvedWithoutGlobalCache() === 0, `${projectName}:: ResolutionsResolvedWithoutGlobalCache should be cleared`);
300344

345+
function verifyResolutionIsInCache<T extends ts.ResolutionWithFailedLookupLocations>(
346+
cacheType: string,
347+
cache: ts.ModeAwareCache<T> | undefined,
348+
resolution: T,
349+
name: string,
350+
mode: ts.ResolutionMode,
351+
fileName: string,
352+
) {
353+
if (resolution as unknown !== ts.emptyResolution) {
354+
// Resolutions should match
355+
ts.Debug.assert(
356+
cache?.get(name, mode) === resolution,
357+
`${projectName}:: ${cacheType}:: ${name}:: ${mode} Expected resolution in program to be in cache ${fileName}`,
358+
);
359+
}
360+
else {
361+
// EmptyResolution is place holder and shouldnt be in the cache
362+
ts.Debug.assert(
363+
!cache?.has(name, mode),
364+
`${projectName}:: ${cacheType}:: ${name}:: ${mode} Ambient moduleResolution, should not be watched ${fileName}`,
365+
);
366+
}
367+
}
368+
301369
function collectResolutionToRefFromCache<T extends ts.ResolutionWithFailedLookupLocations>(
302370
cacheType: string,
303371
fileName: ts.Path,
304372
cache: ts.ModeAwareCache<T> | undefined,
305373
getResolvedFileName: (resolution: T) => string | undefined,
306374
deferWatchingNonRelativeResolution: boolean,
307-
storeExpcted: Map<ts.Path, ts.ModeAwareCache<ts.ResolutionWithFailedLookupLocations>>,
375+
storeExpected: Map<ts.Path, ts.ModeAwareCache<ts.ResolutionWithFailedLookupLocations>>,
376+
getProgramResolutions: (name: string, mode: ts.ResolutionMode) => T | undefined,
308377
) {
309378
ts.Debug.assert(
310-
actualProgram.getSourceFileByPath(fileName) || ts.endsWith(fileName, ts.inferredTypesContainingFile),
379+
actualProgram.getSourceFileByPath(fileName) || inferredTypesPath === fileName,
311380
`${projectName}:: ${cacheType} ${fileName} Expect cache for file in program or auto type ref`,
312381
);
313382
let expectedCache: ts.ModeAwareCache<ts.ResolutionWithFailedLookupLocations> | undefined;
314383
cache?.forEach((resolved, name, mode) => {
315384
const resolvedFileName = getResolvedFileName(resolved);
316385
const expected = collectResolution(cacheType, fileName, resolved, resolvedFileName, name, mode, deferWatchingNonRelativeResolution);
317-
if (!expectedCache) storeExpcted.set(fileName, expectedCache = ts.createModeAwareCache());
386+
if (!expectedCache) storeExpected.set(fileName, expectedCache = ts.createModeAwareCache());
318387
expectedCache.set(name, mode, expected);
388+
// Resolution in cache should be same as that is in program
389+
ts.Debug.assert(
390+
resolved === getProgramResolutions(name, mode),
391+
`${projectName}:: ${cacheType} ${fileName} ${name} ${mode} Expected resolution in cache to be matched to that in the program`,
392+
);
319393
});
320394
}
321395

src/testRunner/unittests/helpers/tscWatch.ts

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ export interface RunWatchBaseline<T extends ts.BuilderProgram> extends BaselineB
221221
getPrograms: () => readonly CommandLineProgram[];
222222
watchOrSolution: WatchOrSolution<T>;
223223
useSourceOfProjectReferenceRedirect?: () => boolean;
224+
userResolvedModuleNames?: true;
224225
}
225226
export function runWatchBaseline<T extends ts.BuilderProgram = ts.EmitAndSemanticDiagnosticsBuilderProgram>({
226227
scenario,
@@ -235,6 +236,7 @@ export function runWatchBaseline<T extends ts.BuilderProgram = ts.EmitAndSemanti
235236
edits,
236237
watchOrSolution,
237238
useSourceOfProjectReferenceRedirect,
239+
userResolvedModuleNames,
238240
}: RunWatchBaseline<T>) {
239241
baseline.push(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}`);
240242
let programs = watchBaseline({
@@ -263,6 +265,7 @@ export function runWatchBaseline<T extends ts.BuilderProgram = ts.EmitAndSemanti
263265
resolutionCache: (watchOrSolution as ts.WatchOfConfigFile<T> | undefined)?.getResolutionCache?.(),
264266
useSourceOfProjectReferenceRedirect,
265267
symlinksNotReflected,
268+
userResolvedModuleNames,
266269
});
267270
}
268271
}
@@ -284,6 +287,7 @@ export interface WatchBaseline extends BaselineBase, TscWatchCheckOptions {
284287
resolutionCache?: ts.ResolutionCache;
285288
useSourceOfProjectReferenceRedirect?: () => boolean;
286289
symlinksNotReflected?: readonly string[];
290+
userResolvedModuleNames?: true;
287291
}
288292
export function watchBaseline({
289293
baseline,
@@ -297,6 +301,7 @@ export function watchBaseline({
297301
resolutionCache,
298302
useSourceOfProjectReferenceRedirect,
299303
symlinksNotReflected,
304+
userResolvedModuleNames,
300305
}: WatchBaseline) {
301306
if (baselineSourceMap) generateSourceMapBaselineFiles(sys);
302307
sys.serializeOutput(baseline);
@@ -311,7 +316,15 @@ export function watchBaseline({
311316
// Verify program structure and resolution cache when incremental edit with tsc --watch (without build mode)
312317
if (resolutionCache && programs.length) {
313318
ts.Debug.assert(programs.length === 1);
314-
verifyProgramStructureAndResolutionCache(caption!, sys, programs[0][0], resolutionCache, useSourceOfProjectReferenceRedirect, symlinksNotReflected);
319+
verifyProgramStructureAndResolutionCache(
320+
caption!,
321+
sys,
322+
programs[0][0],
323+
resolutionCache,
324+
useSourceOfProjectReferenceRedirect,
325+
symlinksNotReflected,
326+
userResolvedModuleNames,
327+
);
315328
}
316329
sys.writtenFiles.clear();
317330
return programs;
@@ -321,8 +334,9 @@ function verifyProgramStructureAndResolutionCache(
321334
sys: TscWatchSystem,
322335
program: ts.Program,
323336
resolutionCache: ts.ResolutionCache,
324-
useSourceOfProjectReferenceRedirect?: () => boolean,
325-
symlinksNotReflected?: readonly string[],
337+
useSourceOfProjectReferenceRedirect: (() => boolean) | undefined,
338+
symlinksNotReflected: readonly string[] | undefined,
339+
userResolvedModuleNames: true | undefined,
326340
) {
327341
const options = program.getCompilerOptions();
328342
const compilerHost = ts.createCompilerHostWorker(options, /*setParentNodes*/ undefined, sys);
@@ -349,24 +363,30 @@ function verifyProgramStructureAndResolutionCache(
349363
program,
350364
caption,
351365
);
352-
verifyResolutionCache(resolutionCache, program, {
353-
...compilerHost,
366+
verifyResolutionCache(
367+
resolutionCache,
368+
program,
369+
{
370+
...compilerHost,
354371

355-
getCompilerHost: () => compilerHost,
356-
toPath: fileName => sys.toPath(fileName),
357-
getCompilationSettings: () => options,
358-
fileIsOpen: ts.returnFalse,
359-
getCurrentProgram: () => program,
372+
getCompilerHost: () => compilerHost,
373+
toPath: fileName => sys.toPath(fileName),
374+
getCompilationSettings: () => options,
375+
fileIsOpen: ts.returnFalse,
376+
getCurrentProgram: () => program,
360377

361-
watchDirectoryOfFailedLookupLocation: ts.returnNoopFileWatcher,
362-
watchAffectingFileLocation: ts.returnNoopFileWatcher,
363-
onInvalidatedResolution: ts.noop,
364-
watchTypeRootsDirectory: ts.returnNoopFileWatcher,
365-
onChangedAutomaticTypeDirectiveNames: ts.noop,
366-
scheduleInvalidateResolutionsOfFailedLookupLocations: ts.noop,
367-
getCachedDirectoryStructureHost: ts.returnUndefined,
368-
writeLog: ts.noop,
369-
}, caption);
378+
watchDirectoryOfFailedLookupLocation: ts.returnNoopFileWatcher,
379+
watchAffectingFileLocation: ts.returnNoopFileWatcher,
380+
onInvalidatedResolution: ts.noop,
381+
watchTypeRootsDirectory: ts.returnNoopFileWatcher,
382+
onChangedAutomaticTypeDirectiveNames: ts.noop,
383+
scheduleInvalidateResolutionsOfFailedLookupLocations: ts.noop,
384+
getCachedDirectoryStructureHost: ts.returnUndefined,
385+
writeLog: ts.noop,
386+
},
387+
caption,
388+
userResolvedModuleNames,
389+
);
370390
}
371391
export interface VerifyTscWatch extends TscWatchCompile {
372392
baselineIncremental?: boolean;

src/testRunner/unittests/tscWatch/watchApi.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolu
125125
},
126126
],
127127
watchOrSolution: watch,
128+
userResolvedModuleNames: true,
128129
});
129130
});
130131
}

0 commit comments

Comments
 (0)