Skip to content

Commit dba4aa3

Browse files
authored
chore: align warning and error objects, add frame property (#12326)
This aligns warning and error objects to contain the same properties and have their toString methods return the same shape. It's implemented by warnings becoming class objects, too, and sharing the same base class with errors. It also adds back the `frame` property that got lost in the Svelte 4->5 transition. The only difference to Svelte 4 now is a slightly adjusted toString property (which is consistent between warnings and errors now) and a `position` property that contains a tuple of start/end offsets instead of a `pos` property only containing the start offset closes #12151
1 parent 243c4b7 commit dba4aa3

File tree

10 files changed

+191
-142
lines changed

10 files changed

+191
-142
lines changed

.changeset/small-owls-remain.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
chore: align warning and error objects, add frame property

packages/svelte/scripts/process-messages/templates/compile-errors.js

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,17 @@
1-
/** @import { Location } from 'locate-character' */
2-
import * as state from './state.js';
1+
import { CompileDiagnostic } from './utils/compile_diagnostic.js';
32

43
/** @typedef {{ start?: number, end?: number }} NodeLike */
54

6-
export class InternalCompileError extends Error {
5+
export class InternalCompileError extends CompileDiagnostic {
76
name = 'CompileError';
87

9-
filename = state.filename;
10-
11-
/** @type {[number, number] | undefined} */
12-
position = undefined;
13-
14-
/** @type {Location | undefined} */
15-
start = undefined;
16-
17-
/** @type {Location | undefined} */
18-
end = undefined;
19-
208
/**
21-
*
229
* @param {string} code
2310
* @param {string} message
2411
* @param {[number, number] | undefined} position
2512
*/
2613
constructor(code, message, position) {
27-
super(message);
28-
29-
this.code = code;
30-
this.position = position;
31-
32-
if (position) {
33-
this.start = state.locator(position[0]);
34-
this.end = state.locator(position[1]);
35-
}
36-
}
37-
38-
toString() {
39-
let out = `${this.name}: ${this.message}`;
40-
41-
out += `\n(${this.code})`;
42-
43-
if (this.filename) {
44-
out += `\n${this.filename}`;
45-
46-
if (this.start) {
47-
out += `${this.start.line}:${this.start.column}`;
48-
}
49-
}
50-
51-
return out;
14+
super(code, message, position);
5215
}
5316
}
5417

@@ -65,7 +28,7 @@ function e(node, code, message) {
6528
throw new InternalCompileError(
6629
code,
6730
message,
68-
start !== undefined && end !== undefined ? [start, end] : undefined
31+
start !== undefined ? [start, end ?? start] : undefined
6932
);
7033
}
7134

packages/svelte/scripts/process-messages/templates/compile-warnings.js

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
1-
import { filename, locator, warnings, ignore_stack, ignore_map } from './state.js';
1+
import { warnings, ignore_stack, ignore_map } from './state.js';
2+
import { CompileDiagnostic } from './utils/compile_diagnostic.js';
23

34
/** @typedef {{ start?: number, end?: number }} NodeLike */
45

6+
export class InternalCompileWarning extends CompileDiagnostic {
7+
name = 'CompileWarning';
8+
9+
/**
10+
* @param {string} code
11+
* @param {string} message
12+
* @param {[number, number] | undefined} position
13+
*/
14+
constructor(code, message, position) {
15+
super(code, message, position);
16+
}
17+
}
18+
519
/**
620
* @param {null | NodeLike} node
721
* @param {string} code
@@ -14,13 +28,13 @@ function w(node, code, message) {
1428
}
1529
if (stack && stack.at(-1)?.has(code)) return;
1630

17-
warnings.push({
18-
code,
19-
message,
20-
filename,
21-
start: node?.start !== undefined ? locator(node.start) : undefined,
22-
end: node?.end !== undefined ? locator(node.end) : undefined
23-
});
31+
warnings.push(
32+
new InternalCompileWarning(
33+
code,
34+
message,
35+
node && node.start !== undefined ? [node.start, node.end ?? node.start] : undefined
36+
)
37+
);
2438
}
2539

2640
export const codes = CODES;

packages/svelte/src/compiler/errors.js

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,18 @@
11
/* This file is generated by scripts/process-messages/index.js. Do not edit! */
22

3-
/** @import { Location } from 'locate-character' */
4-
import * as state from './state.js';
3+
import { CompileDiagnostic } from './utils/compile_diagnostic.js';
54

65
/** @typedef {{ start?: number, end?: number }} NodeLike */
7-
export class InternalCompileError extends Error {
6+
export class InternalCompileError extends CompileDiagnostic {
87
name = 'CompileError';
9-
filename = state.filename;
10-
/** @type {[number, number] | undefined} */
11-
position = undefined;
12-
/** @type {Location | undefined} */
13-
start = undefined;
14-
/** @type {Location | undefined} */
15-
end = undefined;
168

179
/**
18-
*
1910
* @param {string} code
2011
* @param {string} message
2112
* @param {[number, number] | undefined} position
2213
*/
2314
constructor(code, message, position) {
24-
super(message);
25-
this.code = code;
26-
this.position = position;
27-
28-
if (position) {
29-
this.start = state.locator(position[0]);
30-
this.end = state.locator(position[1]);
31-
}
32-
}
33-
34-
toString() {
35-
let out = `${this.name}: ${this.message}`;
36-
37-
out += `\n(${this.code})`;
38-
39-
if (this.filename) {
40-
out += `\n${this.filename}`;
41-
42-
if (this.start) {
43-
out += `${this.start.line}:${this.start.column}`;
44-
}
45-
}
46-
47-
return out;
15+
super(code, message, position);
4816
}
4917
}
5018

@@ -58,7 +26,7 @@ function e(node, code, message) {
5826
const start = typeof node === 'number' ? node : node?.start;
5927
const end = typeof node === 'number' ? node : node?.end;
6028

61-
throw new InternalCompileError(code, message, start !== undefined && end !== undefined ? [start, end] : undefined);
29+
throw new InternalCompileError(code, message, start !== undefined ? [start, end ?? start] : undefined);
6230
}
6331

6432
/**

packages/svelte/src/compiler/state.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ export let warnings = [];
1414
*/
1515
export let filename;
1616

17+
/**
18+
* The original source code
19+
* @type {string}
20+
*/
21+
export let source;
22+
1723
export let locator = getLocator('', { offsetLine: 1 });
1824

1925
/**
@@ -43,10 +49,11 @@ export function pop_ignore() {
4349
}
4450

4551
/**
46-
* @param {string} source
52+
* @param {string} _source
4753
* @param {{ filename?: string, rootDir?: string }} options
4854
*/
49-
export function reset(source, options) {
55+
export function reset(_source, options) {
56+
source = _source;
5057
const root_dir = options.rootDir?.replace(/\\/g, '/');
5158
filename = options.filename?.replace(/\\/g, '/');
5259

packages/svelte/src/compiler/types/index.d.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@ import type {
66
Identifier,
77
ImportDeclaration
88
} from 'estree';
9-
import type { Location } from 'locate-character';
109
import type { SourceMap } from 'magic-string';
1110
import type { Context } from 'zimmerframe';
1211
import type { Scope } from '../phases/scope.js';
1312
import type { Css } from './css.js';
1413
import type { EachBlock, Namespace, SvelteNode, SvelteOptions } from './template.js';
15-
import type { InternalCompileError } from '../errors.js';
14+
import type { ICompileDiagnostic } from '../utils/compile_diagnostic.js';
1615

1716
/** The return value of `compile` from `svelte/compiler` */
1817
export interface CompileResult {
@@ -51,16 +50,9 @@ export interface CompileResult {
5150
ast: any;
5251
}
5352

54-
export interface Warning {
55-
start?: Location;
56-
end?: Location;
57-
// TODO there was pos: number in Svelte 4 - do we want to add it back?
58-
code: string;
59-
message: string;
60-
filename?: string;
61-
}
53+
export interface Warning extends ICompileDiagnostic {}
6254

63-
export interface CompileError extends InternalCompileError {}
55+
export interface CompileError extends ICompileDiagnostic {}
6456

6557
export type CssHashGetter = (args: {
6658
name: string;
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/** @import { Location } from 'locate-character' */
2+
import * as state from '../state.js';
3+
4+
const regex_tabs = /^\t+/;
5+
6+
/**
7+
* @param {string} str
8+
*/
9+
function tabs_to_spaces(str) {
10+
return str.replace(regex_tabs, (match) => match.split('\t').join(' '));
11+
}
12+
13+
/**
14+
* @param {string} source
15+
* @param {number} line
16+
* @param {number} column
17+
*/
18+
function get_code_frame(source, line, column) {
19+
const lines = source.split('\n');
20+
const frame_start = Math.max(0, line - 2);
21+
const frame_end = Math.min(line + 3, lines.length);
22+
const digits = String(frame_end + 1).length;
23+
return lines
24+
.slice(frame_start, frame_end)
25+
.map((str, i) => {
26+
const is_error_line = frame_start + i === line;
27+
const line_num = String(i + frame_start + 1).padStart(digits, ' ');
28+
if (is_error_line) {
29+
const indicator =
30+
' '.repeat(digits + 2 + tabs_to_spaces(str.slice(0, column)).length) + '^';
31+
return `${line_num}: ${tabs_to_spaces(str)}\n${indicator}`;
32+
}
33+
return `${line_num}: ${tabs_to_spaces(str)}`;
34+
})
35+
.join('\n');
36+
}
37+
38+
/**
39+
* @typedef {{
40+
* code: string;
41+
* message: string;
42+
* filename?: string;
43+
* start?: Location;
44+
* end?: Location;
45+
* position?: [number, number];
46+
* frame?: string;
47+
* }} ICompileDiagnostic */
48+
49+
/** @implements {ICompileDiagnostic} */
50+
export class CompileDiagnostic extends Error {
51+
name = 'CompileDiagnostic';
52+
53+
/**
54+
* @param {string} code
55+
* @param {string} message
56+
* @param {[number, number] | undefined} position
57+
*/
58+
constructor(code, message, position) {
59+
super(message);
60+
this.code = code;
61+
62+
if (state.filename) {
63+
this.filename = state.filename;
64+
}
65+
66+
if (position) {
67+
this.position = position;
68+
this.start = state.locator(position[0]);
69+
this.end = state.locator(position[1]);
70+
if (this.start && this.end) {
71+
this.frame = get_code_frame(state.source, this.start.line - 1, this.end.column);
72+
}
73+
}
74+
}
75+
76+
toString() {
77+
let out = `${this.code}: ${this.message}`;
78+
79+
if (this.filename) {
80+
out += `\n${this.filename}`;
81+
82+
if (this.start) {
83+
out += `:${this.start.line}:${this.start.column}`;
84+
}
85+
}
86+
87+
if (this.frame) {
88+
out += `\n${this.frame}`;
89+
}
90+
91+
return out;
92+
}
93+
94+
toJSON() {
95+
return {
96+
code: this.code,
97+
message: this.message,
98+
filename: this.filename,
99+
start: this.start,
100+
end: this.end,
101+
position: this.position,
102+
frame: this.frame
103+
};
104+
}
105+
}

packages/svelte/src/compiler/warnings.js

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
/* This file is generated by scripts/process-messages/index.js. Do not edit! */
22

3-
import {
4-
filename,
5-
locator,
6-
warnings,
7-
ignore_stack,
8-
ignore_map
9-
} from './state.js';
3+
import { warnings, ignore_stack, ignore_map } from './state.js';
4+
import { CompileDiagnostic } from './utils/compile_diagnostic.js';
105

116
/** @typedef {{ start?: number, end?: number }} NodeLike */
7+
export class InternalCompileWarning extends CompileDiagnostic {
8+
name = 'CompileWarning';
9+
10+
/**
11+
* @param {string} code
12+
* @param {string} message
13+
* @param {[number, number] | undefined} position
14+
*/
15+
constructor(code, message, position) {
16+
super(code, message, position);
17+
}
18+
}
19+
1220
/**
1321
* @param {null | NodeLike} node
1422
* @param {string} code
@@ -22,14 +30,7 @@ function w(node, code, message) {
2230
}
2331

2432
if (stack && stack.at(-1)?.has(code)) return;
25-
26-
warnings.push({
27-
code,
28-
message,
29-
filename,
30-
start: node?.start !== undefined ? locator(node.start) : undefined,
31-
end: node?.end !== undefined ? locator(node.end) : undefined
32-
});
33+
warnings.push(new InternalCompileWarning(code, message, node && node.start !== undefined ? [node.start, node.end ?? node.start] : undefined));
3334
}
3435

3536
export const codes = [

0 commit comments

Comments
 (0)