Files
2024-12-17 13:23:11 -08:00

141 lines
4.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import postcss from 'postcss';
import nesting from 'postcss-nesting';
// functional selector match
var functionalSelectorMatch = /(^|[^\w-])(%[_a-zA-Z]+[_a-zA-Z0-9-]*)([^\w-]|$)/i;
// plugin
var index = postcss.plugin('postcss-extend-rule', function (rawopts) {
// options ( onFunctionalSelector, onRecursiveExtend, onUnusedExtend)
var opts = Object(rawopts);
var extendMatch = opts.name instanceof RegExp ? opts.name : 'name' in opts ? new RegExp(`^${opts.name}$`, 'i') : 'extend';
return function (root, result) {
// for each extend at-rule
root.walkAtRules(extendMatch, function (extendAtRule) {
// do not revisit visited extend at-rules
if (!extendAtRule.__extendAtRuleVisited) {
extendAtRule.__extendAtRuleVisited = true;
// selector identifier
var selectorIdMatch = getSelectorIdMatch(extendAtRule.params);
// extending rules
var extendingRules = getExtendingRules(selectorIdMatch, extendAtRule);
// if there are extending rules
if (extendingRules.length) {
// replace the extend at-rule with the extending rules
extendAtRule.replaceWith(extendingRules);
// transform any nesting at-rules
extendingRules.forEach(function (extendingRule) {
transform(extendingRule);
extendingRule.walk(transform);
});
} else {
// manage unused extend at-rules
var unusedExtendMessage = `Unused extend at-rule "${extendAtRule.params}"`;
if (opts.onUnusedExtend === 'throw') {
throw extendAtRule.error(unusedExtendMessage, { word: extendAtRule.name });
} else if (opts.onUnusedExtend === 'warn') {
extendAtRule.warn(result, unusedExtendMessage);
} else if (opts.onUnusedExtend !== 'ignore') {
extendAtRule.remove();
}
}
} else {
// manage revisited extend at-rules
var revisitedExtendMessage = `Revisited extend at-rule "${extendAtRule.params}"`;
if (opts.onRecursiveExtend === 'throw') {
throw extendAtRule.error(revisitedExtendMessage, { word: extendAtRule.name });
} else if (opts.onRecursiveExtend === 'warn') {
extendAtRule.warn(result, revisitedExtendMessage);
} else if (opts.onRecursiveExtend !== 'ignore') {
extendAtRule.remove();
}
}
});
root.walkRules(functionalSelectorMatch, function (functionalRule) {
// manage encountered functional selectors
var functionalSelectorMessage = `Encountered functional selector "${functionalRule.selector}"`;
if (opts.onFunctionalSelector === 'throw') {
throw functionalRule.error(functionalSelectorMessage, { word: functionalRule.selector.match(functionalSelectorMatch)[1] });
} else if (opts.onFunctionalSelector === 'warn') {
functionalRule.warn(result, functionalSelectorMessage);
} else if (opts.onFunctionalSelector !== 'ignore') {
functionalRule.remove();
}
});
};
});
function transform(node) {
return nesting()({
walk(transformer) {
return transformer(node);
}
});
}
function getExtendingRules(selectorIdMatch, extendAtRule) {
// extending rules
var extendingRules = [];
// for each rule found from root of the extend at-rule with a matching selector identifier
extendAtRule.root().walkRules(selectorIdMatch, function (matchingRule) {
// nesting selectors for the selectors matching the selector identifier
var nestingSelectors = matchingRule.selectors.filter(function (selector) {
return selectorIdMatch.test(selector);
}).map(function (selector) {
return selector.replace(selectorIdMatch, '$1&$3');
}).join(',');
// matching rules cloned nodes
var nestingNodes = matchingRule.clone().nodes;
// clone the matching rule as a nested rule
var clone = extendAtRule.clone({
name: 'nest',
params: nestingSelectors,
nodes: nestingNodes,
// empty the extending rules, as they are likely non-comforming
raws: {}
});
// preserve nesting of parent rules and at-rules
var parent = matchingRule.parent;
while (parent && (parent.type === 'rule' || parent.type === 'atrule')) {
clone = parent.clone().removeAll().append([clone]);
parent = parent.parent;
}
// push the matching rule to the extending rules
extendingRules.push(clone);
});
// return the extending rules
return extendingRules;
}
function getSelectorIdMatch(selectorIds) {
// escape the contents of the selector id to avoid being parsed as regex
var escapedSelectorIds = postcss.list.comma(selectorIds).map(function (selectorId) {
return selectorId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}).join('|');
// selector unattached to an existing selector
var selectorIdMatch = new RegExp(`(^|[^\\w-])(${escapedSelectorIds})([^\\w-]|$)`, '');
return selectorIdMatch;
}
export default index;