source: frontend/node_modules/postcss-nested/index.js

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

Fix frontend appearance

  • Property mode set to 100644
File size: 8.8 KB
Line 
1const { AtRule, Rule } = require('postcss')
2let parser = require('postcss-selector-parser')
3
4/**
5 * Run a selector string through postcss-selector-parser
6 */
7function parse(rawSelector, rule) {
8 let nodes
9 try {
10 parser(parsed => {
11 nodes = parsed
12 }).processSync(rawSelector)
13 } catch (e) {
14 if (rawSelector.includes(':')) {
15 throw rule ? rule.error('Missed semicolon') : e
16 } else {
17 throw rule ? rule.error(e.message) : e
18 }
19 }
20 return nodes.at(0)
21}
22
23/**
24 * Replaces the "&" token in a node's selector with the parent selector
25 * similar to what SCSS does.
26 *
27 * Mutates the nodes list
28 */
29function interpolateAmpInSelector(nodes, parent) {
30 let replaced = false
31 nodes.each(node => {
32 if (node.type === 'nesting') {
33 let clonedParent = parent.clone({})
34 if (node.value !== '&') {
35 node.replaceWith(
36 parse(node.value.replace('&', clonedParent.toString()))
37 )
38 } else {
39 node.replaceWith(clonedParent)
40 }
41 replaced = true
42 } else if ('nodes' in node && node.nodes) {
43 if (interpolateAmpInSelector(node, parent)) {
44 replaced = true
45 }
46 }
47 })
48 return replaced
49}
50
51/**
52 * Combines parent and child selectors, in a SCSS-like way
53 */
54function mergeSelectors(parent, child) {
55 let merged = []
56 parent.selectors.forEach(sel => {
57 let parentNode = parse(sel, parent)
58
59 child.selectors.forEach(selector => {
60 if (!selector) {
61 return
62 }
63 let node = parse(selector, child)
64 let replaced = interpolateAmpInSelector(node, parentNode)
65 if (!replaced) {
66 node.prepend(parser.combinator({ value: ' ' }))
67 node.prepend(parentNode.clone({}))
68 }
69 merged.push(node.toString())
70 })
71 })
72 return merged
73}
74
75/**
76 * Move a child and its preceeding comment(s) to after "after"
77 */
78function breakOut(child, after) {
79 let prev = child.prev()
80 after.after(child)
81 while (prev && prev.type === 'comment') {
82 let nextPrev = prev.prev()
83 after.after(prev)
84 prev = nextPrev
85 }
86 return child
87}
88
89function createFnAtruleChilds(bubble) {
90 return function atruleChilds(rule, atrule, bubbling, mergeSels = bubbling) {
91 let children = []
92 atrule.each(child => {
93 if (child.type === 'rule' && bubbling) {
94 if (mergeSels) {
95 child.selectors = mergeSelectors(rule, child)
96 }
97 } else if (child.type === 'atrule' && child.nodes) {
98 if (bubble[child.name]) {
99 atruleChilds(rule, child, mergeSels)
100 } else if (atrule[rootRuleMergeSel] !== false) {
101 children.push(child)
102 }
103 } else {
104 children.push(child)
105 }
106 })
107 if (bubbling) {
108 if (children.length) {
109 let clone = rule.clone({ nodes: [] })
110 for (let child of children) {
111 clone.append(child)
112 }
113 atrule.prepend(clone)
114 }
115 }
116 }
117}
118
119function pickDeclarations(selector, declarations, after) {
120 let parent = new Rule({
121 nodes: [],
122 selector
123 })
124 parent.append(declarations)
125 after.after(parent)
126 return parent
127}
128
129function atruleNames(defaults, custom) {
130 let list = {}
131 for (let name of defaults) {
132 list[name] = true
133 }
134 if (custom) {
135 for (let name of custom) {
136 list[name.replace(/^@/, '')] = true
137 }
138 }
139 return list
140}
141
142function parseRootRuleParams(params) {
143 params = params.trim()
144 let braceBlock = params.match(/^\((.*)\)$/)
145 if (!braceBlock) {
146 return { selector: params, type: 'basic' }
147 }
148 let bits = braceBlock[1].match(/^(with(?:out)?):(.+)$/)
149 if (bits) {
150 let allowlist = bits[1] === 'with'
151 let rules = Object.fromEntries(
152 bits[2]
153 .trim()
154 .split(/\s+/)
155 .map(name => [name, true])
156 )
157 if (allowlist && rules.all) {
158 return { type: 'noop' }
159 }
160 let escapes = rule => !!rules[rule]
161 if (rules.all) {
162 escapes = () => true
163 } else if (allowlist) {
164 escapes = rule => (rule === 'all' ? false : !rules[rule])
165 }
166
167 return {
168 escapes,
169 type: 'withrules'
170 }
171 }
172 // Unrecognized brace block
173 return { type: 'unknown' }
174}
175
176function getAncestorRules(leaf) {
177 let lineage = []
178 let parent = leaf.parent
179
180 while (parent && parent instanceof AtRule) {
181 lineage.push(parent)
182 parent = parent.parent
183 }
184 return lineage
185}
186
187function unwrapRootRule(rule) {
188 let escapes = rule[rootRuleEscapes]
189
190 if (!escapes) {
191 rule.after(rule.nodes)
192 } else {
193 let nodes = rule.nodes
194
195 let topEscaped
196 let topEscapedIdx = -1
197 let breakoutLeaf
198 let breakoutRoot
199 let clone
200
201 let lineage = getAncestorRules(rule)
202 lineage.forEach((parent, i) => {
203 if (escapes(parent.name)) {
204 topEscaped = parent
205 topEscapedIdx = i
206 breakoutRoot = clone
207 } else {
208 let oldClone = clone
209 clone = parent.clone({ nodes: [] })
210 oldClone && clone.append(oldClone)
211 breakoutLeaf = breakoutLeaf || clone
212 }
213 })
214
215 if (!topEscaped) {
216 rule.after(nodes)
217 } else if (!breakoutRoot) {
218 topEscaped.after(nodes)
219 } else {
220 let leaf = breakoutLeaf
221 leaf.append(nodes)
222 topEscaped.after(breakoutRoot)
223 }
224
225 if (rule.next() && topEscaped) {
226 let restRoot
227 lineage.slice(0, topEscapedIdx + 1).forEach((parent, i, arr) => {
228 let oldRoot = restRoot
229 restRoot = parent.clone({ nodes: [] })
230 oldRoot && restRoot.append(oldRoot)
231
232 let nextSibs = []
233 let _child = arr[i - 1] || rule
234 let next = _child.next()
235 while (next) {
236 nextSibs.push(next)
237 next = next.next()
238 }
239 restRoot.append(nextSibs)
240 })
241 restRoot && (breakoutRoot || nodes[nodes.length - 1]).after(restRoot)
242 }
243 }
244
245 rule.remove()
246}
247
248const rootRuleMergeSel = Symbol('rootRuleMergeSel')
249const rootRuleEscapes = Symbol('rootRuleEscapes')
250
251function normalizeRootRule(rule) {
252 let { params } = rule
253 let { escapes, selector, type } = parseRootRuleParams(params)
254 if (type === 'unknown') {
255 throw rule.error(
256 `Unknown @${rule.name} parameter ${JSON.stringify(params)}`
257 )
258 }
259 if (type === 'basic' && selector) {
260 let selectorBlock = new Rule({ nodes: rule.nodes, selector })
261 rule.removeAll()
262 rule.append(selectorBlock)
263 }
264 rule[rootRuleEscapes] = escapes
265 rule[rootRuleMergeSel] = escapes ? !escapes('all') : type === 'noop'
266}
267
268const hasRootRule = Symbol('hasRootRule')
269
270module.exports = (opts = {}) => {
271 let bubble = atruleNames(
272 ['media', 'supports', 'layer', 'container', 'starting-style'],
273 opts.bubble
274 )
275 let atruleChilds = createFnAtruleChilds(bubble)
276 let unwrap = atruleNames(
277 [
278 'document',
279 'font-face',
280 'keyframes',
281 '-webkit-keyframes',
282 '-moz-keyframes'
283 ],
284 opts.unwrap
285 )
286 let rootRuleName = (opts.rootRuleName || 'at-root').replace(/^@/, '')
287 let preserveEmpty = opts.preserveEmpty
288
289 return {
290 Once(root) {
291 root.walkAtRules(rootRuleName, node => {
292 normalizeRootRule(node)
293 root[hasRootRule] = true
294 })
295 },
296
297 postcssPlugin: 'postcss-nested',
298
299 RootExit(root) {
300 if (root[hasRootRule]) {
301 root.walkAtRules(rootRuleName, unwrapRootRule)
302 root[hasRootRule] = false
303 }
304 },
305
306 Rule(rule) {
307 let unwrapped = false
308 let after = rule
309 let copyDeclarations = false
310 let declarations = []
311
312 rule.each(child => {
313 if (child.type === 'rule') {
314 if (declarations.length) {
315 after = pickDeclarations(rule.selector, declarations, after)
316 declarations = []
317 }
318
319 copyDeclarations = true
320 unwrapped = true
321 child.selectors = mergeSelectors(rule, child)
322 after = breakOut(child, after)
323 } else if (child.type === 'atrule') {
324 if (declarations.length) {
325 after = pickDeclarations(rule.selector, declarations, after)
326 declarations = []
327 }
328 if (child.name === rootRuleName) {
329 unwrapped = true
330 atruleChilds(rule, child, true, child[rootRuleMergeSel])
331 after = breakOut(child, after)
332 } else if (bubble[child.name]) {
333 copyDeclarations = true
334 unwrapped = true
335 atruleChilds(rule, child, true)
336 after = breakOut(child, after)
337 } else if (unwrap[child.name]) {
338 copyDeclarations = true
339 unwrapped = true
340 atruleChilds(rule, child, false)
341 after = breakOut(child, after)
342 } else if (copyDeclarations) {
343 declarations.push(child)
344 }
345 } else if (child.type === 'decl' && copyDeclarations) {
346 declarations.push(child)
347 }
348 })
349
350 if (declarations.length) {
351 after = pickDeclarations(rule.selector, declarations, after)
352 }
353
354 if (unwrapped && preserveEmpty !== true) {
355 rule.raws.semicolon = true
356 if (rule.nodes.length === 0) rule.remove()
357 }
358 }
359 }
360}
361module.exports.postcss = true
Note: See TracBrowser for help on using the repository browser.