Skip to content

Commit 9f296ce

Browse files
authored
Do not include global errors in semantic errors from the file (microsoft#37545)
Fixes microsoft#37084
1 parent 7ade67d commit 9f296ce

File tree

4 files changed

+251
-208
lines changed

4 files changed

+251
-208
lines changed

src/server/session.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -856,7 +856,7 @@ namespace ts.server {
856856
private semanticCheck(file: NormalizedPath, project: Project) {
857857
const diags = isDeclarationFileInJSOnlyNonConfiguredProject(project, file)
858858
? emptyArray
859-
: project.getLanguageService().getSemanticDiagnostics(file);
859+
: project.getLanguageService().getSemanticDiagnostics(file).filter(d => !!d.file);
860860
this.sendDiagnosticsEvent(file, project, diags, "semanticDiag");
861861
}
862862

@@ -1234,7 +1234,7 @@ namespace ts.server {
12341234
if (configFile) {
12351235
return this.getConfigFileDiagnostics(configFile, project!, !!args.includeLinePosition); // TODO: GH#18217
12361236
}
1237-
return this.getDiagnosticsWorker(args, /*isSemantic*/ true, (project, file) => project.getLanguageService().getSemanticDiagnostics(file), !!args.includeLinePosition);
1237+
return this.getDiagnosticsWorker(args, /*isSemantic*/ true, (project, file) => project.getLanguageService().getSemanticDiagnostics(file).filter(d => !!d.file), !!args.includeLinePosition);
12381238
}
12391239

12401240
private getSuggestionDiagnosticsSync(args: protocol.SuggestionDiagnosticsSyncRequestArgs): readonly protocol.Diagnostic[] | readonly protocol.DiagnosticWithLinePosition[] {

src/testRunner/unittests/tsserver/helpers.ts

+205
Original file line numberDiff line numberDiff line change
@@ -807,4 +807,209 @@ namespace ts.projectSystem {
807807
lineText,
808808
};
809809
}
810+
811+
export interface GetErrDiagnostics {
812+
file: string | File;
813+
syntax?: protocol.Diagnostic[];
814+
semantic?: protocol.Diagnostic[];
815+
suggestion?: protocol.Diagnostic[];
816+
}
817+
export interface VerifyGetErrRequestBase {
818+
session: TestSession;
819+
host: TestServerHost;
820+
onErrEvent?: () => void;
821+
existingTimeouts?: number;
822+
}
823+
export interface VerifyGetErrRequest extends VerifyGetErrRequestBase {
824+
expected: readonly GetErrDiagnostics[];
825+
}
826+
export function verifyGetErrRequest(request: VerifyGetErrRequest) {
827+
const { session, expected } = request;
828+
session.clearMessages();
829+
const expectedSequenceId = session.getNextSeq();
830+
session.executeCommandSeq<protocol.GeterrRequest>({
831+
command: protocol.CommandTypes.Geterr,
832+
arguments: {
833+
delay: 0,
834+
files: expected.map(f => filePath(f.file))
835+
}
836+
});
837+
checkAllErrors({ ...request, expectedSequenceId });
838+
}
839+
840+
export interface CheckAllErrors extends VerifyGetErrRequest {
841+
expectedSequenceId: number;
842+
}
843+
function checkAllErrors({ expected, expectedSequenceId, ...rest }: CheckAllErrors) {
844+
for (let i = 0; i < expected.length; i++) {
845+
checkErrorsInFile({
846+
...rest,
847+
expected: expected[i],
848+
expectedSequenceId: i === expected.length - 1 ? expectedSequenceId : undefined,
849+
});
850+
}
851+
}
852+
853+
function filePath(file: string | File) {
854+
return isString(file) ? file : file.path;
855+
}
856+
interface CheckErrorsInFile extends VerifyGetErrRequestBase {
857+
expected: GetErrDiagnostics;
858+
expectedSequenceId?: number;
859+
}
860+
function checkErrorsInFile({
861+
session, host, onErrEvent, existingTimeouts, expectedSequenceId,
862+
expected: { file, syntax, semantic, suggestion },
863+
}: CheckErrorsInFile) {
864+
onErrEvent = onErrEvent || noop;
865+
if (existingTimeouts !== undefined) {
866+
host.checkTimeoutQueueLength(existingTimeouts + 1);
867+
host.runQueuedTimeoutCallbacks(host.getNextTimeoutId() - 1);
868+
}
869+
else {
870+
host.checkTimeoutQueueLengthAndRun(1);
871+
}
872+
if (syntax) {
873+
onErrEvent();
874+
checkErrorMessage(session, "syntaxDiag", { file: filePath(file), diagnostics: syntax });
875+
}
876+
if (semantic) {
877+
session.clearMessages();
878+
879+
host.runQueuedImmediateCallbacks(1);
880+
onErrEvent();
881+
checkErrorMessage(session, "semanticDiag", { file: filePath(file), diagnostics: semantic });
882+
}
883+
if (suggestion) {
884+
session.clearMessages();
885+
886+
host.runQueuedImmediateCallbacks(1);
887+
onErrEvent();
888+
checkErrorMessage(session, "suggestionDiag", { file: filePath(file), diagnostics: suggestion });
889+
}
890+
if (expectedSequenceId !== undefined) {
891+
checkCompleteEvent(session, syntax || semantic || suggestion ? 2 : 1, expectedSequenceId);
892+
}
893+
session.clearMessages();
894+
}
895+
896+
function verifyErrorsUsingGeterr({ allFiles, openFiles, expectedGetErr }: VerifyGetErrScenario) {
897+
it("verifies the errors in open file", () => {
898+
const host = createServerHost([...allFiles(), libFile]);
899+
const session = createSession(host, { canUseEvents: true, });
900+
openFilesForSession(openFiles(), session);
901+
902+
verifyGetErrRequest({ session, host, expected: expectedGetErr() });
903+
});
904+
}
905+
906+
function verifyErrorsUsingGeterrForProject({ allFiles, openFiles, expectedGetErrForProject }: VerifyGetErrScenario) {
907+
it("verifies the errors in projects", () => {
908+
const host = createServerHost([...allFiles(), libFile]);
909+
const session = createSession(host, { canUseEvents: true, });
910+
openFilesForSession(openFiles(), session);
911+
912+
session.clearMessages();
913+
for (const expected of expectedGetErrForProject()) {
914+
const expectedSequenceId = session.getNextSeq();
915+
session.executeCommandSeq<protocol.GeterrForProjectRequest>({
916+
command: protocol.CommandTypes.GeterrForProject,
917+
arguments: {
918+
delay: 0,
919+
file: expected.project
920+
}
921+
});
922+
923+
checkAllErrors({ session, host, expected: expected.errors, expectedSequenceId });
924+
}
925+
});
926+
}
927+
928+
function verifyErrorsUsingSyncMethods({ allFiles, openFiles, expectedSyncDiagnostics }: VerifyGetErrScenario) {
929+
it("verifies the errors using sync commands", () => {
930+
const host = createServerHost([...allFiles(), libFile]);
931+
const session = createSession(host);
932+
openFilesForSession(openFiles(), session);
933+
for (const { file, project, syntax, semantic, suggestion } of expectedSyncDiagnostics()) {
934+
const actualSyntax = session.executeCommandSeq<protocol.SyntacticDiagnosticsSyncRequest>({
935+
command: protocol.CommandTypes.SyntacticDiagnosticsSync,
936+
arguments: {
937+
file: filePath(file),
938+
projectFileName: project
939+
}
940+
}).response as protocol.Diagnostic[];
941+
assert.deepEqual(actualSyntax, syntax, `Syntax diagnostics for file: ${filePath(file)}, project: ${project}`);
942+
const actualSemantic = session.executeCommandSeq<protocol.SemanticDiagnosticsSyncRequest>({
943+
command: protocol.CommandTypes.SemanticDiagnosticsSync,
944+
arguments: {
945+
file: filePath(file),
946+
projectFileName: project
947+
}
948+
}).response as protocol.Diagnostic[];
949+
assert.deepEqual(actualSemantic, semantic, `Semantic diagnostics for file: ${filePath(file)}, project: ${project}`);
950+
const actualSuggestion = session.executeCommandSeq<protocol.SuggestionDiagnosticsSyncRequest>({
951+
command: protocol.CommandTypes.SuggestionDiagnosticsSync,
952+
arguments: {
953+
file: filePath(file),
954+
projectFileName: project
955+
}
956+
}).response as protocol.Diagnostic[];
957+
assert.deepEqual(actualSuggestion, suggestion, `Suggestion diagnostics for file: ${filePath(file)}, project: ${project}`);
958+
}
959+
});
960+
}
961+
962+
function verifyConfigFileErrors({ allFiles, openFiles, expectedConfigFileDiagEvents }: VerifyGetErrScenario) {
963+
it("verify config file errors", () => {
964+
const host = createServerHost([...allFiles(), libFile]);
965+
const { session, events } = createSessionWithEventTracking<server.ConfigFileDiagEvent>(host, server.ConfigFileDiagEvent);
966+
967+
for (const file of openFiles()) {
968+
session.executeCommandSeq<protocol.OpenRequest>({
969+
command: protocol.CommandTypes.Open,
970+
arguments: { file: file.path }
971+
});
972+
}
973+
974+
assert.deepEqual(events, expectedConfigFileDiagEvents().map(data => ({
975+
eventName: server.ConfigFileDiagEvent,
976+
data
977+
})));
978+
});
979+
}
980+
981+
export interface GetErrForProjectDiagnostics {
982+
project: string;
983+
errors: readonly GetErrDiagnostics[];
984+
}
985+
export interface SyncDiagnostics extends GetErrDiagnostics {
986+
project?: string;
987+
}
988+
export interface VerifyGetErrScenario {
989+
allFiles: () => readonly File[];
990+
openFiles: () => readonly File[];
991+
expectedGetErr: () => readonly GetErrDiagnostics[];
992+
expectedGetErrForProject: () => readonly GetErrForProjectDiagnostics[];
993+
expectedSyncDiagnostics: () => readonly SyncDiagnostics[];
994+
expectedConfigFileDiagEvents: () => readonly server.ConfigFileDiagEvent["data"][];
995+
}
996+
export function verifyGetErrScenario(scenario: VerifyGetErrScenario) {
997+
verifyErrorsUsingGeterr(scenario);
998+
verifyErrorsUsingGeterrForProject(scenario);
999+
verifyErrorsUsingSyncMethods(scenario);
1000+
verifyConfigFileErrors(scenario);
1001+
}
1002+
1003+
export function emptyDiagnostics(file: File): GetErrDiagnostics {
1004+
return {
1005+
file,
1006+
syntax: emptyArray,
1007+
semantic: emptyArray,
1008+
suggestion: emptyArray
1009+
};
1010+
}
1011+
1012+
export function syncDiagnostics(diagnostics: GetErrDiagnostics, project: string): SyncDiagnostics {
1013+
return { project, ...diagnostics };
1014+
}
8101015
}

src/testRunner/unittests/tsserver/projectErrors.ts

+42
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,48 @@ declare module '@custom/plugin' {
493493
});
494494
}
495495
});
496+
497+
describe("when semantic error returns includes global error", () => {
498+
const file: File = {
499+
path: `${tscWatch.projectRoot}/ui.ts`,
500+
content: `const x = async (_action: string) => {
501+
};`
502+
};
503+
const config: File = {
504+
path: `${tscWatch.projectRoot}/tsconfig.json`,
505+
content: "{}"
506+
};
507+
function expectedDiagnostics(): GetErrDiagnostics {
508+
const span = protocolTextSpanFromSubstring(file.content, `async (_action: string) => {`);
509+
return {
510+
file,
511+
syntax: [],
512+
semantic: [
513+
createDiagnostic(span.start, span.end, Diagnostics.An_async_function_or_method_must_return_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option, [], "error"),
514+
],
515+
suggestion: []
516+
};
517+
}
518+
verifyGetErrScenario({
519+
allFiles: () => [libFile, file, config],
520+
openFiles: () => [file],
521+
expectedGetErr: () => [expectedDiagnostics()],
522+
expectedGetErrForProject: () => [{
523+
project: file.path,
524+
errors: [
525+
expectedDiagnostics(),
526+
]
527+
}],
528+
expectedSyncDiagnostics: () => [
529+
syncDiagnostics(expectedDiagnostics(), config.path),
530+
],
531+
expectedConfigFileDiagEvents: () => [{
532+
triggerFile: file.path,
533+
configFileName: config.path,
534+
diagnostics: emptyArray
535+
}]
536+
});
537+
});
496538
});
497539

498540
describe("unittests:: tsserver:: Project Errors for Configure file diagnostics events", () => {

0 commit comments

Comments
 (0)