Skip to content

Commit efa274f

Browse files
author
Andy
authored
When emitting all files, emit the changed file first (microsoft#18930)
* When emitting all files, emit the changed file first * Export interface
1 parent 25c3b99 commit efa274f

File tree

7 files changed

+130
-41
lines changed

7 files changed

+130
-41
lines changed

Jakefile.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ var harnessSources = harnessCoreSources.concat([
129129
"textStorage.ts",
130130
"moduleResolution.ts",
131131
"tsconfigParsing.ts",
132+
"builder.ts",
132133
"commandLineParsing.ts",
133134
"configurationExtension.ts",
134135
"convertCompilerOptionsFromJson.ts",

src/compiler/builder.ts

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,14 @@ namespace ts {
9393
signature: string;
9494
}
9595

96-
export function createBuilder(
97-
getCanonicalFileName: (fileName: string) => string,
98-
getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) => EmitOutput | EmitOutputDetailed,
99-
computeHash: (data: string) => string,
100-
shouldEmitFile: (sourceFile: SourceFile) => boolean
101-
): Builder {
96+
export interface BuilderOptions {
97+
getCanonicalFileName: (fileName: string) => string;
98+
getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) => EmitOutput | EmitOutputDetailed;
99+
computeHash: (data: string) => string;
100+
shouldEmitFile: (sourceFile: SourceFile) => boolean;
101+
}
102+
103+
export function createBuilder(options: BuilderOptions): Builder {
102104
let isModuleEmit: boolean | undefined;
103105
const fileInfos = createMap<FileInfo>();
104106
const semanticDiagnosticsPerFile = createMap<ReadonlyArray<Diagnostic>>();
@@ -181,7 +183,7 @@ namespace ts {
181183
ensureProgramGraph(program);
182184

183185
const sourceFile = program.getSourceFile(path);
184-
const singleFileResult = sourceFile && shouldEmitFile(sourceFile) ? [sourceFile.fileName] : [];
186+
const singleFileResult = sourceFile && options.shouldEmitFile(sourceFile) ? [sourceFile.fileName] : [];
185187
const info = fileInfos.get(path);
186188
if (!info || !updateShapeSignature(program, sourceFile, info)) {
187189
return singleFileResult;
@@ -197,7 +199,7 @@ namespace ts {
197199
return { outputFiles: [], emitSkipped: true };
198200
}
199201

200-
return getEmitOutput(program, program.getSourceFileByPath(path), /*emitOnlyDtsFiles*/ false, /*isDetailed*/ false);
202+
return options.getEmitOutput(program, program.getSourceFileByPath(path), /*emitOnlyDtsFiles*/ false, /*isDetailed*/ false);
201203
}
202204

203205
function enumerateChangedFilesSet(
@@ -220,21 +222,21 @@ namespace ts {
220222
onChangedFile: (fileName: string, path: Path) => void,
221223
onEmitOutput: (emitOutput: EmitOutputDetailed, sourceFile: SourceFile) => void
222224
) {
223-
const seenFiles = createMap<SourceFile>();
225+
const seenFiles = createMap<true>();
224226
enumerateChangedFilesSet(program, onChangedFile, (fileName, sourceFile) => {
225227
if (!seenFiles.has(fileName)) {
226-
seenFiles.set(fileName, sourceFile);
228+
seenFiles.set(fileName, true);
227229
if (sourceFile) {
228230
// Any affected file shouldnt have the cached diagnostics
229231
semanticDiagnosticsPerFile.delete(sourceFile.path);
230232

231-
const emitOutput = getEmitOutput(program, sourceFile, emitOnlyDtsFiles, /*isDetailed*/ true) as EmitOutputDetailed;
233+
const emitOutput = options.getEmitOutput(program, sourceFile, emitOnlyDtsFiles, /*isDetailed*/ true) as EmitOutputDetailed;
232234
onEmitOutput(emitOutput, sourceFile);
233235

234236
// mark all the emitted source files as seen
235237
if (emitOutput.emittedSourceFiles) {
236238
for (const file of emitOutput.emittedSourceFiles) {
237-
seenFiles.set(file.fileName, file);
239+
seenFiles.set(file.fileName, true);
238240
}
239241
}
240242
}
@@ -309,13 +311,13 @@ namespace ts {
309311
const prevSignature = info.signature;
310312
let latestSignature: string;
311313
if (sourceFile.isDeclarationFile) {
312-
latestSignature = computeHash(sourceFile.text);
314+
latestSignature = options.computeHash(sourceFile.text);
313315
info.signature = latestSignature;
314316
}
315317
else {
316-
const emitOutput = getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, /*isDetailed*/ false);
318+
const emitOutput = options.getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, /*isDetailed*/ false);
317319
if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) {
318-
latestSignature = computeHash(emitOutput.outputFiles[0].text);
320+
latestSignature = options.computeHash(emitOutput.outputFiles[0].text);
319321
info.signature = latestSignature;
320322
}
321323
else {
@@ -352,7 +354,7 @@ namespace ts {
352354
// Handle triple slash references
353355
if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) {
354356
for (const referencedFile of sourceFile.referencedFiles) {
355-
const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName);
357+
const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, options.getCanonicalFileName);
356358
addReferencedFile(referencedPath);
357359
}
358360
}
@@ -365,7 +367,7 @@ namespace ts {
365367
}
366368

367369
const fileName = resolvedTypeReferenceDirective.resolvedFileName;
368-
const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName);
370+
const typeFilePath = toPath(fileName, sourceFileDirectory, options.getCanonicalFileName);
369371
addReferencedFile(typeFilePath);
370372
});
371373
}
@@ -381,18 +383,26 @@ namespace ts {
381383
}
382384

383385
/**
384-
* Gets all the emittable files from the program
386+
* Gets all the emittable files from the program.
387+
* @param firstSourceFile This one will be emitted first. See https://github.com/Microsoft/TypeScript/issues/16888
385388
*/
386-
function getAllEmittableFiles(program: Program) {
389+
function getAllEmittableFiles(program: Program, firstSourceFile: SourceFile): string[] {
387390
const defaultLibraryFileName = getDefaultLibFileName(program.getCompilerOptions());
388391
const sourceFiles = program.getSourceFiles();
389392
const result: string[] = [];
393+
add(firstSourceFile);
390394
for (const sourceFile of sourceFiles) {
391-
if (getBaseFileName(sourceFile.fileName) !== defaultLibraryFileName && shouldEmitFile(sourceFile)) {
392-
result.push(sourceFile.fileName);
395+
if (sourceFile !== firstSourceFile) {
396+
add(sourceFile);
393397
}
394398
}
395399
return result;
400+
401+
function add(sourceFile: SourceFile): void {
402+
if (getBaseFileName(sourceFile.fileName) !== defaultLibraryFileName && options.shouldEmitFile(sourceFile)) {
403+
result.push(sourceFile.fileName);
404+
}
405+
}
396406
}
397407

398408
function getNonModuleEmitHandler(): EmitHandler {
@@ -404,14 +414,14 @@ namespace ts {
404414
getFilesAffectedByUpdatedShape
405415
};
406416

407-
function getFilesAffectedByUpdatedShape(program: Program, _sourceFile: SourceFile, singleFileResult: string[]): string[] {
417+
function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[] {
408418
const options = program.getCompilerOptions();
409419
// If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project,
410420
// so returning the file itself is good enough.
411421
if (options && (options.out || options.outFile)) {
412422
return singleFileResult;
413423
}
414-
return getAllEmittableFiles(program);
424+
return getAllEmittableFiles(program, sourceFile);
415425
}
416426
}
417427

@@ -484,11 +494,11 @@ namespace ts {
484494

485495
function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[] {
486496
if (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile)) {
487-
return getAllEmittableFiles(program);
497+
return getAllEmittableFiles(program, sourceFile);
488498
}
489499

490-
const options = program.getCompilerOptions();
491-
if (options && (options.isolatedModules || options.out || options.outFile)) {
500+
const compilerOptions = program.getCompilerOptions();
501+
if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) {
492502
return singleFileResult;
493503
}
494504

@@ -498,7 +508,7 @@ namespace ts {
498508

499509
const seenFileNamesMap = createMap<string>();
500510
const setSeenFileName = (path: Path, sourceFile: SourceFile) => {
501-
seenFileNamesMap.set(path, sourceFile && shouldEmitFile(sourceFile) ? sourceFile.fileName : undefined);
511+
seenFileNamesMap.set(path, sourceFile && options.shouldEmitFile(sourceFile) ? sourceFile.fileName : undefined);
502512
};
503513

504514
// Start with the paths this file was referenced by

src/compiler/watch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ namespace ts {
308308
getCurrentDirectory()
309309
);
310310
// There is no extra check needed since we can just rely on the program to decide emit
311-
const builder = createBuilder(getCanonicalFileName, getFileEmitOutput, computeHash, _sourceFile => true);
311+
const builder = createBuilder({ getCanonicalFileName, getEmitOutput: getFileEmitOutput, computeHash, shouldEmitFile: () => true });
312312

313313
synchronizeProgram();
314314

src/harness/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@
116116
"./unittests/reuseProgramStructure.ts",
117117
"./unittests/moduleResolution.ts",
118118
"./unittests/tsconfigParsing.ts",
119+
"./unittests/builder.ts",
119120
"./unittests/commandLineParsing.ts",
120121
"./unittests/configurationExtension.ts",
121122
"./unittests/convertCompilerOptionsFromJson.ts",

src/harness/unittests/builder.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/// <reference path="reuseProgramStructure.ts" />
2+
3+
namespace ts {
4+
describe("builder", () => {
5+
it("emits dependent files", () => {
6+
const files: NamedSourceText[] = [
7+
{ name: "/a.ts", text: SourceText.New("", 'import { b } from "./b";', "") },
8+
{ name: "/b.ts", text: SourceText.New("", ' import { c } from "./c";', "export const b = c;") },
9+
{ name: "/c.ts", text: SourceText.New("", "", "export const c = 0;") },
10+
];
11+
12+
let program = newProgram(files, ["/a.ts"], {});
13+
const assertChanges = makeAssertChanges(() => program);
14+
15+
assertChanges(["/c.js", "/b.js", "/a.js"]);
16+
17+
program = updateProgramFile(program, "/a.ts", "//comment");
18+
assertChanges(["/a.js"]);
19+
20+
program = updateProgramFile(program, "/b.ts", "export const b = c + 1;");
21+
assertChanges(["/b.js", "/a.js"]);
22+
23+
program = updateProgramFile(program, "/c.ts", "export const c = 1;");
24+
assertChanges(["/c.js", "/b.js"]);
25+
});
26+
27+
it("if emitting all files, emits the changed file first", () => {
28+
const files: NamedSourceText[] = [
29+
{ name: "/a.ts", text: SourceText.New("", "", "namespace A { export const x = 0; }") },
30+
{ name: "/b.ts", text: SourceText.New("", "", "namespace B { export const x = 0; }") },
31+
];
32+
33+
let program = newProgram(files, ["/a.ts", "/b.ts"], {});
34+
const assertChanges = makeAssertChanges(() => program);
35+
36+
assertChanges(["/a.js", "/b.js"]);
37+
38+
program = updateProgramFile(program, "/a.ts", "namespace A { export const x = 1; }");
39+
assertChanges(["/a.js", "/b.js"]);
40+
41+
program = updateProgramFile(program, "/b.ts", "namespace B { export const x = 1; }");
42+
assertChanges(["/b.js", "/a.js"]);
43+
});
44+
});
45+
46+
function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray<string>) => void {
47+
const builder = createBuilder({
48+
getCanonicalFileName: identity,
49+
getEmitOutput: getFileEmitOutput,
50+
computeHash: identity,
51+
shouldEmitFile: returnTrue,
52+
});
53+
return fileNames => {
54+
const program = getProgram();
55+
builder.updateProgram(program);
56+
const changedFiles = builder.emitChangedFiles(program);
57+
assert.deepEqual(changedFileNames(changedFiles), fileNames);
58+
};
59+
}
60+
61+
function updateProgramFile(program: ProgramWithSourceTexts, fileName: string, fileContent: string): ProgramWithSourceTexts {
62+
return updateProgram(program, program.getRootFileNames(), program.getCompilerOptions(), files => {
63+
updateProgramText(files, fileName, fileContent);
64+
});
65+
}
66+
67+
function changedFileNames(changedFiles: ReadonlyArray<EmitOutputDetailed>): string[] {
68+
return changedFiles.map(f => {
69+
assert.lengthOf(f.outputFiles, 1);
70+
return f.outputFiles[0].name;
71+
});
72+
}
73+
}

src/harness/unittests/reuseProgramStructure.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ namespace ts {
1515
sourceText?: SourceText;
1616
}
1717

18-
interface NamedSourceText {
18+
export interface NamedSourceText {
1919
name: string;
2020
text: SourceText;
2121
}
2222

23-
interface ProgramWithSourceTexts extends Program {
23+
export interface ProgramWithSourceTexts extends Program {
2424
sourceTexts?: ReadonlyArray<NamedSourceText>;
2525
host: TestCompilerHost;
2626
}
@@ -29,7 +29,7 @@ namespace ts {
2929
getTrace(): string[];
3030
}
3131

32-
class SourceText implements IScriptSnapshot {
32+
export class SourceText implements IScriptSnapshot {
3333
private fullText: string;
3434

3535
constructor(private references: string,
@@ -103,10 +103,11 @@ namespace ts {
103103
function createSourceFileWithText(fileName: string, sourceText: SourceText, target: ScriptTarget) {
104104
const file = <SourceFileWithText>createSourceFile(fileName, sourceText.getFullText(), target);
105105
file.sourceText = sourceText;
106+
file.version = "" + sourceText.getVersion();
106107
return file;
107108
}
108109

109-
function createTestCompilerHost(texts: ReadonlyArray<NamedSourceText>, target: ScriptTarget, oldProgram?: ProgramWithSourceTexts): TestCompilerHost {
110+
export function createTestCompilerHost(texts: ReadonlyArray<NamedSourceText>, target: ScriptTarget, oldProgram?: ProgramWithSourceTexts): TestCompilerHost {
110111
const files = arrayToMap(texts, t => t.name, t => {
111112
if (oldProgram) {
112113
let oldFile = <SourceFileWithText>oldProgram.getSourceFile(t.name);
@@ -154,15 +155,15 @@ namespace ts {
154155
};
155156
}
156157

157-
function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions): ProgramWithSourceTexts {
158+
export function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions): ProgramWithSourceTexts {
158159
const host = createTestCompilerHost(texts, options.target);
159160
const program = <ProgramWithSourceTexts>createProgram(rootNames, options, host);
160161
program.sourceTexts = texts;
161162
program.host = host;
162163
return program;
163164
}
164165

165-
function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: ReadonlyArray<string>, options: CompilerOptions, updater: (files: NamedSourceText[]) => void, newTexts?: NamedSourceText[]) {
166+
export function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: ReadonlyArray<string>, options: CompilerOptions, updater: (files: NamedSourceText[]) => void, newTexts?: NamedSourceText[]) {
166167
if (!newTexts) {
167168
newTexts = (<ProgramWithSourceTexts>oldProgram).sourceTexts.slice(0);
168169
}
@@ -174,7 +175,7 @@ namespace ts {
174175
return program;
175176
}
176177

177-
function updateProgramText(files: ReadonlyArray<NamedSourceText>, fileName: string, newProgramText: string) {
178+
export function updateProgramText(files: ReadonlyArray<NamedSourceText>, fileName: string, newProgramText: string) {
178179
const file = find(files, f => f.name === fileName)!;
179180
file.text = file.text.updateProgram(newProgramText);
180181
}

src/server/project.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -420,12 +420,15 @@ namespace ts.server {
420420

421421
private ensureBuilder() {
422422
if (!this.builder) {
423-
this.builder = createBuilder(
424-
this.projectService.toCanonicalFileName,
425-
(_program, sourceFile, emitOnlyDts, isDetailed) => this.getFileEmitOutput(sourceFile, emitOnlyDts, isDetailed),
426-
data => this.projectService.host.createHash(data),
427-
sourceFile => !this.projectService.getScriptInfoForPath(sourceFile.path).isDynamicOrHasMixedContent()
428-
);
423+
this.builder = createBuilder({
424+
getCanonicalFileName: this.projectService.toCanonicalFileName,
425+
getEmitOutput: (_program, sourceFile, emitOnlyDts, isDetailed) =>
426+
this.getFileEmitOutput(sourceFile, emitOnlyDts, isDetailed),
427+
computeHash: data =>
428+
this.projectService.host.createHash(data),
429+
shouldEmitFile: sourceFile =>
430+
!this.projectService.getScriptInfoForPath(sourceFile.path).isDynamicOrHasMixedContent()
431+
});
429432
}
430433
}
431434

0 commit comments

Comments
 (0)