Skip to content

Commit 9f0c120

Browse files
authored
Don't underline the entire schema for top-level errors/warnings (#162)
Signed-off-by: Juan Cruz Viotti <[email protected]>
1 parent 8e8daa3 commit 9f0c120

File tree

4 files changed

+150
-0
lines changed

4 files changed

+150
-0
lines changed

test/vscode/extension.test.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,135 @@ suite('Extension Test Suite', () => {
214214
'Sourcemeta Studio should still report metaschema errors');
215215
});
216216

217+
test('Should clamp root-level lint diagnostics to first token', async function() {
218+
this.timeout(15000);
219+
220+
const extension = vscode.extensions.getExtension('sourcemeta.sourcemeta-studio');
221+
if (extension && !extension.isActive) {
222+
await extension.activate();
223+
}
224+
225+
const fixtureDir = path.join(__dirname, '..', '..', '..', 'test', 'vscode', 'fixtures');
226+
const schemaPath = path.join(fixtureDir, 'root-only-lint-schema.json');
227+
228+
const document = await vscode.workspace.openTextDocument(vscode.Uri.file(schemaPath));
229+
await vscode.window.showTextDocument(document);
230+
231+
await vscode.commands.executeCommand('sourcemeta-studio.openPanel');
232+
233+
await new Promise(resolve => setTimeout(resolve, 5000));
234+
235+
const diagnostics = vscode.languages.getDiagnostics(document.uri);
236+
237+
const lintDiagnostics = diagnostics.filter(diagnostic =>
238+
diagnostic.source === 'Sourcemeta Studio (Lint)');
239+
240+
assert.ok(lintDiagnostics.length > 0,
241+
'Root-level lint issues should still produce diagnostics');
242+
243+
for (const diagnostic of lintDiagnostics) {
244+
assert.strictEqual(diagnostic.range.start.line, 0,
245+
'Root-level diagnostic should start at line 0');
246+
assert.strictEqual(diagnostic.range.start.character, 0,
247+
'Root-level diagnostic should start at character 0');
248+
assert.strictEqual(diagnostic.range.end.line, 0,
249+
'Root-level diagnostic should not extend beyond line 0');
250+
assert.strictEqual(diagnostic.range.end.character, 0,
251+
'Root-level diagnostic should have zero-width range');
252+
}
253+
});
254+
255+
test('Should clamp root-level lint diagnostics on minified single-line files', async function() {
256+
this.timeout(15000);
257+
258+
const extension = vscode.extensions.getExtension('sourcemeta.sourcemeta-studio');
259+
if (extension && !extension.isActive) {
260+
await extension.activate();
261+
}
262+
263+
const fixtureDir = path.join(__dirname, '..', '..', '..', 'test', 'vscode', 'fixtures');
264+
const schemaPath = path.join(fixtureDir, 'minified-root-lint-schema.json');
265+
266+
const document = await vscode.workspace.openTextDocument(vscode.Uri.file(schemaPath));
267+
await vscode.window.showTextDocument(document);
268+
269+
await vscode.commands.executeCommand('sourcemeta-studio.openPanel');
270+
271+
await new Promise(resolve => setTimeout(resolve, 5000));
272+
273+
const diagnostics = vscode.languages.getDiagnostics(document.uri);
274+
275+
const lintDiagnostics = diagnostics.filter(diagnostic =>
276+
diagnostic.source === 'Sourcemeta Studio (Lint)');
277+
278+
assert.ok(lintDiagnostics.length > 0,
279+
'Minified schema should still produce root-level lint diagnostics');
280+
281+
for (const diagnostic of lintDiagnostics) {
282+
assert.strictEqual(diagnostic.range.start.line, 0,
283+
'Root-level diagnostic should start at line 0');
284+
assert.strictEqual(diagnostic.range.start.character, 0,
285+
'Root-level diagnostic should start at character 0');
286+
assert.strictEqual(diagnostic.range.end.line, 0,
287+
'Root-level diagnostic should not extend beyond line 0');
288+
assert.strictEqual(diagnostic.range.end.character, 0,
289+
'Root-level diagnostic should have zero-width range');
290+
}
291+
});
292+
293+
test('Should clamp root-level metaschema diagnostics to first token', async function() {
294+
this.timeout(15000);
295+
296+
const extension = vscode.extensions.getExtension('sourcemeta.sourcemeta-studio');
297+
if (extension && !extension.isActive) {
298+
await extension.activate();
299+
}
300+
301+
const fixtureDir = path.join(__dirname, '..', '..', '..', 'test', 'vscode', 'fixtures');
302+
const schemaPath = path.join(fixtureDir, 'invalid-metaschema.json');
303+
304+
const document = await vscode.workspace.openTextDocument(vscode.Uri.file(schemaPath));
305+
await vscode.window.showTextDocument(document);
306+
307+
await vscode.commands.executeCommand('sourcemeta-studio.openPanel');
308+
309+
await new Promise(resolve => setTimeout(resolve, 5000));
310+
311+
const diagnostics = vscode.languages.getDiagnostics(document.uri);
312+
313+
const metaschemaDiagnostics = diagnostics.filter(diagnostic =>
314+
diagnostic.source === 'Sourcemeta Studio (Metaschema)');
315+
316+
assert.ok(metaschemaDiagnostics.length > 0,
317+
'Metaschema errors should produce diagnostics');
318+
319+
const rootDiagnostics = metaschemaDiagnostics.filter(diagnostic =>
320+
diagnostic.range.start.line === 0);
321+
322+
assert.ok(rootDiagnostics.length > 0,
323+
'Should have root-level metaschema diagnostics');
324+
325+
for (const diagnostic of rootDiagnostics) {
326+
assert.strictEqual(diagnostic.range.start.character, 0,
327+
'Root-level metaschema diagnostic should start at character 0');
328+
assert.strictEqual(diagnostic.range.end.line, 0,
329+
'Root-level metaschema diagnostic should not extend beyond line 0');
330+
assert.strictEqual(diagnostic.range.end.character, 0,
331+
'Root-level metaschema diagnostic should have zero-width range');
332+
}
333+
334+
const nonRootDiagnostics = metaschemaDiagnostics.filter(diagnostic =>
335+
diagnostic.range.start.line > 0);
336+
337+
assert.ok(nonRootDiagnostics.length > 0,
338+
'Should also have non-root metaschema diagnostics');
339+
340+
for (const diagnostic of nonRootDiagnostics) {
341+
assert.ok(diagnostic.range.end.character > diagnostic.range.start.character,
342+
'Non-root metaschema diagnostic should retain a non-zero-width range');
343+
}
344+
});
345+
217346
test('Should run linter even when metaschema validation fails', async function() {
218347
this.timeout(15000);
219348

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object"}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"type": "object"
4+
}

vscode/src/utils/fileUtils.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,25 @@ export function arrayToPosition(arr: [number, number]): vscode.Position {
262262
/**
263263
* Convert error position array to VS Code range
264264
* Position array is 1-based and inclusive, VS Code is 0-based and end-exclusive
265+
*
266+
* When a diagnostic applies to the root of the document (position spanning
267+
* from line 1, column 1 across multiple lines, or across a single line in
268+
* minified files), we collapse the range to
269+
* a zero-width range at (0,0). VS Code renders this by expanding the
270+
* squiggle to the first word, matching what ESLint and Pylint do. VS Code
271+
* does not support file-level diagnostics without a range:
272+
* https://github.com/microsoft/vscode/issues/238608
265273
*/
266274
export function errorPositionToRange(position: Position): vscode.Range {
267275
const [lineStart, columnStart, lineEnd, columnEnd] = position;
276+
277+
if (lineStart === 1 && columnStart === 1 && (lineEnd > lineStart || columnEnd > columnStart)) {
278+
return new vscode.Range(
279+
new vscode.Position(0, 0),
280+
new vscode.Position(0, 0)
281+
);
282+
}
283+
268284
return new vscode.Range(
269285
new vscode.Position(lineStart - 1, columnStart - 1),
270286
new vscode.Position(lineEnd - 1, columnEnd)

0 commit comments

Comments
 (0)