Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c125841
Test for not watchiong referenced projects fileNames and invalidating it
sheetalkamat Jun 9, 2020
86dc659
Add watching wild card directories and caching parsed command line fo…
sheetalkamat Feb 5, 2021
f9c80ce
Handle config file watching and commandline cache together
sheetalkamat Feb 10, 2021
ebb078e
Watch extended files for commndline cache instead of project
sheetalkamat Feb 12, 2021
b0eaa72
Use extended config cache now that we are watching extended config files
sheetalkamat Feb 12, 2021
0313fdd
Structure for getParsedCommandLine from the LS
sheetalkamat Feb 13, 2021
a758125
Adding some more skeleton with todos
sheetalkamat Feb 13, 2021
c375598
getParsedCommandLine on WatchCompilerHost
sheetalkamat Feb 17, 2021
9ff2bf2
Tests for Watch, LS scenarios
sheetalkamat Feb 18, 2021
4d6e18a
Handle getParsedCommandLine so we are looking at all things for refer…
sheetalkamat Feb 23, 2021
7ccd15e
Cleanup and commenting
sheetalkamat Feb 23, 2021
b97e007
Test for transitive references with tsc-watch
sheetalkamat Feb 23, 2021
413bb2e
Cache parsed command line even if host implements getParsedCommandLine
sheetalkamat Feb 23, 2021
4399c6b
Cleanup
sheetalkamat Feb 23, 2021
06ed30b
Cleanup
sheetalkamat Feb 23, 2021
ecf54ca
Merge branch 'master' into resolvedRefProject
sheetalkamat Feb 24, 2021
054e0f1
Some tests to verify exclude from referenced project doesnt trigger t…
sheetalkamat Feb 24, 2021
4fb5264
Baseline when program is same
sheetalkamat Feb 24, 2021
078567b
Test for incremental scenario
sheetalkamat Feb 24, 2021
a8195ad
Tests for output from referenced project
sheetalkamat Feb 25, 2021
60bba13
Merge branch 'master' into resolvedRefProject
sheetalkamat Mar 10, 2021
e07b577
Merge branch 'master' into resolvedRefProject
sheetalkamat Mar 26, 2021
79fdb4d
Comments
sheetalkamat Mar 26, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1564,7 +1564,7 @@ namespace ts {
*/
export function getParsedCommandLineOfConfigFile(
configFileName: string,
optionsToExtend: CompilerOptions,
optionsToExtend: CompilerOptions | undefined,
host: ParseConfigFileHost,
extendedConfigCache?: Map<ExtendedConfigCacheEntry>,
watchOptionsToExtend?: WatchOptions,
Expand Down
86 changes: 48 additions & 38 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -656,46 +656,33 @@ namespace ts {
fileExists: (fileName: string) => boolean,
hasInvalidatedResolution: HasInvalidatedResolution,
hasChangedAutomaticTypeDirectiveNames: HasChangedAutomaticTypeDirectiveNames | undefined,
getParsedCommandLine: (fileName: string) => ParsedCommandLine | undefined,
projectReferences: readonly ProjectReference[] | undefined
): boolean {
// If we haven't created a program yet or have changed automatic type directives, then it is not up-to-date
if (!program || hasChangedAutomaticTypeDirectiveNames?.()) {
return false;
}
if (!program || hasChangedAutomaticTypeDirectiveNames?.()) return false;

// If root file names don't match
if (!arrayIsEqualTo(program.getRootFileNames(), rootFileNames)) {
return false;
}
if (!arrayIsEqualTo(program.getRootFileNames(), rootFileNames)) return false;

let seenResolvedRefs: ResolvedProjectReference[] | undefined;

// If project references don't match
if (!arrayIsEqualTo(program.getProjectReferences(), projectReferences, projectReferenceUptoDate)) {
return false;
}
if (!arrayIsEqualTo(program.getProjectReferences(), projectReferences, projectReferenceUptoDate)) return false;

// If any file is not up-to-date, then the whole program is not up-to-date
if (program.getSourceFiles().some(sourceFileNotUptoDate)) {
return false;
}
if (program.getSourceFiles().some(sourceFileNotUptoDate)) return false;

// If any of the missing file paths are now created
if (program.getMissingFilePaths().some(fileExists)) {
return false;
}
if (program.getMissingFilePaths().some(fileExists)) return false;

const currentOptions = program.getCompilerOptions();
// If the compilation settings do no match, then the program is not up-to-date
if (!compareDataObjects(currentOptions, newOptions)) {
return false;
}
if (!compareDataObjects(currentOptions, newOptions)) return false;

// If everything matches but the text of config file is changed,
// error locations can change for program options, so update the program
if (currentOptions.configFile && newOptions.configFile) {
return currentOptions.configFile.text === newOptions.configFile.text;
}
if (currentOptions.configFile && newOptions.configFile) return currentOptions.configFile.text === newOptions.configFile.text;

return true;

Expand All @@ -709,23 +696,26 @@ namespace ts {
}

function projectReferenceUptoDate(oldRef: ProjectReference, newRef: ProjectReference, index: number) {
if (!projectReferenceIsEqualTo(oldRef, newRef)) {
return false;
}
return resolvedProjectReferenceUptoDate(program!.getResolvedProjectReferences()![index], oldRef);
return projectReferenceIsEqualTo(oldRef, newRef) &&
resolvedProjectReferenceUptoDate(program!.getResolvedProjectReferences()![index], oldRef);
}

function resolvedProjectReferenceUptoDate(oldResolvedRef: ResolvedProjectReference | undefined, oldRef: ProjectReference): boolean {
if (oldResolvedRef) {
if (contains(seenResolvedRefs, oldResolvedRef)) {
// Assume true
return true;
}
if (contains(seenResolvedRefs, oldResolvedRef)) return true;

// If sourceFile for the oldResolvedRef existed, check the version for uptodate
if (!sourceFileVersionUptoDate(oldResolvedRef.sourceFile)) {
return false;
}
const refPath = resolveProjectReferencePath(oldRef);
const newParsedCommandLine = getParsedCommandLine(refPath);

// Check if config file exists
if (!newParsedCommandLine) return false;

// If change in source file
if (oldResolvedRef.commandLine.options.configFile !== newParsedCommandLine.options.configFile) return false;

// check file names
if (!arrayIsEqualTo(oldResolvedRef.commandLine.fileNames, newParsedCommandLine.fileNames)) return false;

// Add to seen before checking the referenced paths of this config file
(seenResolvedRefs || (seenResolvedRefs = [])).push(oldResolvedRef);
Expand All @@ -737,7 +727,8 @@ namespace ts {

// In old program, not able to resolve project reference path,
// so if config file doesnt exist, it is uptodate.
return !fileExists(resolveProjectReferencePath(oldRef));
const refPath = resolveProjectReferencePath(oldRef);
return !getParsedCommandLine(refPath);
}
}

Expand Down Expand Up @@ -1021,11 +1012,28 @@ namespace ts {
host.onReleaseOldSourceFile(oldSourceFile, oldProgram.getCompilerOptions(), !!getSourceFileByPath(oldSourceFile.path));
}
}
oldProgram.forEachResolvedProjectReference(resolvedProjectReference => {
if (!getResolvedProjectReferenceByPath(resolvedProjectReference.sourceFile.path)) {
host.onReleaseOldSourceFile!(resolvedProjectReference.sourceFile, oldProgram!.getCompilerOptions(), /*hasSourceFileByPath*/ false);
if (!host.getParsedCommandLine) {
oldProgram.forEachResolvedProjectReference(resolvedProjectReference => {
if (!getResolvedProjectReferenceByPath(resolvedProjectReference.sourceFile.path)) {
host.onReleaseOldSourceFile!(resolvedProjectReference.sourceFile, oldProgram!.getCompilerOptions(), /*hasSourceFileByPath*/ false);
}
});
}
}

// Release commandlines that new program does not use
if (oldProgram && host.onReleaseParsedCommandLine) {
forEachProjectReference(
oldProgram.getProjectReferences(),
oldProgram.getResolvedProjectReferences(),
(oldResolvedRef, parent, index) => {
const oldReference = parent?.commandLine.projectReferences![index] || oldProgram!.getProjectReferences()![index];
const oldRefPath = resolveProjectReferencePath(oldReference);
if (!projectReferenceRedirects?.has(toPath(oldRefPath))) {
host.onReleaseParsedCommandLine!(oldRefPath, oldResolvedRef, oldProgram!.getCompilerOptions());
}
}
});
);
}

// unconditionally set oldProgram to undefined to prevent it from being captured in closure
Expand Down Expand Up @@ -1367,7 +1375,9 @@ namespace ts {
const newResolvedRef = parseProjectReferenceConfigFile(newRef);
if (oldResolvedRef) {
// Resolved project reference has gone missing or changed
return !newResolvedRef || newResolvedRef.sourceFile !== oldResolvedRef.sourceFile;
return !newResolvedRef ||
newResolvedRef.sourceFile !== oldResolvedRef.sourceFile ||
!arrayIsEqualTo(oldResolvedRef.commandLine.fileNames, newResolvedRef.commandLine.fileNames);
}
else {
// A previously-unresolved reference may be resolved now
Expand Down
17 changes: 10 additions & 7 deletions src/compiler/tsbuildPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,11 @@ namespace ts {
return !!(entry as ParsedCommandLine).options;
}

function getCachedParsedConfigFile(state: SolutionBuilderState, configFilePath: ResolvedConfigFilePath): ParsedCommandLine | undefined {
const value = state.configFileCache.get(configFilePath);
return value && isParsedCommandLine(value) ? value : undefined;
}

function parseConfigFile(state: SolutionBuilderState, configFileName: ResolvedConfigFileName, configFilePath: ResolvedConfigFilePath): ParsedCommandLine | undefined {
const { configFileCache } = state;
const value = configFileCache.get(configFilePath);
Expand Down Expand Up @@ -1804,7 +1809,7 @@ namespace ts {
function watchExtendedConfigFiles(state: SolutionBuilderState, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine | undefined) {
updateSharedExtendedConfigFileWatcher(
resolvedPath,
parsed,
parsed?.options,
state.allWatchedExtendedConfigFiles,
(extendedConfigFileName, extendedConfigFilePath) => state.watchFile(
extendedConfigFileName,
Expand Down Expand Up @@ -1834,9 +1839,10 @@ namespace ts {
configFileName: resolved,
currentDirectory: state.currentDirectory,
options: parsed.options,
program: state.builderPrograms.get(resolvedPath),
program: state.builderPrograms.get(resolvedPath) || getCachedParsedConfigFile(state, resolvedPath)?.fileNames,
useCaseSensitiveFileNames: state.parseConfigFileHost.useCaseSensitiveFileNames,
writeLog: s => state.writeLog(s)
writeLog: s => state.writeLog(s),
toPath: fileName => toPath(state, fileName)
})) return;

invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.Partial);
Expand Down Expand Up @@ -1889,10 +1895,7 @@ namespace ts {

function stopWatching(state: SolutionBuilderState) {
clearMap(state.allWatchedConfigFiles, closeFileWatcher);
clearMap(state.allWatchedExtendedConfigFiles, watcher => {
watcher.projects.clear();
watcher.close();
});
clearMap(state.allWatchedExtendedConfigFiles, closeFileWatcherOf);
clearMap(state.allWatchedWildcardDirectories, watchedWildcardDirectories => clearMap(watchedWildcardDirectories, closeFileWatcherOf));
clearMap(state.allWatchedInputFiles, watchedWildcardDirectories => clearMap(watchedWildcardDirectories, closeFileWatcher));
}
Expand Down
7 changes: 4 additions & 3 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3928,9 +3928,9 @@ namespace ts {

/* @internal */
export const enum StructureIsReused {
Not = 0,
SafeModules = 1 << 0,
Completely = 1 << 1,
Not,
SafeModules,
Completely,
}

export type CustomTransformerFactory = (context: TransformationContext) => CustomTransformer;
Expand Down Expand Up @@ -6443,6 +6443,7 @@ namespace ts {
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedTypeReferenceDirective | undefined)[];
getEnvironmentVariable?(name: string): string | undefined;
/* @internal */ onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions, hasSourceFileByPath: boolean): void;
/* @internal */ onReleaseParsedCommandLine?(configFileName: string, oldResolvedRef: ResolvedProjectReference | undefined, optionOptions: CompilerOptions): void;
/* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution;
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: HasChangedAutomaticTypeDirectiveNames;
createHash?(data: string): string;
Expand Down
22 changes: 11 additions & 11 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5392,31 +5392,31 @@ namespace ts {
/**
* clears already present map by calling onDeleteExistingValue callback before deleting that key/value
*/
export function clearMap<T>(map: { forEach: ESMap<string, T>["forEach"]; clear: ESMap<string, T>["clear"]; }, onDeleteValue: (valueInMap: T, key: string) => void) {
export function clearMap<K, T>(map: { forEach: ESMap<K, T>["forEach"]; clear: ESMap<K, T>["clear"]; }, onDeleteValue: (valueInMap: T, key: K) => void) {
// Remove all
map.forEach(onDeleteValue);
map.clear();
}

export interface MutateMapSkippingNewValuesOptions<T, U> {
onDeleteValue(existingValue: T, key: string): void;
export interface MutateMapSkippingNewValuesOptions<K, T, U> {
onDeleteValue(existingValue: T, key: K): void;

/**
* If present this is called with the key when there is value for that key both in new map as well as existing map provided
* Caller can then decide to update or remove this key.
* If the key is removed, caller will get callback of createNewValue for that key.
* If this callback is not provided, the value of such keys is not updated.
*/
onExistingValue?(existingValue: T, valueInNewMap: U, key: string): void;
onExistingValue?(existingValue: T, valueInNewMap: U, key: K): void;
}

/**
* Mutates the map with newMap such that keys in map will be same as newMap.
*/
export function mutateMapSkippingNewValues<T, U>(
map: ESMap<string, T>,
newMap: ReadonlyESMap<string, U>,
options: MutateMapSkippingNewValuesOptions<T, U>
export function mutateMapSkippingNewValues<K, T, U>(
map: ESMap<K, T>,
newMap: ReadonlyESMap<K, U>,
options: MutateMapSkippingNewValuesOptions<K, T, U>
) {
const { onDeleteValue, onExistingValue } = options;
// Needs update
Expand All @@ -5434,14 +5434,14 @@ namespace ts {
});
}

export interface MutateMapOptions<T, U> extends MutateMapSkippingNewValuesOptions<T, U> {
createNewValue(key: string, valueInNewMap: U): T;
export interface MutateMapOptions<K, T, U> extends MutateMapSkippingNewValuesOptions<K, T, U> {
createNewValue(key: K, valueInNewMap: U): T;
}

/**
* Mutates the map with newMap such that keys in map will be same as newMap.
*/
export function mutateMap<T, U>(map: ESMap<string, T>, newMap: ReadonlyESMap<string, U>, options: MutateMapOptions<T, U>) {
export function mutateMap<K, T, U>(map: ESMap<K, T>, newMap: ReadonlyESMap<K, U>, options: MutateMapOptions<K, T, U>) {
// Needs update
mutateMapSkippingNewValues(map, newMap, options);

Expand Down
14 changes: 10 additions & 4 deletions src/compiler/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ namespace ts {
}

/** Parses config file using System interface */
export function parseConfigFileWithSystem(configFileName: string, optionsToExtend: CompilerOptions, watchOptionsToExtend: WatchOptions | undefined, system: System, reportDiagnostic: DiagnosticReporter) {
export function parseConfigFileWithSystem(configFileName: string, optionsToExtend: CompilerOptions, extendedConfigCache: Map<ExtendedConfigCacheEntry> | undefined, watchOptionsToExtend: WatchOptions | undefined, system: System, reportDiagnostic: DiagnosticReporter) {
const host: ParseConfigFileHost = <any>system;
host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(system, reportDiagnostic, diagnostic);
const result = getParsedCommandLineOfConfigFile(configFileName, optionsToExtend, host, /*extendedConfigCache*/ undefined, watchOptionsToExtend);
const result = getParsedCommandLineOfConfigFile(configFileName, optionsToExtend, host, extendedConfigCache, watchOptionsToExtend);
host.onUnRecoverableConfigFileDiagnostic = undefined!; // TODO: GH#18217
return result;
}
Expand Down Expand Up @@ -414,7 +414,10 @@ namespace ts {
MissingFile: "Missing file",
WildcardDirectory: "Wild card directory",
FailedLookupLocations: "Failed Lookup Locations",
TypeRoots: "Type roots"
TypeRoots: "Type roots",
ConfigFileOfReferencedProject: "Config file of referened project",
ExtendedConfigOfReferencedProject: "Extended config file of referenced project",
WildcardDirectoryOfReferencedProject: "Wild card directory of referenced project",
};

export interface WatchTypeRegistry {
Expand All @@ -424,7 +427,10 @@ namespace ts {
MissingFile: "Missing file",
WildcardDirectory: "Wild card directory",
FailedLookupLocations: "Failed Lookup Locations",
TypeRoots: "Type roots"
TypeRoots: "Type roots",
ConfigFileOfReferencedProject: "Config file of referened project",
ExtendedConfigOfReferencedProject: "Extended config file of referenced project",
WildcardDirectoryOfReferencedProject: "Wild card directory of referenced project",
}

interface WatchFactory<X, Y = undefined> extends ts.WatchFactory<X, Y> {
Expand Down
Loading