Skip to content

Commit bdf6665

Browse files
committed
Verify program structure from watch edits
1 parent a618c34 commit bdf6665

File tree

12 files changed

+112
-18
lines changed

12 files changed

+112
-18
lines changed

src/compiler/resolutionCache.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export interface HasInvalidatedFromResolutionCache {
9191
* @internal
9292
*/
9393
export interface ResolutionCache {
94+
rootDirForResolution: string;
9495
resolvedModuleNames: Map<Path, ModeAwareCache<CachedResolvedModuleWithFailedLookupLocations>>;
9596
resolvedTypeReferenceDirectives: Map<Path, ModeAwareCache<CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations>>;
9697
resolvedLibraries: Map<string, CachedResolvedModuleWithFailedLookupLocations>;
@@ -551,6 +552,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
551552
const typeRootsWatches = new Map<string, FileWatcher>();
552553

553554
return {
555+
rootDirForResolution,
554556
resolvedModuleNames,
555557
resolvedTypeReferenceDirectives,
556558
resolvedLibraries,

src/compiler/watchPublic.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import {
6464
perfLogger,
6565
PollingInterval,
6666
ProjectReference,
67+
ResolutionCache,
6768
ResolutionCacheHost,
6869
ResolutionMode,
6970
ResolvedModule,
@@ -333,6 +334,8 @@ export interface Watch<T> {
333334
getCurrentProgram(): T;
334335
/** Closes the watch */
335336
close(): void;
337+
/** @internal */
338+
getResolutionCache(): ResolutionCache;
336339
}
337340

338341
/**
@@ -543,8 +546,8 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
543546
if (configFileName) updateExtendedConfigFilesWatches(toPath(configFileName), compilerOptions, watchOptions, WatchType.ExtendedConfigFile);
544547

545548
return configFileName ?
546-
{ getCurrentProgram: getCurrentBuilderProgram, getProgram: updateProgram, close } :
547-
{ getCurrentProgram: getCurrentBuilderProgram, getProgram: updateProgram, updateRootFileNames, close };
549+
{ getCurrentProgram: getCurrentBuilderProgram, getProgram: updateProgram, close, getResolutionCache } :
550+
{ getCurrentProgram: getCurrentBuilderProgram, getProgram: updateProgram, updateRootFileNames, close, getResolutionCache };
548551

549552
function close() {
550553
clearInvalidateResolutionsOfFailedLookupLocations();
@@ -584,6 +587,10 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
584587
}
585588
}
586589

590+
function getResolutionCache() {
591+
return resolutionCache;
592+
}
593+
587594
function getCurrentBuilderProgram() {
588595
return builderProgram;
589596
}

src/harness/incrementalUtils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,15 +176,15 @@ function getProgramStructure(program: ts.Program | undefined) {
176176
return baseline.join("\n");
177177
}
178178

179-
function verifyProgramStructure(expectedProgram: ts.Program, actualProgram: ts.Program, projectName: string) {
179+
export function verifyProgramStructure(expectedProgram: ts.Program, actualProgram: ts.Program, projectName: string) {
180180
const actual = getProgramStructure(actualProgram);
181181
const expected = getProgramStructure(expectedProgram);
182182
ts.Debug.assert(actual === expected, `Program verification:: ${projectName}`, () => `Program Details::\nExpected:\n${expected}\nActual:\n${actual}`);
183183
}
184184

185-
function verifyResolutionCache(actual: ts.ResolutionCache, actualProgram: ts.Program, resolutionHostCacheHost: ts.ResolutionCacheHost) {
185+
export function verifyResolutionCache(actual: ts.ResolutionCache, actualProgram: ts.Program, resolutionHostCacheHost: ts.ResolutionCacheHost) {
186186
const currentDirectory = resolutionHostCacheHost.getCurrentDirectory!();
187-
const expected = ts.createResolutionCache(resolutionHostCacheHost, currentDirectory, /*logChangesWhenResolvingModule*/ false);
187+
const expected = ts.createResolutionCache(resolutionHostCacheHost, actual.rootDirForResolution, /*logChangesWhenResolvingModule*/ false);
188188
expected.startCachingPerDirectoryResolution();
189189

190190
type ExpectedResolution = ts.CachedResolvedModuleWithFailedLookupLocations & ts.CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations;

src/testRunner/unittests/helpers/baseline.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,10 @@ export function commandLineCallbacks(
4040
};
4141
}
4242

43-
export function baselinePrograms(baseline: string[], getPrograms: () => readonly CommandLineProgram[], oldPrograms: readonly (CommandLineProgram | undefined)[], baselineDependencies: boolean | undefined) {
44-
const programs = getPrograms();
43+
export function baselinePrograms(baseline: string[], programs: readonly CommandLineProgram[], oldPrograms: readonly (CommandLineProgram | undefined)[], baselineDependencies: boolean | undefined) {
4544
for (let i = 0; i < programs.length; i++) {
4645
baselineProgram(baseline, programs[i], oldPrograms[i], baselineDependencies);
4746
}
48-
return programs;
4947
}
5048

5149
function baselineProgram(baseline: string[], [program, builderProgram]: CommandLineProgram, oldProgram: CommandLineProgram | undefined, baselineDependencies: boolean | undefined) {

src/testRunner/unittests/helpers/tsc.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,11 @@ export function testTscCompile(input: TestTscCompile) {
152152

153153
function additionalBaseline(sys: TscCompileSystem) {
154154
const { baselineSourceMap, baselineReadFileCalls, baselinePrograms: shouldBaselinePrograms, baselineDependencies } = input;
155-
if (input.computeDtsSignatures) storeDtsSignatures(sys, getPrograms!());
155+
const programs = getPrograms!();
156+
if (input.computeDtsSignatures) storeDtsSignatures(sys, programs);
156157
if (shouldBaselinePrograms) {
157158
const baseline: string[] = [];
158-
baselinePrograms(baseline, getPrograms!, ts.emptyArray, baselineDependencies);
159+
baselinePrograms(baseline, programs, ts.emptyArray, baselineDependencies);
159160
sys.write(baseline.join("\n"));
160161
}
161162
if (baselineReadFileCalls) {

src/testRunner/unittests/helpers/tscWatch.ts

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import {
2+
verifyProgramStructure,
3+
verifyResolutionCache,
4+
} from "../../../harness/incrementalUtils";
15
import { patchHostForBuildInfoReadWrite } from "../../_namespaces/fakes";
26
import { Baseline } from "../../_namespaces/Harness";
37
import * as ts from "../../_namespaces/ts";
@@ -34,6 +38,9 @@ export interface TscWatchCompileChange<T extends ts.BuilderProgram = ts.EmitAndS
3438
programs: readonly CommandLineProgram[],
3539
watchOrSolution: WatchOrSolution<T>
3640
) => void;
41+
// TODO:: sheetal: Needing these fields are technically issues that need to be fixed later
42+
symlinksNotReflected?: readonly string[];
43+
skipStructureCheck?: true;
3744
}
3845
export interface TscWatchCheckOptions {
3946
baselineSourceMap?: boolean;
@@ -212,12 +219,13 @@ export interface RunWatchBaseline<T extends ts.BuilderProgram> extends BaselineB
212219
sys: TscWatchSystem;
213220
getPrograms: () => readonly CommandLineProgram[];
214221
watchOrSolution: WatchOrSolution<T>;
222+
useSourceOfProjectReferenceRedirect?: () => boolean;
215223
}
216224
export function runWatchBaseline<T extends ts.BuilderProgram = ts.EmitAndSemanticDiagnosticsBuilderProgram>({
217225
scenario, subScenario, commandLineArgs,
218226
getPrograms, sys, baseline, oldSnap,
219227
baselineSourceMap, baselineDependencies,
220-
edits, watchOrSolution
228+
edits, watchOrSolution, useSourceOfProjectReferenceRedirect,
221229
}: RunWatchBaseline<T>) {
222230
baseline.push(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}`);
223231
let programs = watchBaseline({
@@ -231,7 +239,7 @@ export function runWatchBaseline<T extends ts.BuilderProgram = ts.EmitAndSemanti
231239
});
232240

233241
if (edits) {
234-
for (const { caption, edit, timeouts } of edits) {
242+
for (const { caption, edit, timeouts, symlinksNotReflected, skipStructureCheck } of edits) {
235243
oldSnap = applyEdit(sys, baseline, edit, caption);
236244
timeouts(sys, programs, watchOrSolution);
237245
programs = watchBaseline({
@@ -242,6 +250,9 @@ export function runWatchBaseline<T extends ts.BuilderProgram = ts.EmitAndSemanti
242250
oldSnap,
243251
baselineSourceMap,
244252
baselineDependencies,
253+
resolutionCache: !skipStructureCheck ? (watchOrSolution as ts.WatchOfConfigFile<T> | undefined)?.getResolutionCache?.() : undefined,
254+
useSourceOfProjectReferenceRedirect,
255+
symlinksNotReflected,
245256
});
246257
}
247258
}
@@ -259,20 +270,87 @@ export function isWatch(commandLineArgs: readonly string[]) {
259270
export interface WatchBaseline extends BaselineBase, TscWatchCheckOptions {
260271
oldPrograms: readonly (CommandLineProgram | undefined)[];
261272
getPrograms: () => readonly CommandLineProgram[];
273+
resolutionCache?: ts.ResolutionCache;
274+
useSourceOfProjectReferenceRedirect?: () => boolean;
275+
symlinksNotReflected?: readonly string[]
262276
}
263-
export function watchBaseline({ baseline, getPrograms, oldPrograms, sys, oldSnap, baselineSourceMap, baselineDependencies }: WatchBaseline) {
277+
export function watchBaseline({
278+
baseline,
279+
getPrograms,
280+
oldPrograms,
281+
sys,
282+
oldSnap,
283+
baselineSourceMap,
284+
baselineDependencies,
285+
resolutionCache,
286+
useSourceOfProjectReferenceRedirect,
287+
symlinksNotReflected,
288+
}: WatchBaseline) {
264289
if (baselineSourceMap) generateSourceMapBaselineFiles(sys);
265290
sys.serializeOutput(baseline);
266-
const programs = baselinePrograms(baseline, getPrograms, oldPrograms, baselineDependencies);
291+
const programs = getPrograms();
292+
baselinePrograms(baseline, programs, oldPrograms, baselineDependencies);
267293
sys.serializeWatches(baseline);
268294
baseline.push(`exitCode:: ExitStatus.${ts.ExitStatus[sys.exitCode as ts.ExitStatus]}`, "");
269295
sys.diff(baseline, oldSnap);
270296
sys.writtenFiles.forEach((value, key) => {
271297
assert.equal(value, 1, `Expected to write file ${key} only once`);
272298
});
299+
// Verify program structure and resolution cache when incremental edit with tsc --watch (without build mode)
300+
if (resolutionCache && programs.length) {
301+
ts.Debug.assert(programs.length === 1);
302+
verifyProgramStructureAndResolutionCache(sys, programs[0][0], resolutionCache, useSourceOfProjectReferenceRedirect, symlinksNotReflected);
303+
}
273304
sys.writtenFiles.clear();
274305
return programs;
275306
}
307+
function verifyProgramStructureAndResolutionCache(
308+
sys: TscWatchSystem,
309+
program: ts.Program,
310+
resolutionCache: ts.ResolutionCache,
311+
useSourceOfProjectReferenceRedirect?: () => boolean,
312+
symlinksNotReflected?: readonly string[],
313+
) {
314+
const options = program.getCompilerOptions();
315+
const compilerHost = ts.createCompilerHostWorker(options, /*setParentNodes*/ undefined, sys);
316+
compilerHost.trace = ts.noop;
317+
compilerHost.writeFile = ts.notImplemented;
318+
compilerHost.useSourceOfProjectReferenceRedirect = useSourceOfProjectReferenceRedirect;
319+
const readFile = compilerHost.readFile;
320+
compilerHost.readFile = fileName => {
321+
const text = readFile.call(compilerHost, fileName);
322+
if (!ts.contains(symlinksNotReflected, fileName)) return text;
323+
// Handle symlinks that dont reflect the watch change
324+
ts.Debug.assert(sys.toPath(sys.realpath(fileName)) !== sys.toPath(fileName));
325+
const file = program.getSourceFile(fileName)!;
326+
ts.Debug.assert(file.text !== text);
327+
return file.text;
328+
};
329+
verifyProgramStructure(ts.createProgram({
330+
rootNames: program.getRootFileNames(),
331+
options,
332+
projectReferences: program.getProjectReferences(),
333+
host: compilerHost,
334+
}), program, options.configFilePath || JSON.stringify(program.getRootFileNames()));
335+
verifyResolutionCache(resolutionCache, program, {
336+
...compilerHost,
337+
338+
getCompilerHost: () => compilerHost,
339+
toPath: fileName => sys.toPath(fileName),
340+
getCompilationSettings: () => options,
341+
fileIsOpen: ts.returnFalse,
342+
getCurrentProgram: () => program,
343+
344+
watchDirectoryOfFailedLookupLocation: ts.returnNoopFileWatcher,
345+
watchAffectingFileLocation: ts.returnNoopFileWatcher,
346+
onInvalidatedResolution: ts.noop,
347+
watchTypeRootsDirectory: ts.returnNoopFileWatcher,
348+
onChangedAutomaticTypeDirectiveNames: ts.noop,
349+
scheduleInvalidateResolutionsOfFailedLookupLocations: ts.noop,
350+
getCachedDirectoryStructureHost: ts.returnUndefined,
351+
writeLog: ts.noop,
352+
});
353+
}
276354
export interface VerifyTscWatch extends TscWatchCompile {
277355
baselineIncremental?: boolean;
278356
}

src/testRunner/unittests/helpers/virtualFileSystemWithWatch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost,
275275
private fs: Map<Path, FSEntry> = new Map();
276276
private time = timeIncrements;
277277
getCanonicalFileName: (s: string) => string;
278-
private toPath: (f: string) => Path;
278+
toPath: (f: string) => Path;
279279
readonly timeoutCallbacks = new Callbacks(this, "Timeout");
280280
readonly immediateCallbacks = new Callbacks(this, "Immedidate");
281281
readonly screenClears: number[] = [];

src/testRunner/unittests/tsbuild/publicApi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export function f22() { } // trailing`,
7474
sys.exit(exitStatus);
7575
sys.write(`exitCode:: ExitStatus.${ts.ExitStatus[sys.exitCode as ts.ExitStatus]}\n`);
7676
const baseline: string[] = [];
77-
baselinePrograms(baseline, getPrograms, ts.emptyArray, /*baselineDependencies*/ false);
77+
baselinePrograms(baseline, getPrograms(), ts.emptyArray, /*baselineDependencies*/ false);
7878
sys.write(baseline.join("\n"));
7979
fs.makeReadonly();
8080
sys.baseLine = () => {

src/testRunner/unittests/tscWatch/forceConsistentCasingInFileNames.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ a;b;
211211
edit: sys => sys.prependFile(diskPath, `// some comment
212212
`),
213213
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
214+
symlinksNotReflected: [`/user/username/projects/myproject/link.ts`]
214215
}
215216
],
216217
});
@@ -262,6 +263,7 @@ a;b;
262263
edit: sys => sys.prependFile(`${diskPath}/a.ts`, `// some comment
263264
`),
264265
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
266+
symlinksNotReflected: [`/user/username/projects/myproject/link/a.ts`]
265267
}
266268
],
267269
});

src/testRunner/unittests/tscWatch/resolutionCache.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,10 @@ declare module "fs" {
296296
}
297297
`),
298298
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
299+
// This is currently issue with ambient modules in same file not leading to resolution watching
300+
// In this case initially resolution is watched and will continued to be watched but
301+
// incremental check will determine that the resolution should not be watched as thats what would have happened if we had started tsc --watch at this state.
302+
skipStructureCheck: true,
299303
}
300304
]
301305
});

src/testRunner/unittests/tscWatch/sourceOfProjectReferenceRedirect.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ describe("unittests:: tsc-watch:: watchAPI:: with sourceOfProjectReferenceRedire
4545
baseline,
4646
oldSnap,
4747
getPrograms,
48-
watchOrSolution: watch
48+
watchOrSolution: watch,
49+
useSourceOfProjectReferenceRedirect: ts.returnTrue,
4950
});
5051
}
5152

src/testRunner/unittests/tscWatch/watchApi.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,8 @@ describe("unittests:: tsc-watch:: watchAPI:: when getParsedCommandLine is implem
583583
timeouts: sys => sys.logTimeoutQueueLength(),
584584
},
585585
],
586-
watchOrSolution: watch
586+
watchOrSolution: watch,
587+
useSourceOfProjectReferenceRedirect: ts.returnTrue,
587588
});
588589
});
589590

0 commit comments

Comments
 (0)