UNPKG

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