Skip to content

Commit 447baed

Browse files
committed
fix: invoke mapNodesToParents lazily
1 parent b761d1b commit 447baed

19 files changed

+108
-108
lines changed

lib/style.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as csstree from 'css-tree';
22
import * as csswhat from 'css-what';
33
import { syntax } from 'csso';
4-
import { matches, visit } from './xast.js';
4+
import { matches } from './xast.js';
5+
import { visit } from './util/visit.js';
56
import {
67
attrsGroups,
78
inheritableAttrs,

lib/style.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { collectStylesheet, computeStyle } from './style.js';
2-
import { visit } from './xast.js';
2+
import { visit } from './util/visit.js';
33
import { parseSvg } from './parser.js';
44

55
/**

lib/svgo.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { builtinPlugins } from './builtin.js';
22
import { encodeSVGDatauri } from './svgo/tools.js';
33
import { invokePlugins } from './svgo/plugins.js';
4-
import { mapNodesToParents, querySelector, querySelectorAll } from './xast.js';
4+
import { querySelector, querySelectorAll } from './xast.js';
5+
import { mapNodesToParents } from './util/map-nodes-to-parents.js';
56
import { parseSvg } from './parser.js';
67
import { stringifySvg } from './stringifier.js';
78
import { VERSION } from './version.js';

lib/svgo/css-select-adapter.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { mapNodesToParents } from '../util/map-nodes-to-parents.js';
2+
13
/** @type {Required<import('css-select').Options<import('../types.js').XastNode & { children?: any }, import('../types.js').XastElement>>['adapter']['isTag']} */
24
const isTag = (node) => {
35
return node.type === 'element';
@@ -69,12 +71,17 @@ const findOne = (test, elems) => {
6971
};
7072

7173
/**
72-
* @param {Map<import('../types.js').XastNode, import('../types.js').XastParent>} parents
74+
* @param {import('../types.js').XastParent} relativeNode
75+
* @param {Map<import('../types.js').XastNode, import('../types.js').XastParent>=} parents
7376
* @returns {Required<import('css-select').Options<import('../types.js').XastNode & { children?: any }, import('../types.js').XastElement>>['adapter']}
7477
*/
75-
export function createAdapter(parents) {
78+
export function createAdapter(relativeNode, parents) {
7679
/** @type {Required<import('css-select').Options<import('../types.js').XastNode & { children?: any }, import('../types.js').XastElement>>['adapter']['getParent']} */
7780
const getParent = (node) => {
81+
if (!parents) {
82+
parents = mapNodesToParents(relativeNode);
83+
}
84+
7885
return parents.get(node) || null;
7986
};
8087

lib/svgo/plugins.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { visit } from '../xast.js';
1+
import { visit } from '../util/visit.js';
22

33
/**
44
* Plugins engine.

lib/util/map-nodes-to-parents.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { visit } from './visit.js';
2+
3+
/**
4+
* Maps all nodes to their parent node recursively.
5+
*
6+
* @param {import('../types.js').XastParent} node
7+
* @returns {Map<import('../types.js').XastNode, import('../types.js').XastParent>}
8+
*/
9+
export function mapNodesToParents(node) {
10+
/** @type {Map<import('../types.js').XastNode, import('../types.js').XastParent>} */
11+
const parents = new Map();
12+
13+
for (const child of node.children) {
14+
parents.set(child, node);
15+
visit(
16+
child,
17+
{
18+
element: {
19+
enter: (child, parent) => {
20+
parents.set(child, parent);
21+
},
22+
},
23+
},
24+
node,
25+
);
26+
}
27+
28+
return parents;
29+
}

lib/util/visit.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
export const visitSkip = Symbol();
2+
3+
/**
4+
* @param {import('../types.js').XastNode} node
5+
* @param {import('../types.js').Visitor} visitor
6+
* @param {any=} parentNode
7+
*/
8+
export const visit = (node, visitor, parentNode) => {
9+
const callbacks = visitor[node.type];
10+
if (callbacks?.enter) {
11+
// @ts-expect-error hard to infer
12+
const symbol = callbacks.enter(node, parentNode);
13+
if (symbol === visitSkip) {
14+
return;
15+
}
16+
}
17+
// visit root children
18+
if (node.type === 'root') {
19+
// copy children array to not lose cursor when children is spliced
20+
for (const child of node.children) {
21+
visit(child, visitor, node);
22+
}
23+
}
24+
// visit element children if still attached to parent
25+
if (node.type === 'element') {
26+
if (parentNode.children.includes(node)) {
27+
for (const child of node.children) {
28+
visit(child, visitor, node);
29+
}
30+
}
31+
}
32+
if (callbacks?.exit) {
33+
// @ts-expect-error hard to infer
34+
callbacks.exit(node, parentNode);
35+
}
36+
};

lib/xast.js

Lines changed: 10 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -2,56 +2,25 @@ import { is, selectAll, selectOne } from 'css-select';
22
import { createAdapter } from './svgo/css-select-adapter.js';
33

44
/**
5-
* @param {Map<import('./types.js').XastNode, import('./types.js').XastParent>} parents
5+
* @param {import('./types.js').XastParent} relativeNode
6+
* @param {Map<import('./types.js').XastNode, import('./types.js').XastParent>=} parents
67
* @returns {import('css-select').Options<import('./types.js').XastNode & { children?: any }, import('./types.js').XastElement>}
78
*/
8-
function createCssSelectOptions(parents) {
9+
function createCssSelectOptions(relativeNode, parents) {
910
return {
1011
xmlMode: true,
11-
adapter: createAdapter(parents),
12+
adapter: createAdapter(relativeNode, parents),
1213
};
1314
}
1415

15-
/**
16-
* Maps all nodes to their parent node recursively.
17-
*
18-
* @param {import('./types.js').XastParent} node
19-
* @returns {Map<import('./types.js').XastNode, import('./types.js').XastParent>}
20-
*/
21-
export function mapNodesToParents(node) {
22-
/** @type {Map<import('./types.js').XastNode, import('./types.js').XastParent>} */
23-
const parents = new Map();
24-
25-
for (const child of node.children) {
26-
parents.set(child, node);
27-
visit(
28-
child,
29-
{
30-
element: {
31-
enter: (child, parent) => {
32-
parents.set(child, parent);
33-
},
34-
},
35-
},
36-
node,
37-
);
38-
}
39-
40-
return parents;
41-
}
42-
4316
/**
4417
* @param {import('./types.js').XastParent} node Element to query the children of.
4518
* @param {string} selector CSS selector string.
4619
* @param {Map<import('./types.js').XastNode, import('./types.js').XastParent>=} parents
4720
* @returns {import('./types.js').XastChild[]} All matching elements.
4821
*/
49-
export const querySelectorAll = (
50-
node,
51-
selector,
52-
parents = mapNodesToParents(node),
53-
) => {
54-
return selectAll(selector, node, createCssSelectOptions(parents));
22+
export const querySelectorAll = (node, selector, parents) => {
23+
return selectAll(selector, node, createCssSelectOptions(node, parents));
5524
};
5625

5726
/**
@@ -60,12 +29,8 @@ export const querySelectorAll = (
6029
* @param {Map<import('./types.js').XastNode, import('./types.js').XastParent>=} parents
6130
* @returns {?import('./types.js').XastChild} First match, or null if there was no match.
6231
*/
63-
export const querySelector = (
64-
node,
65-
selector,
66-
parents = mapNodesToParents(node),
67-
) => {
68-
return selectOne(selector, node, createCssSelectOptions(parents));
32+
export const querySelector = (node, selector, parents) => {
33+
return selectOne(selector, node, createCssSelectOptions(node, parents));
6934
};
7035

7136
/**
@@ -74,45 +39,8 @@ export const querySelector = (
7439
* @param {Map<import('./types.js').XastNode, import('./types.js').XastParent>=} parents
7540
* @returns {boolean}
7641
*/
77-
export const matches = (node, selector, parents = mapNodesToParents(node)) => {
78-
return is(node, selector, createCssSelectOptions(parents));
79-
};
80-
81-
export const visitSkip = Symbol();
82-
83-
/**
84-
* @param {import('./types.js').XastNode} node
85-
* @param {import('./types.js').Visitor} visitor
86-
* @param {any=} parentNode
87-
*/
88-
export const visit = (node, visitor, parentNode) => {
89-
const callbacks = visitor[node.type];
90-
if (callbacks?.enter) {
91-
// @ts-expect-error hard to infer
92-
const symbol = callbacks.enter(node, parentNode);
93-
if (symbol === visitSkip) {
94-
return;
95-
}
96-
}
97-
// visit root children
98-
if (node.type === 'root') {
99-
// copy children array to not lose cursor when children is spliced
100-
for (const child of node.children) {
101-
visit(child, visitor, node);
102-
}
103-
}
104-
// visit element children if still attached to parent
105-
if (node.type === 'element') {
106-
if (parentNode.children.includes(node)) {
107-
for (const child of node.children) {
108-
visit(child, visitor, node);
109-
}
110-
}
111-
}
112-
if (callbacks?.exit) {
113-
// @ts-expect-error hard to infer
114-
callbacks.exit(node, parentNode);
115-
}
42+
export const matches = (node, selector, parents) => {
43+
return is(node, selector, createCssSelectOptions(node, parents));
11644
};
11745

11846
/**

lib/xast.test.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { detachNodeFromParent, visit, visitSkip } from './xast.js';
1+
import { detachNodeFromParent } from './xast.js';
2+
import { visit, visitSkip } from './util/visit.js';
23

34
/**
45
* @param {import('./types.js').XastElement[]} children

plugins/cleanupEnableBackground.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as csstree from 'css-tree';
2-
import { visit } from '../lib/xast.js';
2+
import { visit } from '../lib/util/visit.js';
33

44
export const name = 'cleanupEnableBackground';
55
export const description =

0 commit comments

Comments
 (0)