Merge pull request #19306 from Microsoft/doNoWriteFilesMultipleTimes

Fixes the issue with emit where in same file is emitted multiple times
This commit is contained in:
Sheetal Nandi
2017-10-18 16:12:36 -07:00
committed by GitHub
10 changed files with 226 additions and 249 deletions

View File

@@ -6,58 +6,38 @@ namespace ts {
emitSkipped: boolean;
}
export interface EmitOutputDetailed extends EmitOutput {
diagnostics: Diagnostic[];
sourceMaps: SourceMapData[];
emittedSourceFiles: SourceFile[];
}
export interface OutputFile {
name: string;
writeByteOrderMark: boolean;
text: string;
}
export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean,
cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput | EmitOutputDetailed {
const outputFiles: OutputFile[] = [];
let emittedSourceFiles: SourceFile[];
const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
if (!isDetailed) {
return { outputFiles, emitSkipped: emitResult.emitSkipped };
}
return {
outputFiles,
emitSkipped: emitResult.emitSkipped,
diagnostics: emitResult.diagnostics,
sourceMaps: emitResult.sourceMaps,
emittedSourceFiles
};
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, _onError: (message: string) => void, sourceFiles: SourceFile[]) {
outputFiles.push({ name: fileName, writeByteOrderMark, text });
if (isDetailed) {
emittedSourceFiles = addRange(emittedSourceFiles, sourceFiles);
}
}
}
}
/* @internal */
namespace ts {
export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean,
cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput {
const outputFiles: OutputFile[] = [];
const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
return { outputFiles, emitSkipped: emitResult.emitSkipped };
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) {
outputFiles.push({ name: fileName, writeByteOrderMark, text });
}
}
export interface Builder {
/**
* Call this to feed new program
*/
/** Called to inform builder about new program */
updateProgram(newProgram: Program): void;
getFilesAffectedBy(program: Program, path: Path): string[];
emitFile(program: Program, path: Path): EmitOutput;
/** Gets the files affected by the file path */
getFilesAffectedBy(program: Program, path: Path): ReadonlyArray<SourceFile>;
/** Emit the changed files and clear the cache of the changed files */
emitChangedFiles(program: Program): EmitOutputDetailed[];
emitChangedFiles(program: Program, writeFileCallback: WriteFileCallback): ReadonlyArray<EmitResult>;
/** When called gets the semantic diagnostics for the program. It also caches the diagnostics and manage them */
getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): Diagnostic[];
getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
/** Called to reset the status of the builder */
clear(): void;
@@ -88,20 +68,17 @@ namespace ts {
/**
* Gets the files affected by the script info which has updated shape from the known one
*/
getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[];
getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile): ReadonlyArray<SourceFile>;
}
interface FileInfo {
fileName: string;
version: string;
signature: string;
}
export interface BuilderOptions {
getCanonicalFileName: (fileName: string) => string;
getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) => EmitOutput | EmitOutputDetailed;
computeHash: (data: string) => string;
shouldEmitFile: (sourceFile: SourceFile) => boolean;
}
export function createBuilder(options: BuilderOptions): Builder {
@@ -109,12 +86,13 @@ namespace ts {
const fileInfos = createMap<FileInfo>();
const semanticDiagnosticsPerFile = createMap<ReadonlyArray<Diagnostic>>();
/** The map has key by source file's path that has been changed */
const changedFileNames = createMap<string>();
const changedFilesSet = createMap<true>();
const hasShapeChanged = createMap<true>();
let allFilesExcludingDefaultLibraryFile: ReadonlyArray<SourceFile> | undefined;
let emitHandler: EmitHandler;
return {
updateProgram,
getFilesAffectedBy,
emitFile,
emitChangedFiles,
getSemanticDiagnostics,
clear
@@ -128,6 +106,8 @@ namespace ts {
fileInfos.clear();
semanticDiagnosticsPerFile.clear();
}
hasShapeChanged.clear();
allFilesExcludingDefaultLibraryFile = undefined;
mutateMap(
fileInfos,
arrayToMap(program.getSourceFiles(), sourceFile => sourceFile.path),
@@ -142,31 +122,34 @@ namespace ts {
);
}
function registerChangedFile(path: Path, fileName: string) {
changedFileNames.set(path, fileName);
function registerChangedFile(path: Path) {
changedFilesSet.set(path, true);
// All changed files need to re-evaluate its semantic diagnostics
semanticDiagnosticsPerFile.delete(path);
}
function addNewFileInfo(program: Program, sourceFile: SourceFile): FileInfo {
registerChangedFile(sourceFile.path, sourceFile.fileName);
registerChangedFile(sourceFile.path);
emitHandler.onAddSourceFile(program, sourceFile);
return { fileName: sourceFile.fileName, version: sourceFile.version, signature: undefined };
return { version: sourceFile.version, signature: undefined };
}
function removeExistingFileInfo(existingFileInfo: FileInfo, path: Path) {
registerChangedFile(path, existingFileInfo.fileName);
function removeExistingFileInfo(_existingFileInfo: FileInfo, path: Path) {
// Since we dont need to track removed file as changed file
// We can just remove its diagnostics
changedFilesSet.delete(path);
semanticDiagnosticsPerFile.delete(path);
emitHandler.onRemoveSourceFile(path);
}
function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile) {
if (existingInfo.version !== sourceFile.version) {
registerChangedFile(sourceFile.path, sourceFile.fileName);
registerChangedFile(sourceFile.path);
existingInfo.version = sourceFile.version;
emitHandler.onUpdateSourceFile(program, sourceFile);
}
else if (emitHandler.onUpdateSourceFileWithSameVersion(program, sourceFile)) {
registerChangedFile(sourceFile.path, sourceFile.fileName);
registerChangedFile(sourceFile.path);
}
}
@@ -182,114 +165,95 @@ namespace ts {
}
}
function getFilesAffectedBy(program: Program, path: Path): string[] {
function getFilesAffectedBy(program: Program, path: Path): ReadonlyArray<SourceFile> {
ensureProgramGraph(program);
const sourceFile = program.getSourceFile(path);
const singleFileResult = sourceFile && options.shouldEmitFile(sourceFile) ? [sourceFile.fileName] : [];
const info = fileInfos.get(path);
if (!info || !updateShapeSignature(program, sourceFile, info)) {
return singleFileResult;
const sourceFile = program.getSourceFileByPath(path);
if (!sourceFile) {
return emptyArray;
}
Debug.assert(!!sourceFile);
return emitHandler.getFilesAffectedByUpdatedShape(program, sourceFile, singleFileResult);
if (!updateShapeSignature(program, sourceFile)) {
return [sourceFile];
}
return emitHandler.getFilesAffectedByUpdatedShape(program, sourceFile);
}
function emitFile(program: Program, path: Path) {
function emitChangedFiles(program: Program, writeFileCallback: WriteFileCallback): ReadonlyArray<EmitResult> {
ensureProgramGraph(program);
if (!fileInfos.has(path)) {
return { outputFiles: [], emitSkipped: true };
const compilerOptions = program.getCompilerOptions();
if (!changedFilesSet.size) {
return emptyArray;
}
return options.getEmitOutput(program, program.getSourceFileByPath(path), /*emitOnlyDtsFiles*/ false, /*isDetailed*/ false);
}
// With --out or --outFile all outputs go into single file, do it only once
if (compilerOptions.outFile || compilerOptions.out) {
Debug.assert(semanticDiagnosticsPerFile.size === 0);
changedFilesSet.clear();
return [program.emit(/*targetSourceFile*/ undefined, writeFileCallback)];
}
function enumerateChangedFilesSet(
program: Program,
onChangedFile: (fileName: string, path: Path) => void,
onAffectedFile: (fileName: string, sourceFile: SourceFile) => void
) {
changedFileNames.forEach((fileName, path) => {
onChangedFile(fileName, path as Path);
const affectedFiles = getFilesAffectedBy(program, path as Path);
for (const file of affectedFiles) {
onAffectedFile(file, program.getSourceFile(file));
}
});
}
function enumerateChangedFilesEmitOutput(
program: Program,
emitOnlyDtsFiles: boolean,
onChangedFile: (fileName: string, path: Path) => void,
onEmitOutput: (emitOutput: EmitOutputDetailed, sourceFile: SourceFile) => void
) {
const seenFiles = createMap<true>();
enumerateChangedFilesSet(program, onChangedFile, (fileName, sourceFile) => {
if (!seenFiles.has(fileName)) {
seenFiles.set(fileName, true);
if (sourceFile) {
// Any affected file shouldnt have the cached diagnostics
semanticDiagnosticsPerFile.delete(sourceFile.path);
let result: EmitResult[] | undefined;
changedFilesSet.forEach((_true, path) => {
// Get the affected Files by this program
const affectedFiles = getFilesAffectedBy(program, path as Path);
affectedFiles.forEach(affectedFile => {
// Affected files shouldnt have cached diagnostics
semanticDiagnosticsPerFile.delete(affectedFile.path);
const emitOutput = options.getEmitOutput(program, sourceFile, emitOnlyDtsFiles, /*isDetailed*/ true) as EmitOutputDetailed;
onEmitOutput(emitOutput, sourceFile);
if (!seenFiles.has(affectedFile.path)) {
seenFiles.set(affectedFile.path, true);
// mark all the emitted source files as seen
if (emitOutput.emittedSourceFiles) {
for (const file of emitOutput.emittedSourceFiles) {
seenFiles.set(file.fileName, true);
}
}
// Emit the affected file
(result || (result = [])).push(program.emit(affectedFile, writeFileCallback));
}
}
});
});
changedFilesSet.clear();
return result || emptyArray;
}
function emitChangedFiles(program: Program): EmitOutputDetailed[] {
function getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic> {
ensureProgramGraph(program);
const result: EmitOutputDetailed[] = [];
enumerateChangedFilesEmitOutput(program, /*emitOnlyDtsFiles*/ false,
/*onChangedFile*/ noop, emitOutput => result.push(emitOutput));
changedFileNames.clear();
return result;
}
Debug.assert(changedFilesSet.size === 0);
function getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): Diagnostic[] {
ensureProgramGraph(program);
// Ensure that changed files have cleared their respective
enumerateChangedFilesSet(program, /*onChangedFile*/ noop, (_affectedFileName, sourceFile) => {
if (sourceFile) {
semanticDiagnosticsPerFile.delete(sourceFile.path);
}
});
const compilerOptions = program.getCompilerOptions();
if (compilerOptions.outFile || compilerOptions.out) {
Debug.assert(semanticDiagnosticsPerFile.size === 0);
// We dont need to cache the diagnostics just return them from program
return program.getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken);
}
let diagnostics: Diagnostic[];
for (const sourceFile of program.getSourceFiles()) {
const path = sourceFile.path;
const cachedDiagnostics = semanticDiagnosticsPerFile.get(path);
// Report the semantic diagnostics from the cache if we already have those diagnostics present
if (cachedDiagnostics) {
diagnostics = addRange(diagnostics, cachedDiagnostics);
}
else {
// Diagnostics werent cached, get them from program, and cache the result
const cachedDiagnostics = program.getSemanticDiagnostics(sourceFile, cancellationToken);
semanticDiagnosticsPerFile.set(path, cachedDiagnostics);
diagnostics = addRange(diagnostics, cachedDiagnostics);
}
diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(program, sourceFile, cancellationToken));
}
return diagnostics || emptyArray;
}
function getSemanticDiagnosticsOfFile(program: Program, sourceFile: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic> {
const path = sourceFile.path;
const cachedDiagnostics = semanticDiagnosticsPerFile.get(path);
// Report the semantic diagnostics from the cache if we already have those diagnostics present
if (cachedDiagnostics) {
return cachedDiagnostics;
}
// Diagnostics werent cached, get them from program, and cache the result
const diagnostics = program.getSemanticDiagnostics(sourceFile, cancellationToken);
semanticDiagnosticsPerFile.set(path, diagnostics);
return diagnostics;
}
function clear() {
isModuleEmit = undefined;
emitHandler = undefined;
fileInfos.clear();
semanticDiagnosticsPerFile.clear();
changedFileNames.clear();
changedFilesSet.clear();
hasShapeChanged.clear();
}
/**
@@ -310,15 +274,26 @@ namespace ts {
/**
* @return {boolean} indicates if the shape signature has changed since last update.
*/
function updateShapeSignature(program: Program, sourceFile: SourceFile, info: FileInfo) {
function updateShapeSignature(program: Program, sourceFile: SourceFile) {
Debug.assert(!!sourceFile);
// If we have cached the result for this file, that means hence forth we should assume file shape is uptodate
if (hasShapeChanged.has(sourceFile.path)) {
return false;
}
hasShapeChanged.set(sourceFile.path, true);
const info = fileInfos.get(sourceFile.path);
Debug.assert(!!info);
const prevSignature = info.signature;
let latestSignature: string;
if (sourceFile.isDeclarationFile) {
latestSignature = options.computeHash(sourceFile.text);
latestSignature = sourceFile.version;
info.signature = latestSignature;
}
else {
const emitOutput = options.getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, /*isDetailed*/ false);
const emitOutput = getFileEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true);
if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) {
latestSignature = options.computeHash(emitOutput.outputFiles[0].text);
info.signature = latestSignature;
@@ -386,24 +361,27 @@ namespace ts {
}
/**
* Gets all the emittable files from the program.
* @param firstSourceFile This one will be emitted first. See https://github.com/Microsoft/TypeScript/issues/16888
* Gets all files of the program excluding the default library file
*/
function getAllEmittableFiles(program: Program, firstSourceFile: SourceFile): string[] {
const defaultLibraryFileName = getDefaultLibFileName(program.getCompilerOptions());
const sourceFiles = program.getSourceFiles();
const result: string[] = [];
add(firstSourceFile);
for (const sourceFile of sourceFiles) {
function getAllFilesExcludingDefaultLibraryFile(program: Program, firstSourceFile: SourceFile): ReadonlyArray<SourceFile> {
// Use cached result
if (allFilesExcludingDefaultLibraryFile) {
return allFilesExcludingDefaultLibraryFile;
}
let result: SourceFile[];
addSourceFile(firstSourceFile);
for (const sourceFile of program.getSourceFiles()) {
if (sourceFile !== firstSourceFile) {
add(sourceFile);
addSourceFile(sourceFile);
}
}
return result;
allFilesExcludingDefaultLibraryFile = result || emptyArray;
return allFilesExcludingDefaultLibraryFile;
function add(sourceFile: SourceFile): void {
if (getBaseFileName(sourceFile.fileName) !== defaultLibraryFileName && options.shouldEmitFile(sourceFile)) {
result.push(sourceFile.fileName);
function addSourceFile(sourceFile: SourceFile) {
if (!program.isSourceFileDefaultLibrary(sourceFile)) {
(result || (result = [])).push(sourceFile);
}
}
}
@@ -417,14 +395,14 @@ namespace ts {
getFilesAffectedByUpdatedShape
};
function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[] {
function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile): ReadonlyArray<SourceFile> {
const options = program.getCompilerOptions();
// If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project,
// so returning the file itself is good enough.
if (options && (options.out || options.outFile)) {
return singleFileResult;
return [sourceFile];
}
return getAllEmittableFiles(program, sourceFile);
return getAllFilesExcludingDefaultLibraryFile(program, sourceFile);
}
}
@@ -481,7 +459,7 @@ namespace ts {
// add files referencing the removedFilePath, as changed files too
const referencedByInfo = fileInfos.get(filePath);
if (referencedByInfo) {
registerChangedFile(filePath as Path, referencedByInfo.fileName);
registerChangedFile(filePath as Path);
}
}
});
@@ -495,37 +473,33 @@ namespace ts {
);
}
function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[] {
function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile): ReadonlyArray<SourceFile> {
if (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile)) {
return getAllEmittableFiles(program, sourceFile);
return getAllFilesExcludingDefaultLibraryFile(program, sourceFile);
}
const compilerOptions = program.getCompilerOptions();
if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) {
return singleFileResult;
return [sourceFile];
}
// Now we need to if each file in the referencedBy list has a shape change as well.
// Because if so, its own referencedBy files need to be saved as well to make the
// emitting result consistent with files on disk.
const seenFileNamesMap = createMap<string>();
const setSeenFileName = (path: Path, sourceFile: SourceFile) => {
seenFileNamesMap.set(path, sourceFile && options.shouldEmitFile(sourceFile) ? sourceFile.fileName : undefined);
};
const seenFileNamesMap = createMap<SourceFile>();
// Start with the paths this file was referenced by
const path = sourceFile.path;
setSeenFileName(path, sourceFile);
seenFileNamesMap.set(path, sourceFile);
const queue = getReferencedByPaths(path);
while (queue.length > 0) {
const currentPath = queue.pop();
if (!seenFileNamesMap.has(currentPath)) {
const currentSourceFile = program.getSourceFileByPath(currentPath);
if (currentSourceFile && updateShapeSignature(program, currentSourceFile, fileInfos.get(currentPath))) {
seenFileNamesMap.set(currentPath, currentSourceFile);
if (currentSourceFile && updateShapeSignature(program, currentSourceFile)) {
queue.push(...getReferencedByPaths(currentPath));
}
setSeenFileName(currentPath, currentSourceFile);
}
}

View File

@@ -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;
@@ -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;
}
@@ -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);
@@ -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);
}
}
}
@@ -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();

View File

@@ -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
@@ -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);
}

View File

@@ -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);
};
}
@@ -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;
});
}
}

View File

@@ -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", () => {

View File

@@ -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);
@@ -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;
}

View File

@@ -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

View File

@@ -288,7 +288,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;

View File

@@ -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;
@@ -3954,7 +3948,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;
}
@@ -7044,23 +7037,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;
@@ -7205,6 +7181,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
@@ -7223,7 +7200,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;

View File

@@ -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;
@@ -3954,7 +3948,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;
}