From d3f954e0cc9ae75a3364e9518c19f0a53c7f3dae Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 17 Oct 2017 23:02:22 -0700 Subject: [PATCH 1/6] Add failing testcase where when d.ts file is in program, the files get emitted multiple times with --out setting --- src/harness/unittests/tscWatchMode.ts | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index 2ee86d4255867..fefbffa224927 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -1105,6 +1105,46 @@ namespace ts.tscWatch { const outJs = "/a/out.js"; createWatchForOut(/*out*/ undefined, outJs); }); + + it("with --outFile and multiple declaration files in the program", () => { + 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: { + outFile: "../output/common.js", + 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); + let numberOfTimesFileWritten = 0; + host.writeFile = (p: string, content: string) => { + numberOfTimesFileWritten++; + return originalWriteFile(p, content); + }; + createWatchModeWithConfigFile(configFile.path, host); + assert.equal(numberOfTimesFileWritten, 1); + }); }); describe("tsc-watch emit for configured projects", () => { From f9c901ada7148cbaf697c4b8bdd8905ba2cc219f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 17 Oct 2017 21:18:27 -0700 Subject: [PATCH 2/6] Use get files affected by internally and hence use file paths as input --- src/compiler/builder.ts | 136 +++++++++++++++++----------------------- src/server/project.ts | 9 ++- 2 files changed, 66 insertions(+), 79 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 192f1e430278e..93b265f12ca60 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -47,11 +47,11 @@ namespace ts { /* @internal */ namespace ts { 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[]; + /** Gets the files affected by the file path */ + getFilesAffectedBy(program: Program, path: Path): ReadonlyArray; + /** Emits the given file */ emitFile(program: Program, path: Path): EmitOutput; /** Emit the changed files and clear the cache of the changed files */ @@ -88,11 +88,10 @@ 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; } interface FileInfo { - fileName: string; version: string; signature: string; } @@ -109,7 +108,7 @@ namespace ts { const fileInfos = createMap(); const semanticDiagnosticsPerFile = createMap>(); /** The map has key by source file's path that has been changed */ - const changedFileNames = createMap(); + const changedFileNames = createMap(); let emitHandler: EmitHandler; return { updateProgram, @@ -142,31 +141,31 @@ namespace ts { ); } - function registerChangedFile(path: Path, fileName: string) { - changedFileNames.set(path, fileName); + function registerChangedFile(path: Path) { + changedFileNames.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) { + registerChangedFile(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,23 +181,23 @@ namespace ts { } } - function getFilesAffectedBy(program: Program, path: Path): string[] { + function getFilesAffectedBy(program: Program, path: Path): ReadonlyArray { ensureProgramGraph(program); - const sourceFile = program.getSourceFile(path); - const singleFileResult = sourceFile && options.shouldEmitFile(sourceFile) ? [sourceFile.fileName] : []; + const sourceFile = program.getSourceFileByPath(path); const info = fileInfos.get(path); if (!info || !updateShapeSignature(program, sourceFile, info)) { - return singleFileResult; + return sourceFile && [sourceFile] || emptyArray; } Debug.assert(!!sourceFile); - return emitHandler.getFilesAffectedByUpdatedShape(program, sourceFile, singleFileResult); + return emitHandler.getFilesAffectedByUpdatedShape(program, sourceFile); } function emitFile(program: Program, path: Path) { ensureProgramGraph(program); - if (!fileInfos.has(path)) { + const sourceFile = program.getSourceFileByPath(path); + if (!fileInfos.has(path) || options.shouldEmitFile(sourceFile)) { return { outputFiles: [], emitSkipped: true }; } @@ -207,14 +206,12 @@ namespace ts { function enumerateChangedFilesSet( program: Program, - onChangedFile: (fileName: string, path: Path) => void, - onAffectedFile: (fileName: string, sourceFile: SourceFile) => void + onAffectedFile: (sourceFile: SourceFile) => void ) { - changedFileNames.forEach((fileName, path) => { - onChangedFile(fileName, path as Path); + changedFileNames.forEach((_true, path) => { const affectedFiles = getFilesAffectedBy(program, path as Path); for (const file of affectedFiles) { - onAffectedFile(file, program.getSourceFile(file)); + onAffectedFile(file); } }); } @@ -222,27 +219,25 @@ namespace ts { function enumerateChangedFilesEmitOutput( program: Program, emitOnlyDtsFiles: boolean, - onChangedFile: (fileName: string, path: Path) => void, - onEmitOutput: (emitOutput: EmitOutputDetailed, sourceFile: SourceFile) => void + onEmitOutput: (emitOutput: EmitOutputDetailed) => void ) { const seenFiles = createMap(); - 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); - - const emitOutput = options.getEmitOutput(program, sourceFile, emitOnlyDtsFiles, /*isDetailed*/ true) as EmitOutputDetailed; - onEmitOutput(emitOutput, sourceFile); - - // mark all the emitted source files as seen - if (emitOutput.emittedSourceFiles) { - for (const file of emitOutput.emittedSourceFiles) { - seenFiles.set(file.fileName, true); - } + enumerateChangedFilesSet(program, sourceFile => { + if (!seenFiles.has(sourceFile.path)) { + seenFiles.set(sourceFile.path, true); + // Any affected file shouldnt have the cached diagnostics + semanticDiagnosticsPerFile.delete(sourceFile.path); + + const emitOutput = options.getEmitOutput(program, sourceFile, emitOnlyDtsFiles, /*isDetailed*/ true) as EmitOutputDetailed; + + // mark all the emitted source files as seen + if (emitOutput.emittedSourceFiles) { + for (const file of emitOutput.emittedSourceFiles) { + seenFiles.set(file.path, true); } } + + onEmitOutput(emitOutput); } }); } @@ -250,8 +245,7 @@ namespace ts { function emitChangedFiles(program: Program): EmitOutputDetailed[] { ensureProgramGraph(program); const result: EmitOutputDetailed[] = []; - enumerateChangedFilesEmitOutput(program, /*emitOnlyDtsFiles*/ false, - /*onChangedFile*/ noop, emitOutput => result.push(emitOutput)); + enumerateChangedFilesEmitOutput(program, /*emitOnlyDtsFiles*/ false, emitOutput => result.push(emitOutput)); changedFileNames.clear(); return result; } @@ -260,11 +254,7 @@ namespace ts { ensureProgramGraph(program); // Ensure that changed files have cleared their respective - enumerateChangedFilesSet(program, /*onChangedFile*/ noop, (_affectedFileName, sourceFile) => { - if (sourceFile) { - semanticDiagnosticsPerFile.delete(sourceFile.path); - } - }); + enumerateChangedFilesSet(program, sourceFile => semanticDiagnosticsPerFile.delete(sourceFile.path)); let diagnostics: Diagnostic[]; for (const sourceFile of program.getSourceFiles()) { @@ -386,24 +376,20 @@ 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 { + let result: SourceFile[]; + for (const sourceFile of program.getSourceFiles()) { if (sourceFile !== firstSourceFile) { - add(sourceFile); + addSourceFile(sourceFile); } } - return result; + return result || emptyArray; - 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 +403,14 @@ namespace ts { getFilesAffectedByUpdatedShape }; - function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[] { + function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile): ReadonlyArray { 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 +467,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 +481,33 @@ namespace ts { ); } - function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[] { + function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile): ReadonlyArray { 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(); - const setSeenFileName = (path: Path, sourceFile: SourceFile) => { - seenFileNamesMap.set(path, sourceFile && options.shouldEmitFile(sourceFile) ? sourceFile.fileName : undefined); - }; + const seenFileNamesMap = createMap(); // 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); + seenFileNamesMap.set(currentPath, currentSourceFile); if (currentSourceFile && updateShapeSignature(program, currentSourceFile, fileInfos.get(currentPath))) { queue.push(...getReferencedByPaths(currentPath)); } - setSeenFileName(currentPath, currentSourceFile); } } diff --git a/src/server/project.ts b/src/server/project.ts index 725ce725c3881..5e6e76db986a1 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -448,18 +448,23 @@ namespace ts.server { computeHash: data => this.projectService.host.createHash(data), shouldEmitFile: sourceFile => - !this.projectService.getScriptInfoForPath(sourceFile.path).isDynamicOrHasMixedContent() + !this.shouldEmitFile(sourceFile) }); } } + private shouldEmitFile(sourceFile: SourceFile) { + return !this.projectService.getScriptInfoForPath(sourceFile.path).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(sourceFile) ? sourceFile.fileName : undefined); } /** From 8fbfb5ffc0dd930d95a4ed04317a70da16b43c6c Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 3 Oct 2017 15:06:56 -0700 Subject: [PATCH 3/6] Modify api to emit affected files using callback instead of generating in memory output Also marking few apis introduced during watch improvements changes that are suppose to be internal for now --- src/compiler/builder.ts | 191 ++++++++---------- src/compiler/watch.ts | 38 ++-- src/compiler/watchUtilities.ts | 4 +- src/harness/unittests/builder.ts | 16 +- src/harness/unittests/tscWatchMode.ts | 34 +++- src/server/project.ts | 26 +-- src/services/services.ts | 4 +- src/services/types.ts | 1 - .../reference/api/tsserverlibrary.d.ts | 26 +-- tests/baselines/reference/api/typescript.d.ts | 7 - 10 files changed, 141 insertions(+), 206 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 93b265f12ca60..cd678b6f361fd 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -6,56 +6,36 @@ 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 { +/* @internal */ +namespace ts { + export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, + cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput { 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[]) { + function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) { outputFiles.push({ name: fileName, writeByteOrderMark, text }); - if (isDetailed) { - emittedSourceFiles = addRange(emittedSourceFiles, sourceFiles); - } } } -} -/* @internal */ -namespace ts { export interface Builder { /** Called to inform builder about new program */ updateProgram(newProgram: Program): void; + /** Gets the files affected by the file path */ getFilesAffectedBy(program: Program, path: Path): ReadonlyArray; - /** Emits the given file */ - emitFile(program: Program, path: Path): EmitOutput; /** Emit the changed files and clear the cache of the changed files */ - emitChangedFiles(program: Program): EmitOutputDetailed[]; + emitChangedFiles(program: Program, writeFileCallback: WriteFileCallback): ReadonlyArray; + /** When called gets the semantic diagnostics for the program. It also caches the diagnostics and manage them */ getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): Diagnostic[]; @@ -98,9 +78,7 @@ namespace ts { 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 { @@ -108,12 +86,13 @@ namespace ts { const fileInfos = createMap(); const semanticDiagnosticsPerFile = createMap>(); /** The map has key by source file's path that has been changed */ - const changedFileNames = createMap(); + const changedFilesSet = createMap(); + const hasShapeChanged = createMap(); + let allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; let emitHandler: EmitHandler; return { updateProgram, getFilesAffectedBy, - emitFile, emitChangedFiles, getSemanticDiagnostics, clear @@ -127,6 +106,8 @@ namespace ts { fileInfos.clear(); semanticDiagnosticsPerFile.clear(); } + hasShapeChanged.clear(); + allFilesExcludingDefaultLibraryFile = undefined; mutateMap( fileInfos, arrayToMap(program.getSourceFiles(), sourceFile => sourceFile.path), @@ -142,7 +123,7 @@ namespace ts { } function registerChangedFile(path: Path) { - changedFileNames.set(path, true); + changedFilesSet.set(path, true); // All changed files need to re-evaluate its semantic diagnostics semanticDiagnosticsPerFile.delete(path); } @@ -185,101 +166,79 @@ namespace ts { ensureProgramGraph(program); const sourceFile = program.getSourceFileByPath(path); - const info = fileInfos.get(path); - if (!info || !updateShapeSignature(program, sourceFile, info)) { - return sourceFile && [sourceFile] || emptyArray; + if (!sourceFile) { + return emptyArray; } - Debug.assert(!!sourceFile); + if (!updateShapeSignature(program, sourceFile)) { + return [sourceFile]; + } return emitHandler.getFilesAffectedByUpdatedShape(program, sourceFile); } - function emitFile(program: Program, path: Path) { + function emitChangedFiles(program: Program, writeFileCallback: WriteFileCallback): ReadonlyArray { ensureProgramGraph(program); - const sourceFile = program.getSourceFileByPath(path); - if (!fileInfos.has(path) || options.shouldEmitFile(sourceFile)) { - return { outputFiles: [], emitSkipped: true }; - } - - return options.getEmitOutput(program, program.getSourceFileByPath(path), /*emitOnlyDtsFiles*/ false, /*isDetailed*/ false); - } - - function enumerateChangedFilesSet( - program: Program, - onAffectedFile: (sourceFile: SourceFile) => void - ) { - changedFileNames.forEach((_true, path) => { - const affectedFiles = getFilesAffectedBy(program, path as Path); - for (const file of affectedFiles) { - onAffectedFile(file); - } - }); - } - - function enumerateChangedFilesEmitOutput( - program: Program, - emitOnlyDtsFiles: boolean, - onEmitOutput: (emitOutput: EmitOutputDetailed) => void - ) { + const compilerOptions = program.getCompilerOptions(); + let result: EmitResult[] | undefined; const seenFiles = createMap(); - enumerateChangedFilesSet(program, sourceFile => { - if (!seenFiles.has(sourceFile.path)) { - seenFiles.set(sourceFile.path, true); - // Any affected file shouldnt have the cached diagnostics - semanticDiagnosticsPerFile.delete(sourceFile.path); - - const emitOutput = options.getEmitOutput(program, sourceFile, emitOnlyDtsFiles, /*isDetailed*/ true) as EmitOutputDetailed; - - // mark all the emitted source files as seen - if (emitOutput.emittedSourceFiles) { - for (const file of emitOutput.emittedSourceFiles) { - seenFiles.set(file.path, true); + changedFilesSet.forEach((_true, path) => { + const affectedFiles = getFilesAffectedBy(program, path as Path); + affectedFiles.forEach(affectedFile => { + // Affected files shouldnt have cached diagnostics + semanticDiagnosticsPerFile.delete(affectedFile.path); + + if (!seenFiles.has(affectedFile.path)) { + seenFiles.set(affectedFile.path, true); + + // With --out or --outFile all outputs go into single file, do it only once + if (compilerOptions.outFile || compilerOptions.out) { + if (!result) { + result = [program.emit(affectedFile, writeFileCallback)]; + } + } + else { + // Emit the affected file + (result || (result = [])).push(program.emit(affectedFile, writeFileCallback)); } } - - onEmitOutput(emitOutput); - } + }); }); - } - - function emitChangedFiles(program: Program): EmitOutputDetailed[] { - ensureProgramGraph(program); - const result: EmitOutputDetailed[] = []; - enumerateChangedFilesEmitOutput(program, /*emitOnlyDtsFiles*/ false, emitOutput => result.push(emitOutput)); - changedFileNames.clear(); - return result; + changedFilesSet.clear(); + return result || emptyArray; } function getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): Diagnostic[] { ensureProgramGraph(program); - - // Ensure that changed files have cleared their respective - enumerateChangedFilesSet(program, sourceFile => semanticDiagnosticsPerFile.delete(sourceFile.path)); + Debug.assert(changedFilesSet.size === 0); 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 { + 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) { + 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(); } /** @@ -300,7 +259,18 @@ 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) { @@ -308,7 +278,7 @@ namespace ts { 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; @@ -379,13 +349,20 @@ namespace ts { * Gets all files of the program excluding the default library file */ function getAllFilesExcludingDefaultLibraryFile(program: Program, firstSourceFile: SourceFile): ReadonlyArray { + // Use cached result + if (allFilesExcludingDefaultLibraryFile) { + return allFilesExcludingDefaultLibraryFile; + } + let result: SourceFile[]; + addSourceFile(firstSourceFile); for (const sourceFile of program.getSourceFiles()) { if (sourceFile !== firstSourceFile) { addSourceFile(sourceFile); } } - return result || emptyArray; + allFilesExcludingDefaultLibraryFile = result || emptyArray; + return allFilesExcludingDefaultLibraryFile; function addSourceFile(sourceFile: SourceFile) { if (!program.isSourceFileDefaultLibrary(sourceFile)) { @@ -505,7 +482,7 @@ namespace ts { if (!seenFileNamesMap.has(currentPath)) { const currentSourceFile = program.getSourceFileByPath(currentPath); seenFileNamesMap.set(currentPath, currentSourceFile); - if (currentSourceFile && updateShapeSignature(program, currentSourceFile, fileInfos.get(currentPath))) { + if (currentSourceFile && updateShapeSignature(program, currentSourceFile)) { queue.push(...getReferencedByPaths(currentPath)); } } diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 6f61469197b21..53692fe1e0488 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -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(); diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index 26bf689401a8c..f39024d5a7cfd 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -1,5 +1,6 @@ /// +/* @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); } diff --git a/src/harness/unittests/builder.ts b/src/harness/unittests/builder.ts index edd471e9ebe9e..bfdeb1a40678a 100644 --- a/src/harness/unittests/builder.ts +++ b/src/harness/unittests/builder.ts @@ -46,15 +46,14 @@ namespace ts { function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray) => 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): string[] { - return changedFiles.map(f => { - assert.lengthOf(f.outputFiles, 1); - return f.outputFiles[0].name; - }); - } } diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index fefbffa224927..308021f0b0a84 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -1106,7 +1106,7 @@ namespace ts.tscWatch { createWatchForOut(/*out*/ undefined, outJs); }); - it("with --outFile and multiple declaration files in the program", () => { + 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, } }" @@ -1126,10 +1126,9 @@ namespace ts.tscWatch { const configFile: FileOrFolder = { path: "/a/b/project/tsconfig.json", content: JSON.stringify({ - compilerOptions: { - outFile: "../output/common.js", - target: "es5" - }, + compilerOptions: useOutFile ? + { outFile: "../output/common.js", target: "es5" } : + { outDir: "../output", target: "es5" }, files: [file1.path, file2.path, file3.path, file4.path] }) }; @@ -1137,13 +1136,32 @@ namespace ts.tscWatch { const allfiles = files.concat(configFile); const host = createWatchedSystem(allfiles); const originalWriteFile = host.writeFile.bind(host); - let numberOfTimesFileWritten = 0; + const mapOfFilesWritten = createMap(); host.writeFile = (p: string, content: string) => { - numberOfTimesFileWritten++; + const count = mapOfFilesWritten.get(p); + mapOfFilesWritten.set(p, count ? count + 1 : 1); return originalWriteFile(p, content); }; createWatchModeWithConfigFile(configFile.path, host); - assert.equal(numberOfTimesFileWritten, 1); + 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); }); }); diff --git a/src/server/project.ts b/src/server/project.ts index 5e6e76db986a1..3cadfd6822499 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -443,18 +443,13 @@ 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.shouldEmitFile(sourceFile) + computeHash: data => this.projectService.host.createHash(data) }); } } - private shouldEmitFile(sourceFile: SourceFile) { - return !this.projectService.getScriptInfoForPath(sourceFile.path).isDynamicOrHasMixedContent(); + private shouldEmitFile(scriptInfo: ScriptInfo) { + return scriptInfo && !scriptInfo.isDynamicOrHasMixedContent(); } getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[] { @@ -464,15 +459,17 @@ namespace ts.server { this.updateGraph(); this.ensureBuilder(); return mapDefined(this.builder.getFilesAffectedBy(this.program, scriptInfo.path), - sourceFile => this.shouldEmitFile(sourceFile) ? sourceFile.fileName : undefined); + 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); @@ -598,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 { return emptyArray; } diff --git a/src/services/services.ts b/src/services/services.ts index 114692ebba2e5..2923acf404cef 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -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 diff --git a/src/services/types.ts b/src/services/types.ts index 7ec559be92283..f9d725c2fa3e3 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -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; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 52132ae832f25..5f3d90e5ad0e5 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -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; @@ -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; } @@ -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, 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, wildcardDirectories: Map, watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher): void; -} declare namespace ts.server { interface InstallPackageOptionsWithProjectRootPath extends InstallPackageOptions { projectRootPath: Path; @@ -7204,6 +7180,7 @@ declare namespace ts.server { getAllProjectErrors(): ReadonlyArray; getLanguageService(ensureSynchronized?: boolean): LanguageService; private ensureBuilder(); + private shouldEmitFile(scriptInfo); getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[]; /** * Returns true if emit was conducted @@ -7222,7 +7199,6 @@ declare namespace ts.server { getRootFiles(): NormalizedPath[]; getRootScriptInfos(): ScriptInfo[]; getScriptInfos(): ScriptInfo[]; - private getFileEmitOutput(sourceFile, emitOnlyDtsFiles, isDetailed); getExcludedFiles(): ReadonlyArray; getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean): NormalizedPath[]; hasConfigFile(configFilePath: NormalizedPath): boolean; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 976d50942ae1b..d14def7d79156 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -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; @@ -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; } From 835d7cb910f3acd44b72b87b6606e5e08b386dce Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 18 Oct 2017 12:50:36 -0700 Subject: [PATCH 4/6] Simplify emit changed files further Also use source file version as the signature of declaration file instead of computing it from text --- src/compiler/builder.ts | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index cd678b6f361fd..1b110704e7540 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -135,7 +135,10 @@ namespace ts { } function removeExistingFileInfo(_existingFileInfo: FileInfo, path: Path) { - registerChangedFile(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); } @@ -179,9 +182,22 @@ namespace ts { function emitChangedFiles(program: Program, writeFileCallback: WriteFileCallback): ReadonlyArray { ensureProgramGraph(program); const compilerOptions = program.getCompilerOptions(); - let result: EmitResult[] | undefined; + + if (!changedFilesSet.size) { + return emptyArray; + } + + // With --out or --outFile all outputs go into single file, do it only once + if (compilerOptions.outFile || compilerOptions.out) { + semanticDiagnosticsPerFile.clear(); + changedFilesSet.clear(); + return [program.emit(/*targetSourceFile*/ undefined, writeFileCallback)]; + } + const seenFiles = createMap(); + 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 @@ -190,16 +206,8 @@ namespace ts { if (!seenFiles.has(affectedFile.path)) { seenFiles.set(affectedFile.path, true); - // With --out or --outFile all outputs go into single file, do it only once - if (compilerOptions.outFile || compilerOptions.out) { - if (!result) { - result = [program.emit(affectedFile, writeFileCallback)]; - } - } - else { - // Emit the affected file - (result || (result = [])).push(program.emit(affectedFile, writeFileCallback)); - } + // Emit the affected file + (result || (result = [])).push(program.emit(affectedFile, writeFileCallback)); } }); }); @@ -274,7 +282,7 @@ namespace ts { const prevSignature = info.signature; let latestSignature: string; if (sourceFile.isDeclarationFile) { - latestSignature = options.computeHash(sourceFile.text); + latestSignature = sourceFile.version; info.signature = latestSignature; } else { From 7e780c0a1a4cc881dac93f0eddead090aef719d7 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 18 Oct 2017 13:43:20 -0700 Subject: [PATCH 5/6] Do not cache the semantic diagnostics when compiler options has --out since we would anyways get all fresh diagnostics --- src/compiler/builder.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 1b110704e7540..49a5bfc8d974a 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -37,7 +37,7 @@ namespace ts { emitChangedFiles(program: Program, writeFileCallback: WriteFileCallback): ReadonlyArray; /** 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; /** Called to reset the status of the builder */ clear(): void; @@ -189,7 +189,7 @@ namespace ts { // With --out or --outFile all outputs go into single file, do it only once if (compilerOptions.outFile || compilerOptions.out) { - semanticDiagnosticsPerFile.clear(); + Debug.assert(semanticDiagnosticsPerFile.size === 0); changedFilesSet.clear(); return [program.emit(/*targetSourceFile*/ undefined, writeFileCallback)]; } @@ -215,10 +215,17 @@ namespace ts { return result || emptyArray; } - function getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): Diagnostic[] { + function getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): ReadonlyArray { ensureProgramGraph(program); Debug.assert(changedFilesSet.size === 0); + 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()) { diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(program, sourceFile, cancellationToken)); From 9bea0dbdc2ed6ab6ccd8ca005ca422eb28f58924 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 18 Oct 2017 15:28:20 -0700 Subject: [PATCH 6/6] Actually use cached semantic diagnostics --- src/compiler/builder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 49a5bfc8d974a..635855083c08d 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -238,7 +238,7 @@ namespace ts { const cachedDiagnostics = semanticDiagnosticsPerFile.get(path); // Report the semantic diagnostics from the cache if we already have those diagnostics present if (cachedDiagnostics) { - cachedDiagnostics; + return cachedDiagnostics; } // Diagnostics werent cached, get them from program, and cache the result