Skip to content

Commit 0469008

Browse files
authored
chore: remove references to node.parent (#14395)
* chore: remove references to node.parent * types * add ElementWithPath interface * put path on analysis.elements instead of metadata * Revert "put path on analysis.elements instead of metadata" This reverts commit c0c7ab8. * use node.metadata.path * remove a node.parent usage * another * and another, albeit by replacing some bewildering code with some more bewildering code * make loop idiomatic * replace some more weirdo code * simplify
1 parent dd9abea commit 0469008

File tree

9 files changed

+90
-76
lines changed

9 files changed

+90
-76
lines changed

packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js

Lines changed: 58 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -260,12 +260,15 @@ function apply_combinator(combinator, relative_selector, parent_selectors, rule,
260260
switch (name) {
261261
case ' ':
262262
case '>': {
263-
let parent = /** @type {Compiler.TemplateNode | null} */ (element.parent);
264-
265263
let parent_matched = false;
266264
let crossed_component_boundary = false;
267265

268-
while (parent) {
266+
const path = element.metadata.path;
267+
let i = path.length;
268+
269+
while (i--) {
270+
const parent = path[i];
271+
269272
if (parent.type === 'Component' || parent.type === 'SvelteComponent') {
270273
crossed_component_boundary = true;
271274
}
@@ -289,8 +292,6 @@ function apply_combinator(combinator, relative_selector, parent_selectors, rule,
289292

290293
if (name === '>') return parent_matched;
291294
}
292-
293-
parent = /** @type {Compiler.TemplateNode | null} */ (parent.parent);
294295
}
295296

296297
return parent_matched || parent_selectors.every((selector) => is_global(selector, rule));
@@ -679,51 +680,50 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
679680
* @param {boolean} include_self
680681
*/
681682
function get_following_sibling_elements(element, include_self) {
682-
/** @type {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.Root | null} */
683-
let parent = get_element_parent(element);
683+
const path = element.metadata.path;
684+
let i = path.length;
684685

685-
if (!parent) {
686-
parent = element;
687-
while (parent?.type !== 'Root') {
688-
parent = /** @type {any} */ (parent).parent;
686+
/** @type {Compiler.SvelteNode} */
687+
let start = element;
688+
let nodes = /** @type {Compiler.SvelteNode[]} */ (
689+
/** @type {Compiler.AST.Fragment} */ (path[0]).nodes
690+
);
691+
692+
// find the set of nodes to walk...
693+
while (i--) {
694+
const node = path[i];
695+
696+
if (node.type === 'RegularElement' || node.type === 'SvelteElement') {
697+
nodes = node.fragment.nodes;
698+
break;
699+
}
700+
701+
if (node.type !== 'Fragment') {
702+
start = node;
689703
}
690704
}
691705

692706
/** @type {Array<Compiler.AST.RegularElement | Compiler.AST.SvelteElement>} */
693-
const sibling_elements = [];
694-
let found_parent = false;
695-
696-
for (const el of parent.fragment.nodes) {
697-
if (found_parent) {
698-
walk(
699-
el,
700-
{},
701-
{
702-
RegularElement(node) {
703-
sibling_elements.push(node);
704-
},
705-
SvelteElement(node) {
706-
sibling_elements.push(node);
707-
}
708-
}
709-
);
710-
} else {
711-
/** @type {any} */
712-
let child = element;
713-
while (child !== el && child !== parent) {
714-
child = child.parent;
707+
const siblings = [];
708+
709+
// ...then walk them, starting from the node after the one
710+
// containing the element in question
711+
for (const node of nodes.slice(nodes.indexOf(start) + 1)) {
712+
walk(node, null, {
713+
RegularElement(node) {
714+
siblings.push(node);
715+
},
716+
SvelteElement(node) {
717+
siblings.push(node);
715718
}
716-
if (child === el) {
717-
found_parent = true;
718-
}
719-
}
719+
});
720720
}
721721

722722
if (include_self) {
723-
sibling_elements.push(element);
723+
siblings.push(element);
724724
}
725725

726-
return sibling_elements;
726+
return siblings;
727727
}
728728

729729
/**
@@ -867,15 +867,18 @@ function unquote(str) {
867867
* @returns {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | null}
868868
*/
869869
function get_element_parent(node) {
870-
/** @type {Compiler.SvelteNode | null} */
871-
let parent = node;
872-
while (
873-
// @ts-expect-error TODO figure out a more elegant solution
874-
(parent = parent.parent) &&
875-
parent.type !== 'RegularElement' &&
876-
parent.type !== 'SvelteElement'
877-
);
878-
return parent ?? null;
870+
let path = node.metadata.path;
871+
let i = path.length;
872+
873+
while (i--) {
874+
const parent = path[i];
875+
876+
if (parent.type === 'RegularElement' || parent.type === 'SvelteElement') {
877+
return parent;
878+
}
879+
}
880+
881+
return null;
879882
}
880883

881884
/**
@@ -920,7 +923,7 @@ function find_previous_sibling(node) {
920923
while (current_node?.type === 'SlotElement') {
921924
const slot_children = current_node.fragment.nodes;
922925
if (slot_children.length > 0) {
923-
current_node = slot_children.slice(-1)[0];
926+
current_node = slot_children[slot_children.length - 1];
924927
} else {
925928
break;
926929
}
@@ -1118,8 +1121,12 @@ function mark_as_probably(result) {
11181121
function loop_child(children, adjacent_only) {
11191122
/** @type {Map<Compiler.AST.RegularElement, NodeExistsValue>} */
11201123
const result = new Map();
1121-
for (let i = children.length - 1; i >= 0; i--) {
1124+
1125+
let i = children.length;
1126+
1127+
while (i--) {
11221128
const child = children[i];
1129+
11231130
if (child.type === 'RegularElement') {
11241131
result.set(child, NODE_DEFINITELY_EXISTS);
11251132
if (adjacent_only) {
@@ -1137,5 +1144,6 @@ function loop_child(children, adjacent_only) {
11371144
}
11381145
}
11391146
}
1147+
11401148
return result;
11411149
}

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -709,8 +709,8 @@ export function analyze_component(root, source, options) {
709709
analyze_css(analysis.css.ast, analysis);
710710

711711
// mark nodes as scoped/unused/empty etc
712-
for (const element of analysis.elements) {
713-
prune(analysis.css.ast, element);
712+
for (const node of analysis.elements) {
713+
prune(analysis.css.ast, node);
714714
}
715715

716716
const { comment } = analysis.css.ast.content;
@@ -724,18 +724,18 @@ export function analyze_component(root, source, options) {
724724
warn_unused(analysis.css.ast);
725725
}
726726

727-
outer: for (const element of analysis.elements) {
728-
if (element.type === 'RenderTag') continue;
727+
outer: for (const node of analysis.elements) {
728+
if (node.type === 'RenderTag') continue;
729729

730-
if (element.metadata.scoped) {
730+
if (node.metadata.scoped) {
731731
// Dynamic elements in dom mode always use spread for attributes and therefore shouldn't have a class attribute added to them
732732
// TODO this happens during the analysis phase, which shouldn't know anything about client vs server
733-
if (element.type === 'SvelteElement' && options.generate === 'client') continue;
733+
if (node.type === 'SvelteElement' && options.generate === 'client') continue;
734734

735735
/** @type {AST.Attribute | undefined} */
736736
let class_attribute = undefined;
737737

738-
for (const attribute of element.attributes) {
738+
for (const attribute of node.attributes) {
739739
if (attribute.type === 'SpreadAttribute') {
740740
// The spread method appends the hash to the end of the class attribute on its own
741741
continue outer;
@@ -768,7 +768,7 @@ export function analyze_component(root, source, options) {
768768
}
769769
}
770770
} else {
771-
element.attributes.push(
771+
node.attributes.push(
772772
create_attribute('class', -1, -1, [
773773
{
774774
type: 'Text',
@@ -780,8 +780,8 @@ export function analyze_component(root, source, options) {
780780
}
781781
])
782782
);
783-
if (is_custom_element_node(element) && element.attributes.length === 1) {
784-
mark_subtree_dynamic(element.metadata.path);
783+
if (is_custom_element_node(node) && node.attributes.length === 1) {
784+
mark_subtree_dynamic(node.metadata.path);
785785
}
786786
}
787787
}

packages/svelte/src/compiler/phases/2-analyze/visitors/ExpressionTag.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import { mark_subtree_dynamic } from './shared/fragment.js';
99
* @param {Context} context
1010
*/
1111
export function ExpressionTag(node, context) {
12-
if (node.parent && context.state.parent_element) {
12+
const in_attribute = context.path.at(-1)?.type === 'Attribute';
13+
14+
if (!in_attribute && context.state.parent_element) {
1315
if (!is_tag_valid_with_parent('#text', context.state.parent_element)) {
1416
e.node_invalid_placement(node, '`{expression}`', context.state.parent_element);
1517
}

packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,9 @@ import { mark_subtree_dynamic } from './shared/fragment.js';
1919
*/
2020
export function RegularElement(node, context) {
2121
validate_element(node, context);
22-
23-
check_element(node, context.state);
22+
check_element(node, context);
2423

2524
node.metadata.path = [...context.path];
26-
2725
context.state.analysis.elements.push(node);
2826

2927
// Special case: Move the children of <textarea> into a value attribute if they are dynamic

packages/svelte/src/compiler/phases/2-analyze/visitors/RenderTag.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { mark_subtree_dynamic } from './shared/fragment.js';
1212
export function RenderTag(node, context) {
1313
validate_opening_tag(node, context.state, '@');
1414

15+
node.metadata.path = [...context.path];
1516
context.state.analysis.elements.push(node);
1617

1718
const callee = unwrap_optional(node.expression).callee;

packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteElement.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import { mark_subtree_dynamic } from './shared/fragment.js';
1212
*/
1313
export function SvelteElement(node, context) {
1414
validate_element(node, context);
15+
check_element(node, context);
1516

16-
check_element(node, context.state);
17-
17+
node.metadata.path = [...context.path];
1818
context.state.analysis.elements.push(node);
1919

2020
const xmlns = /** @type {AST.Attribute & { value: [AST.Text] } | undefined} */ (

packages/svelte/src/compiler/phases/2-analyze/visitors/Text.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import * as e from '../../../errors.js';
99
* @param {Context} context
1010
*/
1111
export function Text(node, context) {
12-
if (node.parent && context.state.parent_element && regex_not_whitespace.test(node.data)) {
12+
const in_attribute = context.path.at(-1)?.type === 'Attribute';
13+
14+
if (!in_attribute && context.state.parent_element && regex_not_whitespace.test(node.data)) {
1315
if (!is_tag_valid_with_parent('#text', context.state.parent_element)) {
1416
e.node_invalid_placement(node, 'Text node', context.state.parent_element);
1517
}

packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { AnalysisState } from '../../types.js' */
1+
/** @import { Context } from '../../types.js' */
22
/** @import { AST, SvelteNode, TemplateNode } from '#compiler' */
33
/** @import { ARIARoleDefinitionKey, ARIARoleRelationConcept, ARIAProperty, ARIAPropertyDefinition, ARIARoleDefinition } from 'aria-query' */
44
import { roles as roles_map, aria, elementRoles } from 'aria-query';
@@ -582,16 +582,17 @@ function get_implicit_role(name, attribute_map) {
582582
const invisible_elements = ['meta', 'html', 'script', 'style'];
583583

584584
/**
585-
* @param {SvelteNode | null} parent
585+
* @param {SvelteNode[]} path
586586
* @param {string[]} elements
587587
*/
588-
function is_parent(parent, elements) {
589-
while (parent) {
588+
function is_parent(path, elements) {
589+
let i = path.length;
590+
while (i--) {
591+
const parent = path[i];
590592
if (parent.type === 'SvelteElement') return true; // unknown, play it safe, so we don't warn
591593
if (parent.type === 'RegularElement') {
592594
return elements.includes(parent.name);
593595
}
594-
parent = /** @type {TemplateNode} */ (parent).parent;
595596
}
596597
return false;
597598
}
@@ -683,9 +684,9 @@ function get_static_text_value(attribute) {
683684

684685
/**
685686
* @param {AST.RegularElement | AST.SvelteElement} node
686-
* @param {AnalysisState} state
687+
* @param {Context} context
687688
*/
688-
export function check_element(node, state) {
689+
export function check_element(node, context) {
689690
/** @type {Map<string, AST.Attribute>} */
690691
const attribute_map = new Map();
691692

@@ -792,7 +793,7 @@ export function check_element(node, state) {
792793
}
793794

794795
// Footers and headers are special cases, and should not have redundant roles unless they are the children of sections or articles.
795-
const is_parent_section_or_article = is_parent(node.parent, ['section', 'article']);
796+
const is_parent_section_or_article = is_parent(context.path, ['section', 'article']);
796797
if (!is_parent_section_or_article) {
797798
const has_nested_redundant_role =
798799
current_role === a11y_nested_implicit_semantics.get(node.name);
@@ -1114,7 +1115,7 @@ export function check_element(node, state) {
11141115
}
11151116

11161117
if (node.name === 'figcaption') {
1117-
if (!is_parent(node.parent, ['figure'])) {
1118+
if (!is_parent(context.path, ['figure'])) {
11181119
w.a11y_figcaption_parent(node);
11191120
}
11201121
}

packages/svelte/src/compiler/types/template.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ export namespace AST {
167167
metadata: {
168168
dynamic: boolean;
169169
args_with_call_expression: Set<number>;
170+
path: SvelteNode[];
170171
};
171172
}
172173

@@ -344,6 +345,7 @@ export namespace AST {
344345
*/
345346
mathml: boolean;
346347
scoped: boolean;
348+
path: SvelteNode[];
347349
};
348350
}
349351

0 commit comments

Comments
 (0)