Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/flo-bit/shapecraft. flo-bit.dev/shapecraft
Something went wrong. Try again.
11 kB · 449 lines
JavaScript
at commit da69385e
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450'use strict'
let CssSyntaxError = require('./css-syntax-error')let Stringifier = require('./stringifier')let stringify = require('./stringify')let { isClean, my } = require('./symbols')
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}
function sourceOffset(inputCSS, position) { // Not all custom syntaxes support `offset` in `source.start` and `source.end` if (position && typeof position.offset !== 'undefined') { return position.offset }
let column = 1 let line = 1 let offset = 0
for (let i = 0; i < inputCSS.length; i++) { if (line === position.line && column === position.column) { offset = i break }
if (inputCSS[i] === '\n') { column = 1 line += 1 } else { column += 1 } }
return offset}
class Node { get proxyOf() { return this }
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] } } }
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 }
after(add) { this.parent.insertAfter(this, add) return this }
assign(overrides = {}) { for (let name in overrides) { this[name] = overrides[name] } return this }
before(add) { this.parent.insertBefore(this, add) return this }
cleanRaws(keepBetween) { delete this.raws.before delete this.raws.after if (!keepBetween) delete this.raws.between }
clone(overrides = {}) { let cloned = cloneNode(this) for (let name in overrides) { cloned[name] = overrides[name] } return cloned }
cloneAfter(overrides = {}) { let cloned = this.clone(overrides) this.parent.insertAfter(this, cloned) return cloned }
cloneBefore(overrides = {}) { let cloned = this.clone(overrides) this.parent.insertBefore(this, cloned) return cloned }
error(message, opts = {}) { if (this.source) { let { end, start } = this.rangeBy(opts) return this.source.input.error( message, { column: start.column, line: start.line }, { column: end.column, line: end.line }, opts ) } return new CssSyntaxError(message) }
getProxyProcessor() { return { get(node, prop) { if (prop === 'proxyOf') { return node } else if (prop === 'root') { return () => node.root().toProxy() } else { return node[prop] } },
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 } } }
/* c8 ignore next 3 */ markClean() { this[isClean] = true }
markDirty() { if (this[isClean]) { this[isClean] = false let next = this while ((next = next.parent)) { next[isClean] = false } } }
next() { if (!this.parent) return undefined let index = this.parent.index(this) return this.parent.nodes[index + 1] }
positionBy(opts = {}) { let pos = this.source.start if (opts.index) { pos = this.positionInside(opts.index) } else if (opts.word) { let inputString = 'document' in this.source.input ? this.source.input.document : this.source.input.css let stringRepresentation = inputString.slice( sourceOffset(inputString, this.source.start), sourceOffset(inputString, this.source.end) ) let index = stringRepresentation.indexOf(opts.word) if (index !== -1) pos = this.positionInside(index) } return pos }
positionInside(index) { let column = this.source.start.column let line = this.source.start.line let inputString = 'document' in this.source.input ? this.source.input.document : this.source.input.css let offset = sourceOffset(inputString, this.source.start) let end = offset + index
for (let i = offset; i < end; i++) { if (inputString[i] === '\n') { column = 1 line += 1 } else { column += 1 } }
return { column, line, offset: end } }
prev() { if (!this.parent) return undefined let index = this.parent.index(this) return this.parent.nodes[index - 1] }
rangeBy(opts = {}) { let inputString = 'document' in this.source.input ? this.source.input.document : this.source.input.css let start = { column: this.source.start.column, line: this.source.start.line, offset: sourceOffset(inputString, this.source.start) } let end = this.source.end ? { column: this.source.end.column + 1, line: this.source.end.line, offset: typeof this.source.end.offset === 'number' ? // `source.end.offset` is exclusive, so we don't need to add 1 this.source.end.offset : // Since line/column in this.source.end is inclusive, // the `sourceOffset(... , this.source.end)` returns an inclusive offset. // So, we add 1 to convert it to exclusive. sourceOffset(inputString, this.source.end) + 1 } : { column: start.column + 1, line: start.line, offset: start.offset + 1 }
if (opts.word) { let stringRepresentation = inputString.slice( sourceOffset(inputString, this.source.start), sourceOffset(inputString, this.source.end) ) let index = stringRepresentation.indexOf(opts.word) if (index !== -1) { start = this.positionInside(index) end = this.positionInside(index + opts.word.length) } } else { if (opts.start) { start = { column: opts.start.column, line: opts.start.line, offset: sourceOffset(inputString, opts.start) } } else if (opts.index) { start = this.positionInside(opts.index) }
if (opts.end) { end = { column: opts.end.column, line: opts.end.line, offset: sourceOffset(inputString, opts.end) } } else if (typeof opts.endIndex === 'number') { 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 = { column: start.column + 1, line: start.line, offset: start.offset + 1 } }
return { end, start } }
raw(prop, defaultType) { let str = new Stringifier() return str.raw(this, prop, defaultType) }
remove() { if (this.parent) { this.parent.removeChild(this) } this.parent = undefined return this }
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 }
root() { let result = this while (result.parent && result.parent.type !== 'document') { result = result.parent } return result }
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') { if (value == null) continue let inputId = inputs.get(value.input) if (inputId == null) { inputId = inputsNextIndex inputs.set(value.input, inputsNextIndex) inputsNextIndex++ } fixed[name] = { end: value.end, inputId, start: value.start } } else { fixed[name] = value } }
if (emitInputs) { fixed.inputs = [...inputs.keys()].map(input => input.toJSON()) }
return fixed }
toProxy() { if (!this.proxyCache) { this.proxyCache = new Proxy(this, this.getProxyProcessor()) } return this.proxyCache }
toString(stringifier = stringify) { if (stringifier.stringify) stringifier = stringifier.stringify let result = '' stringifier(this, i => { result += i }) return result }
warn(result, text, opts = {}) { let data = { node: this } for (let i in opts) data[i] = opts[i] return result.warn(text, data) }}
module.exports = NodeNode.default = Node