| 'use strict' |
| |
| let { isClean, my } = require('./symbols') |
| let CssSyntaxError = require('./css-syntax-error') |
| let Stringifier = require('./stringifier') |
| let stringify = require('./stringify') |
| |
| function cloneNode(obj, parent) { |
| let cloned = new obj.constructor() |
| |
| for (let i in obj) { |
| if (!Object.prototype.hasOwnProperty.call(obj, i)) { |
| /* c8 ignore next 2 */ |
| continue |
| } |
| if (i === 'proxyCache') continue |
| let value = obj[i] |
| let type = typeof value |
| |
| if (i === 'parent' && type === 'object') { |
| if (parent) cloned[i] = parent |
| } else if (i === 'source') { |
| cloned[i] = value |
| } else if (Array.isArray(value)) { |
| cloned[i] = value.map(j => cloneNode(j, cloned)) |
| } else { |
| if (type === 'object' && value !== null) value = cloneNode(value) |
| cloned[i] = value |
| } |
| } |
| |
| return cloned |
| } |
| |
| class Node { |
| constructor(defaults = {}) { |
| this.raws = {} |
| this[isClean] = false |
| this[my] = true |
| |
| for (let name in defaults) { |
| if (name === 'nodes') { |
| this.nodes = [] |
| for (let node of defaults[name]) { |
| if (typeof node.clone === 'function') { |
| this.append(node.clone()) |
| } else { |
| this.append(node) |
| } |
| } |
| } else { |
| this[name] = defaults[name] |
| } |
| } |
| } |
| |
| error(message, opts = {}) { |
| if (this.source) { |
| let { start, end } = this.rangeBy(opts) |
| return this.source.input.error( |
| message, |
| { line: start.line, column: start.column }, |
| { line: end.line, column: end.column }, |
| opts |
| ) |
| } |
| return new CssSyntaxError(message) |
| } |
| |
| warn(result, text, opts) { |
| let data = { node: this } |
| for (let i in opts) data[i] = opts[i] |
| return result.warn(text, data) |
| } |
| |
| remove() { |
| if (this.parent) { |
| this.parent.removeChild(this) |
| } |
| this.parent = undefined |
| return this |
| } |
| |
| toString(stringifier = stringify) { |
| if (stringifier.stringify) stringifier = stringifier.stringify |
| let result = '' |
| stringifier(this, i => { |
| result += i |
| }) |
| return result |
| } |
| |
| assign(overrides = {}) { |
| for (let name in overrides) { |
| this[name] = overrides[name] |
| } |
| return this |
| } |
| |
| clone(overrides = {}) { |
| let cloned = cloneNode(this) |
| for (let name in overrides) { |
| cloned[name] = overrides[name] |
| } |
| return cloned |
| } |
| |
| cloneBefore(overrides = {}) { |
| let cloned = this.clone(overrides) |
| this.parent.insertBefore(this, cloned) |
| return cloned |
| } |
| |
| cloneAfter(overrides = {}) { |
| let cloned = this.clone(overrides) |
| this.parent.insertAfter(this, cloned) |
| return cloned |
| } |
| |
| replaceWith(...nodes) { |
| if (this.parent) { |
| let bookmark = this |
| let foundSelf = false |
| for (let node of nodes) { |
| if (node === this) { |
| foundSelf = true |
| } else if (foundSelf) { |
| this.parent.insertAfter(bookmark, node) |
| bookmark = node |
| } else { |
| this.parent.insertBefore(bookmark, node) |
| } |
| } |
| |
| if (!foundSelf) { |
| this.remove() |
| } |
| } |
| |
| return this |
| } |
| |
| next() { |
| if (!this.parent) return undefined |
| let index = this.parent.index(this) |
| return this.parent.nodes[index + 1] |
| } |
| |
| prev() { |
| if (!this.parent) return undefined |
| let index = this.parent.index(this) |
| return this.parent.nodes[index - 1] |
| } |
| |
| before(add) { |
| this.parent.insertBefore(this, add) |
| return this |
| } |
| |
| after(add) { |
| this.parent.insertAfter(this, add) |
| return this |
| } |
| |
| root() { |
| let result = this |
| while (result.parent && result.parent.type !== 'document') { |
| result = result.parent |
| } |
| return result |
| } |
| |
| raw(prop, defaultType) { |
| let str = new Stringifier() |
| return str.raw(this, prop, defaultType) |
| } |
| |
| cleanRaws(keepBetween) { |
| delete this.raws.before |
| delete this.raws.after |
| if (!keepBetween) delete this.raws.between |
| } |
| |
| toJSON(_, inputs) { |
| let fixed = {} |
| let emitInputs = inputs == null |
| inputs = inputs || new Map() |
| let inputsNextIndex = 0 |
| |
| for (let name in this) { |
| if (!Object.prototype.hasOwnProperty.call(this, name)) { |
| /* c8 ignore next 2 */ |
| continue |
| } |
| if (name === 'parent' || name === 'proxyCache') continue |
| let value = this[name] |
| |
| if (Array.isArray(value)) { |
| fixed[name] = value.map(i => { |
| if (typeof i === 'object' && i.toJSON) { |
| return i.toJSON(null, inputs) |
| } else { |
| return i |
| } |
| }) |
| } else if (typeof value === 'object' && value.toJSON) { |
| fixed[name] = value.toJSON(null, inputs) |
| } else if (name === 'source') { |
| let inputId = inputs.get(value.input) |
| if (inputId == null) { |
| inputId = inputsNextIndex |
| inputs.set(value.input, inputsNextIndex) |
| inputsNextIndex++ |
| } |
| fixed[name] = { |
| inputId, |
| start: value.start, |
| end: value.end |
| } |
| } else { |
| fixed[name] = value |
| } |
| } |
| |
| if (emitInputs) { |
| fixed.inputs = [...inputs.keys()].map(input => input.toJSON()) |
| } |
| |
| return fixed |
| } |
| |
| positionInside(index) { |
| let string = this.toString() |
| let column = this.source.start.column |
| let line = this.source.start.line |
| |
| for (let i = 0; i < index; i++) { |
| if (string[i] === '\n') { |
| column = 1 |
| line += 1 |
| } else { |
| column += 1 |
| } |
| } |
| |
| return { line, column } |
| } |
| |
| positionBy(opts) { |
| let pos = this.source.start |
| if (opts.index) { |
| pos = this.positionInside(opts.index) |
| } else if (opts.word) { |
| let index = this.toString().indexOf(opts.word) |
| if (index !== -1) pos = this.positionInside(index) |
| } |
| return pos |
| } |
| |
| rangeBy(opts) { |
| let start = { |
| line: this.source.start.line, |
| column: this.source.start.column |
| } |
| let end = this.source.end |
| ? { |
| line: this.source.end.line, |
| column: this.source.end.column + 1 |
| } |
| : { |
| line: start.line, |
| column: start.column + 1 |
| } |
| |
| if (opts.word) { |
| let index = this.toString().indexOf(opts.word) |
| if (index !== -1) { |
| start = this.positionInside(index) |
| end = this.positionInside(index + opts.word.length) |
| } |
| } else { |
| if (opts.start) { |
| start = { |
| line: opts.start.line, |
| column: opts.start.column |
| } |
| } else if (opts.index) { |
| start = this.positionInside(opts.index) |
| } |
| |
| if (opts.end) { |
| end = { |
| line: opts.end.line, |
| column: opts.end.column |
| } |
| } else if (opts.endIndex) { |
| end = this.positionInside(opts.endIndex) |
| } else if (opts.index) { |
| end = this.positionInside(opts.index + 1) |
| } |
| } |
| |
| if ( |
| end.line < start.line || |
| (end.line === start.line && end.column <= start.column) |
| ) { |
| end = { line: start.line, column: start.column + 1 } |
| } |
| |
| return { start, end } |
| } |
| |
| getProxyProcessor() { |
| return { |
| set(node, prop, value) { |
| if (node[prop] === value) return true |
| node[prop] = value |
| if ( |
| prop === 'prop' || |
| prop === 'value' || |
| prop === 'name' || |
| prop === 'params' || |
| prop === 'important' || |
| /* c8 ignore next */ |
| prop === 'text' |
| ) { |
| node.markDirty() |
| } |
| return true |
| }, |
| |
| get(node, prop) { |
| if (prop === 'proxyOf') { |
| return node |
| } else if (prop === 'root') { |
| return () => node.root().toProxy() |
| } else { |
| return node[prop] |
| } |
| } |
| } |
| } |
| |
| toProxy() { |
| if (!this.proxyCache) { |
| this.proxyCache = new Proxy(this, this.getProxyProcessor()) |
| } |
| return this.proxyCache |
| } |
| |
| addToError(error) { |
| error.postcssNode = this |
| if (error.stack && this.source && /\n\s{4}at /.test(error.stack)) { |
| let s = this.source |
| error.stack = error.stack.replace( |
| /\n\s{4}at /, |
| `$&${s.input.from}:${s.start.line}:${s.start.column}$&` |
| ) |
| } |
| return error |
| } |
| |
| markDirty() { |
| if (this[isClean]) { |
| this[isClean] = false |
| let next = this |
| while ((next = next.parent)) { |
| next[isClean] = false |
| } |
| } |
| } |
| |
| get proxyOf() { |
| return this |
| } |
| } |
| |
| module.exports = Node |
| Node.default = Node |