Skip to content

Commit 26b4b6c

Browse files
committed
Create api with watchHost to include in solution builder host
1 parent 0c4003e commit 26b4b6c

File tree

6 files changed

+94
-54
lines changed

6 files changed

+94
-54
lines changed

src/compiler/tsbuild.ts

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,9 @@ namespace ts {
394394
deleteFile(fileName: string): void;
395395
}
396396

397+
export interface SolutionBuilderWithWatchHost extends SolutionBuilderHost, WatchHost {
398+
}
399+
397400
export function createSolutionBuilderHost(system = sys) {
398401
const host = createCompilerHost({}, /*setParentNodes*/ undefined, system) as SolutionBuilderHost;
399402
host.getModifiedTime = system.getModifiedTime ? path => system.getModifiedTime!(path) : () => undefined;
@@ -402,12 +405,25 @@ namespace ts {
402405
return host;
403406
}
404407

408+
export function createSolutionBuilderWithWatchHost(system = sys, reportWatchStatus?: WatchStatusReporter) {
409+
const host = createSolutionBuilderHost(system) as SolutionBuilderWithWatchHost;
410+
const watchHost = createWatchHost(system, reportWatchStatus);
411+
host.onWatchStatusChange = watchHost.onWatchStatusChange;
412+
host.watchFile = watchHost.watchFile;
413+
host.watchDirectory = watchHost.watchDirectory;
414+
host.setTimeout = watchHost.setTimeout;
415+
host.clearTimeout = watchHost.clearTimeout;
416+
return host;
417+
}
418+
405419
/**
406420
* A SolutionBuilder has an immutable set of rootNames that are the "entry point" projects, but
407421
* can dynamically add/remove other projects based on changes on the rootNames' references
422+
* TODO: use SolutionBuilderWithWatchHost => watchedSolution
423+
* use SolutionBuilderHost => Solution
408424
*/
409-
export function createSolutionBuilder(host: SolutionBuilderHost, buildHost: BuildHost, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions, system?: System) {
410-
425+
export function createSolutionBuilder(host: SolutionBuilderHost, buildHost: BuildHost, rootNames: ReadonlyArray<string>, defaultOptions: BuildOptions) {
426+
const hostWithWatch = host as SolutionBuilderWithWatchHost;
411427
const configFileCache = createConfigFileCache(host);
412428
let context = createBuildContext(defaultOptions);
413429

@@ -430,9 +446,6 @@ namespace ts {
430446
};
431447

432448
function startWatching() {
433-
if (!system) throw new Error("System host must be provided if using --watch");
434-
if (!system.watchFile || !system.watchDirectory || !system.setTimeout) throw new Error("System host must support watchFile / watchDirectory / setTimeout if using --watch");
435-
436449
const graph = getGlobalDependencyGraph()!;
437450
if (!graph.buildQueue) {
438451
// Everything is broken - we don't even know what to watch. Give up.
@@ -443,23 +456,23 @@ namespace ts {
443456
const cfg = configFileCache.parseConfigFile(resolved);
444457
if (cfg) {
445458
// Watch this file
446-
system.watchFile(resolved, () => {
459+
hostWithWatch.watchFile(resolved, () => {
447460
configFileCache.removeKey(resolved);
448461
invalidateProjectAndScheduleBuilds(resolved);
449462
});
450463

451464
// Update watchers for wildcard directories
452465
if (cfg.configFileSpecs) {
453466
updateWatchingWildcardDirectories(existingWatchersForWildcards, createMapFromTemplate(cfg.configFileSpecs.wildcardDirectories), (dir, flags) => {
454-
return system.watchDirectory!(dir, () => {
467+
return hostWithWatch.watchDirectory(dir, () => {
455468
invalidateProjectAndScheduleBuilds(resolved);
456469
}, !!(flags & WatchDirectoryFlags.Recursive));
457470
});
458471
}
459472

460473
// Watch input files
461474
for (const input of cfg.fileNames) {
462-
system.watchFile(input, () => {
475+
hostWithWatch.watchFile(input, () => {
463476
invalidateProjectAndScheduleBuilds(resolved);
464477
});
465478
}
@@ -468,8 +481,11 @@ namespace ts {
468481

469482
function invalidateProjectAndScheduleBuilds(resolved: ResolvedConfigFileName) {
470483
invalidateProject(resolved);
471-
system!.setTimeout!(buildInvalidatedProjects, 100);
472-
system!.setTimeout!(buildDependentInvalidatedProjects, 3000);
484+
if (!hostWithWatch.setTimeout) {
485+
return;
486+
}
487+
hostWithWatch.setTimeout(buildInvalidatedProjects, 100);
488+
hostWithWatch.setTimeout(buildDependentInvalidatedProjects, 3000);
473489
}
474490
}
475491

src/compiler/watch.ts

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,17 @@ namespace ts {
174174

175175
const noopFileWatcher: FileWatcher = { close: noop };
176176

177+
export function createWatchHost(system = sys, reportWatchStatus?: WatchStatusReporter): WatchHost {
178+
const onWatchStatusChange = reportWatchStatus || createWatchStatusReporter(system);
179+
return {
180+
onWatchStatusChange,
181+
watchFile: system.watchFile ? ((path, callback, pollingInterval) => system.watchFile!(path, callback, pollingInterval)) : () => noopFileWatcher,
182+
watchDirectory: system.watchDirectory ? ((path, callback, recursive) => system.watchDirectory!(path, callback, recursive)) : () => noopFileWatcher,
183+
setTimeout: system.setTimeout ? ((callback, ms, ...args: any[]) => system.setTimeout!.call(system, callback, ms, ...args)) : noop,
184+
clearTimeout: system.clearTimeout ? (timeoutId => system.clearTimeout!(timeoutId)) : noop
185+
};
186+
}
187+
177188
/**
178189
* Creates the watch compiler host that can be extended with config file or root file names and options host
179190
*/
@@ -186,7 +197,7 @@ namespace ts {
186197
host; // tslint:disable-line no-unused-expression (TODO: `host` is unused!)
187198
const useCaseSensitiveFileNames = () => system.useCaseSensitiveFileNames;
188199
const writeFileName = (s: string) => system.write(s + system.newLine);
189-
const onWatchStatusChange = reportWatchStatus || createWatchStatusReporter(system);
200+
const { onWatchStatusChange, watchFile, watchDirectory, setTimeout, clearTimeout } = createWatchHost(system, reportWatchStatus);
190201
return {
191202
useCaseSensitiveFileNames,
192203
getNewLine: () => system.newLine,
@@ -200,10 +211,10 @@ namespace ts {
200211
readDirectory: (path, extensions, exclude, include, depth) => system.readDirectory(path, extensions, exclude, include, depth),
201212
realpath: system.realpath && (path => system.realpath!(path)),
202213
getEnvironmentVariable: system.getEnvironmentVariable && (name => system.getEnvironmentVariable(name)),
203-
watchFile: system.watchFile ? ((path, callback, pollingInterval) => system.watchFile!(path, callback, pollingInterval)) : () => noopFileWatcher,
204-
watchDirectory: system.watchDirectory ? ((path, callback, recursive) => system.watchDirectory!(path, callback, recursive)) : () => noopFileWatcher,
205-
setTimeout: system.setTimeout ? ((callback, ms, ...args: any[]) => system.setTimeout!.call(system, callback, ms, ...args)) : noop,
206-
clearTimeout: system.clearTimeout ? (timeoutId => system.clearTimeout!(timeoutId)) : noop,
214+
watchFile,
215+
watchDirectory,
216+
setTimeout,
217+
clearTimeout,
207218
trace: s => system.write(s),
208219
onWatchStatusChange,
209220
createDirectory: path => system.createDirectory(path),
@@ -224,10 +235,10 @@ namespace ts {
224235

225236
const reportSummary = (errorCount: number) => {
226237
if (errorCount === 1) {
227-
onWatchStatusChange(createCompilerDiagnostic(Diagnostics.Found_1_error_Watching_for_file_changes, errorCount), newLine, compilerOptions);
238+
onWatchStatusChange!(createCompilerDiagnostic(Diagnostics.Found_1_error_Watching_for_file_changes, errorCount), newLine, compilerOptions);
228239
}
229240
else {
230-
onWatchStatusChange(createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errorCount, errorCount), newLine, compilerOptions);
241+
onWatchStatusChange!(createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errorCount, errorCount), newLine, compilerOptions);
231242
}
232243
};
233244

@@ -270,7 +281,21 @@ namespace ts {
270281
export type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions) => void;
271282
/** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */
272283
export type CreateProgram<T extends BuilderProgram> = (rootNames: ReadonlyArray<string> | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>) => T;
273-
export interface WatchCompilerHost<T extends BuilderProgram> {
284+
/** Host that has watch functionality used in --watch mode */
285+
export interface WatchHost {
286+
/** If provided, called with Diagnostic message that informs about change in watch status */
287+
onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions): void;
288+
289+
/** Used to watch changes in source files, missing files needed to update the program or config file */
290+
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
291+
/** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */
292+
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
293+
/** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */
294+
setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
295+
/** If provided, will be used to reset existing delayed compilation */
296+
clearTimeout?(timeoutId: any): void;
297+
}
298+
export interface WatchCompilerHost<T extends BuilderProgram> extends WatchHost {
274299
// TODO: GH#18217 Optional methods are frequently asserted
275300

276301
/**
@@ -279,8 +304,6 @@ namespace ts {
279304
createProgram: CreateProgram<T>;
280305
/** If provided, callback to invoke after every new program creation */
281306
afterProgramCreate?(program: T): void;
282-
/** If provided, called with Diagnostic message that informs about change in watch status */
283-
onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions): void;
284307

285308
// Only for testing
286309
/*@internal*/
@@ -323,15 +346,6 @@ namespace ts {
323346
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[];
324347
/** If provided, used to resolve type reference directives, otherwise typescript's default resolution */
325348
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
326-
327-
/** Used to watch changes in source files, missing files needed to update the program or config file */
328-
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
329-
/** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */
330-
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
331-
/** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */
332-
setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
333-
/** If provided, will be used to reset existing delayed compilation */
334-
clearTimeout?(timeoutId: any): void;
335349
}
336350

337351
/** Internal interface used to wire emit through same host */

src/testRunner/unittests/tsbuildWatchMode.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
namespace ts.tscWatch {
22
export import libFile = TestFSWithWatch.libFile;
33
function createSolutionBuilder(system: WatchedSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
4-
const host = createSolutionBuilderHost(system);
4+
const host = createSolutionBuilderWithWatchHost(system);
55
const reportDiag = createDiagnosticReporter(system);
66
const report = (message: DiagnosticMessage, ...args: string[]) => reportDiag(createCompilerDiagnostic(message, ...args));
77
const buildHost: BuildHost = {
@@ -10,7 +10,7 @@ namespace ts.tscWatch {
1010
message: report,
1111
errorDiagnostic: d => reportDiag(d)
1212
};
13-
return ts.createSolutionBuilder(host, buildHost, rootNames, defaultOptions || { dry: false, force: false, verbose: false }, system);
13+
return ts.createSolutionBuilder(host, buildHost, rootNames, defaultOptions || { dry: false, force: false, verbose: false, watch: true });
1414
}
1515

1616
function createSolutionBuilderWithWatch(host: WatchedSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {

src/tsc/tsc.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,9 @@ namespace ts {
247247
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--build"));
248248
return ExitStatus.DiagnosticsPresent_OutputsSkipped;
249249
}
250+
if (buildOptions.watch) {
251+
reportWatchModeWithoutSysSupport();
252+
}
250253

251254
// Nonsensical combinations
252255
if (buildOptions.clean && buildOptions.force) {
@@ -279,7 +282,8 @@ namespace ts {
279282
errorDiagnostic: d => reportDiagnostic(d)
280283
};
281284

282-
const builder = createSolutionBuilder(createSolutionBuilderHost(), buildHost, projects, buildOptions);
285+
// TODO: change this to host if watch => watchHost otherwiue without wathc
286+
const builder = createSolutionBuilder(createSolutionBuilderWithWatchHost(), buildHost, projects, buildOptions);
283287
if (buildOptions.clean) {
284288
return builder.cleanAllProjects();
285289
}

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4309,15 +4309,26 @@ declare namespace ts {
43094309
type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions) => void;
43104310
/** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */
43114311
type CreateProgram<T extends BuilderProgram> = (rootNames: ReadonlyArray<string> | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>) => T;
4312-
interface WatchCompilerHost<T extends BuilderProgram> {
4312+
/** Host that has watch functionality used in --watch mode */
4313+
interface WatchHost {
4314+
/** If provided, called with Diagnostic message that informs about change in watch status */
4315+
onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions): void;
4316+
/** Used to watch changes in source files, missing files needed to update the program or config file */
4317+
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
4318+
/** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */
4319+
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
4320+
/** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */
4321+
setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
4322+
/** If provided, will be used to reset existing delayed compilation */
4323+
clearTimeout?(timeoutId: any): void;
4324+
}
4325+
interface WatchCompilerHost<T extends BuilderProgram> extends WatchHost {
43134326
/**
43144327
* Used to create the program when need for program creation or recreation detected
43154328
*/
43164329
createProgram: CreateProgram<T>;
43174330
/** If provided, callback to invoke after every new program creation */
43184331
afterProgramCreate?(program: T): void;
4319-
/** If provided, called with Diagnostic message that informs about change in watch status */
4320-
onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions): void;
43214332
useCaseSensitiveFileNames(): boolean;
43224333
getNewLine(): string;
43234334
getCurrentDirectory(): string;
@@ -4350,14 +4361,6 @@ declare namespace ts {
43504361
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[];
43514362
/** If provided, used to resolve type reference directives, otherwise typescript's default resolution */
43524363
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
4353-
/** Used to watch changes in source files, missing files needed to update the program or config file */
4354-
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
4355-
/** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */
4356-
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
4357-
/** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */
4358-
setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
4359-
/** If provided, will be used to reset existing delayed compilation */
4360-
clearTimeout?(timeoutId: any): void;
43614364
}
43624365
/**
43634366
* Host to create watch with root files and options

0 commit comments

Comments
 (0)