fix: rework directive name handling (#9470)

* move snapshot test to a runtime test

* handle dynamic cases

* huh

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/9475/head
Rich Harris 1 year ago committed by GitHub
parent 73e8820fe7
commit d749685b0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -34,6 +34,7 @@ import {
EACH_ITEM_REACTIVE, EACH_ITEM_REACTIVE,
EACH_KEYED EACH_KEYED
} from '../../../../../constants.js'; } from '../../../../../constants.js';
import { regex_is_valid_identifier } from '../../../patterns.js';
/** /**
* Serializes each style directive into something like `$.style(element, style_property, value)` * Serializes each style directive into something like `$.style(element, style_property, value)`
@ -75,19 +76,24 @@ function serialize_style_directives(style_directives, element_id, context, is_at
} }
/** /**
* goes from nested.access to nested['access'] * For unfortunate legacy reasons, directive names can look like this `use:a.b-c`
* @param {string} expression * This turns that string into a member expression
* @param {string} name
*/ */
function member_expression_id_to_literal(expression) { function parse_directive_name(name) {
// this allow for accessing members of an object // this allow for accessing members of an object
const splitted_expression = expression.split('.'); const parts = name.split('.');
let part = /** @type {string} */ (parts.shift());
let new_expression = splitted_expression.shift() ?? ''; /** @type {import('estree').Identifier | import('estree').MemberExpression} */
let expression = b.id(part);
for (let new_piece of splitted_expression) { while ((part = /** @type {string} */ (parts.shift()))) {
new_expression += `['${new_piece}']`; const computed = !regex_is_valid_identifier.test(part);
expression = b.member(expression, computed ? b.literal(part) : b.id(part), computed);
} }
return new_expression;
return expression;
} }
/** /**
@ -1697,7 +1703,7 @@ export const template_visitors = {
b.call( b.call(
'$.animate', '$.animate',
state.node, state.node,
b.id(member_expression_id_to_literal(node.name)), /** @type {import('estree').Expression} */ (visit(parse_directive_name(node.name))),
expression expression
) )
) )
@ -1721,7 +1727,7 @@ export const template_visitors = {
b.call( b.call(
type, type,
state.node, state.node,
b.id(member_expression_id_to_literal(node.name)), /** @type {import('estree').Expression} */ (visit(parse_directive_name(node.name))),
expression, expression,
node.modifiers.includes('global') ? b.true : b.false node.modifiers.includes('global') ? b.true : b.false
) )
@ -2445,7 +2451,7 @@ export const template_visitors = {
b.arrow( b.arrow(
params, params,
b.call( b.call(
serialize_get_binding(b.id(member_expression_id_to_literal(node.name)), state), /** @type {import('estree').Expression} */ (visit(parse_directive_name(node.name))),
...params ...params
) )
) )

@ -0,0 +1,5 @@
import { test } from '../../test';
// no need to compare the rendered HTML — we only care
// that the generated code is valid
export default test({});

@ -0,0 +1,19 @@
<script>
/**
* @param {Element} [node]
* @param {any} [options]
*/
const fn = (node, options) => ({});
let a = { b: { 'c-d': fn } };
let directive = $derived(a);
</script>
<!-- these will yield TypeScript errors, because it looks like e.g. `nested.with - string`,
in other words a number. Relatedly, people should not do this. It is stupid. -->
<div use:directive.b.c-d />
<div transition:directive.b.c-d />
<div animate:directive.b.c-d />
<div in:directive.b.c-d />
<div out:directive.b.c-d />

@ -1,5 +0,0 @@
import { test } from '../../test';
export default test({
skip_if_ssr: true
});

@ -1,108 +0,0 @@
// 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(`<div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div>`, true);
export default function Directives_with_member_access($$anchor, $$props) {
$.push($$props, false);
const one = () => {};
const nested = { one, "with-string": 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);
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();
}

@ -1,40 +0,0 @@
<script>
const one = ()=>{};
const nested = {one, "with-string": one};
const evenmore = {nested};
</script>
<div use:one />
<div use:nested.one />
<div use:evenmore.nested.one />
<div transition:one />
<div transition:nested.one />
<div transition:evenmore.nested.one />
<div animate:one />
<div animate:nested.one />
<div animate:evenmore.nested.one />
<div in:one />
<div in:nested.one />
<div in:evenmore.nested.one />
<div out:one />
<div out:nested.one />
<div out:evenmore.nested.one />
<div use:nested.with-string />
<div use:evenmore.nested.with-string />
<div transition:nested.with-string />
<div transition:evenmore.nested.with-string />
<div animate:nested.with-string />
<div animate:evenmore.nested.with-string />
<div in:nested.with-string />
<div in:evenmore.nested.with-string />
<div out:nested.with-string />
<div out:evenmore.nested.with-string />

@ -7,14 +7,11 @@ import { VERSION } from 'svelte/compiler';
interface SnapshotTest extends BaseTest { interface SnapshotTest extends BaseTest {
compileOptions?: Partial<import('#compiler').CompileOptions>; compileOptions?: Partial<import('#compiler').CompileOptions>;
skip_if_ssr?: boolean;
} }
const { test, run } = suite<SnapshotTest>(async (config, cwd) => { const { test, run } = suite<SnapshotTest>(async (config, cwd) => {
compile_directory(cwd, 'client', config.compileOptions); compile_directory(cwd, 'client', config.compileOptions);
if (!config.skip_if_ssr) { compile_directory(cwd, 'server', config.compileOptions);
compile_directory(cwd, 'server', config.compileOptions);
}
// run `UPDATE_SNAPSHOTS=true pnpm test snapshot` to update snapshot tests // run `UPDATE_SNAPSHOTS=true pnpm test snapshot` to update snapshot tests
if (process.env.UPDATE_SNAPSHOTS) { if (process.env.UPDATE_SNAPSHOTS) {

Loading…
Cancel
Save