diff --git a/.changeset/brown-dots-change.md b/.changeset/brown-dots-change.md
new file mode 100644
index 000000000..dfa797d2f
--- /dev/null
+++ b/.changeset/brown-dots-change.md
@@ -0,0 +1,5 @@
+---
+'eslint-plugin-svelte': minor
+---
+
+feat: add Svelte 5 support to `no-not-function-handler`
diff --git a/docs/rules/no-not-function-handler.md b/docs/rules/no-not-function-handler.md
index 3162a4254..c1736a0a2 100644
--- a/docs/rules/no-not-function-handler.md
+++ b/docs/rules/no-not-function-handler.md
@@ -29,16 +29,16 @@ If you use a non-function value for the event handler, it event handler will not
-
+
{
+ onclick={() => {
/* */
}}
/>
-
-
+
+
```
## :wrench: Options
diff --git a/packages/eslint-plugin-svelte/src/rules/no-not-function-handler.ts b/packages/eslint-plugin-svelte/src/rules/no-not-function-handler.ts
index 7b46c3d80..84394c74f 100644
--- a/packages/eslint-plugin-svelte/src/rules/no-not-function-handler.ts
+++ b/packages/eslint-plugin-svelte/src/rules/no-not-function-handler.ts
@@ -2,6 +2,7 @@ import type { AST } from 'svelte-eslint-parser';
import type { TSESTree } from '@typescript-eslint/types';
import { createRule } from '../utils/index.js';
import { findVariable } from '../utils/ast-utils.js';
+import { EVENT_NAMES } from '../utils/events.js';
const PHRASES = {
ObjectExpression: 'object',
@@ -95,6 +96,18 @@ export default createRule('no-not-function-handler', {
return;
}
verify(node.expression);
+ },
+ SvelteAttribute(node) {
+ if (node.key.type === 'SvelteName' && EVENT_NAMES.includes(node.key.name)) {
+ const { value } = node;
+ if (Array.isArray(value)) {
+ for (const v of value) {
+ if (v.type === 'SvelteMustacheTag') {
+ verify(v.expression);
+ }
+ }
+ }
+ }
}
};
}
diff --git a/packages/eslint-plugin-svelte/src/utils/events.ts b/packages/eslint-plugin-svelte/src/utils/events.ts
new file mode 100644
index 000000000..d9950db05
--- /dev/null
+++ b/packages/eslint-plugin-svelte/src/utils/events.ts
@@ -0,0 +1,369 @@
+// see: https://github.com/sveltejs/svelte/blob/a129592e5b248a734a68da6e9028941803a3d063/packages/svelte/elements.d.ts#L83
+export const EVENT_NAMES: string[] = [
+ // Clipboard Events
+ 'on:copy',
+ 'oncopy',
+ 'oncopycapture',
+ 'on:cut',
+ 'oncut',
+ 'oncutcapture',
+ 'on:paste',
+ 'onpaste',
+ 'onpastecapture',
+
+ // Composition Events
+ 'on:compositionend',
+ 'oncompositionend',
+ 'oncompositionendcapture',
+ 'on:compositionstart',
+ 'oncompositionstart',
+ 'oncompositionstartcapture',
+ 'on:compositionupdate',
+ 'oncompositionupdate',
+ 'oncompositionupdatecapture',
+
+ // Focus Events
+ 'on:focus',
+ 'onfocus',
+ 'onfocuscapture',
+ 'on:focusin',
+ 'onfocusin',
+ 'onfocusincapture',
+ 'on:focusout',
+ 'onfocusout',
+ 'onfocusoutcapture',
+ 'on:blur',
+ 'onblur',
+ 'onblurcapture',
+
+ // Form Events
+ 'on:change',
+ 'onchange',
+ 'onchangecapture',
+ 'on:beforeinput',
+ 'onbeforeinput',
+ 'onbeforeinputcapture',
+ // oninput can be either an InputEvent or an Event, depending on the target element (input, textarea etc).
+ 'on:input',
+ 'oninput',
+ 'oninputcapture',
+ 'on:reset',
+ 'onreset',
+ 'onresetcapture',
+ 'on:submit',
+ 'onsubmit',
+ 'onsubmitcapture',
+ 'on:invalid',
+ 'oninvalid',
+ 'oninvalidcapture',
+ 'on:formdata',
+ 'onformdata',
+ 'onformdatacapture',
+
+ // Image Events
+ 'on:load',
+ 'onload',
+ 'onloadcapture',
+ 'on:error',
+ 'onerror',
+ 'onerrorcapture',
+
+ // Popover Events
+ 'on:beforetoggle',
+ 'onbeforetoggle',
+ 'onbeforetogglecapture',
+ 'on:toggle',
+ 'ontoggle',
+ 'ontogglecapture',
+
+ // Content visibility Events
+ 'on:contentvisibilityautostatechange',
+ 'oncontentvisibilityautostatechange',
+ 'oncontentvisibilityautostatechangecapture',
+ // Keyboard Events
+ 'on:keydown',
+ 'onkeydown',
+ 'onkeydowncapture',
+ 'on:keypress',
+ 'onkeypress',
+ 'onkeypresscapture',
+ 'on:keyup',
+ 'onkeyup',
+ 'onkeyupcapture',
+
+ // Media Events
+ 'on:abort',
+ 'onabort',
+ 'onabortcapture',
+ 'on:canplay',
+ 'oncanplay',
+ 'oncanplaycapture',
+ 'on:canplaythrough',
+ 'oncanplaythrough',
+ 'oncanplaythroughcapture',
+ 'on:cuechange',
+ 'oncuechange',
+ 'oncuechangecapture',
+ 'on:durationchange',
+ 'ondurationchange',
+ 'ondurationchangecapture',
+ 'on:emptied',
+ 'onemptied',
+ 'onemptiedcapture',
+ 'on:encrypted',
+ 'onencrypted',
+ 'onencryptedcapture',
+ 'on:ended',
+ 'onended',
+ 'onendedcapture',
+ 'on:loadeddata',
+ 'onloadeddata',
+ 'onloadeddatacapture',
+ 'on:loadedmetadata',
+ 'onloadedmetadata',
+ 'onloadedmetadatacapture',
+ 'on:loadstart',
+ 'onloadstart',
+ 'onloadstartcapture',
+ 'on:pause',
+ 'onpause',
+ 'onpausecapture',
+ 'on:play',
+ 'onplay',
+ 'onplaycapture',
+ 'on:playing',
+ 'onplaying',
+ 'onplayingcapture',
+ 'on:progress',
+ 'onprogress',
+ 'onprogresscapture',
+ 'on:ratechange',
+ 'onratechange',
+ 'onratechangecapture',
+ 'on:seeked',
+ 'onseeked',
+ 'onseekedcapture',
+ 'on:seeking',
+ 'onseeking',
+ 'onseekingcapture',
+ 'on:stalled',
+ 'onstalled',
+ 'onstalledcapture',
+ 'on:suspend',
+ 'onsuspend',
+ 'onsuspendcapture',
+ 'on:timeupdate',
+ 'ontimeupdate',
+ 'ontimeupdatecapture',
+ 'on:volumechange',
+ 'onvolumechange',
+ 'onvolumechangecapture',
+ 'on:waiting',
+ 'onwaiting',
+ 'onwaitingcapture',
+
+ // MouseEvents
+ 'on:auxclick',
+ 'onauxclick',
+ 'onauxclickcapture',
+ 'on:click',
+ 'onclick',
+ 'onclickcapture',
+ 'on:contextmenu',
+ 'oncontextmenu',
+ 'oncontextmenucapture',
+ 'on:dblclick',
+ 'ondblclick',
+ 'ondblclickcapture',
+ 'on:drag',
+ 'ondrag',
+ 'ondragcapture',
+ 'on:dragend',
+ 'ondragend',
+ 'ondragendcapture',
+ 'on:dragenter',
+ 'ondragenter',
+ 'ondragentercapture',
+ 'on:dragexit',
+ 'ondragexit',
+ 'ondragexitcapture',
+ 'on:dragleave',
+ 'ondragleave',
+ 'ondragleavecapture',
+ 'on:dragover',
+ 'ondragover',
+ 'ondragovercapture',
+ 'on:dragstart',
+ 'ondragstart',
+ 'ondragstartcapture',
+ 'on:drop',
+ 'ondrop',
+ 'ondropcapture',
+ 'on:mousedown',
+ 'onmousedown',
+ 'onmousedowncapture',
+ 'on:mouseenter',
+ 'onmouseenter',
+ 'on:mouseleave',
+ 'onmouseleave',
+ 'on:mousemove',
+ 'onmousemove',
+ 'onmousemovecapture',
+ 'on:mouseout',
+ 'onmouseout',
+ 'onmouseoutcapture',
+ 'on:mouseover',
+ 'onmouseover',
+ 'onmouseovercapture',
+ 'on:mouseup',
+ 'onmouseup',
+ 'onmouseupcapture',
+
+ // Selection Events
+ 'on:select',
+ 'onselect',
+ 'onselectcapture',
+ 'on:selectionchange',
+ 'onselectionchange',
+ 'onselectionchangecapture',
+ 'on:selectstart',
+ 'onselectstart',
+ 'onselectstartcapture',
+
+ // Touch Events
+ 'on:touchcancel',
+ 'ontouchcancel',
+ 'ontouchcancelcapture',
+ 'on:touchend',
+ 'ontouchend',
+ 'ontouchendcapture',
+ 'on:touchmove',
+ 'ontouchmove',
+ 'ontouchmovecapture',
+ 'on:touchstart',
+ 'ontouchstart',
+ 'ontouchstartcapture',
+
+ // Pointer Events
+ 'on:gotpointercapture',
+ 'ongotpointercapture',
+ 'ongotpointercapturecapture',
+ 'on:pointercancel',
+ 'onpointercancel',
+ 'onpointercancelcapture',
+ 'on:pointerdown',
+ 'onpointerdown',
+ 'onpointerdowncapture',
+ 'on:pointerenter',
+ 'onpointerenter',
+ 'onpointerentercapture',
+ 'on:pointerleave',
+ 'onpointerleave',
+ 'onpointerleavecapture',
+ 'on:pointermove',
+ 'onpointermove',
+ 'onpointermovecapture',
+ 'on:pointerout',
+ 'onpointerout',
+ 'onpointeroutcapture',
+ 'on:pointerover',
+ 'onpointerover',
+ 'onpointerovercapture',
+ 'on:pointerup',
+ 'onpointerup',
+ 'onpointerupcapture',
+ 'on:lostpointercapture',
+ 'onlostpointercapture',
+ 'onlostpointercapturecapture',
+
+ // Gamepad Events
+ 'on:gamepadconnected',
+ 'ongamepadconnected',
+ 'on:gamepaddisconnected',
+ 'ongamepaddisconnected',
+
+ // UI Events
+ 'on:scroll',
+ 'onscroll',
+ 'onscrollcapture',
+ 'on:scrollend',
+ 'onscrollend',
+ 'onscrollendcapture',
+ 'on:resize',
+ 'onresize',
+ 'onresizecapture',
+
+ // Wheel Events
+ 'on:wheel',
+ 'onwheel',
+ 'onwheelcapture',
+
+ // Animation Events
+ 'on:animationstart',
+ 'onanimationstart',
+ 'onanimationstartcapture',
+ 'on:animationend',
+ 'onanimationend',
+ 'onanimationendcapture',
+ 'on:animationiteration',
+ 'onanimationiteration',
+ 'onanimationiterationcapture',
+
+ // Transition Events
+ 'on:transitionstart',
+ 'ontransitionstart',
+ 'ontransitionstartcapture',
+ 'on:transitionrun',
+ 'ontransitionrun',
+ 'ontransitionruncapture',
+ 'on:transitionend',
+ 'ontransitionend',
+ 'ontransitionendcapture',
+ 'on:transitioncancel',
+ 'ontransitioncancel',
+ 'ontransitioncancelcapture',
+
+ // Svelte Transition Events
+ 'on:outrostart',
+ 'onoutrostart',
+ 'onoutrostartcapture',
+ 'on:outroend',
+ 'onoutroend',
+ 'onoutroendcapture',
+ 'on:introstart',
+ 'onintrostart',
+ 'onintrostartcapture',
+ 'on:introend',
+ 'onintroend',
+ 'onintroendcapture',
+
+ // Message Events
+ 'on:message',
+ 'onmessage',
+ 'onmessagecapture',
+ 'on:messageerror',
+ 'onmessageerror',
+ 'onmessageerrorcapture',
+
+ // Document Events
+ 'on:visibilitychange',
+ 'onvisibilitychange',
+ 'onvisibilitychangecapture',
+
+ // Global Events
+ 'on:beforematch',
+ 'onbeforematch',
+ 'onbeforematchcapture',
+ 'on:cancel',
+ 'oncancel',
+ 'oncancelcapture',
+ 'on:close',
+ 'onclose',
+ 'onclosecapture',
+ 'on:fullscreenchange',
+ 'onfullscreenchange',
+ 'onfullscreenchangecapture',
+ 'on:fullscreenerror',
+ 'onfullscreenerror',
+ 'onfullscreenerrorcapture'
+] as const;
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/array01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/array01-errors.yaml
new file mode 100644
index 000000000..4b8f175f5
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/array01-errors.yaml
@@ -0,0 +1,4 @@
+- message: Unexpected array in event handler.
+ line: 5
+ column: 18
+ suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/array01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/array01-input.svelte
new file mode 100644
index 000000000..215e452e7
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/array01-input.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/class01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/class01-errors.yaml
new file mode 100644
index 000000000..46687e79b
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/class01-errors.yaml
@@ -0,0 +1,4 @@
+- message: Unexpected class in event handler.
+ line: 4
+ column: 18
+ suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/class01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/class01-input.svelte
new file mode 100644
index 000000000..c62acc704
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/class01-input.svelte
@@ -0,0 +1,4 @@
+
+
+
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/object01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/object01-errors.yaml
new file mode 100644
index 000000000..b67b8ca3f
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/object01-errors.yaml
@@ -0,0 +1,4 @@
+- message: Unexpected object in event handler.
+ line: 5
+ column: 18
+ suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/object01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/object01-input.svelte
new file mode 100644
index 000000000..6bab42658
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/object01-input.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/requirements.json
new file mode 100644
index 000000000..1a22befbb
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/requirements.json
@@ -0,0 +1,3 @@
+{
+ "svelte": "^5.0.0"
+}
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/string01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/string01-errors.yaml
new file mode 100644
index 000000000..81b12196e
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/string01-errors.yaml
@@ -0,0 +1,8 @@
+- message: Unexpected string value in event handler.
+ line: 6
+ column: 18
+ suggestions: null
+- message: Unexpected string value in event handler.
+ line: 7
+ column: 18
+ suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/string01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/string01-input.svelte
new file mode 100644
index 000000000..01a8dfb58
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/string01-input.svelte
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/value01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/value01-errors.yaml
new file mode 100644
index 000000000..f6f646a22
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/value01-errors.yaml
@@ -0,0 +1,12 @@
+- message: Unexpected number value in event handler.
+ line: 7
+ column: 18
+ suggestions: null
+- message: Unexpected bigint value in event handler.
+ line: 8
+ column: 18
+ suggestions: null
+- message: Unexpected regex value in event handler.
+ line: 9
+ column: 18
+ suggestions: null
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/value01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/value01-input.svelte
new file mode 100644
index 000000000..56a57dc7b
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/invalid/svelte5/value01-input.svelte
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/valid/svelte5/function01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/valid/svelte5/function01-input.svelte
new file mode 100644
index 000000000..090905b42
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/valid/svelte5/function01-input.svelte
@@ -0,0 +1,7 @@
+
+
+ a} />
+
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/valid/svelte5/null01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/valid/svelte5/null01-input.svelte
new file mode 100644
index 000000000..e68461e10
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/valid/svelte5/null01-input.svelte
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/valid/svelte5/requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/valid/svelte5/requirements.json
new file mode 100644
index 000000000..1a22befbb
--- /dev/null
+++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-not-function-handler/valid/svelte5/requirements.json
@@ -0,0 +1,3 @@
+{
+ "svelte": "^5.0.0"
+}