use unified reactivity metadata

pull/12644/head
Rich Harris 2 years ago
parent 7c9dea4b65
commit 7dc674525e

@ -9,7 +9,7 @@ import { decode_character_references } from '../utils/html.js';
import * as e from '../../../errors.js'; import * as e from '../../../errors.js';
import * as w from '../../../warnings.js'; import * as w from '../../../warnings.js';
import { create_fragment } from '../utils/create.js'; import { create_fragment } from '../utils/create.js';
import { create_attribute } from '../../nodes.js'; import { create_attribute, create_expression_metadata } from '../../nodes.js';
import { get_attribute_expression, is_expression_attribute } from '../../../utils/ast.js'; import { get_attribute_expression, is_expression_attribute } from '../../../utils/ast.js';
import { closing_tag_omitted } from '../../../../html-tree-validation.js'; import { closing_tag_omitted } from '../../../../html-tree-validation.js';
@ -127,7 +127,7 @@ export default function element(parser) {
const type = meta_tags.has(name) const type = meta_tags.has(name)
? meta_tags.get(name) ? meta_tags.get(name)
: regex_capital_letter.test(name[0]) || name === 'svelte:self' || name === 'svelte:component' : regex_capital_letter.test(name[0])
? 'Component' ? 'Component'
: name === 'title' && parent_is_head(parser.stack) : name === 'title' && parent_is_head(parser.stack)
? 'TitleElement' ? 'TitleElement'
@ -140,7 +140,7 @@ export default function element(parser) {
const element = const element =
type === 'RegularElement' type === 'RegularElement'
? { ? {
type: type, type,
start, start,
end: -1, end: -1,
name, name,
@ -163,7 +163,7 @@ export default function element(parser) {
fragment: create_fragment(true), fragment: create_fragment(true),
parent: null, parent: null,
metadata: { metadata: {
svg: false expression: create_expression_metadata()
} }
}); });
@ -507,10 +507,8 @@ function read_attribute(parser) {
end: parser.index, end: parser.index,
expression, expression,
parent: null, parent: null,
effect: { metadata: {
is_dynamic: false, expression: create_expression_metadata()
has_call_expression: false,
bindings: new Set()
} }
}; };
@ -538,10 +536,8 @@ function read_attribute(parser) {
name name
}, },
parent: null, parent: null,
effect: { metadata: {
has_call_expression: false, expression: create_expression_metadata()
is_dynamic: false,
bindings: new Set()
} }
}; };
@ -586,7 +582,7 @@ function read_attribute(parser) {
value, value,
parent: null, parent: null,
metadata: { metadata: {
dynamic: false expression: create_expression_metadata()
} }
}; };
} }
@ -618,17 +614,10 @@ function read_attribute(parser) {
modifiers, modifiers,
expression, expression,
metadata: { metadata: {
dynamic: false, expression: create_expression_metadata()
contains_call_expression: false
} }
}; };
if (directive.type === 'ClassDirective') {
directive.metadata = {
dynamic: false
};
}
if (directive.type === 'TransitionDirective') { if (directive.type === 'TransitionDirective') {
const direction = name.slice(0, colon_index); const direction = name.slice(0, colon_index);
directive.intro = direction === 'in' || direction === 'transition'; directive.intro = direction === 'in' || direction === 'transition';
@ -791,8 +780,7 @@ function read_sequence(parser, done, location) {
expression, expression,
parent: null, parent: null,
metadata: { metadata: {
contains_call_expression: false, expression: create_expression_metadata()
dynamic: false
} }
}; };

@ -7,6 +7,7 @@ import * as e from '../../../errors.js';
import { create_fragment } from '../utils/create.js'; import { create_fragment } from '../utils/create.js';
import { walk } from 'zimmerframe'; import { walk } from 'zimmerframe';
import { parse_expression_at } from '../acorn.js'; import { parse_expression_at } from '../acorn.js';
import { create_expression_metadata } from '../../nodes.js';
const regex_whitespace_with_closing_curly_brace = /^\s*}/; const regex_whitespace_with_closing_curly_brace = /^\s*}/;
@ -39,8 +40,7 @@ export default function tag(parser) {
end: parser.index, end: parser.index,
expression, expression,
metadata: { metadata: {
contains_call_expression: false, expression: create_expression_metadata()
dynamic: false
} }
}); });
} }

@ -1178,7 +1178,7 @@ const common_visitors = {
context.next(); context.next();
node.metadata.dynamic = get_attribute_chunks(node.value).some((chunk) => { node.metadata.expression.has_state = get_attribute_chunks(node.value).some((chunk) => {
if (chunk.type !== 'ExpressionTag') { if (chunk.type !== 'ExpressionTag') {
return false; return false;
} }
@ -1190,7 +1190,7 @@ const common_visitors = {
return false; return false;
} }
return chunk.metadata.dynamic || chunk.metadata.contains_call_expression; return chunk.metadata.expression.has_state;
}); });
if (is_event_attribute(node)) { if (is_event_attribute(node)) {
@ -1211,10 +1211,10 @@ const common_visitors = {
} }
}, },
ClassDirective(node, context) { ClassDirective(node, context) {
context.next({ ...context.state, expression: node }); context.next({ ...context.state, expression: node.metadata.expression });
}, },
SpreadAttribute(node, context) { SpreadAttribute(node, context) {
context.next({ ...context.state, expression: node }); context.next({ ...context.state, expression: node.metadata.expression });
}, },
SlotElement(node, context) { SlotElement(node, context) {
let name = 'default'; let name = 'default';
@ -1230,17 +1230,17 @@ const common_visitors = {
if (node.value === true) { if (node.value === true) {
const binding = context.state.scope.get(node.name); const binding = context.state.scope.get(node.name);
if (binding?.kind !== 'normal') { if (binding?.kind !== 'normal') {
node.metadata.dynamic = true; node.metadata.expression.has_state = true;
} }
} else { } else {
context.next(); context.next();
node.metadata.dynamic = get_attribute_chunks(node.value).some( node.metadata.expression.has_state = get_attribute_chunks(node.value).some(
(node) => node.type === 'ExpressionTag' && node.metadata.dynamic (node) => node.type === 'ExpressionTag' && node.metadata.expression.has_state
); );
} }
}, },
ExpressionTag(node, context) { ExpressionTag(node, context) {
context.next({ ...context.state, expression: node }); context.next({ ...context.state, expression: node.metadata.expression });
}, },
Identifier(node, context) { Identifier(node, context) {
const parent = /** @type {Node} */ (context.path.at(-1)); const parent = /** @type {Node} */ (context.path.at(-1));
@ -1264,7 +1264,7 @@ const common_visitors = {
// if no binding, means some global variable // if no binding, means some global variable
if (binding && binding.kind !== 'normal') { if (binding && binding.kind !== 'normal') {
if (context.state.expression) { if (context.state.expression) {
context.state.expression.metadata.dynamic = true; context.state.expression.has_state = true;
} }
// TODO it would be better to just bail out when we hit the ExportSpecifier node but that's // TODO it would be better to just bail out when we hit the ExportSpecifier node but that's
@ -1297,13 +1297,10 @@ const common_visitors = {
}, },
CallExpression(node, context) { CallExpression(node, context) {
const { expression, render_tag } = context.state; const { expression, render_tag } = context.state;
if (
(expression?.type === 'ExpressionTag' || if (expression && !is_known_safe_call(node, context)) {
expression?.type === 'SpreadAttribute' || expression.has_call = true;
expression?.type === 'OnDirective') && expression.has_state = true;
!is_known_safe_call(node, context)
) {
expression.metadata.contains_call_expression = true;
} }
if (render_tag) { if (render_tag) {
@ -1354,7 +1351,7 @@ const common_visitors = {
}, },
MemberExpression(node, context) { MemberExpression(node, context) {
if (context.state.expression) { if (context.state.expression) {
context.state.expression.metadata.dynamic = true; context.state.expression.has_state = true;
} }
if (!is_safe_identifier(node, context.state.scope)) { if (!is_safe_identifier(node, context.state.scope)) {
@ -1368,7 +1365,7 @@ const common_visitors = {
if (parent?.type === 'SvelteElement' || parent?.type === 'RegularElement') { if (parent?.type === 'SvelteElement' || parent?.type === 'RegularElement') {
state.analysis.event_directive_node ??= node; state.analysis.event_directive_node ??= node;
} }
next({ ...state, expression: node }); next({ ...state, expression: node.metadata.expression });
}, },
BindDirective(node, context) { BindDirective(node, context) {
let i = context.path.length; let i = context.path.length;
@ -1543,7 +1540,7 @@ const common_visitors = {
node.name.includes('.') ? node.name.slice(0, node.name.indexOf('.')) : node.name node.name.includes('.') ? node.name.slice(0, node.name.indexOf('.')) : node.name
); );
node.metadata.dynamic = binding !== null && binding.kind !== 'normal'; node.metadata.expression.has_state = binding !== null && binding.kind !== 'normal';
}, },
RenderTag(node, context) { RenderTag(node, context) {
context.next({ ...context.state, render_tag: node }); context.next({ ...context.state, render_tag: node });

@ -2,6 +2,7 @@ import type { Scope } from '../scope.js';
import type { ComponentAnalysis, ReactiveStatement } from '../types.js'; import type { ComponentAnalysis, ReactiveStatement } from '../types.js';
import type { import type {
ClassDirective, ClassDirective,
ExpressionMetadata,
ExpressionTag, ExpressionTag,
OnDirective, OnDirective,
RenderTag, RenderTag,
@ -21,7 +22,7 @@ export interface AnalysisState {
/** Which slots the current parent component has */ /** Which slots the current parent component has */
component_slots: Set<string>; component_slots: Set<string>;
/** The current {expression}, if any */ /** The current {expression}, if any */
expression: ExpressionTag | ClassDirective | OnDirective | SpreadAttribute | null; expression: ExpressionMetadata | null;
/** The current {@render ...} tag, if any */ /** The current {@render ...} tag, if any */
render_tag: null | RenderTag; render_tag: null | RenderTag;
private_derived_state: string[]; private_derived_state: string[];

@ -1,5 +1,5 @@
/** @import { BlockStatement, CallExpression, Expression, ExpressionStatement, Identifier, Literal, MemberExpression, ObjectExpression, Pattern, Property, Statement, Super, TemplateElement, TemplateLiteral } from 'estree' */ /** @import { BlockStatement, CallExpression, Expression, ExpressionStatement, Identifier, Literal, MemberExpression, ObjectExpression, Pattern, Property, Statement, Super, TemplateElement, TemplateLiteral } from 'estree' */
/** @import { Attribute, BindDirective, Binding, ClassDirective, Component, DelegatedEvent, EachBlock, ExpressionTag, Namespace, OnDirective, RegularElement, SpreadAttribute, StyleDirective, SvelteComponent, SvelteElement, SvelteNode, SvelteSelf, TemplateNode, Text } from '#compiler' */ /** @import { Attribute, BindDirective, Binding, ClassDirective, Component, DelegatedEvent, EachBlock, ExpressionMetadata, ExpressionTag, Namespace, OnDirective, RegularElement, SpreadAttribute, StyleDirective, SvelteComponent, SvelteElement, SvelteNode, SvelteSelf, TemplateNode, Text } from '#compiler' */
/** @import { SourceLocation } from '#shared' */ /** @import { SourceLocation } from '#shared' */
/** @import { Scope } from '../../../scope.js' */ /** @import { Scope } from '../../../scope.js' */
/** @import { ComponentClientTransformState, ComponentContext, ComponentVisitors } from '../types.js' */ /** @import { ComponentClientTransformState, ComponentContext, ComponentVisitors } from '../types.js' */
@ -104,12 +104,16 @@ function serialize_style_directives(style_directives, element_id, context, is_at
); );
const contains_call_expression = get_attribute_chunks(directive.value).some( const contains_call_expression = get_attribute_chunks(directive.value).some(
(v) => v.type === 'ExpressionTag' && v.metadata.contains_call_expression (v) => v.type === 'ExpressionTag' && v.metadata.expression.has_call
); );
if (!is_attributes_reactive && contains_call_expression) { if (!is_attributes_reactive && contains_call_expression) {
state.init.push(serialize_update(update)); state.init.push(serialize_update(update));
} else if (is_attributes_reactive || directive.metadata.dynamic || contains_call_expression) { } else if (
is_attributes_reactive ||
directive.metadata.expression.has_state ||
contains_call_expression
) {
state.update.push(update); state.update.push(update);
} else { } else {
state.init.push(update); state.init.push(update);
@ -155,7 +159,11 @@ function serialize_class_directives(class_directives, element_id, context, is_at
if (!is_attributes_reactive && contains_call_expression) { if (!is_attributes_reactive && contains_call_expression) {
state.init.push(serialize_update(update)); state.init.push(serialize_update(update));
} else if (is_attributes_reactive || directive.metadata.dynamic || contains_call_expression) { } else if (
is_attributes_reactive ||
directive.metadata.expression.has_state ||
contains_call_expression
) {
state.update.push(update); state.update.push(update);
} else { } else {
state.init.push(update); state.init.push(update);
@ -306,7 +314,7 @@ function serialize_element_spread_attributes(
} }
needs_isolation ||= needs_isolation ||=
attribute.type === 'SpreadAttribute' && attribute.metadata.contains_call_expression; attribute.type === 'SpreadAttribute' && attribute.metadata.expression.has_call;
} }
const lowercase_attributes = const lowercase_attributes =
@ -405,11 +413,11 @@ function serialize_dynamic_element_attributes(attributes, context, element_id) {
} }
is_reactive ||= is_reactive ||=
attribute.metadata.dynamic || attribute.metadata.expression.has_state ||
// objects could contain reactive getters -> play it safe and always assume spread attributes are reactive // objects could contain reactive getters -> play it safe and always assume spread attributes are reactive
attribute.type === 'SpreadAttribute'; attribute.type === 'SpreadAttribute';
needs_isolation ||= needs_isolation ||=
attribute.type === 'SpreadAttribute' && attribute.metadata.contains_call_expression; attribute.type === 'SpreadAttribute' && attribute.metadata.expression.has_call;
} }
if (needs_isolation || is_reactive) { if (needs_isolation || is_reactive) {
@ -492,7 +500,7 @@ function serialize_element_attribute_update_assignment(element, node_id, attribu
if (context.state.metadata.namespace === 'foreign') { if (context.state.metadata.namespace === 'foreign') {
const statement = b.stmt(b.call('$.set_attribute', node_id, b.literal(name), value)); const statement = b.stmt(b.call('$.set_attribute', node_id, b.literal(name), value));
if (attribute.metadata.dynamic) { if (attribute.metadata.expression.has_state) {
const id = state.scope.generate(`${node_id.name}_${name}`); const id = state.scope.generate(`${node_id.name}_${name}`);
serialize_update_assignment(state, id, undefined, value, statement); serialize_update_assignment(state, id, undefined, value, statement);
return true; return true;
@ -529,7 +537,7 @@ function serialize_element_attribute_update_assignment(element, node_id, attribu
update = b.stmt(b.call(callee, node_id, b.literal(name), value)); update = b.stmt(b.call(callee, node_id, b.literal(name), value));
} }
if (attribute.metadata.dynamic) { if (attribute.metadata.expression.has_state) {
if (contains_call_expression) { if (contains_call_expression) {
state.init.push(serialize_update(update)); state.init.push(serialize_update(update));
} else { } else {
@ -556,7 +564,7 @@ function serialize_custom_element_attribute_update_assignment(node_id, attribute
const update = b.stmt(b.call('$.set_custom_element_data', node_id, b.literal(name), value)); const update = b.stmt(b.call('$.set_custom_element_data', node_id, b.literal(name), value));
if (attribute.metadata.dynamic) { if (attribute.metadata.expression.has_state) {
if (contains_call_expression) { if (contains_call_expression) {
state.init.push(serialize_update(update)); state.init.push(serialize_update(update));
} else { } else {
@ -592,7 +600,7 @@ function serialize_element_special_value_attribute(element, node_id, attribute,
value value
) )
); );
const is_reactive = attribute.metadata.dynamic; const is_reactive = attribute.metadata.expression.has_state;
const is_select_with_value = const is_select_with_value =
// attribute.metadata.dynamic would give false negatives because even if the value does not change, // attribute.metadata.dynamic would give false negatives because even if the value does not change,
// the inner options could still change, so we need to always treat it as reactive // the inner options could still change, so we need to always treat it as reactive
@ -723,10 +731,10 @@ function serialize_inline_component(node, component_name, context, anchor = cont
events[attribute.name].push(handler); events[attribute.name].push(handler);
} else if (attribute.type === 'SpreadAttribute') { } else if (attribute.type === 'SpreadAttribute') {
const expression = /** @type {Expression} */ (context.visit(attribute)); const expression = /** @type {Expression} */ (context.visit(attribute));
if (attribute.metadata.dynamic) { if (attribute.metadata.expression.has_state) {
let value = expression; let value = expression;
if (attribute.metadata.contains_call_expression) { if (attribute.metadata.expression.has_call) {
const id = b.id(context.state.scope.generate('spread_element')); const id = b.id(context.state.scope.generate('spread_element'));
context.state.init.push(b.var(id, b.call('$.derived', b.thunk(value)))); context.state.init.push(b.var(id, b.call('$.derived', b.thunk(value))));
value = b.call('$.get', id); value = b.call('$.get', id);
@ -754,7 +762,7 @@ function serialize_inline_component(node, component_name, context, anchor = cont
const [, value] = serialize_attribute_value(attribute.value, context); const [, value] = serialize_attribute_value(attribute.value, context);
if (attribute.metadata.dynamic) { if (attribute.metadata.expression.has_state) {
let arg = value; let arg = value;
// When we have a non-simple computation, anything other than an Identifier or Member expression, // When we have a non-simple computation, anything other than an Identifier or Member expression,
@ -1118,7 +1126,7 @@ function serialize_render_stmt(update) {
/** /**
* Serializes the event handler function of the `on:` directive * Serializes the event handler function of the `on:` directive
* @param {Pick<OnDirective, 'name' | 'modifiers' | 'expression'>} node * @param {Pick<OnDirective, 'name' | 'modifiers' | 'expression'>} node
* @param {null | { contains_call_expression: boolean; dynamic: boolean; } | null} metadata * @param {null | ExpressionMetadata} metadata
* @param {ComponentContext} context * @param {ComponentContext} context
*/ */
function serialize_event_handler(node, metadata, { state, visit }) { function serialize_event_handler(node, metadata, { state, visit }) {
@ -1145,7 +1153,7 @@ function serialize_event_handler(node, metadata, { state, visit }) {
); );
if ( if (
metadata?.contains_call_expression && metadata?.has_call &&
!( !(
(handler.type === 'ArrowFunctionExpression' || handler.type === 'FunctionExpression') && (handler.type === 'ArrowFunctionExpression' || handler.type === 'FunctionExpression') &&
handler.metadata.hoistable handler.metadata.hoistable
@ -1227,7 +1235,7 @@ function serialize_event_handler(node, metadata, { state, visit }) {
/** /**
* Serializes an event handler function of the `on:` directive or an attribute starting with `on` * Serializes an event handler function of the `on:` directive or an attribute starting with `on`
* @param {{name: string;modifiers: string[];expression: Expression | null;delegated?: DelegatedEvent | null;}} node * @param {{name: string;modifiers: string[];expression: Expression | null;delegated?: DelegatedEvent | null;}} node
* @param {null | { contains_call_expression: boolean; dynamic: boolean; }} metadata * @param {null | ExpressionMetadata} metadata
* @param {ComponentContext} context * @param {ComponentContext} context
*/ */
function serialize_event(node, metadata, context) { function serialize_event(node, metadata, context) {
@ -1353,7 +1361,9 @@ function serialize_event_attribute(node, context) {
modifiers, modifiers,
delegated: node.metadata.delegated delegated: node.metadata.delegated
}, },
!Array.isArray(node.value) && node.value?.type === 'ExpressionTag' ? node.value.metadata : null, !Array.isArray(node.value) && node.value?.type === 'ExpressionTag'
? node.value.metadata.expression
: null,
context context
); );
} }
@ -1397,9 +1407,9 @@ function process_children(nodes, expression, is_element, { visit, state }) {
b.call('$.set_text', text_id, /** @type {Expression} */ (visit(node.expression, state))) b.call('$.set_text', text_id, /** @type {Expression} */ (visit(node.expression, state)))
); );
if (node.metadata.contains_call_expression && !within_bound_contenteditable) { if (node.metadata.expression.has_call && !within_bound_contenteditable) {
state.init.push(serialize_update(update)); state.init.push(serialize_update(update));
} else if (node.metadata.dynamic && !within_bound_contenteditable) { } else if (node.metadata.expression.has_state && !within_bound_contenteditable) {
state.update.push(update); state.update.push(update);
} else { } else {
state.init.push( state.init.push(
@ -1427,7 +1437,9 @@ function process_children(nodes, expression, is_element, { visit, state }) {
if (contains_call_expression && !within_bound_contenteditable) { if (contains_call_expression && !within_bound_contenteditable) {
state.init.push(serialize_update(update)); state.init.push(serialize_update(update));
} else if ( } else if (
sequence.some((node) => node.type === 'ExpressionTag' && node.metadata.dynamic) && sequence.some(
(node) => node.type === 'ExpressionTag' && node.metadata.expression.has_state
) &&
!within_bound_contenteditable !within_bound_contenteditable
) { ) {
state.update.push(update); state.update.push(update);
@ -1528,7 +1540,7 @@ function serialize_attribute_value(value, context) {
} }
return [ return [
chunk.metadata.contains_call_expression, chunk.metadata.expression.has_call,
/** @type {Expression} */ (context.visit(chunk.expression)) /** @type {Expression} */ (context.visit(chunk.expression))
]; ];
} }
@ -1555,7 +1567,7 @@ function serialize_template_literal(values, visit, state) {
for (let i = 0; i < values.length; i++) { for (let i = 0; i < values.length; i++) {
const node = values[i]; const node = values[i];
if (node.type === 'ExpressionTag' && node.metadata.contains_call_expression) { if (node.type === 'ExpressionTag' && node.metadata.expression.has_call) {
if (contains_call_expression) { if (contains_call_expression) {
contains_multiple_call_expression = true; contains_multiple_call_expression = true;
} }
@ -2851,7 +2863,7 @@ export const template_visitors = {
context.next({ ...context.state, in_constructor: false }); context.next({ ...context.state, in_constructor: false });
}, },
OnDirective(node, context) { OnDirective(node, context) {
serialize_event(node, node.metadata, context); serialize_event(node, node.metadata.expression, context);
}, },
UseDirective(node, { state, next, visit }) { UseDirective(node, { state, next, visit }) {
const params = [b.id('$$node')]; const params = [b.id('$$node')];
@ -3115,7 +3127,7 @@ export const template_visitors = {
} }
}, },
Component(node, context) { Component(node, context) {
if (node.metadata.dynamic) { if (node.metadata.expression.has_state) {
// Handle dynamic references to what seems like static inline components // Handle dynamic references to what seems like static inline components
const component = serialize_inline_component(node, '$$component', context, b.id('$$anchor')); const component = serialize_inline_component(node, '$$component', context, b.id('$$anchor'));
context.state.init.push( context.state.init.push(
@ -3234,7 +3246,7 @@ export const template_visitors = {
name = value; name = value;
is_default = false; is_default = false;
} else if (attribute.name !== 'slot') { } else if (attribute.name !== 'slot') {
if (attribute.metadata.dynamic) { if (attribute.metadata.expression.has_state) {
props.push(b.get(attribute.name, [b.return(value)])); props.push(b.get(attribute.name, [b.return(value)]));
} else { } else {
props.push(b.init(attribute.name, value)); props.push(b.init(attribute.name, value));

@ -233,7 +233,8 @@ export function serialize_inline_component(node, expression, context) {
} }
const dynamic = const dynamic =
node.type === 'SvelteComponent' || (node.type === 'Component' && node.metadata.dynamic); node.type === 'SvelteComponent' ||
(node.type === 'Component' && node.metadata.expression.has_state);
if (custom_css_props.length > 0) { if (custom_css_props.length > 0) {
context.state.template.push( context.state.template.push(

@ -12,7 +12,11 @@ import {
LoadErrorElements, LoadErrorElements,
WhitespaceInsensitiveAttributes WhitespaceInsensitiveAttributes
} from '../../../../constants.js'; } from '../../../../constants.js';
import { create_attribute, is_custom_element_node } from '../../../../nodes.js'; import {
create_attribute,
create_expression_metadata,
is_custom_element_node
} from '../../../../nodes.js';
import { regex_starts_with_newline } from '../../../../patterns.js'; import { regex_starts_with_newline } from '../../../../patterns.js';
import * as b from '../../../../../utils/builders.js'; import * as b from '../../../../../utils/builders.js';
import { import {
@ -142,8 +146,7 @@ export function serialize_element_attributes(node, context) {
serialize_attribute_value(value_attribute.value, context) serialize_attribute_value(value_attribute.value, context)
), ),
metadata: { metadata: {
contains_call_expression: false, expression: create_expression_metadata()
dynamic: false
} }
} }
]) ])
@ -158,8 +161,7 @@ export function serialize_element_attributes(node, context) {
parent: attribute, parent: attribute,
expression: attribute.expression, expression: attribute.expression,
metadata: { metadata: {
contains_call_expression: false, expression: create_expression_metadata()
dynamic: false
} }
} }
]) ])
@ -399,7 +401,9 @@ function serialize_class_directives(class_directives, class_attribute) {
), ),
b.literal(' ') b.literal(' ')
), ),
metadata: { contains_call_expression: false, dynamic: false } metadata: {
expression: create_expression_metadata()
}
}); });
class_attribute.value = chunks; class_attribute.value = chunks;

@ -45,8 +45,19 @@ export function create_attribute(name, start, end, value) {
value, value,
parent: null, parent: null,
metadata: { metadata: {
dynamic: false, expression: create_expression_metadata(),
delegated: null delegated: null
} }
}; };
} }
/**
* @returns {Compiler.ExpressionMetadata}
*/
export function create_expression_metadata() {
return {
dependencies: new Set(),
has_state: false,
has_call: false
};
}

@ -317,8 +317,11 @@ export interface Binding {
} }
export interface ExpressionMetadata { export interface ExpressionMetadata {
/** All the bindings that are referenced inside this expression */
dependencies: Set<Binding>; dependencies: Set<Binding>;
/** True if the expression references state directly, or _might_ (via member/call expressions) */
has_state: boolean; has_state: boolean;
/** True if the expression involves a call expression (often, it will need to be wrapped in a derived) */
has_call: boolean; has_call: boolean;
} }

@ -1,4 +1,4 @@
import type { Binding, Css, EffectMetadata } from '#compiler'; import type { Binding, Css, ExpressionMetadata } from '#compiler';
import type { import type {
ArrayExpression, ArrayExpression,
ArrowFunctionExpression, ArrowFunctionExpression,
@ -110,7 +110,9 @@ export interface Text extends BaseNode {
export interface ExpressionTag extends BaseNode { export interface ExpressionTag extends BaseNode {
type: 'ExpressionTag'; type: 'ExpressionTag';
expression: Expression; expression: Expression;
effect: EffectMetadata; metadata: {
expression: ExpressionMetadata;
};
} }
/** A (possibly reactive) HTML template expression — `{@html ...}` */ /** A (possibly reactive) HTML template expression — `{@html ...}` */
@ -183,7 +185,7 @@ export interface ClassDirective extends BaseNode {
/** The 'y' in `class:x={y}`, or the `x` in `class:x` */ /** The 'y' in `class:x={y}`, or the `x` in `class:x` */
expression: Expression; expression: Expression;
metadata: { metadata: {
dynamic: false; expression: ExpressionMetadata;
}; };
} }
@ -205,8 +207,7 @@ export interface OnDirective extends BaseNode {
expression: null | Expression; expression: null | Expression;
modifiers: string[]; // TODO specify modifiers: string[]; // TODO specify
metadata: { metadata: {
contains_call_expression: boolean; expression: ExpressionMetadata;
dynamic: boolean;
}; };
} }
@ -226,7 +227,7 @@ export interface StyleDirective extends BaseNode {
value: true | ExpressionTag | Array<ExpressionTag | Text>; value: true | ExpressionTag | Array<ExpressionTag | Text>;
modifiers: Array<'important'>; modifiers: Array<'important'>;
metadata: { metadata: {
dynamic: boolean; expression: ExpressionMetadata;
}; };
} }
@ -273,7 +274,7 @@ interface BaseElement extends BaseNode {
export interface Component extends BaseElement { export interface Component extends BaseElement {
type: 'Component'; type: 'Component';
metadata: { metadata: {
dynamic: boolean; expression: ExpressionMetadata;
}; };
} }
@ -447,7 +448,7 @@ export interface Attribute extends BaseNode {
name: string; name: string;
value: true | ExpressionTag | Array<Text | ExpressionTag>; value: true | ExpressionTag | Array<Text | ExpressionTag>;
metadata: { metadata: {
dynamic: boolean; expression: ExpressionMetadata;
/** May be set if this is an event attribute */ /** May be set if this is an event attribute */
delegated: null | DelegatedEvent; delegated: null | DelegatedEvent;
}; };
@ -456,7 +457,9 @@ export interface Attribute extends BaseNode {
export interface SpreadAttribute extends BaseNode { export interface SpreadAttribute extends BaseNode {
type: 'SpreadAttribute'; type: 'SpreadAttribute';
expression: Expression; expression: Expression;
effect: EffectMetadata; metadata: {
expression: ExpressionMetadata;
};
} }
export type TemplateNode = export type TemplateNode =

@ -45,7 +45,11 @@
], ],
"parent": null, "parent": null,
"metadata": { "metadata": {
"dynamic": false, "expression": {
"dependencies": {},
"has_state": false,
"has_call": false
},
"delegated": null "delegated": null
} }
}, },
@ -77,13 +81,20 @@
}, },
"parent": null, "parent": null,
"metadata": { "metadata": {
"contains_call_expression": false, "expression": {
"dynamic": false "dependencies": {},
"has_state": false,
"has_call": false
}
} }
}, },
"parent": null, "parent": null,
"metadata": { "metadata": {
"dynamic": false, "expression": {
"dependencies": {},
"has_state": false,
"has_call": false
},
"delegated": null "delegated": null
} }
} }

Loading…
Cancel
Save