Skip to content

Commit 8b31670

Browse files
committed
WatchFactory implementation
1 parent 5d692d5 commit 8b31670

File tree

70 files changed

+2386
-1166
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+2386
-1166
lines changed

src/compiler/tsbuildPublic.ts

Lines changed: 74 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import {
1010
clearMap,
1111
closeFileWatcher,
1212
closeFileWatcherOf,
13+
combinePaths,
1314
commonOptionsWithBuild,
15+
compareDataObjects,
1416
CompilerHost,
1517
CompilerOptions,
1618
CompilerOptionsValue,
@@ -63,6 +65,7 @@ import {
6365
getLocaleTimeString,
6466
getModifiedTime as ts_getModifiedTime,
6567
getNormalizedAbsolutePath,
68+
GetOrLoadUserWatchFactoryDetailsForFactory,
6669
getParsedCommandLineOfConfigFile,
6770
getPendingEmitKind,
6871
getSourceFileVersionAsHashFromText,
@@ -119,6 +122,7 @@ import {
119122
updateWatchingWildcardDirectories,
120123
UpToDateStatus,
121124
UpToDateStatusType,
125+
UserWatchFactoryWithName,
122126
version,
123127
WatchFactory,
124128
WatchHost,
@@ -414,6 +418,7 @@ interface SolutionBuilderState<T extends BuilderProgram> extends WatchFactory<Wa
414418
readonly allWatchedConfigFiles: Map<ResolvedConfigFilePath, FileWatcher>;
415419
readonly allWatchedExtendedConfigFiles: Map<Path, SharedExtendedConfigFileWatcher<ResolvedConfigFilePath>>;
416420
readonly allWatchedPackageJsonFiles: Map<ResolvedConfigFilePath, Map<Path, FileWatcher>>;
421+
readonly allWatchFactories: Map<ResolvedConfigFilePath, UserWatchFactoryWithName | false>;
417422
readonly filesWatched: Map<Path, FileWatcherWithModifiedTime | Date>;
418423
readonly outputTimeStamps: Map<ResolvedConfigFilePath, Map<Path, Date>>;
419424

@@ -424,7 +429,7 @@ interface SolutionBuilderState<T extends BuilderProgram> extends WatchFactory<Wa
424429
writeLog: (s: string) => void;
425430
}
426431

427-
function createSolutionBuilderState<T extends BuilderProgram>(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], options: BuildOptions, baseWatchOptions: WatchOptions | undefined): SolutionBuilderState<T> {
432+
function createSolutionBuilderAndState<T extends BuilderProgram>(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], options: BuildOptions, baseWatchOptions: WatchOptions | undefined): SolutionBuilder<T> {
428433
const host = hostOrHostWithWatch as SolutionBuilderHost<T>;
429434
const hostWithWatch = hostOrHostWithWatch as SolutionBuilderWithWatchHost<T>;
430435

@@ -482,7 +487,11 @@ function createSolutionBuilderState<T extends BuilderProgram>(watch: boolean, ho
482487
}
483488
compilerHost.getBuildInfo = (fileName, configFilePath) => getBuildInfo(state, fileName, toResolvedConfigFilePath(state, configFilePath as ResolvedConfigFileName), /*modifiedTime*/ undefined);
484489

485-
const { watchFile, watchDirectory, writeLog } = createWatchFactory<ResolvedConfigFileName>(hostWithWatch, options);
490+
const { watchFile, watchDirectory, writeLog } = createWatchFactory<ResolvedConfigFileName>(
491+
hostWithWatch,
492+
resolved => getOrLoadUserWatchFactoryDetails(state, () => solution, resolved!),
493+
options,
494+
);
486495

487496
const state: SolutionBuilderState<T> = {
488497
host,
@@ -529,6 +538,7 @@ function createSolutionBuilderState<T extends BuilderProgram>(watch: boolean, ho
529538
allWatchedConfigFiles: new Map(),
530539
allWatchedExtendedConfigFiles: new Map(),
531540
allWatchedPackageJsonFiles: new Map(),
541+
allWatchFactories: new Map(),
532542
filesWatched: new Map(),
533543

534544
lastCachedPackageJsonLookups: new Map(),
@@ -540,7 +550,25 @@ function createSolutionBuilderState<T extends BuilderProgram>(watch: boolean, ho
540550
writeLog,
541551
};
542552

543-
return state;
553+
const solution: SolutionBuilder<T> = {
554+
build: (project, cancellationToken, writeFile, getCustomTransformers) => build(state, project, cancellationToken, writeFile, getCustomTransformers),
555+
clean: project => clean(state, project),
556+
buildReferences: (project, cancellationToken, writeFile, getCustomTransformers) => build(state, project, cancellationToken, writeFile, getCustomTransformers, /*onlyReferences*/ true),
557+
cleanReferences: project => clean(state, project, /*onlyReferences*/ true),
558+
getNextInvalidatedProject: cancellationToken => {
559+
setupInitialBuild(state, cancellationToken);
560+
return getNextInvalidatedProject(state, getBuildOrder(state), /*reportQueue*/ false);
561+
},
562+
getBuildOrder: () => getBuildOrder(state),
563+
getUpToDateStatusOfProject: project => {
564+
const configFileName = resolveProjectName(state, project);
565+
const configFilePath = toResolvedConfigFilePath(state, configFileName);
566+
return getUpToDateStatus(state, parseConfigFile(state, configFileName, configFilePath), configFilePath);
567+
},
568+
invalidateProject: (configFilePath, reloadLevel) => invalidateProject(state, configFilePath, reloadLevel || ConfigFileProgramReloadLevel.None),
569+
close: () => stopWatching(state),
570+
};
571+
return solution;
544572
}
545573

546574
function toPath<T extends BuilderProgram>(state: SolutionBuilderState<T>, fileName: string) {
@@ -704,6 +732,8 @@ function createStateBuildOrder<T extends BuilderProgram>(state: SolutionBuilderS
704732
currentProjects,
705733
{ onDeleteValue: existingMap => existingMap.forEach(closeFileWatcher) }
706734
);
735+
736+
mutateMapSkippingNewValues(state.allWatchFactories, currentProjects, noopOnDelete);
707737
}
708738
return state.buildOrder = buildOrder;
709739
}
@@ -1397,6 +1427,7 @@ function getNextInvalidatedProjectCreateInfo<T extends BuilderProgram>(
13971427
reportBuildQueue(state, buildOrder);
13981428
}
13991429

1430+
const oldConfig = getCachedParsedConfigFile(state, projectPath);
14001431
const config = parseConfigFile(state, project, projectPath);
14011432
if (!config) {
14021433
reportParseConfigFileDiagnostic(state, projectPath);
@@ -1405,8 +1436,9 @@ function getNextInvalidatedProjectCreateInfo<T extends BuilderProgram>(
14051436
}
14061437

14071438
if (reloadLevel === ConfigFileProgramReloadLevel.Full) {
1439+
releaseExistingUseWatchFactory(state, projectPath, oldConfig, config);
14081440
watchConfigFile(state, project, projectPath, config);
1409-
watchExtendedConfigFiles(state, projectPath, config);
1441+
watchExtendedConfigFiles(state, project, projectPath, config);
14101442
watchWildCardDirectories(state, project, projectPath, config);
14111443
watchInputFiles(state, project, projectPath, config);
14121444
watchPackageJsonFiles(state, project, projectPath, config);
@@ -1593,7 +1625,7 @@ function getModifiedTime<T extends BuilderProgram>(state: SolutionBuilderState<T
15931625
return result;
15941626
}
15951627

1596-
function watchFile<T extends BuilderProgram>(state: SolutionBuilderState<T>, file: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined, watchType: WatchType, project?: ResolvedConfigFileName): FileWatcher {
1628+
function watchFile<T extends BuilderProgram>(state: SolutionBuilderState<T>, file: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined, watchType: WatchType, project: ResolvedConfigFileName): FileWatcher {
15971629
const path = toPath(state, file);
15981630
const existing = state.filesWatched.get(path);
15991631
if (existing && isFileWatcherWithModifiedTime(existing)) {
@@ -2319,6 +2351,19 @@ function buildNextInvalidatedProjectWorker<T extends BuilderProgram>(state: Solu
23192351
return buildOrder;
23202352
}
23212353

2354+
function releaseExistingUseWatchFactory<T extends BuilderProgram>(
2355+
state: SolutionBuilderState<T>,
2356+
resolvedPath: ResolvedConfigFilePath,
2357+
oldConfig: ParsedCommandLine | undefined,
2358+
newConfig: ParsedCommandLine,
2359+
) {
2360+
const existing = state.allWatchFactories.get(resolvedPath);
2361+
if (existing !== undefined &&
2362+
!compareDataObjects(oldConfig?.watchOptions?.watchFactory, newConfig.watchOptions?.watchFactory)) {
2363+
state.allWatchFactories.delete(resolvedPath);
2364+
}
2365+
}
2366+
23222367
function watchConfigFile<T extends BuilderProgram>(state: SolutionBuilderState<T>, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine | undefined) {
23232368
if (!state.watch || state.allWatchedConfigFiles.has(resolvedPath)) return;
23242369
state.allWatchedConfigFiles.set(resolvedPath, watchFile(
@@ -2332,7 +2377,7 @@ function watchConfigFile<T extends BuilderProgram>(state: SolutionBuilderState<T
23322377
));
23332378
}
23342379

2335-
function watchExtendedConfigFiles<T extends BuilderProgram>(state: SolutionBuilderState<T>, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine | undefined) {
2380+
function watchExtendedConfigFiles<T extends BuilderProgram>(state: SolutionBuilderState<T>, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine | undefined) {
23362381
updateSharedExtendedConfigFileWatcher(
23372382
resolvedPath,
23382383
parsed?.options,
@@ -2345,6 +2390,7 @@ function watchExtendedConfigFiles<T extends BuilderProgram>(state: SolutionBuild
23452390
PollingInterval.High,
23462391
parsed?.watchOptions,
23472392
WatchType.ExtendedConfigFile,
2393+
resolved,
23482394
),
23492395
fileName => toPath(state, fileName),
23502396
);
@@ -2430,7 +2476,7 @@ function startWatching<T extends BuilderProgram>(state: SolutionBuilderState<T>,
24302476
const cfg = parseConfigFile(state, resolved, resolvedPath);
24312477
// Watch this file
24322478
watchConfigFile(state, resolved, resolvedPath, cfg);
2433-
watchExtendedConfigFiles(state, resolvedPath, cfg);
2479+
watchExtendedConfigFiles(state, resolved, resolvedPath, cfg);
24342480
if (cfg) {
24352481
// Update watchers for wildcard directories
24362482
watchWildCardDirectories(state, resolved, resolvedPath, cfg);
@@ -2452,6 +2498,7 @@ function stopWatching<T extends BuilderProgram>(state: SolutionBuilderState<T>)
24522498
clearMap(state.allWatchedWildcardDirectories, watchedWildcardDirectories => clearMap(watchedWildcardDirectories, closeFileWatcherOf));
24532499
clearMap(state.allWatchedInputFiles, watchedWildcardDirectories => clearMap(watchedWildcardDirectories, closeFileWatcher));
24542500
clearMap(state.allWatchedPackageJsonFiles, watchedPacageJsonFiles => clearMap(watchedPacageJsonFiles, closeFileWatcher));
2501+
state.allWatchFactories.clear();
24552502
}
24562503

24572504
/**
@@ -2461,25 +2508,7 @@ function stopWatching<T extends BuilderProgram>(state: SolutionBuilderState<T>)
24612508
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: false, host: SolutionBuilderHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder<T>;
24622509
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: true, host: SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions, baseWatchOptions?: WatchOptions): SolutionBuilder<T>;
24632510
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], options: BuildOptions, baseWatchOptions?: WatchOptions): SolutionBuilder<T> {
2464-
const state = createSolutionBuilderState(watch, hostOrHostWithWatch, rootNames, options, baseWatchOptions);
2465-
return {
2466-
build: (project, cancellationToken, writeFile, getCustomTransformers) => build(state, project, cancellationToken, writeFile, getCustomTransformers),
2467-
clean: project => clean(state, project),
2468-
buildReferences: (project, cancellationToken, writeFile, getCustomTransformers) => build(state, project, cancellationToken, writeFile, getCustomTransformers, /*onlyReferences*/ true),
2469-
cleanReferences: project => clean(state, project, /*onlyReferences*/ true),
2470-
getNextInvalidatedProject: cancellationToken => {
2471-
setupInitialBuild(state, cancellationToken);
2472-
return getNextInvalidatedProject(state, getBuildOrder(state), /*reportQueue*/ false);
2473-
},
2474-
getBuildOrder: () => getBuildOrder(state),
2475-
getUpToDateStatusOfProject: project => {
2476-
const configFileName = resolveProjectName(state, project);
2477-
const configFilePath = toResolvedConfigFilePath(state, configFileName);
2478-
return getUpToDateStatus(state, parseConfigFile(state, configFileName, configFilePath), configFilePath);
2479-
},
2480-
invalidateProject: (configFilePath, reloadLevel) => invalidateProject(state, configFilePath, reloadLevel || ConfigFileProgramReloadLevel.None),
2481-
close: () => stopWatching(state),
2482-
};
2511+
return createSolutionBuilderAndState(watch, hostOrHostWithWatch, rootNames, options, baseWatchOptions);
24832512
}
24842513

24852514
function relName<T extends BuilderProgram>(state: SolutionBuilderState<T>, path: string): string {
@@ -2693,3 +2722,22 @@ function verboseReportProjectStatus<T extends BuilderProgram>(state: SolutionBui
26932722
reportUpToDateStatus(state, configFileName, status);
26942723
}
26952724
}
2725+
2726+
function getOrLoadUserWatchFactoryDetails<T extends BuilderProgram>(
2727+
state: SolutionBuilderState<T>,
2728+
getSolution: () => SolutionBuilder<T>,
2729+
resolved: ResolvedConfigFileName,
2730+
): GetOrLoadUserWatchFactoryDetailsForFactory {
2731+
const resolvedPath = toResolvedConfigFilePath(state, resolved);
2732+
return {
2733+
getCurrentUserWatchFactory: () => state.allWatchFactories.get(resolvedPath),
2734+
setUserWatchFactory: userWatchFactory => state.allWatchFactories.set(resolvedPath, userWatchFactory),
2735+
getSearchList: () => [
2736+
getDirectoryPath(resolved),
2737+
state.host.getCurrentDirectory(),
2738+
// ../../.. to walk from X/node_modules/typescript/lib/tsserver.js to X/node_modules/
2739+
...(state.host.getDefaultLibLocation ? [combinePaths(state.host.getDefaultLibLocation(), "../..")] : emptyArray),
2740+
],
2741+
getSolution,
2742+
};
2743+
}

src/compiler/watch.ts

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import * as ts from "./_namespaces/ts";
12
import {
23
addRange,
4+
arrayFrom,
35
BuilderProgram,
6+
BuildOptions,
47
CancellationToken,
58
chainDiagnosticMessages,
69
CharacterCodes,
@@ -59,6 +62,8 @@ import {
5962
getLineAndCharacterOfPosition,
6063
getNewLineCharacter,
6164
getNormalizedAbsolutePath,
65+
getOrLoadUserWatchFactory,
66+
GetOrLoadUserWatchFactoryDetails,
6267
getParsedCommandLineOfConfigFile,
6368
getPatternFromSpec,
6469
getReferencedFileLocation,
@@ -87,6 +92,7 @@ import {
8792
ProjectReference,
8893
ReportEmitErrorSummary,
8994
ReportFileInError,
95+
SolutionBuilder,
9096
sortAndDeduplicateDiagnostics,
9197
SortedReadonlyArray,
9298
SourceFile,
@@ -95,13 +101,17 @@ import {
95101
sys,
96102
System,
97103
targetOptionDeclaration,
104+
toPath,
105+
UserWatchFactory,
98106
WatchCompilerHost,
99107
WatchCompilerHostOfConfigFile,
100108
WatchCompilerHostOfFilesAndCompilerOptions,
101109
WatchFactory,
102110
WatchFactoryHost,
103111
WatchHost,
104112
WatchLogLevel,
113+
WatchOfConfigFile,
114+
WatchOfFilesAndCompilerOptions,
105115
WatchOptions,
106116
WatchStatusReporter,
107117
whitespaceOrMapCommentRegExp,
@@ -665,7 +675,9 @@ export function createWatchHost(system = sys, reportWatchStatus?: WatchStatusRep
665675
watchFile: maybeBind(system, system.watchFile) || returnNoopFileWatcher,
666676
watchDirectory: maybeBind(system, system.watchDirectory) || returnNoopFileWatcher,
667677
setTimeout: maybeBind(system, system.setTimeout) || noop,
668-
clearTimeout: maybeBind(system, system.clearTimeout) || noop
678+
clearTimeout: maybeBind(system, system.clearTimeout) || noop,
679+
require: maybeBind(system, system.require),
680+
resolvePath: maybeBind(system, system.resolvePath),
669681
};
670682
}
671683

@@ -729,12 +741,55 @@ export interface WatchFactoryWithLog<X, Y = undefined> extends WatchFactory<X, Y
729741
}
730742

731743
/** @internal */
732-
export function createWatchFactory<Y = undefined>(host: WatchFactoryHost & { trace?(s: string): void; }, options: { extendedDiagnostics?: boolean; diagnostics?: boolean; }) {
744+
export type GetOrLoadUserWatchFactoryDetailsForFactory = Omit<GetOrLoadUserWatchFactoryDetails, "createUserWatchFactory" | "getSearchPaths"> & {
745+
getSearchList: () => readonly string[];
746+
getWatch?: () => WatchOfConfigFile<BuilderProgram> | WatchOfFilesAndCompilerOptions<BuilderProgram>;
747+
getSolution?: () => SolutionBuilder<BuilderProgram>;
748+
};
749+
/** @internal */
750+
export function createWatchFactory<Y = undefined>(
751+
host: WatchHost & WatchFactoryHost & { trace?(s: string): void; },
752+
getOrLoadUserWatchFactoryDetails: (detailsInfo2: Y | undefined) => GetOrLoadUserWatchFactoryDetailsForFactory,
753+
options: CompilerOptions | BuildOptions,
754+
) {
733755
const watchLogLevel = host.trace ? options.extendedDiagnostics ? WatchLogLevel.Verbose : options.diagnostics ? WatchLogLevel.TriggerOnly : WatchLogLevel.None : WatchLogLevel.None;
734756
const writeLog: (s: string) => void = watchLogLevel !== WatchLogLevel.None ? (s => host.trace!(s)) : noop;
735-
const result = getWatchFactory<WatchType, Y>(host, watchLogLevel, writeLog) as WatchFactoryWithLog<WatchType, Y>;
757+
const result = getWatchFactory<WatchType, Y>(host, getUserWatchFactory, watchLogLevel, writeLog) as WatchFactoryWithLog<WatchType, Y>;
736758
result.writeLog = writeLog;
737759
return result;
760+
761+
function getUserWatchFactory(watchOptions: WatchOptions, detailsInfo2: Y | undefined): UserWatchFactory | undefined {
762+
const details = getOrLoadUserWatchFactoryDetails(detailsInfo2) as GetOrLoadUserWatchFactoryDetails & GetOrLoadUserWatchFactoryDetailsForFactory;
763+
details.createUserWatchFactory = (resolvedModule, pluginConfigEntry, watchOptions) => {
764+
const userWatchFactory = resolvedModule({ typescript: ts });
765+
userWatchFactory.create({
766+
options: watchOptions,
767+
config: pluginConfigEntry,
768+
host,
769+
watch: details.getWatch?.(),
770+
solution: details.getSolution?.(),
771+
});
772+
return userWatchFactory;
773+
};
774+
details.getSearchPaths = () => {
775+
const getCanonicalFileName = createGetCanonicalFileName(
776+
typeof host.useCaseSensitiveFileNames === "boolean" ?
777+
host.useCaseSensitiveFileNames :
778+
host.useCaseSensitiveFileNames()
779+
);
780+
// Deduplicate
781+
const map = new Map(details.getSearchList().map(path => [toPath(path, host.getCurrentDirectory?.(), getCanonicalFileName), path]));
782+
return arrayFrom(map.values());
783+
};
784+
return getOrLoadUserWatchFactory(
785+
host,
786+
watchOptions,
787+
s => host.trace?.(s),
788+
writeLog,
789+
details,
790+
!!options.allowPlugins,
791+
);
792+
}
738793
}
739794

740795
/** @internal */

0 commit comments

Comments
 (0)