From 318240e46a2b0073fa0f593e70ea0bbecd25dfc7 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Wed, 15 Nov 2023 10:34:24 +0100 Subject: [PATCH 1/4] fix: allow member access on directives --- .../compiler/phases/1-parse/state/element.js | 11 +++++++- .../action-with-memberaccess/input.svelte | 1 + .../action-with-memberaccess/output.json | 26 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 packages/svelte/tests/parser-legacy/samples/action-with-memberaccess/input.svelte create mode 100644 packages/svelte/tests/parser-legacy/samples/action-with-memberaccess/output.json diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js index d193aeaeb6ab..e0cbf39ad28f 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -467,7 +467,16 @@ function read_attribute(parser, unique_names) { } if (type) { - const [directive_name, ...modifiers] = name.slice(colon_index + 1).split('|'); + const [base_directive_name, ...modifiers] = name.slice(colon_index + 1).split('|'); + + // this allow for accessing members of an object + const splitted_directive = base_directive_name.split('.'); + + let directive_name = splitted_directive.shift() ?? ''; + + for (let new_piece of splitted_directive) { + directive_name += `['${new_piece}']`; + } if (directive_name === '') { error(start + colon_index + 1, 'empty-directive-name', type); diff --git a/packages/svelte/tests/parser-legacy/samples/action-with-memberaccess/input.svelte b/packages/svelte/tests/parser-legacy/samples/action-with-memberaccess/input.svelte new file mode 100644 index 000000000000..22249d8091f3 --- /dev/null +++ b/packages/svelte/tests/parser-legacy/samples/action-with-memberaccess/input.svelte @@ -0,0 +1 @@ + diff --git a/packages/svelte/tests/parser-legacy/samples/action-with-memberaccess/output.json b/packages/svelte/tests/parser-legacy/samples/action-with-memberaccess/output.json new file mode 100644 index 000000000000..54db4a21e474 --- /dev/null +++ b/packages/svelte/tests/parser-legacy/samples/action-with-memberaccess/output.json @@ -0,0 +1,26 @@ +{ + "html": { + "type": "Fragment", + "start": 0, + "end": 25, + "children": [ + { + "type": "Element", + "start": 0, + "end": 25, + "name": "input", + "attributes": [ + { + "start": 7, + "end": 24, + "type": "Action", + "name": "nested['action']", + "modifiers": [], + "expression": null + } + ], + "children": [] + } + ] + } +} From f763eff069741eac230fcb96b260b99d90bd6b82 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Wed, 15 Nov 2023 10:38:15 +0100 Subject: [PATCH 2/4] Create itchy-lions-wash.md --- .changeset/itchy-lions-wash.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/itchy-lions-wash.md diff --git a/.changeset/itchy-lions-wash.md b/.changeset/itchy-lions-wash.md new file mode 100644 index 000000000000..fdd9085d5ec4 --- /dev/null +++ b/.changeset/itchy-lions-wash.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: allow member access on directives From 565c7f6ffee84fc90c29425d67e40e97a766d409 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Wed, 15 Nov 2023 11:14:02 +0100 Subject: [PATCH 3/4] moved transformation in template.js --- .../compiler/phases/1-parse/state/element.js | 11 +-- .../3-transform/client/visitors/template.js | 37 +++++++++- .../action-with-memberaccess/input.svelte | 1 - .../action-with-memberaccess/output.json | 26 ------- .../directives-with-member-access/_config.js | 3 + .../_expected/client/index.svelte.js | 72 +++++++++++++++++++ .../_expected/server/index.svelte.js | 14 ++++ .../index.svelte | 25 +++++++ 8 files changed, 149 insertions(+), 40 deletions(-) delete mode 100644 packages/svelte/tests/parser-legacy/samples/action-with-memberaccess/input.svelte delete mode 100644 packages/svelte/tests/parser-legacy/samples/action-with-memberaccess/output.json create mode 100644 packages/svelte/tests/snapshot/samples/directives-with-member-access/_config.js create mode 100644 packages/svelte/tests/snapshot/samples/directives-with-member-access/_expected/client/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/directives-with-member-access/_expected/server/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/directives-with-member-access/index.svelte diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js index e0cbf39ad28f..d193aeaeb6ab 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -467,16 +467,7 @@ function read_attribute(parser, unique_names) { } if (type) { - const [base_directive_name, ...modifiers] = name.slice(colon_index + 1).split('|'); - - // this allow for accessing members of an object - const splitted_directive = base_directive_name.split('.'); - - let directive_name = splitted_directive.shift() ?? ''; - - for (let new_piece of splitted_directive) { - directive_name += `['${new_piece}']`; - } + const [directive_name, ...modifiers] = name.slice(colon_index + 1).split('|'); if (directive_name === '') { error(start + colon_index + 1, 'empty-directive-name', type); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index a8ac31593498..56c5c512e00a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -74,6 +74,22 @@ function serialize_style_directives(style_directives, element_id, context, is_at } } +/** + * goes from nested.access to nested['access'] + * @param {string} expression + */ +function member_expression_id_to_literal(expression) { + // this allow for accessing members of an object + const splitted_expression = expression.split('.'); + + let new_expression = splitted_expression.shift() ?? ''; + + for (let new_piece of splitted_expression) { + new_expression += `['${new_piece}']`; + } + return new_expression; +} + /** * Serializes each class directive into something like `$.class_toogle(element, class_name, value)` * and adds it either to init or update, depending on whether or not the value or the attributes are dynamic. @@ -1676,7 +1692,16 @@ export const template_visitors = { ? b.literal(null) : b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression))); - state.init.push(b.stmt(b.call('$.animate', state.node, b.id(node.name), expression))); + state.init.push( + b.stmt( + b.call( + '$.animate', + state.node, + b.id(member_expression_id_to_literal(node.name)), + expression + ) + ) + ); }, ClassDirective(node, { state, next }) { error(node, 'INTERNAL', 'Node should have been handled elsewhere'); @@ -1696,7 +1721,7 @@ export const template_visitors = { b.call( type, state.node, - b.id(node.name), + b.id(member_expression_id_to_literal(node.name)), expression, node.modifiers.includes('global') ? b.true : b.false ) @@ -2417,7 +2442,13 @@ export const template_visitors = { /** @type {import('estree').Expression[]} */ const args = [ state.node, - b.arrow(params, b.call(serialize_get_binding(b.id(node.name), state), ...params)) + b.arrow( + params, + b.call( + serialize_get_binding(b.id(member_expression_id_to_literal(node.name)), state), + ...params + ) + ) ]; if (node.expression) { diff --git a/packages/svelte/tests/parser-legacy/samples/action-with-memberaccess/input.svelte b/packages/svelte/tests/parser-legacy/samples/action-with-memberaccess/input.svelte deleted file mode 100644 index 22249d8091f3..000000000000 --- a/packages/svelte/tests/parser-legacy/samples/action-with-memberaccess/input.svelte +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/svelte/tests/parser-legacy/samples/action-with-memberaccess/output.json b/packages/svelte/tests/parser-legacy/samples/action-with-memberaccess/output.json deleted file mode 100644 index 54db4a21e474..000000000000 --- a/packages/svelte/tests/parser-legacy/samples/action-with-memberaccess/output.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "html": { - "type": "Fragment", - "start": 0, - "end": 25, - "children": [ - { - "type": "Element", - "start": 0, - "end": 25, - "name": "input", - "attributes": [ - { - "start": 7, - "end": 24, - "type": "Action", - "name": "nested['action']", - "modifiers": [], - "expression": null - } - ], - "children": [] - } - ] - } -} diff --git a/packages/svelte/tests/snapshot/samples/directives-with-member-access/_config.js b/packages/svelte/tests/snapshot/samples/directives-with-member-access/_config.js new file mode 100644 index 000000000000..f47bee71df87 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/directives-with-member-access/_config.js @@ -0,0 +1,3 @@ +import { test } from '../../test'; + +export default test({}); diff --git a/packages/svelte/tests/snapshot/samples/directives-with-member-access/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/directives-with-member-access/_expected/client/index.svelte.js new file mode 100644 index 000000000000..394f04975c21 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/directives-with-member-access/_expected/client/index.svelte.js @@ -0,0 +1,72 @@ +// index.svelte (Svelte VERSION) +// Note: compiler output will change before 5.0 is released! +import "svelte/internal/disclose-version"; +import * as $ from "svelte/internal"; + +var frag = $.template(`
`, true); + +export default function Directives_with_member_access($$anchor, $$props) { + $.push($$props, false); + + const one = () => {}; + const nested = { one }; + const evenmore = { nested }; + + /* Init */ + var fragment = $.open_frag($$anchor, true, frag); + var div = $.child_frag(fragment); + var div_1 = $.sibling($.sibling(div)); + var div_2 = $.sibling($.sibling(div_1)); + var div_3 = $.sibling($.sibling(div_2)); + + $.transition(div_3, one, null, false); + + var div_4 = $.sibling($.sibling(div_3)); + + $.transition(div_4, nested['one'], null, false); + + var div_5 = $.sibling($.sibling(div_4)); + + $.transition(div_5, evenmore['nested']['one'], null, false); + + var div_6 = $.sibling($.sibling(div_5)); + + $.animate(div_6, one, null); + + var div_7 = $.sibling($.sibling(div_6)); + + $.animate(div_7, nested['one'], null); + + var div_8 = $.sibling($.sibling(div_7)); + + $.animate(div_8, evenmore['nested']['one'], null); + + var div_9 = $.sibling($.sibling(div_8)); + + $.in(div_9, one, null, false); + + var div_10 = $.sibling($.sibling(div_9)); + + $.in(div_10, nested['one'], null, false); + + var div_11 = $.sibling($.sibling(div_10)); + + $.in(div_11, evenmore['nested']['one'], null, false); + + var div_12 = $.sibling($.sibling(div_11)); + + $.out(div_12, one, null, false); + + var div_13 = $.sibling($.sibling(div_12)); + + $.out(div_13, nested['one'], null, false); + + var div_14 = $.sibling($.sibling(div_13)); + + $.out(div_14, evenmore['nested']['one'], null, false); + $.action(div, $$node => one($$node)); + $.action(div_1, $$node => nested['one']($$node)); + $.action(div_2, $$node => evenmore['nested']['one']($$node)); + $.close_frag($$anchor, fragment); + $.pop(); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/directives-with-member-access/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/directives-with-member-access/_expected/server/index.svelte.js new file mode 100644 index 000000000000..fb164e56cd8a --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/directives-with-member-access/_expected/server/index.svelte.js @@ -0,0 +1,14 @@ +// index.svelte (Svelte VERSION) +// Note: compiler output will change before 5.0 is released! +import * as $ from "svelte/internal/server"; + +export default function Directives_with_member_access($$payload, $$props) { + $.push(false); + + const one = () => {}; + const nested = { one }; + const evenmore = { nested }; + + $$payload.out += `
`; + $.pop(); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/directives-with-member-access/index.svelte b/packages/svelte/tests/snapshot/samples/directives-with-member-access/index.svelte new file mode 100644 index 000000000000..00efd33049cb --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/directives-with-member-access/index.svelte @@ -0,0 +1,25 @@ + + +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
\ No newline at end of file From 5cf47d16bef2b3ad7808b6f941e5f39f1dd98c6a Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Wed, 15 Nov 2023 11:38:04 +0100 Subject: [PATCH 4/4] add `with-string` test and `skip_if_ssr` to snapshots --- .../directives-with-member-access/_config.js | 4 +- .../_expected/client/index.svelte.js | 40 ++++++++++++++++++- .../_expected/server/index.svelte.js | 14 ------- .../index.svelte | 19 ++++++++- packages/svelte/tests/snapshot/test.ts | 5 ++- 5 files changed, 62 insertions(+), 20 deletions(-) delete mode 100644 packages/svelte/tests/snapshot/samples/directives-with-member-access/_expected/server/index.svelte.js diff --git a/packages/svelte/tests/snapshot/samples/directives-with-member-access/_config.js b/packages/svelte/tests/snapshot/samples/directives-with-member-access/_config.js index f47bee71df87..43e2501b6ef0 100644 --- a/packages/svelte/tests/snapshot/samples/directives-with-member-access/_config.js +++ b/packages/svelte/tests/snapshot/samples/directives-with-member-access/_config.js @@ -1,3 +1,5 @@ import { test } from '../../test'; -export default test({}); +export default test({ + skip_if_ssr: true +}); diff --git a/packages/svelte/tests/snapshot/samples/directives-with-member-access/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/directives-with-member-access/_expected/client/index.svelte.js index 394f04975c21..3c60a11a7688 100644 --- a/packages/svelte/tests/snapshot/samples/directives-with-member-access/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/directives-with-member-access/_expected/client/index.svelte.js @@ -3,13 +3,13 @@ import "svelte/internal/disclose-version"; import * as $ from "svelte/internal"; -var frag = $.template(`
`, true); +var frag = $.template(`
`, true); export default function Directives_with_member_access($$anchor, $$props) { $.push($$props, false); const one = () => {}; - const nested = { one }; + const nested = { one, "with-string": one }; const evenmore = { nested }; /* Init */ @@ -64,9 +64,45 @@ export default function Directives_with_member_access($$anchor, $$props) { var div_14 = $.sibling($.sibling(div_13)); $.out(div_14, evenmore['nested']['one'], null, false); + + var div_15 = $.sibling($.sibling(div_14)); + var div_16 = $.sibling($.sibling(div_15)); + var div_17 = $.sibling($.sibling(div_16)); + + $.transition(div_17, nested['with-string'], null, false); + + var div_18 = $.sibling($.sibling(div_17)); + + $.transition(div_18, evenmore['nested']['with-string'], null, false); + + var div_19 = $.sibling($.sibling(div_18)); + + $.animate(div_19, nested['with-string'], null); + + var div_20 = $.sibling($.sibling(div_19)); + + $.animate(div_20, evenmore['nested']['with-string'], null); + + var div_21 = $.sibling($.sibling(div_20)); + + $.in(div_21, nested['with-string'], null, false); + + var div_22 = $.sibling($.sibling(div_21)); + + $.in(div_22, evenmore['nested']['with-string'], null, false); + + var div_23 = $.sibling($.sibling(div_22)); + + $.out(div_23, nested['with-string'], null, false); + + var div_24 = $.sibling($.sibling(div_23)); + + $.out(div_24, evenmore['nested']['with-string'], null, false); $.action(div, $$node => one($$node)); $.action(div_1, $$node => nested['one']($$node)); $.action(div_2, $$node => evenmore['nested']['one']($$node)); + $.action(div_15, $$node => nested['with-string']($$node)); + $.action(div_16, $$node => evenmore['nested']['with-string']($$node)); $.close_frag($$anchor, fragment); $.pop(); } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/directives-with-member-access/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/directives-with-member-access/_expected/server/index.svelte.js deleted file mode 100644 index fb164e56cd8a..000000000000 --- a/packages/svelte/tests/snapshot/samples/directives-with-member-access/_expected/server/index.svelte.js +++ /dev/null @@ -1,14 +0,0 @@ -// index.svelte (Svelte VERSION) -// Note: compiler output will change before 5.0 is released! -import * as $ from "svelte/internal/server"; - -export default function Directives_with_member_access($$payload, $$props) { - $.push(false); - - const one = () => {}; - const nested = { one }; - const evenmore = { nested }; - - $$payload.out += `
`; - $.pop(); -} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/directives-with-member-access/index.svelte b/packages/svelte/tests/snapshot/samples/directives-with-member-access/index.svelte index 00efd33049cb..b5487aae33cb 100644 --- a/packages/svelte/tests/snapshot/samples/directives-with-member-access/index.svelte +++ b/packages/svelte/tests/snapshot/samples/directives-with-member-access/index.svelte @@ -1,6 +1,6 @@ @@ -22,4 +22,19 @@
-
\ No newline at end of file +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
\ No newline at end of file diff --git a/packages/svelte/tests/snapshot/test.ts b/packages/svelte/tests/snapshot/test.ts index 26f3a1a1b7ee..495af0df443f 100644 --- a/packages/svelte/tests/snapshot/test.ts +++ b/packages/svelte/tests/snapshot/test.ts @@ -7,11 +7,14 @@ import { VERSION } from 'svelte/compiler'; interface SnapshotTest extends BaseTest { compileOptions?: Partial; + skip_if_ssr?: boolean; } const { test, run } = suite(async (config, cwd) => { compile_directory(cwd, 'client', config.compileOptions); - compile_directory(cwd, 'server', config.compileOptions); + if (!config.skip_if_ssr) { + compile_directory(cwd, 'server', config.compileOptions); + } // run `UPDATE_SNAPSHOTS=true pnpm test snapshot` to update snapshot tests if (process.env.UPDATE_SNAPSHOTS) {