mirror of
https://github.com/svg/svgo.git
synced 2025-04-20 21:07:45 +03:00
Added Typescript support via JSDoc comments for some files. The code has really outdated type signatures, so in order to fully type the codebase the changes need to be incremental. To check the types run: npx tsc
229 lines
6.2 KiB
JavaScript
229 lines
6.2 KiB
JavaScript
'use strict';
|
|
|
|
var csstree = require('css-tree'),
|
|
List = csstree.List,
|
|
stable = require('stable'),
|
|
specificity = require('csso/lib/restructure/prepare/specificity');
|
|
|
|
/**
|
|
* Flatten a CSS AST to a selectors list.
|
|
*
|
|
* @param {import('css-tree').CssNode} cssAst css-tree AST to flatten
|
|
* @return {Array} selectors
|
|
*/
|
|
function flattenToSelectors(cssAst) {
|
|
var selectors = [];
|
|
|
|
csstree.walk(cssAst, {
|
|
visit: 'Rule',
|
|
enter: function (node) {
|
|
if (node.type !== 'Rule') {
|
|
return;
|
|
}
|
|
|
|
var atrule = this.atrule;
|
|
var rule = node;
|
|
|
|
node.prelude.children.each(function (selectorNode, selectorItem) {
|
|
var selector = {
|
|
item: selectorItem,
|
|
atrule: atrule,
|
|
rule: rule,
|
|
pseudos: /** @type {{item: any; list: any[]}[]} */ ([]),
|
|
};
|
|
|
|
selectorNode.children.each(function (
|
|
selectorChildNode,
|
|
selectorChildItem,
|
|
selectorChildList
|
|
) {
|
|
if (
|
|
selectorChildNode.type === 'PseudoClassSelector' ||
|
|
selectorChildNode.type === 'PseudoElementSelector'
|
|
) {
|
|
selector.pseudos.push({
|
|
item: selectorChildItem,
|
|
list: selectorChildList,
|
|
});
|
|
}
|
|
});
|
|
|
|
selectors.push(selector);
|
|
});
|
|
},
|
|
});
|
|
|
|
return selectors;
|
|
}
|
|
|
|
/**
|
|
* Filter selectors by Media Query.
|
|
*
|
|
* @param {Array} selectors to filter
|
|
* @param {Array} useMqs Array with strings of media queries that should pass (<name> <expression>)
|
|
* @return {Array} Filtered selectors that match the passed media queries
|
|
*/
|
|
function filterByMqs(selectors, useMqs) {
|
|
return selectors.filter(function (selector) {
|
|
if (selector.atrule === null) {
|
|
return ~useMqs.indexOf('');
|
|
}
|
|
|
|
var mqName = selector.atrule.name;
|
|
var mqStr = mqName;
|
|
if (
|
|
selector.atrule.expression &&
|
|
selector.atrule.expression.children.first().type === 'MediaQueryList'
|
|
) {
|
|
var mqExpr = csstree.generate(selector.atrule.expression);
|
|
mqStr = [mqName, mqExpr].join(' ');
|
|
}
|
|
|
|
return ~useMqs.indexOf(mqStr);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Filter selectors by the pseudo-elements and/or -classes they contain.
|
|
*
|
|
* @param {Array} selectors to filter
|
|
* @param {Array} usePseudos Array with strings of single or sequence of pseudo-elements and/or -classes that should pass
|
|
* @return {Array} Filtered selectors that match the passed pseudo-elements and/or -classes
|
|
*/
|
|
function filterByPseudos(selectors, usePseudos) {
|
|
return selectors.filter(function (selector) {
|
|
var pseudoSelectorsStr = csstree.generate({
|
|
type: 'Selector',
|
|
children: new List().fromArray(
|
|
selector.pseudos.map(function (pseudo) {
|
|
return pseudo.item.data;
|
|
})
|
|
),
|
|
});
|
|
return ~usePseudos.indexOf(pseudoSelectorsStr);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Remove pseudo-elements and/or -classes from the selectors for proper matching.
|
|
*
|
|
* @param {Array} selectors to clean
|
|
* @return {void}
|
|
*/
|
|
function cleanPseudos(selectors) {
|
|
selectors.forEach(function (selector) {
|
|
selector.pseudos.forEach(function (pseudo) {
|
|
pseudo.list.remove(pseudo.item);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Compares two selector specificities.
|
|
* extracted from https://github.com/keeganstreet/specificity/blob/master/specificity.js#L211
|
|
*
|
|
* @param {Array} aSpecificity Specificity of selector A
|
|
* @param {Array} bSpecificity Specificity of selector B
|
|
* @return {number} Score of selector specificity A compared to selector specificity B
|
|
*/
|
|
function compareSpecificity(aSpecificity, bSpecificity) {
|
|
for (var i = 0; i < 4; i += 1) {
|
|
if (aSpecificity[i] < bSpecificity[i]) {
|
|
return -1;
|
|
} else if (aSpecificity[i] > bSpecificity[i]) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Compare two simple selectors.
|
|
*
|
|
* @param {Object} aSimpleSelectorNode Simple selector A
|
|
* @param {Object} bSimpleSelectorNode Simple selector B
|
|
* @return {number} Score of selector A compared to selector B
|
|
*/
|
|
function compareSimpleSelectorNode(aSimpleSelectorNode, bSimpleSelectorNode) {
|
|
var aSpecificity = specificity(aSimpleSelectorNode),
|
|
bSpecificity = specificity(bSimpleSelectorNode);
|
|
return compareSpecificity(aSpecificity, bSpecificity);
|
|
}
|
|
|
|
function _bySelectorSpecificity(selectorA, selectorB) {
|
|
return compareSimpleSelectorNode(selectorA.item.data, selectorB.item.data);
|
|
}
|
|
|
|
/**
|
|
* Sort selectors stably by their specificity.
|
|
*
|
|
* @param {Array} selectors to be sorted
|
|
* @return {Array} Stable sorted selectors
|
|
*/
|
|
function sortSelectors(selectors) {
|
|
return stable(selectors, _bySelectorSpecificity);
|
|
}
|
|
|
|
/**
|
|
* Convert a css-tree AST style declaration to CSSStyleDeclaration property.
|
|
*
|
|
* @param {import('css-tree').CssNode} declaration css-tree style declaration
|
|
* @return {Object} CSSStyleDeclaration property
|
|
*/
|
|
function csstreeToStyleDeclaration(declaration) {
|
|
var propertyName = declaration.property,
|
|
propertyValue = csstree.generate(declaration.value),
|
|
propertyPriority = declaration.important ? 'important' : '';
|
|
return {
|
|
name: propertyName,
|
|
value: propertyValue,
|
|
priority: propertyPriority,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Gets the CSS string of a style element
|
|
*
|
|
* @param {Object} elem style element
|
|
* @return {string|Array} CSS string or empty array if no styles are set
|
|
*/
|
|
function getCssStr(elem) {
|
|
return elem.content[0].text || elem.content[0].cdata || [];
|
|
}
|
|
|
|
/**
|
|
* Sets the CSS string of a style element
|
|
*
|
|
* @param {Object} elem style element
|
|
* @param {string} css string to be set
|
|
* @return {Object} reference to field with CSS
|
|
*/
|
|
function setCssStr(elem, css) {
|
|
// in case of cdata field
|
|
if (elem.content[0].cdata) {
|
|
elem.content[0].cdata = css;
|
|
return elem.content[0].cdata;
|
|
}
|
|
|
|
// in case of text field + if nothing was set yet
|
|
elem.content[0].text = css;
|
|
return elem.content[0].text;
|
|
}
|
|
|
|
module.exports.flattenToSelectors = flattenToSelectors;
|
|
|
|
module.exports.filterByMqs = filterByMqs;
|
|
module.exports.filterByPseudos = filterByPseudos;
|
|
module.exports.cleanPseudos = cleanPseudos;
|
|
|
|
module.exports.compareSpecificity = compareSpecificity;
|
|
module.exports.compareSimpleSelectorNode = compareSimpleSelectorNode;
|
|
|
|
module.exports.sortSelectors = sortSelectors;
|
|
|
|
module.exports.csstreeToStyleDeclaration = csstreeToStyleDeclaration;
|
|
|
|
module.exports.getCssStr = getCssStr;
|
|
module.exports.setCssStr = setCssStr;
|