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

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