Invalidate the unresolved import resolutions when typing files are set

This has 3 changes:
1. In updateGraph when enqueue the typing installation request (depending on unresolved imports)
2. When ActionSet event is received, invalidate only files with unresolved imports and resolve those.
3. When ActionInvalidate event is received, typing installer has detected some change in global typing cache location, so just enqueue a new typing installation request. This will repeat the cycle of setting correct typings and pickiing unresolved imports
This commit is contained in:
Sheetal Nandi
2018-04-12 16:47:40 -07:00
parent 35abe26824
commit 60b19f5782
7 changed files with 153 additions and 45 deletions

View File

@@ -524,13 +524,13 @@ namespace ts.server {
}
switch (response.kind) {
case ActionSet:
project.resolutionCache.clear();
this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typeAcquisition, response.unresolvedImports, response.typings);
// Update the typing files and update the project
project.updateTypingFiles(this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typeAcquisition, response.unresolvedImports, response.typings));
break;
case ActionInvalidate:
project.resolutionCache.clear();
this.typingsCache.deleteTypingsForProject(response.projectName);
break;
// Do not clear resolution cache, there was changes detected in typings, so enque typing request and let it get us correct results
this.typingsCache.enqueueInstallTypingsForProject(project, project.lastCachedUnresolvedImportsList, /*forceRefresh*/ true);
return;
}
this.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project);
}

View File

@@ -89,7 +89,18 @@ namespace ts.server {
private plugins: PluginModule[] = [];
/*@internal*/
/**
* This is map from files to unresolved imports in it
* Maop does not contain entries for files that do not have unresolved imports
* This helps in containing the set of files to invalidate
*/
cachedUnresolvedImportsPerFile = createMap<ReadonlyArray<string>>();
/**
* This is the set that has entry to true if file doesnt contain any unresolved import
*/
private filesWithNoUnresolvedImports = createMap<true>();
/*@internal*/
lastCachedUnresolvedImportsList: SortedReadonlyArray<string>;
/*@internal*/
@@ -143,7 +154,8 @@ namespace ts.server {
/*@internal*/
hasChangedAutomaticTypeDirectiveNames = false;
private typingFiles: SortedReadonlyArray<string>;
/*@internal*/
typingFiles: SortedReadonlyArray<string> = emptyArray;
private readonly cancellationToken: ThrottledCancellationToken;
@@ -554,6 +566,7 @@ namespace ts.server {
this.resolutionCache.clear();
this.resolutionCache = undefined;
this.cachedUnresolvedImportsPerFile = undefined;
this.filesWithNoUnresolvedImports = undefined;
this.directoryStructureHost = undefined;
// Clean up file watchers waiting for missing files
@@ -714,6 +727,7 @@ namespace ts.server {
else {
this.resolutionCache.invalidateResolutionOfFile(info.path);
}
this.filesWithNoUnresolvedImports.delete(info.path);
this.cachedUnresolvedImportsPerFile.delete(info.path);
if (detachFromProject) {
@@ -735,16 +749,21 @@ namespace ts.server {
}
/* @internal */
private extractUnresolvedImportsFromSourceFile(file: SourceFile, result: Push<string>, ambientModules: string[]) {
private extractUnresolvedImportsFromSourceFile(file: SourceFile, result: string[] | undefined, ambientModules: string[]): string[] | undefined {
// No unresolve imports in this file
if (this.filesWithNoUnresolvedImports.has(file.path)) {
return result;
}
const cached = this.cachedUnresolvedImportsPerFile.get(file.path);
if (cached) {
// found cached result - use it and return
for (const f of cached) {
result.push(f);
(result || (result = [])).push(f);
}
return;
return result;
}
let unresolvedImports: string[];
let unresolvedImports: string[] | undefined;
if (file.resolvedModules) {
file.resolvedModules.forEach((resolvedModule, name) => {
// pick unresolved non-relative names
@@ -760,11 +779,17 @@ namespace ts.server {
trimmed = trimmed.substr(0, i);
}
(unresolvedImports || (unresolvedImports = [])).push(trimmed);
result.push(trimmed);
(result || (result = [])).push(trimmed);
}
});
}
this.cachedUnresolvedImportsPerFile.set(file.path, unresolvedImports || emptyArray);
if (unresolvedImports) {
this.cachedUnresolvedImportsPerFile.set(file.path, unresolvedImports);
}
else {
this.filesWithNoUnresolvedImports.set(file.path, true);
}
return result;
function isAmbientlyDeclaredModule(name: string) {
return ambientModules.some(m => m === name);
@@ -778,7 +803,7 @@ namespace ts.server {
updateGraph(): boolean {
this.resolutionCache.startRecordingFilesWithChangedResolutions();
let hasChanges = this.updateGraphWorker();
const hasChanges = this.updateGraphWorker();
const hasMoreOrLessScriptInfos = this.hasMoreOrLessScriptInfos;
this.hasMoreOrLessScriptInfos = false;
@@ -787,6 +812,7 @@ namespace ts.server {
for (const file of changedFiles) {
// delete cached information for changed files
this.cachedUnresolvedImportsPerFile.delete(file);
this.filesWithNoUnresolvedImports.delete(file);
}
// update builder only if language service is enabled
@@ -799,20 +825,15 @@ namespace ts.server {
// (can reuse cached imports for files that were not changed)
// 4. compilation settings were changed in the way that might affect module resolution - drop all caches and collect all data from the scratch
if (hasChanges || changedFiles.length) {
const result: string[] = [];
let result: string[] | undefined;
const ambientModules = this.program.getTypeChecker().getAmbientModules().map(mod => stripQuotes(mod.getName()));
for (const sourceFile of this.program.getSourceFiles()) {
this.extractUnresolvedImportsFromSourceFile(sourceFile, result, ambientModules);
result = this.extractUnresolvedImportsFromSourceFile(sourceFile, result, ambientModules);
}
this.lastCachedUnresolvedImportsList = toDeduplicatedSortedArray(result);
this.lastCachedUnresolvedImportsList = result ? toDeduplicatedSortedArray(result) : emptyArray;
}
const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasMoreOrLessScriptInfos);
if (!arrayIsEqualTo(this.typingFiles, cachedTypings)) {
this.typingFiles = cachedTypings;
this.markAsDirty();
hasChanges = this.updateGraphWorker() || hasChanges;
}
this.projectService.typingsCache.enqueueInstallTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasMoreOrLessScriptInfos);
}
else {
this.lastCachedUnresolvedImportsList = undefined;
@@ -824,6 +845,13 @@ namespace ts.server {
return !hasChanges;
}
/*@internal*/
updateTypingFiles(typingFiles: SortedReadonlyArray<string>) {
this.typingFiles = typingFiles;
// Invalidate files with unresolved imports
this.resolutionCache.setFilesWithInvalidatedNonRelativeUnresolvedImports(this.cachedUnresolvedImportsPerFile);
}
/* @internal */
getCurrentProgram() {
return this.program;
@@ -959,15 +987,14 @@ namespace ts.server {
setCompilerOptions(compilerOptions: CompilerOptions) {
if (compilerOptions) {
compilerOptions.allowNonTsExtensions = true;
if (changesAffectModuleResolution(this.compilerOptions, compilerOptions)) {
// reset cached unresolved imports if changes in compiler options affected module resolution
this.cachedUnresolvedImportsPerFile.clear();
this.lastCachedUnresolvedImportsList = undefined;
}
const oldOptions = this.compilerOptions;
this.compilerOptions = compilerOptions;
this.setInternalCompilerOptionsForEmittingJsFiles();
if (changesAffectModuleResolution(oldOptions, compilerOptions)) {
// reset cached unresolved imports if changes in compiler options affected module resolution
this.cachedUnresolvedImportsPerFile.clear();
this.filesWithNoUnresolvedImports.clear();
this.lastCachedUnresolvedImportsList = undefined;
this.resolutionCache.clear();
}
this.markAsDirty();

View File

@@ -95,15 +95,14 @@ namespace ts.server {
return this.installer.installPackage(options);
}
getTypingsForProject(project: Project, unresolvedImports: SortedReadonlyArray<string>, forceRefresh: boolean): SortedReadonlyArray<string> {
enqueueInstallTypingsForProject(project: Project, unresolvedImports: SortedReadonlyArray<string>, forceRefresh: boolean) {
const typeAcquisition = project.getTypeAcquisition();
if (!typeAcquisition || !typeAcquisition.enable) {
return <any>emptyArray;
return;
}
const entry = this.perProjectCache.get(project.getProjectName());
const result: SortedReadonlyArray<string> = entry ? entry.typings : <any>emptyArray;
if (forceRefresh ||
!entry ||
typeAcquisitionChanged(typeAcquisition, entry.typeAcquisition) ||
@@ -114,28 +113,25 @@ namespace ts.server {
this.perProjectCache.set(project.getProjectName(), {
compilerOptions: project.getCompilationSettings(),
typeAcquisition,
typings: result,
typings: entry ? entry.typings : emptyArray,
unresolvedImports,
poisoned: true
});
// something has been changed, issue a request to update typings
this.installer.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
}
return result;
}
updateTypingsForProject(projectName: string, compilerOptions: CompilerOptions, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>, newTypings: string[]) {
const typings = toSortedArray(newTypings);
this.perProjectCache.set(projectName, {
compilerOptions,
typeAcquisition,
typings: toSortedArray(newTypings),
typings,
unresolvedImports,
poisoned: false
});
}
deleteTypingsForProject(projectName: string) {
this.perProjectCache.delete(projectName);
return !typeAcquisition || !typeAcquisition.enable ? emptyArray : typings;
}
onProjectClosed(project: Project) {