diff --git a/packages/svelte/src/compiler/phases/1-parse/index.js b/packages/svelte/src/compiler/phases/1-parse/index.js index c520c8ea5c..bc027efbd1 100644 --- a/packages/svelte/src/compiler/phases/1-parse/index.js +++ b/packages/svelte/src/compiler/phases/1-parse/index.js @@ -98,6 +98,7 @@ export class Parser { locations: true, scopes: true, errorRecovery: loose, + parenthesized: true, // a script may export what the component declares elsewhere allowUndeclaredExports: true }); diff --git a/packages/svelte/src/compiler/phases/1-parse/state/tag.js b/packages/svelte/src/compiler/phases/1-parse/state/tag.js index e432accaee..2b3fe2cd27 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -604,14 +604,10 @@ function special(parser) { parser.eat('=', true); parser.allow_whitespace(); - const expression_start = parser.index; const init = read_expression(parser); // parser is past wrapping parens, but `init.end` is not — use the parser position const declarator_end = parser.index; - if ( - init.type === 'SequenceExpression' && - !parser.template.substring(expression_start, init.start).includes('(') - ) { + if (init.type === 'SequenceExpression' && !init.parenthesized) { // const a = (b, c) is allowed but a = b, c = d is not; e.const_tag_invalid_expression(init); } 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 559681cacd..66e8cd7968 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js @@ -135,22 +135,8 @@ export function BindDirective(node, context) { e.bind_group_invalid_expression(node); } - 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 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); - } + if (node.expression.parenthesized) { + e.bind_invalid_parens(node, node.name); } if (node.expression.expressions.length !== 2) { diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/component.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/component.js index 6d09398fb7..33e3147be5 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/component.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/component.js @@ -93,10 +93,7 @@ export function visit_component(node, context) { validate_attribute(attribute, node); if (is_expression_attribute(attribute)) { - disallow_unparenthesized_sequences( - get_attribute_expression(attribute), - context.state.analysis.source - ); + disallow_unparenthesized_sequences(get_attribute_expression(attribute)); } } @@ -112,7 +109,7 @@ export function visit_component(node, context) { } if (attribute.type === 'AttachTag') { - disallow_unparenthesized_sequences(attribute.expression, context.state.analysis.source); + disallow_unparenthesized_sequences(attribute.expression); } } @@ -163,15 +160,9 @@ export function visit_component(node, context) { /** * @param {Expression} expression - * @param {string} source */ -function disallow_unparenthesized_sequences(expression, source) { - if (expression.type === 'SequenceExpression') { - let i = /** @type {number} */ (expression.start); - while (--i > 0) { - const char = source[i]; - if (char === '(') break; // parenthesized sequence expressions are ok - if (char === '{') e.attribute_invalid_sequence_expression(expression); - } +function disallow_unparenthesized_sequences(expression) { + if (expression.type === 'SequenceExpression' && !expression.parenthesized) { + e.attribute_invalid_sequence_expression(expression); } } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/element.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/element.js index 6a0c58ca30..b39f850a60 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/element.js @@ -45,13 +45,8 @@ export function validate_element(node, context) { if (is_expression) { const expression = get_attribute_expression(attribute); - if (expression.type === 'SequenceExpression') { - let i = /** @type {number} */ (expression.start); - while (--i > 0) { - const char = context.state.analysis.source[i]; - if (char === '(') break; // parenthesized sequence expressions are ok - if (char === '{') e.attribute_invalid_sequence_expression(expression); - } + if (expression.type === 'SequenceExpression' && !expression.parenthesized) { + e.attribute_invalid_sequence_expression(expression); } } } diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index 4d4b6fc21f..61e186f7b2 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -665,5 +665,7 @@ declare module 'estree' { end?: number; /** Added by acorn-typescript */ typeAnnotation?: any; + /** The source wraps the node in parens, from the parser; absent otherwise */ + parenthesized?: true; } } diff --git a/packages/svelte/tests/parser-modern/samples/const-tag-parenthesized-init/output.json b/packages/svelte/tests/parser-modern/samples/const-tag-parenthesized-init/output.json index 3e8d8b23d5..f7455ab6e5 100644 --- a/packages/svelte/tests/parser-modern/samples/const-tag-parenthesized-init/output.json +++ b/packages/svelte/tests/parser-modern/samples/const-tag-parenthesized-init/output.json @@ -79,6 +79,7 @@ "column": 24 } }, + "parenthesized": true, "object": { "type": "Identifier", "start": 36, @@ -171,6 +172,7 @@ "column": 24 } }, + "parenthesized": true, "operator": "=", "left": { "type": "Identifier", @@ -295,6 +297,7 @@ "column": 27 } }, + "parenthesized": true, "properties": [ { "type": "Property", @@ -523,5 +526,6 @@ } ] }, - "options": null + "options": null, + "comments": [] } diff --git a/packages/svelte/tests/parser-modern/samples/each-block-object-pattern/output.json b/packages/svelte/tests/parser-modern/samples/each-block-object-pattern/output.json index 60fb4c4c09..fd304d6685 100644 --- a/packages/svelte/tests/parser-modern/samples/each-block-object-pattern/output.json +++ b/packages/svelte/tests/parser-modern/samples/each-block-object-pattern/output.json @@ -1005,6 +1005,7 @@ "column": 60 } }, + "parenthesized": true, "id": null, "expression": false, "generator": false, diff --git a/packages/svelte/tests/parser-modern/samples/parens/output.json b/packages/svelte/tests/parser-modern/samples/parens/output.json index 3543cad410..b537ef5076 100644 --- a/packages/svelte/tests/parser-modern/samples/parens/output.json +++ b/packages/svelte/tests/parser-modern/samples/parens/output.json @@ -25,6 +25,7 @@ "column": 9 } }, + "parenthesized": true, "leadingComments": [ { "type": "Block", diff --git a/packages/svelte/tests/parser-modern/test.ts b/packages/svelte/tests/parser-modern/test.ts index 540a393c38..e4082e763e 100644 --- a/packages/svelte/tests/parser-modern/test.ts +++ b/packages/svelte/tests/parser-modern/test.ts @@ -76,6 +76,9 @@ function clean(ast: AST.SvelteNode) { delete node.leadingComments; // @ts-ignore delete node.trailingComments; + // the printer drops parens it does not need + // @ts-ignore + delete node.parenthesized; context.next(); },