Skip to content

Commit 65234b8

Browse files
authored
chore: unified expression metadata (#12644)
* add ExpressionMetadata type * use unified reactivity metadata * simplify component * rename * more consistency * store bindings on expression metadata * tidy * regenerate types * remove junk from options node * reuse * tweak jsdoc
1 parent 363535c commit 65234b8

File tree

12 files changed

+160
-145
lines changed

12 files changed

+160
-145
lines changed

packages/svelte/src/compiler/index.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,23 @@ export function parse(source, { filename, rootDir, modern } = {}) {
119119
*/
120120
function to_public_ast(source, ast, modern) {
121121
if (modern) {
122+
const clean = (/** @type {any} */ node) => {
123+
delete node.metadata;
124+
delete node.parent;
125+
};
126+
127+
ast.options?.attributes.forEach((attribute) => {
128+
clean(attribute);
129+
clean(attribute.value);
130+
if (Array.isArray(attribute.value)) {
131+
attribute.value.forEach(clean);
132+
}
133+
});
134+
122135
// remove things that we don't want to treat as public API
123136
return zimmerframe_walk(ast, null, {
124137
_(node, { next }) {
125-
// @ts-ignore
126-
delete node.parent;
127-
// @ts-ignore
128-
delete node.metadata;
138+
clean(node);
129139
next();
130140
}
131141
});

packages/svelte/src/compiler/phases/1-parse/state/element.js

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { decode_character_references } from '../utils/html.js';
99
import * as e from '../../../errors.js';
1010
import * as w from '../../../warnings.js';
1111
import { create_fragment } from '../utils/create.js';
12-
import { create_attribute } from '../../nodes.js';
12+
import { create_attribute, create_expression_metadata } from '../../nodes.js';
1313
import { get_attribute_expression, is_expression_attribute } from '../../../utils/ast.js';
1414
import { closing_tag_omitted } from '../../../../html-tree-validation.js';
1515

@@ -127,7 +127,7 @@ export default function element(parser) {
127127

128128
const type = meta_tags.has(name)
129129
? meta_tags.get(name)
130-
: regex_capital_letter.test(name[0]) || name === 'svelte:self' || name === 'svelte:component'
130+
: regex_capital_letter.test(name[0])
131131
? 'Component'
132132
: name === 'title' && parent_is_head(parser.stack)
133133
? 'TitleElement'
@@ -140,7 +140,7 @@ export default function element(parser) {
140140
const element =
141141
type === 'RegularElement'
142142
? {
143-
type: type,
143+
type,
144144
start,
145145
end: -1,
146146
name,
@@ -163,7 +163,7 @@ export default function element(parser) {
163163
fragment: create_fragment(true),
164164
parent: null,
165165
metadata: {
166-
svg: false
166+
// unpopulated at first, differs between types
167167
}
168168
});
169169

@@ -508,8 +508,7 @@ function read_attribute(parser) {
508508
expression,
509509
parent: null,
510510
metadata: {
511-
contains_call_expression: false,
512-
dynamic: false
511+
expression: create_expression_metadata()
513512
}
514513
};
515514

@@ -538,8 +537,7 @@ function read_attribute(parser) {
538537
},
539538
parent: null,
540539
metadata: {
541-
dynamic: false,
542-
contains_call_expression: false
540+
expression: create_expression_metadata()
543541
}
544542
};
545543

@@ -584,7 +582,7 @@ function read_attribute(parser) {
584582
value,
585583
parent: null,
586584
metadata: {
587-
dynamic: false
585+
expression: create_expression_metadata()
588586
}
589587
};
590588
}
@@ -616,17 +614,10 @@ function read_attribute(parser) {
616614
modifiers,
617615
expression,
618616
metadata: {
619-
dynamic: false,
620-
contains_call_expression: false
617+
expression: create_expression_metadata()
621618
}
622619
};
623620

624-
if (directive.type === 'ClassDirective') {
625-
directive.metadata = {
626-
dynamic: false
627-
};
628-
}
629-
630621
if (directive.type === 'TransitionDirective') {
631622
const direction = name.slice(0, colon_index);
632623
directive.intro = direction === 'in' || direction === 'transition';
@@ -789,8 +780,7 @@ function read_sequence(parser, done, location) {
789780
expression,
790781
parent: null,
791782
metadata: {
792-
contains_call_expression: false,
793-
dynamic: false
783+
expression: create_expression_metadata()
794784
}
795785
};
796786

packages/svelte/src/compiler/phases/1-parse/state/tag.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as e from '../../../errors.js';
77
import { create_fragment } from '../utils/create.js';
88
import { walk } from 'zimmerframe';
99
import { parse_expression_at } from '../acorn.js';
10+
import { create_expression_metadata } from '../../nodes.js';
1011

1112
const regex_whitespace_with_closing_curly_brace = /^\s*}/;
1213

@@ -39,8 +40,7 @@ export default function tag(parser) {
3940
end: parser.index,
4041
expression,
4142
metadata: {
42-
contains_call_expression: false,
43-
dynamic: false
43+
expression: create_expression_metadata()
4444
}
4545
});
4646
}

packages/svelte/src/compiler/phases/2-analyze/index.js

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,20 +1178,19 @@ const common_visitors = {
11781178

11791179
context.next();
11801180

1181-
node.metadata.dynamic = get_attribute_chunks(node.value).some((chunk) => {
1182-
if (chunk.type !== 'ExpressionTag') {
1183-
return false;
1184-
}
1181+
for (const chunk of get_attribute_chunks(node.value)) {
1182+
if (chunk.type !== 'ExpressionTag') continue;
11851183

11861184
if (
11871185
chunk.expression.type === 'FunctionExpression' ||
11881186
chunk.expression.type === 'ArrowFunctionExpression'
11891187
) {
1190-
return false;
1188+
continue;
11911189
}
11921190

1193-
return chunk.metadata.dynamic || chunk.metadata.contains_call_expression;
1194-
});
1191+
node.metadata.expression.has_state ||= chunk.metadata.expression.has_state;
1192+
node.metadata.expression.has_call ||= chunk.metadata.expression.has_call;
1193+
}
11951194

11961195
if (is_event_attribute(node)) {
11971196
const parent = context.path.at(-1);
@@ -1211,10 +1210,10 @@ const common_visitors = {
12111210
}
12121211
},
12131212
ClassDirective(node, context) {
1214-
context.next({ ...context.state, expression: node });
1213+
context.next({ ...context.state, expression: node.metadata.expression });
12151214
},
12161215
SpreadAttribute(node, context) {
1217-
context.next({ ...context.state, expression: node });
1216+
context.next({ ...context.state, expression: node.metadata.expression });
12181217
},
12191218
SlotElement(node, context) {
12201219
let name = 'default';
@@ -1230,17 +1229,21 @@ const common_visitors = {
12301229
if (node.value === true) {
12311230
const binding = context.state.scope.get(node.name);
12321231
if (binding?.kind !== 'normal') {
1233-
node.metadata.dynamic = true;
1232+
node.metadata.expression.has_state = true;
12341233
}
12351234
} else {
12361235
context.next();
1237-
node.metadata.dynamic = get_attribute_chunks(node.value).some(
1238-
(node) => node.type === 'ExpressionTag' && node.metadata.dynamic
1239-
);
1236+
1237+
for (const chunk of get_attribute_chunks(node.value)) {
1238+
if (chunk.type !== 'ExpressionTag') continue;
1239+
1240+
node.metadata.expression.has_state ||= chunk.metadata.expression.has_state;
1241+
node.metadata.expression.has_call ||= chunk.metadata.expression.has_call;
1242+
}
12401243
}
12411244
},
12421245
ExpressionTag(node, context) {
1243-
context.next({ ...context.state, expression: node });
1246+
context.next({ ...context.state, expression: node.metadata.expression });
12441247
},
12451248
Identifier(node, context) {
12461249
const parent = /** @type {Node} */ (context.path.at(-1));
@@ -1261,10 +1264,18 @@ const common_visitors = {
12611264

12621265
const binding = context.state.scope.get(node.name);
12631266

1267+
if (binding && context.state.expression) {
1268+
context.state.expression.dependencies.add(binding);
1269+
1270+
if (binding.kind !== 'normal') {
1271+
context.state.expression.has_state = true;
1272+
}
1273+
}
1274+
12641275
// if no binding, means some global variable
12651276
if (binding && binding.kind !== 'normal') {
12661277
if (context.state.expression) {
1267-
context.state.expression.metadata.dynamic = true;
1278+
context.state.expression.has_state = true;
12681279
}
12691280

12701281
// TODO it would be better to just bail out when we hit the ExportSpecifier node but that's
@@ -1297,13 +1308,10 @@ const common_visitors = {
12971308
},
12981309
CallExpression(node, context) {
12991310
const { expression, render_tag } = context.state;
1300-
if (
1301-
(expression?.type === 'ExpressionTag' ||
1302-
expression?.type === 'SpreadAttribute' ||
1303-
expression?.type === 'OnDirective') &&
1304-
!is_known_safe_call(node, context)
1305-
) {
1306-
expression.metadata.contains_call_expression = true;
1311+
1312+
if (expression && !is_known_safe_call(node, context)) {
1313+
expression.has_call = true;
1314+
expression.has_state = true;
13071315
}
13081316

13091317
if (render_tag) {
@@ -1354,7 +1362,7 @@ const common_visitors = {
13541362
},
13551363
MemberExpression(node, context) {
13561364
if (context.state.expression) {
1357-
context.state.expression.metadata.dynamic = true;
1365+
context.state.expression.has_state = true;
13581366
}
13591367

13601368
if (!is_safe_identifier(node, context.state.scope)) {
@@ -1368,7 +1376,7 @@ const common_visitors = {
13681376
if (parent?.type === 'SvelteElement' || parent?.type === 'RegularElement') {
13691377
state.analysis.event_directive_node ??= node;
13701378
}
1371-
next({ ...state, expression: node });
1379+
next({ ...state, expression: node.metadata.expression });
13721380
},
13731381
BindDirective(node, context) {
13741382
let i = context.path.length;

packages/svelte/src/compiler/phases/2-analyze/types.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Scope } from '../scope.js';
22
import type { ComponentAnalysis, ReactiveStatement } from '../types.js';
33
import type {
44
ClassDirective,
5+
ExpressionMetadata,
56
ExpressionTag,
67
OnDirective,
78
RenderTag,
@@ -20,8 +21,8 @@ export interface AnalysisState {
2021
has_props_rune: boolean;
2122
/** Which slots the current parent component has */
2223
component_slots: Set<string>;
23-
/** The current {expression}, if any */
24-
expression: ExpressionTag | ClassDirective | OnDirective | SpreadAttribute | null;
24+
/** Information about the current expression/directive/block value */
25+
expression: ExpressionMetadata | null;
2526
/** The current {@render ...} tag, if any */
2627
render_tag: null | RenderTag;
2728
private_derived_state: string[];

0 commit comments

Comments
 (0)