From 26b4b6c9ad87e616c38aeb7e6c1121daadc63f9c Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 20 Aug 2018 12:52:09 -0700 Subject: [PATCH] Create api with watchHost to include in solution builder host --- src/compiler/tsbuild.ts | 36 +++++++++---- src/compiler/watch.ts | 52 ++++++++++++------- src/testRunner/unittests/tsbuildWatchMode.ts | 4 +- src/tsc/tsc.ts | 6 ++- .../reference/api/tsserverlibrary.d.ts | 25 +++++---- tests/baselines/reference/api/typescript.d.ts | 25 +++++---- 6 files changed, 94 insertions(+), 54 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index f0a81fe8b4..e6bc8d252d 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -394,6 +394,9 @@ namespace ts { deleteFile(fileName: string): void; } + export interface SolutionBuilderWithWatchHost extends SolutionBuilderHost, WatchHost { + } + export function createSolutionBuilderHost(system = sys) { const host = createCompilerHost({}, /*setParentNodes*/ undefined, system) as SolutionBuilderHost; host.getModifiedTime = system.getModifiedTime ? path => system.getModifiedTime!(path) : () => undefined; @@ -402,12 +405,25 @@ namespace ts { return host; } + export function createSolutionBuilderWithWatchHost(system = sys, reportWatchStatus?: WatchStatusReporter) { + const host = createSolutionBuilderHost(system) as SolutionBuilderWithWatchHost; + const watchHost = createWatchHost(system, reportWatchStatus); + host.onWatchStatusChange = watchHost.onWatchStatusChange; + host.watchFile = watchHost.watchFile; + host.watchDirectory = watchHost.watchDirectory; + host.setTimeout = watchHost.setTimeout; + host.clearTimeout = watchHost.clearTimeout; + return host; + } + /** * A SolutionBuilder has an immutable set of rootNames that are the "entry point" projects, but * can dynamically add/remove other projects based on changes on the rootNames' references + * TODO: use SolutionBuilderWithWatchHost => watchedSolution + * use SolutionBuilderHost => Solution */ - export function createSolutionBuilder(host: SolutionBuilderHost, buildHost: BuildHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions, system?: System) { - + export function createSolutionBuilder(host: SolutionBuilderHost, buildHost: BuildHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions) { + const hostWithWatch = host as SolutionBuilderWithWatchHost; const configFileCache = createConfigFileCache(host); let context = createBuildContext(defaultOptions); @@ -430,9 +446,6 @@ namespace ts { }; function startWatching() { - if (!system) throw new Error("System host must be provided if using --watch"); - if (!system.watchFile || !system.watchDirectory || !system.setTimeout) throw new Error("System host must support watchFile / watchDirectory / setTimeout if using --watch"); - const graph = getGlobalDependencyGraph()!; if (!graph.buildQueue) { // Everything is broken - we don't even know what to watch. Give up. @@ -443,7 +456,7 @@ namespace ts { const cfg = configFileCache.parseConfigFile(resolved); if (cfg) { // Watch this file - system.watchFile(resolved, () => { + hostWithWatch.watchFile(resolved, () => { configFileCache.removeKey(resolved); invalidateProjectAndScheduleBuilds(resolved); }); @@ -451,7 +464,7 @@ namespace ts { // Update watchers for wildcard directories if (cfg.configFileSpecs) { updateWatchingWildcardDirectories(existingWatchersForWildcards, createMapFromTemplate(cfg.configFileSpecs.wildcardDirectories), (dir, flags) => { - return system.watchDirectory!(dir, () => { + return hostWithWatch.watchDirectory(dir, () => { invalidateProjectAndScheduleBuilds(resolved); }, !!(flags & WatchDirectoryFlags.Recursive)); }); @@ -459,7 +472,7 @@ namespace ts { // Watch input files for (const input of cfg.fileNames) { - system.watchFile(input, () => { + hostWithWatch.watchFile(input, () => { invalidateProjectAndScheduleBuilds(resolved); }); } @@ -468,8 +481,11 @@ namespace ts { function invalidateProjectAndScheduleBuilds(resolved: ResolvedConfigFileName) { invalidateProject(resolved); - system!.setTimeout!(buildInvalidatedProjects, 100); - system!.setTimeout!(buildDependentInvalidatedProjects, 3000); + if (!hostWithWatch.setTimeout) { + return; + } + hostWithWatch.setTimeout(buildInvalidatedProjects, 100); + hostWithWatch.setTimeout(buildDependentInvalidatedProjects, 3000); } } diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 9c759158f6..63904ffa43 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -174,6 +174,17 @@ namespace ts { const noopFileWatcher: FileWatcher = { close: noop }; + export function createWatchHost(system = sys, reportWatchStatus?: WatchStatusReporter): WatchHost { + const onWatchStatusChange = reportWatchStatus || createWatchStatusReporter(system); + return { + onWatchStatusChange, + watchFile: system.watchFile ? ((path, callback, pollingInterval) => system.watchFile!(path, callback, pollingInterval)) : () => noopFileWatcher, + watchDirectory: system.watchDirectory ? ((path, callback, recursive) => system.watchDirectory!(path, callback, recursive)) : () => noopFileWatcher, + setTimeout: system.setTimeout ? ((callback, ms, ...args: any[]) => system.setTimeout!.call(system, callback, ms, ...args)) : noop, + clearTimeout: system.clearTimeout ? (timeoutId => system.clearTimeout!(timeoutId)) : noop + }; + } + /** * Creates the watch compiler host that can be extended with config file or root file names and options host */ @@ -186,7 +197,7 @@ namespace ts { host; // tslint:disable-line no-unused-expression (TODO: `host` is unused!) const useCaseSensitiveFileNames = () => system.useCaseSensitiveFileNames; const writeFileName = (s: string) => system.write(s + system.newLine); - const onWatchStatusChange = reportWatchStatus || createWatchStatusReporter(system); + const { onWatchStatusChange, watchFile, watchDirectory, setTimeout, clearTimeout } = createWatchHost(system, reportWatchStatus); return { useCaseSensitiveFileNames, getNewLine: () => system.newLine, @@ -200,10 +211,10 @@ namespace ts { readDirectory: (path, extensions, exclude, include, depth) => system.readDirectory(path, extensions, exclude, include, depth), realpath: system.realpath && (path => system.realpath!(path)), getEnvironmentVariable: system.getEnvironmentVariable && (name => system.getEnvironmentVariable(name)), - watchFile: system.watchFile ? ((path, callback, pollingInterval) => system.watchFile!(path, callback, pollingInterval)) : () => noopFileWatcher, - watchDirectory: system.watchDirectory ? ((path, callback, recursive) => system.watchDirectory!(path, callback, recursive)) : () => noopFileWatcher, - setTimeout: system.setTimeout ? ((callback, ms, ...args: any[]) => system.setTimeout!.call(system, callback, ms, ...args)) : noop, - clearTimeout: system.clearTimeout ? (timeoutId => system.clearTimeout!(timeoutId)) : noop, + watchFile, + watchDirectory, + setTimeout, + clearTimeout, trace: s => system.write(s), onWatchStatusChange, createDirectory: path => system.createDirectory(path), @@ -224,10 +235,10 @@ namespace ts { const reportSummary = (errorCount: number) => { if (errorCount === 1) { - onWatchStatusChange(createCompilerDiagnostic(Diagnostics.Found_1_error_Watching_for_file_changes, errorCount), newLine, compilerOptions); + onWatchStatusChange!(createCompilerDiagnostic(Diagnostics.Found_1_error_Watching_for_file_changes, errorCount), newLine, compilerOptions); } else { - onWatchStatusChange(createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errorCount, errorCount), newLine, compilerOptions); + onWatchStatusChange!(createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errorCount, errorCount), newLine, compilerOptions); } }; @@ -270,7 +281,21 @@ namespace ts { export type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions) => void; /** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */ export type CreateProgram = (rootNames: ReadonlyArray | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: ReadonlyArray) => T; - export interface WatchCompilerHost { + /** Host that has watch functionality used in --watch mode */ + export interface WatchHost { + /** If provided, called with Diagnostic message that informs about change in watch status */ + onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions): void; + + /** Used to watch changes in source files, missing files needed to update the program or config file */ + watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; + /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ + watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; + /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */ + setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; + /** If provided, will be used to reset existing delayed compilation */ + clearTimeout?(timeoutId: any): void; + } + export interface WatchCompilerHost extends WatchHost { // TODO: GH#18217 Optional methods are frequently asserted /** @@ -279,8 +304,6 @@ namespace ts { createProgram: CreateProgram; /** If provided, callback to invoke after every new program creation */ afterProgramCreate?(program: T): void; - /** If provided, called with Diagnostic message that informs about change in watch status */ - onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions): void; // Only for testing /*@internal*/ @@ -323,15 +346,6 @@ namespace ts { resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; - - /** Used to watch changes in source files, missing files needed to update the program or config file */ - watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; - /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ - watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; - /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */ - setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; - /** If provided, will be used to reset existing delayed compilation */ - clearTimeout?(timeoutId: any): void; } /** Internal interface used to wire emit through same host */ diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index dd6a95f0f1..87224c0933 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -1,7 +1,7 @@ namespace ts.tscWatch { export import libFile = TestFSWithWatch.libFile; function createSolutionBuilder(system: WatchedSystem, rootNames: ReadonlyArray, defaultOptions?: BuildOptions) { - const host = createSolutionBuilderHost(system); + const host = createSolutionBuilderWithWatchHost(system); const reportDiag = createDiagnosticReporter(system); const report = (message: DiagnosticMessage, ...args: string[]) => reportDiag(createCompilerDiagnostic(message, ...args)); const buildHost: BuildHost = { @@ -10,7 +10,7 @@ namespace ts.tscWatch { message: report, errorDiagnostic: d => reportDiag(d) }; - return ts.createSolutionBuilder(host, buildHost, rootNames, defaultOptions || { dry: false, force: false, verbose: false }, system); + return ts.createSolutionBuilder(host, buildHost, rootNames, defaultOptions || { dry: false, force: false, verbose: false, watch: true }); } function createSolutionBuilderWithWatch(host: WatchedSystem, rootNames: ReadonlyArray, defaultOptions?: BuildOptions) { diff --git a/src/tsc/tsc.ts b/src/tsc/tsc.ts index d53aa1f56f..5c7af1a4b9 100644 --- a/src/tsc/tsc.ts +++ b/src/tsc/tsc.ts @@ -247,6 +247,9 @@ namespace ts { reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--build")); return ExitStatus.DiagnosticsPresent_OutputsSkipped; } + if (buildOptions.watch) { + reportWatchModeWithoutSysSupport(); + } // Nonsensical combinations if (buildOptions.clean && buildOptions.force) { @@ -279,7 +282,8 @@ namespace ts { errorDiagnostic: d => reportDiagnostic(d) }; - const builder = createSolutionBuilder(createSolutionBuilderHost(), buildHost, projects, buildOptions); + // TODO: change this to host if watch => watchHost otherwiue without wathc + const builder = createSolutionBuilder(createSolutionBuilderWithWatchHost(), buildHost, projects, buildOptions); if (buildOptions.clean) { return builder.cleanAllProjects(); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 5c50a7cf45..dd5f3c7c65 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4309,15 +4309,26 @@ declare namespace ts { type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions) => void; /** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */ type CreateProgram = (rootNames: ReadonlyArray | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: ReadonlyArray) => T; - interface WatchCompilerHost { + /** Host that has watch functionality used in --watch mode */ + interface WatchHost { + /** If provided, called with Diagnostic message that informs about change in watch status */ + onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions): void; + /** Used to watch changes in source files, missing files needed to update the program or config file */ + watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; + /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ + watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; + /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */ + setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; + /** If provided, will be used to reset existing delayed compilation */ + clearTimeout?(timeoutId: any): void; + } + interface WatchCompilerHost extends WatchHost { /** * Used to create the program when need for program creation or recreation detected */ createProgram: CreateProgram; /** If provided, callback to invoke after every new program creation */ afterProgramCreate?(program: T): void; - /** If provided, called with Diagnostic message that informs about change in watch status */ - onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions): void; useCaseSensitiveFileNames(): boolean; getNewLine(): string; getCurrentDirectory(): string; @@ -4350,14 +4361,6 @@ declare namespace ts { resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; - /** Used to watch changes in source files, missing files needed to update the program or config file */ - watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; - /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ - watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; - /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */ - setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; - /** If provided, will be used to reset existing delayed compilation */ - clearTimeout?(timeoutId: any): void; } /** * Host to create watch with root files and options diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 29e485415f..2938fc9bcc 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4309,15 +4309,26 @@ declare namespace ts { type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions) => void; /** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */ type CreateProgram = (rootNames: ReadonlyArray | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: ReadonlyArray) => T; - interface WatchCompilerHost { + /** Host that has watch functionality used in --watch mode */ + interface WatchHost { + /** If provided, called with Diagnostic message that informs about change in watch status */ + onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions): void; + /** Used to watch changes in source files, missing files needed to update the program or config file */ + watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; + /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ + watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; + /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */ + setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; + /** If provided, will be used to reset existing delayed compilation */ + clearTimeout?(timeoutId: any): void; + } + interface WatchCompilerHost extends WatchHost { /** * Used to create the program when need for program creation or recreation detected */ createProgram: CreateProgram; /** If provided, callback to invoke after every new program creation */ afterProgramCreate?(program: T): void; - /** If provided, called with Diagnostic message that informs about change in watch status */ - onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions): void; useCaseSensitiveFileNames(): boolean; getNewLine(): string; getCurrentDirectory(): string; @@ -4350,14 +4361,6 @@ declare namespace ts { resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; - /** Used to watch changes in source files, missing files needed to update the program or config file */ - watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; - /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ - watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; - /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */ - setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; - /** If provided, will be used to reset existing delayed compilation */ - clearTimeout?(timeoutId: any): void; } /** * Host to create watch with root files and options