import postcss, { list } from 'postcss'; // clean the raws of the node var cleanNode = (function (node) { node.raws = Object.assign(node.raws.between ? { between: node.raws.between } : {}, node.raws.semicolon ? { semicolon: true } : {}, node.raws.important ? { important: node.raws.important } : {}); return node; }); var comma = list.comma; // merge params var mergeParams = (function (fromParams, toParams) { return comma(fromParams).map(function (params1) { return comma(toParams).map(function (params2) { return params1 + ' and ' + params2; }).join(', '); }).join(', '); }); // move nodes after the current node into a cloned parent node var transformAfterNodes = (function (node) { // affected nodes after the current node var affectedNodes = node.parent.nodes.slice(node.parent.nodes.indexOf(node) + 1).map(cleanNode); if (affectedNodes.length) { // insert an empty parent clone after the parent var afterParent = cleanNode(node.parent.clone()).removeAll(); node.parent.after(afterParent); // append the affected nodes to the empty parent clone afterParent.append(affectedNodes); return afterParent; } return undefined; }); // transform a bubbling atrule (e.g. @document, @media, @supports) var transformBubblingAtrule = (function (node) { // clean node cleanNode(node); // affected nodes after the current node moved into a cloned parent node transformAfterNodes(node); // inner nodes within the current node var innerNodes = node.nodes.slice(0).map(cleanNode); // prepend an empty parent clone to the node var parentCloneForNodesWithinAtrule = cleanNode(node.parent.clone()).removeAll(); node.prepend(parentCloneForNodesWithinAtrule); // append the inner nodes to the empty parent clone parentCloneForNodesWithinAtrule.append(innerNodes); // swap semicolon raws var semicolon = node.raws.semicolon; node.raws.semicolon = node.parent.raws.semicolon; node.parent.raws.semicolon = semicolon; // move the node after the parent var parent = node.parent.after(node); if (!parent.nodes.length) { // conditionally remove the original empty parent parent.remove(); } // if the node and the parent are both media atrules if (node.parent.type === 'atrule' && node.name === node.parent.name) { // affected nodes after the current node moved into a cloned parent node transformAfterNodes(node); // merge media params node.params = mergeParams(node.parent.params, node.params); // move the node after the parent var subparent = node.parent.after(node); if (!subparent.nodes.length) { // conditionally remove the original empty parent subparent.remove(); } } }); // whether the node is a bubbling atrule (e.g. @document, @media, @supports) var test = function test(node) { return node.type === 'atrule' && ['document', 'media', 'supports'].indexOf(node.name) !== -1 && node.parent && node.parent.type === 'rule'; }; // return the closest rule var getClosestRule = (function (node) { var selectorParent = node.parent; while (selectorParent && selectorParent.type !== 'rule') { selectorParent = selectorParent.parent; } return selectorParent; }); // tooling var comma$1 = list.comma; // merge selectors var mergeSelectors = (function (fromSelectors, toSelectors) { return (typeof fromSelectors === 'string' ? comma$1(fromSelectors) : fromSelectors).reduce(function (selectors, fromSelector) { return selectors.concat((typeof toSelectors === 'string' ? comma$1(toSelectors) : toSelectors).map(function (toSelector) { return toSelector.replace(/&/g, fromSelector); })); }, []); }); var comma$2 = list.comma; // transform a nesting atrule (e.g. @nest .something &) var transformNestingAtRule = (function (node) { // clean node and child nodes cleanNode(node).nodes.forEach(cleanNode); // affected nodes after the current node moved into a cloned parent node var afterParent = transformAfterNodes(node); // get the closest rule var selectorParent = getClosestRule(node); // clone of the atrule as a rule var rule = postcss.rule({ // merge selectors selectors: mergeSelectors(selectorParent && selectorParent.selectors || '', node.params), source: node.source }); // clone atrule semicolon raws rule.raws = node.raws.semicolon ? { semicolon: true } : {}; // move the clone after the parent var parent = node.parent.after(rule); // remove the original node node.remove(); // move child nodes into the clone rule.append(node.nodes); if (!parent.nodes.length) { // conditionally remove the original empty parent parent.remove(); } // if the next sibling shares the same selector if (afterParent && afterParent.selector === rule.selector) { rule.append(afterParent.nodes); afterParent.remove(); } // if the previous sibling shares the same selector if (parent.parent && parent.next() === rule && parent.selector === rule.selector) { parent.append(rule.nodes); rule.remove(); return parent; } return rule; }); // whether the node is a nesting atrule (e.g. @nest .something &) var test$1 = function test(node) { return node.type === 'atrule' && node.name === 'nest' && node.parent && node.parent.type === 'rule' && comma$2(node.params).every(function (selector) { return selector.split('&').length === 2 && /&([^\w-|]|$)/.test(selector); }); }; // transform a nesting rule (e.g. &.something) var transformNestingRule = (function (node) { // clean node and child nodes cleanNode(node).nodes.forEach(cleanNode); // move nodes after the current node into a cloned parent node transformAfterNodes(node); // merge selectors node.selectors = mergeSelectors(node.parent.selectors, node.selectors); // move the node after the parent var parent = node.parent.after(node); if (!parent.nodes.length) { // conditionally remove the original empty parent parent.remove(); } }); // whether the node is a nesting rule (e.g. &.something) var test$2 = function test(node) { return node.type === 'rule' && node.parent && node.parent.type === 'rule' && node.selectors.every(function (selector) { return selector.trim().lastIndexOf('&') === 0 && /^&([^\w-|]|$)/.test(selector); }); }; // conditionally transform a nesting rule var transform = (function (node) { if (test(node)) { // conditionally transform a bubbling atrule transformBubblingAtrule(node); } else if (test$1(node)) { // conditionally transform a nesting atrule transformNestingAtRule(node); } else if (test$2(node)) { // conditionally transform a nesting rule transformNestingRule(node); } }); var index = postcss.plugin('postcss-nesting', function () { return function (root) { return root.walk(transform); }; }); export default index;