Skip to content

Commit ce5d5d8

Browse files
authored
Merge pull request microsoft#23636 from Microsoft/getErrWithInferredProject
Fix the crash when reporting errors of file that was referenced by inferred project root, is opened right after closing the root file
2 parents 5d67f8e + 6ec7638 commit ce5d5d8

File tree

3 files changed

+100
-13
lines changed

3 files changed

+100
-13
lines changed

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,10 @@ namespace ts.projectSystem {
480480
checkNthEvent(session, server.toEvent(eventName, diagnostics), 0, isMostRecent);
481481
}
482482

483+
function createDiagnostic(start: protocol.Location, end: protocol.Location, message: DiagnosticMessage, args: ReadonlyArray<string> = [], category = diagnosticCategoryName(message), reportsUnnecessary?: {}): protocol.Diagnostic {
484+
return { start, end, text: formatStringFromArgs(message.message, args), code: message.code, category, reportsUnnecessary, source: undefined };
485+
}
486+
483487
function checkCompleteEvent(session: TestSession, numberOfCurrentEvents: number, expectedSequenceId: number, isMostRecent = true): void {
484488
checkNthEvent(session, server.toEvent("requestCompleted", { request_seq: expectedSequenceId }), numberOfCurrentEvents - 1, isMostRecent);
485489
}
@@ -496,7 +500,7 @@ namespace ts.projectSystem {
496500

497501
function checkNthEvent(session: TestSession, expectedEvent: protocol.Event, index: number, isMostRecent: boolean) {
498502
const events = session.events;
499-
assert.deepEqual(events[index], expectedEvent);
503+
assert.deepEqual(events[index], expectedEvent, `Expected ${JSON.stringify(expectedEvent)} at ${index} in ${JSON.stringify(events)}`);
500504

501505
const outputs = session.host.getOutput();
502506
assert.equal(outputs[index], server.formatMessage(expectedEvent, nullLogger, Utils.byteLength, session.host.newLine));
@@ -3333,6 +3337,89 @@ namespace ts.projectSystem {
33333337
checkCompleteEvent(session, 1, expectedSequenceId);
33343338
session.clearMessages();
33353339
});
3340+
3341+
it("Reports errors correctly when file referenced by inferred project root, is opened right after closing the root file", () => {
3342+
const projectRoot = "/user/username/projects/myproject";
3343+
const app: FileOrFolder = {
3344+
path: `${projectRoot}/src/client/app.js`,
3345+
content: ""
3346+
};
3347+
const serverUtilities: FileOrFolder = {
3348+
path: `${projectRoot}/src/server/utilities.js`,
3349+
content: `function getHostName() { return "hello"; } export { getHostName };`
3350+
};
3351+
const backendTest: FileOrFolder = {
3352+
path: `${projectRoot}/test/backend/index.js`,
3353+
content: `import { getHostName } from '../../src/server/utilities';export default getHostName;`
3354+
};
3355+
const files = [libFile, app, serverUtilities, backendTest];
3356+
const host = createServerHost(files);
3357+
const session = createSession(host, { useInferredProjectPerProjectRoot: true, canUseEvents: true });
3358+
session.executeCommandSeq<protocol.OpenRequest>({
3359+
command: protocol.CommandTypes.Open,
3360+
arguments: {
3361+
file: app.path,
3362+
projectRootPath: projectRoot
3363+
}
3364+
});
3365+
const service = session.getProjectService();
3366+
checkNumberOfProjects(service, { inferredProjects: 1 });
3367+
const project = service.inferredProjects[0];
3368+
checkProjectActualFiles(project, [libFile.path, app.path]);
3369+
session.executeCommandSeq<protocol.OpenRequest>({
3370+
command: protocol.CommandTypes.Open,
3371+
arguments: {
3372+
file: backendTest.path,
3373+
projectRootPath: projectRoot
3374+
}
3375+
});
3376+
checkNumberOfProjects(service, { inferredProjects: 1 });
3377+
checkProjectActualFiles(project, files.map(f => f.path));
3378+
checkErrors([backendTest.path, app.path]);
3379+
session.executeCommandSeq<protocol.CloseRequest>({
3380+
command: protocol.CommandTypes.Close,
3381+
arguments: {
3382+
file: backendTest.path
3383+
}
3384+
});
3385+
session.executeCommandSeq<protocol.OpenRequest>({
3386+
command: protocol.CommandTypes.Open,
3387+
arguments: {
3388+
file: serverUtilities.path,
3389+
projectRootPath: projectRoot
3390+
}
3391+
});
3392+
checkErrors([serverUtilities.path, app.path]);
3393+
3394+
function checkErrors(openFiles: [string, string]) {
3395+
const expectedSequenceId = session.getNextSeq();
3396+
session.executeCommandSeq<protocol.GeterrRequest>({
3397+
command: protocol.CommandTypes.Geterr,
3398+
arguments: {
3399+
delay: 0,
3400+
files: openFiles
3401+
}
3402+
});
3403+
3404+
for (const openFile of openFiles) {
3405+
session.clearMessages();
3406+
host.checkTimeoutQueueLength(3);
3407+
host.runQueuedTimeoutCallbacks(host.getNextTimeoutId() - 1);
3408+
3409+
checkErrorMessage(session, "syntaxDiag", { file: openFile, diagnostics: [] });
3410+
session.clearMessages();
3411+
3412+
host.runQueuedImmediateCallbacks();
3413+
checkErrorMessage(session, "semanticDiag", { file: openFile, diagnostics: [] });
3414+
session.clearMessages();
3415+
3416+
host.runQueuedImmediateCallbacks(1);
3417+
checkErrorMessage(session, "suggestionDiag", { file: openFile, diagnostics: [] });
3418+
}
3419+
checkCompleteEvent(session, 2, expectedSequenceId);
3420+
session.clearMessages();
3421+
}
3422+
});
33363423
});
33373424

33383425
describe("tsserverProjectSystem autoDiscovery", () => {
@@ -4293,10 +4380,6 @@ namespace ts.projectSystem {
42934380

42944381
session.clearMessages();
42954382
});
4296-
4297-
function createDiagnostic(start: protocol.Location, end: protocol.Location, message: DiagnosticMessage, args: ReadonlyArray<string> = [], category = diagnosticCategoryName(message), reportsUnnecessary?: {}): protocol.Diagnostic {
4298-
return { start, end, text: formatStringFromArgs(message.message, args), code: message.code, category, reportsUnnecessary, source: undefined };
4299-
}
43004383
});
43014384

43024385
describe("tsserverProjectSystem Configure file diagnostics events", () => {

src/server/editorServices.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,10 @@ namespace ts.server {
310310
return `Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`;
311311
}
312312

313+
function updateProjectIfDirty(project: Project) {
314+
return project.dirty && project.updateGraph();
315+
}
316+
313317
export class ProjectService {
314318

315319
/*@internal*/
@@ -673,7 +677,7 @@ namespace ts.server {
673677
let hasChanges = this.pendingEnsureProjectForOpenFiles;
674678
this.pendingProjectUpdates.clear();
675679
const updateGraph = (project: Project) => {
676-
hasChanges = this.updateProjectIfDirty(project) || hasChanges;
680+
hasChanges = updateProjectIfDirty(project) || hasChanges;
677681
};
678682

679683
this.externalProjects.forEach(updateGraph);
@@ -684,10 +688,6 @@ namespace ts.server {
684688
}
685689
}
686690

687-
private updateProjectIfDirty(project: Project) {
688-
return project.dirty && project.updateGraph();
689-
}
690-
691691
getFormatCodeOptions(file: NormalizedPath) {
692692
const info = this.getScriptInfoForNormalizedPath(file);
693693
return info && info.getFormatCodeSettings() || this.hostConfiguration.formatCodeOptions;
@@ -1980,7 +1980,7 @@ namespace ts.server {
19801980
}
19811981
});
19821982
this.pendingEnsureProjectForOpenFiles = false;
1983-
this.inferredProjects.forEach(p => this.updateProjectIfDirty(p));
1983+
this.inferredProjects.forEach(updateProjectIfDirty);
19841984

19851985
this.logger.info("Structure after ensureProjectForOpenFiles:");
19861986
this.printProjects();
@@ -2027,7 +2027,7 @@ namespace ts.server {
20272027
}
20282028
else {
20292029
// Ensure project is ready to check if it contains opened script info
2030-
project.updateGraph();
2030+
updateProjectIfDirty(project);
20312031
}
20322032
}
20332033
}
@@ -2036,6 +2036,11 @@ namespace ts.server {
20362036
// - external project search, which updates the project before checking if info is present in it
20372037
// - configured project - either created or updated to ensure we know correct status of info
20382038

2039+
// At this point we need to ensure that containing projects of the info are uptodate
2040+
// This will ensure that later question of info.isOrphan() will return correct answer
2041+
// and we correctly create inferred project for the info
2042+
info.containingProjects.forEach(updateProjectIfDirty);
2043+
20392044
// At this point if file is part of any any configured or external project, then it would be present in the containing projects
20402045
// So if it still doesnt have any containing projects, it needs to be part of inferred project
20412046
if (info.isOrphan()) {

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8050,7 +8050,6 @@ declare namespace ts.server {
80508050
* ensure that each open script info has project
80518051
*/
80528052
private ensureProjectStructuresUptoDate;
8053-
private updateProjectIfDirty;
80548053
getFormatCodeOptions(file: NormalizedPath): FormatCodeSettings;
80558054
getPreferences(file: NormalizedPath): UserPreferences;
80568055
private onSourceFileChanged;

0 commit comments

Comments
 (0)