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
pull/12340/head
Simon H 4 months ago committed by GitHub
parent 243c4b78b1
commit dba4aa3567
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
chore: align warning and error objects, add frame property

@ -1,54 +1,17 @@
/** @import { Location } from 'locate-character' */ import { CompileDiagnostic } from './utils/compile_diagnostic.js';
import * as state from './state.js';
/** @typedef {{ start?: number, end?: number }} NodeLike */ /** @typedef {{ start?: number, end?: number }} NodeLike */
export class InternalCompileError extends Error { export class InternalCompileError extends CompileDiagnostic {
name = 'CompileError'; name = 'CompileError';
filename = state.filename;
/** @type {[number, number] | undefined} */
position = undefined;
/** @type {Location | undefined} */
start = undefined;
/** @type {Location | undefined} */
end = undefined;
/** /**
*
* @param {string} code * @param {string} code
* @param {string} message * @param {string} message
* @param {[number, number] | undefined} position * @param {[number, number] | undefined} position
*/ */
constructor(code, message, position) { constructor(code, message, position) {
super(message); super(code, message, position);
this.code = code;
this.position = position;
if (position) {
this.start = state.locator(position[0]);
this.end = state.locator(position[1]);
}
}
toString() {
let out = `${this.name}: ${this.message}`;
out += `\n(${this.code})`;
if (this.filename) {
out += `\n${this.filename}`;
if (this.start) {
out += `${this.start.line}:${this.start.column}`;
}
}
return out;
} }
} }
@ -65,7 +28,7 @@ function e(node, code, message) {
throw new InternalCompileError( throw new InternalCompileError(
code, code,
message, message,
start !== undefined && end !== undefined ? [start, end] : undefined start !== undefined ? [start, end ?? start] : undefined
); );
} }

@ -1,7 +1,21 @@
import { filename, locator, warnings, ignore_stack, ignore_map } from './state.js'; import { warnings, ignore_stack, ignore_map } from './state.js';
import { CompileDiagnostic } from './utils/compile_diagnostic.js';
/** @typedef {{ start?: number, end?: number }} NodeLike */ /** @typedef {{ start?: number, end?: number }} NodeLike */
export class InternalCompileWarning extends CompileDiagnostic {
name = 'CompileWarning';
/**
* @param {string} code
* @param {string} message
* @param {[number, number] | undefined} position
*/
constructor(code, message, position) {
super(code, message, position);
}
}
/** /**
* @param {null | NodeLike} node * @param {null | NodeLike} node
* @param {string} code * @param {string} code
@ -14,13 +28,13 @@ function w(node, code, message) {
} }
if (stack && stack.at(-1)?.has(code)) return; if (stack && stack.at(-1)?.has(code)) return;
warnings.push({ warnings.push(
code, new InternalCompileWarning(
message, code,
filename, message,
start: node?.start !== undefined ? locator(node.start) : undefined, node && node.start !== undefined ? [node.start, node.end ?? node.start] : undefined
end: node?.end !== undefined ? locator(node.end) : undefined )
}); );
} }
export const codes = CODES; export const codes = CODES;

@ -1,50 +1,18 @@
/* This file is generated by scripts/process-messages/index.js. Do not edit! */ /* This file is generated by scripts/process-messages/index.js. Do not edit! */
/** @import { Location } from 'locate-character' */ import { CompileDiagnostic } from './utils/compile_diagnostic.js';
import * as state from './state.js';
/** @typedef {{ start?: number, end?: number }} NodeLike */ /** @typedef {{ start?: number, end?: number }} NodeLike */
export class InternalCompileError extends Error { export class InternalCompileError extends CompileDiagnostic {
name = 'CompileError'; name = 'CompileError';
filename = state.filename;
/** @type {[number, number] | undefined} */
position = undefined;
/** @type {Location | undefined} */
start = undefined;
/** @type {Location | undefined} */
end = undefined;
/** /**
*
* @param {string} code * @param {string} code
* @param {string} message * @param {string} message
* @param {[number, number] | undefined} position * @param {[number, number] | undefined} position
*/ */
constructor(code, message, position) { constructor(code, message, position) {
super(message); super(code, message, position);
this.code = code;
this.position = position;
if (position) {
this.start = state.locator(position[0]);
this.end = state.locator(position[1]);
}
}
toString() {
let out = `${this.name}: ${this.message}`;
out += `\n(${this.code})`;
if (this.filename) {
out += `\n${this.filename}`;
if (this.start) {
out += `${this.start.line}:${this.start.column}`;
}
}
return out;
} }
} }
@ -58,7 +26,7 @@ function e(node, code, message) {
const start = typeof node === 'number' ? node : node?.start; const start = typeof node === 'number' ? node : node?.start;
const end = typeof node === 'number' ? node : node?.end; const end = typeof node === 'number' ? node : node?.end;
throw new InternalCompileError(code, message, start !== undefined && end !== undefined ? [start, end] : undefined); throw new InternalCompileError(code, message, start !== undefined ? [start, end ?? start] : undefined);
} }
/** /**

@ -14,6 +14,12 @@ export let warnings = [];
*/ */
export let filename; export let filename;
/**
* The original source code
* @type {string}
*/
export let source;
export let locator = getLocator('', { offsetLine: 1 }); export let locator = getLocator('', { offsetLine: 1 });
/** /**
@ -43,10 +49,11 @@ export function pop_ignore() {
} }
/** /**
* @param {string} source * @param {string} _source
* @param {{ filename?: string, rootDir?: string }} options * @param {{ filename?: string, rootDir?: string }} options
*/ */
export function reset(source, options) { export function reset(_source, options) {
source = _source;
const root_dir = options.rootDir?.replace(/\\/g, '/'); const root_dir = options.rootDir?.replace(/\\/g, '/');
filename = options.filename?.replace(/\\/g, '/'); filename = options.filename?.replace(/\\/g, '/');

@ -6,13 +6,12 @@ import type {
Identifier, Identifier,
ImportDeclaration ImportDeclaration
} from 'estree'; } from 'estree';
import type { Location } from 'locate-character';
import type { SourceMap } from 'magic-string'; import type { SourceMap } from 'magic-string';
import type { Context } from 'zimmerframe'; import type { Context } from 'zimmerframe';
import type { Scope } from '../phases/scope.js'; import type { Scope } from '../phases/scope.js';
import type { Css } from './css.js'; import type { Css } from './css.js';
import type { EachBlock, Namespace, SvelteNode, SvelteOptions } from './template.js'; import type { EachBlock, Namespace, SvelteNode, SvelteOptions } from './template.js';
import type { InternalCompileError } from '../errors.js'; import type { ICompileDiagnostic } from '../utils/compile_diagnostic.js';
/** The return value of `compile` from `svelte/compiler` */ /** The return value of `compile` from `svelte/compiler` */
export interface CompileResult { export interface CompileResult {
@ -51,16 +50,9 @@ export interface CompileResult {
ast: any; ast: any;
} }
export interface Warning { export interface Warning extends ICompileDiagnostic {}
start?: Location;
end?: Location;
// TODO there was pos: number in Svelte 4 - do we want to add it back?
code: string;
message: string;
filename?: string;
}
export interface CompileError extends InternalCompileError {} export interface CompileError extends ICompileDiagnostic {}
export type CssHashGetter = (args: { export type CssHashGetter = (args: {
name: string; name: string;

@ -0,0 +1,105 @@
/** @import { Location } from 'locate-character' */
import * as state from '../state.js';
const regex_tabs = /^\t+/;
/**
* @param {string} str
*/
function tabs_to_spaces(str) {
return str.replace(regex_tabs, (match) => match.split('\t').join(' '));
}
/**
* @param {string} source
* @param {number} line
* @param {number} column
*/
function get_code_frame(source, line, column) {
const lines = source.split('\n');
const frame_start = Math.max(0, line - 2);
const frame_end = Math.min(line + 3, lines.length);
const digits = String(frame_end + 1).length;
return lines
.slice(frame_start, frame_end)
.map((str, i) => {
const is_error_line = frame_start + i === line;
const line_num = String(i + frame_start + 1).padStart(digits, ' ');
if (is_error_line) {
const indicator =
' '.repeat(digits + 2 + tabs_to_spaces(str.slice(0, column)).length) + '^';
return `${line_num}: ${tabs_to_spaces(str)}\n${indicator}`;
}
return `${line_num}: ${tabs_to_spaces(str)}`;
})
.join('\n');
}
/**
* @typedef {{
* code: string;
* message: string;
* filename?: string;
* start?: Location;
* end?: Location;
* position?: [number, number];
* frame?: string;
* }} ICompileDiagnostic */
/** @implements {ICompileDiagnostic} */
export class CompileDiagnostic extends Error {
name = 'CompileDiagnostic';
/**
* @param {string} code
* @param {string} message
* @param {[number, number] | undefined} position
*/
constructor(code, message, position) {
super(message);
this.code = code;
if (state.filename) {
this.filename = state.filename;
}
if (position) {
this.position = position;
this.start = state.locator(position[0]);
this.end = state.locator(position[1]);
if (this.start && this.end) {
this.frame = get_code_frame(state.source, this.start.line - 1, this.end.column);
}
}
}
toString() {
let out = `${this.code}: ${this.message}`;
if (this.filename) {
out += `\n${this.filename}`;
if (this.start) {
out += `:${this.start.line}:${this.start.column}`;
}
}
if (this.frame) {
out += `\n${this.frame}`;
}
return out;
}
toJSON() {
return {
code: this.code,
message: this.message,
filename: this.filename,
start: this.start,
end: this.end,
position: this.position,
frame: this.frame
};
}
}

@ -1,14 +1,22 @@
/* This file is generated by scripts/process-messages/index.js. Do not edit! */ /* This file is generated by scripts/process-messages/index.js. Do not edit! */
import { import { warnings, ignore_stack, ignore_map } from './state.js';
filename, import { CompileDiagnostic } from './utils/compile_diagnostic.js';
locator,
warnings,
ignore_stack,
ignore_map
} from './state.js';
/** @typedef {{ start?: number, end?: number }} NodeLike */ /** @typedef {{ start?: number, end?: number }} NodeLike */
export class InternalCompileWarning extends CompileDiagnostic {
name = 'CompileWarning';
/**
* @param {string} code
* @param {string} message
* @param {[number, number] | undefined} position
*/
constructor(code, message, position) {
super(code, message, position);
}
}
/** /**
* @param {null | NodeLike} node * @param {null | NodeLike} node
* @param {string} code * @param {string} code
@ -22,14 +30,7 @@ function w(node, code, message) {
} }
if (stack && stack.at(-1)?.has(code)) return; if (stack && stack.at(-1)?.has(code)) return;
warnings.push(new InternalCompileWarning(code, message, node && node.start !== undefined ? [node.start, node.end ?? node.start] : undefined));
warnings.push({
code,
message,
filename,
start: node?.start !== undefined ? locator(node.start) : undefined,
end: node?.end !== undefined ? locator(node.end) : undefined
});
} }
export const codes = [ export const codes = [

@ -10,6 +10,8 @@ import type { CompileOptions, Warning } from '#compiler';
function normalize_warning(warning: Warning) { function normalize_warning(warning: Warning) {
delete warning.filename; delete warning.filename;
delete warning.position;
delete warning.frame;
return warning; return warning;
} }

@ -562,9 +562,9 @@ declare module 'svelte/animate' {
declare module 'svelte/compiler' { declare module 'svelte/compiler' {
import type { AssignmentExpression, ClassDeclaration, Expression, FunctionDeclaration, Identifier, ImportDeclaration, ArrayExpression, MemberExpression, ObjectExpression, Pattern, ArrowFunctionExpression, VariableDeclaration, VariableDeclarator, FunctionExpression, Node, Program, ChainExpression, SimpleCallExpression } from 'estree'; import type { AssignmentExpression, ClassDeclaration, Expression, FunctionDeclaration, Identifier, ImportDeclaration, ArrayExpression, MemberExpression, ObjectExpression, Pattern, ArrowFunctionExpression, VariableDeclaration, VariableDeclarator, FunctionExpression, Node, Program, ChainExpression, SimpleCallExpression } from 'estree';
import type { Location } from 'locate-character';
import type { SourceMap } from 'magic-string'; import type { SourceMap } from 'magic-string';
import type { Context } from 'zimmerframe'; import type { Context } from 'zimmerframe';
import type { Location } from 'locate-character';
/** /**
* `compile` converts your `.svelte` source code into a JavaScript module that exports a component * `compile` converts your `.svelte` source code into a JavaScript module that exports a component
* *
@ -714,16 +714,9 @@ declare module 'svelte/compiler' {
ast: any; ast: any;
} }
export interface Warning { export interface Warning extends ICompileDiagnostic {}
start?: Location;
end?: Location;
// TODO there was pos: number in Svelte 4 - do we want to add it back?
code: string;
message: string;
filename?: string;
}
export interface CompileError extends InternalCompileError {} export interface CompileError extends ICompileDiagnostic {}
type CssHashGetter = (args: { type CssHashGetter = (args: {
name: string; name: string;
@ -1882,18 +1875,15 @@ declare module 'svelte/compiler' {
content: Program; content: Program;
attributes: Attribute[]; attributes: Attribute[];
} }
class InternalCompileError extends Error { type ICompileDiagnostic = {
constructor(code: string, message: string, position: [number, number] | undefined);
filename: string | undefined;
position: [number, number] | undefined;
start: Location | undefined;
end: Location | undefined;
code: string; code: string;
} message: string;
filename?: string;
start?: Location;
end?: Location;
position?: [number, number];
frame?: string;
};
export {}; export {};
} }
@ -2539,14 +2529,7 @@ declare module 'svelte/types/compiler/interfaces' {
export type CompileOptions = CompileOptions_1; export type CompileOptions = CompileOptions_1;
/** @deprecated import this from 'svelte' instead */ /** @deprecated import this from 'svelte' instead */
export type Warning = Warning_1; export type Warning = Warning_1;
interface Warning_1 { interface Warning_1 extends ICompileDiagnostic {}
start?: Location;
end?: Location;
// TODO there was pos: number in Svelte 4 - do we want to add it back?
code: string;
message: string;
filename?: string;
}
type CssHashGetter = (args: { type CssHashGetter = (args: {
name: string; name: string;
@ -2709,6 +2692,15 @@ declare module 'svelte/types/compiler/interfaces' {
* (also see https://github.com/sveltejs/svelte/pull/5652) * (also see https://github.com/sveltejs/svelte/pull/5652)
*/ */
type Namespace = 'html' | 'svg' | 'mathml' | 'foreign'; type Namespace = 'html' | 'svg' | 'mathml' | 'foreign';
type ICompileDiagnostic = {
code: string;
message: string;
filename?: string;
start?: Location;
end?: Location;
position?: [number, number];
frame?: string;
};
export {}; export {};
}declare module '*.svelte' { }declare module '*.svelte' {

Loading…
Cancel
Save