215 lines
5.2 KiB
JavaScript
215 lines
5.2 KiB
JavaScript
|
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
|