Simplify event sent on background project update since its anyways just to update the error list

This commit is contained in:
Sheetal Nandi 2017-09-12 18:11:45 -07:00
parent b536f9dade
commit 4f7c0e5e1c
6 changed files with 139 additions and 229 deletions

View file

@ -35,8 +35,6 @@ namespace ts {
/** Emit the changed files and clear the cache of the changed files */ /** Emit the changed files and clear the cache of the changed files */
emitChangedFiles(program: Program): EmitOutputDetailed[]; emitChangedFiles(program: Program): EmitOutputDetailed[];
/** Get the changed files since last query and then clear the cache of changed files */
getChangedProgramFiles(program: Program): ChangedProgramFiles;
/** When called gets the semantic diagnostics for the program. It also caches the diagnostics and manage them */ /** 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): Diagnostic[];
@ -103,7 +101,6 @@ namespace ts {
getFilesAffectedBy, getFilesAffectedBy,
emitFile, emitFile,
emitChangedFiles, emitChangedFiles,
getChangedProgramFiles,
getSemanticDiagnostics, getSemanticDiagnostics,
clear clear
}; };
@ -273,30 +270,6 @@ namespace ts {
return diagnostics || emptyArray; return diagnostics || emptyArray;
} }
function getChangedProgramFiles(program: Program): ChangedProgramFiles {
ensureProgramGraph(program);
let filesToEmit: string[];
const changedFiles = createMap<string>();
enumerateChangedFilesEmitOutput(program, /*emitOnlyDtsFiles*/ true,
// All the changed files are required to get diagnostics
(changedFileName, changedFilePath) => addFileForDiagnostics(changedFileName, changedFilePath),
// Emitted file is for emit as well as diagnostic
(_emitOutput, sourceFile) => {
(filesToEmit || (filesToEmit = [])).push(sourceFile.fileName);
addFileForDiagnostics(sourceFile.fileName, sourceFile.path);
});
changedFileNames.clear();
return {
filesToEmit: filesToEmit || emptyArray,
changedFiles: arrayFrom(changedFiles.values())
};
function addFileForDiagnostics(fileName: string, path: Path) {
changedFiles.set(path, fileName);
}
}
function clear() { function clear() {
isModuleEmit = undefined; isModuleEmit = undefined;
emitHandler = undefined; emitHandler = undefined;

View file

@ -2158,7 +2158,7 @@ namespace ts.projectSystem {
const session = createSession(host, { const session = createSession(host, {
canUseEvents: true, canUseEvents: true,
eventHandler: e => { eventHandler: e => {
if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectChangedEvent || e.eventName === server.ProjectInfoTelemetryEvent) { if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectsUpdatedInBackgroundEvent || e.eventName === server.ProjectInfoTelemetryEvent) {
return; return;
} }
assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent); assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent);
@ -4819,7 +4819,7 @@ namespace ts.projectSystem {
}); });
}); });
describe("ProjectChangedEvent", () => { describe("ProjectsChangedInBackground", () => {
function verifyFiles(caption: string, actual: ReadonlyArray<string>, expected: ReadonlyArray<string>) { function verifyFiles(caption: string, actual: ReadonlyArray<string>, expected: ReadonlyArray<string>) {
assert.equal(actual.length, expected.length, `Incorrect number of ${caption}. Actual: ${actual} Expected: ${expected}`); assert.equal(actual.length, expected.length, `Incorrect number of ${caption}. Actual: ${actual} Expected: ${expected}`);
const seen = createMap<true>(); const seen = createMap<true>();
@ -4830,7 +4830,7 @@ namespace ts.projectSystem {
}); });
} }
function createVerifyInitialOpen(session: TestSession, verifyProjectChangedEventHandler: (events: server.ProjectChangedEvent[]) => void) { function createVerifyInitialOpen(session: TestSession, verifyProjectsUpdatedInBackgroundEventHandler: (events: server.ProjectsUpdatedInBackgroundEvent[]) => void) {
return (file: FileOrFolder) => { return (file: FileOrFolder) => {
session.executeCommandSeq(<protocol.OpenRequest>{ session.executeCommandSeq(<protocol.OpenRequest>{
command: server.CommandNames.Open, command: server.CommandNames.Open,
@ -4838,17 +4838,17 @@ namespace ts.projectSystem {
file: file.path file: file.path
} }
}); });
verifyProjectChangedEventHandler([]); verifyProjectsUpdatedInBackgroundEventHandler([]);
}; };
} }
interface ProjectChangeEventVerifier { interface ProjectsUpdatedInBackgroundEventVerifier {
session: TestSession; session: TestSession;
verifyProjectChangedEventHandler(events: server.ProjectChangedEvent[]): void; verifyProjectsUpdatedInBackgroundEventHandler(events: server.ProjectsUpdatedInBackgroundEvent[]): void;
verifyInitialOpen(file: FileOrFolder): void; verifyInitialOpen(file: FileOrFolder): void;
} }
function verifyProjectChangedEvent(createSession: (host: TestServerHost) => ProjectChangeEventVerifier) { function verifyProjectsUpdatedInBackgroundEvent(createSession: (host: TestServerHost) => ProjectsUpdatedInBackgroundEventVerifier) {
it("when adding new file", () => { it("when adding new file", () => {
const commonFile1: FileOrFolder = { const commonFile1: FileOrFolder = {
path: "/a/b/file1.ts", path: "/a/b/file1.ts",
@ -4866,32 +4866,26 @@ namespace ts.projectSystem {
path: "/a/b/tsconfig.json", path: "/a/b/tsconfig.json",
content: `{}` content: `{}`
}; };
const openFiles = [commonFile1.path];
const host = createServerHost([commonFile1, libFile, configFile]); const host = createServerHost([commonFile1, libFile, configFile]);
const { session, verifyProjectChangedEventHandler, verifyInitialOpen } = createSession(host, ); const { verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host, );
const projectService = session.getProjectService();
verifyInitialOpen(commonFile1); verifyInitialOpen(commonFile1);
host.reloadFS([commonFile1, libFile, configFile, commonFile2]); host.reloadFS([commonFile1, libFile, configFile, commonFile2]);
host.runQueuedTimeoutCallbacks(); host.runQueuedTimeoutCallbacks();
// Since this is first event verifyProjectsUpdatedInBackgroundEventHandler([{
const project = projectService.configuredProjects.get(configFile.path); eventName: server.ProjectsUpdatedInBackgroundEvent,
verifyProjectChangedEventHandler([{
eventName: server.ProjectChangedEvent,
data: { data: {
project, openFiles
changedFiles: [libFile.path, commonFile1.path, commonFile2.path],
filesToEmit: [commonFile1.path, commonFile2.path]
} }
}]); }]);
host.reloadFS([commonFile1, commonFile2, libFile, configFile, commonFile3]); host.reloadFS([commonFile1, commonFile2, libFile, configFile, commonFile3]);
host.runQueuedTimeoutCallbacks(); host.runQueuedTimeoutCallbacks();
verifyProjectChangedEventHandler([{ verifyProjectsUpdatedInBackgroundEventHandler([{
eventName: server.ProjectChangedEvent, eventName: server.ProjectsUpdatedInBackgroundEvent,
data: { data: {
project, openFiles
changedFiles: [commonFile3.path],
filesToEmit: [commonFile3.path]
} }
}]); }]);
}); });
@ -4914,36 +4908,30 @@ namespace ts.projectSystem {
content: "export let y = 1" content: "export let y = 1"
}; };
const openFiles = [f1.path];
const files = [f1, config, libFile]; const files = [f1, config, libFile];
const host = createServerHost(files); const host = createServerHost(files);
const { session, verifyInitialOpen, verifyProjectChangedEventHandler } = createSession(host); const { verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host);
const projectService = session.getProjectService();
verifyInitialOpen(f1); verifyInitialOpen(f1);
files.push(f2); files.push(f2);
host.reloadFS(files); host.reloadFS(files);
host.runQueuedTimeoutCallbacks(); host.runQueuedTimeoutCallbacks();
// Since this is first event verifyProjectsUpdatedInBackgroundEventHandler([{
const project = projectService.configuredProjects.get(config.path); eventName: server.ProjectsUpdatedInBackgroundEvent,
verifyProjectChangedEventHandler([{
eventName: server.ProjectChangedEvent,
data: { data: {
project, openFiles
changedFiles: [libFile.path, f1.path, f2.path],
filesToEmit: [f1.path, f2.path]
} }
}]); }]);
f2.content = "export let x = 11"; f2.content = "export let x = 11";
host.reloadFS(files); host.reloadFS(files);
host.runQueuedTimeoutCallbacks(); host.runQueuedTimeoutCallbacks();
verifyProjectChangedEventHandler([{ verifyProjectsUpdatedInBackgroundEventHandler([{
eventName: server.ProjectChangedEvent, eventName: server.ProjectsUpdatedInBackgroundEvent,
data: { data: {
project, openFiles
changedFiles: [f2.path],
filesToEmit: [f2.path]
} }
}]); }]);
} }
@ -4970,14 +4958,12 @@ namespace ts.projectSystem {
interface InitialStateParams { interface InitialStateParams {
/** custom config file options */ /** custom config file options */
configObj?: any; configObj?: any;
/** list of files emitted/changed on first update graph */
firstCompilationEmitFiles?: string[];
/** Additional files and folders to add */ /** Additional files and folders to add */
getAdditionalFileOrFolder?(): FileOrFolder[]; getAdditionalFileOrFolder?(): FileOrFolder[];
/** initial list of files to reload in fs and first file in this list being the file to open */ /** initial list of files to reload in fs and first file in this list being the file to open */
firstReloadFileList?: string[]; firstReloadFileList?: string[];
} }
function getInitialState({ configObj = {}, getAdditionalFileOrFolder, firstReloadFileList, firstCompilationEmitFiles }: InitialStateParams = {}) { function getInitialState({ configObj = {}, getAdditionalFileOrFolder, firstReloadFileList }: InitialStateParams = {}) {
const moduleFile1: FileOrFolder = { const moduleFile1: FileOrFolder = {
path: moduleFile1Path, path: moduleFile1Path,
content: "export function Foo() { };", content: "export function Foo() { };",
@ -5015,20 +5001,19 @@ namespace ts.projectSystem {
const host = createServerHost([filesToReload[0], configFile]); const host = createServerHost([filesToReload[0], configFile]);
// Initial project creation // Initial project creation
const { session, verifyProjectChangedEventHandler, verifyInitialOpen } = createSession(host); const { session, verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host);
const projectService = session.getProjectService(); const openFiles = [filesToReload[0].path];
verifyInitialOpen(filesToReload[0]); verifyInitialOpen(filesToReload[0]);
// Since this is first event, it will have all the files // Since this is first event, it will have all the files
const firstFilesExpected = firstCompilationEmitFiles && getFiles(firstCompilationEmitFiles) || filesToReload; verifyProjectsUpdatedInBackgroundEvent(filesToReload);
verifyProjectChangedEvent(firstFilesExpected, filesToReload);
return { return {
moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile, moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile,
files, files,
updateContentOfOpenFile, updateContentOfOpenFile,
verifyProjectChangedEvent, verifyNoProjectsUpdatedInBackgroundEvent,
verifyAffectedAllFiles, verifyProjectsUpdatedInBackgroundEvent
}; };
function getFiles(filelist: string[]) { function getFiles(filelist: string[]) {
@ -5039,28 +5024,21 @@ namespace ts.projectSystem {
return find(files, file => file.path === fileName); return find(files, file => file.path === fileName);
} }
function verifyAffectedAllFiles() { function verifyNoProjectsUpdatedInBackgroundEvent(filesToReload?: FileOrFolder[]) {
verifyProjectChangedEvent(filter(files, f => f !== libFile));
}
function verifyProjectChangedEvent(filesToEmit: FileOrFolder[], filesToReload?: FileOrFolder[], additionalChangedFiles?: FileOrFolder[]) {
host.reloadFS(filesToReload || files); host.reloadFS(filesToReload || files);
host.runQueuedTimeoutCallbacks(); host.runQueuedTimeoutCallbacks();
if (filesToEmit.length) { verifyProjectsUpdatedInBackgroundEventHandler([]);
const project = projectService.configuredProjects.get(configFile.path); }
const changedFiles = mapDefined(additionalChangedFiles ? filesToEmit.concat(additionalChangedFiles) : filesToEmit, f => f !== configFile ? f.path : undefined);
verifyProjectChangedEventHandler([{ function verifyProjectsUpdatedInBackgroundEvent(filesToReload?: FileOrFolder[]) {
eventName: server.ProjectChangedEvent, host.reloadFS(filesToReload || files);
data: { host.runQueuedTimeoutCallbacks();
project, verifyProjectsUpdatedInBackgroundEventHandler([{
changedFiles, eventName: server.ProjectsUpdatedInBackgroundEvent,
filesToEmit: mapDefined(filesToEmit, f => f !== libFile && f !== configFile ? f.path : undefined) data: {
} openFiles
}]); }
} }]);
else {
verifyProjectChangedEventHandler([]);
}
} }
function updateContentOfOpenFile(file: FileOrFolder, newContent: string) { function updateContentOfOpenFile(file: FileOrFolder, newContent: string) {
@ -5080,35 +5058,35 @@ namespace ts.projectSystem {
} }
it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => {
const { moduleFile1, file1Consumer1, file1Consumer2, verifyProjectChangedEvent } = getInitialState(); const { moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState();
// Change the content of moduleFile1 to `export var T: number;export function Foo() { };` // Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
moduleFile1.content = `export var T: number;export function Foo() { };`; moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyProjectChangedEvent([moduleFile1, file1Consumer1, file1Consumer2]); verifyProjectsUpdatedInBackgroundEvent();
// Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };` // Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };`
moduleFile1.content = `export var T: number;export function Foo() { console.log('hi'); };`; moduleFile1.content = `export var T: number;export function Foo() { console.log('hi'); };`;
verifyProjectChangedEvent([moduleFile1]); verifyProjectsUpdatedInBackgroundEvent();
}); });
it("should be up-to-date with the reference map changes", () => { it("should be up-to-date with the reference map changes", () => {
const { moduleFile1, file1Consumer1, file1Consumer2, updateContentOfOpenFile, verifyProjectChangedEvent } = getInitialState(); const { moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyProjectsUpdatedInBackgroundEvent, verifyNoProjectsUpdatedInBackgroundEvent } = getInitialState();
// Change file1Consumer1 content to `export let y = Foo();` // Change file1Consumer1 content to `export let y = Foo();`
updateContentOfOpenFile(file1Consumer1, "export let y = Foo();"); updateContentOfOpenFile(file1Consumer1, "export let y = Foo();");
verifyProjectChangedEvent([]); verifyNoProjectsUpdatedInBackgroundEvent();
// Change the content of moduleFile1 to `export var T: number;export function Foo() { };` // Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
moduleFile1.content = `export var T: number;export function Foo() { };`; moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyProjectChangedEvent([moduleFile1, file1Consumer2, file1Consumer1]); verifyProjectsUpdatedInBackgroundEvent();
// Add the import statements back to file1Consumer1 // Add the import statements back to file1Consumer1
updateContentOfOpenFile(file1Consumer1, `import {Foo} from "./moduleFile1";let y = Foo();`); updateContentOfOpenFile(file1Consumer1, `import {Foo} from "./moduleFile1";let y = Foo();`);
verifyProjectChangedEvent([]); verifyNoProjectsUpdatedInBackgroundEvent();
// Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };` // Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };`
moduleFile1.content = `export var T: number;export var T2: string;export function Foo() { };`; moduleFile1.content = `export var T: number;export var T2: string;export function Foo() { };`;
verifyProjectChangedEvent([moduleFile1, file1Consumer2, file1Consumer1]); verifyProjectsUpdatedInBackgroundEvent();
// Multiple file edits in one go: // Multiple file edits in one go:
@ -5116,69 +5094,68 @@ namespace ts.projectSystem {
// Change the content of moduleFile1 to `export var T: number;export function Foo() { };` // Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
updateContentOfOpenFile(file1Consumer1, `export let y = Foo();`); updateContentOfOpenFile(file1Consumer1, `export let y = Foo();`);
moduleFile1.content = `export var T: number;export function Foo() { };`; moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyProjectChangedEvent([moduleFile1, file1Consumer1, file1Consumer2]); verifyProjectsUpdatedInBackgroundEvent();
}); });
it("should be up-to-date with deleted files", () => { it("should be up-to-date with deleted files", () => {
const { moduleFile1, file1Consumer1, file1Consumer2, files, verifyProjectChangedEvent } = getInitialState(); const { moduleFile1, file1Consumer2, files, verifyProjectsUpdatedInBackgroundEvent } = getInitialState();
// Change the content of moduleFile1 to `export var T: number;export function Foo() { };` // Change the content of moduleFile1 to `export var T: number;export function Foo() { };`
moduleFile1.content = `export var T: number;export function Foo() { };`; moduleFile1.content = `export var T: number;export function Foo() { };`;
// Delete file1Consumer2 // Delete file1Consumer2
const filesToLoad = filter(files, file => file !== file1Consumer2); const filesToLoad = filter(files, file => file !== file1Consumer2);
verifyProjectChangedEvent([moduleFile1, file1Consumer1], filesToLoad, [file1Consumer2]); verifyProjectsUpdatedInBackgroundEvent(filesToLoad);
}); });
it("should be up-to-date with newly created files", () => { it("should be up-to-date with newly created files", () => {
const { moduleFile1, file1Consumer1, file1Consumer2, files, verifyProjectChangedEvent, } = getInitialState(); const { moduleFile1, files, verifyProjectsUpdatedInBackgroundEvent, } = getInitialState();
const file1Consumer3: FileOrFolder = { const file1Consumer3: FileOrFolder = {
path: "/a/b/file1Consumer3.ts", path: "/a/b/file1Consumer3.ts",
content: `import {Foo} from "./moduleFile1"; let y = Foo();` content: `import {Foo} from "./moduleFile1"; let y = Foo();`
}; };
moduleFile1.content = `export var T: number;export function Foo() { };`; moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyProjectChangedEvent([moduleFile1, file1Consumer1, file1Consumer3, file1Consumer2], files.concat(file1Consumer3)); verifyProjectsUpdatedInBackgroundEvent(files.concat(file1Consumer3));
}); });
it("should detect changes in non-root files", () => { it("should detect changes in non-root files", () => {
const { moduleFile1, file1Consumer1, verifyProjectChangedEvent } = getInitialState({ const { moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
configObj: { files: [file1Consumer1Path] }, configObj: { files: [file1Consumer1Path] },
firstCompilationEmitFiles: [file1Consumer1Path, moduleFile1Path, libFile.path]
}); });
moduleFile1.content = `export var T: number;export function Foo() { };`; moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyProjectChangedEvent([moduleFile1, file1Consumer1]); verifyProjectsUpdatedInBackgroundEvent();
// change file1 internal, and verify only file1 is affected // change file1 internal, and verify only file1 is affected
moduleFile1.content += "var T1: number;"; moduleFile1.content += "var T1: number;";
verifyProjectChangedEvent([moduleFile1]); verifyProjectsUpdatedInBackgroundEvent();
}); });
it("should return all files if a global file changed shape", () => { it("should return all files if a global file changed shape", () => {
const { globalFile3, verifyAffectedAllFiles } = getInitialState(); const { globalFile3, verifyProjectsUpdatedInBackgroundEvent } = getInitialState();
globalFile3.content += "var T2: string;"; globalFile3.content += "var T2: string;";
verifyAffectedAllFiles(); verifyProjectsUpdatedInBackgroundEvent();
}); });
it("should always return the file itself if '--isolatedModules' is specified", () => { it("should always return the file itself if '--isolatedModules' is specified", () => {
const { moduleFile1, verifyProjectChangedEvent } = getInitialState({ const { moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
configObj: { compilerOptions: { isolatedModules: true } } configObj: { compilerOptions: { isolatedModules: true } }
}); });
moduleFile1.content = `export var T: number;export function Foo() { };`; moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyProjectChangedEvent([moduleFile1]); verifyProjectsUpdatedInBackgroundEvent();
}); });
it("should always return the file itself if '--out' or '--outFile' is specified", () => { it("should always return the file itself if '--out' or '--outFile' is specified", () => {
const outFilePath = "/a/b/out.js"; const outFilePath = "/a/b/out.js";
const { moduleFile1, verifyProjectChangedEvent } = getInitialState({ const { moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
configObj: { compilerOptions: { module: "system", outFile: outFilePath } } configObj: { compilerOptions: { module: "system", outFile: outFilePath } }
}); });
moduleFile1.content = `export var T: number;export function Foo() { };`; moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyProjectChangedEvent([moduleFile1]); verifyProjectsUpdatedInBackgroundEvent();
}); });
it("should return cascaded affected file list", () => { it("should return cascaded affected file list", () => {
@ -5186,21 +5163,21 @@ namespace ts.projectSystem {
path: "/a/b/file1Consumer1Consumer1.ts", path: "/a/b/file1Consumer1Consumer1.ts",
content: `import {y} from "./file1Consumer1";` content: `import {y} from "./file1Consumer1";`
}; };
const { moduleFile1, file1Consumer1, file1Consumer2, updateContentOfOpenFile, verifyProjectChangedEvent } = getInitialState({ const { moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
getAdditionalFileOrFolder: () => [file1Consumer1Consumer1] getAdditionalFileOrFolder: () => [file1Consumer1Consumer1]
}); });
updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T: number;"); updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T: number;");
verifyProjectChangedEvent([]); verifyNoProjectsUpdatedInBackgroundEvent();
// Doesnt change the shape of file1Consumer1 // Doesnt change the shape of file1Consumer1
moduleFile1.content = `export var T: number;export function Foo() { };`; moduleFile1.content = `export var T: number;export function Foo() { };`;
verifyProjectChangedEvent([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer1Consumer1]); verifyProjectsUpdatedInBackgroundEvent();
// Change both files before the timeout // Change both files before the timeout
updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T2: number;"); updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T2: number;");
moduleFile1.content = `export var T2: number;export function Foo() { };`; moduleFile1.content = `export var T2: number;export function Foo() { };`;
verifyProjectChangedEvent([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer1Consumer1]); verifyProjectsUpdatedInBackgroundEvent();
}); });
it("should work fine for files with circular references", () => { it("should work fine for files with circular references", () => {
@ -5216,13 +5193,13 @@ namespace ts.projectSystem {
/// <reference path="./file1.ts" /> /// <reference path="./file1.ts" />
export var t2 = 10;` export var t2 = 10;`
}; };
const { configFile, verifyProjectChangedEvent } = getInitialState({ const { configFile, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
getAdditionalFileOrFolder: () => [file1, file2], getAdditionalFileOrFolder: () => [file1, file2],
firstReloadFileList: [file1.path, libFile.path, file2.path, configFilePath] firstReloadFileList: [file1.path, libFile.path, file2.path, configFilePath]
}); });
file2.content += "export var t3 = 10;"; file2.content += "export var t3 = 10;";
verifyProjectChangedEvent([file1, file2], [file1, file2, libFile, configFile]); verifyProjectsUpdatedInBackgroundEvent([file1, file2, libFile, configFile]);
}); });
it("should detect removed code file", () => { it("should detect removed code file", () => {
@ -5232,12 +5209,12 @@ namespace ts.projectSystem {
/// <reference path="./moduleFile1.ts" /> /// <reference path="./moduleFile1.ts" />
export var x = Foo();` export var x = Foo();`
}; };
const { configFile, verifyProjectChangedEvent, moduleFile1 } = getInitialState({ const { configFile, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
getAdditionalFileOrFolder: () => [referenceFile1], getAdditionalFileOrFolder: () => [referenceFile1],
firstReloadFileList: [referenceFile1.path, libFile.path, moduleFile1Path, configFilePath] firstReloadFileList: [referenceFile1.path, libFile.path, moduleFile1Path, configFilePath]
}); });
verifyProjectChangedEvent([referenceFile1], [libFile, referenceFile1, configFile], [moduleFile1]); verifyProjectsUpdatedInBackgroundEvent([libFile, referenceFile1, configFile]);
}); });
it("should detect non-existing code file", () => { it("should detect non-existing code file", () => {
@ -5247,16 +5224,16 @@ namespace ts.projectSystem {
/// <reference path="./moduleFile2.ts" /> /// <reference path="./moduleFile2.ts" />
export var x = Foo();` export var x = Foo();`
}; };
const { configFile, moduleFile2, updateContentOfOpenFile, verifyProjectChangedEvent } = getInitialState({ const { configFile, moduleFile2, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({
getAdditionalFileOrFolder: () => [referenceFile1], getAdditionalFileOrFolder: () => [referenceFile1],
firstReloadFileList: [referenceFile1.path, libFile.path, configFilePath] firstReloadFileList: [referenceFile1.path, libFile.path, configFilePath]
}); });
updateContentOfOpenFile(referenceFile1, referenceFile1.content + "export var yy = Foo();"); updateContentOfOpenFile(referenceFile1, referenceFile1.content + "export var yy = Foo();");
verifyProjectChangedEvent([], [libFile, referenceFile1, configFile]); verifyNoProjectsUpdatedInBackgroundEvent([libFile, referenceFile1, configFile]);
// Create module File2 and see both files are saved // Create module File2 and see both files are saved
verifyProjectChangedEvent([referenceFile1, moduleFile2], [libFile, moduleFile2, referenceFile1, configFile]); verifyProjectsUpdatedInBackgroundEvent([libFile, moduleFile2, referenceFile1, configFile]);
}); });
}); });
@ -5280,9 +5257,10 @@ namespace ts.projectSystem {
}; };
const projectFiles = [file1, file3, libFile, configFile]; const projectFiles = [file1, file3, libFile, configFile];
const openFiles = [file1.path];
const watchedRecursiveDirectories = ["/a/b/project", "/a/b/node_modules", "/a/node_modules", "/node_modules"]; const watchedRecursiveDirectories = ["/a/b/project", "/a/b/node_modules", "/a/node_modules", "/node_modules"];
const host = createServerHost(projectFiles); const host = createServerHost(projectFiles);
const { session, verifyInitialOpen, verifyProjectChangedEventHandler } = createSession(host); const { session, verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host);
const projectService = session.getProjectService(); const projectService = session.getProjectService();
verifyInitialOpen(file1); verifyInitialOpen(file1);
checkNumberOfProjects(projectService, { configuredProjects: 1 }); checkNumberOfProjects(projectService, { configuredProjects: 1 });
@ -5298,12 +5276,10 @@ namespace ts.projectSystem {
// Since this is first event // Since this is first event
verifyProject(); verifyProject();
verifyProjectChangedEventHandler([{ verifyProjectsUpdatedInBackgroundEventHandler([{
eventName: server.ProjectChangedEvent, eventName: server.ProjectsUpdatedInBackgroundEvent,
data: { data: {
project, openFiles
changedFiles: [libFile.path, file1.path, file3.path],
filesToEmit: [file1.path, file3.path]
} }
}]); }]);
@ -5313,13 +5289,10 @@ namespace ts.projectSystem {
watchedRecursiveDirectories.length = 2; watchedRecursiveDirectories.length = 2;
verifyProject(); verifyProject();
const changedFiles = [file1.path, file2.path]; verifyProjectsUpdatedInBackgroundEventHandler([{
verifyProjectChangedEventHandler([{ eventName: server.ProjectsUpdatedInBackgroundEvent,
eventName: server.ProjectChangedEvent,
data: { data: {
project, openFiles
changedFiles,
filesToEmit: changedFiles
} }
}]); }]);
@ -5341,13 +5314,13 @@ namespace ts.projectSystem {
} }
describe("when event handler is set in the session", () => { describe("when event handler is set in the session", () => {
verifyProjectChangedEvent(createSessionWithProjectChangedEventHandler); verifyProjectsUpdatedInBackgroundEvent(createSessionWithProjectChangedEventHandler);
function createSessionWithProjectChangedEventHandler(host: TestServerHost): ProjectChangeEventVerifier { function createSessionWithProjectChangedEventHandler(host: TestServerHost): ProjectsUpdatedInBackgroundEventVerifier {
const projectChangedEvents: server.ProjectChangedEvent[] = []; const projectChangedEvents: server.ProjectsUpdatedInBackgroundEvent[] = [];
const session = createSession(host, { const session = createSession(host, {
eventHandler: e => { eventHandler: e => {
if (e.eventName === server.ProjectChangedEvent) { if (e.eventName === server.ProjectsUpdatedInBackgroundEvent) {
projectChangedEvents.push(e); projectChangedEvents.push(e);
} }
} }
@ -5355,34 +5328,24 @@ namespace ts.projectSystem {
return { return {
session, session,
verifyProjectChangedEventHandler, verifyProjectsUpdatedInBackgroundEventHandler,
verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectChangedEventHandler) verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler)
}; };
function eventToString(event: server.ProjectChangedEvent) { function eventToString(event: server.ProjectsUpdatedInBackgroundEvent) {
const eventToModify = event && { return JSON.stringify(event && { eventName: event.eventName, data: event.data });
eventName: event.eventName,
data: {
project: event.data.project.getProjectName(),
changedFiles: event.data.changedFiles,
filesToEmit: event.data.filesToEmit
}
};
return JSON.stringify(eventToModify);
} }
function eventsToString(events: ReadonlyArray<server.ProjectChangedEvent>) { function eventsToString(events: ReadonlyArray<server.ProjectsUpdatedInBackgroundEvent>) {
return "[" + map(events, eventToString).join(",") + "]"; return "[" + map(events, eventToString).join(",") + "]";
} }
function verifyProjectChangedEventHandler(expectedEvents: ReadonlyArray<server.ProjectChangedEvent>) { function verifyProjectsUpdatedInBackgroundEventHandler(expectedEvents: ReadonlyArray<server.ProjectsUpdatedInBackgroundEvent>) {
assert.equal(projectChangedEvents.length, expectedEvents.length, `Incorrect number of events Actual: ${eventsToString(projectChangedEvents)} Expected: ${eventsToString(expectedEvents)}`); assert.equal(projectChangedEvents.length, expectedEvents.length, `Incorrect number of events Actual: ${eventsToString(projectChangedEvents)} Expected: ${eventsToString(expectedEvents)}`);
forEach(projectChangedEvents, (actualEvent, i) => { forEach(projectChangedEvents, (actualEvent, i) => {
const expectedEvent = expectedEvents[i]; const expectedEvent = expectedEvents[i];
assert.strictEqual(actualEvent.eventName, expectedEvent.eventName); assert.strictEqual(actualEvent.eventName, expectedEvent.eventName);
assert.strictEqual(actualEvent.data.project, expectedEvent.data.project); verifyFiles("openFiles", actualEvent.data.openFiles, expectedEvent.data.openFiles);
verifyFiles("changedFiles", actualEvent.data.changedFiles, expectedEvent.data.changedFiles);
verifyFiles("filesToEmit", actualEvent.data.filesToEmit, expectedEvent.data.filesToEmit);
}); });
// Verified the events, reset them // Verified the events, reset them
@ -5392,41 +5355,37 @@ namespace ts.projectSystem {
}); });
describe("when event handler is not set but session is created with canUseEvents = true", () => { describe("when event handler is not set but session is created with canUseEvents = true", () => {
verifyProjectChangedEvent(createSessionThatUsesEvents); verifyProjectsUpdatedInBackgroundEvent(createSessionThatUsesEvents);
function createSessionThatUsesEvents(host: TestServerHost): ProjectChangeEventVerifier { function createSessionThatUsesEvents(host: TestServerHost): ProjectsUpdatedInBackgroundEventVerifier {
const session = createSession(host, { canUseEvents: true }); const session = createSession(host, { canUseEvents: true });
return { return {
session, session,
verifyProjectChangedEventHandler, verifyProjectsUpdatedInBackgroundEventHandler,
verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectChangedEventHandler) verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler)
}; };
function verifyProjectChangedEventHandler(expected: ReadonlyArray<server.ProjectChangedEvent>) { function verifyProjectsUpdatedInBackgroundEventHandler(expected: ReadonlyArray<server.ProjectsUpdatedInBackgroundEvent>) {
const expectedEvents: protocol.ProjectChangedEventBody[] = map(expected, e => { const expectedEvents: protocol.ProjectsUpdatedInBackgroundEventBody[] = map(expected, e => {
return { return {
projectName: e.data.project.getProjectName(), openFiles: e.data.openFiles
changedFiles: e.data.changedFiles,
fileNamesToEmit: e.data.filesToEmit
}; };
}); });
const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/; const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/;
const events: protocol.ProjectStructureChangedEvent[] = filter( const events: protocol.ProjectsUpdatedInBackgroundEvent[] = filter(
map( map(
host.getOutput(), s => convertToObject( host.getOutput(), s => convertToObject(
ts.parseJsonText("json.json", s.replace(outputEventRegex, "")), ts.parseJsonText("json.json", s.replace(outputEventRegex, "")),
[] []
) )
), ),
e => e.event === server.ProjectChangedEvent e => e.event === server.ProjectsUpdatedInBackgroundEvent
); );
assert.equal(events.length, expectedEvents.length, `Incorrect number of events Actual: ${map(events, e => e.body)} Expected: ${expectedEvents}`); assert.equal(events.length, expectedEvents.length, `Incorrect number of events Actual: ${map(events, e => e.body)} Expected: ${expectedEvents}`);
forEach(events, (actualEvent, i) => { forEach(events, (actualEvent, i) => {
const expectedEvent = expectedEvents[i]; const expectedEvent = expectedEvents[i];
assert.strictEqual(actualEvent.body.projectName, expectedEvent.projectName); verifyFiles("openFiles", actualEvent.body.openFiles, expectedEvent.openFiles);
verifyFiles("changedFiles", actualEvent.body.changedFiles, expectedEvent.changedFiles);
verifyFiles("fileNamesToEmit", actualEvent.body.fileNamesToEmit, expectedEvent.fileNamesToEmit);
}); });
// Verified the events, reset them // Verified the events, reset them

View file

@ -9,14 +9,14 @@
namespace ts.server { namespace ts.server {
export const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024; export const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024;
export const ProjectChangedEvent = "projectChanged"; export const ProjectsUpdatedInBackgroundEvent = "projectsUpdatedInBackground";
export const ConfigFileDiagEvent = "configFileDiag"; export const ConfigFileDiagEvent = "configFileDiag";
export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState"; export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState";
export const ProjectInfoTelemetryEvent = "projectInfo"; export const ProjectInfoTelemetryEvent = "projectInfo";
export interface ProjectChangedEvent { export interface ProjectsUpdatedInBackgroundEvent {
eventName: typeof ProjectChangedEvent; eventName: typeof ProjectsUpdatedInBackgroundEvent;
data: { project: Project; filesToEmit: string[]; changedFiles: string[] }; data: { openFiles: string[]; };
} }
export interface ConfigFileDiagEvent { export interface ConfigFileDiagEvent {
@ -76,7 +76,7 @@ namespace ts.server {
readonly dts: number; readonly dts: number;
} }
export type ProjectServiceEvent = ProjectChangedEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent; export type ProjectServiceEvent = ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent;
export interface ProjectServiceEventHandler { export interface ProjectServiceEventHandler {
(event: ProjectServiceEvent): void; (event: ProjectServiceEvent): void;
@ -517,9 +517,14 @@ namespace ts.server {
if (this.pendingProjectUpdates.size !== 0) { if (this.pendingProjectUpdates.size !== 0) {
this.delayInferredProjectsRefresh(); this.delayInferredProjectsRefresh();
} }
else if (this.pendingInferredProjectUpdate) { else {
this.pendingInferredProjectUpdate = false; if (this.pendingInferredProjectUpdate) {
this.refreshInferredProjects(); this.pendingInferredProjectUpdate = false;
this.refreshInferredProjects();
}
// Send the event to notify that there were background project updates
// send current list of open files
this.sendProjectsUpdatedInBackgroundEvent();
} }
}); });
} }
@ -531,27 +536,18 @@ namespace ts.server {
if (this.pendingProjectUpdates.delete(projectName)) { if (this.pendingProjectUpdates.delete(projectName)) {
project.updateGraph(); project.updateGraph();
} }
// Send the update event to notify about the project changes
this.sendProjectChangedEvent(project);
}); });
} }
private sendProjectChangedEvent(project: Project) { private sendProjectsUpdatedInBackgroundEvent() {
if (project.isClosed() || !this.eventHandler || !project.languageServiceEnabled) { if (!this.eventHandler) {
return; return;
} }
const { filesToEmit, changedFiles } = project.getChangedFiles(); const event: ProjectsUpdatedInBackgroundEvent = {
if (changedFiles.length === 0) { eventName: ProjectsUpdatedInBackgroundEvent,
return;
}
const event: ProjectChangedEvent = {
eventName: ProjectChangedEvent,
data: { data: {
project, openFiles: this.openFiles.map(f => f.fileName)
filesToEmit: filesToEmit as string[],
changedFiles: changedFiles as string[]
} }
}; };
this.eventHandler(event); this.eventHandler(event);

View file

@ -457,12 +457,6 @@ namespace ts.server {
return !emitSkipped; return !emitSkipped;
} }
getChangedFiles() {
Debug.assert(this.languageServiceEnabled);
this.ensureBuilder();
return this.builder.getChangedProgramFiles(this.program);
}
enableLanguageService() { enableLanguageService() {
if (this.languageServiceEnabled) { if (this.languageServiceEnabled) {
return; return;

View file

@ -2040,27 +2040,17 @@ namespace ts.server.protocol {
languageServiceEnabled: boolean; languageServiceEnabled: boolean;
} }
export type ProjectChangedEventName = "projectChanged"; export type ProjectsUpdatedInBackgroundEventName = "projectsUpdatedInBackground";
export interface ProjectStructureChangedEvent extends Event { export interface ProjectsUpdatedInBackgroundEvent extends Event {
event: ProjectChangedEventName; event: ProjectsUpdatedInBackgroundEventName;
body: ProjectChangedEventBody; body: ProjectsUpdatedInBackgroundEventBody;
} }
export interface ProjectChangedEventBody { export interface ProjectsUpdatedInBackgroundEventBody {
/** /**
* Project name that has changes * Current set of open files
*/ */
projectName: string; openFiles: string[];
/**
* Minimum set of file names to emit
*/
fileNamesToEmit: string[];
/**
* List of files that have changed/added/removed or could have been affected by the changed files
*/
changedFiles: string[];
} }
/** /**

View file

@ -332,9 +332,9 @@ namespace ts.server {
private defaultEventHandler(event: ProjectServiceEvent) { private defaultEventHandler(event: ProjectServiceEvent) {
switch (event.eventName) { switch (event.eventName) {
case ProjectChangedEvent: case ProjectsUpdatedInBackgroundEvent:
const { project, filesToEmit, changedFiles } = event.data; const { openFiles } = event.data;
this.projectChangedEvent(project, filesToEmit, changedFiles); this.projectsUpdatedInBackgroundEvent(openFiles);
break; break;
case ConfigFileDiagEvent: case ConfigFileDiagEvent:
const { triggerFile, configFileName: configFile, diagnostics } = event.data; const { triggerFile, configFileName: configFile, diagnostics } = event.data;
@ -364,21 +364,19 @@ namespace ts.server {
} }
} }
private projectChangedEvent(project: Project, fileNamesToEmit: string[], changedFiles: string[]): void { private projectsUpdatedInBackgroundEvent(openFiles: string[]): void {
this.projectService.logger.info(`got project changed event, updating diagnostics for ${changedFiles}`); this.projectService.logger.info(`got projects updated in background, updating diagnostics for ${openFiles}`);
if (changedFiles.length) { if (openFiles.length) {
const checkList = this.createCheckList(changedFiles, project); const checkList = this.createCheckList(openFiles);
// For now only queue error checking for open files. We can change this to include non open files as well // For now only queue error checking for open files. We can change this to include non open files as well
this.errorCheck.startNew(next => this.updateErrorCheck(next, checkList, 100, /*requireOpen*/ true)); this.errorCheck.startNew(next => this.updateErrorCheck(next, checkList, 100, /*requireOpen*/ true));
// Send project changed event // Send project changed event
this.event<protocol.ProjectChangedEventBody>({ this.event<protocol.ProjectsUpdatedInBackgroundEventBody>({
projectName: project.getProjectName(), openFiles
changedFiles, }, "projectsUpdatedInBackground");
fileNamesToEmit
}, "projectChanged");
} }
} }