mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-14 19:16:17 -06:00
Implementation for invalidating source file containing possibly changed module resolution
This commit is contained in:
parent
8dc62484ec
commit
7474ba762c
@ -22,7 +22,7 @@ namespace ts {
|
||||
/**
|
||||
* This is the callback when file infos in the builder are updated
|
||||
*/
|
||||
onProgramUpdateGraph(program: Program): void;
|
||||
onProgramUpdateGraph(program: Program, hasInvalidatedResolution: HasInvalidatedResolution): void;
|
||||
getFilesAffectedBy(program: Program, path: Path): string[];
|
||||
emitFile(program: Program, path: Path): EmitOutput;
|
||||
emitChangedFiles(program: Program): EmitOutputDetailed[];
|
||||
@ -84,7 +84,7 @@ namespace ts {
|
||||
clear
|
||||
};
|
||||
|
||||
function createProgramGraph(program: Program) {
|
||||
function createProgramGraph(program: Program, hasInvalidatedResolution: HasInvalidatedResolution) {
|
||||
const currentIsModuleEmit = program.getCompilerOptions().module !== ModuleKind.None;
|
||||
if (isModuleEmit !== currentIsModuleEmit) {
|
||||
isModuleEmit = currentIsModuleEmit;
|
||||
@ -100,7 +100,7 @@ namespace ts {
|
||||
// Remove existing file info
|
||||
removeExistingFileInfo,
|
||||
// We will update in place instead of deleting existing value and adding new one
|
||||
(existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile)
|
||||
(existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile, hasInvalidatedResolution)
|
||||
);
|
||||
}
|
||||
|
||||
@ -115,8 +115,8 @@ namespace ts {
|
||||
emitHandler.removeScriptInfo(path);
|
||||
}
|
||||
|
||||
function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile) {
|
||||
if (existingInfo.version !== sourceFile.version) {
|
||||
function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile, hasInvalidatedResolution: HasInvalidatedResolution) {
|
||||
if (existingInfo.version !== sourceFile.version || hasInvalidatedResolution(sourceFile.path)) {
|
||||
changedFilesSinceLastEmit.set(sourceFile.path, true);
|
||||
existingInfo.version = sourceFile.version;
|
||||
emitHandler.updateScriptInfo(program, sourceFile);
|
||||
@ -125,13 +125,13 @@ namespace ts {
|
||||
|
||||
function ensureProgramGraph(program: Program) {
|
||||
if (!emitHandler) {
|
||||
createProgramGraph(program);
|
||||
createProgramGraph(program, noop);
|
||||
}
|
||||
}
|
||||
|
||||
function onProgramUpdateGraph(program: Program) {
|
||||
function onProgramUpdateGraph(program: Program, hasInvalidatedResolution: HasInvalidatedResolution) {
|
||||
if (emitHandler) {
|
||||
createProgramGraph(program);
|
||||
createProgramGraph(program, hasInvalidatedResolution);
|
||||
}
|
||||
}
|
||||
|
||||
@ -298,8 +298,6 @@ namespace ts {
|
||||
return result;
|
||||
}
|
||||
|
||||
function noop() { }
|
||||
|
||||
function getNonModuleEmitHandler(): EmitHandler {
|
||||
return {
|
||||
addScriptInfo: noop,
|
||||
|
||||
@ -1220,7 +1220,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
/** Does nothing. */
|
||||
export function noop(): void {}
|
||||
export function noop(): any {}
|
||||
|
||||
/** Throws an error because a function is not implemented. */
|
||||
export function notImplemented(): never {
|
||||
|
||||
@ -768,6 +768,8 @@ namespace ts {
|
||||
return !host.directoryExists || host.directoryExists(directoryName);
|
||||
}
|
||||
|
||||
export type HasInvalidatedResolution = (sourceFile: Path) => boolean;
|
||||
|
||||
/**
|
||||
* @param {boolean} onlyRecordFailures - if true then function won't try to actually load files but instead record all attempts as failures. This flag is necessary
|
||||
* in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations.
|
||||
|
||||
@ -394,7 +394,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
export function isProgramUptoDate(program: Program, rootFileNames: string[], newOptions: CompilerOptions,
|
||||
getSourceVersion: (path: Path) => string, fileExists: (fileName: string) => boolean): boolean {
|
||||
getSourceVersion: (path: Path) => string, fileExists: (fileName: string) => boolean, hasInvalidatedResolution: HasInvalidatedResolution): boolean {
|
||||
// If we haven't create a program yet, then it is not up-to-date
|
||||
if (!program) {
|
||||
return false;
|
||||
@ -432,10 +432,9 @@ namespace ts {
|
||||
return true;
|
||||
|
||||
function sourceFileUpToDate(sourceFile: SourceFile): boolean {
|
||||
if (!sourceFile) {
|
||||
return false;
|
||||
}
|
||||
return sourceFile.version === getSourceVersion(sourceFile.path);
|
||||
return sourceFile &&
|
||||
sourceFile.version === getSourceVersion(sourceFile.path) &&
|
||||
!hasInvalidatedResolution(sourceFile.path);
|
||||
}
|
||||
}
|
||||
|
||||
@ -565,6 +564,7 @@ namespace ts {
|
||||
|
||||
let moduleResolutionCache: ModuleResolutionCache;
|
||||
let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string) => ResolvedModuleFull[];
|
||||
const hasInvalidatedResolution = host.hasInvalidatedResolution || noop;
|
||||
if (host.resolveModuleNames) {
|
||||
resolveModuleNamesWorker = (moduleNames, containingFile) => host.resolveModuleNames(checkAllDefined(moduleNames), containingFile).map(resolved => {
|
||||
// An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName.
|
||||
@ -803,7 +803,7 @@ namespace ts {
|
||||
trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName, containingFile);
|
||||
}
|
||||
}
|
||||
else {
|
||||
else if (!hasInvalidatedResolution(oldProgramState.file.path)) {
|
||||
resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName, oldProgramState);
|
||||
}
|
||||
|
||||
@ -962,6 +962,13 @@ namespace ts {
|
||||
// tentatively approve the file
|
||||
modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile });
|
||||
}
|
||||
else if (hasInvalidatedResolution(oldSourceFile.path)) {
|
||||
// 'module/types' references could have changed
|
||||
oldProgram.structureIsReused = StructureIsReused.SafeModules;
|
||||
|
||||
// add file to the modified list so that we will resolve it later
|
||||
modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile });
|
||||
}
|
||||
|
||||
// if file has passed all checks it should be safe to reuse it
|
||||
newSourceFiles.push(newSourceFile);
|
||||
|
||||
@ -4,12 +4,18 @@
|
||||
namespace ts {
|
||||
export interface ResolutionCache {
|
||||
setModuleResolutionHost(host: ModuleResolutionHost): void;
|
||||
|
||||
startRecordingFilesWithChangedResolutions(): void;
|
||||
finishRecordingFilesWithChangedResolutions(): Path[];
|
||||
|
||||
resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[];
|
||||
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
|
||||
|
||||
invalidateResolutionOfDeletedFile(filePath: Path): void;
|
||||
invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation: string): void;
|
||||
|
||||
createHasInvalidatedResolution(): HasInvalidatedResolution;
|
||||
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
@ -40,6 +46,7 @@ namespace ts {
|
||||
|
||||
let host: ModuleResolutionHost;
|
||||
let filesWithChangedSetOfUnresolvedImports: Path[];
|
||||
let filesWithInvalidatedResolutions: Map<true>;
|
||||
|
||||
const resolvedModuleNames = createMap<Map<ResolvedModuleWithFailedLookupLocations>>();
|
||||
const resolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
|
||||
@ -55,6 +62,7 @@ namespace ts {
|
||||
resolveTypeReferenceDirectives,
|
||||
invalidateResolutionOfDeletedFile,
|
||||
invalidateResolutionOfChangedFailedLookupLocation,
|
||||
createHasInvalidatedResolution,
|
||||
clear
|
||||
};
|
||||
|
||||
@ -82,6 +90,12 @@ namespace ts {
|
||||
return collected;
|
||||
}
|
||||
|
||||
function createHasInvalidatedResolution(): HasInvalidatedResolution {
|
||||
const collected = filesWithInvalidatedResolutions;
|
||||
filesWithInvalidatedResolutions = undefined;
|
||||
return path => collected && collected.has(path);
|
||||
}
|
||||
|
||||
function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
|
||||
const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host);
|
||||
// return result immediately only if it is .ts, .tsx or .d.ts
|
||||
@ -250,7 +264,7 @@ namespace ts {
|
||||
cache: Map<Map<T>>,
|
||||
getResult: (s: T) => R,
|
||||
getResultFileName: (result: R) => string | undefined) {
|
||||
cache.forEach((value, path) => {
|
||||
cache.forEach((value, path: Path) => {
|
||||
if (path === deletedFilePath) {
|
||||
cache.delete(path);
|
||||
value.forEach((resolution, name) => {
|
||||
@ -264,6 +278,7 @@ namespace ts {
|
||||
if (result) {
|
||||
if (getResultFileName(result) === deletedFilePath) {
|
||||
resolution.isInvalidated = true;
|
||||
(filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap<true>())).set(path, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -275,14 +290,13 @@ namespace ts {
|
||||
function invalidateResolutionCacheOfChangedFailedLookupLocation<T extends NameResolutionWithFailedLookupLocations>(
|
||||
failedLookupLocation: string,
|
||||
cache: Map<Map<T>>) {
|
||||
cache.forEach((value, _containingFilePath) => {
|
||||
cache.forEach((value, containingFile: Path) => {
|
||||
if (value) {
|
||||
value.forEach((resolution, __name) => {
|
||||
if (resolution && !resolution.isInvalidated && contains(resolution.failedLookupLocations, failedLookupLocation)) {
|
||||
// TODO: mark the file as needing re-evaluation of module resolution instead of using it blindly.
|
||||
// Note: Right now this invalidation path is not used at all as it doesnt matter as we are anyways clearing the program,
|
||||
// which means all the resolutions will be discarded.
|
||||
// Mark the file as needing re-evaluation of module resolution instead of using it blindly.
|
||||
resolution.isInvalidated = true;
|
||||
(filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap<true>())).set(containingFile, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -3979,6 +3979,7 @@ namespace ts {
|
||||
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
|
||||
getEnvironmentVariable?(name: string): string;
|
||||
onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions): void;
|
||||
hasInvalidatedResolution?: HasInvalidatedResolution;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
|
||||
@ -256,6 +256,7 @@ namespace ts {
|
||||
|
||||
const sourceFilesCache = createMap<HostFileInfo | string>(); // Cache that stores the source file and version info
|
||||
let missingFilePathsRequestedForRelease: Path[]; // These paths are held temparirly so that we can remove the entry from source file cache if the file is not tracked by missing files
|
||||
let hasInvalidatedResolution: HasInvalidatedResolution; // Passed along to see if source file has invalidated resolutions
|
||||
|
||||
watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty);
|
||||
const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost;
|
||||
@ -292,7 +293,8 @@ namespace ts {
|
||||
function synchronizeProgram() {
|
||||
writeLog(`Synchronizing program`);
|
||||
|
||||
if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists)) {
|
||||
hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution();
|
||||
if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -306,7 +308,7 @@ namespace ts {
|
||||
|
||||
// Compile the program
|
||||
program = createProgram(rootFileNames, compilerOptions, compilerHost, program);
|
||||
builder.onProgramUpdateGraph(program);
|
||||
builder.onProgramUpdateGraph(program, hasInvalidatedResolution);
|
||||
|
||||
// Update watches
|
||||
missingFilesMap = updateMissingFilePathsWatch(program, missingFilesMap, watchMissingFilePath, closeMissingFilePathWatcher);
|
||||
@ -351,7 +353,8 @@ namespace ts {
|
||||
realpath,
|
||||
resolveTypeReferenceDirectives: (typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile),
|
||||
resolveModuleNames: (moduleNames, containingFile) => resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ false),
|
||||
onReleaseOldSourceFile
|
||||
onReleaseOldSourceFile,
|
||||
hasInvalidatedResolution
|
||||
};
|
||||
}
|
||||
|
||||
@ -569,13 +572,7 @@ namespace ts {
|
||||
writeLog(`Failed lookup location : ${failedLookupLocation} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName} containingFile: ${containingFile}, name: ${name}`);
|
||||
const path = toPath(failedLookupLocation);
|
||||
updateCachedSystem(failedLookupLocation, path);
|
||||
|
||||
// TODO: We need more intensive approach wherein we are able to comunicate to the program structure reuser that the even though the source file
|
||||
// refering to this failed location hasnt changed, it needs to re-evaluate the module resolutions for the invalidated resolutions.
|
||||
// For now just clear existing program, that should still reuse the source files but atleast compute the resolutions again.
|
||||
|
||||
// resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation);
|
||||
program = undefined;
|
||||
resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation);
|
||||
scheduleProgramUpdate();
|
||||
}
|
||||
|
||||
|
||||
@ -110,6 +110,10 @@ namespace ts.server {
|
||||
|
||||
readonly trace: (s: string) => void;
|
||||
readonly realpath?: (path: string) => string;
|
||||
|
||||
/*@internal*/
|
||||
hasInvalidatedResolution: HasInvalidatedResolution;
|
||||
|
||||
/**
|
||||
* This is the host that is associated with the project. This is normally same as projectService's host
|
||||
* except in Configured projects where it is CachedServerHost so that we can cache the results of the
|
||||
|
||||
@ -241,12 +241,8 @@ namespace ts.server {
|
||||
if (this.projectKind === ProjectKind.Configured) {
|
||||
(this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(failedLookupLocation));
|
||||
}
|
||||
this.updateTypes();
|
||||
// TODO: We need more intensive approach wherein we are able to comunicate to the program structure reuser that the even though the source file
|
||||
// refering to this failed location hasnt changed, it needs to re-evaluate the module resolutions for the invalidated resolutions.
|
||||
// For now just clear existing program, that should still reuse the source files but atleast compute the resolutions again.
|
||||
// this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation);
|
||||
// this.markAsDirty();
|
||||
this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation);
|
||||
this.markAsDirty();
|
||||
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
|
||||
});
|
||||
}
|
||||
@ -605,6 +601,7 @@ namespace ts.server {
|
||||
*/
|
||||
updateGraph(): boolean {
|
||||
this.resolutionCache.startRecordingFilesWithChangedResolutions();
|
||||
this.lsHost.hasInvalidatedResolution = this.resolutionCache.createHasInvalidatedResolution();
|
||||
|
||||
let hasChanges = this.updateGraphWorker();
|
||||
|
||||
@ -640,7 +637,7 @@ namespace ts.server {
|
||||
// otherwise tell it to drop its internal state
|
||||
if (this.builder) {
|
||||
if (this.languageServiceEnabled && this.compileOnSaveEnabled) {
|
||||
this.builder.onProgramUpdateGraph(this.program);
|
||||
this.builder.onProgramUpdateGraph(this.program, this.lsHost.hasInvalidatedResolution);
|
||||
}
|
||||
else {
|
||||
this.builder.clear();
|
||||
|
||||
@ -1115,8 +1115,11 @@ namespace ts {
|
||||
// Get a fresh cache of the host information
|
||||
let hostCache = new HostCache(host, getCanonicalFileName);
|
||||
const rootFileNames = hostCache.getRootFileNames();
|
||||
|
||||
const hasInvalidatedResolution: HasInvalidatedResolution = host.hasInvalidatedResolution || noop;
|
||||
|
||||
// If the program is already up-to-date, we can reuse it
|
||||
if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache.getVersion(path), fileExists)) {
|
||||
if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache.getVersion(path), fileExists, hasInvalidatedResolution)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1155,7 +1158,8 @@ namespace ts {
|
||||
getDirectories: path => {
|
||||
return host.getDirectories ? host.getDirectories(path) : [];
|
||||
},
|
||||
onReleaseOldSourceFile
|
||||
onReleaseOldSourceFile,
|
||||
hasInvalidatedResolution
|
||||
};
|
||||
if (host.trace) {
|
||||
compilerHost.trace = message => host.trace(message);
|
||||
|
||||
@ -185,6 +185,7 @@ namespace ts {
|
||||
*/
|
||||
resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[];
|
||||
resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
|
||||
hasInvalidatedResolution?: HasInvalidatedResolution;
|
||||
directoryExists?(directoryName: string): boolean;
|
||||
|
||||
/*
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user