|
|
'use strict'
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
|