diff --git a/.changeset/cool-poems-watch.md b/.changeset/cool-poems-watch.md new file mode 100644 index 0000000000..34527ad16f --- /dev/null +++ b/.changeset/cool-poems-watch.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: disallow mixing on:click and onclick syntax diff --git a/packages/svelte/messages/compile-errors/template.md b/packages/svelte/messages/compile-errors/template.md index c1b8c5f3c3..293d4845cd 100644 --- a/packages/svelte/messages/compile-errors/template.md +++ b/packages/svelte/messages/compile-errors/template.md @@ -172,6 +172,10 @@ > `let:` directive at invalid position +## mixed_event_handler_syntaxes + +> Mixing old (on:%name%) and new syntaxes for event handling is not allowed. Use only the on%name% syntax. + ## node_invalid_placement > %thing% is invalid inside <%parent%> diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index a00a2cd593..d2834d03d4 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -918,6 +918,16 @@ export function let_directive_invalid_placement(node) { e(node, "let_directive_invalid_placement", "`let:` directive at invalid position"); } +/** + * Mixing old (on:%name%) and new syntaxes for event handling is not allowed. Use only the on%name% syntax. + * @param {null | number | NodeLike} node + * @param {string} name + * @returns {never} + */ +export function mixed_event_handler_syntaxes(node, name) { + e(node, "mixed_event_handler_syntaxes", `Mixing old (on:${name}) and new syntaxes for event handling is not allowed. Use only the on${name} syntax.`); +} + /** * %thing% is invalid inside <%parent%> * @param {null | number | NodeLike} node diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 2ed2288a8e..8d3088806d 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -372,6 +372,8 @@ export function analyze_component(root, source, options) { uses_render_tags: false, needs_context: false, needs_props: false, + event_directive_node: null, + uses_event_attributes: false, custom_element: options.customElementOptions ?? options.customElement, inject_styles: options.css === 'injected' || options.customElement, accessors: options.customElement @@ -494,6 +496,13 @@ export function analyze_component(root, source, options) { analysis.reactive_statements = order_reactive_statements(analysis.reactive_statements); } + if (analysis.event_directive_node && analysis.uses_event_attributes) { + e.mixed_event_handler_syntaxes( + analysis.event_directive_node, + analysis.event_directive_node.name + ); + } + if (analysis.uses_render_tags && (analysis.uses_slots || analysis.slot_names.size > 0)) { e.slot_snippet_conflict(analysis.slot_names.values().next().value); } @@ -1153,6 +1162,11 @@ const common_visitors = { }); if (is_event_attribute(node)) { + const parent = context.path.at(-1); + if (parent?.type === 'RegularElement' || parent?.type === 'SvelteElement') { + context.state.analysis.uses_event_attributes = true; + } + const expression = node.value[0].expression; const delegated_event = get_delegated_event(node.name.slice(2), expression, context); @@ -1286,6 +1300,13 @@ const common_visitors = { context.next(); }, + OnDirective(node, { state, path, next }) { + const parent = path.at(-1); + if (parent?.type === 'SvelteElement' || parent?.type === 'RegularElement') { + state.analysis.event_directive_node ??= node; + } + next(); + }, BindDirective(node, context) { let i = context.path.length; while (i--) { diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index 4f68b5f9ed..ab793bf1a7 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -8,6 +8,7 @@ import * as e from '../../errors.js'; import { extract_identifiers, get_parent, + is_event_attribute, is_expression_attribute, is_text_attribute, object, diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index 3e892fefef..ae346068c4 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -2,6 +2,7 @@ import type { Binding, Css, Fragment, + OnDirective, RegularElement, SlotElement, SvelteElement, @@ -59,6 +60,10 @@ export interface ComponentAnalysis extends Analysis { uses_render_tags: boolean; needs_context: boolean; needs_props: boolean; + /** Set to the first event directive (on:x) found on a DOM element in the code */ + event_directive_node: OnDirective | null; + /** true if uses event attributes (onclick) on a DOM element */ + uses_event_attributes: boolean; custom_element: boolean | SvelteOptions['customElement']; /** If `true`, should append styles through JavaScript */ inject_styles: boolean; diff --git a/packages/svelte/tests/runtime-runes/samples/event-attribute-delegation-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/event-attribute-delegation-2/main.svelte index 2a18005538..3e803a6283 100644 --- a/packages/svelte/tests/runtime-runes/samples/event-attribute-delegation-2/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/event-attribute-delegation-2/main.svelte @@ -1,4 +1,4 @@ -