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}
*/
const pushToContent = (node) => {
// TODO remove legacy parentNode in v4
Object.defineProperty(node, 'parentNode', {
writable: true,
value: current,
});
current.children.push(node);
};

View File

@ -10,15 +10,16 @@ import {
/**
* @typedef {import('css-tree').Rule} CsstreeRule
* @typedef {import('./types.js').ComputedStyles} ComputedStyles
* @typedef {import('./types.js').Specificity} Specificity
* @typedef {import('./types.js').Stylesheet} Stylesheet
* @typedef {import('./types.js').StylesheetRule} StylesheetRule
* @typedef {import('./types.js').StylesheetDeclaration} StylesheetDeclaration
* @typedef {import('./types.js').ComputedStyles} ComputedStyles
* @typedef {import('./types.js').XastRoot} XastRoot
* @typedef {import('./types.js').XastElement} XastElement
* @typedef {import('./types.js').XastParent} XastParent
* @typedef {import('./types.js').StylesheetRule} StylesheetRule
* @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;
@ -130,9 +131,10 @@ const parseStyleDeclarations = (css) => {
/**
* @param {Stylesheet} stylesheet
* @param {XastElement} node
* @param {Map<XastNode, XastParent>=} parents
* @returns {ComputedStyles}
*/
const computeOwnStyle = (stylesheet, node) => {
const computeOwnStyle = (stylesheet, node, parents = undefined) => {
/** @type {ComputedStyles} */
const computedStyle = {};
const importantStyles = new Map();
@ -147,7 +149,7 @@ const computeOwnStyle = (stylesheet, node) => {
// collect matching 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) {
const computed = computedStyle[name];
if (computed && computed.type === 'dynamic') {
@ -259,10 +261,10 @@ export const collectStylesheet = (root) => {
*/
export const computeStyle = (stylesheet, node) => {
const { parents } = stylesheet;
const computedStyles = computeOwnStyle(stylesheet, node);
const computedStyles = computeOwnStyle(stylesheet, node, parents);
let parent = parents.get(node);
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)) {
if (
computedStyles[name] == null &&

View File

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

View File

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

2
lib/svgo.d.ts vendored
View File

@ -162,4 +162,4 @@ export declare const VERSION: string;
/** The core of SVGO */
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 { encodeSVGDatauri } from './svgo/tools.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';
/**
@ -78,6 +78,7 @@ const resolvePluginConfig = (plugin) => {
export {
VERSION,
builtin as builtinPlugins,
mapNodesToParents,
querySelector,
querySelectorAll,
_collections,
@ -147,6 +148,7 @@ export default {
VERSION,
optimize,
builtinPlugins: builtin,
mapNodesToParents,
querySelector,
querySelectorAll,
_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').XastElement} XastElement
* @typedef {import('../types.js').XastNode} XastNode
@ -33,20 +33,6 @@ const getName = (elemAst) => {
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']} */
const getText = (node) => {
if (node.children[0].type === 'text' || node.children[0].type === 'cdata') {
@ -60,38 +46,6 @@ const hasAttrib = (elem, name) => {
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']} */
const findAll = (test, elems) => {
const result = [];
@ -123,19 +77,68 @@ const findOne = (test, elems) => {
};
/**
* @type {Adapter}
* @param {Map<XastNode, XastParent>} parents
* @returns {Adapter}
*/
export default {
isTag,
existsOne,
getAttributeValue,
getChildren,
getName,
getParent,
getSiblings,
getText,
hasAttrib,
removeSubsets,
findAll,
findOne,
};
export function createAdapter(parents) {
/** @type {Adapter['getParent']} */
const getParent = (node) => {
return parents.get(node) || null;
};
/**
* @param {any} elem
* @returns {any}
*/
const getSiblings = (elem) => {
const parent = getParent(elem);
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 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').XastNode} XastNode
* @typedef {import('./types.js').XastChild} XastChild
@ -10,37 +10,81 @@ import adapter from './svgo/css-select-adapter.js';
* @typedef {import('./types.js').Visitor} Visitor
*/
/** @type {Options} */
const cssSelectOptions = {
xmlMode: true,
adapter,
};
/**
* @param {Map<XastNode, XastParent>} parents
* @returns {Options}
*/
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 {Map<XastNode, XastParent>=} parents
* @returns {XastChild[]} All matching elements.
*/
export const querySelectorAll = (node, selector) => {
return selectAll(selector, node, cssSelectOptions);
export const querySelectorAll = (
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 {Map<XastNode, XastParent>=} parents
* @returns {?XastChild} First match, or null if there was no match.
*/
export const querySelector = (node, selector) => {
return selectOne(selector, node, cssSelectOptions);
export const querySelector = (
node,
selector,
parents = mapNodesToParents(node),
) => {
return selectOne(selector, node, createCssSelectOptions(parents));
};
/**
* @param {XastElement} node
* @param {string} selector
* @param {Map<XastNode, XastParent>=} parents
* @returns {boolean}
*/
export const matches = (node, selector) => {
return is(node, selector, cssSelectOptions);
export const matches = (node, selector, parents = mapNodesToParents(node)) => {
return is(node, selector, createCssSelectOptions(parents));
};
export const visitSkip = Symbol();
@ -48,7 +92,7 @@ export const visitSkip = Symbol();
/**
* @param {XastNode} node
* @param {Visitor} visitor
* @param {?any} parentNode
* @param {any=} parentNode
*/
export const visit = (node, visitor, parentNode = undefined) => {
const callbacks = visitor[node.type];

View File

@ -129,13 +129,6 @@ export const fn = (root) => {
// replace current node with all its children
const index = parentNode.children.indexOf(node);
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}
*/
const child = { type: styleContentType, value: collectedStyles };
// TODO remove legacy parentNode in v4
Object.defineProperty(child, 'parentNode', {
writable: true,
value: firstStyleElement,
});
firstStyleElement.children = [child];
}
},

View File

@ -55,14 +55,6 @@ export const fn = () => {
(child) => child.type !== 'text',
);
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) {
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;
}
},

View File

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