[6a3a178] | 1 | 'use strict'
|
---|
| 2 |
|
---|
| 3 | const DEFAULT_RAW = {
|
---|
| 4 | colon: ': ',
|
---|
| 5 | indent: ' ',
|
---|
| 6 | beforeDecl: '\n',
|
---|
| 7 | beforeRule: '\n',
|
---|
| 8 | beforeOpen: ' ',
|
---|
| 9 | beforeClose: '\n',
|
---|
| 10 | beforeComment: '\n',
|
---|
| 11 | after: '\n',
|
---|
| 12 | emptyBody: '',
|
---|
| 13 | commentLeft: ' ',
|
---|
| 14 | commentRight: ' ',
|
---|
| 15 | semicolon: false
|
---|
| 16 | }
|
---|
| 17 |
|
---|
| 18 | function capitalize(str) {
|
---|
| 19 | return str[0].toUpperCase() + str.slice(1)
|
---|
| 20 | }
|
---|
| 21 |
|
---|
| 22 | class Stringifier {
|
---|
| 23 | constructor(builder) {
|
---|
| 24 | this.builder = builder
|
---|
| 25 | }
|
---|
| 26 |
|
---|
| 27 | stringify(node, semicolon) {
|
---|
| 28 | /* istanbul ignore if */
|
---|
| 29 | if (!this[node.type]) {
|
---|
| 30 | throw new Error(
|
---|
| 31 | 'Unknown AST node type ' +
|
---|
| 32 | node.type +
|
---|
| 33 | '. ' +
|
---|
| 34 | 'Maybe you need to change PostCSS stringifier.'
|
---|
| 35 | )
|
---|
| 36 | }
|
---|
| 37 | this[node.type](node, semicolon)
|
---|
| 38 | }
|
---|
| 39 |
|
---|
| 40 | document(node) {
|
---|
| 41 | this.body(node)
|
---|
| 42 | }
|
---|
| 43 |
|
---|
| 44 | root(node) {
|
---|
| 45 | this.body(node)
|
---|
| 46 | if (node.raws.after) this.builder(node.raws.after)
|
---|
| 47 | }
|
---|
| 48 |
|
---|
| 49 | comment(node) {
|
---|
| 50 | let left = this.raw(node, 'left', 'commentLeft')
|
---|
| 51 | let right = this.raw(node, 'right', 'commentRight')
|
---|
| 52 | this.builder('/*' + left + node.text + right + '*/', node)
|
---|
| 53 | }
|
---|
| 54 |
|
---|
| 55 | decl(node, semicolon) {
|
---|
| 56 | let between = this.raw(node, 'between', 'colon')
|
---|
| 57 | let string = node.prop + between + this.rawValue(node, 'value')
|
---|
| 58 |
|
---|
| 59 | if (node.important) {
|
---|
| 60 | string += node.raws.important || ' !important'
|
---|
| 61 | }
|
---|
| 62 |
|
---|
| 63 | if (semicolon) string += ';'
|
---|
| 64 | this.builder(string, node)
|
---|
| 65 | }
|
---|
| 66 |
|
---|
| 67 | rule(node) {
|
---|
| 68 | this.block(node, this.rawValue(node, 'selector'))
|
---|
| 69 | if (node.raws.ownSemicolon) {
|
---|
| 70 | this.builder(node.raws.ownSemicolon, node, 'end')
|
---|
| 71 | }
|
---|
| 72 | }
|
---|
| 73 |
|
---|
| 74 | atrule(node, semicolon) {
|
---|
| 75 | let name = '@' + node.name
|
---|
| 76 | let params = node.params ? this.rawValue(node, 'params') : ''
|
---|
| 77 |
|
---|
| 78 | if (typeof node.raws.afterName !== 'undefined') {
|
---|
| 79 | name += node.raws.afterName
|
---|
| 80 | } else if (params) {
|
---|
| 81 | name += ' '
|
---|
| 82 | }
|
---|
| 83 |
|
---|
| 84 | if (node.nodes) {
|
---|
| 85 | this.block(node, name + params)
|
---|
| 86 | } else {
|
---|
| 87 | let end = (node.raws.between || '') + (semicolon ? ';' : '')
|
---|
| 88 | this.builder(name + params + end, node)
|
---|
| 89 | }
|
---|
| 90 | }
|
---|
| 91 |
|
---|
| 92 | body(node) {
|
---|
| 93 | let last = node.nodes.length - 1
|
---|
| 94 | while (last > 0) {
|
---|
| 95 | if (node.nodes[last].type !== 'comment') break
|
---|
| 96 | last -= 1
|
---|
| 97 | }
|
---|
| 98 |
|
---|
| 99 | let semicolon = this.raw(node, 'semicolon')
|
---|
| 100 | for (let i = 0; i < node.nodes.length; i++) {
|
---|
| 101 | let child = node.nodes[i]
|
---|
| 102 | let before = this.raw(child, 'before')
|
---|
| 103 | if (before) this.builder(before)
|
---|
| 104 | this.stringify(child, last !== i || semicolon)
|
---|
| 105 | }
|
---|
| 106 | }
|
---|
| 107 |
|
---|
| 108 | block(node, start) {
|
---|
| 109 | let between = this.raw(node, 'between', 'beforeOpen')
|
---|
| 110 | this.builder(start + between + '{', node, 'start')
|
---|
| 111 |
|
---|
| 112 | let after
|
---|
| 113 | if (node.nodes && node.nodes.length) {
|
---|
| 114 | this.body(node)
|
---|
| 115 | after = this.raw(node, 'after')
|
---|
| 116 | } else {
|
---|
| 117 | after = this.raw(node, 'after', 'emptyBody')
|
---|
| 118 | }
|
---|
| 119 |
|
---|
| 120 | if (after) this.builder(after)
|
---|
| 121 | this.builder('}', node, 'end')
|
---|
| 122 | }
|
---|
| 123 |
|
---|
| 124 | raw(node, own, detect) {
|
---|
| 125 | let value
|
---|
| 126 | if (!detect) detect = own
|
---|
| 127 |
|
---|
| 128 | // Already had
|
---|
| 129 | if (own) {
|
---|
| 130 | value = node.raws[own]
|
---|
| 131 | if (typeof value !== 'undefined') return value
|
---|
| 132 | }
|
---|
| 133 |
|
---|
| 134 | let parent = node.parent
|
---|
| 135 |
|
---|
| 136 | if (detect === 'before') {
|
---|
| 137 | // Hack for first rule in CSS
|
---|
| 138 | if (!parent || (parent.type === 'root' && parent.first === node)) {
|
---|
| 139 | return ''
|
---|
| 140 | }
|
---|
| 141 |
|
---|
| 142 | // `root` nodes in `document` should use only their own raws
|
---|
| 143 | if (parent && parent.type === 'document') {
|
---|
| 144 | return ''
|
---|
| 145 | }
|
---|
| 146 | }
|
---|
| 147 |
|
---|
| 148 | // Floating child without parent
|
---|
| 149 | if (!parent) return DEFAULT_RAW[detect]
|
---|
| 150 |
|
---|
| 151 | // Detect style by other nodes
|
---|
| 152 | let root = node.root()
|
---|
| 153 | if (!root.rawCache) root.rawCache = {}
|
---|
| 154 | if (typeof root.rawCache[detect] !== 'undefined') {
|
---|
| 155 | return root.rawCache[detect]
|
---|
| 156 | }
|
---|
| 157 |
|
---|
| 158 | if (detect === 'before' || detect === 'after') {
|
---|
| 159 | return this.beforeAfter(node, detect)
|
---|
| 160 | } else {
|
---|
| 161 | let method = 'raw' + capitalize(detect)
|
---|
| 162 | if (this[method]) {
|
---|
| 163 | value = this[method](root, node)
|
---|
| 164 | } else {
|
---|
| 165 | root.walk(i => {
|
---|
| 166 | value = i.raws[own]
|
---|
| 167 | if (typeof value !== 'undefined') return false
|
---|
| 168 | })
|
---|
| 169 | }
|
---|
| 170 | }
|
---|
| 171 |
|
---|
| 172 | if (typeof value === 'undefined') value = DEFAULT_RAW[detect]
|
---|
| 173 |
|
---|
| 174 | root.rawCache[detect] = value
|
---|
| 175 | return value
|
---|
| 176 | }
|
---|
| 177 |
|
---|
| 178 | rawSemicolon(root) {
|
---|
| 179 | let value
|
---|
| 180 | root.walk(i => {
|
---|
| 181 | if (i.nodes && i.nodes.length && i.last.type === 'decl') {
|
---|
| 182 | value = i.raws.semicolon
|
---|
| 183 | if (typeof value !== 'undefined') return false
|
---|
| 184 | }
|
---|
| 185 | })
|
---|
| 186 | return value
|
---|
| 187 | }
|
---|
| 188 |
|
---|
| 189 | rawEmptyBody(root) {
|
---|
| 190 | let value
|
---|
| 191 | root.walk(i => {
|
---|
| 192 | if (i.nodes && i.nodes.length === 0) {
|
---|
| 193 | value = i.raws.after
|
---|
| 194 | if (typeof value !== 'undefined') return false
|
---|
| 195 | }
|
---|
| 196 | })
|
---|
| 197 | return value
|
---|
| 198 | }
|
---|
| 199 |
|
---|
| 200 | rawIndent(root) {
|
---|
| 201 | if (root.raws.indent) return root.raws.indent
|
---|
| 202 | let value
|
---|
| 203 | root.walk(i => {
|
---|
| 204 | let p = i.parent
|
---|
| 205 | if (p && p !== root && p.parent && p.parent === root) {
|
---|
| 206 | if (typeof i.raws.before !== 'undefined') {
|
---|
| 207 | let parts = i.raws.before.split('\n')
|
---|
| 208 | value = parts[parts.length - 1]
|
---|
| 209 | value = value.replace(/\S/g, '')
|
---|
| 210 | return false
|
---|
| 211 | }
|
---|
| 212 | }
|
---|
| 213 | })
|
---|
| 214 | return value
|
---|
| 215 | }
|
---|
| 216 |
|
---|
| 217 | rawBeforeComment(root, node) {
|
---|
| 218 | let value
|
---|
| 219 | root.walkComments(i => {
|
---|
| 220 | if (typeof i.raws.before !== 'undefined') {
|
---|
| 221 | value = i.raws.before
|
---|
| 222 | if (value.includes('\n')) {
|
---|
| 223 | value = value.replace(/[^\n]+$/, '')
|
---|
| 224 | }
|
---|
| 225 | return false
|
---|
| 226 | }
|
---|
| 227 | })
|
---|
| 228 | if (typeof value === 'undefined') {
|
---|
| 229 | value = this.raw(node, null, 'beforeDecl')
|
---|
| 230 | } else if (value) {
|
---|
| 231 | value = value.replace(/\S/g, '')
|
---|
| 232 | }
|
---|
| 233 | return value
|
---|
| 234 | }
|
---|
| 235 |
|
---|
| 236 | rawBeforeDecl(root, node) {
|
---|
| 237 | let value
|
---|
| 238 | root.walkDecls(i => {
|
---|
| 239 | if (typeof i.raws.before !== 'undefined') {
|
---|
| 240 | value = i.raws.before
|
---|
| 241 | if (value.includes('\n')) {
|
---|
| 242 | value = value.replace(/[^\n]+$/, '')
|
---|
| 243 | }
|
---|
| 244 | return false
|
---|
| 245 | }
|
---|
| 246 | })
|
---|
| 247 | if (typeof value === 'undefined') {
|
---|
| 248 | value = this.raw(node, null, 'beforeRule')
|
---|
| 249 | } else if (value) {
|
---|
| 250 | value = value.replace(/\S/g, '')
|
---|
| 251 | }
|
---|
| 252 | return value
|
---|
| 253 | }
|
---|
| 254 |
|
---|
| 255 | rawBeforeRule(root) {
|
---|
| 256 | let value
|
---|
| 257 | root.walk(i => {
|
---|
| 258 | if (i.nodes && (i.parent !== root || root.first !== i)) {
|
---|
| 259 | if (typeof i.raws.before !== 'undefined') {
|
---|
| 260 | value = i.raws.before
|
---|
| 261 | if (value.includes('\n')) {
|
---|
| 262 | value = value.replace(/[^\n]+$/, '')
|
---|
| 263 | }
|
---|
| 264 | return false
|
---|
| 265 | }
|
---|
| 266 | }
|
---|
| 267 | })
|
---|
| 268 | if (value) value = value.replace(/\S/g, '')
|
---|
| 269 | return value
|
---|
| 270 | }
|
---|
| 271 |
|
---|
| 272 | rawBeforeClose(root) {
|
---|
| 273 | let value
|
---|
| 274 | root.walk(i => {
|
---|
| 275 | if (i.nodes && i.nodes.length > 0) {
|
---|
| 276 | if (typeof i.raws.after !== 'undefined') {
|
---|
| 277 | value = i.raws.after
|
---|
| 278 | if (value.includes('\n')) {
|
---|
| 279 | value = value.replace(/[^\n]+$/, '')
|
---|
| 280 | }
|
---|
| 281 | return false
|
---|
| 282 | }
|
---|
| 283 | }
|
---|
| 284 | })
|
---|
| 285 | if (value) value = value.replace(/\S/g, '')
|
---|
| 286 | return value
|
---|
| 287 | }
|
---|
| 288 |
|
---|
| 289 | rawBeforeOpen(root) {
|
---|
| 290 | let value
|
---|
| 291 | root.walk(i => {
|
---|
| 292 | if (i.type !== 'decl') {
|
---|
| 293 | value = i.raws.between
|
---|
| 294 | if (typeof value !== 'undefined') return false
|
---|
| 295 | }
|
---|
| 296 | })
|
---|
| 297 | return value
|
---|
| 298 | }
|
---|
| 299 |
|
---|
| 300 | rawColon(root) {
|
---|
| 301 | let value
|
---|
| 302 | root.walkDecls(i => {
|
---|
| 303 | if (typeof i.raws.between !== 'undefined') {
|
---|
| 304 | value = i.raws.between.replace(/[^\s:]/g, '')
|
---|
| 305 | return false
|
---|
| 306 | }
|
---|
| 307 | })
|
---|
| 308 | return value
|
---|
| 309 | }
|
---|
| 310 |
|
---|
| 311 | beforeAfter(node, detect) {
|
---|
| 312 | let value
|
---|
| 313 | if (node.type === 'decl') {
|
---|
| 314 | value = this.raw(node, null, 'beforeDecl')
|
---|
| 315 | } else if (node.type === 'comment') {
|
---|
| 316 | value = this.raw(node, null, 'beforeComment')
|
---|
| 317 | } else if (detect === 'before') {
|
---|
| 318 | value = this.raw(node, null, 'beforeRule')
|
---|
| 319 | } else {
|
---|
| 320 | value = this.raw(node, null, 'beforeClose')
|
---|
| 321 | }
|
---|
| 322 |
|
---|
| 323 | let buf = node.parent
|
---|
| 324 | let depth = 0
|
---|
| 325 | while (buf && buf.type !== 'root') {
|
---|
| 326 | depth += 1
|
---|
| 327 | buf = buf.parent
|
---|
| 328 | }
|
---|
| 329 |
|
---|
| 330 | if (value.includes('\n')) {
|
---|
| 331 | let indent = this.raw(node, null, 'indent')
|
---|
| 332 | if (indent.length) {
|
---|
| 333 | for (let step = 0; step < depth; step++) value += indent
|
---|
| 334 | }
|
---|
| 335 | }
|
---|
| 336 |
|
---|
| 337 | return value
|
---|
| 338 | }
|
---|
| 339 |
|
---|
| 340 | rawValue(node, prop) {
|
---|
| 341 | let value = node[prop]
|
---|
| 342 | let raw = node.raws[prop]
|
---|
| 343 | if (raw && raw.value === value) {
|
---|
| 344 | return raw.raw
|
---|
| 345 | }
|
---|
| 346 |
|
---|
| 347 | return value
|
---|
| 348 | }
|
---|
| 349 | }
|
---|
| 350 |
|
---|
| 351 | module.exports = Stringifier
|
---|