You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

439 lines
10 KiB

2 years ago
  1. 'use strict'
  2. let { isClean, my } = require('./symbols')
  3. let Declaration = require('./declaration')
  4. let Comment = require('./comment')
  5. let Node = require('./node')
  6. let parse, Rule, AtRule, Root
  7. function cleanSource(nodes) {
  8. return nodes.map(i => {
  9. if (i.nodes) i.nodes = cleanSource(i.nodes)
  10. delete i.source
  11. return i
  12. })
  13. }
  14. function markDirtyUp(node) {
  15. node[isClean] = false
  16. if (node.proxyOf.nodes) {
  17. for (let i of node.proxyOf.nodes) {
  18. markDirtyUp(i)
  19. }
  20. }
  21. }
  22. class Container extends Node {
  23. push(child) {
  24. child.parent = this
  25. this.proxyOf.nodes.push(child)
  26. return this
  27. }
  28. each(callback) {
  29. if (!this.proxyOf.nodes) return undefined
  30. let iterator = this.getIterator()
  31. let index, result
  32. while (this.indexes[iterator] < this.proxyOf.nodes.length) {
  33. index = this.indexes[iterator]
  34. result = callback(this.proxyOf.nodes[index], index)
  35. if (result === false) break
  36. this.indexes[iterator] += 1
  37. }
  38. delete this.indexes[iterator]
  39. return result
  40. }
  41. walk(callback) {
  42. return this.each((child, i) => {
  43. let result
  44. try {
  45. result = callback(child, i)
  46. } catch (e) {
  47. throw child.addToError(e)
  48. }
  49. if (result !== false && child.walk) {
  50. result = child.walk(callback)
  51. }
  52. return result
  53. })
  54. }
  55. walkDecls(prop, callback) {
  56. if (!callback) {
  57. callback = prop
  58. return this.walk((child, i) => {
  59. if (child.type === 'decl') {
  60. return callback(child, i)
  61. }
  62. })
  63. }
  64. if (prop instanceof RegExp) {
  65. return this.walk((child, i) => {
  66. if (child.type === 'decl' && prop.test(child.prop)) {
  67. return callback(child, i)
  68. }
  69. })
  70. }
  71. return this.walk((child, i) => {
  72. if (child.type === 'decl' && child.prop === prop) {
  73. return callback(child, i)
  74. }
  75. })
  76. }
  77. walkRules(selector, callback) {
  78. if (!callback) {
  79. callback = selector
  80. return this.walk((child, i) => {
  81. if (child.type === 'rule') {
  82. return callback(child, i)
  83. }
  84. })
  85. }
  86. if (selector instanceof RegExp) {
  87. return this.walk((child, i) => {
  88. if (child.type === 'rule' && selector.test(child.selector)) {
  89. return callback(child, i)
  90. }
  91. })
  92. }
  93. return this.walk((child, i) => {
  94. if (child.type === 'rule' && child.selector === selector) {
  95. return callback(child, i)
  96. }
  97. })
  98. }
  99. walkAtRules(name, callback) {
  100. if (!callback) {
  101. callback = name
  102. return this.walk((child, i) => {
  103. if (child.type === 'atrule') {
  104. return callback(child, i)
  105. }
  106. })
  107. }
  108. if (name instanceof RegExp) {
  109. return this.walk((child, i) => {
  110. if (child.type === 'atrule' && name.test(child.name)) {
  111. return callback(child, i)
  112. }
  113. })
  114. }
  115. return this.walk((child, i) => {
  116. if (child.type === 'atrule' && child.name === name) {
  117. return callback(child, i)
  118. }
  119. })
  120. }
  121. walkComments(callback) {
  122. return this.walk((child, i) => {
  123. if (child.type === 'comment') {
  124. return callback(child, i)
  125. }
  126. })
  127. }
  128. append(...children) {
  129. for (let child of children) {
  130. let nodes = this.normalize(child, this.last)
  131. for (let node of nodes) this.proxyOf.nodes.push(node)
  132. }
  133. this.markDirty()
  134. return this
  135. }
  136. prepend(...children) {
  137. children = children.reverse()
  138. for (let child of children) {
  139. let nodes = this.normalize(child, this.first, 'prepend').reverse()
  140. for (let node of nodes) this.proxyOf.nodes.unshift(node)
  141. for (let id in this.indexes) {
  142. this.indexes[id] = this.indexes[id] + nodes.length
  143. }
  144. }
  145. this.markDirty()
  146. return this
  147. }
  148. cleanRaws(keepBetween) {
  149. super.cleanRaws(keepBetween)
  150. if (this.nodes) {
  151. for (let node of this.nodes) node.cleanRaws(keepBetween)
  152. }
  153. }
  154. insertBefore(exist, add) {
  155. let existIndex = this.index(exist)
  156. let type = exist === 0 ? 'prepend' : false
  157. let nodes = this.normalize(add, this.proxyOf.nodes[existIndex], type).reverse()
  158. existIndex = this.index(exist)
  159. for (let node of nodes) this.proxyOf.nodes.splice(existIndex, 0, node)
  160. let index
  161. for (let id in this.indexes) {
  162. index = this.indexes[id]
  163. if (existIndex <= index) {
  164. this.indexes[id] = index + nodes.length
  165. }
  166. }
  167. this.markDirty()
  168. return this
  169. }
  170. insertAfter(exist, add) {
  171. let existIndex = this.index(exist)
  172. let nodes = this.normalize(add, this.proxyOf.nodes[existIndex]).reverse()
  173. existIndex = this.index(exist)
  174. for (let node of nodes) this.proxyOf.nodes.splice(existIndex + 1, 0, node)
  175. let index
  176. for (let id in this.indexes) {
  177. index = this.indexes[id]
  178. if (existIndex < index) {
  179. this.indexes[id] = index + nodes.length
  180. }
  181. }
  182. this.markDirty()
  183. return this
  184. }
  185. removeChild(child) {
  186. child = this.index(child)
  187. this.proxyOf.nodes[child].parent = undefined
  188. this.proxyOf.nodes.splice(child, 1)
  189. let index
  190. for (let id in this.indexes) {
  191. index = this.indexes[id]
  192. if (index >= child) {
  193. this.indexes[id] = index - 1
  194. }
  195. }
  196. this.markDirty()
  197. return this
  198. }
  199. removeAll() {
  200. for (let node of this.proxyOf.nodes) node.parent = undefined
  201. this.proxyOf.nodes = []
  202. this.markDirty()
  203. return this
  204. }
  205. replaceValues(pattern, opts, callback) {
  206. if (!callback) {
  207. callback = opts
  208. opts = {}
  209. }
  210. this.walkDecls(decl => {
  211. if (opts.props && !opts.props.includes(decl.prop)) return
  212. if (opts.fast && !decl.value.includes(opts.fast)) return
  213. decl.value = decl.value.replace(pattern, callback)
  214. })
  215. this.markDirty()
  216. return this
  217. }
  218. every(condition) {
  219. return this.nodes.every(condition)
  220. }
  221. some(condition) {
  222. return this.nodes.some(condition)
  223. }
  224. index(child) {
  225. if (typeof child === 'number') return child
  226. if (child.proxyOf) child = child.proxyOf
  227. return this.proxyOf.nodes.indexOf(child)
  228. }
  229. get first() {
  230. if (!this.proxyOf.nodes) return undefined
  231. return this.proxyOf.nodes[0]
  232. }
  233. get last() {
  234. if (!this.proxyOf.nodes) return undefined
  235. return this.proxyOf.nodes[this.proxyOf.nodes.length - 1]
  236. }
  237. normalize(nodes, sample) {
  238. if (typeof nodes === 'string') {
  239. nodes = cleanSource(parse(nodes).nodes)
  240. } else if (Array.isArray(nodes)) {
  241. nodes = nodes.slice(0)
  242. for (let i of nodes) {
  243. if (i.parent) i.parent.removeChild(i, 'ignore')
  244. }
  245. } else if (nodes.type === 'root' && this.type !== 'document') {
  246. nodes = nodes.nodes.slice(0)
  247. for (let i of nodes) {
  248. if (i.parent) i.parent.removeChild(i, 'ignore')
  249. }
  250. } else if (nodes.type) {
  251. nodes = [nodes]
  252. } else if (nodes.prop) {
  253. if (typeof nodes.value === 'undefined') {
  254. throw new Error('Value field is missed in node creation')
  255. } else if (typeof nodes.value !== 'string') {
  256. nodes.value = String(nodes.value)
  257. }
  258. nodes = [new Declaration(nodes)]
  259. } else if (nodes.selector) {
  260. nodes = [new Rule(nodes)]
  261. } else if (nodes.name) {
  262. nodes = [new AtRule(nodes)]
  263. } else if (nodes.text) {
  264. nodes = [new Comment(nodes)]
  265. } else {
  266. throw new Error('Unknown node type in node creation')
  267. }
  268. let processed = nodes.map(i => {
  269. /* c8 ignore next */
  270. if (!i[my]) Container.rebuild(i)
  271. i = i.proxyOf
  272. if (i.parent) i.parent.removeChild(i)
  273. if (i[isClean]) markDirtyUp(i)
  274. if (typeof i.raws.before === 'undefined') {
  275. if (sample && typeof sample.raws.before !== 'undefined') {
  276. i.raws.before = sample.raws.before.replace(/\S/g, '')
  277. }
  278. }
  279. i.parent = this.proxyOf
  280. return i
  281. })
  282. return processed
  283. }
  284. getProxyProcessor() {
  285. return {
  286. set(node, prop, value) {
  287. if (node[prop] === value) return true
  288. node[prop] = value
  289. if (prop === 'name' || prop === 'params' || prop === 'selector') {
  290. node.markDirty()
  291. }
  292. return true
  293. },
  294. get(node, prop) {
  295. if (prop === 'proxyOf') {
  296. return node
  297. } else if (!node[prop]) {
  298. return node[prop]
  299. } else if (
  300. prop === 'each' ||
  301. (typeof prop === 'string' && prop.startsWith('walk'))
  302. ) {
  303. return (...args) => {
  304. return node[prop](
  305. ...args.map(i => {
  306. if (typeof i === 'function') {
  307. return (child, index) => i(child.toProxy(), index)
  308. } else {
  309. return i
  310. }
  311. })
  312. )
  313. }
  314. } else if (prop === 'every' || prop === 'some') {
  315. return cb => {
  316. return node[prop]((child, ...other) =>
  317. cb(child.toProxy(), ...other)
  318. )
  319. }
  320. } else if (prop === 'root') {
  321. return () => node.root().toProxy()
  322. } else if (prop === 'nodes') {
  323. return node.nodes.map(i => i.toProxy())
  324. } else if (prop === 'first' || prop === 'last') {
  325. return node[prop].toProxy()
  326. } else {
  327. return node[prop]
  328. }
  329. }
  330. }
  331. }
  332. getIterator() {
  333. if (!this.lastEach) this.lastEach = 0
  334. if (!this.indexes) this.indexes = {}
  335. this.lastEach += 1
  336. let iterator = this.lastEach
  337. this.indexes[iterator] = 0
  338. return iterator
  339. }
  340. }
  341. Container.registerParse = dependant => {
  342. parse = dependant
  343. }
  344. Container.registerRule = dependant => {
  345. Rule = dependant
  346. }
  347. Container.registerAtRule = dependant => {
  348. AtRule = dependant
  349. }
  350. Container.registerRoot = dependant => {
  351. Root = dependant
  352. }
  353. module.exports = Container
  354. Container.default = Container
  355. /* c8 ignore start */
  356. Container.rebuild = node => {
  357. if (node.type === 'atrule') {
  358. Object.setPrototypeOf(node, AtRule.prototype)
  359. } else if (node.type === 'rule') {
  360. Object.setPrototypeOf(node, Rule.prototype)
  361. } else if (node.type === 'decl') {
  362. Object.setPrototypeOf(node, Declaration.prototype)
  363. } else if (node.type === 'comment') {
  364. Object.setPrototypeOf(node, Comment.prototype)
  365. } else if (node.type === 'root') {
  366. Object.setPrototypeOf(node, Root.prototype)
  367. }
  368. node[my] = true
  369. if (node.nodes) {
  370. node.nodes.forEach(child => {
  371. Container.rebuild(child)
  372. })
  373. }
  374. }
  375. /* c8 ignore stop */