UNPKG

10.6 kBJavaScriptView Raw
1'use strict'
2
3let { isClean, my } = require('./symbols')
4let Declaration = require('./declaration')
5let Comment = require('./comment')
6let Node = require('./node')
7
8let parse, Rule, AtRule, Root
9
10function cleanSource(nodes) {
11 return nodes.map(i => {
12 if (i.nodes) i.nodes = cleanSource(i.nodes)
13 delete i.source
14 return i
15 })
16}
17
18function markDirtyUp(node) {
19 node[isClean] = false
20 if (node.proxyOf.nodes) {
21 for (let i of node.proxyOf.nodes) {
22 markDirtyUp(i)
23 }
24 }
25}
26
27class Container extends Node {
28 append(...children) {
29 for (let child of children) {
30 let nodes = this.normalize(child, this.last)
31 for (let node of nodes) this.proxyOf.nodes.push(node)
32 }
33
34 this.markDirty()
35
36 return this
37 }
38
39 cleanRaws(keepBetween) {
40 super.cleanRaws(keepBetween)
41 if (this.nodes) {
42 for (let node of this.nodes) node.cleanRaws(keepBetween)
43 }
44 }
45
46 each(callback) {
47 if (!this.proxyOf.nodes) return undefined
48 let iterator = this.getIterator()
49
50 let index, result
51 while (this.indexes[iterator] < this.proxyOf.nodes.length) {
52 index = this.indexes[iterator]
53 result = callback(this.proxyOf.nodes[index], index)
54 if (result === false) break
55
56 this.indexes[iterator] += 1
57 }
58
59 delete this.indexes[iterator]
60 return result
61 }
62
63 every(condition) {
64 return this.nodes.every(condition)
65 }
66
67 getIterator() {
68 if (!this.lastEach) this.lastEach = 0
69 if (!this.indexes) this.indexes = {}
70
71 this.lastEach += 1
72 let iterator = this.lastEach
73 this.indexes[iterator] = 0
74
75 return iterator
76 }
77
78 getProxyProcessor() {
79 return {
80 get(node, prop) {
81 if (prop === 'proxyOf') {
82 return node
83 } else if (!node[prop]) {
84 return node[prop]
85 } else if (
86 prop === 'each' ||
87 (typeof prop === 'string' && prop.startsWith('walk'))
88 ) {
89 return (...args) => {
90 return node[prop](
91 ...args.map(i => {
92 if (typeof i === 'function') {
93 return (child, index) => i(child.toProxy(), index)
94 } else {
95 return i
96 }
97 })
98 )
99 }
100 } else if (prop === 'every' || prop === 'some') {
101 return cb => {
102 return node[prop]((child, ...other) =>
103 cb(child.toProxy(), ...other)
104 )
105 }
106 } else if (prop === 'root') {
107 return () => node.root().toProxy()
108 } else if (prop === 'nodes') {
109 return node.nodes.map(i => i.toProxy())
110 } else if (prop === 'first' || prop === 'last') {
111 return node[prop].toProxy()
112 } else {
113 return node[prop]
114 }
115 },
116
117 set(node, prop, value) {
118 if (node[prop] === value) return true
119 node[prop] = value
120 if (prop === 'name' || prop === 'params' || prop === 'selector') {
121 node.markDirty()
122 }
123 return true
124 }
125 }
126 }
127
128 index(child) {
129 if (typeof child === 'number') return child
130 if (child.proxyOf) child = child.proxyOf
131 return this.proxyOf.nodes.indexOf(child)
132 }
133
134 insertAfter(exist, add) {
135 let existIndex = this.index(exist)
136 let nodes = this.normalize(add, this.proxyOf.nodes[existIndex]).reverse()
137 existIndex = this.index(exist)
138 for (let node of nodes) this.proxyOf.nodes.splice(existIndex + 1, 0, node)
139
140 let index
141 for (let id in this.indexes) {
142 index = this.indexes[id]
143 if (existIndex < index) {
144 this.indexes[id] = index + nodes.length
145 }
146 }
147
148 this.markDirty()
149
150 return this
151 }
152
153 insertBefore(exist, add) {
154 let existIndex = this.index(exist)
155 let type = existIndex === 0 ? 'prepend' : false
156 let nodes = this.normalize(add, this.proxyOf.nodes[existIndex], type).reverse()
157 existIndex = this.index(exist)
158 for (let node of nodes) this.proxyOf.nodes.splice(existIndex, 0, node)
159
160 let index
161 for (let id in this.indexes) {
162 index = this.indexes[id]
163 if (existIndex <= index) {
164 this.indexes[id] = index + nodes.length
165 }
166 }
167
168 this.markDirty()
169
170 return this
171 }
172
173 normalize(nodes, sample) {
174 if (typeof nodes === 'string') {
175 nodes = cleanSource(parse(nodes).nodes)
176 } else if (typeof nodes === 'undefined') {
177 nodes = []
178 } else if (Array.isArray(nodes)) {
179 nodes = nodes.slice(0)
180 for (let i of nodes) {
181 if (i.parent) i.parent.removeChild(i, 'ignore')
182 }
183 } else if (nodes.type === 'root' && this.type !== 'document') {
184 nodes = nodes.nodes.slice(0)
185 for (let i of nodes) {
186 if (i.parent) i.parent.removeChild(i, 'ignore')
187 }
188 } else if (nodes.type) {
189 nodes = [nodes]
190 } else if (nodes.prop) {
191 if (typeof nodes.value === 'undefined') {
192 throw new Error('Value field is missed in node creation')
193 } else if (typeof nodes.value !== 'string') {
194 nodes.value = String(nodes.value)
195 }
196 nodes = [new Declaration(nodes)]
197 } else if (nodes.selector) {
198 nodes = [new Rule(nodes)]
199 } else if (nodes.name) {
200 nodes = [new AtRule(nodes)]
201 } else if (nodes.text) {
202 nodes = [new Comment(nodes)]
203 } else {
204 throw new Error('Unknown node type in node creation')
205 }
206
207 let processed = nodes.map(i => {
208 /* c8 ignore next */
209 if (!i[my]) Container.rebuild(i)
210 i = i.proxyOf
211 if (i.parent) i.parent.removeChild(i)
212 if (i[isClean]) markDirtyUp(i)
213 if (typeof i.raws.before === 'undefined') {
214 if (sample && typeof sample.raws.before !== 'undefined') {
215 i.raws.before = sample.raws.before.replace(/\S/g, '')
216 }
217 }
218 i.parent = this.proxyOf
219 return i
220 })
221
222 return processed
223 }
224
225 prepend(...children) {
226 children = children.reverse()
227 for (let child of children) {
228 let nodes = this.normalize(child, this.first, 'prepend').reverse()
229 for (let node of nodes) this.proxyOf.nodes.unshift(node)
230 for (let id in this.indexes) {
231 this.indexes[id] = this.indexes[id] + nodes.length
232 }
233 }
234
235 this.markDirty()
236
237 return this
238 }
239
240 push(child) {
241 child.parent = this
242 this.proxyOf.nodes.push(child)
243 return this
244 }
245
246 removeAll() {
247 for (let node of this.proxyOf.nodes) node.parent = undefined
248 this.proxyOf.nodes = []
249
250 this.markDirty()
251
252 return this
253 }
254
255 removeChild(child) {
256 child = this.index(child)
257 this.proxyOf.nodes[child].parent = undefined
258 this.proxyOf.nodes.splice(child, 1)
259
260 let index
261 for (let id in this.indexes) {
262 index = this.indexes[id]
263 if (index >= child) {
264 this.indexes[id] = index - 1
265 }
266 }
267
268 this.markDirty()
269
270 return this
271 }
272
273 replaceValues(pattern, opts, callback) {
274 if (!callback) {
275 callback = opts
276 opts = {}
277 }
278
279 this.walkDecls(decl => {
280 if (opts.props && !opts.props.includes(decl.prop)) return
281 if (opts.fast && !decl.value.includes(opts.fast)) return
282
283 decl.value = decl.value.replace(pattern, callback)
284 })
285
286 this.markDirty()
287
288 return this
289 }
290
291 some(condition) {
292 return this.nodes.some(condition)
293 }
294
295 walk(callback) {
296 return this.each((child, i) => {
297 let result
298 try {
299 result = callback(child, i)
300 } catch (e) {
301 throw child.addToError(e)
302 }
303 if (result !== false && child.walk) {
304 result = child.walk(callback)
305 }
306
307 return result
308 })
309 }
310
311 walkAtRules(name, callback) {
312 if (!callback) {
313 callback = name
314 return this.walk((child, i) => {
315 if (child.type === 'atrule') {
316 return callback(child, i)
317 }
318 })
319 }
320 if (name instanceof RegExp) {
321 return this.walk((child, i) => {
322 if (child.type === 'atrule' && name.test(child.name)) {
323 return callback(child, i)
324 }
325 })
326 }
327 return this.walk((child, i) => {
328 if (child.type === 'atrule' && child.name === name) {
329 return callback(child, i)
330 }
331 })
332 }
333
334 walkComments(callback) {
335 return this.walk((child, i) => {
336 if (child.type === 'comment') {
337 return callback(child, i)
338 }
339 })
340 }
341
342 walkDecls(prop, callback) {
343 if (!callback) {
344 callback = prop
345 return this.walk((child, i) => {
346 if (child.type === 'decl') {
347 return callback(child, i)
348 }
349 })
350 }
351 if (prop instanceof RegExp) {
352 return this.walk((child, i) => {
353 if (child.type === 'decl' && prop.test(child.prop)) {
354 return callback(child, i)
355 }
356 })
357 }
358 return this.walk((child, i) => {
359 if (child.type === 'decl' && child.prop === prop) {
360 return callback(child, i)
361 }
362 })
363 }
364
365 walkRules(selector, callback) {
366 if (!callback) {
367 callback = selector
368
369 return this.walk((child, i) => {
370 if (child.type === 'rule') {
371 return callback(child, i)
372 }
373 })
374 }
375 if (selector instanceof RegExp) {
376 return this.walk((child, i) => {
377 if (child.type === 'rule' && selector.test(child.selector)) {
378 return callback(child, i)
379 }
380 })
381 }
382 return this.walk((child, i) => {
383 if (child.type === 'rule' && child.selector === selector) {
384 return callback(child, i)
385 }
386 })
387 }
388
389 get first() {
390 if (!this.proxyOf.nodes) return undefined
391 return this.proxyOf.nodes[0]
392 }
393
394 get last() {
395 if (!this.proxyOf.nodes) return undefined
396 return this.proxyOf.nodes[this.proxyOf.nodes.length - 1]
397 }
398}
399
400Container.registerParse = dependant => {
401 parse = dependant
402}
403
404Container.registerRule = dependant => {
405 Rule = dependant
406}
407
408Container.registerAtRule = dependant => {
409 AtRule = dependant
410}
411
412Container.registerRoot = dependant => {
413 Root = dependant
414}
415
416module.exports = Container
417Container.default = Container
418
419/* c8 ignore start */
420Container.rebuild = node => {
421 if (node.type === 'atrule') {
422 Object.setPrototypeOf(node, AtRule.prototype)
423 } else if (node.type === 'rule') {
424 Object.setPrototypeOf(node, Rule.prototype)
425 } else if (node.type === 'decl') {
426 Object.setPrototypeOf(node, Declaration.prototype)
427 } else if (node.type === 'comment') {
428 Object.setPrototypeOf(node, Comment.prototype)
429 } else if (node.type === 'root') {
430 Object.setPrototypeOf(node, Root.prototype)
431 }
432
433 node[my] = true
434
435 if (node.nodes) {
436 node.nodes.forEach(child => {
437 Container.rebuild(child)
438 })
439 }
440}
441/* c8 ignore stop */