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.

442 lines
12 KiB

2 years ago
  1. import Node, { ChildNode, NodeProps, ChildProps } from './node.js'
  2. import Declaration from './declaration.js'
  3. import Comment from './comment.js'
  4. import AtRule from './at-rule.js'
  5. import Rule from './rule.js'
  6. interface ValueOptions {
  7. /**
  8. * An array of property names.
  9. */
  10. props?: string[]
  11. /**
  12. * String thats used to narrow down values and speed up the regexp search.
  13. */
  14. fast?: string
  15. }
  16. export interface ContainerProps extends NodeProps {
  17. nodes?: (ChildNode | ChildProps)[]
  18. }
  19. /**
  20. * The `Root`, `AtRule`, and `Rule` container nodes
  21. * inherit some common methods to help work with their children.
  22. *
  23. * Note that all containers can store any content. If you write a rule inside
  24. * a rule, PostCSS will parse it.
  25. */
  26. export default abstract class Container<
  27. Child extends Node = ChildNode
  28. > extends Node {
  29. /**
  30. * An array containing the containers children.
  31. *
  32. * ```js
  33. * const root = postcss.parse('a { color: black }')
  34. * root.nodes.length //=> 1
  35. * root.nodes[0].selector //=> 'a'
  36. * root.nodes[0].nodes[0].prop //=> 'color'
  37. * ```
  38. */
  39. nodes: Child[]
  40. /**
  41. * The containers first child.
  42. *
  43. * ```js
  44. * rule.first === rules.nodes[0]
  45. * ```
  46. */
  47. get first(): Child | undefined
  48. /**
  49. * The containers last child.
  50. *
  51. * ```js
  52. * rule.last === rule.nodes[rule.nodes.length - 1]
  53. * ```
  54. */
  55. get last(): Child | undefined
  56. /**
  57. * Iterates through the containers immediate children,
  58. * calling `callback` for each child.
  59. *
  60. * Returning `false` in the callback will break iteration.
  61. *
  62. * This method only iterates through the containers immediate children.
  63. * If you need to recursively iterate through all the containers descendant
  64. * nodes, use `Container#walk`.
  65. *
  66. * Unlike the for `{}`-cycle or `Array#forEach` this iterator is safe
  67. * if you are mutating the array of child nodes during iteration.
  68. * PostCSS will adjust the current index to match the mutations.
  69. *
  70. * ```js
  71. * const root = postcss.parse('a { color: black; z-index: 1 }')
  72. * const rule = root.first
  73. *
  74. * for (const decl of rule.nodes) {
  75. * decl.cloneBefore({ prop: '-webkit-' + decl.prop })
  76. * // Cycle will be infinite, because cloneBefore moves the current node
  77. * // to the next index
  78. * }
  79. *
  80. * rule.each(decl => {
  81. * decl.cloneBefore({ prop: '-webkit-' + decl.prop })
  82. * // Will be executed only for color and z-index
  83. * })
  84. * ```
  85. *
  86. * @param callback Iterator receives each node and index.
  87. * @return Returns `false` if iteration was broke.
  88. */
  89. each(
  90. callback: (node: Child, index: number) => false | void
  91. ): false | undefined
  92. /**
  93. * Traverses the containers descendant nodes, calling callback
  94. * for each node.
  95. *
  96. * Like container.each(), this method is safe to use
  97. * if you are mutating arrays during iteration.
  98. *
  99. * If you only need to iterate through the containers immediate children,
  100. * use `Container#each`.
  101. *
  102. * ```js
  103. * root.walk(node => {
  104. * // Traverses all descendant nodes.
  105. * })
  106. * ```
  107. *
  108. * @param callback Iterator receives each node and index.
  109. * @return Returns `false` if iteration was broke.
  110. */
  111. walk(
  112. callback: (node: ChildNode, index: number) => false | void
  113. ): false | undefined
  114. /**
  115. * Traverses the containers descendant nodes, calling callback
  116. * for each declaration node.
  117. *
  118. * If you pass a filter, iteration will only happen over declarations
  119. * with matching properties.
  120. *
  121. * ```js
  122. * root.walkDecls(decl => {
  123. * checkPropertySupport(decl.prop)
  124. * })
  125. *
  126. * root.walkDecls('border-radius', decl => {
  127. * decl.remove()
  128. * })
  129. *
  130. * root.walkDecls(/^background/, decl => {
  131. * decl.value = takeFirstColorFromGradient(decl.value)
  132. * })
  133. * ```
  134. *
  135. * Like `Container#each`, this method is safe
  136. * to use if you are mutating arrays during iteration.
  137. *
  138. * @param prop String or regular expression to filter declarations
  139. * by property name.
  140. * @param callback Iterator receives each node and index.
  141. * @return Returns `false` if iteration was broke.
  142. */
  143. walkDecls(
  144. propFilter: string | RegExp,
  145. callback: (decl: Declaration, index: number) => false | void
  146. ): false | undefined
  147. walkDecls(
  148. callback: (decl: Declaration, index: number) => false | void
  149. ): false | undefined
  150. /**
  151. * Traverses the containers descendant nodes, calling callback
  152. * for each rule node.
  153. *
  154. * If you pass a filter, iteration will only happen over rules
  155. * with matching selectors.
  156. *
  157. * Like `Container#each`, this method is safe
  158. * to use if you are mutating arrays during iteration.
  159. *
  160. * ```js
  161. * const selectors = []
  162. * root.walkRules(rule => {
  163. * selectors.push(rule.selector)
  164. * })
  165. * console.log(`Your CSS uses ${ selectors.length } selectors`)
  166. * ```
  167. *
  168. * @param selector String or regular expression to filter rules by selector.
  169. * @param callback Iterator receives each node and index.
  170. * @return Returns `false` if iteration was broke.
  171. */
  172. walkRules(
  173. selectorFilter: string | RegExp,
  174. callback: (rule: Rule, index: number) => false | void
  175. ): false | undefined
  176. walkRules(
  177. callback: (rule: Rule, index: number) => false | void
  178. ): false | undefined
  179. /**
  180. * Traverses the containers descendant nodes, calling callback
  181. * for each at-rule node.
  182. *
  183. * If you pass a filter, iteration will only happen over at-rules
  184. * that have matching names.
  185. *
  186. * Like `Container#each`, this method is safe
  187. * to use if you are mutating arrays during iteration.
  188. *
  189. * ```js
  190. * root.walkAtRules(rule => {
  191. * if (isOld(rule.name)) rule.remove()
  192. * })
  193. *
  194. * let first = false
  195. * root.walkAtRules('charset', rule => {
  196. * if (!first) {
  197. * first = true
  198. * } else {
  199. * rule.remove()
  200. * }
  201. * })
  202. * ```
  203. *
  204. * @param name String or regular expression to filter at-rules by name.
  205. * @param callback Iterator receives each node and index.
  206. * @return Returns `false` if iteration was broke.
  207. */
  208. walkAtRules(
  209. nameFilter: string | RegExp,
  210. callback: (atRule: AtRule, index: number) => false | void
  211. ): false | undefined
  212. walkAtRules(
  213. callback: (atRule: AtRule, index: number) => false | void
  214. ): false | undefined
  215. /**
  216. * Traverses the containers descendant nodes, calling callback
  217. * for each comment node.
  218. *
  219. * Like `Container#each`, this method is safe
  220. * to use if you are mutating arrays during iteration.
  221. *
  222. * ```js
  223. * root.walkComments(comment => {
  224. * comment.remove()
  225. * })
  226. * ```
  227. *
  228. * @param callback Iterator receives each node and index.
  229. * @return Returns `false` if iteration was broke.
  230. */
  231. walkComments(
  232. callback: (comment: Comment, indexed: number) => false | void
  233. ): false | undefined
  234. walkComments(
  235. callback: (comment: Comment, indexed: number) => false | void
  236. ): false | undefined
  237. /**
  238. * Inserts new nodes to the end of the container.
  239. *
  240. * ```js
  241. * const decl1 = new Declaration({ prop: 'color', value: 'black' })
  242. * const decl2 = new Declaration({ prop: 'background-color', value: 'white' })
  243. * rule.append(decl1, decl2)
  244. *
  245. * root.append({ name: 'charset', params: '"UTF-8"' }) // at-rule
  246. * root.append({ selector: 'a' }) // rule
  247. * rule.append({ prop: 'color', value: 'black' }) // declaration
  248. * rule.append({ text: 'Comment' }) // comment
  249. *
  250. * root.append('a {}')
  251. * root.first.append('color: black; z-index: 1')
  252. * ```
  253. *
  254. * @param nodes New nodes.
  255. * @return This node for methods chain.
  256. */
  257. append(
  258. ...nodes: (Node | Node[] | ChildProps | ChildProps[] | string | string[])[]
  259. ): this
  260. /**
  261. * Inserts new nodes to the start of the container.
  262. *
  263. * ```js
  264. * const decl1 = new Declaration({ prop: 'color', value: 'black' })
  265. * const decl2 = new Declaration({ prop: 'background-color', value: 'white' })
  266. * rule.prepend(decl1, decl2)
  267. *
  268. * root.append({ name: 'charset', params: '"UTF-8"' }) // at-rule
  269. * root.append({ selector: 'a' }) // rule
  270. * rule.append({ prop: 'color', value: 'black' }) // declaration
  271. * rule.append({ text: 'Comment' }) // comment
  272. *
  273. * root.append('a {}')
  274. * root.first.append('color: black; z-index: 1')
  275. * ```
  276. *
  277. * @param nodes New nodes.
  278. * @return This node for methods chain.
  279. */
  280. prepend(
  281. ...nodes: (Node | Node[] | ChildProps | ChildProps[] | string | string[])[]
  282. ): this
  283. /**
  284. * Add child to the end of the node.
  285. *
  286. * ```js
  287. * rule.push(new Declaration({ prop: 'color', value: 'black' }))
  288. * ```
  289. *
  290. * @param child New node.
  291. * @return This node for methods chain.
  292. */
  293. push(child: Child): this
  294. /**
  295. * Insert new node before old node within the container.
  296. *
  297. * ```js
  298. * rule.insertBefore(decl, decl.clone({ prop: '-webkit-' + decl.prop }))
  299. * ```
  300. *
  301. * @param oldNode Child or childs index.
  302. * @param newNode New node.
  303. * @return This node for methods chain.
  304. */
  305. insertBefore(
  306. oldNode: Child | number,
  307. newNode: Child | ChildProps | string | Child[] | ChildProps[] | string[]
  308. ): this
  309. /**
  310. * Insert new node after old node within the container.
  311. *
  312. * @param oldNode Child or childs index.
  313. * @param newNode New node.
  314. * @return This node for methods chain.
  315. */
  316. insertAfter(
  317. oldNode: Child | number,
  318. newNode: Child | ChildProps | string | Child[] | ChildProps[] | string[]
  319. ): this
  320. /**
  321. * Removes node from the container and cleans the parent properties
  322. * from the node and its children.
  323. *
  324. * ```js
  325. * rule.nodes.length //=> 5
  326. * rule.removeChild(decl)
  327. * rule.nodes.length //=> 4
  328. * decl.parent //=> undefined
  329. * ```
  330. *
  331. * @param child Child or childs index.
  332. * @return This node for methods chain.
  333. */
  334. removeChild(child: Child | number): this
  335. /**
  336. * Removes all children from the container
  337. * and cleans their parent properties.
  338. *
  339. * ```js
  340. * rule.removeAll()
  341. * rule.nodes.length //=> 0
  342. * ```
  343. *
  344. * @return This node for methods chain.
  345. */
  346. removeAll(): this
  347. /**
  348. * Passes all declaration values within the container that match pattern
  349. * through callback, replacing those values with the returned result
  350. * of callback.
  351. *
  352. * This method is useful if you are using a custom unit or function
  353. * and need to iterate through all values.
  354. *
  355. * ```js
  356. * root.replaceValues(/\d+rem/, { fast: 'rem' }, string => {
  357. * return 15 * parseInt(string) + 'px'
  358. * })
  359. * ```
  360. *
  361. * @param pattern Replace pattern.
  362. * @param {object} opts Options to speed up the search.
  363. * @param callback String to replace pattern or callback
  364. * that returns a new value. The callback
  365. * will receive the same arguments
  366. * as those passed to a function parameter
  367. * of `String#replace`.
  368. * @return This node for methods chain.
  369. */
  370. replaceValues(
  371. pattern: string | RegExp,
  372. options: ValueOptions,
  373. replaced: string | { (substring: string, ...args: any[]): string }
  374. ): this
  375. replaceValues(
  376. pattern: string | RegExp,
  377. replaced: string | { (substring: string, ...args: any[]): string }
  378. ): this
  379. /**
  380. * Returns `true` if callback returns `true`
  381. * for all of the containers children.
  382. *
  383. * ```js
  384. * const noPrefixes = rule.every(i => i.prop[0] !== '-')
  385. * ```
  386. *
  387. * @param condition Iterator returns true or false.
  388. * @return Is every child pass condition.
  389. */
  390. every(
  391. condition: (node: Child, index: number, nodes: Child[]) => boolean
  392. ): boolean
  393. /**
  394. * Returns `true` if callback returns `true` for (at least) one
  395. * of the containers children.
  396. *
  397. * ```js
  398. * const hasPrefix = rule.some(i => i.prop[0] === '-')
  399. * ```
  400. *
  401. * @param condition Iterator returns true or false.
  402. * @return Is some child pass condition.
  403. */
  404. some(
  405. condition: (node: Child, index: number, nodes: Child[]) => boolean
  406. ): boolean
  407. /**
  408. * Returns a `child`s index within the `Container#nodes` array.
  409. *
  410. * ```js
  411. * rule.index( rule.nodes[2] ) //=> 2
  412. * ```
  413. *
  414. * @param child Child of the current container.
  415. * @return Child index.
  416. */
  417. index(child: Child | number): number
  418. }