You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
214 lines
5.2 KiB
214 lines
5.2 KiB
let parser = require('postcss-selector-parser') |
|
|
|
function parse (str, rule) { |
|
let nodes |
|
let saver = parser(parsed => { |
|
nodes = parsed |
|
}) |
|
try { |
|
saver.processSync(str) |
|
} catch (e) { |
|
if (str.includes(':')) { |
|
throw rule ? rule.error('Missed semicolon') : e |
|
} else { |
|
throw rule ? rule.error(e.message) : e |
|
} |
|
} |
|
return nodes.at(0) |
|
} |
|
|
|
function replace (nodes, parent) { |
|
let replaced = false |
|
nodes.each(i => { |
|
if (i.type === 'nesting') { |
|
let clonedParent = parent.clone() |
|
if (i.value !== '&') { |
|
i.replaceWith(parse(i.value.replace('&', clonedParent.toString()))) |
|
} else { |
|
i.replaceWith(clonedParent) |
|
} |
|
replaced = true |
|
} else if (i.nodes) { |
|
if (replace(i, parent)) { |
|
replaced = true |
|
} |
|
} |
|
}) |
|
return replaced |
|
} |
|
|
|
function selectors (parent, child) { |
|
let result = [] |
|
parent.selectors.forEach(i => { |
|
let parentNode = parse(i, parent) |
|
|
|
child.selectors.forEach(j => { |
|
if (j.length) { |
|
let node = parse(j, child) |
|
let replaced = replace(node, parentNode) |
|
if (!replaced) { |
|
node.prepend(parser.combinator({ value: ' ' })) |
|
node.prepend(parentNode.clone()) |
|
} |
|
result.push(node.toString()) |
|
} |
|
}) |
|
}) |
|
return result |
|
} |
|
|
|
function pickComment (comment, after) { |
|
if (comment && comment.type === 'comment') { |
|
after.after(comment) |
|
return comment |
|
} else { |
|
return after |
|
} |
|
} |
|
|
|
function createFnAtruleChilds (bubble) { |
|
return function atruleChilds (rule, atrule, bubbling) { |
|
let children = [] |
|
atrule.each(child => { |
|
if (child.type === 'comment') { |
|
children.push(child) |
|
} else if (child.type === 'decl') { |
|
children.push(child) |
|
} else if (child.type === 'rule' && bubbling) { |
|
child.selectors = selectors(rule, child) |
|
} else if (child.type === 'atrule') { |
|
if (child.nodes && bubble[child.name]) { |
|
atruleChilds(rule, child, true) |
|
} else { |
|
children.push(child) |
|
} |
|
} |
|
}) |
|
if (bubbling) { |
|
if (children.length) { |
|
let clone = rule.clone({ nodes: [] }) |
|
for (let child of children) { |
|
clone.append(child) |
|
} |
|
atrule.prepend(clone) |
|
} |
|
} |
|
} |
|
} |
|
|
|
function pickDeclarations (selector, declarations, after, Rule) { |
|
let parent = new Rule({ |
|
selector, |
|
nodes: [] |
|
}) |
|
|
|
for (let declaration of declarations) { |
|
parent.append(declaration) |
|
} |
|
|
|
after.after(parent) |
|
return parent |
|
} |
|
|
|
function atruleNames (defaults, custom) { |
|
let list = {} |
|
for (let i of defaults) { |
|
list[i] = true |
|
} |
|
if (custom) { |
|
for (let i of custom) { |
|
let name = i.replace(/^@/, '') |
|
list[name] = true |
|
} |
|
} |
|
return list |
|
} |
|
|
|
module.exports = (opts = {}) => { |
|
let bubble = atruleNames(['media', 'supports'], opts.bubble) |
|
let atruleChilds = createFnAtruleChilds(bubble) |
|
let unwrap = atruleNames( |
|
[ |
|
'document', |
|
'font-face', |
|
'keyframes', |
|
'-webkit-keyframes', |
|
'-moz-keyframes' |
|
], |
|
opts.unwrap |
|
) |
|
let preserveEmpty = opts.preserveEmpty |
|
|
|
return { |
|
postcssPlugin: 'postcss-nested', |
|
Rule (rule, { Rule }) { |
|
let unwrapped = false |
|
let after = rule |
|
let copyDeclarations = false |
|
let declarations = [] |
|
|
|
rule.each(child => { |
|
if (child.type === 'rule') { |
|
if (declarations.length) { |
|
after = pickDeclarations(rule.selector, declarations, after, Rule) |
|
declarations = [] |
|
} |
|
|
|
copyDeclarations = true |
|
unwrapped = true |
|
child.selectors = selectors(rule, child) |
|
after = pickComment(child.prev(), after) |
|
after.after(child) |
|
after = child |
|
} else if (child.type === 'atrule') { |
|
if (declarations.length) { |
|
after = pickDeclarations(rule.selector, declarations, after, Rule) |
|
declarations = [] |
|
} |
|
|
|
if (child.name === 'at-root') { |
|
unwrapped = true |
|
atruleChilds(rule, child, false) |
|
|
|
let nodes = child.nodes |
|
if (child.params) { |
|
nodes = new Rule({ selector: child.params, nodes }) |
|
} |
|
|
|
after.after(nodes) |
|
after = nodes |
|
child.remove() |
|
} else if (bubble[child.name]) { |
|
copyDeclarations = true |
|
unwrapped = true |
|
atruleChilds(rule, child, true) |
|
after = pickComment(child.prev(), after) |
|
after.after(child) |
|
after = child |
|
} else if (unwrap[child.name]) { |
|
copyDeclarations = true |
|
unwrapped = true |
|
atruleChilds(rule, child, false) |
|
after = pickComment(child.prev(), after) |
|
after.after(child) |
|
after = child |
|
} else if (copyDeclarations) { |
|
declarations.push(child) |
|
} |
|
} else if (child.type === 'decl' && copyDeclarations) { |
|
declarations.push(child) |
|
} |
|
}) |
|
|
|
if (declarations.length) { |
|
after = pickDeclarations(rule.selector, declarations, after, Rule) |
|
} |
|
|
|
if (unwrapped && preserveEmpty !== true) { |
|
rule.raws.semicolon = true |
|
if (rule.nodes.length === 0) rule.remove() |
|
} |
|
} |
|
} |
|
} |
|
module.exports.postcss = true
|
|
|