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