From 6671dd68f0387bff7ce08e676179353899d953d7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 22 Apr 2024 11:30:07 -0400 Subject: [PATCH 01/22] rename errors.js to errors-tmp.js --- packages/svelte/src/compiler/{errors.js => errors-tmp.js} | 0 packages/svelte/src/compiler/index.js | 4 ++-- packages/svelte/src/compiler/phases/1-parse/index.js | 2 +- packages/svelte/src/compiler/phases/1-parse/read/context.js | 2 +- .../svelte/src/compiler/phases/1-parse/read/expression.js | 2 +- packages/svelte/src/compiler/phases/1-parse/read/options.js | 2 +- packages/svelte/src/compiler/phases/1-parse/read/script.js | 2 +- packages/svelte/src/compiler/phases/1-parse/read/style.js | 2 +- packages/svelte/src/compiler/phases/1-parse/state/element.js | 2 +- packages/svelte/src/compiler/phases/1-parse/state/tag.js | 2 +- .../svelte/src/compiler/phases/2-analyze/css/css-analyze.js | 2 +- packages/svelte/src/compiler/phases/2-analyze/index.js | 2 +- packages/svelte/src/compiler/phases/2-analyze/validation.js | 2 +- .../compiler/phases/3-transform/client/transform-client.js | 2 +- .../svelte/src/compiler/phases/3-transform/client/utils.js | 2 +- .../compiler/phases/3-transform/client/visitors/template.js | 2 +- .../compiler/phases/3-transform/server/transform-server.js | 2 +- packages/svelte/src/compiler/phases/scope.js | 2 +- packages/svelte/src/compiler/utils/assert.js | 2 +- packages/svelte/src/compiler/validate-options.js | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) rename packages/svelte/src/compiler/{errors.js => errors-tmp.js} (100%) diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors-tmp.js similarity index 100% rename from packages/svelte/src/compiler/errors.js rename to packages/svelte/src/compiler/errors-tmp.js diff --git a/packages/svelte/src/compiler/index.js b/packages/svelte/src/compiler/index.js index eee35bf2a81e..30b623865ae9 100644 --- a/packages/svelte/src/compiler/index.js +++ b/packages/svelte/src/compiler/index.js @@ -1,6 +1,6 @@ import { getLocator } from 'locate-character'; import { walk as zimmerframe_walk } from 'zimmerframe'; -import { CompileError } from './errors.js'; +import { CompileError } from './errors-tmp.js'; import { convert } from './legacy.js'; import { parse as parse_acorn } from './phases/1-parse/acorn.js'; import { parse as _parse } from './phases/1-parse/index.js'; @@ -157,6 +157,6 @@ export function walk() { ); } -export { CompileError } from './errors.js'; +export { CompileError } from './errors-tmp.js'; export { VERSION } from '../version.js'; diff --git a/packages/svelte/src/compiler/phases/1-parse/index.js b/packages/svelte/src/compiler/phases/1-parse/index.js index 5c77d36ea65f..5c06c0a5ce53 100644 --- a/packages/svelte/src/compiler/phases/1-parse/index.js +++ b/packages/svelte/src/compiler/phases/1-parse/index.js @@ -4,7 +4,7 @@ import fragment from './state/fragment.js'; import { regex_whitespace } from '../patterns.js'; import { reserved } from './utils/names.js'; import full_char_code_at from './utils/full_char_code_at.js'; -import { error } from '../../errors.js'; +import { error } from '../../errors-tmp.js'; import { create_fragment } from './utils/create.js'; import read_options from './read/options.js'; import { getLocator } from 'locate-character'; diff --git a/packages/svelte/src/compiler/phases/1-parse/read/context.js b/packages/svelte/src/compiler/phases/1-parse/read/context.js index b69c16d320d2..76a0701935a6 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/context.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/context.js @@ -9,7 +9,7 @@ import { } from '../utils/bracket.js'; import { parse_expression_at } from '../acorn.js'; import { regex_not_newline_characters } from '../../patterns.js'; -import { error } from '../../../errors.js'; +import { error } from '../../../errors-tmp.js'; /** * @param {import('../index.js').Parser} parser diff --git a/packages/svelte/src/compiler/phases/1-parse/read/expression.js b/packages/svelte/src/compiler/phases/1-parse/read/expression.js index 21d4bb96fe76..8e370bd5e723 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/expression.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/expression.js @@ -1,6 +1,6 @@ import { parse_expression_at } from '../acorn.js'; import { regex_whitespace } from '../../patterns.js'; -import { error } from '../../../errors.js'; +import { error } from '../../../errors-tmp.js'; /** * @param {import('../index.js').Parser} parser diff --git a/packages/svelte/src/compiler/phases/1-parse/read/options.js b/packages/svelte/src/compiler/phases/1-parse/read/options.js index c9262c54a8f8..4b98df77ccee 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/options.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/options.js @@ -1,5 +1,5 @@ import { namespace_svg } from '../../../../constants.js'; -import { error } from '../../../errors.js'; +import { error } from '../../../errors-tmp.js'; const regex_valid_tag_name = /^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/; diff --git a/packages/svelte/src/compiler/phases/1-parse/read/script.js b/packages/svelte/src/compiler/phases/1-parse/read/script.js index cd9129e8b594..2a348670f112 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/script.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/script.js @@ -1,6 +1,6 @@ import * as acorn from '../acorn.js'; import { regex_not_newline_characters } from '../../patterns.js'; -import { error } from '../../../errors.js'; +import { error } from '../../../errors-tmp.js'; const regex_closing_script_tag = /<\/script\s*>/; const regex_starts_with_closing_script_tag = /^<\/script\s*>/; diff --git a/packages/svelte/src/compiler/phases/1-parse/read/style.js b/packages/svelte/src/compiler/phases/1-parse/read/style.js index 8ec00daff1c9..baf13574b296 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/style.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/style.js @@ -1,4 +1,4 @@ -import { error } from '../../../errors.js'; +import { error } from '../../../errors-tmp.js'; const REGEX_MATCHER = /^[~^$*|]?=/; const REGEX_CLOSING_BRACKET = /[\s\]]/; diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js index bfaee9572d13..e578a7c19243 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -5,7 +5,7 @@ import read_expression from '../read/expression.js'; import { read_script } from '../read/script.js'; import read_style from '../read/style.js'; import { closing_tag_omitted, decode_character_references } from '../utils/html.js'; -import { error } from '../../../errors.js'; +import { error } from '../../../errors-tmp.js'; import { create_fragment } from '../utils/create.js'; import { create_attribute } from '../../nodes.js'; diff --git a/packages/svelte/src/compiler/phases/1-parse/state/tag.js b/packages/svelte/src/compiler/phases/1-parse/state/tag.js index 1c7342746322..c2deef9828bd 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -1,6 +1,6 @@ import read_pattern from '../read/context.js'; import read_expression from '../read/expression.js'; -import { error } from '../../../errors.js'; +import { error } from '../../../errors-tmp.js'; import { create_fragment } from '../utils/create.js'; import { walk } from 'zimmerframe'; diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index 38a62b9ca7ae..6aa5105ed5c3 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -1,5 +1,5 @@ import { walk } from 'zimmerframe'; -import { error } from '../../../errors.js'; +import { error } from '../../../errors-tmp.js'; import { is_keyframes_node } from '../../css.js'; import { merge } from '../../visitors.js'; diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 37eb3a3c92d4..65726ed953f6 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -1,6 +1,6 @@ import is_reference from 'is-reference'; import { walk } from 'zimmerframe'; -import { error } from '../../errors.js'; +import { error } from '../../errors-tmp.js'; import { extract_identifiers, extract_all_identifiers_from_expression, diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index 0f6f1bf6dd30..8f1002a9066c 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -3,7 +3,7 @@ import { interactive_elements, is_tag_valid_with_parent } from '../../../constants.js'; -import { error } from '../../errors.js'; +import { error } from '../../errors-tmp.js'; import { extract_identifiers, get_parent, diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index f1de213a9b51..76b726059bad 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -1,5 +1,5 @@ import { walk } from 'zimmerframe'; -import { error } from '../../../errors.js'; +import { error } from '../../../errors-tmp.js'; import * as b from '../../../utils/builders.js'; import { set_scope } from '../../scope.js'; import { template_visitors } from './visitors/template.js'; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index 17e3fedc1d3d..ce385dd52998 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -5,7 +5,7 @@ import { is_simple_expression, object } from '../../../utils/ast.js'; -import { error } from '../../../errors.js'; +import { error } from '../../../errors-tmp.js'; import { PROPS_IS_LAZY_INITIAL, PROPS_IS_IMMUTABLE, diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index e9d3b519c724..4f897ba5cf47 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -16,7 +16,7 @@ import { import { DOMProperties, PassiveEvents, VoidElements } from '../../../constants.js'; import { is_custom_element_node, is_element_node } from '../../../nodes.js'; import * as b from '../../../../utils/builders.js'; -import { error } from '../../../../errors.js'; +import { error } from '../../../../errors-tmp.js'; import { with_loc, function_visitor, diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index d5402351c7ee..4e6a12734d6a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -22,7 +22,7 @@ import { transform_inspect_rune } from '../utils.js'; import { create_attribute, is_custom_element_node, is_element_node } from '../../nodes.js'; -import { error } from '../../../errors.js'; +import { error } from '../../../errors-tmp.js'; import { binding_properties } from '../../bindings.js'; import { regex_starts_with_newline, regex_whitespaces_strict } from '../../patterns.js'; import { diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index cfc2f0bb4492..28abaa9734cf 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -2,7 +2,7 @@ import is_reference from 'is-reference'; import { walk } from 'zimmerframe'; import { is_element_node } from './nodes.js'; import * as b from '../utils/builders.js'; -import { error } from '../errors.js'; +import { error } from '../errors-tmp.js'; import { extract_identifiers, extract_identifiers_from_destructuring } from '../utils/ast.js'; import { JsKeywords, Runes } from './constants.js'; diff --git a/packages/svelte/src/compiler/utils/assert.js b/packages/svelte/src/compiler/utils/assert.js index ac1cc2bd4752..303f50855313 100644 --- a/packages/svelte/src/compiler/utils/assert.js +++ b/packages/svelte/src/compiler/utils/assert.js @@ -1,4 +1,4 @@ -import { error } from '../errors.js'; +import { error } from '../errors-tmp.js'; /** * @template T diff --git a/packages/svelte/src/compiler/validate-options.js b/packages/svelte/src/compiler/validate-options.js index 6c12a43e2b6a..e525858a6860 100644 --- a/packages/svelte/src/compiler/validate-options.js +++ b/packages/svelte/src/compiler/validate-options.js @@ -1,4 +1,4 @@ -import { error } from './errors.js'; +import { error } from './errors-tmp.js'; /** * @template [Input=any] From 45f375a5eb9b5eb23ecc0112a1316b3bbab4299a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 22 Apr 2024 11:36:45 -0400 Subject: [PATCH 02/22] start porting errors --- .prettierignore | 1 + .../svelte/messages/compile-errors/parse.md | 7 + .../svelte/scripts/process-messages/index.js | 215 ++++++++++++++++++ .../templates/compile-errors.js | 74 ++++++ packages/svelte/src/compiler/errors.js | 76 +++++++ .../src/compiler/phases/1-parse/index.js | 5 +- .../unexpected-end-of-input-d/_config.js | 2 +- .../unexpected-end-of-input/_config.js | 2 +- 8 files changed, 378 insertions(+), 4 deletions(-) create mode 100644 packages/svelte/messages/compile-errors/parse.md create mode 100644 packages/svelte/scripts/process-messages/index.js create mode 100644 packages/svelte/scripts/process-messages/templates/compile-errors.js create mode 100644 packages/svelte/src/compiler/errors.js diff --git a/.prettierignore b/.prettierignore index 48d37dc02fc8..b24b12550b49 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,6 +2,7 @@ packages/**/dist/*.js packages/**/build/*.js packages/**/npm/**/* packages/**/config/*.js +packages/svelte/src/compiler/errors.js packages/svelte/tests/**/*.svelte packages/svelte/tests/**/_expected* packages/svelte/tests/**/_actual* diff --git a/packages/svelte/messages/compile-errors/parse.md b/packages/svelte/messages/compile-errors/parse.md new file mode 100644 index 000000000000..d2fbe371df03 --- /dev/null +++ b/packages/svelte/messages/compile-errors/parse.md @@ -0,0 +1,7 @@ +## unclosed_element + +%element% was left open + +## unclosed_block + +Block was left open diff --git a/packages/svelte/scripts/process-messages/index.js b/packages/svelte/scripts/process-messages/index.js new file mode 100644 index 000000000000..fb825c18de96 --- /dev/null +++ b/packages/svelte/scripts/process-messages/index.js @@ -0,0 +1,215 @@ +// @ts-check +import fs from 'node:fs'; +import * as acorn from 'acorn'; +import { walk } from 'zimmerframe'; +import * as esrap from 'esrap'; + +const messages = {}; +const seen = new Set(); + +for (const category of fs.readdirSync('messages')) { + messages[category] = {}; + + for (const file of fs.readdirSync(`messages/${category}`)) { + if (!file.endsWith('.md')) continue; + + const markdown = fs.readFileSync(`messages/${category}/${file}`, 'utf-8'); + + for (const match of markdown.matchAll(/## ([\w]+)\n\n([^]+?)(?=$|\n\n## )/g)) { + const [_, code, text] = match; + + if (seen.has(code)) { + throw new Error(`Duplicate message code ${category}/${code}`); + } + + seen.add(code); + messages[category][code] = text.trim(); + } + } +} + +function transform(name, dest) { + const source = fs.readFileSync(new URL(`./templates/${name}.js`, import.meta.url), 'utf-8'); + + const comments = []; + + const ast = acorn.parse(source, { + ecmaVersion: 'latest', + sourceType: 'module', + onComment: (block, value, start, end) => { + if (block && /\n/.test(value)) { + let a = start; + while (a > 0 && source[a - 1] !== '\n') a -= 1; + + let b = a; + while (/[ \t]/.test(source[b])) b += 1; + + const indentation = source.slice(a, b); + value = value.replace(new RegExp(`^${indentation}`, 'gm'), ''); + } + + comments.push({ type: block ? 'Block' : 'Line', value, start, end }); + } + }); + + walk(ast, null, { + _(node, { next }) { + let comment; + + while (comments[0] && comments[0].start < node.start) { + comment = comments.shift(); + // @ts-expect-error + (node.leadingComments ||= []).push(comment); + } + + next(); + + if (comments[0]) { + const slice = source.slice(node.end, comments[0].start); + + if (/^[,) \t]*$/.test(slice)) { + // @ts-expect-error + node.trailingComments = [comments.shift()]; + } + } + } + }); + + const category = messages[name]; + + // find the `export function CODE` node + const index = ast.body.findIndex((node) => { + if ( + node.type === 'ExportNamedDeclaration' && + node.declaration && + node.declaration.type === 'FunctionDeclaration' + ) { + return node.declaration.id.name === 'CODE'; + } + }); + + if (index === -1) throw new Error(`missing export function CODE in ${name}.js`); + + const template_node = ast.body[index]; + ast.body.splice(index, 1); + + for (const code in category) { + const message = category[code]; + const vars = []; + for (const match of message.matchAll(/%(\w+)%/g)) { + const name = match[1]; + if (!vars.includes(name)) { + vars.push(match[1]); + } + } + + const clone = walk(/** @type {import('estree').Node} */ (template_node), null, { + // @ts-expect-error Block is a block comment, which is not recognised + Block(node, context) { + if (!node.value.includes('PARAMETER')) return; + + const value = node.value + .split('\n') + .map((line) => { + if (line.includes('PARAMETER')) { + return vars.map((name) => ` * @param {string} ${name}`).join('\n'); + } + + return line; + }) + .join('\n'); + + if (value !== node.value) { + return { ...node, value }; + } + }, + FunctionDeclaration(node, context) { + if (node.id.name !== 'CODE') return; + + const params = []; + + for (const param of node.params) { + if (param.type === 'Identifier' && param.name === 'PARAMETER') { + params.push(...vars.map((name) => ({ type: 'Identifier', name }))); + } else { + params.push(param); + } + } + + return /** @type {import('estree').FunctionDeclaration} */ ({ + .../** @type {import('estree').FunctionDeclaration} */ (context.next()), + params, + id: { + ...node.id, + name: code + } + }); + }, + Literal(node) { + if (node.value === 'CODE') { + return { + type: 'Literal', + value: code + }; + } + }, + Identifier(node) { + if (node.name !== 'MESSAGE') return; + + if (/%\w+%/.test(message)) { + const parts = message.split(/(%\w+%)/); + + /** @type {import('estree').Expression[]} */ + const expressions = []; + + /** @type {import('estree').TemplateElement[]} */ + const quasis = []; + + for (let i = 0; i < parts.length; i += 1) { + const part = parts[i]; + if (i % 2 === 0) { + quasis.push({ + type: 'TemplateElement', + value: { + raw: part, + cooked: part + }, + tail: i === parts.length - 1 + }); + } else { + expressions.push({ + type: 'Identifier', + name: part.slice(1, -1) + }); + } + } + + return { + type: 'TemplateLiteral', + expressions, + quasis + }; + } + + return { + type: 'Literal', + value: message + }; + } + }); + + // @ts-expect-error + ast.body.push(clone); + } + + // @ts-expect-error + const module = esrap.print(ast); + + fs.writeFileSync( + dest, + `/* This file is generated by scripts/process-messages.js. Do not edit! */\n\n` + module.code, + 'utf-8' + ); +} + +transform('compile-errors', 'src/compiler/errors.js'); diff --git a/packages/svelte/scripts/process-messages/templates/compile-errors.js b/packages/svelte/scripts/process-messages/templates/compile-errors.js new file mode 100644 index 000000000000..2ee86f449c02 --- /dev/null +++ b/packages/svelte/scripts/process-messages/templates/compile-errors.js @@ -0,0 +1,74 @@ +/** @typedef {{ start?: number, end?: number }} NodeLike */ + +// interface is duplicated between here (used internally) and ./interfaces.js +// (exposed publicly), and I'm not sure how to avoid that +export class CompileError extends Error { + name = 'CompileError'; + + /** @type {import('#compiler').CompileError['filename']} */ + filename = undefined; + + /** @type {import('#compiler').CompileError['position']} */ + position = undefined; + + /** @type {import('#compiler').CompileError['start']} */ + start = undefined; + + /** @type {import('#compiler').CompileError['end']} */ + end = undefined; + + /** + * + * @param {string} code + * @param {string} message + * @param {[number, number] | undefined} position + */ + constructor(code, message, position) { + super(message); + this.code = code; + this.position = position; + } + + 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; + } +} + +/** + * + * @param {number | NodeLike} node + * @param {string} code + * @param {string} message + * @returns {never} + */ +function __error(node, code, message) { + const start = typeof node === 'number' ? node : node?.start; + const end = typeof node === 'number' ? node : node?.end; + + throw new CompileError( + code, + message, + start !== undefined && end !== undefined ? [start, end] : undefined + ); +} + +/** + * @param {number | NodeLike} node + * @param {string} PARAMETER + * @returns {never} + */ +export function CODE(node, PARAMETER) { + __error(node, 'CODE', MESSAGE); +} diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js new file mode 100644 index 000000000000..082acb24cf09 --- /dev/null +++ b/packages/svelte/src/compiler/errors.js @@ -0,0 +1,76 @@ +/* This file is generated by scripts/process-messages.js. Do not edit! */ + +/** @typedef {{ start?: number, end?: number }} NodeLike */ +// interface is duplicated between here (used internally) and ./interfaces.js +// (exposed publicly), and I'm not sure how to avoid that +export class CompileError extends Error { + name = 'CompileError'; + /** @type {import('#compiler').CompileError['filename']} */ + filename = undefined; + /** @type {import('#compiler').CompileError['position']} */ + position = undefined; + /** @type {import('#compiler').CompileError['start']} */ + start = undefined; + /** @type {import('#compiler').CompileError['end']} */ + end = undefined; + + /** + * + * @param {string} code + * @param {string} message + * @param {[number, number] | undefined} position + */ + constructor(code, message, position) { + super(message); + this.code = code; + this.position = position; + } + + 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; + } +} + +/** + * + * @param {number | NodeLike} node + * @param {string} code + * @param {string} message + * @returns {never} + */ +function __error(node, code, message) { + const start = typeof node === 'number' ? node : node?.start; + const end = typeof node === 'number' ? node : node?.end; + + throw new CompileError(code, message, start !== undefined && end !== undefined ? [start, end] : undefined); +} + +/** + * @param {number | NodeLike} node + * @param {string} element + * @returns {never} + */ +export function unclosed_element(node, element) { + __error(node, "unclosed_element", `${element} was left open`); +} + +/** + * @param {number | NodeLike} node + + * @returns {never} + */ +export function unclosed_block(node) { + __error(node, "unclosed_block", "Block was left open"); +} \ No newline at end of file diff --git a/packages/svelte/src/compiler/phases/1-parse/index.js b/packages/svelte/src/compiler/phases/1-parse/index.js index 5c06c0a5ce53..871e40a60399 100644 --- a/packages/svelte/src/compiler/phases/1-parse/index.js +++ b/packages/svelte/src/compiler/phases/1-parse/index.js @@ -5,6 +5,7 @@ import { regex_whitespace } from '../patterns.js'; import { reserved } from './utils/names.js'; import full_char_code_at from './utils/full_char_code_at.js'; import { error } from '../../errors-tmp.js'; +import * as errors from '../../errors.js'; import { create_fragment } from './utils/create.js'; import read_options from './read/options.js'; import { getLocator } from 'locate-character'; @@ -92,10 +93,10 @@ export class Parser { if (current.type === 'RegularElement') { current.end = current.start + 1; - error(current, 'unclosed-element', current.name); + errors.unclosed_element(current, `<${current.name}>`); } else { current.end = current.start + 1; - error(current, 'unclosed-block'); + errors.unclosed_block(current); } } diff --git a/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-d/_config.js b/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-d/_config.js index b10de772d3c5..5099013e1612 100644 --- a/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-d/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-d/_config.js @@ -2,7 +2,7 @@ import { test } from '../../test'; export default test({ error: { - code: 'unclosed-block', + code: 'unclosed_block', message: 'Block was left open', position: [0, 1] } diff --git a/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input/_config.js b/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input/_config.js index 8ff85b724389..d141ce161fdf 100644 --- a/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input/_config.js @@ -2,7 +2,7 @@ import { test } from '../../test'; export default test({ error: { - code: 'unclosed-element', + code: 'unclosed_element', message: '
was left open', position: [0, 1] } From 0c40cc59374db837042290c482050f0b07965eec Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 22 Apr 2024 11:59:17 -0400 Subject: [PATCH 03/22] generate markdown files for more errors --- .../messages/compile-errors/attributes.md | 51 + .../messages/compile-errors/bindings.md | 27 + .../compile-errors/compiler_options.md | 7 + .../messages/compile-errors/components.md | 3 + .../messages/compile-errors/const_tag.md | 3 + .../svelte/messages/compile-errors/css.md | 55 + .../messages/compile-errors/elements.md | 27 + .../compile-errors/legacy_reactivity.md | 3 + .../svelte/messages/compile-errors/parse.md | 142 +- .../svelte/messages/compile-errors/runes.md | 101 ++ .../svelte/messages/compile-errors/slots.md | 28 + .../compile-errors/special_elements.md | 99 ++ .../messages/compile-errors/variables.md | 19 + packages/svelte/src/compiler/errors.js | 1288 ++++++++++++++++- 14 files changed, 1847 insertions(+), 6 deletions(-) create mode 100644 packages/svelte/messages/compile-errors/attributes.md create mode 100644 packages/svelte/messages/compile-errors/bindings.md create mode 100644 packages/svelte/messages/compile-errors/compiler_options.md create mode 100644 packages/svelte/messages/compile-errors/components.md create mode 100644 packages/svelte/messages/compile-errors/const_tag.md create mode 100644 packages/svelte/messages/compile-errors/css.md create mode 100644 packages/svelte/messages/compile-errors/elements.md create mode 100644 packages/svelte/messages/compile-errors/legacy_reactivity.md create mode 100644 packages/svelte/messages/compile-errors/runes.md create mode 100644 packages/svelte/messages/compile-errors/slots.md create mode 100644 packages/svelte/messages/compile-errors/special_elements.md create mode 100644 packages/svelte/messages/compile-errors/variables.md diff --git a/packages/svelte/messages/compile-errors/attributes.md b/packages/svelte/messages/compile-errors/attributes.md new file mode 100644 index 000000000000..e59391fb9a48 --- /dev/null +++ b/packages/svelte/messages/compile-errors/attributes.md @@ -0,0 +1,51 @@ +## empty_attribute_shorthand + +Attribute shorthand cannot be empty + +## duplicate_attribute + +Attributes need to be unique + +## invalid_event_attribute_value + +Event attribute must be a JavaScript expression, not a string + +## invalid_attribute_name + +'%name%' is not a valid attribute name + +## invalid_animation + +An element that uses the animate directive must be the immediate child of a keyed each block` + : type === 'each-key' + ? `An element that uses the animate directive must be used inside a keyed each block. Did you forget to add a key to your each block?` + : `An element that uses the animate directive must be the sole child of a keyed each block + +## duplicate_animation + +An element can only have one 'animate' directive + +## invalid_event_modifier + +Valid event modifiers are %modifiers.slice(0, -1).join(', ')% or %modifiers.slice(-1)%` + : `Event modifiers other than 'once' can only be used on DOM elements + +## invalid_event_modifier_combination + +The '%modifier1%' and '%modifier2%' modifiers cannot be used together + +## duplicate_transition + +TODO + +## invalid_let_directive_placement + +TODO + +## invalid_style_directive_modifier + +Invalid 'style:' modifier. Valid modifiers are: 'important' + +## invalid_sequence_expression + +Sequence expressions are not allowed as attribute/directive values in runes mode, unless wrapped in parentheses \ No newline at end of file diff --git a/packages/svelte/messages/compile-errors/bindings.md b/packages/svelte/messages/compile-errors/bindings.md new file mode 100644 index 000000000000..ad96e37fe4ea --- /dev/null +++ b/packages/svelte/messages/compile-errors/bindings.md @@ -0,0 +1,27 @@ +## invalid_binding_expression + +Can only bind to an Identifier or MemberExpression + +## invalid_binding_value + +Can only bind to state or props + +## invalid_binding + +TODO + +## invalid_type_attribute + +'type' attribute must be a static text value if input uses two-way binding + +## invalid_multiple_attribute + +'multiple' attribute must be static if select uses two-way binding + +## missing_contenteditable_attribute + +'contenteditable' attribute is required for textContent, innerHTML and innerText two-way bindings + +## dynamic_contenteditable_attribute + +'contenteditable' attribute cannot be dynamic if element uses two-way binding \ No newline at end of file diff --git a/packages/svelte/messages/compile-errors/compiler_options.md b/packages/svelte/messages/compile-errors/compiler_options.md new file mode 100644 index 000000000000..761d74c72a39 --- /dev/null +++ b/packages/svelte/messages/compile-errors/compiler_options.md @@ -0,0 +1,7 @@ +## invalid_compiler_option + +Invalid compiler option: %msg% + +## removed_compiler_option + +Invalid compiler option: %msg% \ No newline at end of file diff --git a/packages/svelte/messages/compile-errors/components.md b/packages/svelte/messages/compile-errors/components.md new file mode 100644 index 000000000000..88a31a81c6ca --- /dev/null +++ b/packages/svelte/messages/compile-errors/components.md @@ -0,0 +1,3 @@ +## invalid_component_directive + +This type of directive is not valid on components \ No newline at end of file diff --git a/packages/svelte/messages/compile-errors/const_tag.md b/packages/svelte/messages/compile-errors/const_tag.md new file mode 100644 index 000000000000..de8a8ca90965 --- /dev/null +++ b/packages/svelte/messages/compile-errors/const_tag.md @@ -0,0 +1,3 @@ +## invalid_const_placement + +{@const} must be the immediate child of {#snippet}, {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, or \ No newline at end of file diff --git a/packages/svelte/messages/compile-errors/css.md b/packages/svelte/messages/compile-errors/css.md new file mode 100644 index 000000000000..440f7f7b28a9 --- /dev/null +++ b/packages/svelte/messages/compile-errors/css.md @@ -0,0 +1,55 @@ +## css_parse_error + +TODO + +## invalid_css_empty_declaration + +Declaration cannot be empty + +## invalid_css_global_block_list + +A :global {...} block cannot be part of a selector list with more than one item + +## invalid_css_global_block_modifier + +A :global {...} block cannot modify an existing selector + +## invalid_css_global_block_combinator + +A :global {...} block cannot follow a %name% combinator + +## invalid_css_global_block_declaration + +A :global {...} block can only contain rules, not declarations + +## invalid_css_global_placement + +:global(...) can be at the start or end of a selector sequence, but not in the middle + +## invalid_css_global_selector + +:global(...) must contain exactly one selector + +## invalid_css_global_selector_list + +:global(...) must not contain type or universal selectors when used in a compound selector + +## invalid_css_type_selector_placement + +:global(...) must not be followed with a type selector + +## invalid_css_selector + +Invalid selector + +## invalid_css_identifier + +TODO + +## invalid_nesting_selector + +Nesting selectors can only be used inside a rule + +## invalid_css_declaration + +TODO \ No newline at end of file diff --git a/packages/svelte/messages/compile-errors/elements.md b/packages/svelte/messages/compile-errors/elements.md new file mode 100644 index 000000000000..b15f75c0bf4b --- /dev/null +++ b/packages/svelte/messages/compile-errors/elements.md @@ -0,0 +1,27 @@ +## invalid_textarea_content + +A