Skip to content

Commit a06f0c3

Browse files
committed
Use builder state to emit instead
1 parent eb5797f commit a06f0c3

7 files changed

Lines changed: 401 additions & 309 deletions

File tree

src/compiler/builder.ts

Lines changed: 241 additions & 258 deletions
Large diffs are not rendered by default.

src/compiler/tsc.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,8 @@ namespace ts {
155155
const watchingHost = ts.createWatchingSystemHost(/*pretty*/ undefined, sys, parseConfigFile, reportDiagnostic, reportWatchDiagnostic);
156156
watchingHost.beforeCompile = enableStatistics;
157157
const afterCompile = watchingHost.afterCompile;
158-
watchingHost.afterCompile = (host, program, builder) => {
159-
afterCompile(host, program, builder);
158+
watchingHost.afterCompile = (host, program) => {
159+
afterCompile(host, program);
160160
reportStatistics(program);
161161
};
162162
return watchingHost;

src/compiler/watch.ts

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
/// <reference path="builder.ts" />
33
/// <reference path="resolutionCache.ts"/>
44

5-
/* @internal */
65
namespace ts {
76
export type DiagnosticReporter = (diagnostic: Diagnostic) => void;
87
export type ParseConfigFile = (configFileName: string, optionsToExtend: CompilerOptions, system: DirectoryStructureHost, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter) => ParsedCommandLine;
@@ -19,7 +18,7 @@ namespace ts {
1918

2019
// Callbacks to do custom action before creating program and after creating program
2120
beforeCompile(compilerOptions: CompilerOptions): void;
22-
afterCompile(host: DirectoryStructureHost, program: Program, builder: Builder): void;
21+
afterCompile(host: DirectoryStructureHost, program: Program): void;
2322
}
2423

2524
const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = sys ? {
@@ -133,6 +132,11 @@ namespace ts {
133132
reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system, pretty ? reportDiagnosticWithColorAndContext : reportDiagnosticSimply);
134133
reportWatchDiagnostic = reportWatchDiagnostic || createWatchDiagnosticReporter(system);
135134
parseConfigFile = parseConfigFile || ts.parseConfigFile;
135+
let builderState: Readonly<BuilderState> | undefined;
136+
const options: BuilderOptions = {
137+
getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames),
138+
computeHash: data => system.createHash ? system.createHash(data) : data
139+
};
136140
return {
137141
system,
138142
parseConfigFile,
@@ -142,7 +146,9 @@ namespace ts {
142146
afterCompile: compileWatchedProgram,
143147
};
144148

145-
function compileWatchedProgram(host: DirectoryStructureHost, program: Program, builder: Builder) {
149+
function compileWatchedProgram(host: DirectoryStructureHost, program: Program) {
150+
builderState = createBuilderState(program, options, builderState);
151+
146152
// First get and report any syntactic errors.
147153
const diagnostics = program.getSyntacticDiagnostics().slice();
148154
let reportSemanticDiagnostics = false;
@@ -163,22 +169,15 @@ namespace ts {
163169
let sourceMaps: SourceMapData[];
164170
let emitSkipped: boolean;
165171

166-
const result = builder.emitChangedFiles(program, writeFile);
167-
if (result.length === 0) {
168-
emitSkipped = true;
169-
}
170-
else {
171-
for (const emitOutput of result) {
172-
if (emitOutput.emitSkipped) {
173-
emitSkipped = true;
174-
}
175-
addRange(diagnostics, emitOutput.diagnostics);
176-
sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps);
177-
}
172+
let affectedEmitResult: AffectedFileEmitResult;
173+
while (affectedEmitResult = builderState.emitNextAffectedFile(program, writeFile)) {
174+
emitSkipped = emitSkipped || affectedEmitResult.emitSkipped;
175+
addRange(diagnostics, affectedEmitResult.diagnostics);
176+
sourceMaps = addRange(sourceMaps, affectedEmitResult.sourceMaps);
178177
}
179178

180179
if (reportSemanticDiagnostics) {
181-
addRange(diagnostics, builder.getSemanticDiagnostics(program));
180+
addRange(diagnostics, builderState.getSemanticDiagnostics(program));
182181
}
183182
return handleEmitOutputAndReportErrors(host, program, emittedFiles, emitSkipped,
184183
diagnostics, reportDiagnostic);
@@ -299,8 +298,6 @@ namespace ts {
299298
getDirectoryPath(getNormalizedAbsolutePath(configFileName, getCurrentDirectory())) :
300299
getCurrentDirectory()
301300
);
302-
// There is no extra check needed since we can just rely on the program to decide emit
303-
const builder = createBuilder({ getCanonicalFileName, computeHash });
304301

305302
synchronizeProgram();
306303

@@ -334,7 +331,6 @@ namespace ts {
334331
compilerHost.hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames;
335332
program = createProgram(rootFileNames, compilerOptions, compilerHost, program);
336333
resolutionCache.finishCachingPerDirectoryResolution();
337-
builder.updateProgram(program);
338334

339335
// Update watches
340336
updateMissingFilePathsWatch(program, missingFilesMap || (missingFilesMap = createMap()), watchMissingFilePath);
@@ -356,7 +352,7 @@ namespace ts {
356352
missingFilePathsRequestedForRelease = undefined;
357353
}
358354

359-
afterCompile(directoryStructureHost, program, builder);
355+
afterCompile(directoryStructureHost, program);
360356
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes));
361357
}
362358

@@ -640,9 +636,5 @@ namespace ts {
640636
flags
641637
);
642638
}
643-
644-
function computeHash(data: string) {
645-
return system.createHash ? system.createHash(data) : data;
646-
}
647639
}
648640
}

src/harness/unittests/builder.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,16 @@ namespace ts {
4444
});
4545

4646
function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray<string>) => void {
47-
const builder = createBuilder({
47+
let builderState: BuilderState;
48+
const builderOptions: BuilderOptions = {
4849
getCanonicalFileName: identity,
4950
computeHash: identity
50-
});
51+
};
5152
return fileNames => {
5253
const program = getProgram();
53-
builder.updateProgram(program);
54+
builderState = createBuilderState(program, builderOptions, builderState);
5455
const outputFileNames: string[] = [];
55-
builder.emitChangedFiles(program, fileName => outputFileNames.push(fileName));
56+
while (builderState.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName))) { }
5657
assert.deepEqual(outputFileNames, fileNames);
5758
};
5859
}

src/server/project.ts

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ namespace ts.server {
139139
/*@internal*/
140140
resolutionCache: ResolutionCache;
141141

142-
private builder: Builder;
142+
private builderState: BuilderState;
143143
/**
144144
* Set of files names that were updated since the last call to getChangesSinceVersion.
145145
*/
@@ -442,15 +442,6 @@ namespace ts.server {
442442
return this.languageService;
443443
}
444444

445-
private ensureBuilder() {
446-
if (!this.builder) {
447-
this.builder = createBuilder({
448-
getCanonicalFileName: this.projectService.toCanonicalFileName,
449-
computeHash: data => this.projectService.host.createHash(data)
450-
});
451-
}
452-
}
453-
454445
private shouldEmitFile(scriptInfo: ScriptInfo) {
455446
return scriptInfo && !scriptInfo.isDynamicOrHasMixedContent();
456447
}
@@ -460,8 +451,11 @@ namespace ts.server {
460451
return [];
461452
}
462453
this.updateGraph();
463-
this.ensureBuilder();
464-
return mapDefined(this.builder.getFilesAffectedBy(this.program, scriptInfo.path),
454+
this.builderState = createBuilderState(this.program, {
455+
getCanonicalFileName: this.projectService.toCanonicalFileName,
456+
computeHash: data => this.projectService.host.createHash(data)
457+
}, this.builderState);
458+
return mapDefined(this.builderState.getFilesAffectedBy(this.program, scriptInfo.path),
465459
sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined);
466460
}
467461

@@ -497,6 +491,7 @@ namespace ts.server {
497491
}
498492
this.languageService.cleanupSemanticCache();
499493
this.languageServiceEnabled = false;
494+
this.builderState = undefined;
500495
this.resolutionCache.closeTypeRootsWatch();
501496
this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ false);
502497
}
@@ -537,7 +532,7 @@ namespace ts.server {
537532
this.rootFilesMap = undefined;
538533
this.externalFiles = undefined;
539534
this.program = undefined;
540-
this.builder = undefined;
535+
this.builderState = undefined;
541536
this.resolutionCache.clear();
542537
this.resolutionCache = undefined;
543538
this.cachedUnresolvedImportsPerFile = undefined;
@@ -787,15 +782,9 @@ namespace ts.server {
787782
if (this.setTypings(cachedTypings)) {
788783
hasChanges = this.updateGraphWorker() || hasChanges;
789784
}
790-
if (this.builder) {
791-
this.builder.updateProgram(this.program);
792-
}
793785
}
794786
else {
795787
this.lastCachedUnresolvedImportsList = undefined;
796-
if (this.builder) {
797-
this.builder.clear();
798-
}
799788
}
800789

801790
if (hasChanges) {

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3771,6 +3771,70 @@ declare namespace ts {
37713771
writeByteOrderMark: boolean;
37723772
text: string;
37733773
}
3774+
/**
3775+
* State on which you can query affected files (files to save) and get semantic diagnostics(with their cache managed in the object)
3776+
* Note that it is only safe to pass BuilderState as old state when creating new state, when
3777+
* - If iterator's next method to get next affected file is never called
3778+
* - Iteration of single changed file and its dependencies (iteration through all of its affected files) is complete
3779+
*/
3780+
interface BuilderState {
3781+
/**
3782+
* The map of file infos, where there is entry for each file in the program
3783+
* The entry is signature of the file (from last emit) or empty string
3784+
*/
3785+
fileInfos: ReadonlyMap<Readonly<FileInfo>>;
3786+
/**
3787+
* Returns true if module gerneration is not ModuleKind.None
3788+
*/
3789+
isModuleEmit: boolean;
3790+
/**
3791+
* Map of file referenced or undefined if it wasnt module emit
3792+
* The entry is present only if file references other files
3793+
* The key is path of file and value is referenced map for that file (for every file referenced, there is entry in the set)
3794+
*/
3795+
referencedMap: ReadonlyMap<ReferencedSet> | undefined;
3796+
/**
3797+
* Set of source file's paths that have been changed, either in resolution or versions
3798+
*/
3799+
changedFilesSet: ReadonlyMap<true>;
3800+
/**
3801+
* Set of cached semantic diagnostics per file
3802+
*/
3803+
semanticDiagnosticsPerFile: ReadonlyMap<ReadonlyArray<Diagnostic>>;
3804+
/**
3805+
* Returns true if this state is safe to use as oldState
3806+
*/
3807+
canCreateNewStateFrom(): boolean;
3808+
/**
3809+
* Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete
3810+
*/
3811+
emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined;
3812+
/**
3813+
* Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program
3814+
* The semantic diagnostics are cached and managed here
3815+
* Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files
3816+
*/
3817+
getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
3818+
}
3819+
/**
3820+
* Information about the source file: Its version and optional signature from last emit
3821+
*/
3822+
interface FileInfo {
3823+
version: string;
3824+
signature: string;
3825+
}
3826+
interface AffectedFileEmitResult extends EmitResult {
3827+
affectedFile?: SourceFile;
3828+
}
3829+
/**
3830+
* Referenced files with values for the keys as referenced file's path to be true
3831+
*/
3832+
type ReferencedSet = ReadonlyMap<true>;
3833+
interface BuilderOptions {
3834+
getCanonicalFileName: (fileName: string) => string;
3835+
computeHash: (data: string) => string;
3836+
}
3837+
function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly<BuilderState>): BuilderState;
37743838
}
37753839
declare namespace ts {
37763840
function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string;
@@ -7210,7 +7274,7 @@ declare namespace ts.server {
72107274
languageServiceEnabled: boolean;
72117275
readonly trace?: (s: string) => void;
72127276
readonly realpath?: (path: string) => string;
7213-
private builder;
7277+
private builderState;
72147278
/**
72157279
* Set of files names that were updated since the last call to getChangesSinceVersion.
72167280
*/
@@ -7271,7 +7335,6 @@ declare namespace ts.server {
72717335
getGlobalProjectErrors(): ReadonlyArray<Diagnostic>;
72727336
getAllProjectErrors(): ReadonlyArray<Diagnostic>;
72737337
getLanguageService(ensureSynchronized?: boolean): LanguageService;
7274-
private ensureBuilder();
72757338
private shouldEmitFile(scriptInfo);
72767339
getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[];
72777340
/**

tests/baselines/reference/api/typescript.d.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3718,6 +3718,70 @@ declare namespace ts {
37183718
writeByteOrderMark: boolean;
37193719
text: string;
37203720
}
3721+
/**
3722+
* State on which you can query affected files (files to save) and get semantic diagnostics(with their cache managed in the object)
3723+
* Note that it is only safe to pass BuilderState as old state when creating new state, when
3724+
* - If iterator's next method to get next affected file is never called
3725+
* - Iteration of single changed file and its dependencies (iteration through all of its affected files) is complete
3726+
*/
3727+
interface BuilderState {
3728+
/**
3729+
* The map of file infos, where there is entry for each file in the program
3730+
* The entry is signature of the file (from last emit) or empty string
3731+
*/
3732+
fileInfos: ReadonlyMap<Readonly<FileInfo>>;
3733+
/**
3734+
* Returns true if module gerneration is not ModuleKind.None
3735+
*/
3736+
isModuleEmit: boolean;
3737+
/**
3738+
* Map of file referenced or undefined if it wasnt module emit
3739+
* The entry is present only if file references other files
3740+
* The key is path of file and value is referenced map for that file (for every file referenced, there is entry in the set)
3741+
*/
3742+
referencedMap: ReadonlyMap<ReferencedSet> | undefined;
3743+
/**
3744+
* Set of source file's paths that have been changed, either in resolution or versions
3745+
*/
3746+
changedFilesSet: ReadonlyMap<true>;
3747+
/**
3748+
* Set of cached semantic diagnostics per file
3749+
*/
3750+
semanticDiagnosticsPerFile: ReadonlyMap<ReadonlyArray<Diagnostic>>;
3751+
/**
3752+
* Returns true if this state is safe to use as oldState
3753+
*/
3754+
canCreateNewStateFrom(): boolean;
3755+
/**
3756+
* Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete
3757+
*/
3758+
emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined;
3759+
/**
3760+
* Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program
3761+
* The semantic diagnostics are cached and managed here
3762+
* Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files
3763+
*/
3764+
getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
3765+
}
3766+
/**
3767+
* Information about the source file: Its version and optional signature from last emit
3768+
*/
3769+
interface FileInfo {
3770+
version: string;
3771+
signature: string;
3772+
}
3773+
interface AffectedFileEmitResult extends EmitResult {
3774+
affectedFile?: SourceFile;
3775+
}
3776+
/**
3777+
* Referenced files with values for the keys as referenced file's path to be true
3778+
*/
3779+
type ReferencedSet = ReadonlyMap<true>;
3780+
interface BuilderOptions {
3781+
getCanonicalFileName: (fileName: string) => string;
3782+
computeHash: (data: string) => string;
3783+
}
3784+
function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly<BuilderState>): BuilderState;
37213785
}
37223786
declare namespace ts {
37233787
function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string;

0 commit comments

Comments
 (0)