1 | 'use strict'
|
2 |
|
3 | let CssSyntaxError = require('./css-syntax-error')
|
4 | let Stringifier = require('./stringifier')
|
5 | let { isClean } = require('./symbols')
|
6 | let stringify = require('./stringify')
|
7 |
|
8 | function 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 |
|
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 |
|
35 | class 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 |
|
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 |
|
290 | module.exports = Node
|
291 | Node.default = Node
|