node.js 6.9 KB

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