Skip to content

Commit 044152e

Browse files
author
Arthur Ozga
committed
Merge branch 'moduleReuse' of github.com:aozgaa/TypeScript into moduleReuse
2 parents 5819402 + c3582d2 commit 044152e

1 file changed

Lines changed: 103 additions & 59 deletions

File tree

src/compiler/program.ts

Lines changed: 103 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,23 @@ namespace ts {
66
const emptyArray: any[] = [];
77
const ignoreDiagnosticCommentRegEx = /(^\s*$)|(^\s*\/\/\/?\s*(@ts-ignore)?)/;
88

9+
10+
const enum StructuralChangesFromOldProgram {
11+
None = 0,
12+
NoOldProgram = 1,
13+
ModuleResolutionOptions = 2,
14+
RootNames = 3,
15+
TypesOptions = 4,
16+
SourceFileRemoved = 5,
17+
HasNoDefaultLib = 6,
18+
TripleSlashReferences = 7,
19+
Imports = 8,
20+
ModuleAugmentations = 9,
21+
TripleSlashTypesReferences = 10,
22+
23+
CannotReuseResolution = NoOldProgram | ModuleResolutionOptions | RootNames | TypesOptions | SourceFileRemoved
24+
}
25+
926
export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName = "tsconfig.json"): string {
1027
while (true) {
1128
const fileName = combinePaths(searchPath, configName);
@@ -298,6 +315,7 @@ namespace ts {
298315
let diagnosticsProducingTypeChecker: TypeChecker;
299316
let noDiagnosticsTypeChecker: TypeChecker;
300317
let classifiableNames: Map<string>;
318+
let modifiedFilePaths: Path[] | undefined;
301319

302320
const cachedSemanticDiagnosticsForFile: DiagnosticCache = {};
303321
const cachedDeclarationDiagnosticsForFile: DiagnosticCache = {};
@@ -367,7 +385,11 @@ namespace ts {
367385
// used to track cases when two file names differ only in casing
368386
const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createFileMap<SourceFile>(fileName => fileName.toLowerCase()) : undefined;
369387

370-
if (!tryReuseStructureFromOldProgram()) {
388+
const structuralChanges = tryReuseStructureFromOldProgram();
389+
if (structuralChanges === StructuralChangesFromOldProgram.None) {
390+
Debug.assert(oldProgram.structureIsReused === true);
391+
}
392+
else {
371393
forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false));
372394

373395
// load type declarations specified via 'types' argument or implicitly from types/ and node_modules/@types folders
@@ -476,18 +498,36 @@ namespace ts {
476498
}
477499

478500
interface OldProgramState {
479-
program: Program;
501+
program: Program | undefined;
480502
file: SourceFile;
481503
modifiedFilePaths: Path[];
482504
}
483505

484-
function resolveModuleNamesReusingOldState(moduleNames: string[], containingFile: string, file: SourceFile, oldProgramState?: OldProgramState) {
485-
if (!oldProgramState && !file.ambientModuleNames.length) {
486-
// if old program state is not supplied and file does not contain locally defined ambient modules
487-
// then the best we can do is fallback to the default logic
506+
function createOldProgramState(
507+
program: Program | undefined,
508+
file: SourceFile,
509+
modifiedFilePaths: Path[]): OldProgramState {
510+
return { program, file, modifiedFilePaths };
511+
}
512+
513+
function resolveModuleNamesReusingOldState(moduleNames: string[], containingFile: string, file: SourceFile, oldProgramState: OldProgramState) {
514+
if (structuralChanges & StructuralChangesFromOldProgram.CannotReuseResolution && !file.ambientModuleNames.length) {
515+
// If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules,
516+
// the best we can do is fallback to the default logic.
488517
return resolveModuleNamesWorker(moduleNames, containingFile);
489518
}
490519

520+
const oldSourceFile = oldProgramState.program && oldProgramState.program.getSourceFile(containingFile);
521+
if (oldSourceFile === file) {
522+
// `file` is unchanged from the old program, so we can reuse old module resolutions.
523+
const oldSourceFileResult: ResolvedModuleFull[] = [];
524+
for (const moduleName of moduleNames) {
525+
const resolvedModule = oldSourceFile.resolvedModules.get(moduleName);
526+
oldSourceFileResult.push(resolvedModule);
527+
}
528+
return oldSourceFileResult;
529+
}
530+
491531
// at this point we know that either
492532
// - file has local declarations for ambient modules
493533
// OR
@@ -535,7 +575,7 @@ namespace ts {
535575
// since they are known to be unknown
536576
unknownModuleNames = moduleNames.slice(0, i);
537577
}
538-
// mark prediced resolution in the result list
578+
// Mark predicted resolution in the result list.
539579
result[i] = predictedToResolveToAmbientModuleMarker;
540580
}
541581
else if (unknownModuleNames) {
@@ -568,16 +608,13 @@ namespace ts {
568608
Debug.assert(j === resolutions.length);
569609
return result;
570610

571-
function checkModuleNameResolvedToAmbientModuleInNonModifiedFile(moduleName: string, oldProgramState?: OldProgramState): boolean {
572-
if (!oldProgramState) {
573-
return false;
574-
}
611+
function checkModuleNameResolvedToAmbientModuleInNonModifiedFile(moduleName: string, oldProgramState: OldProgramState): boolean {
575612
const resolutionToFile = getResolvedModule(oldProgramState.file, moduleName);
576613
if (resolutionToFile) {
577614
// module used to be resolved to file - ignore it
578615
return false;
579616
}
580-
const ambientModule = oldProgram.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(moduleName);
617+
const ambientModule = oldProgramState.program && oldProgramState.program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(moduleName);
581618
if (!(ambientModule && ambientModule.declarations)) {
582619
return false;
583620
}
@@ -599,99 +636,106 @@ namespace ts {
599636
}
600637
}
601638

602-
function tryReuseStructureFromOldProgram(): boolean {
639+
function tryReuseStructureFromOldProgram(): StructuralChangesFromOldProgram {
603640
if (!oldProgram) {
604-
return false;
641+
return StructuralChangesFromOldProgram.NoOldProgram;
605642
}
606643

607644
// check properties that can affect structure of the program or module resolution strategy
608645
// if any of these properties has changed - structure cannot be reused
609646
const oldOptions = oldProgram.getCompilerOptions();
610647
if (changesAffectModuleResolution(oldOptions, options)) {
611-
return false;
648+
return StructuralChangesFromOldProgram.ModuleResolutionOptions;
612649
}
613650

614651
Debug.assert(!oldProgram.structureIsReused);
615652

616653
// there is an old program, check if we can reuse its structure
617654
const oldRootNames = oldProgram.getRootFileNames();
618655
if (!arrayIsEqualTo(oldRootNames, rootNames)) {
619-
return false;
656+
return StructuralChangesFromOldProgram.RootNames;
620657
}
621658

622659
if (!arrayIsEqualTo(options.types, oldOptions.types)) {
623-
return false;
660+
return StructuralChangesFromOldProgram.TypesOptions;
624661
}
625662

626663
// check if program source files has changed in the way that can affect structure of the program
627664
const newSourceFiles: SourceFile[] = [];
628665
const filePaths: Path[] = [];
629666
const modifiedSourceFiles: { oldFile: SourceFile, newFile: SourceFile }[] = [];
667+
let structuralChanges = StructuralChangesFromOldProgram.None;
630668

631669
for (const oldSourceFile of oldProgram.getSourceFiles()) {
632670
let newSourceFile = host.getSourceFileByPath
633671
? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.path, options.target)
634672
: host.getSourceFile(oldSourceFile.fileName, options.target);
635673

636674
if (!newSourceFile) {
637-
return false;
675+
structuralChanges |= StructuralChangesFromOldProgram.SourceFileRemoved;
638676
}
677+
else {
678+
newSourceFile.path = oldSourceFile.path;
679+
filePaths.push(newSourceFile.path);
680+
681+
if (oldSourceFile !== newSourceFile) {
682+
if (oldSourceFile.hasNoDefaultLib !== newSourceFile.hasNoDefaultLib) {
683+
// value of no-default-lib has changed
684+
// this will affect if default library is injected into the list of files
685+
structuralChanges |= StructuralChangesFromOldProgram.HasNoDefaultLib;
686+
}
639687

640-
newSourceFile.path = oldSourceFile.path;
641-
filePaths.push(newSourceFile.path);
688+
// check tripleslash references
689+
if (!arrayIsEqualTo(oldSourceFile.referencedFiles, newSourceFile.referencedFiles, fileReferenceIsEqualTo)) {
690+
// tripleslash references has changed
691+
structuralChanges |= StructuralChangesFromOldProgram.TripleSlashReferences;
692+
}
642693

643-
if (oldSourceFile !== newSourceFile) {
644-
if (oldSourceFile.hasNoDefaultLib !== newSourceFile.hasNoDefaultLib) {
645-
// value of no-default-lib has changed
646-
// this will affect if default library is injected into the list of files
647-
return false;
648-
}
694+
// check imports and module augmentations
695+
collectExternalModuleReferences(newSourceFile);
696+
if (!arrayIsEqualTo(oldSourceFile.imports, newSourceFile.imports, moduleNameIsEqualTo)) {
697+
// imports has changed
698+
structuralChanges |= StructuralChangesFromOldProgram.Imports;
699+
}
700+
if (!arrayIsEqualTo(oldSourceFile.moduleAugmentations, newSourceFile.moduleAugmentations, moduleNameIsEqualTo)) {
701+
// moduleAugmentations has changed
702+
structuralChanges |= StructuralChangesFromOldProgram.ModuleAugmentations;
703+
}
649704

650-
// check tripleslash references
651-
if (!arrayIsEqualTo(oldSourceFile.referencedFiles, newSourceFile.referencedFiles, fileReferenceIsEqualTo)) {
652-
// tripleslash references has changed
653-
return false;
654-
}
705+
if (!arrayIsEqualTo(oldSourceFile.typeReferenceDirectives, newSourceFile.typeReferenceDirectives, fileReferenceIsEqualTo)) {
706+
// 'types' references has changed
707+
structuralChanges |= StructuralChangesFromOldProgram.TripleSlashTypesReferences;
708+
}
655709

656-
// check imports and module augmentations
657-
collectExternalModuleReferences(newSourceFile);
658-
if (!arrayIsEqualTo(oldSourceFile.imports, newSourceFile.imports, moduleNameIsEqualTo)) {
659-
// imports has changed
660-
return false;
661-
}
662-
if (!arrayIsEqualTo(oldSourceFile.moduleAugmentations, newSourceFile.moduleAugmentations, moduleNameIsEqualTo)) {
663-
// moduleAugmentations has changed
664-
return false;
710+
// tentatively approve the file
711+
modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile });
665712
}
666-
667-
if (!arrayIsEqualTo(oldSourceFile.typeReferenceDirectives, newSourceFile.typeReferenceDirectives, fileReferenceIsEqualTo)) {
668-
// 'types' references has changed
669-
return false;
713+
else {
714+
// file has no changes - use it as is
715+
newSourceFile = oldSourceFile;
670716
}
671717

672-
// tentatively approve the file
673-
modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile });
674-
}
675-
else {
676-
// file has no changes - use it as is
677-
newSourceFile = oldSourceFile;
718+
// if file has passed all checks it should be safe to reuse it
719+
newSourceFiles.push(newSourceFile);
678720
}
721+
}
679722

680-
// if file has passed all checks it should be safe to reuse it
681-
newSourceFiles.push(newSourceFile);
723+
if (structuralChanges !== StructuralChangesFromOldProgram.None) {
724+
return structuralChanges;
682725
}
683726

684-
const modifiedFilePaths = modifiedSourceFiles.map(f => f.newFile.path);
727+
modifiedFilePaths = modifiedSourceFiles.map(f => f.newFile.path);
685728
// try to verify results of module resolution
686729
for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) {
687730
const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.fileName, currentDirectory);
688731
if (resolveModuleNamesWorker) {
689732
const moduleNames = map(concatenate(newSourceFile.imports, newSourceFile.moduleAugmentations), getTextOfLiteral);
690-
const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFilePath, newSourceFile, { file: oldSourceFile, program: oldProgram, modifiedFilePaths });
733+
const oldProgramState = createOldProgramState(oldProgram, oldSourceFile, modifiedFilePaths);
734+
const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFilePath, newSourceFile, oldProgramState);
691735
// ensure that module resolution results are still correct
692736
const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo);
693737
if (resolutionsChanged) {
694-
return false;
738+
return StructuralChangesFromOldProgram.Imports | StructuralChangesFromOldProgram.ModuleAugmentations;
695739
}
696740
}
697741
if (resolveTypeReferenceDirectiveNamesWorker) {
@@ -700,7 +744,7 @@ namespace ts {
700744
// ensure that types resolutions are still correct
701745
const resolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, resolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo);
702746
if (resolutionsChanged) {
703-
return false;
747+
return StructuralChangesFromOldProgram.TripleSlashTypesReferences;
704748
}
705749
}
706750
// pass the cache of module/types resolutions from the old source file
@@ -722,7 +766,7 @@ namespace ts {
722766
resolvedTypeReferenceDirectives = oldProgram.getResolvedTypeReferenceDirectives();
723767
oldProgram.structureIsReused = true;
724768

725-
return true;
769+
return StructuralChangesFromOldProgram.None;
726770
}
727771

728772
function getEmitHost(writeFileCallback?: WriteFileCallback): EmitHost {
@@ -1521,11 +1565,11 @@ namespace ts {
15211565
function processImportedModules(file: SourceFile) {
15221566
collectExternalModuleReferences(file);
15231567
if (file.imports.length || file.moduleAugmentations.length) {
1524-
file.resolvedModules = createMap<ResolvedModuleFull>();
15251568
// Because global augmentation doesn't have string literal name, we can check for global augmentation as such.
15261569
const nonGlobalAugmentation = filter(file.moduleAugmentations, (moduleAugmentation) => moduleAugmentation.kind === SyntaxKind.StringLiteral);
15271570
const moduleNames = map(concatenate(file.imports, nonGlobalAugmentation), getTextOfLiteral);
1528-
const resolutions = resolveModuleNamesReusingOldState(moduleNames, getNormalizedAbsolutePath(file.fileName, currentDirectory), file);
1571+
const oldProgramState = createOldProgramState(oldProgram, file, modifiedFilePaths);
1572+
const resolutions = resolveModuleNamesReusingOldState(moduleNames, getNormalizedAbsolutePath(file.fileName, currentDirectory), file, oldProgramState);
15291573
Debug.assert(resolutions.length === moduleNames.length);
15301574
for (let i = 0; i < moduleNames.length; i++) {
15311575
const resolution = resolutions[i];

0 commit comments

Comments
 (0)