chore: markdown errors (#11294)

* rename errors.js to errors-tmp.js

* start porting errors

* generate markdown files for more errors

* convert parse errors

* convert css errors

* convert special_element errors

* convert runes errors

* convert element errors

* more

* more

* more

* more

* more

* more

* fix some tests

* fix some tests

* more tests

* remove unused error

* prettierignore

* weird

* ugh

* lint
pull/11303/head
Rich Harris 1 year ago committed by GitHub
parent 3866887f37
commit 73490bbb8e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -2,6 +2,8 @@ packages/**/dist/*.js
packages/**/build/*.js
packages/**/npm/**/*
packages/**/config/*.js
packages/svelte/messages/**/*.md
packages/svelte/src/compiler/errors.js
packages/svelte/tests/**/*.svelte
packages/svelte/tests/**/_expected*
packages/svelte/tests/**/_actual*

@ -33,13 +33,16 @@ export default [
ignores: [
'**/*.d.ts',
'**/tests',
'packages/svelte/scripts/process-messages/templates/*.js',
'packages/svelte/src/compiler/errors.js',
'packages/svelte/compiler/index.js',
// documentation can contain invalid examples
'documentation',
// contains a fork of the REPL which doesn't adhere to eslint rules
'sites/svelte-5-preview/**',
// wasn't checked previously, reenable at some point
'sites/svelte.dev/**'
'sites/svelte.dev/**',
'tmp/**'
]
}
];

@ -0,0 +1,59 @@
## 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
## animation_invalid_placement
An element that uses the `animate:` directive must be the only child of a keyed `{#each ...}` block
## animation_missing_key
An element that uses the `animate:` directive must be the only child of a keyed `{#each ...}` block. Did you forget to add a key to your each block?
## animation_duplicate
An element can only have one 'animate' directive
## invalid_event_modifier
Valid event modifiers are %list%
## invalid_component_event_modifier
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
## transition_duplicate
Cannot use multiple `%type%:` directives on a single element
## transition_conflict
Cannot use `%type%:` alongside existing `%existing%:` directive
## invalid_let_directive_placement
`let:` directive at invalid position
## 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

@ -0,0 +1,35 @@
## invalid_binding_expression
Can only bind to an Identifier or MemberExpression
## invalid_binding_value
Can only bind to state or props
## bind_invalid_target
`bind:%name%` can only be used with %elements%
## bind_invalid
`bind:%name%` is not a valid binding
## bind_invalid_detailed
`bind:%name%` is not a valid binding. %explanation%
## 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

@ -0,0 +1,7 @@
## invalid_compiler_option
Invalid compiler option: %msg%
## removed_compiler_option
Invalid compiler option: %msg%

@ -0,0 +1,3 @@
## invalid_component_directive
This type of directive is not valid on components

@ -0,0 +1,3 @@
## invalid_const_placement
{@const} must be the immediate child of {#snippet}, {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, <svelte:fragment> or <Component>

@ -0,0 +1,51 @@
## 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
Expected a valid CSS identifier
## invalid_nesting_selector
Nesting selectors can only be used inside a rule
## invalid_css_declaration
Declaration cannot be empty

@ -0,0 +1,27 @@
## invalid_textarea_content
A `<textarea>` can have either a value attribute or (equivalently) child content, but not both
## invalid_void_content
Void elements cannot have children or closing tags
## invalid_element_content
<%name%> cannot have children
## invalid_tag_name
Expected valid tag name
## invalid_node_placement
%thing% is invalid inside <%parent%>
## illegal_title_attribute
`<title>` cannot have attributes nor directives
## invalid_title_content
`<title>` can only contain text and {tags}

@ -0,0 +1,3 @@
## cyclical_reactive_declaration
Cyclical dependency detected: %cycle%

@ -0,0 +1,147 @@
## unclosed_element
`<%name%>` was left open
## unclosed_block
Block was left open
## unexpected_block_close
Unexpected block closing tag
## unexpected_eof
Unexpected end of input
## js_parse_error
%message%
## expected_token
Expected token %token%
## unexpected_reserved_word
'%word%' is a reserved word in JavaScript and cannot be used here
## missing_whitespace
Expected whitespace
## expected_pattern
Expected identifier or destructure pattern
## invalid_script_context
If the context attribute is supplied, its value must be "module"
## invalid_elseif
'elseif' should be 'else if'
## invalid_continuing_block_placement
{:...} block is invalid at this position (did you forget to close the preceeding element or block?)
## invalid_block_missing_parent
%child% block must be a child of %parent%
## duplicate_block_part
%name% cannot appear more than once within a block
## expected_block_type
Expected 'if', 'each', 'await', 'key' or 'snippet'
## expected_identifier
Expected an identifier
## invalid_debug
{@debug ...} arguments must be identifiers, not arbitrary expressions
## invalid_const
{@const ...} must be an assignment
## invalid_block_placement
{#%name% ...} block cannot be %location%
## invalid_tag_placement
{@%name% ...} tag cannot be %location%
## missing_attribute_value
Expected attribute value
## unclosed_attribute_value
Expected closing %delimiter% character
## invalid_directive_value
Directive value must be a JavaScript expression enclosed in curly braces
## empty_directive_name
%type% name cannot be empty
## invalid_closing_tag
</%name%> attempted to close an element that was not open
## invalid_closing_tag_after_autoclose
</%name%> attempted to close element that was already automatically closed by <%reason%> (cannot nest <%reason%> inside <%name%>)
## invalid_dollar_binding
The $ name is reserved, and cannot be used for variables and imports
## invalid_dollar_prefix
The $ prefix is reserved, and cannot be used for variables and imports
## invalid_dollar_global
The $ name is reserved. To reference a global variable called $, use globalThis.$
## illegal_subscription
Cannot reference store value inside `<script context="module">`
## duplicate_style_element
A component can have a single top-level `<style>` element
## duplicate_script_element
A component can have a single top-level `<script>` element and/or a single top-level `<script context="module">` element
## invalid_render_expression
{@render ...} tags can only contain call expressions
## invalid_render_arguments
expected at most one argument
## invalid_render_call
Calling a snippet function using apply, bind or call is not allowed
## invalid_render_spread_argument
cannot use spread arguments in {@render ...} tags
## invalid_snippet_rest_parameter
snippets do not support rest parameters; use an array instead

@ -0,0 +1,95 @@
## invalid_legacy_props
Cannot use `$$props` in runes mode
## invalid_legacy_rest_props
Cannot use `$$restProps` in runes mode
## invalid_legacy_reactive_statement
`$:` is not allowed in runes mode, use `$derived` or `$effect` instead
## invalid_legacy_export
Cannot use `export let` in runes mode — use $props instead
## invalid_rune_usage
Cannot use %rune% rune in non-runes mode
## invalid_state_export
Cannot export state from a module if it is reassigned. Either export a function returning the state value or only mutate the state value's properties
## invalid_derived_export
Cannot export derived state from a module. To expose the current derived value, export a function returning its value
## invalid_props_id
`$props()` can only be used with an object destructuring pattern
## invalid_props_pattern
`$props()` assignment must not contain nested properties or computed keys
## invalid_props_location
`$props()` can only be used at the top level of components as a variable declaration initializer
## invalid_bindable_location
`$bindable()` can only be used inside a `$props()` declaration
## invalid_state_location
`%rune%(...)` can only be used as a variable declaration initializer or a class field
## invalid_effect_location
`$effect()` can only be used as an expression statement
## invalid_host_location
`$host()` can only be used inside custom element component instances
## invalid_assignment
Cannot assign to %thing%
## invalid_binding
Cannot bind to %thing%
## invalid_rune_args
`%rune%` cannot be called with arguments
## invalid_rune_args_length
`%rune%` must be called with %args%
## invalid_runes_mode_import
%name% cannot be used in runes mode
## duplicate_props_rune
Cannot use `$props()` more than once
## invalid_each_assignment
Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`)
## invalid_snippet_assignment
Cannot reassign or bind to snippet parameter
## invalid_derived_call
`$derived.call(...)` has been replaced with `$derived.by(...)`
## conflicting_property_name
Cannot have a property and a component export with the same name

@ -0,0 +1,31 @@
## invalid_slot_element_attribute
`<slot>` can only receive attributes and (optionally) let directives
## invalid_slot_attribute
slot attribute must be a static value
## invalid_slot_name_default
`default` is a reserved word — it cannot be used as a slot name
## invalid_slot_name
slot attribute must be a static value
## invalid_slot_placement
Element with a slot='...' attribute must be a child of a component or a descendant of a custom element
## duplicate_slot_name
Duplicate slot name '%name%' in <%component%>
## invalid_default_slot_content
Found default slot content alongside an explicit slot="default"
## conflicting_children_snippet
Cannot use explicit children snippet at the same time as implicit children content. Remove either the non-whitespace content or the children snippet block

@ -0,0 +1,99 @@
## invalid_svelte_option_attribute
`<svelte:options>` can only receive static attributes
## invalid_svelte_option_namespace
Unsupported `<svelte:option>` value for "namespace". Valid values are "html", "svg" or "foreign"
## tag_option_deprecated
"tag" option is deprecated — use "customElement" instead
## invalid_svelte_option_runes
Unsupported `<svelte:option>` value for "runes". Valid values are true or false
## invalid_svelte_option_accessors
Unsupported `<svelte:option>` value for "accessors". Valid values are true or false
## invalid_svelte_option_preserveWhitespace
Unsupported `<svelte:option>` value for "preserveWhitespace". Valid values are true or false
## invalid_svelte_option_immutable
Unsupported `<svelte:option>` value for "immutable". Valid values are true or false
## invalid_tag_property
Tag name must be two or more words joined by the "-" character
## invalid_svelte_option_customElement
"customElement" must be a string literal defining a valid custom element name or an object of the form { tag: string; shadow?: "open" | "none"; props?: { [key: string]: { attribute?: string; reflect?: boolean; type: .. } } }
## invalid_customElement_props_attribute
"props" must be a statically analyzable object literal of the form "{ [key: string]: { attribute?: string; reflect?: boolean; type?: "String" | "Boolean" | "Number" | "Array" | "Object" }"
## invalid_customElement_shadow_attribute
"shadow" must be either "open" or "none"
## unknown_svelte_option_attribute
`<svelte:options>` unknown attribute '%name%'
## illegal_svelte_head_attribute
`<svelte:head>` cannot have attributes nor directives
## invalid_svelte_fragment_attribute
`<svelte:fragment>` can only have a slot attribute and (optionally) a let: directive
## invalid_svelte_fragment_slot
`<svelte:fragment>` slot attribute must have a static value
## invalid_svelte_fragment_placement
`<svelte:fragment>` must be the direct child of a component
## invalid_svelte_element_placement
<%name%> tags cannot be inside elements or blocks
## duplicate_svelte_element
A component can only have one <%name%> element
## invalid_self_placement
`<svelte:self>` components can only exist inside {#if} blocks, {#each} blocks, {#snippet} blocks or slots passed to components
## missing_svelte_element_definition
`<svelte:element>` must have a 'this' attribute
## missing_svelte_component_definition
`<svelte:component>` must have a 'this' attribute
## invalid_svelte_element_definition
Invalid element definition — must be an {expression}
## invalid_svelte_component_definition
Invalid component definition — must be an {expression}
## invalid_svelte_tag
Valid `<svelte:...>` tag names are %list%
## conflicting_slot_usage
Cannot use `<slot>` syntax and `{@render ...}` tags in the same component. Migrate towards `{@render ...}` tags completely.

@ -0,0 +1,19 @@
## illegal_global
`%name%` is an illegal variable name. To reference a global variable called `%name%`, use `globalThis.%name%`
## duplicate_declaration
`%name%` has already been declared
## default_export
A component cannot have a default export
## illegal_variable_declaration
Cannot declare same variable name which is imported inside `<script context="module">`
## illegal_store_subscription
Cannot subscribe to stores that are not declared at the top level of the component

@ -0,0 +1,213 @@
// @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) {
const str = part.replace(/(`|\$)/g, '\\$1');
quasis.push({
type: 'TemplateElement',
value: { raw: str, cooked: str },
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');

@ -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 {null | number | NodeLike} node
* @param {string} code
* @param {string} message
* @returns {never}
*/
function e(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 {null | number | NodeLike} node
* @param {string} PARAMETER
* @returns {never}
*/
export function CODE(node, PARAMETER) {
e(node, 'CODE', MESSAGE);
}

File diff suppressed because it is too large Load Diff

@ -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 * as e from '../../errors.js';
import { create_fragment } from './utils/create.js';
import read_options from './read/options.js';
import { getLocator } from 'locate-character';
@ -92,15 +92,15 @@ export class Parser {
if (current.type === 'RegularElement') {
current.end = current.start + 1;
error(current, 'unclosed-element', current.name);
e.unclosed_element(current, current.name);
} else {
current.end = current.start + 1;
error(current, 'unclosed-block');
e.unclosed_block(current);
}
}
if (state !== fragment) {
error(this.index, 'unexpected-eof');
e.unexpected_eof(this.index);
}
if (this.root.fragment.nodes.length) {
@ -158,7 +158,7 @@ export class Parser {
* @returns {never}
*/
acorn_error(err) {
error(err.pos, 'js-parse-error', err.message.replace(regex_position_indicator, ''));
e.js_parse_error(err.pos, err.message.replace(regex_position_indicator, ''));
}
/**
@ -172,11 +172,7 @@ export class Parser {
}
if (required) {
if (this.index === this.template.length) {
error(this.index, 'unexpected-eof', str);
} else {
error(this.index, 'expected-token', str);
}
e.expected_token(this.index, str);
}
return false;
@ -241,7 +237,7 @@ export class Parser {
const identifier = this.template.slice(this.index, (this.index = i));
if (!allow_reserved && reserved.includes(identifier)) {
error(start, 'unexpected-reserved-word', identifier);
e.unexpected_reserved_word(start, identifier);
}
return identifier;
@ -250,7 +246,7 @@ export class Parser {
/** @param {RegExp} pattern */
read_until(pattern) {
if (this.index >= this.template.length) {
error(this.template.length, 'unexpected-eof');
e.unexpected_eof(this.template.length);
}
const start = this.index;
@ -267,7 +263,7 @@ export class Parser {
require_whitespace() {
if (!regex_whitespace.test(this.template[this.index])) {
error(this.index, 'missing-whitespace');
e.missing_whitespace(this.index);
}
this.allow_whitespace();

@ -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 * as e from '../../../errors.js';
/**
* @param {import('../index.js').Parser} parser
@ -36,7 +36,7 @@ export default function read_pattern(parser, optional_allowed = false) {
}
if (!is_bracket_open(code)) {
error(i, 'expected-pattern');
e.expected_pattern(i);
}
const bracket_stack = [code];
@ -49,11 +49,7 @@ export default function read_pattern(parser, optional_allowed = false) {
} else if (is_bracket_close(code)) {
const popped = /** @type {number} */ (bracket_stack.pop());
if (!is_bracket_pair(popped, code)) {
error(
i,
'expected-token',
String.fromCharCode(/** @type {number} */ (get_bracket_close(popped)))
);
e.expected_token(i, String.fromCharCode(/** @type {number} */ (get_bracket_close(popped))));
}
if (bracket_stack.length === 0) {
i += code <= 0xffff ? 1 : 2;

@ -1,6 +1,6 @@
import { parse_expression_at } from '../acorn.js';
import { regex_whitespace } from '../../patterns.js';
import { error } from '../../../errors.js';
import * as e from '../../../errors.js';
/**
* @param {import('../index.js').Parser} parser
@ -23,7 +23,7 @@ export default function read_expression(parser) {
if (char === ')') {
num_parens -= 1;
} else if (!regex_whitespace.test(char)) {
error(index, 'expected-token', ')');
e.expected_token(index, ')');
}
index += 1;

@ -1,5 +1,5 @@
import { namespace_svg } from '../../../../constants.js';
import { error } from '../../../errors.js';
import * as e from '../../../errors.js';
const regex_valid_tag_name = /^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/;
@ -22,24 +22,18 @@ export default function read_options(node) {
for (const attribute of node.attributes) {
if (attribute.type !== 'Attribute') {
error(attribute, 'invalid-svelte-option-attribute');
e.invalid_svelte_option_attribute(attribute);
}
const { name } = attribute;
switch (name) {
case 'runes': {
const value = get_static_value(attribute, () =>
error(attribute, 'invalid-svelte-option-runes')
);
if (typeof value !== 'boolean') {
error(attribute, 'invalid-svelte-option-runes');
}
component_options.runes = value;
component_options.runes = get_boolean_value(attribute, e.invalid_svelte_option_runes);
break;
}
case 'tag': {
error(attribute, 'tag-option-deprecated');
e.tag_option_deprecated(attribute);
break; // eslint doesn't know this is unnecessary
}
case 'customElement': {
@ -48,9 +42,9 @@ export default function read_options(node) {
const { value } = attribute;
if (value === true) {
error(attribute, 'invalid-svelte-option-customElement');
e.invalid_svelte_option_customElement(attribute);
} else if (value[0].type === 'Text') {
const tag = get_static_value(attribute, () => error(attribute, 'invalid-tag-property'));
const tag = get_static_value(attribute, () => e.invalid_tag_property(attribute));
validate_tag(attribute, tag);
ce.tag = tag;
component_options.customElement = ce;
@ -61,7 +55,7 @@ export default function read_options(node) {
if (value[0].expression.type === 'Literal' && value[0].expression.value === null) {
break;
}
error(attribute, 'invalid-svelte-option-customElement');
e.invalid_svelte_option_customElement(attribute);
}
/** @type {Array<[string, any]>} */
@ -72,7 +66,7 @@ export default function read_options(node) {
property.computed ||
property.key.type !== 'Identifier'
) {
error(attribute, 'invalid-svelte-option-customElement');
e.invalid_svelte_option_customElement(attribute);
}
properties.push([property.key.name, property.value]);
}
@ -83,13 +77,13 @@ export default function read_options(node) {
validate_tag(tag, tag_value);
ce.tag = tag_value;
} else {
error(attribute, 'invalid-svelte-option-customElement');
e.invalid_svelte_option_customElement(attribute);
}
const props = properties.find(([name]) => name === 'props')?.[1];
if (props) {
if (props.type !== 'ObjectExpression') {
error(attribute, 'invalid-customElement-props-attribute');
e.invalid_customElement_props_attribute(attribute);
}
ce.props = {};
for (const property of /** @type {import('estree').ObjectExpression} */ (props)
@ -100,7 +94,7 @@ export default function read_options(node) {
property.key.type !== 'Identifier' ||
property.value.type !== 'ObjectExpression'
) {
error(attribute, 'invalid-customElement-props-attribute');
e.invalid_customElement_props_attribute(attribute);
}
ce.props[property.key.name] = {};
for (const prop of property.value.properties) {
@ -110,7 +104,7 @@ export default function read_options(node) {
prop.key.type !== 'Identifier' ||
prop.value.type !== 'Literal'
) {
error(attribute, 'invalid-customElement-props-attribute');
e.invalid_customElement_props_attribute(attribute);
}
if (prop.key.name === 'type') {
@ -119,21 +113,21 @@ export default function read_options(node) {
/** @type {string} */ (prop.value.value)
) === -1
) {
error(attribute, 'invalid-customElement-props-attribute');
e.invalid_customElement_props_attribute(attribute);
}
ce.props[property.key.name].type = /** @type {any} */ (prop.value.value);
} else if (prop.key.name === 'reflect') {
if (typeof prop.value.value !== 'boolean') {
error(attribute, 'invalid-customElement-props-attribute');
e.invalid_customElement_props_attribute(attribute);
}
ce.props[property.key.name].reflect = prop.value.value;
} else if (prop.key.name === 'attribute') {
if (typeof prop.value.value !== 'string') {
error(attribute, 'invalid-customElement-props-attribute');
e.invalid_customElement_props_attribute(attribute);
}
ce.props[property.key.name].attribute = prop.value.value;
} else {
error(attribute, 'invalid-customElement-props-attribute');
e.invalid_customElement_props_attribute(attribute);
}
}
}
@ -143,7 +137,7 @@ export default function read_options(node) {
if (shadow) {
const shadowdom = shadow?.value;
if (shadowdom !== 'open' && shadowdom !== 'none') {
error(shadow, 'invalid-customElement-shadow-attribute');
e.invalid_customElement_shadow_attribute(shadow);
}
ce.shadow = shadowdom;
}
@ -158,10 +152,10 @@ export default function read_options(node) {
}
case 'namespace': {
const value = get_static_value(attribute, () =>
error(attribute, 'invalid-svelte-option-namespace')
e.invalid_svelte_option_namespace(attribute)
);
if (typeof value !== 'string') {
error(attribute, 'invalid-svelte-option-namespace');
e.invalid_svelte_option_namespace(attribute);
}
if (value === namespace_svg) {
@ -169,43 +163,34 @@ export default function read_options(node) {
} else if (value === 'html' || value === 'svg' || value === 'foreign') {
component_options.namespace = value;
} else {
error(attribute, 'invalid-svelte-option-namespace');
e.invalid_svelte_option_namespace(attribute);
}
break;
}
case 'immutable': {
const value = get_static_value(attribute, () =>
error(attribute, 'invalid-svelte-option-immutable')
component_options.immutable = get_boolean_value(
attribute,
e.invalid_svelte_option_immutable
);
if (typeof value !== 'boolean') {
error(attribute, 'invalid-svelte-option-immutable');
}
component_options.immutable = value;
break;
}
case 'preserveWhitespace': {
const value = get_static_value(attribute, () =>
error(attribute, 'invalid-svelte-option-preserveWhitespace')
component_options.preserveWhitespace = get_boolean_value(
attribute,
e.invalid_svelte_option_preserveWhitespace
);
if (typeof value !== 'boolean') {
error(attribute, 'invalid-svelte-option-preserveWhitespace');
}
component_options.preserveWhitespace = value;
break;
}
case 'accessors': {
const value = get_static_value(attribute, () =>
error(attribute, 'invalid-svelte-option-accessors')
component_options.accessors = get_boolean_value(
attribute,
e.invalid_svelte_option_accessors
);
if (typeof value !== 'boolean') {
error(attribute, 'invalid-svelte-option-accessors');
}
component_options.accessors = value;
break;
}
default:
error(attribute, 'unknown-svelte-option-attribute', name);
e.unknown_svelte_option_attribute(attribute, name);
}
}
@ -230,6 +215,18 @@ function get_static_value(attribute, error) {
return chunk.expression.value;
}
/**
* @param {any} attribute
* @param {(attribute: any) => never} error
*/
function get_boolean_value(attribute, error) {
const value = get_static_value(attribute, () => error(attribute));
if (typeof value !== 'boolean') {
error(attribute);
}
return value;
}
/**
* @param {any} attribute
* @param {string} tag
@ -237,10 +234,10 @@ function get_static_value(attribute, error) {
*/
function validate_tag(attribute, tag) {
if (typeof tag !== 'string' && tag !== null) {
error(attribute, 'invalid-tag-property');
e.invalid_tag_property(attribute);
}
if (tag && !regex_valid_tag_name.test(tag)) {
error(attribute, 'invalid-tag-property');
e.invalid_tag_property(attribute);
}
// TODO do we still need this?
// if (tag && !component.compile_options.customElement) {

@ -1,6 +1,6 @@
import * as acorn from '../acorn.js';
import { regex_not_newline_characters } from '../../patterns.js';
import { error } from '../../../errors.js';
import * as e from '../../../errors.js';
const regex_closing_script_tag = /<\/script\s*>/;
const regex_starts_with_closing_script_tag = /^<\/script\s*>/;
@ -16,13 +16,13 @@ function get_context(attributes) {
if (!context) return 'default';
if (context.value.length !== 1 || context.value[0].type !== 'Text') {
error(context.start, 'invalid-script-context');
e.invalid_script_context(context.start);
}
const value = context.value[0].data;
if (value !== 'module') {
error(context.start, 'invalid-script-context');
e.invalid_script_context(context.start);
}
return value;
@ -38,7 +38,7 @@ export function read_script(parser, start, attributes) {
const script_start = parser.index;
const data = parser.read_until(regex_closing_script_tag);
if (parser.index >= parser.template.length) {
error(parser.template.length, 'unclosed-element', 'script');
e.unclosed_element(parser.template.length, 'script');
}
const source =

@ -1,4 +1,4 @@
import { error } from '../../../errors.js';
import * as e from '../../../errors.js';
const REGEX_MATCHER = /^[~^$*|]?=/;
const REGEX_CLOSING_BRACKET = /[\s\]]/;
@ -64,7 +64,7 @@ function read_body(parser, close) {
}
}
error(parser.template.length, 'expected-token', close);
e.expected_token(parser.template.length, close);
}
/**
@ -154,7 +154,7 @@ function read_selector_list(parser, inside_pseudo_class = false) {
}
}
error(parser.template.length, 'unexpected-eof');
e.unexpected_eof(parser.template.length);
}
/**
@ -352,7 +352,7 @@ function read_selector(parser, inside_pseudo_class = false) {
if (combinator) {
if (relative_selector.selectors.length === 0) {
if (!inside_pseudo_class) {
error(start, 'invalid-css-selector');
e.invalid_css_selector(start);
}
} else {
relative_selector.end = index;
@ -365,12 +365,12 @@ function read_selector(parser, inside_pseudo_class = false) {
parser.allow_whitespace();
if (parser.match(',') || (inside_pseudo_class ? parser.match(')') : parser.match('{'))) {
error(parser.index, 'invalid-css-selector');
e.invalid_css_selector(parser.index);
}
}
}
error(parser.template.length, 'unexpected-eof');
e.unexpected_eof(parser.template.length);
}
/**
@ -476,7 +476,7 @@ function read_declaration(parser) {
const value = read_value(parser);
if (!value && !property.startsWith('--')) {
error(parser.index, 'invalid-css-declaration');
e.invalid_css_declaration(parser.index);
}
const end = parser.index;
@ -531,7 +531,7 @@ function read_value(parser) {
parser.index++;
}
error(parser.template.length, 'unexpected-eof');
e.unexpected_eof(parser.template.length);
}
/**
@ -564,7 +564,7 @@ function read_attribute_value(parser) {
parser.index++;
}
error(parser.template.length, 'unexpected-eof');
e.unexpected_eof(parser.template.length);
}
/**
@ -577,7 +577,7 @@ function read_identifier(parser) {
let identifier = '';
if (parser.match('--') || parser.match_regex(REGEX_LEADING_HYPHEN_OR_DIGIT)) {
error(start, 'invalid-css-identifier');
e.invalid_css_identifier(start);
}
let escaped = false;
@ -602,7 +602,7 @@ function read_identifier(parser) {
}
if (identifier === '') {
error(start, 'invalid-css-identifier');
e.invalid_css_identifier(start);
}
return identifier;

@ -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 * as e from '../../../errors.js';
import { create_fragment } from '../utils/create.js';
import { create_attribute } from '../../nodes.js';
@ -104,19 +104,18 @@ export default function tag(parser) {
['svelte:options', 'svelte:window', 'svelte:body', 'svelte:document'].includes(name) &&
/** @type {import('#compiler').ElementLike} */ (parent).fragment.nodes.length
) {
error(
e.invalid_element_content(
/** @type {import('#compiler').ElementLike} */ (parent).fragment.nodes[0].start,
'invalid-element-content',
name
);
}
} else {
if (name in parser.meta_tags) {
error(start, 'duplicate-svelte-element', name);
e.duplicate_svelte_element(start, name);
}
if (parent.type !== 'Root') {
error(start, 'invalid-svelte-element-placement', name);
e.invalid_svelte_element_placement(start, name);
}
parser.meta_tags[name] = true;
@ -168,7 +167,7 @@ export default function tag(parser) {
if (is_closing_tag) {
if (is_void(name)) {
error(start, 'invalid-void-content');
e.invalid_void_content(start);
}
parser.eat('>', true);
@ -177,14 +176,9 @@ export default function tag(parser) {
while (/** @type {import('#compiler').RegularElement} */ (parent).name !== name) {
if (parent.type !== 'RegularElement') {
if (parser.last_auto_closed_tag && parser.last_auto_closed_tag.tag === name) {
error(
start,
'invalid-closing-tag-after-autoclose',
name,
parser.last_auto_closed_tag.reason
);
e.invalid_closing_tag_after_autoclose(start, name, parser.last_auto_closed_tag.reason);
} else {
error(start, 'invalid-closing-tag', name);
e.invalid_closing_tag(start, name);
}
}
@ -225,7 +219,7 @@ export default function tag(parser) {
while ((attribute = read(parser))) {
if (attribute.type === 'Attribute' || attribute.type === 'BindDirective') {
if (unique_names.includes(attribute.name)) {
error(attribute.start, 'duplicate-attribute');
e.duplicate_attribute(attribute.start);
// <svelte:element bind:this this=..> is allowed
} else if (attribute.name !== 'this') {
unique_names.push(attribute.name);
@ -242,7 +236,7 @@ export default function tag(parser) {
(attr) => attr.type === 'Attribute' && attr.name === 'this'
);
if (index === -1) {
error(start, 'missing-svelte-component-definition');
e.missing_svelte_component_definition(start);
}
const definition = /** @type {import('#compiler').Attribute} */ (
@ -253,7 +247,7 @@ export default function tag(parser) {
definition.value.length !== 1 ||
definition.value[0].type === 'Text'
) {
error(definition.start, 'invalid-svelte-component-definition');
e.invalid_svelte_component_definition(definition.start);
}
element.expression = definition.value[0].expression;
@ -265,14 +259,14 @@ export default function tag(parser) {
(attr) => attr.type === 'Attribute' && attr.name === 'this'
);
if (index === -1) {
error(start, 'missing-svelte-element-definition');
e.missing_svelte_element_definition(start);
}
const definition = /** @type {import('#compiler').Attribute} */ (
element.attributes.splice(index, 1)[0]
);
if (definition.value === true || definition.value.length !== 1) {
error(definition.start, 'invalid-svelte-element-definition');
e.invalid_svelte_element_definition(definition.start);
}
const chunk = definition.value[0];
element.tag =
@ -311,17 +305,17 @@ export default function tag(parser) {
}
if (content.context === 'module') {
if (current.module) error(start, 'duplicate-script-element');
if (current.module) e.duplicate_script_element(start);
current.module = content;
} else {
if (current.instance) error(start, 'duplicate-script-element');
if (current.instance) e.duplicate_script_element(start);
current.instance = content;
}
} else {
const content = read_style(parser, start, element.attributes);
content.content.comment = prev_comment;
if (current.css) error(start, 'duplicate-style-element');
if (current.css) e.duplicate_style_element(start);
current.css = content;
}
return;
@ -396,7 +390,7 @@ function read_tag_name(parser) {
}
if (!legal) {
error(start, 'invalid-self-placement');
e.invalid_self_placement(start);
}
return 'svelte:self';
@ -412,12 +406,12 @@ function read_tag_name(parser) {
if (meta_tags.has(name)) return name;
if (name.startsWith('svelte:')) {
const match = fuzzymatch(name.slice(7), valid_meta_tags);
error(start, 'invalid-svelte-tag', valid_meta_tags, match);
const list = `${valid_meta_tags.slice(0, -1).join(', ')} or ${valid_meta_tags[valid_meta_tags.length - 1]}`;
e.invalid_svelte_tag(start, list);
}
if (!valid_tag_name.test(name)) {
error(start, 'invalid-tag-name');
e.invalid_tag_name(start);
}
return name;
@ -445,7 +439,7 @@ function read_static_attribute(parser) {
parser.allow_whitespace();
let raw = parser.match_regex(regex_attribute_value);
if (!raw) {
error(parser.index, 'missing-attribute-value');
e.missing_attribute_value(parser.index);
}
parser.index += raw.length;
@ -468,7 +462,7 @@ function read_static_attribute(parser) {
}
if (parser.match_regex(regex_starts_with_quote_characters)) {
error(parser.index, 'expected-token', '=');
e.expected_token(parser.index, '=');
}
return create_attribute(name, start, parser.index, value);
@ -509,7 +503,7 @@ function read_attribute(parser) {
const name = parser.read_identifier();
if (name === null) {
error(start, 'empty-attribute-shorthand');
e.empty_attribute_shorthand(start);
}
parser.allow_whitespace();
@ -554,14 +548,14 @@ function read_attribute(parser) {
value = read_attribute_value(parser);
end = parser.index;
} else if (parser.match_regex(regex_starts_with_quote_characters)) {
error(parser.index, 'expected-token', '=');
e.expected_token(parser.index, '=');
}
if (type) {
const [directive_name, ...modifiers] = name.slice(colon_index + 1).split('|');
if (directive_name === '') {
error(start + colon_index + 1, 'empty-directive-name', type);
e.empty_directive_name(start + colon_index + 1, type);
}
if (type === 'StyleDirective') {
@ -586,7 +580,7 @@ function read_attribute(parser) {
const attribute_contains_text =
/** @type {any[]} */ (value).length > 1 || first_value.type === 'Text';
if (attribute_contains_text) {
error(/** @type {number} */ (first_value.start), 'invalid-directive-value');
e.invalid_directive_value(/** @type {number} */ (first_value.start));
} else {
expression = first_value.expression;
}
@ -679,22 +673,22 @@ function read_attribute_value(parser) {
},
'in attribute value'
);
} catch (/** @type {any} e */ e) {
if (e.code === 'js-parse-error') {
} catch (/** @type {any} */ error) {
if (error.code === 'js_parse_error') {
// if the attribute value didn't close + self-closing tag
// eg: `<Component test={{a:1} />`
// acorn may throw a `Unterminated regular expression` because of `/>`
const pos = e.position?.[0];
const pos = error.position?.[0];
if (pos !== undefined && parser.template.slice(pos - 1, pos + 1) === '/>') {
parser.index = pos;
error(pos, 'unclosed-attribute-value', quote_mark || '}');
e.unclosed_attribute_value(pos, quote_mark || '}');
}
}
throw e;
throw error;
}
if (value.length === 0 && !quote_mark) {
error(parser.index, 'missing-attribute-value');
e.missing_attribute_value(parser.index);
}
if (quote_mark) parser.index += 1;
@ -741,12 +735,12 @@ function read_sequence(parser, done, location) {
const index = parser.index - 1;
parser.eat('#');
const name = parser.read_until(/[^a-z]/);
error(index, 'invalid-block-placement', location, name);
e.invalid_block_placement(index, name, location);
} else if (parser.match('@')) {
const index = parser.index - 1;
parser.eat('@');
const name = parser.read_until(/[^a-z]/);
error(index, 'invalid-tag-placement', location, name);
e.invalid_tag_placement(index, name, location);
}
flush(parser.index - 1);
@ -784,5 +778,5 @@ function read_sequence(parser, done, location) {
}
}
error(parser.template.length, 'unexpected-eof');
e.unexpected_eof(parser.template.length);
}

@ -1,6 +1,6 @@
import read_pattern from '../read/context.js';
import read_expression from '../read/expression.js';
import { error } from '../../../errors.js';
import * as e from '../../../errors.js';
import { create_fragment } from '../utils/create.js';
import { walk } from 'zimmerframe';
@ -147,7 +147,7 @@ function open(parser) {
parser.allow_whitespace();
index = parser.read_identifier();
if (!index) {
error(parser.index, 'expected-identifier');
e.expected_identifier(parser.index);
}
parser.allow_whitespace();
@ -265,7 +265,7 @@ function open(parser) {
const name_end = parser.index;
if (name === null) {
error(parser.index, 'expected-identifier');
e.expected_identifier(parser.index);
}
parser.eat('(', true);
@ -320,7 +320,7 @@ function open(parser) {
return;
}
error(parser.index, 'expected-block-type');
e.expected_block_type(parser.index);
}
/** @param {import('../index.js').Parser} parser */
@ -330,8 +330,8 @@ function next(parser) {
const block = parser.current(); // TODO type should not be TemplateNode, that's much too broad
if (block.type === 'IfBlock') {
if (!parser.eat('else')) error(start, 'expected-token', '{:else} or {:else if}');
if (parser.eat('if')) error(start, 'invalid-elseif');
if (!parser.eat('else')) e.expected_token(start, '{:else} or {:else if}');
if (parser.eat('if')) e.invalid_elseif(start);
parser.allow_whitespace();
@ -373,7 +373,7 @@ function next(parser) {
}
if (block.type === 'EachBlock') {
if (!parser.eat('else')) error(start, 'expected-token', '{:else}');
if (!parser.eat('else')) e.expected_token(start, '{:else}');
parser.allow_whitespace();
parser.eat('}', true);
@ -389,7 +389,7 @@ function next(parser) {
if (block.type === 'AwaitBlock') {
if (parser.eat('then')) {
if (block.then) {
error(start, 'duplicate-block-part', '{:then}');
e.duplicate_block_part(start, '{:then}');
}
if (!parser.eat('}')) {
@ -408,7 +408,7 @@ function next(parser) {
if (parser.eat('catch')) {
if (block.catch) {
error(start, 'duplicate-block-part', '{:catch}');
e.duplicate_block_part(start, '{:catch}');
}
if (!parser.eat('}')) {
@ -425,10 +425,10 @@ function next(parser) {
return;
}
error(start, 'expected-token', '{:then ...} or {:catch ...}');
e.expected_token(start, '{:then ...} or {:catch ...}');
}
error(start, 'invalid-continuing-block-placement');
e.invalid_continuing_block_placement(start);
}
/** @param {import('../index.js').Parser} parser */
@ -466,11 +466,11 @@ function close(parser) {
case 'RegularElement':
// TODO handle implicitly closed elements
error(start, 'unexpected-block-close');
e.unexpected_block_close(start);
break;
default:
error(start, 'unexpected-block-close');
e.unexpected_block_close(start);
}
parser.allow_whitespace();
@ -522,7 +522,7 @@ function special(parser) {
identifiers.forEach(
/** @param {any} node */ (node) => {
if (node.type !== 'Identifier') {
error(/** @type {number} */ (node.start), 'invalid-debug');
e.invalid_debug(/** @type {number} */ (node.start));
}
}
);
@ -583,7 +583,7 @@ function special(parser) {
expression.expression.type !== 'CallExpression' ||
!expression.expression.optional)
) {
error(expression, 'invalid-render-expression');
e.invalid_render_expression(expression);
}
parser.allow_whitespace();

@ -1,5 +1,5 @@
import { walk } from 'zimmerframe';
import { error } from '../../../errors.js';
import * as e from '../../../errors.js';
import { is_keyframes_node } from '../../css.js';
import { merge } from '../../visitors.js';
@ -98,31 +98,26 @@ const validation_visitors = {
Rule(node, context) {
if (node.metadata.is_global_block) {
if (node.prelude.children.length > 1) {
error(node.prelude, 'invalid-css-global-block-list');
e.invalid_css_global_block_list(node.prelude);
}
const complex_selector = node.prelude.children[0];
const relative_selector = complex_selector.children[complex_selector.children.length - 1];
if (relative_selector.selectors.length > 1) {
error(
relative_selector.selectors[relative_selector.selectors.length - 1],
'invalid-css-global-block-modifier'
e.invalid_css_global_block_modifier(
relative_selector.selectors[relative_selector.selectors.length - 1]
);
}
if (relative_selector.combinator && relative_selector.combinator.name !== ' ') {
error(
relative_selector,
'invalid-css-global-block-combinator',
relative_selector.combinator.name
);
e.invalid_css_global_block_combinator(relative_selector, relative_selector.combinator.name);
}
const declaration = node.block.children.find((child) => child.type === 'Declaration');
if (declaration) {
error(declaration, 'invalid-css-global-block-declaration');
e.invalid_css_global_block_declaration(declaration);
}
}
@ -137,7 +132,7 @@ const validation_visitors = {
if (a !== b) {
for (let i = a; i <= b; i += 1) {
if (is_global(node.children[i])) {
error(node.children[i].selectors[0], 'invalid-css-global-placement');
e.invalid_css_global_placement(node.children[i].selectors[0]);
}
}
}
@ -152,12 +147,12 @@ const validation_visitors = {
const child = selector.args?.children[0].children[0];
// ensure `:global(element)` to be at the first position in a compound selector
if (child?.selectors[0].type === 'TypeSelector' && i !== 0) {
error(selector, 'invalid-css-global-selector-list');
e.invalid_css_global_selector_list(selector);
}
// ensure `:global(.class)` is not followed by a type selector, eg: `:global(.class)element`
if (relative_selector.selectors[i + 1]?.type === 'TypeSelector') {
error(relative_selector.selectors[i + 1], 'invalid-css-type-selector-placement');
e.invalid_css_type_selector_placement(relative_selector.selectors[i + 1]);
}
// ensure `:global(...)`contains a single selector
@ -167,7 +162,7 @@ const validation_visitors = {
selector.args.children.length > 1 &&
(node.children.length > 1 || relative_selector.selectors.length > 1)
) {
error(selector, 'invalid-css-global-selector');
e.invalid_css_global_selector(selector);
}
}
}
@ -176,7 +171,7 @@ const validation_visitors = {
NestingSelector(node, context) {
const rule = /** @type {import('#compiler').Css.Rule} */ (context.state.rule);
if (!rule.metadata.parent_rule) {
error(node, 'invalid-nesting-selector');
e.invalid_nesting_selector(node);
}
}
};

@ -1,6 +1,6 @@
import is_reference from 'is-reference';
import { walk } from 'zimmerframe';
import { error } from '../../errors.js';
import * as e from '../../errors.js';
import {
extract_identifiers,
extract_all_identifiers_from_expression,
@ -229,7 +229,7 @@ export function analyze_module(ast, options) {
for (const [name, references] of scope.references) {
if (name[0] !== '$' || ReservedKeywords.includes(name)) continue;
if (name === '$' || name[1] === '$') {
error(references[0].node, 'illegal-global', name);
e.illegal_global(references[0].node, name);
}
}
@ -281,7 +281,7 @@ export function analyze_component(root, source, options) {
for (const [name, references] of module.scope.references) {
if (name[0] !== '$' || ReservedKeywords.includes(name)) continue;
if (name === '$' || name[1] === '$') {
error(references[0].node, 'illegal-global', name);
e.illegal_global(references[0].node, name);
}
const store_name = name.slice(1);
@ -321,12 +321,12 @@ export function analyze_component(root, source, options) {
}
if (is_nested_store_subscription_node) {
error(is_nested_store_subscription_node, 'illegal-store-subscription');
e.illegal_store_subscription(is_nested_store_subscription_node);
}
if (options.runes !== false) {
if (declaration === null && /[a-z]/.test(store_name[0])) {
error(references[0].node, 'illegal-global', name);
e.illegal_global(references[0].node, name);
} else if (declaration !== null && Runes.includes(/** @type {any} */ (name))) {
for (const { node, path } of references) {
if (path.at(-1)?.type === 'CallExpression') {
@ -345,7 +345,7 @@ export function analyze_component(root, source, options) {
// const state = $state(0) is valid
get_rune(/** @type {import('estree').Node} */ (path.at(-1)), module.scope) === null
) {
error(node, 'illegal-subscription');
e.illegal_subscription(node);
}
}
}
@ -413,12 +413,12 @@ export function analyze_component(root, source, options) {
if (analysis.runes) {
const props_refs = module.scope.references.get('$$props');
if (props_refs) {
error(props_refs[0].node, 'invalid-legacy-props');
e.invalid_legacy_props(props_refs[0].node);
}
const rest_props_refs = module.scope.references.get('$$restProps');
if (rest_props_refs) {
error(rest_props_refs[0].node, 'invalid-legacy-rest-props');
e.invalid_legacy_rest_props(rest_props_refs[0].node);
}
for (const { ast, scope, scopes } of [module, instance, template]) {
@ -451,7 +451,7 @@ export function analyze_component(root, source, options) {
({ alias, name }) => (binding.prop_alias ?? binding.node.name) === (alias ?? name)
)
) {
error(binding.node, 'conflicting-property-name');
e.conflicting_property_name(binding.node);
}
}
}
@ -504,7 +504,7 @@ export function analyze_component(root, source, options) {
}
if (analysis.uses_render_tags && (analysis.uses_slots || analysis.slot_names.size > 0)) {
error(analysis.slot_names.values().next().value, 'conflicting-slot-usage');
e.conflicting_slot_usage(analysis.slot_names.values().next().value);
}
// warn on any nonstate declarations that are a) reassigned and b) referenced in the template
@ -1425,7 +1425,7 @@ function order_reactive_statements(unsorted_reactive_declarations) {
const cycle = check_graph_for_cycles(edges);
if (cycle?.length) {
const declaration = /** @type {Tuple[]} */ (lookup.get(cycle[0]))[0];
error(declaration[0], 'cyclical-reactive-declaration', cycle);
e.cyclical_reactive_declaration(declaration[0], cycle.join(' → '));
}
// We use a map and take advantage of the fact that the spec says insertion order is preserved when iterating

@ -3,7 +3,7 @@ import {
interactive_elements,
is_tag_valid_with_parent
} from '../../../constants.js';
import { error } from '../../errors.js';
import * as e from '../../errors.js';
import {
extract_identifiers,
get_parent,
@ -44,14 +44,14 @@ function validate_component(node, context) {
attribute.type !== 'OnDirective' &&
attribute.type !== 'BindDirective'
) {
error(attribute, 'invalid-component-directive');
e.invalid_component_directive(attribute);
}
if (
attribute.type === 'OnDirective' &&
(attribute.modifiers.length > 1 || attribute.modifiers.some((m) => m !== 'once'))
) {
error(attribute, 'invalid-event-modifier');
e.invalid_component_event_modifier(attribute);
}
if (attribute.type === 'Attribute') {
@ -62,7 +62,7 @@ function validate_component(node, context) {
while (--i > 0) {
const char = context.state.analysis.source[i];
if (char === '(') break; // parenthesized sequence expressions are ok
if (char === '{') error(expression, 'invalid-sequence-expression');
if (char === '{') e.invalid_sequence_expression(expression);
}
}
}
@ -93,8 +93,12 @@ const react_attributes = new Map([
*/
function validate_element(node, context) {
let has_animate_directive = false;
let has_in_transition = false;
let has_out_transition = false;
/** @type {import('#compiler').TransitionDirective | null} */
let in_transition = null;
/** @type {import('#compiler').TransitionDirective | null} */
let out_transition = null;
for (const attribute of node.attributes) {
if (attribute.type === 'Attribute') {
@ -107,18 +111,18 @@ function validate_element(node, context) {
while (--i > 0) {
const char = context.state.analysis.source[i];
if (char === '(') break; // parenthesized sequence expressions are ok
if (char === '{') error(expression, 'invalid-sequence-expression');
if (char === '{') e.invalid_sequence_expression(expression);
}
}
}
if (regex_illegal_attribute_character.test(attribute.name)) {
error(attribute, 'invalid-attribute-name', attribute.name);
e.invalid_attribute_name(attribute, attribute.name);
}
if (attribute.name.startsWith('on') && attribute.name.length > 2) {
if (!is_expression) {
error(attribute, 'invalid-event-attribute-value');
e.invalid_event_attribute_value(attribute);
}
const value = attribute.value[0].expression;
@ -162,9 +166,9 @@ function validate_element(node, context) {
} else if (attribute.type === 'AnimateDirective') {
const parent = context.path.at(-2);
if (parent?.type !== 'EachBlock') {
error(attribute, 'invalid-animation', 'no-each');
e.animation_invalid_placement(attribute);
} else if (!parent.key) {
error(attribute, 'invalid-animation', 'each-key');
e.animation_missing_key(attribute);
} else if (
parent.body.nodes.filter(
(n) =>
@ -173,34 +177,39 @@ function validate_element(node, context) {
(n.type !== 'Text' || n.data.trim() !== '')
).length > 1
) {
error(attribute, 'invalid-animation', 'child');
e.animation_invalid_placement(attribute);
}
if (has_animate_directive) {
error(attribute, 'duplicate-animation');
e.animation_duplicate(attribute);
} else {
has_animate_directive = true;
}
} else if (attribute.type === 'TransitionDirective') {
if ((attribute.outro && has_out_transition) || (attribute.intro && has_in_transition)) {
/** @param {boolean} _in @param {boolean} _out */
const type = (_in, _out) => (_in && _out ? 'transition' : _in ? 'in' : 'out');
error(
attribute,
'duplicate-transition',
type(has_in_transition, has_out_transition),
type(attribute.intro, attribute.outro)
);
const existing = /** @type {import('#compiler').TransitionDirective | null} */ (
(attribute.intro && in_transition) || (attribute.outro && out_transition)
);
if (existing) {
const a = existing.intro ? (existing.outro ? 'transition' : 'in') : 'out';
const b = attribute.intro ? (attribute.outro ? 'transition' : 'in') : 'out';
if (a === b) {
e.transition_duplicate(attribute, a);
} else {
e.transition_conflict(attribute, a, b);
}
}
has_in_transition = has_in_transition || attribute.intro;
has_out_transition = has_out_transition || attribute.outro;
if (attribute.intro) in_transition = attribute;
if (attribute.outro) out_transition = attribute;
} else if (attribute.type === 'OnDirective') {
let has_passive_modifier = false;
let conflicting_passive_modifier = '';
for (const modifier of attribute.modifiers) {
if (!EventModifiers.includes(modifier)) {
error(attribute, 'invalid-event-modifier', EventModifiers);
const list = `${EventModifiers.slice(0, -1).join(', ')} or ${EventModifiers.at(-1)}`;
e.invalid_event_modifier(attribute, list);
}
if (modifier === 'passive') {
has_passive_modifier = true;
@ -208,12 +217,7 @@ function validate_element(node, context) {
conflicting_passive_modifier = modifier;
}
if (has_passive_modifier && conflicting_passive_modifier) {
error(
attribute,
'invalid-event-modifier-combination',
'passive',
conflicting_passive_modifier
);
e.invalid_event_modifier_combination(attribute, 'passive', conflicting_passive_modifier);
}
}
}
@ -259,7 +263,7 @@ function validate_slot_attribute(context, attribute) {
if (owner) {
if (!is_text_attribute(attribute)) {
error(attribute, 'invalid-slot-attribute');
e.invalid_slot_attribute(attribute);
}
if (
@ -268,13 +272,13 @@ function validate_slot_attribute(context, attribute) {
owner.type === 'SvelteSelf'
) {
if (owner !== context.path.at(-2)) {
error(attribute, 'invalid-slot-placement');
e.invalid_slot_placement(attribute);
}
const name = attribute.value[0].data;
if (context.state.component_slots.has(name)) {
error(attribute, 'duplicate-slot-name', name, owner.name);
e.duplicate_slot_name(attribute, name, owner.name);
}
context.state.component_slots.add(name);
@ -291,12 +295,12 @@ function validate_slot_attribute(context, attribute) {
}
}
error(node, 'invalid-default-slot-content');
e.invalid_default_slot_content(node);
}
}
}
} else {
error(attribute, 'invalid-slot-placement');
e.invalid_slot_placement(attribute);
}
}
@ -327,7 +331,7 @@ const validation = {
const left = object(assignee);
if (left === null) {
error(node, 'invalid-binding-expression');
e.invalid_binding_expression(node);
}
const binding = context.state.scope.get(left.name);
@ -347,25 +351,25 @@ const validation = {
binding.kind !== 'store_sub' &&
!binding.mutated)
) {
error(node.expression, 'invalid-binding-value');
e.invalid_binding_value(node.expression);
}
if (binding.kind === 'derived') {
error(node.expression, 'invalid-derived-binding');
e.invalid_binding(node.expression, 'derived state');
}
if (context.state.analysis.runes && binding.kind === 'each') {
error(node, 'invalid-each-assignment');
e.invalid_each_assignment(node);
}
if (binding.kind === 'snippet') {
error(node, 'invalid-snippet-assignment');
e.invalid_snippet_assignment(node);
}
}
if (node.name === 'group') {
if (!binding) {
error(node, 'INTERNAL', 'Cannot find declaration for bind:group');
throw new Error('Cannot find declaration for bind:group');
}
}
@ -389,21 +393,14 @@ const validation = {
parent?.type === 'SvelteBody'
) {
if (context.state.options.namespace === 'foreign' && node.name !== 'this') {
error(
node,
'invalid-binding',
node.name,
undefined,
'. Foreign elements only support bind:this'
);
e.bind_invalid_detailed(node, node.name, 'Foreign elements only support `bind:this`');
}
if (node.name in binding_properties) {
const property = binding_properties[node.name];
if (property.valid_elements && !property.valid_elements.includes(parent.name)) {
error(
e.bind_invalid_target(
node,
'invalid-binding',
node.name,
property.valid_elements.map((valid_element) => `<${valid_element}>`).join(', ')
);
@ -415,17 +412,17 @@ const validation = {
);
if (type && !is_text_attribute(type)) {
if (node.name !== 'value' || type.value === true) {
error(type, 'invalid-type-attribute');
e.invalid_type_attribute(type);
}
return; // bind:value can handle dynamic `type` attributes
}
if (node.name === 'checked' && type?.value[0].data !== 'checkbox') {
error(node, 'invalid-binding', node.name, '<input type="checkbox">');
e.bind_invalid_target(node, node.name, '<input type="checkbox">');
}
if (node.name === 'files' && type?.value[0].data !== 'file') {
error(node, 'invalid-binding', node.name, '<input type="file">');
e.bind_invalid_target(node, node.name, '<input type="file">');
}
}
@ -438,14 +435,13 @@ const validation = {
a.value !== true
);
if (multiple) {
error(multiple, 'invalid-multiple-attribute');
e.invalid_multiple_attribute(multiple);
}
}
if (node.name === 'offsetWidth' && SVGElements.includes(parent.name)) {
error(
e.bind_invalid_target(
node,
'invalid-binding',
node.name,
`non-<svg> elements. Use 'clientWidth' for <svg> instead`
);
@ -456,9 +452,9 @@ const validation = {
parent.attributes.find((a) => a.type === 'Attribute' && a.name === 'contenteditable')
);
if (!contenteditable) {
error(node, 'missing-contenteditable-attribute');
e.missing_contenteditable_attribute(node);
} else if (!is_text_attribute(contenteditable) && contenteditable.value !== true) {
error(contenteditable, 'dynamic-contenteditable-attribute');
e.dynamic_contenteditable_attribute(contenteditable);
}
}
} else {
@ -466,15 +462,15 @@ const validation = {
if (match) {
const property = binding_properties[match];
if (!property.valid_elements || property.valid_elements.includes(parent.name)) {
error(node, 'invalid-binding', node.name, undefined, ` (did you mean '${match}'?)`);
e.bind_invalid_detailed(node, node.name, `Did you mean '${match}'?`);
}
}
error(node, 'invalid-binding', node.name);
e.bind_invalid(node, node.name);
}
}
},
ExportDefaultDeclaration(node) {
error(node, 'default-export');
e.default_export(node);
},
ConstTag(node, context) {
const parent = context.path.at(-1);
@ -491,7 +487,7 @@ const validation = {
((grand_parent?.type !== 'RegularElement' && grand_parent?.type !== 'SvelteElement') ||
!grand_parent.attributes.some((a) => a.type === 'Attribute' && a.name === 'slot')))
) {
error(node, 'invalid-const-placement');
e.invalid_const_placement(node);
}
},
ImportDeclaration(node, context) {
@ -502,7 +498,7 @@ const validation = {
specifier.imported.name === 'beforeUpdate' ||
specifier.imported.name === 'afterUpdate'
) {
error(specifier, 'invalid-runes-mode-import', specifier.imported.name);
e.invalid_runes_mode_import(specifier, specifier.imported.name);
}
}
}
@ -520,14 +516,14 @@ const validation = {
parent.type !== 'SvelteSelf' &&
parent.type !== 'SvelteFragment')
) {
error(node, 'invalid-let-directive-placement');
e.invalid_let_directive_placement(node);
}
},
RegularElement(node, context) {
if (node.name === 'textarea' && node.fragment.nodes.length > 0) {
for (const attribute of node.attributes) {
if (attribute.type === 'Attribute' && attribute.name === 'value') {
error(node, 'invalid-textarea-content');
e.invalid_textarea_content(node);
}
}
}
@ -551,7 +547,7 @@ const validation = {
if (context.state.parent_element) {
if (!is_tag_valid_with_parent(node.name, context.state.parent_element)) {
error(node, 'invalid-node-placement', `<${node.name}>`, context.state.parent_element);
e.invalid_node_placement(node, `<${node.name}>`, context.state.parent_element);
}
}
@ -563,7 +559,7 @@ const validation = {
parent.name === node.name &&
interactive_elements.has(parent.name)
) {
error(node, 'invalid-node-placement', `<${node.name}>`, parent.name);
e.invalid_node_placement(node, `<${node.name}>`, parent.name);
}
}
}
@ -572,7 +568,7 @@ const validation = {
const path = context.path;
for (let parent of path) {
if (parent.type === 'RegularElement' && parent.name === 'p') {
error(node, 'invalid-node-placement', `<${node.name}>`, parent.name);
e.invalid_node_placement(node, `<${node.name}>`, parent.name);
}
}
}
@ -603,7 +599,7 @@ const validation = {
const raw_args = unwrap_optional(node.expression).arguments;
for (const arg of raw_args) {
if (arg.type === 'SpreadElement') {
error(arg, 'invalid-render-spread-argument');
e.invalid_render_spread_argument(arg);
}
}
@ -613,7 +609,7 @@ const validation = {
callee.property.type === 'Identifier' &&
['bind', 'apply', 'call'].includes(callee.property.name)
) {
error(node, 'invalid-render-call');
e.invalid_render_call(node);
}
const is_inside_textarea = context.path.find((n) => {
@ -625,9 +621,8 @@ const validation = {
);
});
if (is_inside_textarea) {
error(
e.invalid_tag_placement(
node,
'invalid-tag-placement',
'inside <textarea> or <svelte:element this="textarea">',
'render'
);
@ -667,19 +662,19 @@ const validation = {
(node) => node.type !== 'SnippetBlock' && (node.type !== 'Text' || node.data.trim())
)
) {
error(node, 'conflicting-children-snippet');
e.conflicting_children_snippet(node);
}
}
},
StyleDirective(node) {
if (node.modifiers.length > 1 || (node.modifiers.length && node.modifiers[0] !== 'important')) {
error(node, 'invalid-style-directive-modifier');
e.invalid_style_directive_modifier(node);
}
},
SvelteHead(node) {
const attribute = node.attributes[0];
if (attribute) {
error(attribute, 'illegal-svelte-head-attribute');
e.illegal_svelte_head_attribute(attribute);
}
},
SvelteElement(node, context) {
@ -692,7 +687,7 @@ const validation = {
SvelteFragment(node, context) {
const parent = context.path.at(-2);
if (parent?.type !== 'Component' && parent?.type !== 'SvelteComponent') {
error(node, 'invalid-svelte-fragment-placement');
e.invalid_svelte_fragment_placement(node);
}
for (const attribute of node.attributes) {
@ -701,7 +696,7 @@ const validation = {
validate_slot_attribute(context, attribute);
}
} else if (attribute.type !== 'LetDirective') {
error(attribute, 'invalid-svelte-fragment-attribute');
e.invalid_svelte_fragment_attribute(attribute);
}
}
},
@ -710,15 +705,15 @@ const validation = {
if (attribute.type === 'Attribute') {
if (attribute.name === 'name') {
if (!is_text_attribute(attribute)) {
error(attribute, 'invalid-slot-name', false);
e.invalid_slot_name(attribute);
}
const slot_name = attribute.value[0].data;
if (slot_name === 'default') {
error(attribute, 'invalid-slot-name', true);
e.invalid_slot_name_default(attribute);
}
}
} else if (attribute.type !== 'SpreadAttribute' && attribute.type !== 'LetDirective') {
error(attribute, 'invalid-slot-element-attribute');
e.invalid_slot_element_attribute(attribute);
}
}
},
@ -729,19 +724,19 @@ const validation = {
if (!node.parent) return;
if (context.state.parent_element && regex_not_whitespace.test(node.data)) {
if (!is_tag_valid_with_parent('#text', context.state.parent_element)) {
error(node, 'invalid-node-placement', 'Text node', context.state.parent_element);
e.invalid_node_placement(node, 'Text node', context.state.parent_element);
}
}
},
TitleElement(node) {
const attribute = node.attributes[0];
if (attribute) {
error(attribute, 'illegal-title-attribute');
e.illegal_title_attribute(attribute);
}
const child = node.fragment.nodes.find((n) => n.type !== 'Text' && n.type !== 'ExpressionTag');
if (child) {
error(child, 'invalid-title-content');
e.invalid_title_content(child);
}
},
UpdateExpression(node, context) {
@ -751,7 +746,7 @@ const validation = {
if (!node.parent) return;
if (context.state.parent_element) {
if (!is_tag_valid_with_parent('#text', context.state.parent_element)) {
error(node, 'invalid-node-placement', '{expression}', context.state.parent_element);
e.invalid_node_placement(node, '{expression}', context.state.parent_element);
}
}
}
@ -772,7 +767,7 @@ export const validation_legacy = merge(validation, a11y_validators, {
}
if (state.scope.get(callee.name)?.kind !== 'store_sub') {
error(node.init, 'invalid-rune-usage', callee.name);
e.invalid_rune_usage(node.init, callee.name);
}
},
AssignmentExpression(node, { state, path }) {
@ -805,11 +800,11 @@ function validate_export(node, scope, name) {
if (!binding) return;
if (binding.kind === 'derived') {
error(node, 'invalid-derived-export');
e.invalid_derived_export(node);
}
if ((binding.kind === 'state' || binding.kind === 'frozen_state') && binding.reassigned) {
error(node, 'invalid-state-export');
e.invalid_state_export(node);
}
}
@ -827,7 +822,7 @@ function validate_call_expression(node, scope, path) {
if (rune === '$props') {
if (parent.type === 'VariableDeclarator') return;
error(node, 'invalid-props-location');
e.invalid_props_location(node);
}
if (rune === '$bindable') {
@ -840,7 +835,7 @@ function validate_call_expression(node, scope, path) {
return;
}
}
error(node, 'invalid-bindable-location');
e.invalid_bindable_location(node);
}
if (
@ -851,46 +846,46 @@ function validate_call_expression(node, scope, path) {
) {
if (parent.type === 'VariableDeclarator') return;
if (parent.type === 'PropertyDefinition' && !parent.static && !parent.computed) return;
error(node, 'invalid-state-location', rune);
e.invalid_state_location(node, rune);
}
if (rune === '$effect' || rune === '$effect.pre') {
if (parent.type !== 'ExpressionStatement') {
error(node, 'invalid-effect-location');
e.invalid_effect_location(node);
}
if (node.arguments.length !== 1) {
error(node, 'invalid-rune-args-length', rune, [1]);
e.invalid_rune_args_length(node, rune, 'exactly one argument');
}
}
if (rune === '$effect.active') {
if (node.arguments.length !== 0) {
error(node, 'invalid-rune-args-length', rune, [0]);
e.invalid_rune_args(node, rune);
}
}
if (rune === '$effect.root') {
if (node.arguments.length !== 1) {
error(node, 'invalid-rune-args-length', rune, [1]);
e.invalid_rune_args_length(node, rune, 'exactly one argument');
}
}
if (rune === '$inspect') {
if (node.arguments.length < 1) {
error(node, 'invalid-rune-args-length', rune, [1, 'more']);
e.invalid_rune_args_length(node, rune, 'one or more arguments');
}
}
if (rune === '$inspect().with') {
if (node.arguments.length !== 1) {
error(node, 'invalid-rune-args-length', rune, [1]);
e.invalid_rune_args_length(node, rune, 'exactly one argument');
}
}
if (rune === '$state.snapshot') {
if (node.arguments.length !== 1) {
error(node, 'invalid-rune-args-length', rune, [1]);
e.invalid_rune_args_length(node, rune, 'exactly one argument');
}
}
}
@ -906,7 +901,7 @@ function ensure_no_module_import_conflict(node, state) {
state.scope === state.analysis.instance.scope &&
state.analysis.module.scope.get(id.name)?.declaration_kind === 'import'
) {
error(node.id, 'illegal-variable-declaration');
e.illegal_variable_declaration(node.id);
}
}
}
@ -932,7 +927,7 @@ export const validation_runes_js = {
},
CallExpression(node, { state, path }) {
if (get_rune(node, state.scope) === '$host') {
error(node, 'invalid-host-location');
e.invalid_host_location(node);
}
validate_call_expression(node, state.scope, path);
},
@ -945,13 +940,13 @@ export const validation_runes_js = {
const args = /** @type {import('estree').CallExpression} */ (init).arguments;
if ((rune === '$derived' || rune === '$derived.by') && args.length !== 1) {
error(node, 'invalid-rune-args-length', rune, [1]);
e.invalid_rune_args_length(node, rune, 'exactly one argument');
} else if (rune === '$state' && args.length > 1) {
error(node, 'invalid-rune-args-length', rune, [0, 1]);
e.invalid_rune_args_length(node, rune, 'zero or one arguments');
} else if (rune === '$props') {
error(node, 'invalid-props-location');
e.invalid_props_location(node);
} else if (rune === '$bindable') {
error(node, 'invalid-bindable-location');
e.invalid_bindable_location(node);
}
},
AssignmentExpression(node, { state }) {
@ -1021,16 +1016,24 @@ function validate_no_const_assignment(node, argument, scope, is_binding) {
} else if (argument.type === 'Identifier') {
const binding = scope.get(argument.name);
if (binding?.declaration_kind === 'const' && binding.kind !== 'each') {
error(
node,
'invalid-const-assignment',
is_binding,
// This takes advantage of the fact that we don't assign initial for let directives and then/catch variables.
// If we start doing that, we need another property on the binding to differentiate, or give up on the more precise error message.
binding.kind !== 'state' &&
binding.kind !== 'frozen_state' &&
(binding.kind !== 'normal' || !binding.initial)
);
// e.invalid_const_assignment(
// node,
// is_binding,
// // This takes advantage of the fact that we don't assign initial for let directives and then/catch variables.
// // If we start doing that, we need another property on the binding to differentiate, or give up on the more precise error message.
// binding.kind !== 'state' &&
// binding.kind !== 'frozen_state' &&
// (binding.kind !== 'normal' || !binding.initial)
// );
// TODO have a more specific error message for assignments to things like `{:then foo}`
const thing = 'constant';
if (is_binding) {
e.invalid_binding(node, thing);
} else {
e.invalid_assignment(node, thing);
}
}
}
}
@ -1048,16 +1051,16 @@ function validate_assignment(node, argument, state) {
if (state.analysis.runes) {
if (binding?.kind === 'derived') {
error(node, 'invalid-derived-assignment');
e.invalid_assignment(node, 'derived state');
}
if (binding?.kind === 'each') {
error(node, 'invalid-each-assignment');
e.invalid_each_assignment(node);
}
}
if (binding?.kind === 'snippet') {
error(node, 'invalid-snippet-assignment');
e.invalid_snippet_assignment(node);
}
}
@ -1073,7 +1076,7 @@ function validate_assignment(node, argument, state) {
if (object.type === 'ThisExpression' && property?.type === 'PrivateIdentifier') {
if (state.private_derived_state.includes(property.name)) {
error(node, 'invalid-derived-assignment');
e.invalid_assignment(node, 'derived state');
}
}
}
@ -1081,7 +1084,7 @@ function validate_assignment(node, argument, state) {
export const validation_runes = merge(validation, a11y_validators, {
LabeledStatement(node, { path }) {
if (node.label.name !== '$' || path.at(-1)?.type !== 'Program') return;
error(node, 'invalid-legacy-reactive-statement');
e.invalid_legacy_reactive_statement(node);
},
ExportNamedDeclaration(node, { state, next }) {
if (state.ast_type === 'module') {
@ -1099,7 +1102,7 @@ export const validation_runes = merge(validation, a11y_validators, {
if (node.declaration?.type !== 'VariableDeclaration') return;
if (node.declaration.kind !== 'let') return;
if (state.analysis.instance.scope !== state.scope) return;
error(node, 'invalid-legacy-export');
e.invalid_legacy_export(node);
}
},
ExportSpecifier(node, { state }) {
@ -1110,12 +1113,12 @@ export const validation_runes = merge(validation, a11y_validators, {
CallExpression(node, { state, path }) {
const rune = get_rune(node, state.scope);
if (rune === '$bindable' && node.arguments.length > 1) {
error(node, 'invalid-rune-args-length', '$bindable', [0, 1]);
e.invalid_rune_args_length(node, '$bindable', 'zero or one arguments');
} else if (rune === '$host') {
if (node.arguments.length > 0) {
error(node, 'invalid-rune-args-length', '$host', [0]);
e.invalid_rune_args(node, '$host');
} else if (state.ast_type === 'module' || !state.analysis.custom_element) {
error(node, 'invalid-host-location');
e.invalid_host_location(node);
}
}
@ -1127,7 +1130,7 @@ export const validation_runes = merge(validation, a11y_validators, {
context.type === 'Identifier' &&
(context.name === '$state' || context.name === '$derived')
) {
error(node, 'invalid-state-location', context.name);
e.invalid_state_location(node, context.name);
}
next({ ...state });
},
@ -1148,39 +1151,39 @@ export const validation_runes = merge(validation, a11y_validators, {
// TODO some of this is duplicated with above, seems off
if ((rune === '$derived' || rune === '$derived.by') && args.length !== 1) {
error(node, 'invalid-rune-args-length', rune, [1]);
e.invalid_rune_args_length(node, rune, 'exactly one argument');
} else if (rune === '$state' && args.length > 1) {
error(node, 'invalid-rune-args-length', rune, [0, 1]);
e.invalid_rune_args_length(node, rune, 'zero or one arguments');
} else if (rune === '$props') {
if (state.has_props_rune) {
error(node, 'duplicate-props-rune');
e.duplicate_props_rune(node);
}
state.has_props_rune = true;
if (args.length > 0) {
error(node, 'invalid-rune-args-length', rune, [0]);
e.invalid_rune_args(node, rune);
}
if (node.id.type !== 'ObjectPattern') {
error(node, 'invalid-props-id');
e.invalid_props_id(node);
}
if (state.scope !== state.analysis.instance.scope) {
error(node, 'invalid-props-location');
e.invalid_props_location(node);
}
for (const property of node.id.properties) {
if (property.type === 'Property') {
if (property.computed) {
error(property, 'invalid-props-pattern');
e.invalid_props_pattern(property);
}
const value =
property.value.type === 'AssignmentPattern' ? property.value.left : property.value;
if (value.type !== 'Identifier') {
error(property, 'invalid-props-pattern');
e.invalid_props_pattern(property);
}
}
}

@ -1,5 +1,4 @@
import { walk } from 'zimmerframe';
import { error } from '../../../errors.js';
import * as b from '../../../utils/builders.js';
import { set_scope } from '../../scope.js';
import { template_visitors } from './visitors/template.js';
@ -52,35 +51,41 @@ export function client_component(source, analysis, options) {
get before_init() {
/** @type {any[]} */
const a = [];
a.push = () =>
error(null, 'INTERNAL', 'before_init.push should not be called outside create_block');
a.push = () => {
throw new Error('before_init.push should not be called outside create_block');
};
return a;
},
get init() {
/** @type {any[]} */
const a = [];
a.push = () => error(null, 'INTERNAL', 'init.push should not be called outside create_block');
a.push = () => {
throw new Error('init.push should not be called outside create_block');
};
return a;
},
get update() {
/** @type {any[]} */
const a = [];
a.push = () =>
error(null, 'INTERNAL', 'update.push should not be called outside create_block');
a.push = () => {
throw new Error('update.push should not be called outside create_block');
};
return a;
},
get after_update() {
/** @type {any[]} */
const a = [];
a.push = () =>
error(null, 'INTERNAL', 'after_update.push should not be called outside create_block');
a.push = () => {
throw new Error('after_update.push should not be called outside create_block');
};
return a;
},
get template() {
/** @type {any[]} */
const a = [];
a.push = () =>
error(null, 'INTERNAL', 'template.push should not be called outside create_block');
a.push = () => {
throw new Error('template.push should not be called outside create_block');
};
return a;
},
legacy_reactive_statements: new Map(),
@ -209,7 +214,7 @@ export function client_component(source, analysis, options) {
for (const [node] of analysis.reactive_statements) {
const statement = [...state.legacy_reactive_statements].find(([n]) => n === node);
if (statement === undefined) {
error(node, 'INTERNAL', 'Could not find reactive statement');
throw new Error('Could not find reactive statement');
}
instance.body.push(statement[1]);
}

@ -5,7 +5,6 @@ import {
is_simple_expression,
object
} from '../../../utils/ast.js';
import { error } from '../../../errors.js';
import {
PROPS_IS_LAZY_INITIAL,
PROPS_IS_IMMUTABLE,
@ -185,7 +184,7 @@ export function serialize_set_binding(node, context, fallback, options) {
}
if (assignee.type !== 'Identifier' && assignee.type !== 'MemberExpression') {
error(node, 'INTERNAL', `Unexpected assignment type ${assignee.type}`);
throw new Error(`Unexpected assignment type ${assignee.type}`);
}
// Handle class private/public state assignment cases

@ -16,7 +16,6 @@ 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 {
with_loc,
function_visitor,
@ -1776,10 +1775,10 @@ export const template_visitors = {
);
},
ClassDirective(node, { state, next }) {
error(node, 'INTERNAL', 'Node should have been handled elsewhere');
throw new Error('Node should have been handled elsewhere');
},
StyleDirective(node, { state, next }) {
error(node, 'INTERNAL', 'Node should have been handled elsewhere');
throw new Error('Node should have been handled elsewhere');
},
TransitionDirective(node, { state, visit }) {
let flags = node.modifiers.includes('global') ? TRANSITION_GLOBAL : 0;
@ -2775,7 +2774,7 @@ export const template_visitors = {
}
default:
error(node, 'INTERNAL', 'unknown binding ' + node.name);
throw new Error('unknown binding ' + node.name);
}
}

@ -22,7 +22,6 @@ 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 { binding_properties } from '../../bindings.js';
import { regex_starts_with_newline, regex_whitespaces_strict } from '../../patterns.js';
import {
@ -419,7 +418,7 @@ function serialize_set_binding(node, context, fallback) {
}
if (node.left.type !== 'Identifier' && node.left.type !== 'MemberExpression') {
error(node, 'INTERNAL', `Unexpected assignment type ${node.left.type}`);
throw new Error(`Unexpected assignment type ${node.left.type}`);
}
let left = node.left;
@ -1311,10 +1310,10 @@ const template_visitors = {
state.template.push(block_close);
},
ClassDirective(node) {
error(node, 'INTERNAL', 'Node should have been handled elsewhere');
throw new Error('Node should have been handled elsewhere');
},
StyleDirective(node) {
error(node, 'INTERNAL', 'Node should have been handled elsewhere');
throw new Error('Node should have been handled elsewhere');
},
RegularElement(node, context) {
const metadata = {
@ -2106,14 +2105,17 @@ export function server_component(analysis, options) {
get init() {
/** @type {any[]} */
const a = [];
a.push = () => error(null, 'INTERNAL', 'init.push should not be called outside create_block');
a.push = () => {
throw new Error('init.push should not be called outside create_block');
};
return a;
},
get template() {
/** @type {any[]} */
const a = [];
a.push = () =>
error(null, 'INTERNAL', 'template.push should not be called outside create_block');
a.push = () => {
throw new Error('template.push should not be called outside create_block');
};
return a;
},
metadata: {
@ -2181,7 +2183,7 @@ export function server_component(analysis, options) {
for (const [node] of analysis.reactive_statements) {
const statement = [...state.legacy_reactive_statements].find(([n]) => n === node);
if (statement === undefined) {
error(node, 'INTERNAL', 'Could not find reactive statement');
throw new Error('Could not find reactive statement');
}
if (

@ -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 * as e from '../errors.js';
import { extract_identifiers, extract_identifiers_from_destructuring } from '../utils/ast.js';
import { JsKeywords, Runes } from './constants.js';
@ -70,7 +70,7 @@ export class Scope {
*/
declare(node, kind, declaration_kind, initial = null) {
if (node.name === '$') {
error(node, 'invalid-dollar-binding');
e.invalid_dollar_binding(node);
}
if (
@ -80,7 +80,7 @@ export class Scope {
declaration_kind !== 'rest_param' &&
this.function_depth <= 1
) {
error(node, 'invalid-dollar-prefix');
e.invalid_dollar_prefix(node);
}
if (this.parent) {
@ -95,7 +95,7 @@ export class Scope {
if (this.declarations.has(node.name)) {
// This also errors on var/function types, but that's arguably a good thing
error(node, 'duplicate-declaration', node.name);
e.duplicate_declaration(node, node.name);
}
/** @type {import('#compiler').Binding} */
@ -166,7 +166,7 @@ export class Scope {
get_bindings(node) {
const bindings = this.declarators.get(node);
if (!bindings) {
error(node, 'INTERNAL', 'No binding found for declarator');
throw new Error('No binding found for declarator');
}
return bindings;
}
@ -767,7 +767,7 @@ export function get_rune(node, scope) {
joined = n.name + joined;
if (joined === '$derived.call') error(node, 'invalid-derived-call');
if (joined === '$derived.call') e.invalid_derived_call(node);
if (!Runes.includes(/** @type {any} */ (joined))) return null;
const binding = scope.get(n.name);

@ -1,12 +1,10 @@
import { error } from '../errors.js';
/**
* @template T
* @param {T} value
* @returns {asserts value is NonNullable<T>}
*/
export function ok(value) {
if (!value) error(null, 'INTERNAL', 'Assertion failed');
if (!value) throw new Error('Assertion failed');
}
/**
@ -16,5 +14,5 @@ export function ok(value) {
* @returns {asserts actual is T}
*/
export function equal(actual, expected) {
if (actual !== expected) error(null, 'INTERNAL', 'Assertion failed');
if (actual !== expected) throw new Error('Assertion failed');
}

@ -1,4 +1,4 @@
import { error } from './errors.js';
import * as e from './errors.js';
/**
* @template [Input=any]
@ -141,7 +141,7 @@ export const validate_component_options =
function removed(msg) {
return (input) => {
if (input !== undefined) {
error(null, 'removed-compiler-option', msg);
e.removed_compiler_option(null, msg);
}
return /** @type {any} */ (undefined);
};
@ -200,9 +200,8 @@ function object(children, allow_unknown = false) {
if (allow_unknown) {
output[key] = input[key];
} else {
error(
e.invalid_compiler_option(
null,
'invalid-compiler-option',
`Unexpected option ${keypath ? `${keypath}.${key}` : key}`
);
}
@ -307,5 +306,5 @@ function fun(fallback) {
/** @param {string} msg */
function throw_error(msg) {
error(null, 'invalid-compiler-option', msg);
e.invalid_compiler_option(null, msg);
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'missing-attribute-value',
code: 'missing_attribute_value',
message: 'Expected attribute value',
position: [12, 12]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-sequence-expression',
code: 'invalid_sequence_expression',
message:
'Sequence expressions are not allowed as attribute/directive values in runes mode, unless wrapped in parentheses',
position: [124, 131]

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-sequence-expression',
code: 'invalid_sequence_expression',
message:
'Sequence expressions are not allowed as attribute/directive values in runes mode, unless wrapped in parentheses',
position: [163, 170]

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'duplicate-attribute',
code: 'duplicate_attribute',
message: 'Attributes need to be unique',
position: [17, 17]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'duplicate-attribute',
code: 'duplicate_attribute',
message: 'Attributes need to be unique',
position: [17, 17]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'duplicate-attribute',
code: 'duplicate_attribute',
message: 'Attributes need to be unique',
position: [17, 17]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'expected-token',
code: 'expected_token',
message: 'Expected token {:else}',
position: [35, 35]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-continuing-block-placement',
code: 'invalid_continuing_block_placement',
message:
'{:...} block is invalid at this position (did you forget to close the preceeding element or block?)',
position: [1, 1]

@ -2,8 +2,9 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-state-location',
message: '$state(...) can only be used as a variable declaration initializer or a class field',
code: 'invalid_state_location',
message:
'`$state(...)` can only be used as a variable declaration initializer or a class field',
position: [33, 41]
}
});

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({
error: {
code: 'unexpected-eof',
message: 'Unexpected end of input (expected -->)',
code: 'expected_token',
message: 'Expected token -->',
position: [24, 24]
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'duplicate-slot-name',
code: 'duplicate_slot_name',
message: "Duplicate slot name 'foo' in <Nested>"
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'duplicate-slot-name',
code: 'duplicate_slot_name',
message: "Duplicate slot name 'foo' in <Nested>"
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-default-slot-content',
code: 'invalid_default_slot_content',
message: 'Found default slot content alongside an explicit slot="default"'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'duplicate-slot-name',
code: 'duplicate_slot_name',
message: "Duplicate slot name 'foo' in <Nested>"
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'duplicate-slot-name',
code: 'duplicate_slot_name',
message: "Duplicate slot name 'foo' in <Nested>"
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'duplicate-slot-name',
code: 'duplicate_slot_name',
message: "Duplicate slot name 'foo' in <Nested>"
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-slot-placement',
code: 'invalid_slot_placement',
message:
"Element with a slot='...' attribute must be a child of a component or a descendant of a custom element"
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-slot-placement',
code: 'invalid_slot_placement',
message:
"Element with a slot='...' attribute must be a child of a component or a descendant of a custom element"
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-slot-placement',
code: 'invalid_slot_placement',
message:
"Element with a slot='...' attribute must be a child of a component or a descendant of a custom element"
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-css-global-block-combinator',
code: 'invalid_css_global_block_combinator',
message: 'A :global {...} block cannot follow a > combinator',
position: [12, 21]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-css-global-block-declaration',
code: 'invalid_css_global_block_declaration',
message: 'A :global {...} block can only contain rules, not declarations',
position: [24, 34]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-css-global-block-modifier',
code: 'invalid_css_global_block_modifier',
message: 'A :global {...} block cannot modify an existing selector',
position: [14, 21]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-css-global-block-list',
code: 'invalid_css_global_block_list',
message: 'A :global {...} block cannot be part of a selector list with more than one item',
position: [9, 31]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-css-identifier',
code: 'invalid_css_identifier',
message: 'Expected a valid CSS identifier',
position: [25, 25]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-dollar-binding',
code: 'invalid_dollar_binding',
message: 'The $ name is reserved, and cannot be used for variables and imports'
}
});

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({
error: {
code: 'illegal-global',
code: 'illegal_global',
message:
'$ is an illegal variable name. To reference a global variable called $, use globalThis.$'
'`$` is an illegal variable name. To reference a global variable called `$`, use `globalThis.$`'
}
});

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({
error: {
code: 'illegal-global',
code: 'illegal_global',
message:
'$ is an illegal variable name. To reference a global variable called $, use globalThis.$'
'`$` is an illegal variable name. To reference a global variable called `$`, use `globalThis.$`'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-dollar-binding',
code: 'invalid_dollar_binding',
message: 'The $ name is reserved, and cannot be used for variables and imports'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-binding',
message: "'value' binding can only be used with <input>, <textarea>, <select>"
code: 'bind_invalid_target',
message: '`bind:value` can only be used with <input>, <textarea>, <select>'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'expected-token',
code: 'expected_token',
message: 'Expected token {:then ...} or {:catch ...}',
position: [24, 24]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-continuing-block-placement',
code: 'invalid_continuing_block_placement',
message:
'{:...} block is invalid at this position (did you forget to close the preceeding element or block?)',
position: [6, 6]

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-continuing-block-placement',
code: 'invalid_continuing_block_placement',
message:
'{:...} block is invalid at this position (did you forget to close the preceeding element or block?)',
position: [18, 18]

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-continuing-block-placement',
code: 'invalid_continuing_block_placement',
message:
'{:...} block is invalid at this position (did you forget to close the preceeding element or block?)',
position: [17, 17]

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'expected-token',
code: 'expected_token',
message: 'Expected token {:then ...} or {:catch ...}',
position: [26, 26]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'expected-token',
code: 'expected_token',
message: 'Expected token {:then ...} or {:catch ...}',
position: [27, 27]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'empty-attribute-shorthand',
code: 'empty_attribute_shorthand',
message: 'Attribute shorthand cannot be empty',
position: [6, 6]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'empty-directive-name',
code: 'empty_directive_name',
message: 'ClassDirective name cannot be empty',
position: [10, 10]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'empty-directive-name',
code: 'empty_directive_name',
message: 'UseDirective name cannot be empty',
position: [8, 8]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-derived-export',
code: 'invalid_derived_export',
message:
'Cannot export derived state from a module. To expose the current derived value, export a function returning its value',
position: [24, 66]

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-state-export',
code: 'invalid_state_export',
message:
"Cannot export state from a module if it is reassigned. Either export a function returning the state value or only mutate the state value's properties",
position: [76, 114]

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-state-export',
code: 'invalid_state_export',
message:
"Cannot export state from a module if it is reassigned. Either export a function returning the state value or only mutate the state value's properties",
position: [46, 86]

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'js-parse-error',
code: 'js_parse_error',
message: 'Assigning to rvalue',
position: [1, 1]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-snippet-assignment',
code: 'invalid_snippet_assignment',
message: 'Cannot reassign or bind to snippet parameter'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-snippet-assignment',
code: 'invalid_snippet_assignment',
message: 'Cannot reassign or bind to snippet parameter'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-const-assignment',
message: 'Invalid assignment to const variable'
code: 'invalid_assignment',
message: 'Cannot assign to constant'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-const-assignment',
message: 'Invalid assignment to const variable'
code: 'invalid_assignment',
message: 'Cannot assign to constant'
}
});

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({
error: {
code: 'duplicate-style-element',
message: 'A component can have a single top-level <style> element',
code: 'duplicate_style_element',
message: 'A component can have a single top-level `<style>` element',
position: [58, 58]
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-element-content',
code: 'invalid_element_content',
message: '<svelte:options> cannot have children',
position: [16, 16]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'missing-whitespace',
code: 'missing_whitespace',
message: 'Expected whitespace',
position: [6, 6]
}

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-render-call',
code: 'invalid_render_call',
message: 'Calling a snippet function using apply, bind or call is not allowed'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-runes-mode-import',
code: 'invalid_runes_mode_import',
message: 'beforeUpdate cannot be used in runes mode'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'duplicate-props-rune',
message: 'Cannot use $props() more than once'
code: 'duplicate_props_rune',
message: 'Cannot use `$props()` more than once'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-legacy-export',
code: 'invalid_legacy_export',
message: 'Cannot use `export let` in runes mode — use $props instead'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-state-export',
code: 'invalid_state_export',
message:
"Cannot export state from a module if it is reassigned. Either export a function returning the state value or only mutate the state value's properties",
position: [28, 53]

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-each-assignment',
code: 'invalid_each_assignment',
message:
"Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. 'array[i] = value' instead of 'entry = value')"
'Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`)'
}
});

@ -2,8 +2,8 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-each-assignment',
code: 'invalid_each_assignment',
message:
"Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. 'array[i] = value' instead of 'entry = value')"
'Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`)'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-const-assignment',
message: 'Invalid assignment to const variable'
code: 'invalid_assignment',
message: 'Cannot assign to constant'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-const-assignment',
message: 'Invalid assignment to const variable'
code: 'invalid_assignment',
message: 'Cannot assign to constant'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-derived-assignment',
message: 'Invalid assignment to derived state'
code: 'invalid_assignment',
message: 'Cannot assign to derived state'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-derived-binding',
message: 'Invalid binding to derived state'
code: 'invalid_binding',
message: 'Cannot bind to derived state'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-derived-assignment',
message: 'Invalid assignment to derived state'
code: 'invalid_assignment',
message: 'Cannot assign to derived state'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-derived-assignment',
message: 'Invalid assignment to derived state'
code: 'invalid_assignment',
message: 'Cannot assign to derived state'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-derived-assignment',
message: 'Invalid assignment to derived state'
code: 'invalid_assignment',
message: 'Cannot assign to derived state'
}
});

@ -2,7 +2,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'invalid-state-location',
message: '$state(...) can only be used as a variable declaration initializer or a class field'
code: 'invalid_state_location',
message: '`$state(...)` can only be used as a variable declaration initializer or a class field'
}
});

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save