Skip to content

Commit a4b5d50

Browse files
committed
chore: align warning and error objects, add frame property
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 ae3bf9e commit a4b5d50

File tree

9 files changed

+182
-186
lines changed

9 files changed

+182
-186
lines changed

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

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

63
/** @typedef {{ start?: number, end?: number }} NodeLike */
74

8-
export class InternalCompileError extends Error {
5+
export class InternalCompileError extends CompileDiagnostic {
96
name = 'CompileError';
10-
filename = state.filename;
11-
/** @type {[number, number] | undefined} */
12-
position = undefined;
13-
/** @type {Location | undefined} */
14-
start = undefined;
15-
/** @type {Location | undefined} */
16-
end = undefined;
17-
/** @type {string | undefined} */
18-
frame = undefined;
197

208
/**
219
* @param {string} code
2210
* @param {string} message
2311
* @param {[number, number] | undefined} position
2412
*/
2513
constructor(code, message, position) {
26-
super(message);
27-
this.code = code;
28-
this.position = position;
29-
30-
if (position) {
31-
this.start = state.locator(position[0]);
32-
this.end = state.locator(position[1]);
33-
if (this.start && this.end) {
34-
this.frame = get_code_frame(state.source, this.start.line - 1, this.end.column);
35-
}
36-
}
37-
}
38-
39-
toString() {
40-
return error_to_string(this.code, this.message, this.filename, this.start, this.frame);
14+
super(code, message, position);
4115
}
4216
}
4317

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

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1-
import { filename, locator, warnings, ignore_stack, ignore_map, source } from './state.js';
2-
import { get_code_frame } from './utils/get_code_frame.js';
3-
import { error_to_string } from './utils/error_to_string.js';
1+
import { warnings, ignore_stack, ignore_map } from './state.js';
2+
import { CompileDiagnostic } from './utils/compile_diagnostic.js';
43

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

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+
719
/**
820
* @param {null | NodeLike} node
921
* @param {string} code
@@ -16,20 +28,9 @@ function w(node, code, message) {
1628
}
1729
if (stack && stack.at(-1)?.has(code)) return;
1830

19-
const start = node?.start !== undefined ? locator(node.start) : undefined;
20-
const frame = start && get_code_frame(source, start.line - 1, start.column);
21-
22-
warnings.push({
23-
code,
24-
message,
25-
filename,
26-
position:
27-
node?.start !== undefined && node?.end !== undefined ? [node.start, node.end] : undefined,
28-
start,
29-
end: node?.end !== undefined ? locator(node.end) : undefined,
30-
frame,
31-
toString: () => error_to_string(code, message, filename, start, frame)
32-
});
31+
warnings.push(
32+
new InternalCompileWarning(code, message, node ? [node.start, node.end] : undefined)
33+
);
3334
}
3435

3536
export const codes = CODES;

packages/svelte/src/compiler/errors.js

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +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';
5-
import { get_code_frame } from './utils/get_code_frame.js';
6-
import { error_to_string } from './utils/error_to_string.js';
3+
import { CompileDiagnostic } from './utils/compile_diagnostic.js';
74

85
/** @typedef {{ start?: number, end?: number }} NodeLike */
9-
export class InternalCompileError extends Error {
6+
export class InternalCompileError extends CompileDiagnostic {
107
name = 'CompileError';
11-
filename = state.filename;
12-
/** @type {[number, number] | undefined} */
13-
position = undefined;
14-
/** @type {Location | undefined} */
15-
start = undefined;
16-
/** @type {Location | undefined} */
17-
end = undefined;
18-
/** @type {string | undefined} */
19-
frame = undefined;
208

219
/**
2210
* @param {string} code
2311
* @param {string} message
2412
* @param {[number, number] | undefined} position
2513
*/
2614
constructor(code, message, position) {
27-
super(message);
28-
this.code = code;
29-
this.position = position;
30-
31-
if (position) {
32-
this.start = state.locator(position[0]);
33-
this.end = state.locator(position[1]);
34-
35-
if (this.start && this.end) {
36-
this.frame = get_code_frame(state.source, this.start.line - 1, this.end.column);
37-
}
38-
}
39-
}
40-
41-
toString() {
42-
return error_to_string(this.code, this.message, this.filename, this.start, this.frame);
15+
super(code, message, position);
4316
}
4417
}
4518

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

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ 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';
1514
import type { InternalCompileError } from '../errors.js';
15+
import type { InternalCompileWarning } from '../warnings.js';
1616

1717
/** The return value of `compile` from `svelte/compiler` */
1818
export interface CompileResult {
@@ -51,15 +51,7 @@ export interface CompileResult {
5151
ast: any;
5252
}
5353

54-
export interface Warning {
55-
code: string;
56-
message: string;
57-
filename?: string;
58-
start?: Location;
59-
end?: Location;
60-
position?: [number, number];
61-
frame?: string;
62-
}
54+
export interface Warning extends InternalCompileWarning {}
6355

6456
export interface CompileError extends InternalCompileError {}
6557

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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+
export class CompileDiagnostic extends Error {
39+
name = 'CompileDiagnostic';
40+
filename = state.filename;
41+
/** @type {[number, number] | undefined} */
42+
position = undefined;
43+
/** @type {Location | undefined} */
44+
start = undefined;
45+
/** @type {Location | undefined} */
46+
end = undefined;
47+
/** @type {string | undefined} */
48+
frame = undefined;
49+
50+
/**
51+
* @param {string} code
52+
* @param {string} message
53+
* @param {[number, number] | undefined} position
54+
*/
55+
constructor(code, message, position) {
56+
super(message);
57+
this.code = code;
58+
this.position = position;
59+
60+
if (position) {
61+
this.start = state.locator(position[0]);
62+
this.end = state.locator(position[1]);
63+
if (this.start && this.end) {
64+
this.frame = get_code_frame(state.source, this.start.line - 1, this.end.column);
65+
}
66+
}
67+
}
68+
69+
toString() {
70+
let out = `${this.code}: ${this.message}`;
71+
72+
if (this.filename) {
73+
out += `\n${this.filename}`;
74+
75+
if (this.start) {
76+
out += `:${this.start.line}:${this.start.column}`;
77+
}
78+
}
79+
80+
if (this.frame) {
81+
out += `\n${this.frame}`;
82+
}
83+
84+
return out;
85+
}
86+
87+
toJSON() {
88+
return {
89+
code: this.code,
90+
message: this.message,
91+
filename: this.filename,
92+
start: this.start,
93+
end: this.end,
94+
position: this.position,
95+
frame: this.frame
96+
};
97+
}
98+
}

packages/svelte/src/compiler/utils/error_to_string.js

Lines changed: 0 additions & 24 deletions
This file was deleted.

packages/svelte/src/compiler/utils/get_code_frame.js

Lines changed: 0 additions & 33 deletions
This file was deleted.

packages/svelte/src/compiler/warnings.js

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +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-
source
10-
} from './state.js';
11-
12-
import { get_code_frame } from './utils/get_code_frame.js';
13-
import { error_to_string } from './utils/error_to_string.js';
3+
import { warnings, ignore_stack, ignore_map } from './state.js';
4+
import { CompileDiagnostic } from './utils/compile_diagnostic.js';
145

156
/** @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+
1620
/**
1721
* @param {null | NodeLike} node
1822
* @param {string} code
@@ -26,20 +30,7 @@ function w(node, code, message) {
2630
}
2731

2832
if (stack && stack.at(-1)?.has(code)) return;
29-
30-
const start = node?.start !== undefined ? locator(node.start) : undefined;
31-
const frame = start && get_code_frame(source, start.line - 1, start.column);
32-
33-
warnings.push({
34-
code,
35-
message,
36-
filename,
37-
position: node?.start !== undefined && node?.end !== undefined ? [node.start, node.end] : undefined,
38-
start,
39-
end: node?.end !== undefined ? locator(node.end) : undefined,
40-
frame,
41-
toString: () => error_to_string(code, message, filename, start, frame)
42-
});
33+
warnings.push(new InternalCompileWarning(code, message, node ? [node.start, node.end] : undefined));
4334
}
4435

4536
export const codes = [

0 commit comments

Comments
 (0)