From c1f143411c57f981b322598d9ee53a71b0218206 Mon Sep 17 00:00:00 2001 From: gtmnayan Date: Sun, 28 May 2023 17:22:22 +0545 Subject: [PATCH] fix(internal): parse let directive as pattern instead of expression --- .../svelte/src/compiler/compile/Component.js | 9 +- .../svelte/src/compiler/compile/nodes/Let.js | 9 +- .../svelte/src/compiler/parse/state/tag.js | 14 +- .../output.json | 2 +- .../dynamic-element-string/output.json | 14 +- .../dynamic-element-variable/output.json | 24 ++-- .../let-directive-destructure/input.svelte | 3 + .../let-directive-destructure/output.json | 133 ++++++++++++++++++ packages/svelte/test/parser/update.js | 2 + 9 files changed, 171 insertions(+), 39 deletions(-) create mode 100644 packages/svelte/test/parser/samples/let-directive-destructure/input.svelte create mode 100644 packages/svelte/test/parser/samples/let-directive-destructure/output.json diff --git a/packages/svelte/src/compiler/compile/Component.js b/packages/svelte/src/compiler/compile/Component.js index 894cb9b787b9..0c78abfd44cc 100644 --- a/packages/svelte/src/compiler/compile/Component.js +++ b/packages/svelte/src/compiler/compile/Component.js @@ -1664,13 +1664,10 @@ export default class Component { const { let_attributes } = parent; for (const attr of let_attributes) { - if ( + if (attr.expression) { // @ts-expect-error - // TODO extract_names only considers patterns but let attributes return expressions - (attr.expression && extract_names(attr.expression).includes(name)) || - attr.name === name - ) - return true; + if (extract_names(attr.expression).includes(name)) return true; + } else if (attr.name === name) return true; } } } diff --git a/packages/svelte/src/compiler/compile/nodes/Let.js b/packages/svelte/src/compiler/compile/nodes/Let.js index 140073897e27..e00ba3ca1d08 100644 --- a/packages/svelte/src/compiler/compile/nodes/Let.js +++ b/packages/svelte/src/compiler/compile/nodes/Let.js @@ -2,7 +2,7 @@ import Node from './shared/Node.js'; import { walk } from 'estree-walker'; import compiler_errors from '../compiler_errors.js'; -const applicable = new Set(['Identifier', 'ObjectExpression', 'ArrayExpression', 'Property']); +const applicable = new Set(['Identifier', 'ObjectPattern', 'ArrayPattern', 'Property']); /** @extends Node<'Let'> */ export default class Let extends Node { @@ -36,13 +36,6 @@ export default class Let extends Node { if (node.type === 'Identifier') { names.push(/** @type {import('estree').Identifier} */ (node).name); } - // slightly unfortunate hack - if (node.type === 'ArrayExpression') { - node.type = 'ArrayPattern'; - } - if (node.type === 'ObjectExpression') { - node.type = 'ObjectPattern'; - } } }); } else { diff --git a/packages/svelte/src/compiler/parse/state/tag.js b/packages/svelte/src/compiler/parse/state/tag.js index c0df00df929c..478d4e5490e0 100644 --- a/packages/svelte/src/compiler/parse/state/tag.js +++ b/packages/svelte/src/compiler/parse/state/tag.js @@ -6,6 +6,7 @@ import read_expression from '../read/expression.js'; import read_script from '../read/script.js'; import read_style from '../read/style.js'; import { closing_tag_omitted, decode_character_references } from '../utils/html.js'; +import read_context from '../read/context.js'; // eslint-disable-next-line no-useless-escape const valid_tag_name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/; @@ -348,7 +349,7 @@ function read_attribute(parser, unique_names) { let value = true; if (parser.eat('=')) { parser.allow_whitespace(); - value = read_attribute_value(parser); + value = read_attribute_value(parser, type === 'Let'); end = parser.index; } else if (parser.match_regex(regex_starts_with_quote_characters)) { parser.error(parser_errors.unexpected_token('='), parser.index); @@ -438,8 +439,9 @@ function get_directive_type(name) { /** * @param {import('../index.js').Parser} parser + * @param {boolean} as_pattern */ -function read_attribute_value(parser) { +function read_attribute_value(parser, as_pattern = false) { const quote_mark = parser.eat("'") ? "'" : parser.eat('"') ? '"' : null; if (quote_mark && parser.eat(quote_mark)) { return [ @@ -461,7 +463,8 @@ function read_attribute_value(parser) { if (quote_mark) return parser.match(quote_mark); return !!parser.match_regex(regex_starts_with_invalid_attr_value); }, - 'in attribute value' + 'in attribute value', + as_pattern ); } catch (error) { if (error.code === 'parse-error') { @@ -486,9 +489,10 @@ function read_attribute_value(parser) { * @param {import('../index.js').Parser} parser * @param {() => boolean} done * @param {string} location + * @param {boolean} as_pattern * @returns {import('../../interfaces.js').TemplateNode[]} */ -function read_sequence(parser, done, location) { +function read_sequence(parser, done, location, as_pattern = false) { /** * @type {import('../../interfaces.js').Text} */ @@ -534,7 +538,7 @@ function read_sequence(parser, done, location) { } flush(parser.index - 1); parser.allow_whitespace(); - const expression = read_expression(parser); + const expression = as_pattern ? read_context(parser) : read_expression(parser); parser.allow_whitespace(); parser.eat('}', true); chunks.push({ diff --git a/packages/svelte/test/parser/samples/attribute-style-directive-string/output.json b/packages/svelte/test/parser/samples/attribute-style-directive-string/output.json index 505e9333e134..4088f4035696 100644 --- a/packages/svelte/test/parser/samples/attribute-style-directive-string/output.json +++ b/packages/svelte/test/parser/samples/attribute-style-directive-string/output.json @@ -46,8 +46,8 @@ "start": 35, "end": 52, "type": "StyleDirective", - "modifiers": [], "name": "color", + "modifiers": [], "value": [ { "start": 48, diff --git a/packages/svelte/test/parser/samples/dynamic-element-string/output.json b/packages/svelte/test/parser/samples/dynamic-element-string/output.json index 9e9245b1aed7..7211fcda49a8 100644 --- a/packages/svelte/test/parser/samples/dynamic-element-string/output.json +++ b/packages/svelte/test/parser/samples/dynamic-element-string/output.json @@ -14,11 +14,11 @@ "tag": "div" }, { - "type": "Text", "start": 44, "end": 45, - "data": "\n", - "raw": "\n" + "type": "Text", + "raw": "\n", + "data": "\n" }, { "start": 45, @@ -27,17 +27,17 @@ "name": "svelte:element", "attributes": [ { - "type": "Attribute", "start": 72, "end": 83, + "type": "Attribute", "name": "class", "value": [ { - "type": "Text", "start": 79, "end": 82, - "data": "foo", - "raw": "foo" + "type": "Text", + "raw": "foo", + "data": "foo" } ] } diff --git a/packages/svelte/test/parser/samples/dynamic-element-variable/output.json b/packages/svelte/test/parser/samples/dynamic-element-variable/output.json index 53769e94e3b0..72adc84caf67 100644 --- a/packages/svelte/test/parser/samples/dynamic-element-variable/output.json +++ b/packages/svelte/test/parser/samples/dynamic-element-variable/output.json @@ -12,6 +12,7 @@ "attributes": [], "children": [], "tag": { + "type": "Identifier", "start": 22, "end": 25, "loc": { @@ -24,41 +25,41 @@ "column": 25 } }, - "name": "tag", - "type": "Identifier" + "name": "tag" } }, { - "type": "Text", "start": 44, "end": 45, - "data": "\n", - "raw": "\n" + "type": "Text", + "raw": "\n", + "data": "\n" }, { "start": 45, "end": 101, "type": "Element", "name": "svelte:element", - "children": [], "attributes": [ { - "type": "Attribute", "start": 72, "end": 83, + "type": "Attribute", "name": "class", "value": [ { - "type": "Text", "start": 79, "end": 82, - "data": "foo", - "raw": "foo" + "type": "Text", + "raw": "foo", + "data": "foo" } ] } ], + "children": [], "tag": { + "type": "Identifier", "start": 67, "end": 70, "loc": { @@ -71,8 +72,7 @@ "column": 25 } }, - "name": "tag", - "type": "Identifier" + "name": "tag" } } ] diff --git a/packages/svelte/test/parser/samples/let-directive-destructure/input.svelte b/packages/svelte/test/parser/samples/let-directive-destructure/input.svelte new file mode 100644 index 000000000000..944dd1235a61 --- /dev/null +++ b/packages/svelte/test/parser/samples/let-directive-destructure/input.svelte @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/svelte/test/parser/samples/let-directive-destructure/output.json b/packages/svelte/test/parser/samples/let-directive-destructure/output.json new file mode 100644 index 000000000000..7c165a6b616e --- /dev/null +++ b/packages/svelte/test/parser/samples/let-directive-destructure/output.json @@ -0,0 +1,133 @@ +{ + "html": { + "start": 0, + "end": 43, + "type": "Fragment", + "children": [ + { + "start": 0, + "end": 43, + "type": "InlineComponent", + "name": "Component", + "attributes": [ + { + "start": 11, + "end": 28, + "type": "Let", + "name": "x", + "modifiers": [], + "expression": { + "type": "ObjectPattern", + "start": 18, + "end": 27, + "loc": { + "start": { + "line": 1, + "column": 18 + }, + "end": { + "line": 1, + "column": 27 + } + }, + "properties": [ + { + "type": "Property", + "start": 19, + "end": 26, + "loc": { + "start": { + "line": 1, + "column": 19 + }, + "end": { + "line": 1, + "column": 26 + } + }, + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "start": 19, + "end": 22, + "loc": { + "start": { + "line": 1, + "column": 19 + }, + "end": { + "line": 1, + "column": 22 + } + }, + "name": "foo" + }, + "kind": "init", + "value": { + "type": "AssignmentPattern", + "start": 19, + "end": 26, + "loc": { + "start": { + "line": 1, + "column": 19 + }, + "end": { + "line": 1, + "column": 26 + } + }, + "left": { + "type": "Identifier", + "start": 19, + "end": 22, + "loc": { + "start": { + "line": 1, + "column": 19 + }, + "end": { + "line": 1, + "column": 22 + } + }, + "name": "foo" + }, + "right": { + "type": "Literal", + "start": 25, + "end": 26, + "loc": { + "start": { + "line": 1, + "column": 25 + }, + "end": { + "line": 1, + "column": 26 + } + }, + "value": 1, + "raw": "1" + } + } + } + ] + } + } + ], + "children": [ + { + "start": 29, + "end": 31, + "type": "Text", + "raw": "\n\n", + "data": "\n\n" + } + ] + } + ] + } +} diff --git a/packages/svelte/test/parser/update.js b/packages/svelte/test/parser/update.js index 0a33b17535a7..715b02c777c9 100644 --- a/packages/svelte/test/parser/update.js +++ b/packages/svelte/test/parser/update.js @@ -4,6 +4,8 @@ import * as fs from 'node:fs'; import glob from 'tiny-glob/sync.js'; +const __dirname = new URL('.', import.meta.url).pathname; + glob('samples/*/_actual.json', { cwd: __dirname }).forEach((file) => { const actual = fs.readFileSync(`${__dirname}/${file}`, 'utf-8'); fs.writeFileSync(`${__dirname}/${file.replace('_actual.json', 'output.json')}`, actual);