UNPKG

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