Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce related spans into tsserver protocol #24548

Merged
merged 3 commits into from
Jun 15, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4238,6 +4238,13 @@ namespace ts {
reportsUnnecessary?: {};
code: number;
source?: string;
relatedInformation?: DiagnosticRelatedInformation[];
}
export interface DiagnosticRelatedInformation {
file: SourceFile | undefined;
start: number | undefined;
length: number | undefined;
messageText: string | DiagnosticMessageChain;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

messageText: string | DiagnosticMessageChain; [](start = 8, length = 45)

I think I need more information about how these are intended to be used. Roslyn diagnostics support additional locations, but those locations don't have messages. We could create separate diagnostics but we would need a new code and category and I don't believe we'd be able to link them together. I think Roslyn more or less dropped this functionality (vs the old native compiler).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Secondary code locations and more information pertaining to specifically that location in the context of the diagnostic. (eg, "Defined here", "Declared here", "Replicated here", and so on. The additional spans also allow us to use those spans when calculating quick fixes - so we can, for example, know what we can suggest a cast at the site of an assignability error, but also suggest modifying the type the assignment is failing against (because it is mentioned as a related span). The only diagnostic set we currently do a similar thing with is the esModuleInterop import error, which we currently issue a secondary diagnostic for (so there's no obvious link between the two).

Copy link
Member Author

@weswigham weswigham Jun 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference, this isn't a new concept and there is prior art, which is why vscode has basic support for them (and is looking to iterate on their UI) - rust makes good use of related spans in it's errors.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not saying that it's either novel or bad, just that it goes against the grain of the API we're using.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to sit down and design this feature.. there seems to be two uses cases,

  1. additional locations, e.g. duplicate identifier errors, you would list them all in the same error message
  2. related diagnostics, e.g. type of var x is not assignable to string, and here is where x was declated

}
export interface DiagnosticWithLocation extends Diagnostic {
file: SourceFile;
Expand Down
4 changes: 2 additions & 2 deletions src/harness/unittests/tsserverProjectSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,8 +495,8 @@ namespace ts.projectSystem {
checkNthEvent(session, server.toEvent(eventName, diagnostics), 0, isMostRecent);
}

function createDiagnostic(start: protocol.Location, end: protocol.Location, message: DiagnosticMessage, args: ReadonlyArray<string> = [], category = diagnosticCategoryName(message), reportsUnnecessary?: {}): protocol.Diagnostic {
return { start, end, text: formatStringFromArgs(message.message, args), code: message.code, category, reportsUnnecessary, source: undefined };
function createDiagnostic(start: protocol.Location, end: protocol.Location, message: DiagnosticMessage, args: ReadonlyArray<string> = [], category = diagnosticCategoryName(message), reportsUnnecessary?: {}, relatedInformation?: protocol.DiagnosticRelatedInformation[]): protocol.Diagnostic {
return { start, end, text: formatStringFromArgs(message.message, args), code: message.code, category, reportsUnnecessary, relatedInformation, source: undefined };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

relatedInformation [](start = 130, length = 18)

Should this new property be validated in some way?

Copy link
Member Author

@weswigham weswigham Jun 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing has been modified to produce one yet, though we have a massive list courtesy of @DanielRosenwasser - this just makes it so we can start chipping that list away.

}

function checkCompleteEvent(session: TestSession, numberOfCurrentEvents: number, expectedSequenceId: number, isMostRecent = true): void {
Expand Down
23 changes: 23 additions & 0 deletions src/server/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ namespace ts.server.protocol {
code: number;
/** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */
reportsUnnecessary?: {};
relatedInformation?: DiagnosticRelatedInformation[];
}

/**
Expand Down Expand Up @@ -2203,6 +2204,11 @@ namespace ts.server.protocol {

reportsUnnecessary?: {};

/**
* Any related spans the diagnostic may have, such as other locations relevant to an error, such as declarartion sites
*/
relatedInformation?: DiagnosticRelatedInformation[];

/**
* The error code of the diagnostic message.
*/
Expand All @@ -2221,6 +2227,23 @@ namespace ts.server.protocol {
fileName: string;
}

/**
* Represents additional spans returned with a diagnostic which are relevant to it
* Like DiagnosticWithLinePosition, this is provided in two forms:
* - start and length of the span
* - startLocation and endLocation a pair of Location objects storing the start/end line offset of the span
*/
export interface DiagnosticRelatedInformation {
/**
* Text of related or additional information.
*/
message: string;
/**
* Associated location
*/
span?: FileSpan;
}

export interface DiagnosticEventBody {
/**
* The file for which diagnostic information is reported.
Expand Down
40 changes: 35 additions & 5 deletions src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,24 @@ namespace ts.server {
code: diag.code,
category: diagnosticCategoryName(diag),
reportsUnnecessary: diag.reportsUnnecessary,
source: diag.source
source: diag.source,
relatedInformation: map(diag.relatedInformation, formatRelatedInformation),
};
}

function formatRelatedInformation(info: DiagnosticRelatedInformation): protocol.DiagnosticRelatedInformation {
if (!info.file) {
return {
message: flattenDiagnosticMessageText(info.messageText, "\n")
};
}
return {
span: {
start: convertToLocation(getLineAndCharacterOfPosition(info.file, info.start!)),
end: convertToLocation(getLineAndCharacterOfPosition(info.file, info.start! + info.length!)), // TODO: GH#18217
file: info.file.fileName
},
message: flattenDiagnosticMessageText(info.messageText, "\n")
};
}

Expand All @@ -92,8 +109,19 @@ namespace ts.server {
const text = flattenDiagnosticMessageText(diag.messageText, "\n");
const { code, source } = diag;
const category = diagnosticCategoryName(diag);
return includeFileName ? { start, end, text, code, category, source, reportsUnnecessary: diag.reportsUnnecessary, fileName: diag.file && diag.file.fileName } :
{ start, end, text, code, category, reportsUnnecessary: diag.reportsUnnecessary, source };
const common = {
start,
end,
text,
code,
category,
reportsUnnecessary: diag.reportsUnnecessary,
source,
relatedInformation: map(diag.relatedInformation, formatRelatedInformation),
};
return includeFileName
? { ...common, fileName: diag.file && diag.file.fileName }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to create an object just to inline it.. you can set fileName to undefined

: common;
}

export interface PendingErrorCheck {
Expand Down Expand Up @@ -610,7 +638,8 @@ namespace ts.server {
category: diagnosticCategoryName(d),
code: d.code,
startLocation: (d.file && convertToLocation(getLineAndCharacterOfPosition(d.file, d.start!)))!, // TODO: GH#18217
endLocation: (d.file && convertToLocation(getLineAndCharacterOfPosition(d.file, d.start! + d.length!)))! // TODO: GH#18217
endLocation: (d.file && convertToLocation(getLineAndCharacterOfPosition(d.file, d.start! + d.length!)))!, // TODO: GH#18217
relatedInformation: map(d.relatedInformation, formatRelatedInformation)
}));
}

Expand Down Expand Up @@ -638,7 +667,8 @@ namespace ts.server {
source: d.source,
startLocation: scriptInfo && scriptInfo.positionToLineOffset(d.start!), // TODO: GH#18217
endLocation: scriptInfo && scriptInfo.positionToLineOffset(d.start! + d.length!),
reportsUnnecessary: d.reportsUnnecessary
reportsUnnecessary: d.reportsUnnecessary,
relatedInformation: map(d.relatedInformation, formatRelatedInformation),
});
}

Expand Down
28 changes: 28 additions & 0 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2362,6 +2362,13 @@ declare namespace ts {
reportsUnnecessary?: {};
code: number;
source?: string;
relatedInformation?: DiagnosticRelatedInformation[];
}
interface DiagnosticRelatedInformation {
file: SourceFile | undefined;
start: number | undefined;
length: number | undefined;
messageText: string | DiagnosticMessageChain;
}
interface DiagnosticWithLocation extends Diagnostic {
file: SourceFile;
Expand Down Expand Up @@ -5838,6 +5845,7 @@ declare namespace ts.server.protocol {
code: number;
/** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */
reportsUnnecessary?: {};
relatedInformation?: DiagnosticRelatedInformation[];
}
/**
* Response message for "projectInfo" request
Expand Down Expand Up @@ -7204,6 +7212,10 @@ declare namespace ts.server.protocol {
*/
category: string;
reportsUnnecessary?: {};
/**
* Any related spans the diagnostic may have, such as other locations relevant to an error, such as declarartion sites
*/
relatedInformation?: DiagnosticRelatedInformation[];
/**
* The error code of the diagnostic message.
*/
Expand All @@ -7219,6 +7231,22 @@ declare namespace ts.server.protocol {
*/
fileName: string;
}
/**
* Represents additional spans returned with a diagnostic which are relevant to it
* Like DiagnosticWithLinePosition, this is provided in two forms:
* - start and length of the span
* - startLocation and endLocation a pair of Location objects storing the start/end line offset of the span
*/
interface DiagnosticRelatedInformation {
/**
* Text of related or additional information.
*/
message: string;
/**
* Associated location
*/
span?: FileSpan;
}
interface DiagnosticEventBody {
/**
* The file for which diagnostic information is reported.
Expand Down
7 changes: 7 additions & 0 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2362,6 +2362,13 @@ declare namespace ts {
reportsUnnecessary?: {};
code: number;
source?: string;
relatedInformation?: DiagnosticRelatedInformation[];
}
interface DiagnosticRelatedInformation {
file: SourceFile | undefined;
start: number | undefined;
length: number | undefined;
messageText: string | DiagnosticMessageChain;
}
interface DiagnosticWithLocation extends Diagnostic {
file: SourceFile;
Expand Down