Skip to content

Commit 3cd6db7

Browse files
authored
Merge pull request microsoft#24816 from Microsoft/updateGraphBeforeErrorChecking
Updates the graph before checking if file is present in project for error checking
2 parents ed20f7d + 153f25a commit 3cd6db7

File tree

2 files changed

+139
-9
lines changed

2 files changed

+139
-9
lines changed

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 137 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ namespace ts.projectSystem {
6262
getLogFileName: () => undefined,
6363
};
6464

65+
function createHasErrorMessageLogger() {
66+
let hasErrorMsg = false;
67+
const { close, hasLevel, loggingEnabled, startGroup, endGroup, info, getLogFileName, perftrc } = nullLogger;
68+
const logger: server.Logger = {
69+
close, hasLevel, loggingEnabled, startGroup, endGroup, info, getLogFileName, perftrc,
70+
msg: () => {
71+
hasErrorMsg = true;
72+
}
73+
};
74+
return { logger, hasErrorMsg: () => hasErrorMsg };
75+
}
76+
6577
export class TestTypingsInstaller extends TI.TypingsInstaller implements server.ITypingsInstaller {
6678
protected projectService: server.ProjectService;
6779
constructor(
@@ -2917,14 +2929,7 @@ namespace ts.projectSystem {
29172929
});
29182930

29192931
it("Getting errors from closed script info does not throw exception (because of getting project from orphan script info)", () => {
2920-
let hasErrorMsg = false;
2921-
const { close, hasLevel, loggingEnabled, startGroup, endGroup, info, getLogFileName, perftrc } = nullLogger;
2922-
const logger: server.Logger = {
2923-
close, hasLevel, loggingEnabled, startGroup, endGroup, info, getLogFileName, perftrc,
2924-
msg: () => {
2925-
hasErrorMsg = true;
2926-
}
2927-
};
2932+
const { logger, hasErrorMsg } = createHasErrorMessageLogger();
29282933
const f1 = {
29292934
path: "/a/b/app.ts",
29302935
content: "let x = 1;"
@@ -2954,7 +2959,7 @@ namespace ts.projectSystem {
29542959
files: [f1.path]
29552960
}
29562961
});
2957-
assert.isFalse(hasErrorMsg);
2962+
assert.isFalse(hasErrorMsg());
29582963
});
29592964

29602965
it("Changed module resolution reflected when specifying files list", () => {
@@ -3320,6 +3325,129 @@ namespace ts.projectSystem {
33203325
assert.equal(info.containingProjects.length, 0);
33213326
}
33223327
});
3328+
3329+
it("handles delayed directory watch invoke on file creation", () => {
3330+
const projectRootPath = "/users/username/projects/project";
3331+
const fileB: File = {
3332+
path: `${projectRootPath}/b.ts`,
3333+
content: "export const b = 10;"
3334+
};
3335+
const fileA: File = {
3336+
path: `${projectRootPath}/a.ts`,
3337+
content: "export const a = 10;"
3338+
};
3339+
const fileSubA: File = {
3340+
path: `${projectRootPath}/sub/a.ts`,
3341+
content: fileA.content
3342+
};
3343+
const config: File = {
3344+
path: `${projectRootPath}/tsconfig.json`,
3345+
content: "{}"
3346+
};
3347+
const files = [fileSubA, fileB, config, libFile];
3348+
const host = createServerHost(files);
3349+
const { logger, hasErrorMsg } = createHasErrorMessageLogger();
3350+
const session = createSession(host, { canUseEvents: true, noGetErrOnBackgroundUpdate: true, logger });
3351+
openFile(fileB);
3352+
openFile(fileSubA);
3353+
3354+
const services = session.getProjectService();
3355+
checkNumberOfProjects(services, { configuredProjects: 1 });
3356+
checkProjectActualFiles(services.configuredProjects.get(config.path)!, files.map(f => f.path));
3357+
host.checkTimeoutQueueLengthAndRun(0);
3358+
3359+
// This should schedule 2 timeouts for ensuring project structure and ensuring projects for open file
3360+
const filesWithFileA = files.map(f => f === fileSubA ? fileA : f);
3361+
host.reloadFS(files.map(f => f === fileSubA ? fileA : f));
3362+
host.checkTimeoutQueueLength(2);
3363+
3364+
closeFile(fileSubA);
3365+
// This should cancel existing updates and schedule new ones
3366+
host.checkTimeoutQueueLength(2);
3367+
checkNumberOfProjects(services, { configuredProjects: 1 });
3368+
checkProjectActualFiles(services.configuredProjects.get(config.path)!, files.map(f => f.path));
3369+
3370+
// Open the fileA (as if rename)
3371+
openFile(fileA);
3372+
3373+
// config project is updated to check if fileA is present in it
3374+
checkNumberOfProjects(services, { configuredProjects: 1 });
3375+
checkProjectActualFiles(services.configuredProjects.get(config.path)!, filesWithFileA.map(f => f.path));
3376+
3377+
// Run the timeout for updating configured project and ensuring projects for open file
3378+
host.checkTimeoutQueueLengthAndRun(2);
3379+
checkProjectActualFiles(services.configuredProjects.get(config.path)!, filesWithFileA.map(f => f.path));
3380+
3381+
// file is deleted but watches are not yet invoked
3382+
const originalFileExists = host.fileExists;
3383+
host.fileExists = s => s === fileA.path ? false : originalFileExists.call(host, s);
3384+
closeFile(fileA);
3385+
host.checkTimeoutQueueLength(2); // Update configured project and projects for open file
3386+
checkProjectActualFiles(services.configuredProjects.get(config.path)!, filesWithFileA.map(f => f.path));
3387+
3388+
// host.fileExists = originalFileExists;
3389+
openFile(fileSubA);
3390+
// This should create inferred project since fileSubA not on the disk
3391+
checkProjectActualFiles(services.configuredProjects.get(config.path)!, mapDefined(filesWithFileA, f => f === fileA ? undefined : f.path));
3392+
checkProjectActualFiles(services.inferredProjects[0], [fileSubA.path, libFile.path]);
3393+
3394+
host.checkTimeoutQueueLengthAndRun(2); // Update configured project and projects for open file
3395+
host.fileExists = originalFileExists;
3396+
3397+
// Actually trigger the file move
3398+
host.reloadFS(files);
3399+
host.checkTimeoutQueueLength(2);
3400+
const fileBErrorTimeoutId = host.getNextTimeoutId();
3401+
3402+
session.executeCommandSeq<protocol.GeterrRequest>({
3403+
command: protocol.CommandTypes.Geterr,
3404+
arguments: {
3405+
files: [fileB.path, fileSubA.path],
3406+
delay: 0
3407+
}
3408+
});
3409+
const getErrSeqId = session.getSeq();
3410+
host.checkTimeoutQueueLength(3);
3411+
3412+
session.clearMessages();
3413+
host.runQueuedTimeoutCallbacks(fileBErrorTimeoutId);
3414+
checkErrorMessage(session, "syntaxDiag", { file: fileB.path, diagnostics: [] });
3415+
3416+
session.clearMessages();
3417+
host.runQueuedImmediateCallbacks();
3418+
checkErrorMessage(session, "semanticDiag", { file: fileB.path, diagnostics: [] });
3419+
3420+
session.clearMessages();
3421+
const fileSubAErrorTimeoutId = host.getNextTimeoutId();
3422+
host.runQueuedImmediateCallbacks();
3423+
checkErrorMessage(session, "suggestionDiag", { file: fileB.path, diagnostics: [] });
3424+
3425+
session.clearMessages();
3426+
host.checkTimeoutQueueLength(3);
3427+
host.runQueuedTimeoutCallbacks(fileSubAErrorTimeoutId);
3428+
checkCompleteEvent(session, 1, getErrSeqId);
3429+
assert.isFalse(hasErrorMsg());
3430+
3431+
function openFile(file: File) {
3432+
session.executeCommandSeq<protocol.OpenRequest>({
3433+
command: protocol.CommandTypes.Open,
3434+
arguments: {
3435+
file: file.path,
3436+
fileContent: file.content,
3437+
projectRootPath
3438+
}
3439+
});
3440+
}
3441+
3442+
function closeFile(file: File) {
3443+
session.executeCommandSeq<protocol.CloseRequest>({
3444+
command: protocol.CommandTypes.Close,
3445+
arguments: {
3446+
file: file.path
3447+
}
3448+
});
3449+
}
3450+
});
33233451
});
33243452

33253453
describe("tsserverProjectSystem Proper errors", () => {

src/server/session.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,8 @@ namespace ts.server {
511511

512512
const { fileName, project } = checkList[index];
513513
index++;
514+
// Ensure the project is upto date before checking if this file is present in the project
515+
project.updateGraph();
514516
if (!project.containsFile(fileName, requireOpen)) {
515517
return;
516518
}

0 commit comments

Comments
 (0)