blob: 4065c47524526f92502cf8c1ee7d5671671b37fe [file] [log] [blame]
Tim van der Lippe16b82282021-11-08 13:50:26 +00001'use strict'
Mathias Bynens79e2cf02020-05-29 16:46:17 +02002
Tim van der Lippe16b82282021-11-08 13:50:26 +00003const DEFAULT_RAW = {
Mathias Bynens79e2cf02020-05-29 16:46:17 +02004 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
Tim van der Lippef2ea2c92021-11-08 10:55:56 +000016}
17
Tim van der Lippe16b82282021-11-08 13:50:26 +000018function capitalize(str) {
19 return str[0].toUpperCase() + str.slice(1)
20}
21
22class Stringifier {
23 constructor(builder) {
24 this.builder = builder
Mathias Bynens79e2cf02020-05-29 16:46:17 +020025 }
26
Tim van der Lippe16b82282021-11-08 13:50:26 +000027 stringify(node, semicolon) {
Tim van der Lippe4cb09742022-01-07 14:25:03 +010028 /* c8 ignore start */
Tim van der Lippe16b82282021-11-08 13:50:26 +000029 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 }
Tim van der Lippe4cb09742022-01-07 14:25:03 +010037 /* c8 ignore stop */
Tim van der Lippe16b82282021-11-08 13:50:26 +000038 this[node.type](node, semicolon)
39 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020040
Tim van der Lippe16b82282021-11-08 13:50:26 +000041 document(node) {
42 this.body(node)
43 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020044
Tim van der Lippe16b82282021-11-08 13:50:26 +000045 root(node) {
46 this.body(node)
47 if (node.raws.after) this.builder(node.raws.after)
48 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020049
Tim van der Lippe16b82282021-11-08 13:50:26 +000050 comment(node) {
51 let left = this.raw(node, 'left', 'commentLeft')
52 let right = this.raw(node, 'right', 'commentRight')
53 this.builder('/*' + left + node.text + right + '*/', node)
54 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020055
Tim van der Lippe16b82282021-11-08 13:50:26 +000056 decl(node, semicolon) {
57 let between = this.raw(node, 'between', 'colon')
58 let string = node.prop + between + this.rawValue(node, 'value')
Mathias Bynens79e2cf02020-05-29 16:46:17 +020059
60 if (node.important) {
Tim van der Lippe16b82282021-11-08 13:50:26 +000061 string += node.raws.important || ' !important'
Mathias Bynens79e2cf02020-05-29 16:46:17 +020062 }
63
Tim van der Lippe16b82282021-11-08 13:50:26 +000064 if (semicolon) string += ';'
65 this.builder(string, node)
66 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020067
Tim van der Lippe16b82282021-11-08 13:50:26 +000068 rule(node) {
69 this.block(node, this.rawValue(node, 'selector'))
Mathias Bynens79e2cf02020-05-29 16:46:17 +020070 if (node.raws.ownSemicolon) {
Tim van der Lippe16b82282021-11-08 13:50:26 +000071 this.builder(node.raws.ownSemicolon, node, 'end')
Mathias Bynens79e2cf02020-05-29 16:46:17 +020072 }
Tim van der Lippe16b82282021-11-08 13:50:26 +000073 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020074
Tim van der Lippe16b82282021-11-08 13:50:26 +000075 atrule(node, semicolon) {
76 let name = '@' + node.name
77 let params = node.params ? this.rawValue(node, 'params') : ''
Mathias Bynens79e2cf02020-05-29 16:46:17 +020078
79 if (typeof node.raws.afterName !== 'undefined') {
Tim van der Lippe16b82282021-11-08 13:50:26 +000080 name += node.raws.afterName
Mathias Bynens79e2cf02020-05-29 16:46:17 +020081 } else if (params) {
Tim van der Lippe16b82282021-11-08 13:50:26 +000082 name += ' '
Mathias Bynens79e2cf02020-05-29 16:46:17 +020083 }
84
85 if (node.nodes) {
Tim van der Lippe16b82282021-11-08 13:50:26 +000086 this.block(node, name + params)
Mathias Bynens79e2cf02020-05-29 16:46:17 +020087 } else {
Tim van der Lippe16b82282021-11-08 13:50:26 +000088 let end = (node.raws.between || '') + (semicolon ? ';' : '')
89 this.builder(name + params + end, node)
Mathias Bynens79e2cf02020-05-29 16:46:17 +020090 }
Tim van der Lippe16b82282021-11-08 13:50:26 +000091 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020092
Tim van der Lippe16b82282021-11-08 13:50:26 +000093 body(node) {
94 let last = node.nodes.length - 1
Mathias Bynens79e2cf02020-05-29 16:46:17 +020095 while (last > 0) {
Tim van der Lippe16b82282021-11-08 13:50:26 +000096 if (node.nodes[last].type !== 'comment') break
97 last -= 1
Mathias Bynens79e2cf02020-05-29 16:46:17 +020098 }
99
Tim van der Lippe16b82282021-11-08 13:50:26 +0000100 let semicolon = this.raw(node, 'semicolon')
101 for (let i = 0; i < node.nodes.length; i++) {
102 let child = node.nodes[i]
103 let before = this.raw(child, 'before')
104 if (before) this.builder(before)
105 this.stringify(child, last !== i || semicolon)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200106 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000107 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200108
Tim van der Lippe16b82282021-11-08 13:50:26 +0000109 block(node, start) {
110 let between = this.raw(node, 'between', 'beforeOpen')
111 this.builder(start + between + '{', node, 'start')
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200112
Tim van der Lippe16b82282021-11-08 13:50:26 +0000113 let after
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200114 if (node.nodes && node.nodes.length) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000115 this.body(node)
116 after = this.raw(node, 'after')
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200117 } else {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000118 after = this.raw(node, 'after', 'emptyBody')
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200119 }
120
Tim van der Lippe16b82282021-11-08 13:50:26 +0000121 if (after) this.builder(after)
122 this.builder('}', node, 'end')
123 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200124
Tim van der Lippe16b82282021-11-08 13:50:26 +0000125 raw(node, own, detect) {
126 let value
127 if (!detect) detect = own
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200128
Tim van der Lippe16b82282021-11-08 13:50:26 +0000129 // Already had
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200130 if (own) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000131 value = node.raws[own]
132 if (typeof value !== 'undefined') return value
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200133 }
134
Tim van der Lippe16b82282021-11-08 13:50:26 +0000135 let parent = node.parent
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200136
137 if (detect === 'before') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000138 // Hack for first rule in CSS
139 if (!parent || (parent.type === 'root' && parent.first === node)) {
140 return ''
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200141 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200142
Tim van der Lippe16b82282021-11-08 13:50:26 +0000143 // `root` nodes in `document` should use only their own raws
144 if (parent && parent.type === 'document') {
145 return ''
146 }
147 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200148
Tim van der Lippe16b82282021-11-08 13:50:26 +0000149 // Floating child without parent
150 if (!parent) return DEFAULT_RAW[detect]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200151
Tim van der Lippe16b82282021-11-08 13:50:26 +0000152 // Detect style by other nodes
153 let root = node.root()
154 if (!root.rawCache) root.rawCache = {}
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200155 if (typeof root.rawCache[detect] !== 'undefined') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000156 return root.rawCache[detect]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200157 }
158
159 if (detect === 'before' || detect === 'after') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000160 return this.beforeAfter(node, detect)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200161 } else {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000162 let method = 'raw' + capitalize(detect)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200163 if (this[method]) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000164 value = this[method](root, node)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200165 } else {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000166 root.walk(i => {
167 value = i.raws[own]
168 if (typeof value !== 'undefined') return false
169 })
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200170 }
171 }
172
Tim van der Lippe16b82282021-11-08 13:50:26 +0000173 if (typeof value === 'undefined') value = DEFAULT_RAW[detect]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200174
Tim van der Lippe16b82282021-11-08 13:50:26 +0000175 root.rawCache[detect] = value
176 return value
177 }
178
179 rawSemicolon(root) {
180 let value
181 root.walk(i => {
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200182 if (i.nodes && i.nodes.length && i.last.type === 'decl') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000183 value = i.raws.semicolon
184 if (typeof value !== 'undefined') return false
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200185 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000186 })
187 return value
188 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200189
Tim van der Lippe16b82282021-11-08 13:50:26 +0000190 rawEmptyBody(root) {
191 let value
192 root.walk(i => {
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200193 if (i.nodes && i.nodes.length === 0) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000194 value = i.raws.after
195 if (typeof value !== 'undefined') return false
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200196 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000197 })
198 return value
199 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200200
Tim van der Lippe16b82282021-11-08 13:50:26 +0000201 rawIndent(root) {
202 if (root.raws.indent) return root.raws.indent
203 let value
204 root.walk(i => {
205 let p = i.parent
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200206 if (p && p !== root && p.parent && p.parent === root) {
207 if (typeof i.raws.before !== 'undefined') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000208 let parts = i.raws.before.split('\n')
209 value = parts[parts.length - 1]
210 value = value.replace(/\S/g, '')
211 return false
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200212 }
213 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000214 })
215 return value
216 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200217
Tim van der Lippe16b82282021-11-08 13:50:26 +0000218 rawBeforeComment(root, node) {
219 let value
220 root.walkComments(i => {
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200221 if (typeof i.raws.before !== 'undefined') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000222 value = i.raws.before
223 if (value.includes('\n')) {
224 value = value.replace(/[^\n]+$/, '')
Tim van der Lippe2b4a9df2021-11-08 12:58:12 +0000225 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000226 return false
Tim van der Lippe2b4a9df2021-11-08 12:58:12 +0000227 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000228 })
Tim van der Lippe2b4a9df2021-11-08 12:58:12 +0000229 if (typeof value === 'undefined') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000230 value = this.raw(node, null, 'beforeDecl')
Tim van der Lippe2b4a9df2021-11-08 12:58:12 +0000231 } else if (value) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000232 value = value.replace(/\S/g, '')
Tim van der Lippe2b4a9df2021-11-08 12:58:12 +0000233 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000234 return value
235 }
Tim van der Lippe2b4a9df2021-11-08 12:58:12 +0000236
Tim van der Lippe16b82282021-11-08 13:50:26 +0000237 rawBeforeDecl(root, node) {
238 let value
239 root.walkDecls(i => {
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200240 if (typeof i.raws.before !== 'undefined') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000241 value = i.raws.before
242 if (value.includes('\n')) {
243 value = value.replace(/[^\n]+$/, '')
Tim van der Lippe2b4a9df2021-11-08 12:58:12 +0000244 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000245 return false
Tim van der Lippe2b4a9df2021-11-08 12:58:12 +0000246 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000247 })
Tim van der Lippe2b4a9df2021-11-08 12:58:12 +0000248 if (typeof value === 'undefined') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000249 value = this.raw(node, null, 'beforeRule')
Tim van der Lippe2b4a9df2021-11-08 12:58:12 +0000250 } else if (value) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000251 value = value.replace(/\S/g, '')
Tim van der Lippe2b4a9df2021-11-08 12:58:12 +0000252 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000253 return value
254 }
Tim van der Lippe2b4a9df2021-11-08 12:58:12 +0000255
Tim van der Lippe16b82282021-11-08 13:50:26 +0000256 rawBeforeRule(root) {
257 let value
258 root.walk(i => {
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200259 if (i.nodes && (i.parent !== root || root.first !== i)) {
260 if (typeof i.raws.before !== 'undefined') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000261 value = i.raws.before
262 if (value.includes('\n')) {
263 value = value.replace(/[^\n]+$/, '')
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200264 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000265 return false
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200266 }
267 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000268 })
269 if (value) value = value.replace(/\S/g, '')
270 return value
271 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200272
Tim van der Lippe16b82282021-11-08 13:50:26 +0000273 rawBeforeClose(root) {
274 let value
275 root.walk(i => {
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200276 if (i.nodes && i.nodes.length > 0) {
277 if (typeof i.raws.after !== 'undefined') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000278 value = i.raws.after
279 if (value.includes('\n')) {
280 value = value.replace(/[^\n]+$/, '')
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200281 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000282 return false
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200283 }
284 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000285 })
286 if (value) value = value.replace(/\S/g, '')
287 return value
288 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200289
Tim van der Lippe16b82282021-11-08 13:50:26 +0000290 rawBeforeOpen(root) {
291 let value
292 root.walk(i => {
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200293 if (i.type !== 'decl') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000294 value = i.raws.between
295 if (typeof value !== 'undefined') return false
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200296 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000297 })
298 return value
299 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200300
Tim van der Lippe16b82282021-11-08 13:50:26 +0000301 rawColon(root) {
302 let value
303 root.walkDecls(i => {
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200304 if (typeof i.raws.between !== 'undefined') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000305 value = i.raws.between.replace(/[^\s:]/g, '')
306 return false
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200307 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000308 })
309 return value
310 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200311
Tim van der Lippe16b82282021-11-08 13:50:26 +0000312 beforeAfter(node, detect) {
313 let value
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200314 if (node.type === 'decl') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000315 value = this.raw(node, null, 'beforeDecl')
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200316 } else if (node.type === 'comment') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000317 value = this.raw(node, null, 'beforeComment')
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200318 } else if (detect === 'before') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000319 value = this.raw(node, null, 'beforeRule')
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200320 } else {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000321 value = this.raw(node, null, 'beforeClose')
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200322 }
323
Tim van der Lippe16b82282021-11-08 13:50:26 +0000324 let buf = node.parent
325 let depth = 0
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200326 while (buf && buf.type !== 'root') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000327 depth += 1
328 buf = buf.parent
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200329 }
330
Tim van der Lippe16b82282021-11-08 13:50:26 +0000331 if (value.includes('\n')) {
332 let indent = this.raw(node, null, 'indent')
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200333 if (indent.length) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000334 for (let step = 0; step < depth; step++) value += indent
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200335 }
336 }
337
Tim van der Lippe16b82282021-11-08 13:50:26 +0000338 return value
339 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200340
Tim van der Lippe16b82282021-11-08 13:50:26 +0000341 rawValue(node, prop) {
342 let value = node[prop]
343 let raw = node.raws[prop]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200344 if (raw && raw.value === value) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000345 return raw.raw
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200346 }
347
Tim van der Lippe16b82282021-11-08 13:50:26 +0000348 return value
349 }
350}
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200351
Tim van der Lippe16b82282021-11-08 13:50:26 +0000352module.exports = Stringifier
Tim van der Lippe4cb09742022-01-07 14:25:03 +0100353Stringifier.default = Stringifier