AK-21/Graphite-Industrial-Intelligence
0
1'use strict'2 3let CssSyntaxError = require('./css-syntax-error')4let Stringifier = require('./stringifier')5let stringify = require('./stringify')6let { isClean, my } = require('./symbols')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 /* c8 ignore next 2 */14 continue15 }16 if (i === 'proxyCache') continue17 let value = obj[i]18 let type = typeof value19 20 if (i === 'parent' && type === 'object') {21 if (parent) cloned[i] = parent22 } else if (i === 'source') {23 cloned[i] = value24 } 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] = value29 }30 }31 32 return cloned33}34 35function sourceOffset(inputCSS, position) {36 // Not all custom syntaxes support `offset` in `source.start` and `source.end`37 if (position && typeof position.offset !== 'undefined') {38 return position.offset39 }40 41 let column = 142 let line = 143 let offset = 044 45 for (let i = 0; i < inputCSS.length; i++) {46 if (line === position.line && column === position.column) {47 offset = i48 break49 }50 51 if (inputCSS[i] === '\n') {52 column = 153 line += 154 } else {55 column += 156 }57 }58 59 return offset60}61 62class Node {63 get proxyOf() {64 return this65 }66 67 constructor(defaults = {}) {68 this.raws = {}69 this[isClean] = false70 this[my] = true71 72 for (let name in defaults) {73 if (name === 'nodes') {74 this.nodes = []75 for (let node of defaults[name]) {76 // Clone only nodes that already belong to another tree, so passing a77 // freshly created (parent-less) node adopts that instance instead of78 // a copy and keeps the caller's reference usable. See #1987.79 if (typeof node.clone === 'function' && node.parent) {80 this.append(node.clone())81 } else {82 this.append(node)83 }84 }85 } else {86 this[name] = defaults[name]87 }88 }89 }90 91 addToError(error) {92 error.postcssNode = this93 if (error.stack && this.source && /\n\s{4}at /.test(error.stack)) {94 let s = this.source95 error.stack = error.stack.replace(96 /\n\s{4}at /,97 `$&${s.input.from}:${s.start.line}:${s.start.column}$&`98 )99 }100 return error101 }102 103 after(add) {104 this.parent.insertAfter(this, add)105 return this106 }107 108 assign(overrides = {}) {109 for (let name in overrides) {110 this[name] = overrides[name]111 }112 return this113 }114 115 before(add) {116 this.parent.insertBefore(this, add)117 return this118 }119 120 cleanRaws(keepBetween) {121 delete this.raws.before122 delete this.raws.after123 if (!keepBetween) delete this.raws.between124 }125 126 clone(overrides = {}) {127 let cloned = cloneNode(this)128 for (let name in overrides) {129 cloned[name] = overrides[name]130 }131 return cloned132 }133 134 cloneAfter(overrides = {}) {135 let cloned = this.clone(overrides)136 this.parent.insertAfter(this, cloned)137 return cloned138 }139 140 cloneBefore(overrides = {}) {141 let cloned = this.clone(overrides)142 this.parent.insertBefore(this, cloned)143 return cloned144 }145 146 error(message, opts = {}) {147 if (this.source) {148 let { end, start } = this.rangeBy(opts)149 return this.source.input.error(150 message,151 { column: start.column, line: start.line },152 { column: end.column, line: end.line },153 opts154 )155 }156 return new CssSyntaxError(message)157 }158 159 getProxyProcessor() {160 return {161 get(node, prop) {162 if (prop === 'proxyOf') {163 return node164 } else if (prop === 'root') {165 return () => node.root().toProxy()166 } else {167 return node[prop]168 }169 },170 171 set(node, prop, value) {172 if (node[prop] === value) return true173 node[prop] = value174 if (175 prop === 'prop' ||176 prop === 'value' ||177 prop === 'name' ||178 prop === 'params' ||179 prop === 'important' ||180 /* c8 ignore next */181 prop === 'text'182 ) {183 node.markDirty()184 }185 return true186 }187 }188 }189 190 /* c8 ignore next 3 */191 markClean() {192 this[isClean] = true193 }194 195 markDirty() {196 if (this[isClean]) {197 this[isClean] = false198 let next = this199 while ((next = next.parent)) {200 next[isClean] = false201 }202 }203 }204 205 next() {206 if (!this.parent) return undefined207 let index = this.parent.index(this)208 return this.parent.nodes[index + 1]209 }210 211 positionBy(opts = {}) {212 let inputString =213 'document' in this.source.input214 ? this.source.input.document215 : this.source.input.css216 let pos = {217 column: this.source.start.column,218 line: this.source.start.line,219 offset: sourceOffset(inputString, this.source.start)220 }221 if (opts.index) {222 pos = this.positionInside(opts.index)223 } else if (opts.word) {224 let stringRepresentation = inputString.slice(225 sourceOffset(inputString, this.source.start),226 sourceOffset(inputString, this.source.end)227 )228 let index = stringRepresentation.indexOf(opts.word)229 if (index !== -1) pos = this.positionInside(index)230 }231 return pos232 }233 234 positionInside(index) {235 let column = this.source.start.column236 let line = this.source.start.line237 let inputString =238 'document' in this.source.input239 ? this.source.input.document240 : this.source.input.css241 let offset = sourceOffset(inputString, this.source.start)242 let end = offset + index243 244 for (let i = offset; i < end; i++) {245 if (inputString[i] === '\n') {246 column = 1247 line += 1248 } else {249 column += 1250 }251 }252 253 return { column, line, offset: end }254 }255 256 prev() {257 if (!this.parent) return undefined258 let index = this.parent.index(this)259 return this.parent.nodes[index - 1]260 }261 262 rangeBy(opts = {}) {263 let inputString =264 'document' in this.source.input265 ? this.source.input.document266 : this.source.input.css267 let start = {268 column: this.source.start.column,269 line: this.source.start.line,270 offset: sourceOffset(inputString, this.source.start)271 }272 let end = this.source.end273 ? {274 column: this.source.end.column + 1,275 line: this.source.end.line,276 offset:277 typeof this.source.end.offset === 'number'278 ? // `source.end.offset` is exclusive, so we don't need to add 1279 this.source.end.offset280 : // Since line/column in this.source.end is inclusive,281 // the `sourceOffset(... , this.source.end)` returns an inclusive offset.282 // So, we add 1 to convert it to exclusive.283 sourceOffset(inputString, this.source.end) + 1284 }285 : {286 column: start.column + 1,287 line: start.line,288 offset: start.offset + 1289 }290 291 if (opts.word) {292 let stringRepresentation = inputString.slice(293 sourceOffset(inputString, this.source.start),294 sourceOffset(inputString, this.source.end)295 )296 let index = stringRepresentation.indexOf(opts.word)297 if (index !== -1) {298 start = this.positionInside(index)299 end = this.positionInside(index + opts.word.length)300 }301 } else {302 if (opts.start) {303 start = {304 column: opts.start.column,305 line: opts.start.line,306 offset: sourceOffset(inputString, opts.start)307 }308 } else if (typeof opts.index === 'number') {309 start = this.positionInside(opts.index)310 }311 312 if (opts.end) {313 end = {314 column: opts.end.column,315 line: opts.end.line,316 offset: sourceOffset(inputString, opts.end)317 }318 } else if (typeof opts.endIndex === 'number') {319 end = this.positionInside(opts.endIndex)320 } else if (typeof opts.index === 'number') {321 end = this.positionInside(opts.index + 1)322 }323 }324 325 if (326 end.line < start.line ||327 (end.line === start.line && end.column <= start.column)328 ) {329 end = {330 column: start.column + 1,331 line: start.line,332 offset: start.offset + 1333 }334 }335 336 return { end, start }337 }338 339 raw(prop, defaultType) {340 let str = new Stringifier()341 return str.raw(this, prop, defaultType)342 }343 344 remove() {345 if (this.parent) {346 this.parent.removeChild(this)347 }348 this.parent = undefined349 return this350 }351 352 replaceWith(...nodes) {353 if (this.parent) {354 let bookmark = this355 let foundSelf = false356 for (let node of nodes) {357 if (node === this) {358 foundSelf = true359 } else if (foundSelf) {360 this.parent.insertAfter(bookmark, node)361 bookmark = node362 } else {363 this.parent.insertBefore(bookmark, node)364 }365 }366 367 if (!foundSelf) {368 this.remove()369 }370 }371 372 return this373 }374 375 root() {376 let result = this377 while (result.parent && result.parent.type !== 'document') {378 result = result.parent379 }380 return result381 }382 383 toJSON(_, inputs) {384 let fixed = {}385 let emitInputs = inputs == null386 inputs = inputs || new Map()387 let inputsNextIndex = 0388 389 for (let name in this) {390 if (!Object.prototype.hasOwnProperty.call(this, name)) {391 /* c8 ignore next 2 */392 continue393 }394 if (name === 'parent' || name === 'proxyCache') continue395 let value = this[name]396 397 if (Array.isArray(value)) {398 fixed[name] = value.map(i => {399 if (typeof i === 'object' && i.toJSON) {400 return i.toJSON(null, inputs)401 } else {402 return i403 }404 })405 } else if (typeof value === 'object' && value.toJSON) {406 fixed[name] = value.toJSON(null, inputs)407 } else if (name === 'source') {408 if (value == null) continue409 let inputId = inputs.get(value.input)410 if (inputId == null) {411 inputId = inputsNextIndex412 inputs.set(value.input, inputsNextIndex)413 inputsNextIndex++414 }415 fixed[name] = {416 end: value.end,417 inputId,418 start: value.start419 }420 } else {421 fixed[name] = value422 }423 }424 425 if (emitInputs) {426 fixed.inputs = [...inputs.keys()].map(input => input.toJSON())427 }428 429 return fixed430 }431 432 toProxy() {433 if (!this.proxyCache) {434 this.proxyCache = new Proxy(this, this.getProxyProcessor())435 }436 return this.proxyCache437 }438 439 toString(stringifier = stringify) {440 if (stringifier.stringify) stringifier = stringifier.stringify441 let result = ''442 stringifier(this, i => {443 result += i444 })445 return result446 }447 448 warn(result, text, opts = {}) {449 let data = { node: this }450 for (let i in opts) data[i] = opts[i]451 return result.warn(text, data)452 }453}454 455module.exports = Node456Node.default = Node457 