Spaces:
Running
Running
const DEFAULT_RAW = { | |
colon: ': ', | |
indent: ' ', | |
beforeDecl: '\n', | |
beforeRule: '\n', | |
beforeOpen: ' ', | |
beforeClose: '\n', | |
beforeComment: '\n', | |
after: '\n', | |
emptyBody: '', | |
commentLeft: ' ', | |
commentRight: ' ', | |
semicolon: false | |
} | |
function capitalize(str) { | |
return str[0].toUpperCase() + str.slice(1) | |
} | |
class Stringifier { | |
constructor(builder) { | |
this.builder = builder | |
} | |
stringify(node, semicolon) { | |
/* c8 ignore start */ | |
if (!this[node.type]) { | |
throw new Error( | |
'Unknown AST node type ' + | |
node.type + | |
'. ' + | |
'Maybe you need to change PostCSS stringifier.' | |
) | |
} | |
/* c8 ignore stop */ | |
this[node.type](node, semicolon) | |
} | |
document(node) { | |
this.body(node) | |
} | |
root(node) { | |
this.body(node) | |
if (node.raws.after) this.builder(node.raws.after) | |
} | |
comment(node) { | |
let left = this.raw(node, 'left', 'commentLeft') | |
let right = this.raw(node, 'right', 'commentRight') | |
this.builder('/*' + left + node.text + right + '*/', node) | |
} | |
decl(node, semicolon) { | |
let between = this.raw(node, 'between', 'colon') | |
let string = node.prop + between + this.rawValue(node, 'value') | |
if (node.important) { | |
string += node.raws.important || ' !important' | |
} | |
if (semicolon) string += ';' | |
this.builder(string, node) | |
} | |
rule(node) { | |
this.block(node, this.rawValue(node, 'selector')) | |
if (node.raws.ownSemicolon) { | |
this.builder(node.raws.ownSemicolon, node, 'end') | |
} | |
} | |
atrule(node, semicolon) { | |
let name = '@' + node.name | |
let params = node.params ? this.rawValue(node, 'params') : '' | |
if (typeof node.raws.afterName !== 'undefined') { | |
name += node.raws.afterName | |
} else if (params) { | |
name += ' ' | |
} | |
if (node.nodes) { | |
this.block(node, name + params) | |
} else { | |
let end = (node.raws.between || '') + (semicolon ? ';' : '') | |
this.builder(name + params + end, node) | |
} | |
} | |
body(node) { | |
let last = node.nodes.length - 1 | |
while (last > 0) { | |
if (node.nodes[last].type !== 'comment') break | |
last -= 1 | |
} | |
let semicolon = this.raw(node, 'semicolon') | |
for (let i = 0; i < node.nodes.length; i++) { | |
let child = node.nodes[i] | |
let before = this.raw(child, 'before') | |
if (before) this.builder(before) | |
this.stringify(child, last !== i || semicolon) | |
} | |
} | |
block(node, start) { | |
let between = this.raw(node, 'between', 'beforeOpen') | |
this.builder(start + between + '{', node, 'start') | |
let after | |
if (node.nodes && node.nodes.length) { | |
this.body(node) | |
after = this.raw(node, 'after') | |
} else { | |
after = this.raw(node, 'after', 'emptyBody') | |
} | |
if (after) this.builder(after) | |
this.builder('}', node, 'end') | |
} | |
raw(node, own, detect) { | |
let value | |
if (!detect) detect = own | |
// Already had | |
if (own) { | |
value = node.raws[own] | |
if (typeof value !== 'undefined') return value | |
} | |
let parent = node.parent | |
if (detect === 'before') { | |
// Hack for first rule in CSS | |
if (!parent || (parent.type === 'root' && parent.first === node)) { | |
return '' | |
} | |
// `root` nodes in `document` should use only their own raws | |
if (parent && parent.type === 'document') { | |
return '' | |
} | |
} | |
// Floating child without parent | |
if (!parent) return DEFAULT_RAW[detect] | |
// Detect style by other nodes | |
let root = node.root() | |
if (!root.rawCache) root.rawCache = {} | |
if (typeof root.rawCache[detect] !== 'undefined') { | |
return root.rawCache[detect] | |
} | |
if (detect === 'before' || detect === 'after') { | |
return this.beforeAfter(node, detect) | |
} else { | |
let method = 'raw' + capitalize(detect) | |
if (this[method]) { | |
value = this[method](root, node) | |
} else { | |
root.walk(i => { | |
value = i.raws[own] | |
if (typeof value !== 'undefined') return false | |
}) | |
} | |
} | |
if (typeof value === 'undefined') value = DEFAULT_RAW[detect] | |
root.rawCache[detect] = value | |
return value | |
} | |
rawSemicolon(root) { | |
let value | |
root.walk(i => { | |
if (i.nodes && i.nodes.length && i.last.type === 'decl') { | |
value = i.raws.semicolon | |
if (typeof value !== 'undefined') return false | |
} | |
}) | |
return value | |
} | |
rawEmptyBody(root) { | |
let value | |
root.walk(i => { | |
if (i.nodes && i.nodes.length === 0) { | |
value = i.raws.after | |
if (typeof value !== 'undefined') return false | |
} | |
}) | |
return value | |
} | |
rawIndent(root) { | |
if (root.raws.indent) return root.raws.indent | |
let value | |
root.walk(i => { | |
let p = i.parent | |
if (p && p !== root && p.parent && p.parent === root) { | |
if (typeof i.raws.before !== 'undefined') { | |
let parts = i.raws.before.split('\n') | |
value = parts[parts.length - 1] | |
value = value.replace(/\S/g, '') | |
return false | |
} | |
} | |
}) | |
return value | |
} | |
rawBeforeComment(root, node) { | |
let value | |
root.walkComments(i => { | |
if (typeof i.raws.before !== 'undefined') { | |
value = i.raws.before | |
if (value.includes('\n')) { | |
value = value.replace(/[^\n]+$/, '') | |
} | |
return false | |
} | |
}) | |
if (typeof value === 'undefined') { | |
value = this.raw(node, null, 'beforeDecl') | |
} else if (value) { | |
value = value.replace(/\S/g, '') | |
} | |
return value | |
} | |
rawBeforeDecl(root, node) { | |
let value | |
root.walkDecls(i => { | |
if (typeof i.raws.before !== 'undefined') { | |
value = i.raws.before | |
if (value.includes('\n')) { | |
value = value.replace(/[^\n]+$/, '') | |
} | |
return false | |
} | |
}) | |
if (typeof value === 'undefined') { | |
value = this.raw(node, null, 'beforeRule') | |
} else if (value) { | |
value = value.replace(/\S/g, '') | |
} | |
return value | |
} | |
rawBeforeRule(root) { | |
let value | |
root.walk(i => { | |
if (i.nodes && (i.parent !== root || root.first !== i)) { | |
if (typeof i.raws.before !== 'undefined') { | |
value = i.raws.before | |
if (value.includes('\n')) { | |
value = value.replace(/[^\n]+$/, '') | |
} | |
return false | |
} | |
} | |
}) | |
if (value) value = value.replace(/\S/g, '') | |
return value | |
} | |
rawBeforeClose(root) { | |
let value | |
root.walk(i => { | |
if (i.nodes && i.nodes.length > 0) { | |
if (typeof i.raws.after !== 'undefined') { | |
value = i.raws.after | |
if (value.includes('\n')) { | |
value = value.replace(/[^\n]+$/, '') | |
} | |
return false | |
} | |
} | |
}) | |
if (value) value = value.replace(/\S/g, '') | |
return value | |
} | |
rawBeforeOpen(root) { | |
let value | |
root.walk(i => { | |
if (i.type !== 'decl') { | |
value = i.raws.between | |
if (typeof value !== 'undefined') return false | |
} | |
}) | |
return value | |
} | |
rawColon(root) { | |
let value | |
root.walkDecls(i => { | |
if (typeof i.raws.between !== 'undefined') { | |
value = i.raws.between.replace(/[^\s:]/g, '') | |
return false | |
} | |
}) | |
return value | |
} | |
beforeAfter(node, detect) { | |
let value | |
if (node.type === 'decl') { | |
value = this.raw(node, null, 'beforeDecl') | |
} else if (node.type === 'comment') { | |
value = this.raw(node, null, 'beforeComment') | |
} else if (detect === 'before') { | |
value = this.raw(node, null, 'beforeRule') | |
} else { | |
value = this.raw(node, null, 'beforeClose') | |
} | |
let buf = node.parent | |
let depth = 0 | |
while (buf && buf.type !== 'root') { | |
depth += 1 | |
buf = buf.parent | |
} | |
if (value.includes('\n')) { | |
let indent = this.raw(node, null, 'indent') | |
if (indent.length) { | |
for (let step = 0; step < depth; step++) value += indent | |
} | |
} | |
return value | |
} | |
rawValue(node, prop) { | |
let value = node[prop] | |
let raw = node.raws[prop] | |
if (raw && raw.value === value) { | |
return raw.raw | |
} | |
return value | |
} | |
} | |
module.exports = Stringifier | |
Stringifier.default = Stringifier | |