Skip to content

Commit 8fc6518

Browse files
authored
Merge pull request #19306 from Microsoft/doNoWriteFilesMultipleTimes
Fixes the issue with emit where in same file is emitted multiple times
2 parents 0f55f4a + 9bea0db commit 8fc6518

File tree

10 files changed

+222
-245
lines changed

10 files changed

+222
-245
lines changed

src/compiler/builder.ts

Lines changed: 130 additions & 156 deletions
Large diffs are not rendered by default.

src/compiler/watch.ts

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,14 @@ namespace ts {
144144

145145
function compileWatchedProgram(host: DirectoryStructureHost, program: Program, builder: Builder) {
146146
// First get and report any syntactic errors.
147-
let diagnostics = program.getSyntacticDiagnostics().slice();
147+
const diagnostics = program.getSyntacticDiagnostics().slice();
148148
let reportSemanticDiagnostics = false;
149149

150150
// If we didn't have any syntactic errors, then also try getting the global and
151151
// semantic errors.
152152
if (diagnostics.length === 0) {
153-
diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics());
153+
addRange(diagnostics, program.getOptionsDiagnostics());
154+
addRange(diagnostics, program.getGlobalDiagnostics());
154155

155156
if (diagnostics.length === 0) {
156157
reportSemanticDiagnostics = true;
@@ -162,7 +163,7 @@ namespace ts {
162163
let sourceMaps: SourceMapData[];
163164
let emitSkipped: boolean;
164165

165-
const result = builder.emitChangedFiles(program);
166+
const result = builder.emitChangedFiles(program, writeFile);
166167
if (result.length === 0) {
167168
emitSkipped = true;
168169
}
@@ -171,14 +172,13 @@ namespace ts {
171172
if (emitOutput.emitSkipped) {
172173
emitSkipped = true;
173174
}
174-
diagnostics = concatenate(diagnostics, emitOutput.diagnostics);
175+
addRange(diagnostics, emitOutput.diagnostics);
175176
sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps);
176-
writeOutputFiles(emitOutput.outputFiles);
177177
}
178178
}
179179

180180
if (reportSemanticDiagnostics) {
181-
diagnostics = diagnostics.concat(builder.getSemanticDiagnostics(program));
181+
addRange(diagnostics, builder.getSemanticDiagnostics(program));
182182
}
183183
return handleEmitOutputAndReportErrors(host, program, emittedFiles, emitSkipped,
184184
diagnostics, reportDiagnostic);
@@ -191,31 +191,23 @@ namespace ts {
191191
}
192192
}
193193

194-
function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) {
194+
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, onError: (message: string) => void) {
195195
try {
196196
performance.mark("beforeIOWrite");
197197
ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName)));
198198

199-
host.writeFile(fileName, data, writeByteOrderMark);
199+
host.writeFile(fileName, text, writeByteOrderMark);
200200

201201
performance.mark("afterIOWrite");
202202
performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite");
203+
204+
if (emittedFiles) {
205+
emittedFiles.push(fileName);
206+
}
203207
}
204208
catch (e) {
205-
return createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, e);
206-
}
207-
}
208-
209-
function writeOutputFiles(outputFiles: OutputFile[]) {
210-
if (outputFiles) {
211-
for (const outputFile of outputFiles) {
212-
const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark);
213-
if (error) {
214-
diagnostics.push(error);
215-
}
216-
if (emittedFiles) {
217-
emittedFiles.push(outputFile.name);
218-
}
209+
if (onError) {
210+
onError(e.message);
219211
}
220212
}
221213
}
@@ -308,7 +300,7 @@ namespace ts {
308300
getCurrentDirectory()
309301
);
310302
// There is no extra check needed since we can just rely on the program to decide emit
311-
const builder = createBuilder({ getCanonicalFileName, getEmitOutput: getFileEmitOutput, computeHash, shouldEmitFile: () => true });
303+
const builder = createBuilder({ getCanonicalFileName, computeHash });
312304

313305
synchronizeProgram();
314306

src/compiler/watchUtilities.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/// <reference path="core.ts" />
22

3+
/* @internal */
34
namespace ts {
45
/**
56
* Updates the existing missing file watches with the new set of missing files after new program is created
@@ -72,10 +73,7 @@ namespace ts {
7273
existingWatchedForWildcards.set(directory, createWildcardDirectoryWatcher(directory, flags));
7374
}
7475
}
75-
}
7676

77-
/* @internal */
78-
namespace ts {
7977
export function addFileWatcher(host: System, file: string, cb: FileWatcherCallback): FileWatcher {
8078
return host.watchFile(file, cb);
8179
}

src/harness/unittests/builder.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,14 @@ namespace ts {
4646
function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray<string>) => void {
4747
const builder = createBuilder({
4848
getCanonicalFileName: identity,
49-
getEmitOutput: getFileEmitOutput,
50-
computeHash: identity,
51-
shouldEmitFile: returnTrue,
49+
computeHash: identity
5250
});
5351
return fileNames => {
5452
const program = getProgram();
5553
builder.updateProgram(program);
56-
const changedFiles = builder.emitChangedFiles(program);
57-
assert.deepEqual(changedFileNames(changedFiles), fileNames);
54+
const outputFileNames: string[] = [];
55+
builder.emitChangedFiles(program, fileName => outputFileNames.push(fileName));
56+
assert.deepEqual(outputFileNames, fileNames);
5857
};
5958
}
6059

@@ -63,11 +62,4 @@ namespace ts {
6362
updateProgramText(files, fileName, fileContent);
6463
});
6564
}
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-
}
7365
}

src/harness/unittests/tscWatchMode.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,64 @@ namespace ts.tscWatch {
11051105
const outJs = "/a/out.js";
11061106
createWatchForOut(/*out*/ undefined, outJs);
11071107
});
1108+
1109+
function verifyFilesEmittedOnce(useOutFile: boolean) {
1110+
const file1: FileOrFolder = {
1111+
path: "/a/b/output/AnotherDependency/file1.d.ts",
1112+
content: "declare namespace Common.SomeComponent.DynamicMenu { enum Z { Full = 0, Min = 1, Average = 2, } }"
1113+
};
1114+
const file2: FileOrFolder = {
1115+
path: "/a/b/dependencies/file2.d.ts",
1116+
content: "declare namespace Dependencies.SomeComponent { export class SomeClass { version: string; } }"
1117+
};
1118+
const file3: FileOrFolder = {
1119+
path: "/a/b/project/src/main.ts",
1120+
content: "namespace Main { export function fooBar() {} }"
1121+
};
1122+
const file4: FileOrFolder = {
1123+
path: "/a/b/project/src/main2.ts",
1124+
content: "namespace main.file4 { import DynamicMenu = Common.SomeComponent.DynamicMenu; export function foo(a: DynamicMenu.z) { } }"
1125+
};
1126+
const configFile: FileOrFolder = {
1127+
path: "/a/b/project/tsconfig.json",
1128+
content: JSON.stringify({
1129+
compilerOptions: useOutFile ?
1130+
{ outFile: "../output/common.js", target: "es5" } :
1131+
{ outDir: "../output", target: "es5" },
1132+
files: [file1.path, file2.path, file3.path, file4.path]
1133+
})
1134+
};
1135+
const files = [file1, file2, file3, file4];
1136+
const allfiles = files.concat(configFile);
1137+
const host = createWatchedSystem(allfiles);
1138+
const originalWriteFile = host.writeFile.bind(host);
1139+
const mapOfFilesWritten = createMap<number>();
1140+
host.writeFile = (p: string, content: string) => {
1141+
const count = mapOfFilesWritten.get(p);
1142+
mapOfFilesWritten.set(p, count ? count + 1 : 1);
1143+
return originalWriteFile(p, content);
1144+
};
1145+
createWatchModeWithConfigFile(configFile.path, host);
1146+
if (useOutFile) {
1147+
// Only out file
1148+
assert.equal(mapOfFilesWritten.size, 1);
1149+
}
1150+
else {
1151+
// main.js and main2.js
1152+
assert.equal(mapOfFilesWritten.size, 2);
1153+
}
1154+
mapOfFilesWritten.forEach((value, key) => {
1155+
assert.equal(value, 1, "Key: " + key);
1156+
});
1157+
}
1158+
1159+
it("with --outFile and multiple declaration files in the program", () => {
1160+
verifyFilesEmittedOnce(/*useOutFile*/ true);
1161+
});
1162+
1163+
it("without --outFile and multiple declaration files in the program", () => {
1164+
verifyFilesEmittedOnce(/*useOutFile*/ false);
1165+
});
11081166
});
11091167

11101168
describe("tsc-watch emit for configured projects", () => {

src/server/project.ts

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -443,31 +443,33 @@ namespace ts.server {
443443
if (!this.builder) {
444444
this.builder = createBuilder({
445445
getCanonicalFileName: this.projectService.toCanonicalFileName,
446-
getEmitOutput: (_program, sourceFile, emitOnlyDts, isDetailed) =>
447-
this.getFileEmitOutput(sourceFile, emitOnlyDts, isDetailed),
448-
computeHash: data =>
449-
this.projectService.host.createHash(data),
450-
shouldEmitFile: sourceFile =>
451-
!this.projectService.getScriptInfoForPath(sourceFile.path).isDynamicOrHasMixedContent()
446+
computeHash: data => this.projectService.host.createHash(data)
452447
});
453448
}
454449
}
455450

451+
private shouldEmitFile(scriptInfo: ScriptInfo) {
452+
return scriptInfo && !scriptInfo.isDynamicOrHasMixedContent();
453+
}
454+
456455
getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[] {
457456
if (!this.languageServiceEnabled) {
458457
return [];
459458
}
460459
this.updateGraph();
461460
this.ensureBuilder();
462-
return this.builder.getFilesAffectedBy(this.program, scriptInfo.path);
461+
return mapDefined(this.builder.getFilesAffectedBy(this.program, scriptInfo.path),
462+
sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined);
463463
}
464464

465465
/**
466466
* Returns true if emit was conducted
467467
*/
468468
emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean {
469-
this.ensureBuilder();
470-
const { emitSkipped, outputFiles } = this.builder.emitFile(this.program, scriptInfo.path);
469+
if (!this.languageServiceEnabled || !this.shouldEmitFile(scriptInfo)) {
470+
return false;
471+
}
472+
const { emitSkipped, outputFiles } = this.getLanguageService(/*ensureSynchronized*/ false).getEmitOutput(scriptInfo.fileName);
471473
if (!emitSkipped) {
472474
for (const outputFile of outputFiles) {
473475
const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, this.currentDirectory);
@@ -593,13 +595,6 @@ namespace ts.server {
593595
});
594596
}
595597

596-
private getFileEmitOutput(sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) {
597-
if (!this.languageServiceEnabled) {
598-
return undefined;
599-
}
600-
return this.getLanguageService(/*ensureSynchronized*/ false).getEmitOutput(sourceFile.fileName, emitOnlyDtsFiles, isDetailed);
601-
}
602-
603598
getExcludedFiles(): ReadonlyArray<NormalizedPath> {
604599
return emptyArray;
605600
}

src/services/services.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1511,12 +1511,12 @@ namespace ts {
15111511
return ts.NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles);
15121512
}
15131513

1514-
function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean) {
1514+
function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean) {
15151515
synchronizeHostData();
15161516

15171517
const sourceFile = getValidSourceFile(fileName);
15181518
const customTransformers = host.getCustomTransformers && host.getCustomTransformers();
1519-
return getFileEmitOutput(program, sourceFile, emitOnlyDtsFiles, isDetailed, cancellationToken, customTransformers);
1519+
return getFileEmitOutput(program, sourceFile, emitOnlyDtsFiles, cancellationToken, customTransformers);
15201520
}
15211521

15221522
// Signature help

src/services/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,6 @@ namespace ts {
288288
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined;
289289

290290
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
291-
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean): EmitOutput | EmitOutputDetailed;
292291

293292
getProgram(): Program;
294293

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

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3731,17 +3731,11 @@ declare namespace ts {
37313731
outputFiles: OutputFile[];
37323732
emitSkipped: boolean;
37333733
}
3734-
interface EmitOutputDetailed extends EmitOutput {
3735-
diagnostics: Diagnostic[];
3736-
sourceMaps: SourceMapData[];
3737-
emittedSourceFiles: SourceFile[];
3738-
}
37393734
interface OutputFile {
37403735
name: string;
37413736
writeByteOrderMark: boolean;
37423737
text: string;
37433738
}
3744-
function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput | EmitOutputDetailed;
37453739
}
37463740
declare namespace ts {
37473741
function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string;
@@ -3954,7 +3948,6 @@ declare namespace ts {
39543948
getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[];
39553949
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined;
39563950
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
3957-
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean): EmitOutput | EmitOutputDetailed;
39583951
getProgram(): Program;
39593952
dispose(): void;
39603953
}
@@ -7044,23 +7037,6 @@ declare namespace ts.server {
70447037
isJavaScript(): boolean;
70457038
}
70467039
}
7047-
declare namespace ts {
7048-
/**
7049-
* Updates the existing missing file watches with the new set of missing files after new program is created
7050-
*/
7051-
function updateMissingFilePathsWatch(program: Program, missingFileWatches: Map<FileWatcher>, createMissingFileWatch: (missingFilePath: Path) => FileWatcher): void;
7052-
interface WildcardDirectoryWatcher {
7053-
watcher: FileWatcher;
7054-
flags: WatchDirectoryFlags;
7055-
}
7056-
/**
7057-
* Updates the existing wild card directory watches with the new set of wild card directories from the config file
7058-
* after new program is created because the config file was reloaded or program was created first time from the config file
7059-
* Note that there is no need to call this function when the program is updated with additional files without reloading config files,
7060-
* as wildcard directories wont change unless reloading config file
7061-
*/
7062-
function updateWatchingWildcardDirectories(existingWatchedForWildcards: Map<WildcardDirectoryWatcher>, wildcardDirectories: Map<WatchDirectoryFlags>, watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher): void;
7063-
}
70647040
declare namespace ts.server {
70657041
interface InstallPackageOptionsWithProjectRootPath extends InstallPackageOptions {
70667042
projectRootPath: Path;
@@ -7205,6 +7181,7 @@ declare namespace ts.server {
72057181
getAllProjectErrors(): ReadonlyArray<Diagnostic>;
72067182
getLanguageService(ensureSynchronized?: boolean): LanguageService;
72077183
private ensureBuilder();
7184+
private shouldEmitFile(scriptInfo);
72087185
getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[];
72097186
/**
72107187
* Returns true if emit was conducted
@@ -7223,7 +7200,6 @@ declare namespace ts.server {
72237200
getRootFiles(): NormalizedPath[];
72247201
getRootScriptInfos(): ScriptInfo[];
72257202
getScriptInfos(): ScriptInfo[];
7226-
private getFileEmitOutput(sourceFile, emitOnlyDtsFiles, isDetailed);
72277203
getExcludedFiles(): ReadonlyArray<NormalizedPath>;
72287204
getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean): NormalizedPath[];
72297205
hasConfigFile(configFilePath: NormalizedPath): boolean;

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

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3678,17 +3678,11 @@ declare namespace ts {
36783678
outputFiles: OutputFile[];
36793679
emitSkipped: boolean;
36803680
}
3681-
interface EmitOutputDetailed extends EmitOutput {
3682-
diagnostics: Diagnostic[];
3683-
sourceMaps: SourceMapData[];
3684-
emittedSourceFiles: SourceFile[];
3685-
}
36863681
interface OutputFile {
36873682
name: string;
36883683
writeByteOrderMark: boolean;
36893684
text: string;
36903685
}
3691-
function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput | EmitOutputDetailed;
36923686
}
36933687
declare namespace ts {
36943688
function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string;
@@ -3954,7 +3948,6 @@ declare namespace ts {
39543948
getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[];
39553949
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined;
39563950
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
3957-
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean): EmitOutput | EmitOutputDetailed;
39583951
getProgram(): Program;
39593952
dispose(): void;
39603953
}

0 commit comments

Comments
 (0)