Fix linking Rust error messages to source (#8345)

The compiler output pane only links error messages to the source editor
when a filename is given. `parseRustOutput` didn’t parse the filename,
so Rust error messages were never linked.

This PR also simplifies the regex used to parse the `-->
filename:line:column` line in the `rustc` output. As far as I’m aware
(and I checked the history of the corresponding output in the `rustc`
source), the output format will always be `--> filename:line:column` and
not anything else accepted by the previous regex (no parentheses around
the line number, the column is not optional).
This commit is contained in:
narpfel
2025-12-20 12:54:45 +01:00
committed by GitHub
parent 7b9bdb9d4e
commit 8afd059943
2 changed files with 85 additions and 18 deletions

View File

@@ -255,6 +255,7 @@ export function parseOutput(
}
export function parseRustOutput(lines: string, inputFilename?: string, pathPrefix?: string) {
const inputBasename = inputFilename ? path.basename(inputFilename) : undefined;
const quickfixes: {re: RegExp; makeFix: (match: string[]) => Fix}[] = [
{
re: / *help: add `#!\[feature\((.*?)\)]`/,
@@ -288,7 +289,7 @@ export function parseRustOutput(lines: string, inputFilename?: string, pathPrefi
},
];
const re = /^\s+-->\s+<source>[(:](\d+)(:?,?(\d+):?)?[):]*\s*(.*)/;
const re = /^\s+-->\s+(?<filename>.*):(?<line>\d+):(?<column>\d+)/;
const result: ResultLine[] = [];
let currentDiagnostic: ResultLine | undefined;
eachLine(lines, line => {
@@ -298,14 +299,17 @@ export function parseRustOutput(lines: string, inputFilename?: string, pathPrefi
const filteredLine = filterEscapeSequences(line);
const match = filteredLine.match(re);
if (match) {
const line = Number.parseInt(match[1], 10);
const column = Number.parseInt(match[3] || '0', 10);
if (match?.groups) {
const file =
match.groups.filename === '<source>' ? inputBasename : path.basename(match.groups.filename);
const line = Number.parseInt(match.groups.line, 10);
const column = Number.parseInt(match.groups.column, 10);
currentDiagnostic = result.pop();
if (currentDiagnostic !== undefined) {
const text = filterEscapeSequences(currentDiagnostic.text);
currentDiagnostic.tag = {
file,
line,
column,
text,
@@ -316,6 +320,7 @@ export function parseRustOutput(lines: string, inputFilename?: string, pathPrefi
}
lineObj.tag = {
file,
line,
column,
text: '', // Left empty so that it does not show up in the editor

View File

@@ -247,49 +247,49 @@ describe('Pascal compiler output', () => {
describe('Rust compiler output', () => {
it('handles simple cases', () => {
expect(utils.parseRustOutput('Line one\nLine two', 'bob.rs')).toEqual([{text: 'Line one'}, {text: 'Line two'}]);
expect(utils.parseRustOutput('Unrelated\nLine one\n --> bob.rs:1\nUnrelated', 'bob.rs')).toEqual([
expect(utils.parseRustOutput('Unrelated\nLine one\n --> bob.rs:1:0\nUnrelated', 'bob.rs')).toEqual([
{text: 'Unrelated'},
{
tag: {column: 0, line: 1, text: 'Line one', severity: 3, fixes: []},
tag: {file: 'bob.rs', column: 0, line: 1, text: 'Line one', severity: 3, fixes: []},
text: 'Line one',
},
{
tag: {column: 0, line: 1, text: '', severity: 3},
text: ' --> <source>:1',
tag: {file: 'bob.rs', column: 0, line: 1, text: '', severity: 3},
text: ' --> <source>:1:0',
},
{text: 'Unrelated'},
]);
expect(utils.parseRustOutput('Line one\n --> bob.rs:1:5', 'bob.rs')).toEqual([
{
tag: {column: 5, line: 1, text: 'Line one', severity: 3, fixes: []},
tag: {file: 'bob.rs', column: 5, line: 1, text: 'Line one', severity: 3, fixes: []},
text: 'Line one',
},
{
tag: {column: 5, line: 1, text: '', severity: 3},
tag: {file: 'bob.rs', column: 5, line: 1, text: '', severity: 3},
text: ' --> <source>:1:5',
},
]);
expect(utils.parseRustOutput('Multiple spaces\n --> bob.rs:1:5', 'bob.rs')).toEqual([
{
tag: {column: 5, line: 1, text: 'Multiple spaces', severity: 3, fixes: []},
tag: {file: 'bob.rs', column: 5, line: 1, text: 'Multiple spaces', severity: 3, fixes: []},
text: 'Multiple spaces',
},
{
tag: {column: 5, line: 1, text: '', severity: 3},
tag: {file: 'bob.rs', column: 5, line: 1, text: '', severity: 3},
text: ' --> <source>:1:5',
},
]);
});
it('replaces all references to input source', () => {
expect(utils.parseRustOutput('error: Error in bob.rs\n --> bob.rs:1', 'bob.rs')).toEqual([
expect(utils.parseRustOutput('error: Error in bob.rs\n --> bob.rs:1:42', 'bob.rs')).toEqual([
{
tag: {column: 0, line: 1, text: 'error: Error in <source>', severity: 3, fixes: []},
tag: {file: 'bob.rs', column: 42, line: 1, text: 'error: Error in <source>', severity: 3, fixes: []},
text: 'error: Error in <source>',
},
{
tag: {column: 0, line: 1, text: '', severity: 3},
text: ' --> <source>:1',
tag: {file: 'bob.rs', column: 42, line: 1, text: '', severity: 3},
text: ' --> <source>:1:42',
},
]);
});
@@ -297,11 +297,11 @@ describe('Rust compiler output', () => {
it('treats <stdin> as if it were the compiler source', () => {
expect(utils.parseRustOutput('error: <stdin> is sad\n --> <stdin>:120:25', 'bob.rs')).toEqual([
{
tag: {column: 25, line: 120, text: 'error: <source> is sad', severity: 3, fixes: []},
tag: {file: 'bob.rs', column: 25, line: 120, text: 'error: <source> is sad', severity: 3, fixes: []},
text: 'error: <source> is sad',
},
{
tag: {column: 25, line: 120, text: '', severity: 3},
tag: {file: 'bob.rs', column: 25, line: 120, text: '', severity: 3},
text: ' --> <source>:120:25',
},
]);
@@ -412,6 +412,68 @@ describe('Rust compiler output', () => {
{text: ' = help: add `#![feature(num_midpoint_signed)]` to the crate attributes to enable'},
]);
});
it('attaches filenames to errors from multiple files', () => {
// https://godbolt.org/z/nWE3PTeGf
expect(
utils.parseRustOutput(
`warning: function \`f1\` is never used
--> <source>:3:4
warning: function \`f2\` is never used
--> /app/m.rs:1:4
warning: 2 warnings emitted`,
'example.rs',
),
).toEqual([
{
text: 'warning: function `f1` is never used',
tag: {
line: 3,
column: 4,
file: 'example.rs',
severity: 2,
text: 'warning: function `f1` is never used',
fixes: [],
},
},
{
text: ' --> <source>:3:4',
tag: {
line: 3,
column: 4,
file: 'example.rs',
severity: 3,
text: '',
},
},
{text: ''},
{
text: 'warning: function `f2` is never used',
tag: {
line: 1,
column: 4,
file: 'm.rs',
severity: 2,
text: 'warning: function `f2` is never used',
fixes: [],
},
},
{
text: ' --> /app/m.rs:1:4',
tag: {
line: 1,
column: 4,
file: 'm.rs',
severity: 3,
text: '',
},
},
{text: ''},
{text: 'warning: 2 warnings emitted'},
]);
});
});
describe('Tool output', () => {