node.js 6.7 KB

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