convert runes errors

pull/11294/head
Rich Harris 2 years ago
parent 5d85ba708d
commit 741b744194

@ -6,7 +6,7 @@ Can only bind to an Identifier or MemberExpression
Can only bind to state or props
## invalid_binding
## invalid_bind_directive
TODO
@ -24,4 +24,4 @@ TODO
## dynamic_contenteditable_attribute
'contenteditable' attribute cannot be dynamic if element uses two-way binding
'contenteditable' attribute cannot be dynamic if element uses two-way binding

@ -54,27 +54,21 @@ $effect() can only be used as an expression statement
$host() can only be used inside custom element component instances
## invalid_const_assignment
## invalid_assignment
Invalid %is_binding ? 'binding' : 'assignment'% to const variable%
show_details
? ' ($derived values, let: directives, :then/:catch variables and @const declarations count as const)'
: ''
%
Invalid assignment to %thing%
## invalid_derived_assignment
## invalid_binding
Invalid assignment to derived state
Invalid assignment to %thing%
## invalid_derived_binding
## invalid_rune_args
Invalid binding to derived state
%rune% cannot be called with arguments
## invalid_rune_args_length
%rune% can only be called with %list(args, 'or')% %
args.length === 1 && args[0] === 1 ? 'argument' : 'arguments'
%
%rune% must be called with %args%
## invalid_runes_mode_import
@ -98,4 +92,4 @@ $derived.call(...) has been replaced with $derived.by(...)
## conflicting_property_name
Cannot have a property and a component export with the same name
Cannot have a property and a component export with the same name

@ -20,114 +20,6 @@ const internal = {
`Internal compiler error: ${message}. Please report this to https://github.com/sveltejs/svelte/issues`
};
/** @satisfies {Errors} */
const special_elements = {
'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': /** @param {string} name */ (name) =>
`<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`,
/** @param {string} name */
'invalid-svelte-element-placement': (name) =>
`<${name}> tags cannot be inside elements or blocks`,
/** @param {string} name */
'duplicate-svelte-element': (name) => `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}`,
/**
* @param {string[]} tags
* @param {string | null} match
*/
'invalid-svelte-tag': (tags, match) =>
`Valid <svelte:...> tag names are ${list(tags)}${match ? ' (did you mean ' + match + '?)' : ''}`,
'conflicting-slot-usage': () =>
`Cannot use <slot> syntax and {@render ...} tags in the same component. Migrate towards {@render ...} tags completely.`
};
/** @satisfies {Errors} */
const runes = {
'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`,
/** @param {string} rune */
'invalid-rune-usage': (rune) => `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`,
/** @param {string} rune */
'invalid-state-location': (rune) =>
`${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`,
/**
* @param {boolean} is_binding
* @param {boolean} show_details
*/
'invalid-const-assignment': (is_binding, show_details) =>
`Invalid ${is_binding ? 'binding' : 'assignment'} to const variable${
show_details
? ' ($derived values, let: directives, :then/:catch variables and @const declarations count as const)'
: ''
}`,
'invalid-derived-assignment': () => `Invalid assignment to derived state`,
'invalid-derived-binding': () => `Invalid binding to derived state`,
/**
* @param {string} rune
* @param {Array<number | string>} args
*/
'invalid-rune-args-length': (rune, args) =>
`${rune} can only be called with ${list(args, 'or')} ${
args.length === 1 && args[0] === 1 ? 'argument' : 'arguments'
}`,
/** @param {string} name */
'invalid-runes-mode-import': (name) => `${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`
};
/** @satisfies {Errors} */
const elements = {
'invalid-textarea-content': () =>
@ -279,7 +171,6 @@ const const_tag = {
/** @satisfies {Errors} */
const errors = {
...internal,
...runes,
...elements,
...components,
...attributes,

@ -189,8 +189,8 @@ export function invalid_binding_value(node) {
* @returns {never}
*/
export function invalid_binding(node) {
e(node, "invalid_binding", "TODO");
export function invalid_bind_directive(node) {
e(node, "invalid_bind_directive", "TODO");
}
/**
@ -929,11 +929,20 @@ export function invalid_host_location(node) {
/**
* @param {number | NodeLike} node
* @param {string} thing
* @returns {never}
*/
export function invalid_assignment(node, thing) {
e(node, "invalid_assignment", `Invalid assignment to ${thing}`);
}
/**
* @param {number | NodeLike} node
* @param {string} thing
* @returns {never}
*/
export function invalid_const_assignment(node) {
e(node, "invalid_const_assignment", "Invalid %is_binding ? 'binding' : 'assignment'% to const variable%\n\t\t\tshow_details\n\t\t\t\t? ' ($derived values, let: directives, :then/:catch variables and @const declarations count as const)'\n\t\t\t\t: ''\n\t\t%");
export function invalid_binding(node, thing) {
e(node, "invalid_binding", `Invalid assignment to ${thing}`);
}
/**
@ -941,28 +950,27 @@ export function invalid_const_assignment(node) {
* @returns {never}
*/
export function invalid_derived_assignment(node) {
e(node, "invalid_derived_assignment", "Invalid assignment to derived state");
export function invalid_const_assignment(node) {
e(node, "invalid_const_assignment", "Invalid %is_binding ? 'binding' : 'assignment'% to const variable%\nshow_details\n? ' ($derived values, let: directives, :then/:catch variables and @const declarations count as const)'\n: ''\n%");
}
/**
* @param {number | NodeLike} node
* @param {string} rune
* @returns {never}
*/
export function invalid_derived_binding(node) {
e(node, "invalid_derived_binding", "Invalid binding to derived state");
export function invalid_rune_args(node, rune) {
e(node, "invalid_rune_args", `${rune} cannot be called with arguments`);
}
/**
* @param {number | NodeLike} node
* @param {string} rune
* @param {string} args
* @returns {never}
*/
export function invalid_rune_args_length(node, rune) {
e(node, "invalid_rune_args_length", `${rune} can only be called with %list(args, 'or')% %
args.length === 1 && args[0] === 1 ? 'argument' : 'arguments'
%`);
export function invalid_rune_args_length(node, rune, args) {
e(node, "invalid_rune_args_length", `${rune} must be called with ${args}`);
}
/**

@ -414,12 +414,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]) {
@ -452,7 +452,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);
}
}
}

@ -352,15 +352,15 @@ const validation = {
}
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);
}
}
@ -503,7 +503,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);
}
}
}
@ -772,7 +772,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 +805,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 +827,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 +840,7 @@ function validate_call_expression(node, scope, path) {
return;
}
}
error(node, 'invalid-bindable-location');
e.invalid_bindable_location(node);
}
if (
@ -851,46 +851,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');
}
}
}
@ -932,7 +932,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 +945,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 +1021,21 @@ 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)
// );
if (is_binding) {
e.invalid_binding(node, 'TODO');
} else {
e.invalid_assignment(node, 'TODO');
}
}
}
}
@ -1048,16 +1053,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 +1078,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 +1086,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 +1104,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 +1115,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 +1132,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 +1153,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);
}
}
}

@ -768,7 +768,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);

Loading…
Cancel
Save