source: frontend/node_modules/postcss/lib/stringifier.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

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