mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-06 20:14:01 -06:00
Handle error reporting of files when new file is created after its opened in editor (#36271)
* If script info is not attached to the project on which wild card is invoked, update it. * Instead of getting default project before starting error list timer, get it at that time if no project is specified Fixes #35794 * Fix the open File watch triggered setting
This commit is contained in:
parent
8e0b091795
commit
096e1b12e4
@ -1110,7 +1110,14 @@ namespace ts.server {
|
||||
// don't trigger callback on open, existing files
|
||||
if (project.fileIsOpen(fileOrDirectoryPath)) {
|
||||
if (project.pendingReload !== ConfigFileProgramReloadLevel.Full) {
|
||||
project.openFileWatchTriggered.set(fileOrDirectoryPath, true);
|
||||
const info = Debug.assertDefined(this.getScriptInfoForPath(fileOrDirectoryPath));
|
||||
if (info.isAttached(project)) {
|
||||
project.openFileWatchTriggered.set(fileOrDirectoryPath, true);
|
||||
}
|
||||
else {
|
||||
project.pendingReload = ConfigFileProgramReloadLevel.Partial;
|
||||
this.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -719,10 +719,8 @@ namespace ts.server {
|
||||
this.projectService.logger.info(`got projects updated in background, updating diagnostics for ${openFiles}`);
|
||||
if (openFiles.length) {
|
||||
if (!this.suppressDiagnosticEvents && !this.noGetErrOnBackgroundUpdate) {
|
||||
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
|
||||
this.errorCheck.startNew(next => this.updateErrorCheck(next, checkList, 100, /*requireOpen*/ true));
|
||||
this.errorCheck.startNew(next => this.updateErrorCheck(next, openFiles, 100, /*requireOpen*/ true));
|
||||
}
|
||||
|
||||
// Send project changed event
|
||||
@ -870,20 +868,37 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
/** It is the caller's responsibility to verify that `!this.suppressDiagnosticEvents`. */
|
||||
private updateErrorCheck(next: NextStep, checkList: PendingErrorCheck[], ms: number, requireOpen = true) {
|
||||
private updateErrorCheck(next: NextStep, checkList: readonly string[] | readonly PendingErrorCheck[], ms: number, requireOpen = true) {
|
||||
Debug.assert(!this.suppressDiagnosticEvents); // Caller's responsibility
|
||||
|
||||
const seq = this.changeSeq;
|
||||
const followMs = Math.min(ms, 200);
|
||||
|
||||
let index = 0;
|
||||
const goNext = () => {
|
||||
index++;
|
||||
if (checkList.length > index) {
|
||||
next.delay(followMs, checkOne);
|
||||
}
|
||||
};
|
||||
const checkOne = () => {
|
||||
if (this.changeSeq !== seq) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { fileName, project } = checkList[index];
|
||||
index++;
|
||||
let item: string | PendingErrorCheck | undefined = checkList[index];
|
||||
if (isString(item)) {
|
||||
// Find out project for the file name
|
||||
item = this.toPendingErrorCheck(item);
|
||||
if (!item) {
|
||||
// Ignore file if there is no project for the file
|
||||
goNext();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const { fileName, project } = item;
|
||||
|
||||
// Ensure the project is upto date before checking if this file is present in the project
|
||||
updateProjectIfDirty(project);
|
||||
if (!project.containsFile(fileName, requireOpen)) {
|
||||
@ -901,11 +916,6 @@ namespace ts.server {
|
||||
return;
|
||||
}
|
||||
|
||||
const goNext = () => {
|
||||
if (checkList.length > index) {
|
||||
next.delay(followMs, checkOne);
|
||||
}
|
||||
};
|
||||
if (this.getPreferences(fileName).disableSuggestions) {
|
||||
goNext();
|
||||
}
|
||||
@ -1727,12 +1737,10 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
private createCheckList(fileNames: string[]): PendingErrorCheck[] {
|
||||
return mapDefined<string, PendingErrorCheck>(fileNames, uncheckedFileName => {
|
||||
const fileName = toNormalizedPath(uncheckedFileName);
|
||||
const project = this.projectService.tryGetDefaultProjectForFile(fileName);
|
||||
return project && { fileName, project };
|
||||
});
|
||||
private toPendingErrorCheck(uncheckedFileName: string): PendingErrorCheck | undefined {
|
||||
const fileName = toNormalizedPath(uncheckedFileName);
|
||||
const project = this.projectService.tryGetDefaultProjectForFile(fileName);
|
||||
return project && { fileName, project };
|
||||
}
|
||||
|
||||
private getDiagnostics(next: NextStep, delay: number, fileNames: string[]): void {
|
||||
@ -1740,9 +1748,8 @@ namespace ts.server {
|
||||
return;
|
||||
}
|
||||
|
||||
const checkList = this.createCheckList(fileNames);
|
||||
if (checkList.length > 0) {
|
||||
this.updateErrorCheck(next, checkList, delay);
|
||||
if (fileNames.length > 0) {
|
||||
this.updateErrorCheck(next, fileNames, delay);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -84,7 +84,10 @@ namespace ts.projectSystem {
|
||||
command: "geterr",
|
||||
arguments: { files: ["/a/missing"] }
|
||||
});
|
||||
// no files - expect 'completed' event
|
||||
// Queued files
|
||||
assert.equal(host.getOutput().length, 0, "expected 0 message");
|
||||
host.checkTimeoutQueueLengthAndRun(1);
|
||||
// Completed event since file is missing
|
||||
assert.equal(host.getOutput().length, 1, "expect 1 message");
|
||||
verifyRequestCompleted(session.getSeq(), 0);
|
||||
}
|
||||
|
||||
@ -923,6 +923,146 @@ declare var console: {
|
||||
const service = createProjectService(host);
|
||||
service.openClientFile(file.path);
|
||||
});
|
||||
|
||||
describe("when creating new file", () => {
|
||||
const foo: File = {
|
||||
path: `${tscWatch.projectRoot}/src/foo.ts`,
|
||||
content: "export function foo() { }"
|
||||
};
|
||||
const bar: File = {
|
||||
path: `${tscWatch.projectRoot}/src/bar.ts`,
|
||||
content: "export function bar() { }"
|
||||
};
|
||||
const config: File = {
|
||||
path: `${tscWatch.projectRoot}/tsconfig.json`,
|
||||
content: JSON.stringify({
|
||||
include: ["./src"]
|
||||
})
|
||||
};
|
||||
const fooBar: File = {
|
||||
path: `${tscWatch.projectRoot}/src/sub/fooBar.ts`,
|
||||
content: "export function fooBar() { }"
|
||||
};
|
||||
function verifySessionWorker({ withExclude, openFileBeforeCreating, checkProjectBeforeError, checkProjectAfterError, }: VerifySession, errorOnNewFileBeforeOldFile: boolean) {
|
||||
const host = createServerHost([
|
||||
foo, bar, libFile, { path: `${tscWatch.projectRoot}/src/sub` },
|
||||
withExclude ?
|
||||
{
|
||||
path: config.path,
|
||||
content: JSON.stringify({
|
||||
include: ["./src"],
|
||||
exclude: ["./src/sub"]
|
||||
})
|
||||
} :
|
||||
config
|
||||
]);
|
||||
const session = createSession(host, {
|
||||
canUseEvents: true
|
||||
});
|
||||
session.executeCommandSeq<protocol.OpenRequest>({
|
||||
command: protocol.CommandTypes.Open,
|
||||
arguments: {
|
||||
file: foo.path,
|
||||
fileContent: foo.content,
|
||||
projectRootPath: tscWatch.projectRoot
|
||||
}
|
||||
});
|
||||
if (!openFileBeforeCreating) {
|
||||
host.writeFile(fooBar.path, fooBar.content);
|
||||
}
|
||||
session.executeCommandSeq<protocol.OpenRequest>({
|
||||
command: protocol.CommandTypes.Open,
|
||||
arguments: {
|
||||
file: fooBar.path,
|
||||
fileContent: fooBar.content,
|
||||
projectRootPath: tscWatch.projectRoot
|
||||
}
|
||||
});
|
||||
if (openFileBeforeCreating) {
|
||||
host.writeFile(fooBar.path, fooBar.content);
|
||||
}
|
||||
const service = session.getProjectService();
|
||||
checkProjectBeforeError(service);
|
||||
verifyGetErrRequest({
|
||||
session,
|
||||
host,
|
||||
expected: errorOnNewFileBeforeOldFile ?
|
||||
[
|
||||
{ file: fooBar, syntax: [], semantic: [], suggestion: [] },
|
||||
{ file: foo, syntax: [], semantic: [], suggestion: [] },
|
||||
] :
|
||||
[
|
||||
{ file: foo, syntax: [], semantic: [], suggestion: [] },
|
||||
{ file: fooBar, syntax: [], semantic: [], suggestion: [] },
|
||||
],
|
||||
existingTimeouts: 2
|
||||
});
|
||||
checkProjectAfterError(service);
|
||||
}
|
||||
interface VerifySession {
|
||||
withExclude?: boolean;
|
||||
openFileBeforeCreating: boolean;
|
||||
checkProjectBeforeError: (service: server.ProjectService) => void;
|
||||
checkProjectAfterError: (service: server.ProjectService) => void;
|
||||
}
|
||||
function verifySession(input: VerifySession) {
|
||||
it("when error on new file are asked before old one", () => {
|
||||
verifySessionWorker(input, /*errorOnNewFileBeforeOldFile*/ true);
|
||||
});
|
||||
|
||||
it("when error on new file are asked after old one", () => {
|
||||
verifySessionWorker(input, /*errorOnNewFileBeforeOldFile*/ false);
|
||||
});
|
||||
}
|
||||
function checkFooBarInInferredProject(service: server.ProjectService) {
|
||||
checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 });
|
||||
checkProjectActualFiles(service.configuredProjects.get(config.path)!, [foo.path, bar.path, libFile.path, config.path]);
|
||||
checkProjectActualFiles(service.inferredProjects[0], [fooBar.path, libFile.path]);
|
||||
}
|
||||
function checkFooBarInConfiguredProject(service: server.ProjectService) {
|
||||
checkNumberOfProjects(service, { configuredProjects: 1 });
|
||||
checkProjectActualFiles(service.configuredProjects.get(config.path)!, [foo.path, bar.path, fooBar.path, libFile.path, config.path]);
|
||||
}
|
||||
describe("when new file creation directory watcher is invoked before file is opened in editor", () => {
|
||||
verifySession({
|
||||
openFileBeforeCreating: false,
|
||||
checkProjectBeforeError: checkFooBarInConfiguredProject,
|
||||
checkProjectAfterError: checkFooBarInConfiguredProject
|
||||
});
|
||||
describe("when new file is excluded from config", () => {
|
||||
verifySession({
|
||||
withExclude: true,
|
||||
openFileBeforeCreating: false,
|
||||
checkProjectBeforeError: checkFooBarInInferredProject,
|
||||
checkProjectAfterError: checkFooBarInInferredProject
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when new file creation directory watcher is invoked after file is opened in editor", () => {
|
||||
verifySession({
|
||||
openFileBeforeCreating: true,
|
||||
checkProjectBeforeError: checkFooBarInInferredProject,
|
||||
checkProjectAfterError: service => {
|
||||
// Both projects exist but fooBar is in configured project after the update
|
||||
// Inferred project is yet to be updated so still has fooBar
|
||||
checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 });
|
||||
checkProjectActualFiles(service.configuredProjects.get(config.path)!, [foo.path, bar.path, fooBar.path, libFile.path, config.path]);
|
||||
checkProjectActualFiles(service.inferredProjects[0], [fooBar.path, libFile.path]);
|
||||
assert.isTrue(service.inferredProjects[0].dirty);
|
||||
assert.equal(service.inferredProjects[0].getRootFilesMap().size, 0);
|
||||
}
|
||||
});
|
||||
describe("when new file is excluded from config", () => {
|
||||
verifySession({
|
||||
withExclude: true,
|
||||
openFileBeforeCreating: true,
|
||||
checkProjectBeforeError: checkFooBarInInferredProject,
|
||||
checkProjectAfterError: checkFooBarInInferredProject
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("unittests:: tsserver:: ConfiguredProjects:: non-existing directories listed in config file input array", () => {
|
||||
|
||||
@ -382,7 +382,7 @@ namespace ts.projectSystem {
|
||||
}
|
||||
});
|
||||
|
||||
host.runQueuedImmediateCallbacks();
|
||||
host.checkTimeoutQueueLengthAndRun(1);
|
||||
assert.isFalse(hasError());
|
||||
checkCompleteEvent(session, 1, expectedSequenceId);
|
||||
session.clearMessages();
|
||||
|
||||
@ -1508,7 +1508,7 @@ var x = 10;`
|
||||
host,
|
||||
expected: [
|
||||
{ file: fileB, syntax: [], semantic: [], suggestion: [] },
|
||||
{ file: fileSubA },
|
||||
{ file: fileSubA, syntax: [], semantic: [], suggestion: [] },
|
||||
],
|
||||
existingTimeouts: 2,
|
||||
onErrEvent: () => assert.isFalse(hasErrorMsg())
|
||||
|
||||
@ -9483,7 +9483,7 @@ declare namespace ts.server {
|
||||
private getCompileOnSaveAffectedFileList;
|
||||
private emitFile;
|
||||
private getSignatureHelpItems;
|
||||
private createCheckList;
|
||||
private toPendingErrorCheck;
|
||||
private getDiagnostics;
|
||||
private change;
|
||||
private reload;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user