Skip to content

Commit fa4619c

Browse files
author
Andy
authored
Add 'info' diagnostics (#22204)
* Add 'info' diagnostics * Code review
1 parent e26d4e4 commit fa4619c

File tree

50 files changed

+400
-383
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+400
-383
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3810,6 +3810,11 @@
38103810
"code": 18003
38113811
},
38123812

3813+
"File is a CommonJS module; it may be converted to an ES6 module.": {
3814+
"category": "Suggestion",
3815+
"code": 80001
3816+
},
3817+
38133818
"Add missing 'super()' call": {
38143819
"category": "Message",
38153820
"code": 90001

src/compiler/program.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,7 @@ namespace ts {
227227
}
228228

229229
export function formatDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost): string {
230-
const category = DiagnosticCategory[diagnostic.category].toLowerCase();
231-
const errorMessage = `${category} TS${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine())}${host.getNewLine()}`;
230+
const errorMessage = `${diagnosticCategoryName(diagnostic)} TS${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine())}${host.getNewLine()}`;
232231

233232
if (diagnostic.file) {
234233
const { line, character } = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
@@ -254,8 +253,9 @@ namespace ts {
254253
const ellipsis = "...";
255254
function getCategoryFormat(category: DiagnosticCategory): string {
256255
switch (category) {
257-
case DiagnosticCategory.Warning: return ForegroundColorEscapeSequences.Yellow;
258256
case DiagnosticCategory.Error: return ForegroundColorEscapeSequences.Red;
257+
case DiagnosticCategory.Warning: return ForegroundColorEscapeSequences.Yellow;
258+
case DiagnosticCategory.Suggestion: return Debug.fail("Should never get an Info diagnostic on the command line.");
259259
case DiagnosticCategory.Message: return ForegroundColorEscapeSequences.Blue;
260260
}
261261
}
@@ -337,9 +337,7 @@ namespace ts {
337337
output += " - ";
338338
}
339339

340-
const categoryColor = getCategoryFormat(diagnostic.category);
341-
const category = DiagnosticCategory[diagnostic.category].toLowerCase();
342-
output += formatColorAndReset(category, categoryColor);
340+
output += formatColorAndReset(diagnosticCategoryName(diagnostic), getCategoryFormat(diagnostic.category));
343341
output += formatColorAndReset(` TS${ diagnostic.code }: `, ForegroundColorEscapeSequences.Grey);
344342
output += flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine());
345343

src/compiler/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4017,8 +4017,14 @@ namespace ts {
40174017
export enum DiagnosticCategory {
40184018
Warning,
40194019
Error,
4020+
Suggestion,
40204021
Message
40214022
}
4023+
/* @internal */
4024+
export function diagnosticCategoryName(d: { category: DiagnosticCategory }, lowerCase = true): string {
4025+
const name = DiagnosticCategory[d.category];
4026+
return lowerCase ? name.toLowerCase() : name;
4027+
}
40224028

40234029
export enum ModuleResolutionKind {
40244030
Classic = 1,

src/harness/fourslash.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -506,8 +506,11 @@ namespace FourSlash {
506506
}
507507

508508
private getDiagnostics(fileName: string): ts.Diagnostic[] {
509-
return ts.concatenate(this.languageService.getSyntacticDiagnostics(fileName),
510-
this.languageService.getSemanticDiagnostics(fileName));
509+
return [
510+
...this.languageService.getSyntacticDiagnostics(fileName),
511+
...this.languageService.getSemanticDiagnostics(fileName),
512+
...this.languageService.getSuggestionDiagnostics(fileName),
513+
];
511514
}
512515

513516
private getAllDiagnostics(): ts.Diagnostic[] {
@@ -581,7 +584,7 @@ namespace FourSlash {
581584
public verifyNoErrors() {
582585
ts.forEachKey(this.inputFiles, fileName => {
583586
if (!ts.isAnySupportedFileExtension(fileName)) return;
584-
const errors = this.getDiagnostics(fileName);
587+
const errors = this.getDiagnostics(fileName).filter(e => e.category !== ts.DiagnosticCategory.Suggestion);
585588
if (errors.length) {
586589
this.printErrorLog(/*expectErrors*/ false, errors);
587590
const error = errors[0];
@@ -1246,6 +1249,10 @@ Actual: ${stringify(fullActual)}`);
12461249
this.testDiagnostics(expected, diagnostics);
12471250
}
12481251

1252+
public getSuggestionDiagnostics(expected: ReadonlyArray<ts.RealizedDiagnostic>): void {
1253+
this.testDiagnostics(expected, this.languageService.getSuggestionDiagnostics(this.activeFile.fileName));
1254+
}
1255+
12491256
private testDiagnostics(expected: ReadonlyArray<ts.RealizedDiagnostic>, diagnostics: ReadonlyArray<ts.Diagnostic>) {
12501257
assert.deepEqual(ts.realizeDiagnostics(diagnostics, ts.newLineCharacter), expected);
12511258
}
@@ -4327,6 +4334,10 @@ namespace FourSlashInterface {
43274334
this.state.getSemanticDiagnostics(expected);
43284335
}
43294336

4337+
public getSuggestionDiagnostics(expected: ReadonlyArray<ts.RealizedDiagnostic>) {
4338+
this.state.getSuggestionDiagnostics(expected);
4339+
}
4340+
43304341
public ProjectInfo(expected: string[]) {
43314342
this.state.verifyProjectInfo(expected);
43324343
}

src/harness/harness.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ namespace Utils {
242242
start: diagnostic.start,
243243
length: diagnostic.length,
244244
messageText: ts.flattenDiagnosticMessageText(diagnostic.messageText, Harness.IO.newLine()),
245-
category: (<any>ts).DiagnosticCategory[diagnostic.category],
245+
category: ts.diagnosticCategoryName(diagnostic, /*lowerCase*/ false),
246246
code: diagnostic.code
247247
};
248248
}
@@ -1376,7 +1376,7 @@ namespace Harness {
13761376
.split("\n")
13771377
.map(s => s.length > 0 && s.charAt(s.length - 1) === "\r" ? s.substr(0, s.length - 1) : s)
13781378
.filter(s => s.length > 0)
1379-
.map(s => "!!! " + ts.DiagnosticCategory[error.category].toLowerCase() + " TS" + error.code + ": " + s);
1379+
.map(s => "!!! " + ts.diagnosticCategoryName(error) + " TS" + error.code + ": " + s);
13801380
errLines.forEach(e => outputLines += (newLine() + e));
13811381
errorsReported++;
13821382

src/harness/harnessLanguageService.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,9 @@ namespace Harness.LanguageService {
402402
getSemanticDiagnostics(fileName: string): ts.Diagnostic[] {
403403
return unwrapJSONCallResult(this.shim.getSemanticDiagnostics(fileName));
404404
}
405+
getSuggestionDiagnostics(fileName: string): ts.Diagnostic[] {
406+
return unwrapJSONCallResult(this.shim.getSuggestionDiagnostics(fileName));
407+
}
405408
getCompilerOptionsDiagnostics(): ts.Diagnostic[] {
406409
return unwrapJSONCallResult(this.shim.getCompilerOptionsDiagnostics());
407410
}

src/harness/unittests/session.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ namespace ts.server {
216216
CommandNames.GeterrForProject,
217217
CommandNames.SemanticDiagnosticsSync,
218218
CommandNames.SyntacticDiagnosticsSync,
219+
CommandNames.SuggestionDiagnosticsSync,
219220
CommandNames.NavBar,
220221
CommandNames.NavBarFull,
221222
CommandNames.Navto,

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 87 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -467,12 +467,12 @@ namespace ts.projectSystem {
467467
verifyDiagnostics(actual, []);
468468
}
469469

470-
function checkErrorMessage(session: TestSession, eventName: "syntaxDiag" | "semanticDiag", diagnostics: protocol.DiagnosticEventBody) {
471-
checkNthEvent(session, ts.server.toEvent(eventName, diagnostics), 0, /*isMostRecent*/ false);
470+
function checkErrorMessage(session: TestSession, eventName: protocol.DiagnosticEventKind, diagnostics: protocol.DiagnosticEventBody, isMostRecent = false): void {
471+
checkNthEvent(session, ts.server.toEvent(eventName, diagnostics), 0, isMostRecent);
472472
}
473473

474-
function checkCompleteEvent(session: TestSession, numberOfCurrentEvents: number, expectedSequenceId: number) {
475-
checkNthEvent(session, ts.server.toEvent("requestCompleted", { request_seq: expectedSequenceId }), numberOfCurrentEvents - 1, /*isMostRecent*/ true);
474+
function checkCompleteEvent(session: TestSession, numberOfCurrentEvents: number, expectedSequenceId: number, isMostRecent = true): void {
475+
checkNthEvent(session, ts.server.toEvent("requestCompleted", { request_seq: expectedSequenceId }), numberOfCurrentEvents - 1, isMostRecent);
476476
}
477477

478478
function checkProjectUpdatedInBackgroundEvent(session: TestSession, openFiles: string[]) {
@@ -3076,8 +3076,13 @@ namespace ts.projectSystem {
30763076
host.runQueuedImmediateCallbacks();
30773077
assert.isFalse(hasError());
30783078
checkErrorMessage(session, "semanticDiag", { file: untitledFile, diagnostics: [] });
3079+
session.clearMessages();
30793080

3081+
host.runQueuedImmediateCallbacks(1);
3082+
assert.isFalse(hasError());
3083+
checkErrorMessage(session, "suggestionDiag", { file: untitledFile, diagnostics: [] });
30803084
checkCompleteEvent(session, 2, expectedSequenceId);
3085+
session.clearMessages();
30813086
}
30823087

30833088
it("has projectRoot", () => {
@@ -3136,6 +3141,10 @@ namespace ts.projectSystem {
31363141

31373142
host.runQueuedImmediateCallbacks();
31383143
checkErrorMessage(session, "semanticDiag", { file: app.path, diagnostics: [] });
3144+
session.clearMessages();
3145+
3146+
host.runQueuedImmediateCallbacks(1);
3147+
checkErrorMessage(session, "suggestionDiag", { file: app.path, diagnostics: [] });
31393148
checkCompleteEvent(session, 2, expectedSequenceId);
31403149
session.clearMessages();
31413150
}
@@ -3934,18 +3943,17 @@ namespace ts.projectSystem {
39343943
session.clearMessages();
39353944

39363945
host.runQueuedImmediateCallbacks();
3937-
const moduleNotFound = Diagnostics.Cannot_find_module_0;
39383946
const startOffset = file1.content.indexOf('"') + 1;
39393947
checkErrorMessage(session, "semanticDiag", {
3940-
file: file1.path, diagnostics: [{
3941-
start: { line: 1, offset: startOffset },
3942-
end: { line: 1, offset: startOffset + '"pad"'.length },
3943-
text: formatStringFromArgs(moduleNotFound.message, ["pad"]),
3944-
code: moduleNotFound.code,
3945-
category: DiagnosticCategory[moduleNotFound.category].toLowerCase(),
3946-
source: undefined
3947-
}]
3948+
file: file1.path,
3949+
diagnostics: [
3950+
createDiagnostic({ line: 1, offset: startOffset }, { line: 1, offset: startOffset + '"pad"'.length }, Diagnostics.Cannot_find_module_0, ["pad"])
3951+
],
39483952
});
3953+
session.clearMessages();
3954+
3955+
host.runQueuedImmediateCallbacks(1);
3956+
checkErrorMessage(session, "suggestionDiag", { file: file1.path, diagnostics: [] });
39493957
checkCompleteEvent(session, 2, expectedSequenceId);
39503958
session.clearMessages();
39513959

@@ -3966,6 +3974,63 @@ namespace ts.projectSystem {
39663974
host.runQueuedImmediateCallbacks();
39673975
checkErrorMessage(session, "semanticDiag", { file: file1.path, diagnostics: [] });
39683976
});
3977+
3978+
it("info diagnostics", () => {
3979+
const file: FileOrFolder = {
3980+
path: "/a.js",
3981+
content: 'require("b")',
3982+
};
3983+
3984+
const host = createServerHost([file]);
3985+
const session = createSession(host, { canUseEvents: true });
3986+
const service = session.getProjectService();
3987+
3988+
session.executeCommandSeq<protocol.OpenRequest>({
3989+
command: server.CommandNames.Open,
3990+
arguments: { file: file.path, fileContent: file.content },
3991+
});
3992+
3993+
checkNumberOfProjects(service, { inferredProjects: 1 });
3994+
session.clearMessages();
3995+
const expectedSequenceId = session.getNextSeq();
3996+
host.checkTimeoutQueueLengthAndRun(2);
3997+
3998+
checkProjectUpdatedInBackgroundEvent(session, [file.path]);
3999+
session.clearMessages();
4000+
4001+
session.executeCommandSeq<protocol.GeterrRequest>({
4002+
command: server.CommandNames.Geterr,
4003+
arguments: {
4004+
delay: 0,
4005+
files: [file.path],
4006+
}
4007+
});
4008+
4009+
host.checkTimeoutQueueLengthAndRun(1);
4010+
4011+
checkErrorMessage(session, "syntaxDiag", { file: file.path, diagnostics: [] }, /*isMostRecent*/ true);
4012+
session.clearMessages();
4013+
4014+
host.runQueuedImmediateCallbacks(1);
4015+
4016+
checkErrorMessage(session, "semanticDiag", { file: file.path, diagnostics: [] });
4017+
session.clearMessages();
4018+
4019+
host.runQueuedImmediateCallbacks(1);
4020+
4021+
checkErrorMessage(session, "suggestionDiag", {
4022+
file: file.path,
4023+
diagnostics: [
4024+
createDiagnostic({ line: 1, offset: 1 }, { line: 1, offset: 13 }, Diagnostics.File_is_a_CommonJS_module_it_may_be_converted_to_an_ES6_module)
4025+
],
4026+
});
4027+
checkCompleteEvent(session, 2, expectedSequenceId);
4028+
session.clearMessages();
4029+
});
4030+
4031+
function createDiagnostic(start: protocol.Location, end: protocol.Location, message: DiagnosticMessage, args: ReadonlyArray<string> = []): protocol.Diagnostic {
4032+
return { start, end, text: formatStringFromArgs(message.message, args), code: message.code, category: diagnosticCategoryName(message), source: undefined };
4033+
}
39694034
});
39704035

39714036
describe("tsserverProjectSystem Configure file diagnostics events", () => {
@@ -5154,9 +5219,15 @@ namespace ts.projectSystem {
51545219

51555220
// the semanticDiag message
51565221
host.runQueuedImmediateCallbacks();
5157-
assert.equal(host.getOutput().length, 2, "expect 2 messages");
5222+
assert.equal(host.getOutput().length, 1);
51585223
const e2 = <protocol.Event>getMessage(0);
51595224
assert.equal(e2.event, "semanticDiag");
5225+
session.clearMessages();
5226+
5227+
host.runQueuedImmediateCallbacks(1);
5228+
assert.equal(host.getOutput().length, 2);
5229+
const e3 = <protocol.Event>getMessage(0);
5230+
assert.equal(e3.event, "suggestionDiag");
51605231
verifyRequestCompleted(getErrId, 1);
51615232

51625233
cancellationToken.resetToken();
@@ -5194,6 +5265,7 @@ namespace ts.projectSystem {
51945265
return JSON.parse(server.extractMessage(host.getOutput()[n]));
51955266
}
51965267
});
5268+
51975269
it("Lower priority tasks are cancellable", () => {
51985270
const f1 = {
51995271
path: "/a/app.ts",
@@ -5495,7 +5567,7 @@ namespace ts.projectSystem {
54955567
}
54965568
type CalledMaps = CalledMapsWithSingleArg | CalledMapsWithFiveArgs;
54975569
function createCallsTrackingHost(host: TestServerHost) {
5498-
const calledMaps: Record<CalledMapsWithSingleArg, MultiMap<true>> & Record<CalledMapsWithFiveArgs, MultiMap<[ReadonlyArray<string>, ReadonlyArray<string>, ReadonlyArray<string>, number]>> = {
5570+
const calledMaps: Record<CalledMapsWithSingleArg, MultiMap<true>> & Record<CalledMapsWithFiveArgs, MultiMap<[ReadonlyArray<string>, ReadonlyArray<string>, ReadonlyArray<string>, number]>> = {
54995571
fileExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.fileExists),
55005572
directoryExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.directoryExists),
55015573
getDirectories: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.getDirectories),

src/harness/virtualFileSystemWithWatch.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -708,7 +708,10 @@ interface Array<T> {}`
708708
}
709709
}
710710

711-
runQueuedImmediateCallbacks() {
711+
runQueuedImmediateCallbacks(checkCount?: number) {
712+
if (checkCount !== undefined) {
713+
assert.equal(this.immediateCallbacks.count(), checkCount);
714+
}
712715
this.immediateCallbacks.invoke();
713716
}
714717

src/server/client.ts

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ namespace ts.server {
7575
return { span: this.decodeSpan(codeEdit, fileName), newText: codeEdit.newText };
7676
}
7777

78-
private processRequest<T extends protocol.Request>(command: string, args?: any): T {
78+
private processRequest<T extends protocol.Request>(command: string, args?: T["arguments"]): T {
7979
const request: protocol.Request = {
8080
seq: this.sequence,
8181
type: "request",
@@ -343,41 +343,31 @@ namespace ts.server {
343343
}
344344

345345
getSyntacticDiagnostics(file: string): Diagnostic[] {
346-
const args: protocol.SyntacticDiagnosticsSyncRequestArgs = { file, includeLinePosition: true };
347-
348-
const request = this.processRequest<protocol.SyntacticDiagnosticsSyncRequest>(CommandNames.SyntacticDiagnosticsSync, args);
349-
const response = this.processResponse<protocol.SyntacticDiagnosticsSyncResponse>(request);
350-
351-
return (<protocol.DiagnosticWithLinePosition[]>response.body).map(entry => this.convertDiagnostic(entry, file));
346+
return this.getDiagnostics(file, CommandNames.SyntacticDiagnosticsSync);
352347
}
353-
354348
getSemanticDiagnostics(file: string): Diagnostic[] {
355-
const args: protocol.SemanticDiagnosticsSyncRequestArgs = { file, includeLinePosition: true };
356-
357-
const request = this.processRequest<protocol.SemanticDiagnosticsSyncRequest>(CommandNames.SemanticDiagnosticsSync, args);
358-
const response = this.processResponse<protocol.SemanticDiagnosticsSyncResponse>(request);
359-
360-
return (<protocol.DiagnosticWithLinePosition[]>response.body).map(entry => this.convertDiagnostic(entry, file));
349+
return this.getDiagnostics(file, CommandNames.SemanticDiagnosticsSync);
350+
}
351+
getSuggestionDiagnostics(file: string): Diagnostic[] {
352+
return this.getDiagnostics(file, CommandNames.SuggestionDiagnosticsSync);
361353
}
362354

363-
convertDiagnostic(entry: protocol.DiagnosticWithLinePosition, _fileName: string): Diagnostic {
364-
let category: DiagnosticCategory;
365-
for (const id in DiagnosticCategory) {
366-
if (isString(id) && entry.category === id.toLowerCase()) {
367-
category = (<any>DiagnosticCategory)[id];
368-
}
369-
}
370-
371-
Debug.assert(category !== undefined, "convertDiagnostic: category should not be undefined");
355+
private getDiagnostics(file: string, command: CommandNames) {
356+
const request = this.processRequest<protocol.SyntacticDiagnosticsSyncRequest | protocol.SemanticDiagnosticsSyncRequest | protocol.SuggestionDiagnosticsSyncRequest>(command, { file, includeLinePosition: true });
357+
const response = this.processResponse<protocol.SyntacticDiagnosticsSyncResponse | protocol.SemanticDiagnosticsSyncResponse | protocol.SuggestionDiagnosticsSyncResponse>(request);
372358

373-
return {
374-
file: undefined,
375-
start: entry.start,
376-
length: entry.length,
377-
messageText: entry.message,
378-
category,
379-
code: entry.code
380-
};
359+
return (<protocol.DiagnosticWithLinePosition[]>response.body).map(entry => {
360+
const category = firstDefined(Object.keys(DiagnosticCategory), id =>
361+
isString(id) && entry.category === id.toLowerCase() ? (<any>DiagnosticCategory)[id] : undefined);
362+
return {
363+
file: undefined,
364+
start: entry.start,
365+
length: entry.length,
366+
messageText: entry.message,
367+
category: Debug.assertDefined(category, "convertDiagnostic: category should not be undefined"),
368+
code: entry.code
369+
};
370+
});
381371
}
382372

383373
getCompilerOptionsDiagnostics(): Diagnostic[] {

0 commit comments

Comments
 (0)