UNPKG

6.31 kBJavaScriptView Raw
1'use strict'
2
3let CssSyntaxError = require('./css-syntax-error')
4let Stringifier = require('./stringifier')
5let { isClean } = require('./symbols')
6let stringify = require('./stringify')
7
8function cloneNode (obj, parent) {
9 let cloned = new obj.constructor()
10
11 for (let i in obj) {
12 if (!Object.prototype.hasOwnProperty.call(obj, i)) {
13 // istanbul ignore next
14 continue
15 }
16 if (i === 'proxyCache') continue
17 let value = obj[i]
18 let type = typeof value
19
20 if (i === 'parent' && type === 'object') {
21 if (parent) cloned[i] = parent
22 } else if (i === 'source') {
23 cloned[i] = value
24 } else if (Array.isArray(value)) {
25 cloned[i] = value.map(j => cloneNode(j, cloned))
26 } else {
27 if (type === 'object' && value !== null) value = cloneNode(value)
28 cloned[i] = value
29 }
30 }
31
32 return cloned
33}
34
35class Node {
36 constructor (defaults = {}) {
37 this.raws = {}
38 this[isClean] = false
39
40 for (let name in defaults) {
41 if (name === 'nodes') {
42 this.nodes = []
43 for (let node of defaults[name]) {
44 if (typeof node.clone === 'function') {
45 this.append(node.clone())
46 } else {
47 this.append(node)
48 }
49 }
50 } else {
51 this[name] = defaults[name]
52 }
53 }
54 }
55
56 error (message, opts = {}) {
57 if (this.source) {
58 let pos = this.positionBy(opts)
59 return this.source.input.error(message, pos.line, pos.column, opts)
60 }
61 return new CssSyntaxError(message)
62 }
63
64 warn (result, text, opts) {
65 let data = { node: this }
66 for (let i in opts) data[i] = opts[i]
67 return result.warn(text, data)
68 }
69
70 remove () {
71 if (this.parent) {
72 this.parent.removeChild(this)
73 }
74 this.parent = undefined
75 return this
76 }
77
78 toString (stringifier = stringify) {
79 if (stringifier.stringify) stringifier = stringifier.stringify
80 let result = ''
81 stringifier(this, i => {
82 result += i
83 })
84 return result
85 }
86
87 clone (overrides = {}) {
88 let cloned = cloneNode(this)
89 for (let name in overrides) {
90 cloned[name] = overrides[name]
91 }
92 return cloned
93 }
94
95 cloneBefore (overrides = {}) {
96 let cloned = this.clone(overrides)
97 this.parent.insertBefore(this, cloned)
98 return cloned
99 }
100
101 cloneAfter (overrides = {}) {
102 let cloned = this.clone(overrides)
103 this.parent.insertAfter(this, cloned)
104 return cloned
105 }
106
107 replaceWith (...nodes) {
108 if (this.parent) {
109 let bookmark = this
110 let foundSelf = false
111 for (let node of nodes) {
112 if (node === this) {
113 foundSelf = true
114 } else if (foundSelf) {
115 this.parent.insertAfter(bookmark, node)
116 bookmark = node
117 } else {
118 this.parent.insertBefore(bookmark, node)
119 }
120 }
121
122 if (!foundSelf) {
123 this.remove()
124 }
125 }
126
127 return this
128 }
129
130 next () {
131 if (!this.parent) return undefined
132 let index = this.parent.index(this)
133 return this.parent.nodes[index + 1]
134 }
135
136 prev () {
137 if (!this.parent) return undefined
138 let index = this.parent.index(this)
139 return this.parent.nodes[index - 1]
140 }
141
142 before (add) {
143 this.parent.insertBefore(this, add)
144 return this
145 }
146
147 after (add) {
148 this.parent.insertAfter(this, add)
149 return this
150 }
151
152 root () {
153 let result = this
154 while (result.parent) result = result.parent
155 return result
156 }
157
158 raw (prop, defaultType) {
159 let str = new Stringifier()
160 return str.raw(this, prop, defaultType)
161 }
162
163 cleanRaws (keepBetween) {
164 delete this.raws.before
165 delete this.raws.after
166 if (!keepBetween) delete this.raws.between
167 }
168
169 toJSON () {
170 let fixed = {}
171
172 for (let name in this) {
173 if (!Object.prototype.hasOwnProperty.call(this, name)) {
174 // istanbul ignore next
175 continue
176 }
177 if (name === 'parent') continue
178 let value = this[name]
179
180 if (Array.isArray(value)) {
181 fixed[name] = value.map(i => {
182 if (typeof i === 'object' && i.toJSON) {
183 return i.toJSON()
184 } else {
185 return i
186 }
187 })
188 } else if (typeof value === 'object' && value.toJSON) {
189 fixed[name] = value.toJSON()
190 } else {
191 fixed[name] = value
192 }
193 }
194
195 return fixed
196 }
197
198 positionInside (index) {
199 let string = this.toString()
200 let column = this.source.start.column
201 let line = this.source.start.line
202
203 for (let i = 0; i < index; i++) {
204 if (string[i] === '\n') {
205 column = 1
206 line += 1
207 } else {
208 column += 1
209 }
210 }
211
212 return { line, column }
213 }
214
215 positionBy (opts) {
216 let pos = this.source.start
217 if (opts.index) {
218 pos = this.positionInside(opts.index)
219 } else if (opts.word) {
220 let index = this.toString().indexOf(opts.word)
221 if (index !== -1) pos = this.positionInside(index)
222 }
223 return pos
224 }
225
226 getProxyProcessor () {
227 return {
228 set (node, prop, value) {
229 if (node[prop] === value) return true
230 node[prop] = value
231 if (
232 prop === 'prop' ||
233 prop === 'value' ||
234 prop === 'name' ||
235 prop === 'params' ||
236 prop === 'important' ||
237 prop === 'text'
238 ) {
239 node.markDirty()
240 }
241 return true
242 },
243
244 get (node, prop) {
245 if (prop === 'proxyOf') {
246 return node
247 } else if (prop === 'root') {
248 return () => node.root().toProxy()
249 } else {
250 return node[prop]
251 }
252 }
253 }
254 }
255
256 toProxy () {
257 if (!this.proxyCache) {
258 this.proxyCache = new Proxy(this, this.getProxyProcessor())
259 }
260 return this.proxyCache
261 }
262
263 addToError (error) {
264 error.postcssNode = this
265 if (error.stack && this.source && /\n\s{4}at /.test(error.stack)) {
266 let s = this.source
267 error.stack = error.stack.replace(
268 /\n\s{4}at /,
269 `$&${s.input.from}:${s.start.line}:${s.start.column}$&`
270 )
271 }
272 return error
273 }
274
275 markDirty () {
276 if (this[isClean]) {
277 this[isClean] = false
278 let next = this
279 while ((next = next.parent)) {
280 next[isClean] = false
281 }
282 }
283 }
284
285 get proxyOf () {
286 return this
287 }
288}
289
290module.exports = Node
291Node.default = Node