Skip to content

Commit 74b8233

Browse files
authored
Fix location for diagnostics generated from macros via swiftc (#1234)
* Fix location for diagnostics generated from macros via swiftc The format of messages generated from macro errors/warnings is different than those generated normally. The parsing regex would capture the location for the related info diagnostic with a backtick. The location for the parent regex would be "#macro expansion" which when clicked would open an error window in a VS Code tab stating that the file "#macro expansion" could not be found. This patch tweaks two things: 1. Makes the regex for capturing the filepaths of warnings/errors account for leading trivia 2. If the path for a diagnostic is invalid, wait until we parse the related infromation and use the URI from there. * No Testing on Swift < 6.0
1 parent f99bc17 commit 74b8233

File tree

4 files changed

+67
-5
lines changed

4 files changed

+67
-5
lines changed

assets/test/.vscode/settings.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,4 @@
77
"-DTEST_ARGUMENT_SET_VIA_TEST_BUILD_ARGUMENTS_SETTING"
88
],
99
"lldb.verboseLogging": true
10-
1110
}

assets/test/diagnostics/Sources/main.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,8 @@ repeat {
1212
line = readLine()
1313
print(line ?? "nil")
1414
} while line != nil;
15+
16+
#if canImport(Testing)
17+
import Testing
18+
#expect(try myFunc() != 0)
19+
#endif

src/DiagnosticsManager.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import * as vscode from "vscode";
16+
import * as fs from "fs";
1617
// eslint-disable-next-line @typescript-eslint/no-require-imports
1718
import stripAnsi = require("strip-ansi");
1819
import configuration from "./configuration";
@@ -255,6 +256,7 @@ export class DiagnosticsManager implements vscode.Disposable {
255256
};
256257
let remainingData: string | undefined;
257258
let lastDiagnostic: vscode.Diagnostic | undefined;
259+
let lastDiagnosticNeedsSaving = false;
258260
disposables.push(
259261
swiftExecution.onDidWrite(data => {
260262
const sanitizedData = (remainingData || "") + stripAnsi(data);
@@ -293,6 +295,18 @@ export class DiagnosticsManager implements vscode.Disposable {
293295
lastDiagnostic.relatedInformation = (
294296
lastDiagnostic.relatedInformation || []
295297
).concat(relatedInformation);
298+
299+
if (lastDiagnosticNeedsSaving) {
300+
const expandedUri = relatedInformation.location.uri.fsPath;
301+
const currentUriDiagnostics = diagnostics.get(expandedUri) || [];
302+
lastDiagnostic.range = relatedInformation.location.range;
303+
diagnostics.set(expandedUri, [
304+
...currentUriDiagnostics,
305+
lastDiagnostic,
306+
]);
307+
308+
lastDiagnosticNeedsSaving = false;
309+
}
296310
continue;
297311
}
298312
const { uri, diagnostic } = result as ParsedDiagnostic;
@@ -312,18 +326,36 @@ export class DiagnosticsManager implements vscode.Disposable {
312326
continue;
313327
}
314328
lastDiagnostic = diagnostic;
315-
diagnostics.set(uri, [...currentUriDiagnostics, diagnostic]);
329+
330+
// If the diagnostic comes from a macro expansion the URI is going to be an invalid URI.
331+
// Save the diagnostic for when we get the related information which has the macro expansion location
332+
// that should be used as the correct URI.
333+
if (this.isValidUri(uri)) {
334+
diagnostics.set(uri, [...currentUriDiagnostics, diagnostic]);
335+
} else {
336+
lastDiagnosticNeedsSaving = true;
337+
}
316338
}
317339
}),
318340
swiftExecution.onDidClose(done)
319341
);
320342
});
321343
}
322344

345+
private isValidUri(uri: string): boolean {
346+
try {
347+
fs.accessSync(uri, fs.constants.F_OK);
348+
return true;
349+
} catch {
350+
return false;
351+
}
352+
}
353+
323354
private parseDiagnostic(
324355
line: string
325356
): ParsedDiagnostic | vscode.DiagnosticRelatedInformation | undefined {
326-
const diagnosticRegex = /^(.*?):(\d+)(?::(\d+))?:\s+(warning|error|note):\s+([^\\[]*)/g;
357+
const diagnosticRegex =
358+
/^(?:\S+\s+)?(.*?):(\d+)(?::(\d+))?:\s+(warning|error|note):\s+([^\\[]*)/g;
327359
const match = diagnosticRegex.exec(line);
328360
if (!match) {
329361
return;

test/integration-tests/DiagnosticsManager.test.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ function assertHasDiagnostic(uri: vscode.Uri, expected: vscode.Diagnostic): vsco
5656
assert.notEqual(
5757
diagnostic,
5858
undefined,
59-
`Could not find diagnostic matching:\n${JSON.stringify(expected)}`
59+
`Could not find diagnostic matching:\n${JSON.stringify(expected)}\nDiagnostics:\n${JSON.stringify(diagnostics)}`
6060
);
6161
return diagnostic!;
6262
}
@@ -66,7 +66,7 @@ function assertWithoutDiagnostic(uri: vscode.Uri, expected: vscode.Diagnostic) {
6666
assert.equal(
6767
diagnostics.find(findDiagnostic(expected)),
6868
undefined,
69-
`Unexpected diagnostic matching:\n${JSON.stringify(expected)}`
69+
`Unexpected diagnostic matching:\n${JSON.stringify(expected)}\nDiagnostics:\n${JSON.stringify(diagnostics)}`
7070
);
7171
}
7272

@@ -136,6 +136,23 @@ suite("DiagnosticsManager Test Suite", async function () {
136136
);
137137
expectedFuncErrorDiagnostic.source = "swiftc";
138138

139+
const expectedMacroDiagnostic = new vscode.Diagnostic(
140+
new vscode.Range(new vscode.Position(17, 26), new vscode.Position(17, 26)),
141+
"No calls to throwing functions occur within 'try' expression",
142+
vscode.DiagnosticSeverity.Warning
143+
);
144+
145+
expectedMacroDiagnostic.source = "swiftc";
146+
expectedMacroDiagnostic.relatedInformation = [
147+
{
148+
location: {
149+
uri: mainUri,
150+
range: expectedMacroDiagnostic.range,
151+
},
152+
message: "Expanded code originates here",
153+
},
154+
];
155+
139156
// SourceKit-LSP sometimes sends diagnostics
140157
// after first build and can cause intermittent
141158
// failure if `swiftc` diagnostic is fixed
@@ -161,6 +178,9 @@ suite("DiagnosticsManager Test Suite", async function () {
161178
// Should have parsed correct severity
162179
assertHasDiagnostic(mainUri, expectedWarningDiagnostic);
163180
assertHasDiagnostic(mainUri, expectedMainErrorDiagnostic);
181+
if (workspaceContext.swiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0))) {
182+
assertHasDiagnostic(mainUri, expectedMacroDiagnostic);
183+
}
164184
// Check parsed for other file
165185
assertHasDiagnostic(funcUri, expectedFuncErrorDiagnostic);
166186
}).timeout(2 * 60 * 1000); // Allow 2 minutes to build
@@ -183,6 +203,9 @@ suite("DiagnosticsManager Test Suite", async function () {
183203
// Should have parsed severity
184204
assertHasDiagnostic(mainUri, expectedWarningDiagnostic);
185205
assertHasDiagnostic(mainUri, expectedMainErrorDiagnostic);
206+
if (workspaceContext.swiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0))) {
207+
assertHasDiagnostic(mainUri, expectedMacroDiagnostic);
208+
}
186209
// Check parsed for other file
187210
assertHasDiagnostic(funcUri, expectedFuncErrorDiagnostic);
188211
}).timeout(2 * 60 * 1000); // Allow 2 minutes to build
@@ -198,6 +221,9 @@ suite("DiagnosticsManager Test Suite", async function () {
198221

199222
// Should have parsed severity
200223
assertHasDiagnostic(mainUri, expectedWarningDiagnostic);
224+
225+
// llvm style doesn't do macro diagnostics
226+
assertWithoutDiagnostic(mainUri, expectedMacroDiagnostic);
201227
const diagnostic = assertHasDiagnostic(mainUri, expectedMainErrorDiagnostic);
202228
// Should have parsed related note
203229
assert.equal(diagnostic.relatedInformation?.length, 1);

0 commit comments

Comments
 (0)