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

Add visitSkip symbol (#1547)

This should help to avoid node.parentNode and closestByName
in some cases by skiping visiting children of current node.

Works only in `enter` listener.
This commit is contained in:
Bogdan Chadkin
2021-08-27 17:01:29 +03:00
committed by GitHub
parent ac8edbaf41
commit 3ca57d1856
4 changed files with 121 additions and 100 deletions

View File

@@ -52,7 +52,7 @@ export type XastParent = XastRoot | XastElement;
export type XastNode = XastRoot | XastChild; export type XastNode = XastRoot | XastChild;
type VisitorNode<Node> = { type VisitorNode<Node> = {
enter?: (node: Node, parentNode: XastParent) => void; enter?: (node: Node, parentNode: XastParent) => void | symbol;
exit?: (node: Node, parentNode: XastParent) => void; exit?: (node: Node, parentNode: XastParent) => void;
}; };

View File

@@ -75,6 +75,9 @@ const traverse = (node, fn) => {
}; };
exports.traverse = traverse; exports.traverse = traverse;
const visitSkip = Symbol();
exports.visitSkip = visitSkip;
/** /**
* @type {(node: XastNode, visitor: Visitor, parentNode?: any) => void} * @type {(node: XastNode, visitor: Visitor, parentNode?: any) => void}
*/ */
@@ -82,7 +85,10 @@ const visit = (node, visitor, parentNode) => {
const callbacks = visitor[node.type]; const callbacks = visitor[node.type];
if (callbacks && callbacks.enter) { if (callbacks && callbacks.enter) {
// @ts-ignore hard to infer // @ts-ignore hard to infer
callbacks.enter(node, parentNode); const symbol = callbacks.enter(node, parentNode);
if (symbol === visitSkip) {
return;
}
} }
// visit root children // visit root children
if (node.type === 'root') { if (node.type === 'root') {

View File

@@ -1,50 +1,37 @@
'use strict'; 'use strict';
const { visit, detachNodeFromParent } = require('./xast.js'); /**
* @typedef {import('./types').XastRoot} XastRoot
* @typedef {import('./types').XastElement} XastElement
*/
const getAst = () => { const { visit, visitSkip, detachNodeFromParent } = require('./xast.js');
const ast = {
type: 'root', /**
children: [ * @type {(children: Array<XastElement>) => XastRoot}
{ */
type: 'element', const root = (children) => {
name: 'g', return { type: 'root', children };
attributes: {},
children: [
{
type: 'element',
name: 'rect',
attributes: {},
children: [],
},
{
type: 'element',
name: 'circle',
attributes: {},
children: [],
},
],
},
{
type: 'element',
name: 'ellipse',
attributes: {},
children: [],
},
],
};
ast.children[0].parentNode = ast;
ast.children[0].children[0].parentNode = ast.children[0];
ast.children[0].children[1].parentNode = ast.children[0];
ast.children[1].parentNode = ast;
return ast;
}; };
describe('xast', () => { /**
it('enter into nodes', () => { * @type {(
const root = getAst(); * name: string,
* attrs?: null | Record<string, string>,
* children?: Array<XastElement>
* ) => XastElement}
*/
const x = (name, attrs = null, children = []) => {
return { type: 'element', name, attributes: attrs || {}, children };
};
test('visit enters into nodes', () => {
const ast = root([x('g', null, [x('rect'), x('circle')]), x('ellipse')]);
/**
* @type {Array<string>}
*/
const entered = []; const entered = [];
visit(root, { visit(ast, {
root: { root: {
enter: (node) => { enter: (node) => {
entered.push(node.type); entered.push(node.type);
@@ -65,10 +52,13 @@ describe('xast', () => {
]); ]);
}); });
it('exit from nodes', () => { test('visit exits from nodes', () => {
const root = getAst(); const ast = root([x('g', null, [x('rect'), x('circle')]), x('ellipse')]);
/**
* @type {Array<string>}
*/
const exited = []; const exited = [];
visit(root, { visit(ast, {
root: { root: {
exit: (node) => { exit: (node) => {
exited.push(node.type); exited.push(node.type);
@@ -89,10 +79,13 @@ describe('xast', () => {
]); ]);
}); });
it('skip entering children if node is detached', () => { test('visit skips entering children if node is detached', () => {
const root = getAst(); const ast = root([x('g', null, [x('rect'), x('circle')]), x('ellipse')]);
/**
* @type {Array<string>}
*/
const entered = []; const entered = [];
visit(root, { visit(ast, {
element: { element: {
enter: (node, parentNode) => { enter: (node, parentNode) => {
entered.push(node.name); entered.push(node.name);
@@ -103,5 +96,27 @@ describe('xast', () => {
}, },
}); });
expect(entered).toEqual(['g', 'ellipse']); expect(entered).toEqual(['g', 'ellipse']);
expect(ast).toEqual(root([x('ellipse')]));
}); });
test('visit skips entering children when symbol is passed', () => {
const ast = root([x('g', null, [x('rect'), x('circle')]), x('ellipse')]);
/**
* @type {Array<string>}
*/
const entered = [];
visit(ast, {
element: {
enter: (node) => {
entered.push(node.name);
if (node.name === 'g') {
return visitSkip;
}
},
},
});
expect(entered).toEqual(['g', 'ellipse']);
expect(ast).toEqual(
root([x('g', null, [x('rect'), x('circle')]), x('ellipse')])
);
}); });

View File

@@ -10,7 +10,7 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"noImplicitAny": true "noImplicitAny": true
}, },
"include": ["plugins/**/*"], "include": ["plugins/**/*", "lib/xast.test.js"],
"exclude": [ "exclude": [
"plugins/_applyTransforms.js", "plugins/_applyTransforms.js",
"plugins/cleanupIDs.js", "plugins/cleanupIDs.js",