diff --git a/.changeset/violet-apes-punch.md b/.changeset/violet-apes-punch.md
new file mode 100644
index 000000000000..77f4e923c8ce
--- /dev/null
+++ b/.changeset/violet-apes-punch.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: correctly parse leading comments in function binding
diff --git a/packages/svelte/src/compiler/phases/1-parse/read/expression.js b/packages/svelte/src/compiler/phases/1-parse/read/expression.js
index 82a667d38c59..a596cdf572cb 100644
--- a/packages/svelte/src/compiler/phases/1-parse/read/expression.js
+++ b/packages/svelte/src/compiler/phases/1-parse/read/expression.js
@@ -38,6 +38,10 @@ export default function read_expression(parser, opening_token, disallow_loose) {
let num_parens = 0;
+ if (node.leadingComments !== undefined && node.leadingComments.length > 0) {
+ parser.index = node.leadingComments.at(-1).end;
+ }
+
for (let i = parser.index; i < /** @type {number} */ (node.start); i += 1) {
if (parser.template[i] === '(') num_parens += 1;
}
diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js
index b4de1925df24..7719eee6772e 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js
@@ -132,8 +132,19 @@ export function BindDirective(node, context) {
}
let i = /** @type {number} */ (node.expression.start);
+ let leading_comments_start = /**@type {any}*/ (node.expression.leadingComments?.at(0))?.start;
+ let leading_comments_end = /**@type {any}*/ (node.expression.leadingComments?.at(-1))?.end;
while (context.state.analysis.source[--i] !== '{') {
- if (context.state.analysis.source[i] === '(') {
+ if (
+ context.state.analysis.source[i] === '(' &&
+ // if the parenthesis is in a leading comment we don't need to throw the error
+ !(
+ leading_comments_start &&
+ leading_comments_end &&
+ i <= leading_comments_end &&
+ i >= leading_comments_start
+ )
+ ) {
e.bind_invalid_parens(node, node.name);
}
}
diff --git a/packages/svelte/tests/parser-modern/samples/comment-before-function-binding/input.svelte b/packages/svelte/tests/parser-modern/samples/comment-before-function-binding/input.svelte
new file mode 100644
index 000000000000..50200a9eac5e
--- /dev/null
+++ b/packages/svelte/tests/parser-modern/samples/comment-before-function-binding/input.svelte
@@ -0,0 +1,10 @@
+
+
+ value,
+ (v) => value = v.toLowerCase()
+}
+/>
diff --git a/packages/svelte/tests/parser-modern/samples/comment-before-function-binding/output.json b/packages/svelte/tests/parser-modern/samples/comment-before-function-binding/output.json
new file mode 100644
index 000000000000..7189e27e964d
--- /dev/null
+++ b/packages/svelte/tests/parser-modern/samples/comment-before-function-binding/output.json
@@ -0,0 +1,326 @@
+{
+ "css": null,
+ "js": [],
+ "start": 37,
+ "end": 117,
+ "type": "Root",
+ "fragment": {
+ "type": "Fragment",
+ "nodes": [
+ {
+ "type": "Text",
+ "start": 35,
+ "end": 37,
+ "raw": "\n\n",
+ "data": "\n\n"
+ },
+ {
+ "type": "RegularElement",
+ "start": 37,
+ "end": 117,
+ "name": "input",
+ "attributes": [
+ {
+ "start": 44,
+ "end": 114,
+ "type": "BindDirective",
+ "name": "value",
+ "expression": {
+ "type": "SequenceExpression",
+ "start": 68,
+ "end": 112,
+ "loc": {
+ "start": {
+ "line": 7,
+ "column": 1
+ },
+ "end": {
+ "line": 8,
+ "column": 31
+ }
+ },
+ "expressions": [
+ {
+ "type": "ArrowFunctionExpression",
+ "start": 68,
+ "end": 79,
+ "loc": {
+ "start": {
+ "line": 7,
+ "column": 1
+ },
+ "end": {
+ "line": 7,
+ "column": 12
+ }
+ },
+ "id": null,
+ "expression": true,
+ "generator": false,
+ "async": false,
+ "params": [],
+ "body": {
+ "type": "Identifier",
+ "start": 74,
+ "end": 79,
+ "loc": {
+ "start": {
+ "line": 7,
+ "column": 7
+ },
+ "end": {
+ "line": 7,
+ "column": 12
+ }
+ },
+ "name": "value"
+ }
+ },
+ {
+ "type": "ArrowFunctionExpression",
+ "start": 82,
+ "end": 112,
+ "loc": {
+ "start": {
+ "line": 8,
+ "column": 1
+ },
+ "end": {
+ "line": 8,
+ "column": 31
+ }
+ },
+ "id": null,
+ "expression": true,
+ "generator": false,
+ "async": false,
+ "params": [
+ {
+ "type": "Identifier",
+ "start": 83,
+ "end": 84,
+ "loc": {
+ "start": {
+ "line": 8,
+ "column": 2
+ },
+ "end": {
+ "line": 8,
+ "column": 3
+ }
+ },
+ "name": "v"
+ }
+ ],
+ "body": {
+ "type": "AssignmentExpression",
+ "start": 89,
+ "end": 112,
+ "loc": {
+ "start": {
+ "line": 8,
+ "column": 8
+ },
+ "end": {
+ "line": 8,
+ "column": 31
+ }
+ },
+ "operator": "=",
+ "left": {
+ "type": "Identifier",
+ "start": 89,
+ "end": 94,
+ "loc": {
+ "start": {
+ "line": 8,
+ "column": 8
+ },
+ "end": {
+ "line": 8,
+ "column": 13
+ }
+ },
+ "name": "value"
+ },
+ "right": {
+ "type": "CallExpression",
+ "start": 97,
+ "end": 112,
+ "loc": {
+ "start": {
+ "line": 8,
+ "column": 16
+ },
+ "end": {
+ "line": 8,
+ "column": 31
+ }
+ },
+ "callee": {
+ "type": "MemberExpression",
+ "start": 97,
+ "end": 110,
+ "loc": {
+ "start": {
+ "line": 8,
+ "column": 16
+ },
+ "end": {
+ "line": 8,
+ "column": 29
+ }
+ },
+ "object": {
+ "type": "Identifier",
+ "start": 97,
+ "end": 98,
+ "loc": {
+ "start": {
+ "line": 8,
+ "column": 16
+ },
+ "end": {
+ "line": 8,
+ "column": 17
+ }
+ },
+ "name": "v"
+ },
+ "property": {
+ "type": "Identifier",
+ "start": 99,
+ "end": 110,
+ "loc": {
+ "start": {
+ "line": 8,
+ "column": 18
+ },
+ "end": {
+ "line": 8,
+ "column": 29
+ }
+ },
+ "name": "toLowerCase"
+ },
+ "computed": false,
+ "optional": false
+ },
+ "arguments": [],
+ "optional": false
+ }
+ }
+ }
+ ],
+ "leadingComments": [
+ {
+ "type": "Block",
+ "value": "* ( ",
+ "start": 58,
+ "end": 66
+ }
+ ]
+ },
+ "modifiers": []
+ }
+ ],
+ "fragment": {
+ "type": "Fragment",
+ "nodes": []
+ }
+ }
+ ]
+ },
+ "options": null,
+ "instance": {
+ "type": "Script",
+ "start": 0,
+ "end": 35,
+ "context": "default",
+ "content": {
+ "type": "Program",
+ "start": 8,
+ "end": 26,
+ "loc": {
+ "start": {
+ "line": 1,
+ "column": 0
+ },
+ "end": {
+ "line": 3,
+ "column": 0
+ }
+ },
+ "body": [
+ {
+ "type": "VariableDeclaration",
+ "start": 10,
+ "end": 25,
+ "loc": {
+ "start": {
+ "line": 2,
+ "column": 1
+ },
+ "end": {
+ "line": 2,
+ "column": 16
+ }
+ },
+ "declarations": [
+ {
+ "type": "VariableDeclarator",
+ "start": 14,
+ "end": 24,
+ "loc": {
+ "start": {
+ "line": 2,
+ "column": 5
+ },
+ "end": {
+ "line": 2,
+ "column": 15
+ }
+ },
+ "id": {
+ "type": "Identifier",
+ "start": 14,
+ "end": 19,
+ "loc": {
+ "start": {
+ "line": 2,
+ "column": 5
+ },
+ "end": {
+ "line": 2,
+ "column": 10
+ }
+ },
+ "name": "value"
+ },
+ "init": {
+ "type": "Literal",
+ "start": 22,
+ "end": 24,
+ "loc": {
+ "start": {
+ "line": 2,
+ "column": 13
+ },
+ "end": {
+ "line": 2,
+ "column": 15
+ }
+ },
+ "value": "",
+ "raw": "''"
+ }
+ }
+ ],
+ "kind": "let"
+ }
+ ],
+ "sourceType": "module"
+ },
+ "attributes": []
+ }
+}
\ No newline at end of file
diff --git a/packages/svelte/tests/validator/samples/comment-before-function-binding/errors.json b/packages/svelte/tests/validator/samples/comment-before-function-binding/errors.json
new file mode 100644
index 000000000000..fe51488c7066
--- /dev/null
+++ b/packages/svelte/tests/validator/samples/comment-before-function-binding/errors.json
@@ -0,0 +1 @@
+[]
diff --git a/packages/svelte/tests/validator/samples/comment-before-function-binding/input.svelte b/packages/svelte/tests/validator/samples/comment-before-function-binding/input.svelte
new file mode 100644
index 000000000000..50200a9eac5e
--- /dev/null
+++ b/packages/svelte/tests/validator/samples/comment-before-function-binding/input.svelte
@@ -0,0 +1,10 @@
+
+
+ value,
+ (v) => value = v.toLowerCase()
+}
+/>