diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 736c087ec6a..a7437e34599 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -1526,11 +1526,24 @@ namespace ts { }); describe("CompileOnSave affected list", () => { - function sendAffectedFileRequestAndCheckResult(session: server.Session, request: server.protocol.Request, expectedFileList: FileOrFolder[]) { - const actualResult = session.executeCommand(request).response; - const expectedFileNameList = expectedFileList.length > 0 ? ts.map(expectedFileList, f => f.path).sort() : []; - const actualFileNameList = actualResult.sort(); - assert.isTrue(arrayIsEqualTo(actualFileNameList, expectedFileNameList), `Actual result is ${actualFileNameList}, while expected ${expectedFileNameList}`); + function sendAffectedFileRequestAndCheckResult(session: server.Session, request: server.protocol.Request, expectedFileList: { projectFileName: string, files: FileOrFolder[] }[]) { + const response: server.protocol.CompileOnSaveAffectedFileListSingleProject[] = session.executeCommand(request).response; + const actualResult = response.sort((list1, list2) => compareStrings(list1.projectFileName, list2.projectFileName)); + expectedFileList = expectedFileList.sort((list1, list2) => compareStrings(list1.projectFileName, list2.projectFileName)); + + assert.equal(actualResult.length, expectedFileList.length, `Actual result project number is different from the expected project number`); + + for (let i = 0; i < actualResult.length; i++) { + const actualResultSingleProject = actualResult[i]; + const expectedResultSingleProject = expectedFileList[i]; + assert.equal(actualResultSingleProject.projectFileName, expectedResultSingleProject.projectFileName, `Actual result contains different projects than the expected result`); + + const actualResultSingleProjectFileNameList = actualResultSingleProject.fileNames.sort(); + const expectedResultSingleProjectFileNameList = map(expectedResultSingleProject.files, f => f.path).sort(); + assert.isTrue( + arrayIsEqualTo(actualResultSingleProjectFileNameList, expectedResultSingleProjectFileNameList), + `For project ${actualResultSingleProject.projectFileName}, the actual result is ${actualResultSingleProjectFileNameList}, while expected ${expectedResultSingleProjectFileNameList}`); + } } describe("for configured projects", () => { @@ -1545,9 +1558,6 @@ namespace ts { let changeModuleFile1ShapeRequest2: server.protocol.Request; // A compile on save affected file request using file1 let moduleFile1FileListRequest: server.protocol.Request; - let host: TestServerHost; - let typingsInstaller: server.ITypingsInstaller; - let session: server.Session; beforeEach(() => { moduleFile1 = { @@ -1612,20 +1622,20 @@ namespace ts { insertString: `export var T2: number;` }); - moduleFile1FileListRequest = makeSessionRequest(server.CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path }); - - host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - typingsInstaller = new TestTypingsInstaller("/a/data/", host); - session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + moduleFile1FileListRequest = makeSessionRequest(server.CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path, projectFileName: configFile.path }); }); it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { + const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); + const typingsInstaller = new TestTypingsInstaller("/a/data/", host); + const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + openFilesForSession([moduleFile1, file1Consumer1], session); // Send an initial compileOnSave request - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1, file1Consumer1, file1Consumer2]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); session.executeCommand(changeModuleFile1ShapeRequest1); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1, file1Consumer1, file1Consumer2]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); // Change the content of file1 to `export var T: number;export function Foo() { console.log('hi'); };` const changeFile1InternalRequest = makeSessionRequest(server.CommandNames.Change, { @@ -1637,14 +1647,18 @@ namespace ts { insertString: `console.log('hi');` }); session.executeCommand(changeFile1InternalRequest); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); }); it("should be up-to-date with the reference map changes", () => { + const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); + const typingsInstaller = new TestTypingsInstaller("/a/data/", host); + const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + openFilesForSession([moduleFile1, file1Consumer1], session); // Send an initial compileOnSave request - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1, file1Consumer1, file1Consumer2]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); // Change file2 content to `let y = Foo();` const removeFile1Consumer1ImportRequest = makeSessionRequest(server.CommandNames.Change, { @@ -1657,7 +1671,7 @@ namespace ts { }); session.executeCommand(removeFile1Consumer1ImportRequest); session.executeCommand(changeModuleFile1ShapeRequest1); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1, file1Consumer2]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]); // Add the import statements back to file2 const addFile2ImportRequest = makeSessionRequest(server.CommandNames.Change, { @@ -1680,37 +1694,49 @@ namespace ts { insertString: `export var T2: string;` }); session.executeCommand(changeModuleFile1ShapeRequest2); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1, file1Consumer1, file1Consumer2]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); }); it("should be up-to-date with changes made in non-open files", () => { + const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); + const typingsInstaller = new TestTypingsInstaller("/a/data/", host); + const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + openFilesForSession([moduleFile1], session); // Send an initial compileOnSave request - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1, file1Consumer1, file1Consumer2]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); file1Consumer1.content = `let y = 10;`; host.reloadFS([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]); host.triggerFileWatcherCallback(file1Consumer1.path, /*removed*/ false); session.executeCommand(changeModuleFile1ShapeRequest1); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1, file1Consumer2]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]); }); it("should be up-to-date with deleted files", () => { + const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); + const typingsInstaller = new TestTypingsInstaller("/a/data/", host); + const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + openFilesForSession([moduleFile1], session); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1, file1Consumer1, file1Consumer2]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); session.executeCommand(changeModuleFile1ShapeRequest1); // Delete file1Consumer2 host.reloadFS([moduleFile1, file1Consumer1, configFile, libFile]); host.triggerFileWatcherCallback(file1Consumer2.path, /*removed*/ true); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1, file1Consumer1]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]); }); it("should be up-to-date with newly created files", () => { + const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); + const typingsInstaller = new TestTypingsInstaller("/a/data/", host); + const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + openFilesForSession([moduleFile1], session); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1, file1Consumer1, file1Consumer2]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); const file1Consumer3: FileOrFolder = { path: "/a/b/file1Consumer3.ts", @@ -1720,7 +1746,7 @@ namespace ts { host.triggerDirectoryWatcherCallback(ts.getDirectoryPath(file1Consumer3.path), file1Consumer3.path); host.runQueuedTimeoutCallbacks(); session.executeCommand(changeModuleFile1ShapeRequest1); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1, file1Consumer1, file1Consumer2, file1Consumer3]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, file1Consumer3] }]); }); it("should detect changes in non-root files", () => { @@ -1742,23 +1768,27 @@ namespace ts { }` }; - host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); - typingsInstaller = new TestTypingsInstaller("/a/data/", host); - session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); + const typingsInstaller = new TestTypingsInstaller("/a/data/", host); + const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); openFilesForSession([moduleFile1, file1Consumer1], session); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1, file1Consumer1]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]); // change file1 shape now, and verify both files are affected session.executeCommand(changeModuleFile1ShapeRequest1); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1, file1Consumer1]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]); // change file1 internal, and verify only file1 is affected session.executeCommand(changeModuleFile1InternalRequest1); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); }); it("should return all files if a global file changed shape", () => { + const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); + const typingsInstaller = new TestTypingsInstaller("/a/data/", host); + const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + openFilesForSession([globalFile3], session); const changeGlobalFile3ShapeRequest = makeSessionRequest(server.CommandNames.Change, { file: globalFile3.path, @@ -1772,7 +1802,7 @@ namespace ts { // check after file1 shape changes session.executeCommand(changeGlobalFile3ShapeRequest); const globalFile3FileListRequest = makeSessionRequest(server.CommandNames.CompileOnSaveAffectedFileList, { file: globalFile3.path }); - sendAffectedFileRequestAndCheckResult(session, globalFile3FileListRequest, [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2]); + sendAffectedFileRequestAndCheckResult(session, globalFile3FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2] }]); }); it("should return empty array if CompileOnSave is not enabled", () => { @@ -1781,9 +1811,9 @@ namespace ts { content: `{}` }; - host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]); - typingsInstaller = new TestTypingsInstaller("/a/data/", host); - session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]); + const typingsInstaller = new TestTypingsInstaller("/a/data/", host); + const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); openFilesForSession([moduleFile1], session); sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, []); }); @@ -1799,9 +1829,9 @@ namespace ts { }` }; - host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); - typingsInstaller = new TestTypingsInstaller("/a/data/", host); - session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); + const typingsInstaller = new TestTypingsInstaller("/a/data/", host); + const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); openFilesForSession([moduleFile1], session); const file1ChangeShapeRequest = makeSessionRequest(server.CommandNames.Change, { @@ -1813,7 +1843,7 @@ namespace ts { insertString: `Point,` }); session.executeCommand(file1ChangeShapeRequest); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); }); it("should always return the file itself if '--out' or '--outFile' is specified", () => { @@ -1828,9 +1858,9 @@ namespace ts { }` }; - host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); - typingsInstaller = new TestTypingsInstaller("/a/data/", host); - session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); + const typingsInstaller = new TestTypingsInstaller("/a/data/", host); + const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); openFilesForSession([moduleFile1], session); const file1ChangeShapeRequest = makeSessionRequest(server.CommandNames.Change, { @@ -1842,7 +1872,7 @@ namespace ts { insertString: `Point,` }); session.executeCommand(file1ChangeShapeRequest); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); }); it("should return cascaded affected file list", () => { @@ -1850,12 +1880,12 @@ namespace ts { path: "/a/b/file1Consumer1Consumer1.ts", content: `import {y} from "./file1Consumer1";` }; - host = createServerHost([moduleFile1, file1Consumer1, file1Consumer1Consumer1, globalFile3, configFile, libFile]); - typingsInstaller = new TestTypingsInstaller("/a/data/", host); - session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer1Consumer1, globalFile3, configFile, libFile]); + const typingsInstaller = new TestTypingsInstaller("/a/data/", host); + const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); openFilesForSession([moduleFile1, file1Consumer1], session); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1, file1Consumer1, file1Consumer1Consumer1]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]); const changeFile1Consumer1ShapeRequest = makeSessionRequest(server.CommandNames.Change, { file: file1Consumer1.path, @@ -1867,7 +1897,7 @@ namespace ts { }); session.executeCommand(changeModuleFile1ShapeRequest1); session.executeCommand(changeFile1Consumer1ShapeRequest); - sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [moduleFile1, file1Consumer1, file1Consumer1Consumer1]); + sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]); }); it("should work fine for files with circular references", () => { @@ -1883,13 +1913,33 @@ namespace ts { /// export var t2 = 10;` }; - host = createServerHost([file1, file2, configFile]); - typingsInstaller = new TestTypingsInstaller("/a/data/", host); - session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const host = createServerHost([file1, file2, configFile]); + const typingsInstaller = new TestTypingsInstaller("/a/data/", host); + const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); openFilesForSession([file1, file2], session); const file1AffectedListRequest = makeSessionRequest(server.CommandNames.CompileOnSaveAffectedFileList, { file: file1.path }); - sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [file1, file2]); + sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [{ projectFileName: configFile.path, files: [file1, file2] }]); + }); + + 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 host = createServerHost([file1, file2, file3, configFile1, configFile2]); + const typingsInstaller = new TestTypingsInstaller("/a/data/", host); + const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + + openFilesForSession([file1, file2, file3], session); + const file1AffectedListRequest = makeSessionRequest(server.CommandNames.CompileOnSaveAffectedFileList, { file: file1.path }); + + sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [ + { projectFileName: configFile1.path, files: [file1, file2] }, + { projectFileName: configFile2.path, files: [file1, file3] } + ]); }); }); }); @@ -1904,16 +1954,16 @@ namespace ts { path: "/a/b/f2.ts", content: `import {Foo} from "./f1"; let y = Foo();` }; - const config = { + const configFile = { path: "/a/b/tsconfig.json", content: `{}` }; - const host = createServerHost([file1, file2, config, libFile]); + const host = createServerHost([file1, file2, configFile, libFile]); const typingsInstaller = new TestTypingsInstaller("/a/data/", host); const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); openFilesForSession([file1, file2], session); - const compileFileRequest = makeSessionRequest(server.CommandNames.CompileOnSaveEmitFile, { file: file1.path, projectFileName: config.path }); + const compileFileRequest = makeSessionRequest(server.CommandNames.CompileOnSaveEmitFile, { file: file1.path, projectFileName: configFile.path }); session.executeCommand(compileFileRequest); const expectedEmittedFileName = "/a/b/f1.js"; diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 4b2c69411ae..f72e7456cbe 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -732,6 +732,15 @@ declare namespace ts.server.protocol { export interface CompileOnSaveAffectedFileListRequest extends FileRequest { } + export interface CompileOnSaveAffectedFileListSingleProject { + projectFileName: string; + fileNames: string[]; + } + + export interface CompileOnSaveAffectedFileListResponse extends Response { + body: CompileOnSaveAffectedFileListSingleProject[]; + } + export interface CompileOnSaveEmitFileRequest extends FileRequest { args: CompileOnSaveEmitFileRequestArgs; } diff --git a/src/server/session.ts b/src/server/session.ts index b83ca2cfc4e..978cfe79a48 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -941,12 +941,18 @@ namespace ts.server { }, []); } - private getCompileOnSaveAffectedFileList(args: protocol.FileRequestArgs) { + private getCompileOnSaveAffectedFileList(args: protocol.FileRequestArgs): protocol.CompileOnSaveAffectedFileListSingleProject[] { const info = this.projectService.getScriptInfo(args.file); - let result: string[] = []; - for (const project of info.containingProjects) { + const result: protocol.CompileOnSaveAffectedFileListSingleProject[] = []; + + // if specified a project, we only return affected file list in this project + const projectsToSearch = args.projectFileName ? [this.projectService.findProject(args.projectFileName)] : info.containingProjects; + for (const project of projectsToSearch) { if (project.compileOnSaveEnabled) { - result = concatenate(result, project.getCompileOnSaveAffectedFileList(info)); + result.push({ + projectFileName: project.getProjectName(), + fileNames: project.getCompileOnSaveAffectedFileList(info) + }); } } return result;