From 89c61e797c794515a06a3719ca95e37d97783d6e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 3 Aug 2017 00:27:46 -0700 Subject: [PATCH] Modify the api in builder so that it tracks changed files --- src/compiler/builder.ts | 186 +++++++++------ src/compiler/core.ts | 8 +- src/compiler/tscLib.ts | 74 +++--- src/compiler/utilities.ts | 20 +- src/harness/unittests/tscWatchMode.ts | 322 ++++++++++++-------------- src/server/project.ts | 8 +- src/services/services.ts | 4 +- src/services/types.ts | 2 +- 8 files changed, 320 insertions(+), 304 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index c507b1570a8..489111f26db 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -18,13 +18,14 @@ namespace ts { text: string; } - export interface Builder { + export interface Builder { /** * This is the callback when file infos in the builder are updated */ onProgramUpdateGraph(program: Program): void; getFilesAffectedBy(program: Program, path: Path): string[]; - emitFile(program: Program, path: Path): T | EmitOutput; + emitFile(program: Program, path: Path): EmitOutput; + emitChangedFiles(program: Program): EmitOutputDetailed[]; clear(): void; } @@ -38,19 +39,7 @@ namespace ts { getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[]; } - export function getDetailedEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, - cancellationToken ?: CancellationToken, customTransformers ?: CustomTransformers): EmitOutputDetailed { - return getEmitOutput(/*detailed*/ true, program, sourceFile, emitOnlyDtsFiles, - cancellationToken, customTransformers) as EmitOutputDetailed; - } - - export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, - cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput { - return getEmitOutput(/*detailed*/ false, program, sourceFile, emitOnlyDtsFiles, - cancellationToken, customTransformers); - } - - function getEmitOutput(isDetailed: boolean, program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, + export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput | EmitOutputDetailed { const outputFiles: OutputFile[] = []; let emittedSourceFiles: SourceFile[]; @@ -75,20 +64,23 @@ namespace ts { } } - export function createBuilder( + export function createBuilder( getCanonicalFileName: (fileName: string) => string, - getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles?: boolean) => T, + getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) => EmitOutput | EmitOutputDetailed, computeHash: (data: string) => string, shouldEmitFile: (sourceFile: SourceFile) => boolean - ): Builder { + ): Builder { let isModuleEmit: boolean | undefined; // Last checked shape signature for the file info - let fileInfos: Map; + type FileInfo = { version: string; signature: string; }; + let fileInfos: Map; + let changedFilesSinceLastEmit: Map; let emitHandler: EmitHandler; return { onProgramUpdateGraph, getFilesAffectedBy, emitFile, + emitChangedFiles, clear }; @@ -100,19 +92,37 @@ namespace ts { fileInfos = undefined; } - fileInfos = mutateExistingMap( + changedFilesSinceLastEmit = changedFilesSinceLastEmit || createMap(); + fileInfos = mutateExistingMapWithSameExistingValues( fileInfos, arrayToMap(program.getSourceFiles(), sourceFile => sourceFile.path), - (_path, sourceFile) => { - emitHandler.addScriptInfo(program, sourceFile); - return ""; - }, - (path: Path, _value) => emitHandler.removeScriptInfo(path), - /*isSameValue*/ undefined, - /*OnDeleteExistingMismatchValue*/ undefined, - (_prevValue, sourceFile) => emitHandler.updateScriptInfo(program, sourceFile) + // Add new file info + (_path, sourceFile) => addNewFileInfo(program, sourceFile), + // Remove existing file info + removeExistingFileInfo, + // We will update in place instead of deleting existing value and adding new one + (existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile) ); } + function addNewFileInfo(program: Program, sourceFile: SourceFile): FileInfo { + changedFilesSinceLastEmit.set(sourceFile.path, true); + emitHandler.addScriptInfo(program, sourceFile); + return { version: sourceFile.version, signature: undefined }; + } + + function removeExistingFileInfo(path: Path, _existingFileInfo: FileInfo) { + changedFilesSinceLastEmit.set(path, true); + emitHandler.removeScriptInfo(path); + } + + function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile) { + if (existingInfo.version !== sourceFile.version) { + changedFilesSinceLastEmit.set(sourceFile.path, true); + existingInfo.version = sourceFile.version; + emitHandler.updateScriptInfo(program, sourceFile); + } + } + function ensureProgramGraph(program: Program) { if (!emitHandler) { createProgramGraph(program); @@ -130,20 +140,53 @@ namespace ts { const sourceFile = program.getSourceFile(path); const singleFileResult = sourceFile && shouldEmitFile(sourceFile) ? [sourceFile.fileName] : []; - if (!fileInfos || !fileInfos.has(path) || !updateShapeSignature(program, sourceFile)) { + const info = fileInfos && fileInfos.get(path); + if (!info || !updateShapeSignature(program, sourceFile, info)) { return singleFileResult; } + Debug.assert(!!sourceFile); return emitHandler.getFilesAffectedByUpdatedShape(program, sourceFile, singleFileResult); } - function emitFile(program: Program, path: Path): T | EmitOutput { + function emitFile(program: Program, path: Path) { ensureProgramGraph(program); if (!fileInfos || !fileInfos.has(path)) { return { outputFiles: [], emitSkipped: true }; } - return getEmitOutput(program, program.getSourceFileByPath(path), /*emitOnlyDtsFiles*/ false); + return getEmitOutput(program, program.getSourceFileByPath(path), /*emitOnlyDtsFiles*/ false, /*isDetailed*/ false); + } + + function emitChangedFiles(program: Program): EmitOutputDetailed[] { + ensureProgramGraph(program); + const result: EmitOutputDetailed[] = []; + if (changedFilesSinceLastEmit) { + const seenFiles = createMap(); + changedFilesSinceLastEmit.forEach((__value, path: Path) => { + const affectedFiles = getFilesAffectedBy(program, path); + for (const file of affectedFiles) { + if (!seenFiles.has(file)) { + const sourceFile = program.getSourceFile(file); + seenFiles.set(file, sourceFile); + if (sourceFile) { + const emitOutput = getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ false, /*isDetailed*/ true) as EmitOutputDetailed; + result.push(emitOutput); + + // mark all the emitted source files as seen + if (emitOutput.emittedSourceFiles) { + for (const file of emitOutput.emittedSourceFiles) { + seenFiles.set(file.fileName, file); + } + } + } + } + } + }); + + changedFilesSinceLastEmit = undefined; + } + return result; } function clear() { @@ -152,10 +195,6 @@ namespace ts { fileInfos = undefined; } - function isExternalModuleOrHasOnlyAmbientExternalModules(sourceFile: SourceFile) { - return sourceFile && (isExternalModule(sourceFile) || containsOnlyAmbientModules(sourceFile)); - } - /** * For script files that contains only ambient external modules, although they are not actually external module files, * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, @@ -174,20 +213,21 @@ namespace ts { /** * @return {boolean} indicates if the shape signature has changed since last update. */ - function updateShapeSignature(program: Program, sourceFile: SourceFile) { - const path = sourceFile.path; - - const prevSignature = fileInfos.get(path); - let latestSignature = prevSignature; + function updateShapeSignature(program: Program, sourceFile: SourceFile, info: FileInfo) { + const prevSignature = info.signature; + let latestSignature: string; if (sourceFile.isDeclarationFile) { latestSignature = computeHash(sourceFile.text); - fileInfos.set(path, latestSignature); + info.signature = latestSignature; } else { - const emitOutput = getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true); + const emitOutput = getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, /*isDetailed*/ false); if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { latestSignature = computeHash(emitOutput.outputFiles[0].text); - fileInfos.set(path, latestSignature); + info.signature = latestSignature; + } + else { + latestSignature = prevSignature; } } @@ -201,6 +241,7 @@ namespace ts { */ function getReferencedFiles(program: Program, sourceFile: SourceFile): Map { const referencedFiles = createMap(); + // We need to use a set here since the code can contain the same import twice, // but that will only be one dependency. // To avoid invernal conversion, the key of the referencedFiles map must be of type Path @@ -281,44 +322,45 @@ namespace ts { function getModuleEmitHandler(): EmitHandler { const references = createMap>(); const referencedBy = createMultiMap(); - const scriptVersionForReferences = createMap(); return { - addScriptInfo, + addScriptInfo: setReferences, removeScriptInfo, - updateScriptInfo, + updateScriptInfo: setReferences, getFilesAffectedByUpdatedShape }; - function setReferences(program: Program, sourceFile: SourceFile, existingMap: Map) { + function setReferences(program: Program, sourceFile: SourceFile) { const path = sourceFile.path; - existingMap = mutateExistingMapWithNewSet( - existingMap, - getReferencedFiles(program, sourceFile), - // Creating new Reference: as sourceFile references file with path 'key' - // in other words source file (path) is referenced by 'key' - key => { referencedBy.add(key, path); return true; }, - // Remove existing reference by entry: source file doesnt reference file 'key' any more - // in other words source file (path) is not referenced by 'key' - (key, _existingValue) => { referencedBy.remove(key, path); } + references.set(path, + mutateExistingMapWithNewSet( + // Existing references + references.get(path), + // Updated references + getReferencedFiles(program, sourceFile), + // Creating new Reference: as sourceFile references file with path 'key' + // in other words source file (path) is referenced by 'key' + key => { referencedBy.add(key, path); return true; }, + // Remove existing reference by entry: source file doesnt reference file 'key' any more + // in other words source file (path) is not referenced by 'key' + (key, _existingValue) => { referencedBy.remove(key, path); } + ) ); - references.set(path, existingMap); - scriptVersionForReferences.set(path, sourceFile.version); - } - - function addScriptInfo(program: Program, sourceFile: SourceFile) { - setReferences(program, sourceFile, undefined); } function removeScriptInfo(path: Path) { + // Remove existing references + references.forEach((_value, key) => { + referencedBy.remove(key, path); + }); references.delete(path); - scriptVersionForReferences.delete(path); - } - function updateScriptInfo(program: Program, sourceFile: SourceFile) { - const path = sourceFile.path; - const lastUpdatedVersion = scriptVersionForReferences.get(path); - if (lastUpdatedVersion !== sourceFile.version) { - setReferences(program, sourceFile, references.get(path)); + // Delete the entry and add files referencing this file, as chagned files too + const referencedByPaths = referencedBy.get(path); + if (referencedByPaths) { + for (const path of referencedByPaths) { + changedFilesSinceLastEmit.set(path, true); + } + referencedBy.delete(path); } } @@ -327,7 +369,7 @@ namespace ts { } function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[] { - if (!isExternalModuleOrHasOnlyAmbientExternalModules(sourceFile)) { + if (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile)) { return getAllEmittableFiles(program); } @@ -353,7 +395,7 @@ namespace ts { const currentPath = queue.pop(); if (!seenFileNamesMap.has(currentPath)) { const currentSourceFile = program.getSourceFileByPath(currentPath); - if (currentSourceFile && updateShapeSignature(program, currentSourceFile)) { + if (currentSourceFile && updateShapeSignature(program, currentSourceFile, fileInfos.get(currentPath))) { queue.push(...getReferencedByPaths(currentPath)); } setSeenFileName(currentPath, currentSourceFile); @@ -361,7 +403,7 @@ namespace ts { } // Return array of values that needs emit - return flatMapIter(seenFileNamesMap.values(), value => value); + return flatMapIter(seenFileNamesMap.values()); } } } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index cbccdcbed02..2a41427f2fd 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -473,12 +473,14 @@ namespace ts { return result; } - export function flatMapIter(iter: Iterator, mapfn: (x: T) => U | U[] | undefined): U[] { - const result: U[] = []; + export function flatMapIter(iter: Iterator): T[]; + export function flatMapIter(iter: Iterator, mapfn: (x: T) => U | U[] | undefined): U[]; + export function flatMapIter(iter: Iterator, mapfn?: (x: any) => any): any[] { + const result = []; while (true) { const { value, done } = iter.next(); if (done) break; - const res = mapfn(value); + const res = mapfn ? mapfn(value) : value; if (res) { if (isArray(res)) { result.push(...res); diff --git a/src/compiler/tscLib.ts b/src/compiler/tscLib.ts index b9234906176..13c1b27a5b0 100644 --- a/src/compiler/tscLib.ts +++ b/src/compiler/tscLib.ts @@ -248,7 +248,6 @@ namespace ts { let timerToUpdateProgram: any; // timer callback to recompile the program const sourceFilesCache = createMap(); // Cache that stores the source file and version info - let changedFilePaths: Path[] = []; let host: System; if (configFileName) { @@ -262,7 +261,7 @@ namespace ts { const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); // There is no extra check needed since we can just rely on the program to decide emit - const builder = createBuilder(getCanonicalFileName, getDetailedEmitOutput, computeHash, _sourceFile => true); + const builder = createBuilder(getCanonicalFileName, getFileEmitOutput, computeHash, _sourceFile => true); if (compilerOptions.pretty) { reportDiagnosticWorker = reportDiagnosticWithColorAndContext; @@ -352,51 +351,44 @@ namespace ts { function emitWithBuilder(program: Program): EmitResult { builder.onProgramUpdateGraph(program); - const filesPendingToEmit = changedFilePaths; - changedFilePaths = []; - - const seenFiles = createMap(); - - let emitSkipped: boolean; - const diagnostics: Diagnostic[] = []; const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined; let sourceMaps: SourceMapData[]; - while (filesPendingToEmit.length) { - const filePath = filesPendingToEmit.pop(); - const affectedFiles = builder.getFilesAffectedBy(program, filePath); - for (const file of affectedFiles) { - if (!seenFiles.has(file)) { - seenFiles.set(file, true); - const sourceFile = program.getSourceFile(file); - if (sourceFile) { - writeFiles(builder.emitFile(program, sourceFile.path)); + let emitSkipped: boolean; + let diagnostics: Diagnostic[]; + + const result = builder.emitChangedFiles(program); + switch (result.length) { + case 0: + emitSkipped = true; + break; + case 1: + const emitOutput = result[0]; + ({ diagnostics, sourceMaps, emitSkipped } = emitOutput); + writeOutputFiles(emitOutput.outputFiles); + break; + default: + for (const emitOutput of result) { + if (emitOutput.emitSkipped) { + emitSkipped = true; } + diagnostics = concatenate(diagnostics, emitOutput.diagnostics); + sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps); + writeOutputFiles(emitOutput.outputFiles); } - } } - return { emitSkipped, diagnostics, emittedFiles, sourceMaps }; + return { emitSkipped, diagnostics: diagnostics || [], emittedFiles, sourceMaps }; - function writeFiles(emitOutput: EmitOutputDetailed) { - if (emitOutput.emitSkipped) { - emitSkipped = true; - } - - diagnostics.push(...emitOutput.diagnostics); - sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps); - // If it emitted more than one source files, just mark all those source files as seen - if (emitOutput.emittedSourceFiles && emitOutput.emittedSourceFiles.length > 1) { - for (const file of emitOutput.emittedSourceFiles) { - seenFiles.set(file.fileName, true); - } - } - for (const outputFile of emitOutput.outputFiles) { - const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark); - if (error) { - diagnostics.push(error); - } - if (emittedFiles) { - emittedFiles.push(outputFile.name); + 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); + } } } } @@ -430,8 +422,6 @@ namespace ts { // Create new source file if requested or the versions dont match if (!hostSourceFile || shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) { - changedFilePaths.push(path); - const sourceFile = getNewSourceFile(); if (hostSourceFile) { if (shouldCreateNewSourceFile) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 08816382ecf..0a38e6af58b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3656,13 +3656,27 @@ namespace ts { ); } + export function mutateExistingMapWithSameExistingValues( + existingMap: Map, newMap: Map, + createNewValue: (key: string, valueInNewMap: U) => T, + onDeleteExistingValue: (key: string, existingValue: T) => void, + onExistingValue?: (existingValue: T, valueInNewMap: U) => void + ): Map { + return mutateExistingMap( + existingMap, newMap, + createNewValue, onDeleteExistingValue, + /*isSameValue*/ undefined, /*onDeleteExistingMismatchValue*/ undefined, + onExistingValue + ); + } + export function mutateExistingMap( existingMap: Map, newMap: Map, createNewValue: (key: string, valueInNewMap: U) => T, onDeleteExistingValue: (key: string, existingValue: T) => void, isSameValue?: (existingValue: T, valueInNewMap: U) => boolean, OnDeleteExistingMismatchValue?: (key: string, existingValue: T) => void, - onSameExistingValue?: (existingValue: T, valueInNewMap: U) => void + onExistingValue?: (existingValue: T, valueInNewMap: U) => void ): Map { // If there are new values update them if (newMap) { @@ -3680,8 +3694,8 @@ namespace ts { existingMap.delete(key); OnDeleteExistingMismatchValue(key, existingValue); } - else if (onSameExistingValue) { - onSameExistingValue(existingValue, valueInNewMap); + else if (onExistingValue) { + onExistingValue(existingValue, valueInNewMap); } }); } diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index 1025e250d27..7622bc2556b 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -1015,9 +1015,24 @@ namespace ts.tscWatch { describe("tsc-watch emit for configured projects", () => { const file1Consumer1Path = "/a/b/file1Consumer1.ts"; const moduleFile1Path = "/a/b/moduleFile1.ts"; - function getInitialState(configObj: any = {}, fileNames?: string[]) { + const configFilePath = "/a/b/tsconfig.json"; + type InitialStateParams = { + /** custom config file options */ + configObj?: any; + /** list of the files that will be emitted for first compilation */ + firstCompilationEmitFiles?: string[]; + /** get the emit file for file - default is multi file emit line */ + getEmitLine?(file: FileOrFolder, host: WatchedSystem): string; + /** Additional files and folders to add */ + getAdditionalFileOrFolder?(): FileOrFolder[]; + /** initial list of files to emit if not the default list */ + firstReloadFileList?: string[]; + }; + function getInitialState({ configObj = {}, firstCompilationEmitFiles, getEmitLine, getAdditionalFileOrFolder, firstReloadFileList }: InitialStateParams = {}) { const host = createWatchedSystem([]); - const getOutputName = (file: FileOrFolder) => getEmittedLineForMultiFileOutput(file, host); + const getOutputName = getEmitLine ? (file: FileOrFolder) => getEmitLine(file, host) : + (file: FileOrFolder) => getEmittedLineForMultiFileOutput(file, host); + const moduleFile1 = getFileOrFolderEmit({ path: moduleFile1Path, content: "export function Foo() { };", @@ -1043,20 +1058,24 @@ namespace ts.tscWatch { content: `interface GlobalFoo { age: number }` }); + const additionalFiles = getAdditionalFileOrFolder ? + map(getAdditionalFileOrFolder(), file => getFileOrFolderEmit(file, getOutputName)) : + []; + (configObj.compilerOptions || (configObj.compilerOptions = {})).listEmittedFiles = true; const configFile = getFileOrFolderEmit({ - path: "/a/b/tsconfig.json", + path: configFilePath, content: JSON.stringify(configObj) }); - const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]; + const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile, ...additionalFiles]; let allEmittedFiles = getEmittedLines(files); - host.reloadFS(files); + host.reloadFS(firstReloadFileList ? getFiles(firstReloadFileList) : files); // Initial compile createWatchWithConfig(configFile.path, host); - if (fileNames) { - checkAffectedLines(host, map(fileNames, name => find(files, file => file.path === name)), allEmittedFiles); + if (firstCompilationEmitFiles) { + checkAffectedLines(host, getFiles(firstCompilationEmitFiles), allEmittedFiles); } else { checkOutputContains(host, allEmittedFiles); @@ -1066,11 +1085,20 @@ namespace ts.tscWatch { return { moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile, files, + getFile, verifyAffectedFiles, verifyAffectedAllFiles, getOutputName }; + function getFiles(filelist: string[]) { + return map(filelist, getFile); + } + + function getFile(fileName: string) { + return find(files, file => file.path === fileName); + } + function verifyAffectedAllFiles() { host.reloadFS(files); host.checkTimeoutQueueLengthAndRun(1); @@ -1172,8 +1200,8 @@ namespace ts.tscWatch { it("should detect changes in non-root files", () => { const { moduleFile1, file1Consumer1, - verifyAffectedFiles, - } = getInitialState({ files: [file1Consumer1Path] }, [file1Consumer1Path, moduleFile1Path]); + verifyAffectedFiles + } = getInitialState({ configObj: { files: [file1Consumer1Path] }, firstCompilationEmitFiles: [file1Consumer1Path, moduleFile1Path] }); moduleFile1.content = `export var T: number;export function Foo() { };`; verifyAffectedFiles([moduleFile1, file1Consumer1]); @@ -1192,189 +1220,129 @@ namespace ts.tscWatch { verifyAffectedAllFiles(); }); - //it("should save with base tsconfig.json", () => { - // configFile = { - // path: "/a/b/tsconfig.json", - // content: `{ - // "extends": "/a/tsconfig.json" - // }` - // }; + it("should always return the file itself if '--isolatedModules' is specified", () => { + const { + moduleFile1, verifyAffectedFiles + } = getInitialState({ configObj: { compilerOptions: { isolatedModules: true } } }); - // const configFile2: FileOrFolder = { - // path: "/a/tsconfig.json", - // content: `{ - // "compileOnSave": true - // }` - // }; + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1]); + }); - // const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile2, configFile, libFile]); - // const typingsInstaller = createTestTypingsInstaller(host); - // const session = createSession(host, typingsInstaller); + it("should always return the file itself if '--out' or '--outFile' is specified", () => { + const outFilePath = "/a/b/out.js"; + const { + moduleFile1, verifyAffectedFiles + } = getInitialState({ + configObj: { compilerOptions: { module: "system", outFile: outFilePath } }, + getEmitLine: (_, host) => getEmittedLineForSingleFileOutput(outFilePath, host) + }); - // openFilesForSession([moduleFile1, file1Consumer1], session); - // sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); - //}); + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1]); + }); - //it("should always return the file itself if '--isolatedModules' is specified", () => { - // configFile = { - // path: "/a/b/tsconfig.json", - // content: `{ - // "compileOnSave": true, - // "compilerOptions": { - // "isolatedModules": true - // } - // }` - // }; + it("should return cascaded affected file list", () => { + const file1Consumer1Consumer1: FileOrFolder = { + path: "/a/b/file1Consumer1Consumer1.ts", + content: `import {y} from "./file1Consumer1";` + }; + const { + moduleFile1, file1Consumer1, file1Consumer2, verifyAffectedFiles, getFile + } = getInitialState({ + getAdditionalFileOrFolder: () => [file1Consumer1Consumer1] + }); - // const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); - // const typingsInstaller = createTestTypingsInstaller(host); - // const session = createSession(host, typingsInstaller); - // openFilesForSession([moduleFile1], session); + const file1Consumer1Consumer1Emit = getFile(file1Consumer1Consumer1.path); + file1Consumer1.content += "export var T: number;"; + verifyAffectedFiles([file1Consumer1, file1Consumer1Consumer1Emit]); - // const file1ChangeShapeRequest = makeSessionRequest(CommandNames.Change, { - // file: moduleFile1.path, - // line: 1, - // offset: 27, - // endLine: 1, - // endOffset: 27, - // insertString: `Point,` - // }); - // session.executeCommand(file1ChangeShapeRequest); - // sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); - //}); + // Doesnt change the shape of file1Consumer1 + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2]); - //it("should always return the file itself if '--out' or '--outFile' is specified", () => { - // configFile = { - // path: "/a/b/tsconfig.json", - // content: `{ - // "compileOnSave": true, - // "compilerOptions": { - // "module": "system", - // "outFile": "/a/b/out.js" - // } - // }` - // }; + // Change both files before the timeout + file1Consumer1.content += "export var T2: number;"; + moduleFile1.content = `export var T2: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer1Consumer1Emit]); + }); - // const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); - // const typingsInstaller = createTestTypingsInstaller(host); - // const session = createSession(host, typingsInstaller); - // openFilesForSession([moduleFile1], session); + it("should work fine for files with circular references", () => { + // TODO: do not exit on such errors? Just continue to watch the files for update in watch mode - // const file1ChangeShapeRequest = makeSessionRequest(CommandNames.Change, { - // file: moduleFile1.path, - // line: 1, - // offset: 27, - // endLine: 1, - // endOffset: 27, - // insertString: `Point,` - // }); - // session.executeCommand(file1ChangeShapeRequest); - // sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); - //}); + const file1: FileOrFolder = { + path: "/a/b/file1.ts", + content: ` + /// + export var t1 = 10;` + }; + const file2: FileOrFolder = { + path: "/a/b/file2.ts", + content: ` + /// + export var t2 = 10;` + }; + const { + configFile, + getFile, + verifyAffectedFiles + } = getInitialState({ + firstCompilationEmitFiles: [file1.path, file2.path], + getAdditionalFileOrFolder: () => [file1, file2], + firstReloadFileList: [libFile.path, file1.path, file2.path, configFilePath] + }); + const file1Emit = getFile(file1.path), file2Emit = getFile(file2.path); - //it("should return cascaded affected file list", () => { - // const file1Consumer1Consumer1: FileOrFolder = { - // path: "/a/b/file1Consumer1Consumer1.ts", - // content: `import {y} from "./file1Consumer1";` - // }; - // const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer1Consumer1, globalFile3, configFile, libFile]); - // const typingsInstaller = createTestTypingsInstaller(host); - // const session = createSession(host, typingsInstaller); + file1Emit.content += "export var t3 = 10;"; + verifyAffectedFiles([file1Emit, file2Emit], [file1, file2, libFile, configFile]); - // openFilesForSession([moduleFile1, file1Consumer1], session); - // sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]); + }); - // const changeFile1Consumer1ShapeRequest = makeSessionRequest(CommandNames.Change, { - // file: file1Consumer1.path, - // line: 2, - // offset: 1, - // endLine: 2, - // endOffset: 1, - // insertString: `export var T: number;` - // }); - // session.executeCommand(changeModuleFile1ShapeRequest1); - // session.executeCommand(changeFile1Consumer1ShapeRequest); - // sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]); - //}); + it("should detect removed code file", () => { + const referenceFile1: FileOrFolder = { + path: "/a/b/referenceFile1.ts", + content: ` + /// + export var x = Foo();` + }; + const { + configFile, + getFile, + verifyAffectedFiles + } = getInitialState({ + firstCompilationEmitFiles: [referenceFile1.path, moduleFile1Path], + getAdditionalFileOrFolder: () => [referenceFile1], + firstReloadFileList: [libFile.path, referenceFile1.path, moduleFile1Path, configFilePath] + }); - //it("should work fine for files with circular references", () => { - // const file1: FileOrFolder = { - // path: "/a/b/file1.ts", - // content: ` - // /// - // export var t1 = 10;` - // }; - // const file2: FileOrFolder = { - // path: "/a/b/file2.ts", - // content: ` - // /// - // export var t2 = 10;` - // }; - // const host = createServerHost([file1, file2, configFile]); - // const typingsInstaller = createTestTypingsInstaller(host); - // const session = createSession(host, typingsInstaller); + const referenceFile1Emit = getFile(referenceFile1.path); + verifyAffectedFiles([referenceFile1Emit], [libFile, referenceFile1Emit, configFile]); + }); - // openFilesForSession([file1, file2], session); - // const file1AffectedListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path }); - // sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [{ projectFileName: configFile.path, files: [file1, file2] }]); - //}); + it("should detect non-existing code file", () => { + const referenceFile1: FileOrFolder = { + path: "/a/b/referenceFile1.ts", + content: ` + /// + export var x = Foo();` + }; + const { + configFile, + moduleFile2, + getFile, + verifyAffectedFiles + } = getInitialState({ + firstCompilationEmitFiles: [referenceFile1.path], + getAdditionalFileOrFolder: () => [referenceFile1], + firstReloadFileList: [libFile.path, referenceFile1.path, configFilePath] + }); - //it("should return results for all projects if not specifying projectFileName", () => { - // const file1: FileOrFolder = { path: "/a/b/file1.ts", content: "export var t = 10;" }; - // const file2: FileOrFolder = { path: "/a/b/file2.ts", content: `import {t} from "./file1"; var t2 = 11;` }; - // const file3: FileOrFolder = { path: "/a/c/file2.ts", content: `import {t} from "../b/file1"; var t3 = 11;` }; - // const configFile1: FileOrFolder = { path: "/a/b/tsconfig.json", content: `{ "compileOnSave": true }` }; - // const configFile2: FileOrFolder = { path: "/a/c/tsconfig.json", content: `{ "compileOnSave": true }` }; + const referenceFile1Emit = getFile(referenceFile1.path); + referenceFile1Emit.content += "export var yy = Foo();"; + verifyAffectedFiles([referenceFile1Emit], [libFile, referenceFile1Emit, configFile]); - // const host = createServerHost([file1, file2, file3, configFile1, configFile2]); - // const session = createSession(host); - - // openFilesForSession([file1, file2, file3], session); - // const file1AffectedListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path }); - - // sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [ - // { projectFileName: configFile1.path, files: [file1, file2] }, - // { projectFileName: configFile2.path, files: [file1, file3] } - // ]); - //}); - - //it("should detect removed code file", () => { - // const referenceFile1: FileOrFolder = { - // path: "/a/b/referenceFile1.ts", - // content: ` - // /// - // export var x = Foo();` - // }; - // const host = createServerHost([moduleFile1, referenceFile1, configFile]); - // const session = createSession(host); - - // openFilesForSession([referenceFile1], session); - // host.reloadFS([referenceFile1, configFile]); - - // const request = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path }); - // sendAffectedFileRequestAndCheckResult(session, request, [ - // { projectFileName: configFile.path, files: [referenceFile1] } - // ]); - // const requestForMissingFile = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path }); - // sendAffectedFileRequestAndCheckResult(session, requestForMissingFile, []); - //}); - - //it("should detect non-existing code file", () => { - // const referenceFile1: FileOrFolder = { - // path: "/a/b/referenceFile1.ts", - // content: ` - // /// - // export var x = Foo();` - // }; - // const host = createServerHost([referenceFile1, configFile]); - // const session = createSession(host); - - // openFilesForSession([referenceFile1], session); - // const request = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path }); - // sendAffectedFileRequestAndCheckResult(session, request, [ - // { projectFileName: configFile.path, files: [referenceFile1] } - // ]); - //}); + // Create module File2 and see both files are saved + verifyAffectedFiles([referenceFile1Emit, moduleFile2], [libFile, moduleFile2, referenceFile1Emit, configFile]); + }); }); - } diff --git a/src/server/project.ts b/src/server/project.ts index 942ff9f71a4..02e2fb20759 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -130,7 +130,7 @@ namespace ts.server { /*@internal*/ lsHost: LSHost; - builder: Builder; + builder: Builder; /** * Set of files names that were updated since the last call to getChangesSinceVersion. */ @@ -250,7 +250,7 @@ namespace ts.server { if (!this.builder) { this.builder = createBuilder( this.projectService.toCanonicalFileName, - (_program, sourceFile, emitOnlyDts) => this.getFileEmitOutput(sourceFile, emitOnlyDts), + (_program, sourceFile, emitOnlyDts, isDetailed) => this.getFileEmitOutput(sourceFile, emitOnlyDts, isDetailed), data => this.projectService.host.createHash(data), sourceFile => !this.projectService.getScriptInfoForPath(sourceFile.path).hasMixedContent ); @@ -418,11 +418,11 @@ namespace ts.server { }); } - private getFileEmitOutput(sourceFile: SourceFile, emitOnlyDtsFiles: boolean) { + private getFileEmitOutput(sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) { if (!this.languageServiceEnabled) { return undefined; } - return this.getLanguageService().getEmitOutput(sourceFile.fileName, emitOnlyDtsFiles); + return this.getLanguageService().getEmitOutput(sourceFile.fileName, emitOnlyDtsFiles, isDetailed); } getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) { diff --git a/src/services/services.ts b/src/services/services.ts index 8fe158a4798..40a446e0967 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1468,12 +1468,12 @@ namespace ts { return ts.NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles); } - function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput { + function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean) { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); const customTransformers = host.getCustomTransformers && host.getCustomTransformers(); - return getFileEmitOutput(program, sourceFile, emitOnlyDtsFiles, cancellationToken, customTransformers); + return getFileEmitOutput(program, sourceFile, emitOnlyDtsFiles, isDetailed, cancellationToken, customTransformers); } // Signature help diff --git a/src/services/types.ts b/src/services/types.ts index 34d79b311ca..e17df08378a 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -275,7 +275,7 @@ 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;