Avoid double-resolving modified files

This commit is contained in:
Arthur Ozga 2017-04-28 16:27:41 -07:00
parent 86d7031932
commit c63d6d145a
4 changed files with 54 additions and 24 deletions

View File

@ -251,6 +251,15 @@ namespace ts {
}
}
export function zipToMap<T>(keys: string[], values: T[]): Map<T> {
Debug.assert(keys.length === values.length);
const map = createMap<T>();
for (let i = 0; i < keys.length; ++i) {
map.set(keys[i], values[i]);
}
return map;
}
/**
* Iterates through `array` by index and performs the callback on each element of array until the callback
* returns a falsey value, then returns false.

View File

@ -491,7 +491,24 @@ namespace ts {
return resolveModuleNamesWorker(moduleNames, containingFile);
}
// at this point we know at least one of the following hold:
const oldSourceFile = oldProgramState.program && oldProgramState.program.getSourceFile(containingFile);
if (oldSourceFile !== file && file.resolvedModules) {
// `file` was created for the new program.
//
// We only set `file.resolvedModules` via work from the current function,
// so it is defined iff we already called the current function on `file`.
// That call happened no later than the creation of the `file` object,
// which per above occured during the current program creation.
// Since we model program creation as an atomic operation w/r/t IO,
// it is safe to reuse resolutions from the earlier call.
const result: ResolvedModuleFull[] = [];
for (const moduleName of moduleNames) {
const resolvedModule = file.resolvedModules.get(moduleName);
result.push(resolvedModule);
}
return result;
}
// At this point, we know at least one of the following hold:
// - file has local declarations for ambient modules
// - old program state is available
// With this information, we can infer some module resolutions without performing resolution.
@ -511,7 +528,6 @@ namespace ts {
/** A transient placeholder used to mark predicted resolution in the result list. */
const predictedToResolveToAmbientModuleMarker: ResolvedModuleFull = <any>{};
const oldSourceFile = oldProgramState.program && oldProgramState.program.getSourceFile(containingFile);
for (let i = 0; i < moduleNames.length; i++) {
const moduleName = moduleNames[i];
@ -620,7 +636,7 @@ namespace ts {
return oldProgram.structureIsReused = StructureIsReused.Not;
}
Debug.assert(!(oldProgram.structureIsReused & (StructureIsReused.Completely | StructureIsReused.ModulesInUneditedFiles)));
Debug.assert(!(oldProgram.structureIsReused & (StructureIsReused.Completely | StructureIsReused.SafeModules)));
// there is an old program, check if we can reuse its structure
const oldRootNames = oldProgram.getRootFileNames();
@ -653,32 +669,34 @@ namespace ts {
filePaths.push(newSourceFile.path);
if (oldSourceFile !== newSourceFile) {
// The `newSourceFile` object was created for the new program.
if (oldSourceFile.hasNoDefaultLib !== newSourceFile.hasNoDefaultLib) {
// value of no-default-lib has changed
// this will affect if default library is injected into the list of files
oldProgram.structureIsReused = StructureIsReused.ModulesInUneditedFiles;
oldProgram.structureIsReused = StructureIsReused.SafeModules;
}
// check tripleslash references
if (!arrayIsEqualTo(oldSourceFile.referencedFiles, newSourceFile.referencedFiles, fileReferenceIsEqualTo)) {
// tripleslash references has changed
oldProgram.structureIsReused = StructureIsReused.ModulesInUneditedFiles;
oldProgram.structureIsReused = StructureIsReused.SafeModules;
}
// check imports and module augmentations
collectExternalModuleReferences(newSourceFile);
if (!arrayIsEqualTo(oldSourceFile.imports, newSourceFile.imports, moduleNameIsEqualTo)) {
// imports has changed
oldProgram.structureIsReused = StructureIsReused.ModulesInUneditedFiles;
oldProgram.structureIsReused = StructureIsReused.SafeModules;
}
if (!arrayIsEqualTo(oldSourceFile.moduleAugmentations, newSourceFile.moduleAugmentations, moduleNameIsEqualTo)) {
// moduleAugmentations has changed
oldProgram.structureIsReused = StructureIsReused.ModulesInUneditedFiles;
oldProgram.structureIsReused = StructureIsReused.SafeModules;
}
if (!arrayIsEqualTo(oldSourceFile.typeReferenceDirectives, newSourceFile.typeReferenceDirectives, fileReferenceIsEqualTo)) {
// 'types' references has changed
oldProgram.structureIsReused = StructureIsReused.ModulesInUneditedFiles;
oldProgram.structureIsReused = StructureIsReused.SafeModules;
}
// tentatively approve the file
@ -695,7 +713,7 @@ namespace ts {
modifiedFilePaths = modifiedSourceFiles.map(f => f.newFile.path);
// try to verify results of module resolution
modifiedSourceFilesLoop: for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) {
for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) {
const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.fileName, currentDirectory);
if (resolveModuleNamesWorker) {
const moduleNames = map(concatenate(newSourceFile.imports, newSourceFile.moduleAugmentations), getTextOfLiteral);
@ -704,8 +722,11 @@ namespace ts {
// ensure that module resolution results are still correct
const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo);
if (resolutionsChanged) {
oldProgram.structureIsReused = StructureIsReused.ModulesInUneditedFiles;
continue modifiedSourceFilesLoop;
oldProgram.structureIsReused = StructureIsReused.SafeModules;
newSourceFile.resolvedModules = zipToMap(moduleNames, resolutions);
}
else {
newSourceFile.resolvedModules = oldSourceFile.resolvedModules;
}
}
if (resolveTypeReferenceDirectiveNamesWorker) {
@ -714,13 +735,13 @@ namespace ts {
// ensure that types resolutions are still correct
const resolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, resolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo);
if (resolutionsChanged) {
oldProgram.structureIsReused = StructureIsReused.ModulesInUneditedFiles;
continue modifiedSourceFilesLoop;
oldProgram.structureIsReused = StructureIsReused.SafeModules;
newSourceFile.resolvedTypeReferenceDirectiveNames = zipToMap(typesReferenceDirectives, resolutions);
}
else {
newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames;
}
}
// pass the cache of module/types resolutions from the old source file
newSourceFile.resolvedModules = oldSourceFile.resolvedModules;
newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames;
}
if (oldProgram.structureIsReused !== StructureIsReused.Completely) {

View File

@ -2412,7 +2412,7 @@ namespace ts {
/* @internal */
export const enum StructureIsReused {
Completely,
ModulesInUneditedFiles,
SafeModules,
Not
}

View File

@ -261,7 +261,7 @@ namespace ts {
`;
files[0].text = files[0].text.updateReferences(newReferences);
});
assert.isTrue(program_1.structureIsReused === StructureIsReused.ModulesInUneditedFiles);
assert.isTrue(program_1.structureIsReused === StructureIsReused.SafeModules);
});
it("fails if change affects type references", () => {
@ -281,7 +281,7 @@ namespace ts {
updateProgram(program_1, ["a.ts"], { target }, files => {
files[2].text = files[2].text.updateImportsAndExports("import x from 'b'");
});
assert.isTrue(program_1.structureIsReused === StructureIsReused.ModulesInUneditedFiles);
assert.isTrue(program_1.structureIsReused === StructureIsReused.SafeModules);
});
it("fails if change affects type directives", () => {
@ -293,7 +293,7 @@ namespace ts {
/// <reference types="typerefs1" />`;
files[0].text = files[0].text.updateReferences(newReferences);
});
assert.isTrue(program_1.structureIsReused === StructureIsReused.ModulesInUneditedFiles);
assert.isTrue(program_1.structureIsReused === StructureIsReused.SafeModules);
});
it("fails if module kind changes", () => {
@ -340,7 +340,7 @@ namespace ts {
const program_3 = updateProgram(program_2, ["a.ts"], options, files => {
files[0].text = files[0].text.updateImportsAndExports("");
});
assert.isTrue(program_2.structureIsReused === StructureIsReused.ModulesInUneditedFiles);
assert.isTrue(program_2.structureIsReused === StructureIsReused.SafeModules);
checkResolvedModulesCache(program_3, "a.ts", /*expectedContent*/ undefined);
const program_4 = updateProgram(program_3, ["a.ts"], options, files => {
@ -349,7 +349,7 @@ namespace ts {
`;
files[0].text = files[0].text.updateImportsAndExports(newImports);
});
assert.isTrue(program_3.structureIsReused === StructureIsReused.ModulesInUneditedFiles);
assert.isTrue(program_3.structureIsReused === StructureIsReused.SafeModules);
checkResolvedModulesCache(program_4, "a.ts", createMapFromTemplate({ "b": createResolvedModule("b.ts"), "c": undefined }));
});
@ -378,7 +378,7 @@ namespace ts {
files[0].text = files[0].text.updateReferences("");
});
assert.isTrue(program_2.structureIsReused === StructureIsReused.ModulesInUneditedFiles);
assert.isTrue(program_2.structureIsReused === StructureIsReused.SafeModules);
checkResolvedTypeDirectivesCache(program_3, "/a.ts", /*expectedContent*/ undefined);
updateProgram(program_3, ["/a.ts"], options, files => {
@ -387,7 +387,7 @@ namespace ts {
`;
files[0].text = files[0].text.updateReferences(newReferences);
});
assert.isTrue(program_3.structureIsReused === StructureIsReused.ModulesInUneditedFiles);
assert.isTrue(program_3.structureIsReused === StructureIsReused.SafeModules);
checkResolvedTypeDirectivesCache(program_1, "/a.ts", createMapFromTemplate({ "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }));
});