1
0
mirror of https://github.com/svg/svgo.git synced 2025-07-28 09:22:00 +03:00

chore: drop parentNode property (#2113)

This commit is contained in:
Seth Falco
2025-05-01 15:59:23 +01:00
committed by GitHub
parent 1b4919a0d4
commit 9eb3af59f2
13 changed files with 143 additions and 134 deletions

View File

@ -107,11 +107,6 @@ export const parseSvg = (data, from) => {
* @type {(node: XastChild) => void} * @type {(node: XastChild) => void}
*/ */
const pushToContent = (node) => { const pushToContent = (node) => {
// TODO remove legacy parentNode in v4
Object.defineProperty(node, 'parentNode', {
writable: true,
value: current,
});
current.children.push(node); current.children.push(node);
}; };

View File

@ -10,15 +10,16 @@ import {
/** /**
* @typedef {import('css-tree').Rule} CsstreeRule * @typedef {import('css-tree').Rule} CsstreeRule
* @typedef {import('./types.js').ComputedStyles} ComputedStyles
* @typedef {import('./types.js').Specificity} Specificity * @typedef {import('./types.js').Specificity} Specificity
* @typedef {import('./types.js').Stylesheet} Stylesheet * @typedef {import('./types.js').Stylesheet} Stylesheet
* @typedef {import('./types.js').StylesheetRule} StylesheetRule
* @typedef {import('./types.js').StylesheetDeclaration} StylesheetDeclaration * @typedef {import('./types.js').StylesheetDeclaration} StylesheetDeclaration
* @typedef {import('./types.js').ComputedStyles} ComputedStyles * @typedef {import('./types.js').StylesheetRule} StylesheetRule
* @typedef {import('./types.js').XastRoot} XastRoot
* @typedef {import('./types.js').XastElement} XastElement
* @typedef {import('./types.js').XastParent} XastParent
* @typedef {import('./types.js').XastChild} XastChild * @typedef {import('./types.js').XastChild} XastChild
* @typedef {import('./types.js').XastElement} XastElement
* @typedef {import('./types.js').XastNode} XastNode
* @typedef {import('./types.js').XastParent} XastParent
* @typedef {import('./types.js').XastRoot} XastRoot
*/ */
const csstreeWalkSkip = csstree.walk.skip; const csstreeWalkSkip = csstree.walk.skip;
@ -130,9 +131,10 @@ const parseStyleDeclarations = (css) => {
/** /**
* @param {Stylesheet} stylesheet * @param {Stylesheet} stylesheet
* @param {XastElement} node * @param {XastElement} node
* @param {Map<XastNode, XastParent>=} parents
* @returns {ComputedStyles} * @returns {ComputedStyles}
*/ */
const computeOwnStyle = (stylesheet, node) => { const computeOwnStyle = (stylesheet, node, parents = undefined) => {
/** @type {ComputedStyles} */ /** @type {ComputedStyles} */
const computedStyle = {}; const computedStyle = {};
const importantStyles = new Map(); const importantStyles = new Map();
@ -147,7 +149,7 @@ const computeOwnStyle = (stylesheet, node) => {
// collect matching rules // collect matching rules
for (const { selector, declarations, dynamic } of stylesheet.rules) { for (const { selector, declarations, dynamic } of stylesheet.rules) {
if (matches(node, selector)) { if (matches(node, selector, parents)) {
for (const { name, value, important } of declarations) { for (const { name, value, important } of declarations) {
const computed = computedStyle[name]; const computed = computedStyle[name];
if (computed && computed.type === 'dynamic') { if (computed && computed.type === 'dynamic') {
@ -259,10 +261,10 @@ export const collectStylesheet = (root) => {
*/ */
export const computeStyle = (stylesheet, node) => { export const computeStyle = (stylesheet, node) => {
const { parents } = stylesheet; const { parents } = stylesheet;
const computedStyles = computeOwnStyle(stylesheet, node); const computedStyles = computeOwnStyle(stylesheet, node, parents);
let parent = parents.get(node); let parent = parents.get(node);
while (parent != null && parent.type !== 'root') { while (parent != null && parent.type !== 'root') {
const inheritedStyles = computeOwnStyle(stylesheet, parent); const inheritedStyles = computeOwnStyle(stylesheet, parent, parents);
for (const [name, computed] of Object.entries(inheritedStyles)) { for (const [name, computed] of Object.entries(inheritedStyles)) {
if ( if (
computedStyles[name] == null && computedStyles[name] == null &&

View File

@ -11,9 +11,7 @@ import { parseSvg } from './parser.js';
* @type {(node: XastParent, id: string) => XastElement} * @type {(node: XastParent, id: string) => XastElement}
*/ */
const getElementById = (node, id) => { const getElementById = (node, id) => {
/** /** @type {?XastElement} */
* @type {?XastElement}
*/
let matched = null; let matched = null;
visit(node, { visit(node, {
element: { element: {

View File

@ -5,6 +5,7 @@ import {
VERSION, VERSION,
optimize as optimizeAgnostic, optimize as optimizeAgnostic,
builtinPlugins, builtinPlugins,
mapNodesToParents,
querySelector, querySelector,
querySelectorAll, querySelectorAll,
_collections, _collections,
@ -113,6 +114,7 @@ export default {
builtinPlugins, builtinPlugins,
loadConfig, loadConfig,
optimize, optimize,
mapNodesToParents,
querySelector, querySelector,
querySelectorAll, querySelectorAll,
_collections, _collections,

2
lib/svgo.d.ts vendored
View File

@ -162,4 +162,4 @@ export declare const VERSION: string;
/** The core of SVGO */ /** The core of SVGO */
export declare function optimize(input: string, config?: Config): Output; export declare function optimize(input: string, config?: Config): Output;
export { querySelector, querySelectorAll } from './xast.js'; export { mapNodesToParents, querySelector, querySelectorAll } from './xast.js';

View File

@ -4,7 +4,7 @@ import { builtin } from './builtin.js';
import { invokePlugins } from './svgo/plugins.js'; import { invokePlugins } from './svgo/plugins.js';
import { encodeSVGDatauri } from './svgo/tools.js'; import { encodeSVGDatauri } from './svgo/tools.js';
import { VERSION } from './version.js'; import { VERSION } from './version.js';
import { querySelector, querySelectorAll } from './xast.js'; import { mapNodesToParents, querySelector, querySelectorAll } from './xast.js';
import _collections from '../plugins/_collections.js'; import _collections from '../plugins/_collections.js';
/** /**
@ -78,6 +78,7 @@ const resolvePluginConfig = (plugin) => {
export { export {
VERSION, VERSION,
builtin as builtinPlugins, builtin as builtinPlugins,
mapNodesToParents,
querySelector, querySelector,
querySelectorAll, querySelectorAll,
_collections, _collections,
@ -147,6 +148,7 @@ export default {
VERSION, VERSION,
optimize, optimize,
builtinPlugins: builtin, builtinPlugins: builtin,
mapNodesToParents,
querySelector, querySelector,
querySelectorAll, querySelectorAll,
_collections, _collections,

View File

@ -1,5 +1,5 @@
/** /**
* @typedef {Required<import('css-select').Options<XastNode & { children?: any }, XastElement & { parentNode?: any }>>['adapter']} Adapter * @typedef {Required<import('css-select').Options<XastNode & { children?: any }, XastElement>>['adapter']} Adapter
* @typedef {import('../types.js').XastChild} XastChild * @typedef {import('../types.js').XastChild} XastChild
* @typedef {import('../types.js').XastElement} XastElement * @typedef {import('../types.js').XastElement} XastElement
* @typedef {import('../types.js').XastNode} XastNode * @typedef {import('../types.js').XastNode} XastNode
@ -33,20 +33,6 @@ const getName = (elemAst) => {
return elemAst.name; return elemAst.name;
}; };
/** @type {Adapter['getParent']} */
const getParent = (node) => {
return node.parentNode || null;
};
/**
* @param {any} elem
* @returns {any}
*/
const getSiblings = (elem) => {
const parent = getParent(elem);
return parent ? getChildren(parent) : [];
};
/** @type {Adapter['getText']} */ /** @type {Adapter['getText']} */
const getText = (node) => { const getText = (node) => {
if (node.children[0].type === 'text' || node.children[0].type === 'cdata') { if (node.children[0].type === 'text' || node.children[0].type === 'cdata') {
@ -60,38 +46,6 @@ const hasAttrib = (elem, name) => {
return elem.attributes[name] !== undefined; return elem.attributes[name] !== undefined;
}; };
/**
* @param {any} nodes
* @returns {any}
*/
const removeSubsets = (nodes) => {
let idx = nodes.length;
let node;
let ancestor;
let replace;
// Check if each node (or one of its ancestors) is already contained in the
// array.
while (--idx > -1) {
node = ancestor = nodes[idx];
// Temporarily remove the node under consideration
nodes[idx] = null;
replace = true;
while (ancestor) {
if (nodes.includes(ancestor)) {
replace = false;
nodes.splice(idx, 1);
break;
}
ancestor = getParent(ancestor);
}
// If the node has been found to be unique, re-insert it.
if (replace) {
nodes[idx] = node;
}
}
return nodes;
};
/** @type {Adapter['findAll']} */ /** @type {Adapter['findAll']} */
const findAll = (test, elems) => { const findAll = (test, elems) => {
const result = []; const result = [];
@ -123,19 +77,68 @@ const findOne = (test, elems) => {
}; };
/** /**
* @type {Adapter} * @param {Map<XastNode, XastParent>} parents
* @returns {Adapter}
*/ */
export default { export function createAdapter(parents) {
isTag, /** @type {Adapter['getParent']} */
existsOne, const getParent = (node) => {
getAttributeValue, return parents.get(node) || null;
getChildren, };
getName,
getParent, /**
getSiblings, * @param {any} elem
getText, * @returns {any}
hasAttrib, */
removeSubsets, const getSiblings = (elem) => {
findAll, const parent = getParent(elem);
findOne, return parent ? getChildren(parent) : [];
}; };
/**
* @param {any} nodes
* @returns {any}
*/
const removeSubsets = (nodes) => {
let idx = nodes.length;
let node;
let ancestor;
let replace;
// Check if each node (or one of its ancestors) is already contained in the
// array.
while (--idx > -1) {
node = ancestor = nodes[idx];
// Temporarily remove the node under consideration
nodes[idx] = null;
replace = true;
while (ancestor) {
if (nodes.includes(ancestor)) {
replace = false;
nodes.splice(idx, 1);
break;
}
ancestor = getParent(ancestor);
}
// If the node has been found to be unique, re-insert it.
if (replace) {
nodes[idx] = node;
}
}
return nodes;
};
return {
isTag,
existsOne,
getAttributeValue,
getChildren,
getName,
getParent,
getSiblings,
getText,
hasAttrib,
removeSubsets,
findAll,
findOne,
};
}

View File

@ -1,8 +1,8 @@
import { selectAll, selectOne, is } from 'css-select'; import { selectAll, selectOne, is } from 'css-select';
import adapter from './svgo/css-select-adapter.js'; import { createAdapter } from './svgo/css-select-adapter.js';
/** /**
* @typedef {import('css-select').Options<XastNode & { children?: any }, XastElement & { parentNode?: any }>} Options * @typedef {import('css-select').Options<XastNode & { children?: any }, XastElement>} Options
* @typedef {import('./types.js').XastElement} XastElement * @typedef {import('./types.js').XastElement} XastElement
* @typedef {import('./types.js').XastNode} XastNode * @typedef {import('./types.js').XastNode} XastNode
* @typedef {import('./types.js').XastChild} XastChild * @typedef {import('./types.js').XastChild} XastChild
@ -10,37 +10,81 @@ import adapter from './svgo/css-select-adapter.js';
* @typedef {import('./types.js').Visitor} Visitor * @typedef {import('./types.js').Visitor} Visitor
*/ */
/** @type {Options} */ /**
const cssSelectOptions = { * @param {Map<XastNode, XastParent>} parents
xmlMode: true, * @returns {Options}
adapter, */
}; function createCssSelectOptions(parents) {
return {
xmlMode: true,
adapter: createAdapter(parents),
};
}
/** /**
* @param {XastNode} node Element to query the children of. * Maps all nodes to their parent node recursively.
*
* @param {XastParent} node
* @returns {Map<XastNode, XastParent>}
*/
export function mapNodesToParents(node) {
/** @type {Map<XastNode, XastParent>} */
const parents = new Map();
for (const child of node.children) {
parents.set(child, node);
visit(
child,
{
element: {
enter: (child, parent) => {
parents.set(child, parent);
},
},
},
node,
);
}
return parents;
}
/**
* @param {XastParent} node Element to query the children of.
* @param {string} selector CSS selector string. * @param {string} selector CSS selector string.
* @param {Map<XastNode, XastParent>=} parents
* @returns {XastChild[]} All matching elements. * @returns {XastChild[]} All matching elements.
*/ */
export const querySelectorAll = (node, selector) => { export const querySelectorAll = (
return selectAll(selector, node, cssSelectOptions); node,
selector,
parents = mapNodesToParents(node),
) => {
return selectAll(selector, node, createCssSelectOptions(parents));
}; };
/** /**
* @param {XastNode} node Element to query the children of. * @param {XastParent} node Element to query the children of.
* @param {string} selector CSS selector string. * @param {string} selector CSS selector string.
* @param {Map<XastNode, XastParent>=} parents
* @returns {?XastChild} First match, or null if there was no match. * @returns {?XastChild} First match, or null if there was no match.
*/ */
export const querySelector = (node, selector) => { export const querySelector = (
return selectOne(selector, node, cssSelectOptions); node,
selector,
parents = mapNodesToParents(node),
) => {
return selectOne(selector, node, createCssSelectOptions(parents));
}; };
/** /**
* @param {XastElement} node * @param {XastElement} node
* @param {string} selector * @param {string} selector
* @param {Map<XastNode, XastParent>=} parents
* @returns {boolean} * @returns {boolean}
*/ */
export const matches = (node, selector) => { export const matches = (node, selector, parents = mapNodesToParents(node)) => {
return is(node, selector, cssSelectOptions); return is(node, selector, createCssSelectOptions(parents));
}; };
export const visitSkip = Symbol(); export const visitSkip = Symbol();
@ -48,7 +92,7 @@ export const visitSkip = Symbol();
/** /**
* @param {XastNode} node * @param {XastNode} node
* @param {Visitor} visitor * @param {Visitor} visitor
* @param {?any} parentNode * @param {any=} parentNode
*/ */
export const visit = (node, visitor, parentNode = undefined) => { export const visit = (node, visitor, parentNode = undefined) => {
const callbacks = visitor[node.type]; const callbacks = visitor[node.type];

View File

@ -129,13 +129,6 @@ export const fn = (root) => {
// replace current node with all its children // replace current node with all its children
const index = parentNode.children.indexOf(node); const index = parentNode.children.indexOf(node);
parentNode.children.splice(index, 1, ...node.children); parentNode.children.splice(index, 1, ...node.children);
// TODO remove legacy parentNode in v4
for (const child of node.children) {
Object.defineProperty(child, 'parentNode', {
writable: true,
value: parentNode,
});
}
} }
}, },
}, },

View File

@ -83,11 +83,6 @@ export const fn = () => {
* @type {XastChild} * @type {XastChild}
*/ */
const child = { type: styleContentType, value: collectedStyles }; const child = { type: styleContentType, value: collectedStyles };
// TODO remove legacy parentNode in v4
Object.defineProperty(child, 'parentNode', {
writable: true,
value: firstStyleElement,
});
firstStyleElement.children = [child]; firstStyleElement.children = [child];
} }
}, },

View File

@ -55,14 +55,6 @@ export const fn = () => {
(child) => child.type !== 'text', (child) => child.type !== 'text',
); );
parentNode.children.splice(index, 1, ...usefulChildren); parentNode.children.splice(index, 1, ...usefulChildren);
// TODO remove legacy parentNode in v4
for (const child of node.children) {
Object.defineProperty(child, 'parentNode', {
writable: true,
value: parentNode,
});
}
} }
} }
}, },

View File

@ -32,13 +32,6 @@ export const fn = () => {
if (usefulNodes.length === 0) { if (usefulNodes.length === 0) {
detachNodeFromParent(node, parentNode); detachNodeFromParent(node, parentNode);
} }
// TODO remove legacy parentNode in v4
for (const usefulNode of usefulNodes) {
Object.defineProperty(usefulNode, 'parentNode', {
writable: true,
value: node,
});
}
node.children = usefulNodes; node.children = usefulNodes;
} }
}, },

View File

@ -92,11 +92,6 @@ export const fn = (root) => {
attributes: {}, attributes: {},
children: [], children: [],
}; };
// TODO remove legacy parentNode in v4
Object.defineProperty(defsTag, 'parentNode', {
writable: true,
value: node,
});
} }
let index = 0; let index = 0;
@ -129,11 +124,6 @@ export const fn = (root) => {
reusablePath.attributes.id = originalId; reusablePath.attributes.id = originalId;
delete list[0].attributes.id; delete list[0].attributes.id;
} }
// TODO remove legacy parentNode in v4
Object.defineProperty(reusablePath, 'parentNode', {
writable: true,
value: defsTag,
});
defsTag.children.push(reusablePath); defsTag.children.push(reusablePath);
// convert paths to <use> // convert paths to <use>
for (const pathNode of list) { for (const pathNode of list) {