Skip to content

Fixes the issue with emit where in same file is emitted multiple times #19306

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 130 additions & 156 deletions src/compiler/builder.ts

Large diffs are not rendered by default.

38 changes: 15 additions & 23 deletions src/compiler/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,14 @@ namespace ts {

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

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

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

const result = builder.emitChangedFiles(program);
const result = builder.emitChangedFiles(program, writeFile);
if (result.length === 0) {
emitSkipped = true;
}
Expand All @@ -171,14 +172,13 @@ namespace ts {
if (emitOutput.emitSkipped) {
emitSkipped = true;
}
diagnostics = concatenate(diagnostics, emitOutput.diagnostics);
addRange(diagnostics, emitOutput.diagnostics);
sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps);
writeOutputFiles(emitOutput.outputFiles);
}
}

if (reportSemanticDiagnostics) {
diagnostics = diagnostics.concat(builder.getSemanticDiagnostics(program));
addRange(diagnostics, builder.getSemanticDiagnostics(program));
}
return handleEmitOutputAndReportErrors(host, program, emittedFiles, emitSkipped,
diagnostics, reportDiagnostic);
Expand All @@ -191,31 +191,23 @@ namespace ts {
}
}

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

host.writeFile(fileName, data, writeByteOrderMark);
host.writeFile(fileName, text, writeByteOrderMark);

performance.mark("afterIOWrite");
performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite");

if (emittedFiles) {
emittedFiles.push(fileName);
}
}
catch (e) {
return createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, e);
}
}

function writeOutputFiles(outputFiles: OutputFile[]) {
if (outputFiles) {
for (const outputFile of outputFiles) {
const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark);
if (error) {
diagnostics.push(error);
}
if (emittedFiles) {
emittedFiles.push(outputFile.name);
}
if (onError) {
onError(e.message);
}
}
}
Expand Down Expand Up @@ -308,7 +300,7 @@ namespace ts {
getCurrentDirectory()
);
// There is no extra check needed since we can just rely on the program to decide emit
const builder = createBuilder({ getCanonicalFileName, getEmitOutput: getFileEmitOutput, computeHash, shouldEmitFile: () => true });
const builder = createBuilder({ getCanonicalFileName, computeHash });

synchronizeProgram();

Expand Down
4 changes: 1 addition & 3 deletions src/compiler/watchUtilities.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference path="core.ts" />

/* @internal */
namespace ts {
/**
* Updates the existing missing file watches with the new set of missing files after new program is created
Expand Down Expand Up @@ -72,10 +73,7 @@ namespace ts {
existingWatchedForWildcards.set(directory, createWildcardDirectoryWatcher(directory, flags));
}
}
}

/* @internal */
namespace ts {
export function addFileWatcher(host: System, file: string, cb: FileWatcherCallback): FileWatcher {
return host.watchFile(file, cb);
}
Expand Down
16 changes: 4 additions & 12 deletions src/harness/unittests/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,14 @@ namespace ts {
function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray<string>) => void {
const builder = createBuilder({
getCanonicalFileName: identity,
getEmitOutput: getFileEmitOutput,
computeHash: identity,
shouldEmitFile: returnTrue,
computeHash: identity
});
return fileNames => {
const program = getProgram();
builder.updateProgram(program);
const changedFiles = builder.emitChangedFiles(program);
assert.deepEqual(changedFileNames(changedFiles), fileNames);
const outputFileNames: string[] = [];
builder.emitChangedFiles(program, fileName => outputFileNames.push(fileName));
assert.deepEqual(outputFileNames, fileNames);
};
}

Expand All @@ -63,11 +62,4 @@ namespace ts {
updateProgramText(files, fileName, fileContent);
});
}

function changedFileNames(changedFiles: ReadonlyArray<EmitOutputDetailed>): string[] {
return changedFiles.map(f => {
assert.lengthOf(f.outputFiles, 1);
return f.outputFiles[0].name;
});
}
}
58 changes: 58 additions & 0 deletions src/harness/unittests/tscWatchMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,64 @@ namespace ts.tscWatch {
const outJs = "/a/out.js";
createWatchForOut(/*out*/ undefined, outJs);
});

function verifyFilesEmittedOnce(useOutFile: boolean) {
const file1: FileOrFolder = {
path: "/a/b/output/AnotherDependency/file1.d.ts",
content: "declare namespace Common.SomeComponent.DynamicMenu { enum Z { Full = 0, Min = 1, Average = 2, } }"
};
const file2: FileOrFolder = {
path: "/a/b/dependencies/file2.d.ts",
content: "declare namespace Dependencies.SomeComponent { export class SomeClass { version: string; } }"
};
const file3: FileOrFolder = {
path: "/a/b/project/src/main.ts",
content: "namespace Main { export function fooBar() {} }"
};
const file4: FileOrFolder = {
path: "/a/b/project/src/main2.ts",
content: "namespace main.file4 { import DynamicMenu = Common.SomeComponent.DynamicMenu; export function foo(a: DynamicMenu.z) { } }"
};
const configFile: FileOrFolder = {
path: "/a/b/project/tsconfig.json",
content: JSON.stringify({
compilerOptions: useOutFile ?
{ outFile: "../output/common.js", target: "es5" } :
{ outDir: "../output", target: "es5" },
files: [file1.path, file2.path, file3.path, file4.path]
})
};
const files = [file1, file2, file3, file4];
const allfiles = files.concat(configFile);
const host = createWatchedSystem(allfiles);
const originalWriteFile = host.writeFile.bind(host);
const mapOfFilesWritten = createMap<number>();
host.writeFile = (p: string, content: string) => {
const count = mapOfFilesWritten.get(p);
mapOfFilesWritten.set(p, count ? count + 1 : 1);
return originalWriteFile(p, content);
};
createWatchModeWithConfigFile(configFile.path, host);
if (useOutFile) {
// Only out file
assert.equal(mapOfFilesWritten.size, 1);
}
else {
// main.js and main2.js
assert.equal(mapOfFilesWritten.size, 2);
}
mapOfFilesWritten.forEach((value, key) => {
assert.equal(value, 1, "Key: " + key);
});
}

it("with --outFile and multiple declaration files in the program", () => {
verifyFilesEmittedOnce(/*useOutFile*/ true);
});

it("without --outFile and multiple declaration files in the program", () => {
verifyFilesEmittedOnce(/*useOutFile*/ false);
});
});

describe("tsc-watch emit for configured projects", () => {
Expand Down
27 changes: 11 additions & 16 deletions src/server/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,31 +443,33 @@ namespace ts.server {
if (!this.builder) {
this.builder = createBuilder({
getCanonicalFileName: this.projectService.toCanonicalFileName,
getEmitOutput: (_program, sourceFile, emitOnlyDts, isDetailed) =>
this.getFileEmitOutput(sourceFile, emitOnlyDts, isDetailed),
computeHash: data =>
this.projectService.host.createHash(data),
shouldEmitFile: sourceFile =>
!this.projectService.getScriptInfoForPath(sourceFile.path).isDynamicOrHasMixedContent()
computeHash: data => this.projectService.host.createHash(data)
});
}
}

private shouldEmitFile(scriptInfo: ScriptInfo) {
return scriptInfo && !scriptInfo.isDynamicOrHasMixedContent();
}

getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[] {
if (!this.languageServiceEnabled) {
return [];
}
this.updateGraph();
this.ensureBuilder();
return this.builder.getFilesAffectedBy(this.program, scriptInfo.path);
return mapDefined(this.builder.getFilesAffectedBy(this.program, scriptInfo.path),
sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined);
}

/**
* Returns true if emit was conducted
*/
emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean {
this.ensureBuilder();
const { emitSkipped, outputFiles } = this.builder.emitFile(this.program, scriptInfo.path);
if (!this.languageServiceEnabled || !this.shouldEmitFile(scriptInfo)) {
return false;
}
const { emitSkipped, outputFiles } = this.getLanguageService(/*ensureSynchronized*/ false).getEmitOutput(scriptInfo.fileName);
if (!emitSkipped) {
for (const outputFile of outputFiles) {
const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, this.currentDirectory);
Expand Down Expand Up @@ -593,13 +595,6 @@ namespace ts.server {
});
}

private getFileEmitOutput(sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) {
if (!this.languageServiceEnabled) {
return undefined;
}
return this.getLanguageService(/*ensureSynchronized*/ false).getEmitOutput(sourceFile.fileName, emitOnlyDtsFiles, isDetailed);
}

getExcludedFiles(): ReadonlyArray<NormalizedPath> {
return emptyArray;
}
Expand Down
4 changes: 2 additions & 2 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1511,12 +1511,12 @@ namespace ts {
return ts.NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles);
}

function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean) {
function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean) {
synchronizeHostData();

const sourceFile = getValidSourceFile(fileName);
const customTransformers = host.getCustomTransformers && host.getCustomTransformers();
return getFileEmitOutput(program, sourceFile, emitOnlyDtsFiles, isDetailed, cancellationToken, customTransformers);
return getFileEmitOutput(program, sourceFile, emitOnlyDtsFiles, cancellationToken, customTransformers);
}

// Signature help
Expand Down
1 change: 0 additions & 1 deletion src/services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,6 @@ namespace ts {
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined;

getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean): EmitOutput | EmitOutputDetailed;

getProgram(): Program;

Expand Down
26 changes: 1 addition & 25 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3731,17 +3731,11 @@ declare namespace ts {
outputFiles: OutputFile[];
emitSkipped: boolean;
}
interface EmitOutputDetailed extends EmitOutput {
diagnostics: Diagnostic[];
sourceMaps: SourceMapData[];
emittedSourceFiles: SourceFile[];
}
interface OutputFile {
name: string;
writeByteOrderMark: boolean;
text: string;
}
function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput | EmitOutputDetailed;
}
declare namespace ts {
function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string;
Expand Down Expand Up @@ -3953,7 +3947,6 @@ declare namespace ts {
getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[];
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined;
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean): EmitOutput | EmitOutputDetailed;
getProgram(): Program;
dispose(): void;
}
Expand Down Expand Up @@ -7043,23 +7036,6 @@ declare namespace ts.server {
isJavaScript(): boolean;
}
}
declare namespace ts {
/**
* Updates the existing missing file watches with the new set of missing files after new program is created
*/
function updateMissingFilePathsWatch(program: Program, missingFileWatches: Map<FileWatcher>, createMissingFileWatch: (missingFilePath: Path) => FileWatcher): void;
interface WildcardDirectoryWatcher {
watcher: FileWatcher;
flags: WatchDirectoryFlags;
}
/**
* Updates the existing wild card directory watches with the new set of wild card directories from the config file
* after new program is created because the config file was reloaded or program was created first time from the config file
* Note that there is no need to call this function when the program is updated with additional files without reloading config files,
* as wildcard directories wont change unless reloading config file
*/
function updateWatchingWildcardDirectories(existingWatchedForWildcards: Map<WildcardDirectoryWatcher>, wildcardDirectories: Map<WatchDirectoryFlags>, watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher): void;
}
declare namespace ts.server {
interface InstallPackageOptionsWithProjectRootPath extends InstallPackageOptions {
projectRootPath: Path;
Expand Down Expand Up @@ -7204,6 +7180,7 @@ declare namespace ts.server {
getAllProjectErrors(): ReadonlyArray<Diagnostic>;
getLanguageService(ensureSynchronized?: boolean): LanguageService;
private ensureBuilder();
private shouldEmitFile(scriptInfo);
getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[];
/**
* Returns true if emit was conducted
Expand All @@ -7222,7 +7199,6 @@ declare namespace ts.server {
getRootFiles(): NormalizedPath[];
getRootScriptInfos(): ScriptInfo[];
getScriptInfos(): ScriptInfo[];
private getFileEmitOutput(sourceFile, emitOnlyDtsFiles, isDetailed);
getExcludedFiles(): ReadonlyArray<NormalizedPath>;
getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean): NormalizedPath[];
hasConfigFile(configFilePath: NormalizedPath): boolean;
Expand Down
7 changes: 0 additions & 7 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3678,17 +3678,11 @@ declare namespace ts {
outputFiles: OutputFile[];
emitSkipped: boolean;
}
interface EmitOutputDetailed extends EmitOutput {
diagnostics: Diagnostic[];
sourceMaps: SourceMapData[];
emittedSourceFiles: SourceFile[];
}
interface OutputFile {
name: string;
writeByteOrderMark: boolean;
text: string;
}
function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput | EmitOutputDetailed;
}
declare namespace ts {
function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string;
Expand Down Expand Up @@ -3953,7 +3947,6 @@ declare namespace ts {
getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[];
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined;
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean): EmitOutput | EmitOutputDetailed;
getProgram(): Program;
dispose(): void;
}
Expand Down