@jesscss/core 2.0.0-alpha.5 → 2.0.0-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (637) hide show
  1. package/lib/index.cjs +20159 -0
  2. package/lib/index.d.cts +5993 -0
  3. package/lib/index.d.cts.map +1 -0
  4. package/lib/index.d.ts +5992 -21
  5. package/lib/index.d.ts.map +1 -1
  6. package/lib/index.js +19926 -22
  7. package/lib/index.js.map +1 -1
  8. package/package.json +15 -14
  9. package/src/__tests__/define-function-record.test.ts +58 -0
  10. package/src/__tests__/define-function-simple.test.ts +55 -0
  11. package/src/__tests__/define-function-split-sequence.test.ts +547 -0
  12. package/src/__tests__/define-function-type-parity.test.ts +9 -0
  13. package/src/__tests__/define-function.test.ts +763 -0
  14. package/src/__tests__/num-operations.test.ts +91 -0
  15. package/src/__tests__/safe-parse.test.ts +374 -0
  16. package/src/context.ts +896 -0
  17. package/src/conversions.ts +282 -0
  18. package/src/debug-log.ts +29 -0
  19. package/src/define-function.ts +1006 -0
  20. package/src/deprecation.ts +67 -0
  21. package/src/globals.d.ts +26 -0
  22. package/src/index.ts +31 -0
  23. package/src/jess-error.ts +773 -0
  24. package/src/logger/deprecation-processing.ts +109 -0
  25. package/src/logger.ts +31 -0
  26. package/src/plugin.ts +292 -0
  27. package/src/tree/LOOKUP_CHAINS.md +35 -0
  28. package/src/tree/README.md +18 -0
  29. package/src/tree/__tests__/__snapshots__/extend-eval-integration.test.ts.snap +1455 -0
  30. package/src/tree/__tests__/ampersand.test.ts +382 -0
  31. package/src/tree/__tests__/at-rule.test.ts +2047 -0
  32. package/src/tree/__tests__/basic-render.test.ts +212 -0
  33. package/src/tree/__tests__/block.test.ts +40 -0
  34. package/src/tree/__tests__/call.test.ts +346 -0
  35. package/src/tree/__tests__/color.test.ts +537 -0
  36. package/src/tree/__tests__/condition.test.ts +186 -0
  37. package/src/tree/__tests__/control.test.ts +564 -0
  38. package/src/tree/__tests__/declaration.test.ts +253 -0
  39. package/src/tree/__tests__/dependency-graph.test.ts +177 -0
  40. package/src/tree/__tests__/detached-rulesets.test.ts +213 -0
  41. package/src/tree/__tests__/dimension.test.ts +236 -0
  42. package/src/tree/__tests__/expression.test.ts +73 -0
  43. package/src/tree/__tests__/ext-node.test.ts +31 -0
  44. package/src/tree/__tests__/extend-eval-integration.test.ts +1033 -0
  45. package/src/tree/__tests__/extend-import-style.test.ts +929 -0
  46. package/src/tree/__tests__/extend-less-fixtures.test.ts +851 -0
  47. package/src/tree/__tests__/extend-list.test.ts +31 -0
  48. package/src/tree/__tests__/extend-roots.test.ts +1045 -0
  49. package/src/tree/__tests__/extend-rules.test.ts +740 -0
  50. package/src/tree/__tests__/func.test.ts +171 -0
  51. package/src/tree/__tests__/import-js.test.ts +33 -0
  52. package/src/tree/__tests__/import-style-test-helpers.ts +56 -0
  53. package/src/tree/__tests__/import-style.test.ts +1967 -0
  54. package/src/tree/__tests__/interpolated-reference.test.ts +44 -0
  55. package/src/tree/__tests__/interpolated.test.ts +41 -0
  56. package/src/tree/__tests__/list.test.ts +177 -0
  57. package/src/tree/__tests__/log.test.ts +83 -0
  58. package/src/tree/__tests__/mixin-recursion.test.ts +639 -0
  59. package/src/tree/__tests__/mixin.test.ts +2171 -0
  60. package/src/tree/__tests__/negative.test.ts +45 -0
  61. package/src/tree/__tests__/nesting-collapse.test.ts +519 -0
  62. package/src/tree/__tests__/node-flags-perf.test.ts +195 -0
  63. package/src/tree/__tests__/node-flags.test.ts +410 -0
  64. package/src/tree/__tests__/node-graph.test.ts +598 -0
  65. package/src/tree/__tests__/node-mutation.test.ts +182 -0
  66. package/src/tree/__tests__/operation.test.ts +18 -0
  67. package/src/tree/__tests__/paren.test.ts +90 -0
  68. package/src/tree/__tests__/preserve-mode-output.test.ts +50 -0
  69. package/src/tree/__tests__/quoted.test.ts +72 -0
  70. package/src/tree/__tests__/range.test.ts +59 -0
  71. package/src/tree/__tests__/reference.test.ts +743 -0
  72. package/src/tree/__tests__/rest.test.ts +29 -0
  73. package/src/tree/__tests__/rules-raw.test.ts +14 -0
  74. package/src/tree/__tests__/rules.test.ts +1271 -0
  75. package/src/tree/__tests__/ruleset.test.ts +597 -0
  76. package/src/tree/__tests__/selector-attr.test.ts +50 -0
  77. package/src/tree/__tests__/selector-basic.test.ts +44 -0
  78. package/src/tree/__tests__/selector-capture.test.ts +22 -0
  79. package/src/tree/__tests__/selector-complex.test.ts +120 -0
  80. package/src/tree/__tests__/selector-compound.test.ts +74 -0
  81. package/src/tree/__tests__/selector-interpolated.test.ts +50 -0
  82. package/src/tree/__tests__/selector-list.test.ts +59 -0
  83. package/src/tree/__tests__/selector-pseudo.test.ts +23 -0
  84. package/src/tree/__tests__/selector.test.ts +182 -0
  85. package/src/tree/__tests__/sequence.test.ts +226 -0
  86. package/src/tree/__tests__/serialize-types.test.ts +529 -0
  87. package/src/tree/__tests__/spaced.test.ts +8 -0
  88. package/src/tree/__tests__/url.test.ts +72 -0
  89. package/src/tree/__tests__/var-declaration.test.ts +90 -0
  90. package/src/tree/ampersand.ts +538 -0
  91. package/src/tree/any.ts +169 -0
  92. package/src/tree/at-rule.ts +760 -0
  93. package/src/tree/block.ts +72 -0
  94. package/src/tree/bool.ts +46 -0
  95. package/src/tree/call.ts +593 -0
  96. package/src/tree/collection.ts +52 -0
  97. package/src/tree/color.ts +629 -0
  98. package/src/tree/combinator.ts +30 -0
  99. package/src/tree/comment.ts +36 -0
  100. package/src/tree/condition.ts +194 -0
  101. package/src/tree/control.ts +452 -0
  102. package/src/tree/declaration-custom.ts +56 -0
  103. package/src/tree/declaration-var.ts +87 -0
  104. package/src/tree/declaration.ts +742 -0
  105. package/src/tree/default-guard.ts +35 -0
  106. package/src/tree/dimension.ts +392 -0
  107. package/src/tree/expression.ts +97 -0
  108. package/src/tree/extend-list.ts +51 -0
  109. package/src/tree/extend.ts +391 -0
  110. package/src/tree/function.ts +254 -0
  111. package/src/tree/import-js.ts +130 -0
  112. package/src/tree/import-style.ts +875 -0
  113. package/{lib/tree/index.js → src/tree/index.ts} +49 -22
  114. package/src/tree/interpolated.ts +346 -0
  115. package/src/tree/js-array.ts +21 -0
  116. package/src/tree/js-expr.ts +50 -0
  117. package/src/tree/js-function.ts +31 -0
  118. package/src/tree/js-object.ts +22 -0
  119. package/src/tree/list.ts +415 -0
  120. package/src/tree/log.ts +89 -0
  121. package/src/tree/mixin.ts +331 -0
  122. package/src/tree/negative.ts +58 -0
  123. package/src/tree/nil.ts +57 -0
  124. package/src/tree/node-base.ts +1716 -0
  125. package/src/tree/node-type.ts +122 -0
  126. package/src/tree/node.ts +118 -0
  127. package/src/tree/number.ts +54 -0
  128. package/src/tree/operation.ts +187 -0
  129. package/src/tree/paren.ts +132 -0
  130. package/src/tree/query-condition.ts +47 -0
  131. package/src/tree/quoted.ts +119 -0
  132. package/src/tree/range.ts +101 -0
  133. package/src/tree/reference.ts +1099 -0
  134. package/src/tree/rest.ts +55 -0
  135. package/src/tree/rules-raw.ts +52 -0
  136. package/src/tree/rules.ts +2896 -0
  137. package/src/tree/ruleset.ts +1217 -0
  138. package/src/tree/selector-attr.ts +172 -0
  139. package/src/tree/selector-basic.ts +75 -0
  140. package/src/tree/selector-capture.ts +85 -0
  141. package/src/tree/selector-complex.ts +189 -0
  142. package/src/tree/selector-compound.ts +205 -0
  143. package/src/tree/selector-interpolated.ts +95 -0
  144. package/src/tree/selector-list.ts +245 -0
  145. package/src/tree/selector-pseudo.ts +173 -0
  146. package/src/tree/selector-simple.ts +10 -0
  147. package/src/tree/selector.ts +152 -0
  148. package/src/tree/sequence.ts +463 -0
  149. package/src/tree/tree.ts +130 -0
  150. package/src/tree/url.ts +95 -0
  151. package/src/tree/util/EXTEND_ARCHITECTURE_ANALYSIS.md +215 -0
  152. package/src/tree/util/EXTEND_AUDIT.md +233 -0
  153. package/src/tree/util/EXTEND_BASELINE.md +64 -0
  154. package/src/tree/util/EXTEND_CALL_GRAPH_ANALYSIS.md +244 -0
  155. package/src/tree/util/EXTEND_DOCS.md +24 -0
  156. package/src/tree/util/EXTEND_FINAL_SUMMARY.md +95 -0
  157. package/src/tree/util/EXTEND_FUNCTION_AUDIT.md +1433 -0
  158. package/src/tree/util/EXTEND_OPTIMIZATION_PLAN.md +114 -0
  159. package/src/tree/util/EXTEND_REFACTORING_SUMMARY.md +152 -0
  160. package/src/tree/util/EXTEND_RULES.md +74 -0
  161. package/src/tree/util/EXTEND_UNUSED_FUNCTIONS.md +127 -0
  162. package/src/tree/util/EXTEND_UNUSED_FUNCTIONS_ANALYSIS.md +227 -0
  163. package/src/tree/util/NODE_COPY_REDUCTION_PLAN.md +12 -0
  164. package/src/tree/util/__tests__/EXTEND_TEST_INDEX.md +59 -0
  165. package/src/tree/util/__tests__/OPTIMIZATION-ANALYSIS.md +130 -0
  166. package/src/tree/util/__tests__/WALK_AND_CONSUME_DESIGN.md +138 -0
  167. package/src/tree/util/__tests__/_archive/2026-02-09__OPTIMIZATION-ANALYSIS.md +9 -0
  168. package/src/tree/util/__tests__/_archive/README.md +4 -0
  169. package/src/tree/util/__tests__/bitset.test.ts +142 -0
  170. package/src/tree/util/__tests__/debug-log.ts +50 -0
  171. package/src/tree/util/__tests__/extend-comment-handling.test.ts +187 -0
  172. package/src/tree/util/__tests__/extend-core-unit.test.ts +941 -0
  173. package/src/tree/util/__tests__/extend-pipeline-bench.test.ts +154 -0
  174. package/src/tree/util/__tests__/extend-pipeline-bench.ts +190 -0
  175. package/src/tree/util/__tests__/fast-reject.test.ts +377 -0
  176. package/src/tree/util/__tests__/is-node.test.ts +63 -0
  177. package/src/tree/util/__tests__/list-like.test.ts +63 -0
  178. package/src/tree/util/__tests__/outputwriter.test.ts +523 -0
  179. package/src/tree/util/__tests__/print.test.ts +183 -0
  180. package/src/tree/util/__tests__/process-extends.test.ts +226 -0
  181. package/src/tree/util/__tests__/process-leading-is.test.ts +205 -0
  182. package/src/tree/util/__tests__/recursion-helper.test.ts +184 -0
  183. package/src/tree/util/__tests__/selector-match-unit.test.ts +1427 -0
  184. package/src/tree/util/__tests__/sourcemap.test.ts +117 -0
  185. package/src/tree/util/ampersand-template.ts +9 -0
  186. package/src/tree/util/bitset.ts +194 -0
  187. package/src/tree/util/calculate.ts +11 -0
  188. package/src/tree/util/cast.ts +89 -0
  189. package/src/tree/util/cloning.ts +8 -0
  190. package/src/tree/util/collections.ts +299 -0
  191. package/src/tree/util/compare.ts +90 -0
  192. package/src/tree/util/cursor.ts +171 -0
  193. package/src/tree/util/extend-core.ts +2139 -0
  194. package/src/tree/util/extend-roots.ts +1108 -0
  195. package/src/tree/util/field-helpers.ts +354 -0
  196. package/src/tree/util/is-node.ts +43 -0
  197. package/src/tree/util/list-like.ts +93 -0
  198. package/src/tree/util/mixin-instance-primitives.ts +2020 -0
  199. package/src/tree/util/print.ts +303 -0
  200. package/src/tree/util/process-leading-is.ts +421 -0
  201. package/src/tree/util/recursion-helper.ts +54 -0
  202. package/src/tree/util/regex.ts +2 -0
  203. package/src/tree/util/registry-utils.ts +1953 -0
  204. package/src/tree/util/ruleset-trace.ts +17 -0
  205. package/src/tree/util/scoped-body-eval.ts +320 -0
  206. package/src/tree/util/selector-match-core.ts +2005 -0
  207. package/src/tree/util/selector-utils.ts +757 -0
  208. package/src/tree/util/serialize-helper.ts +535 -0
  209. package/src/tree/util/serialize-types.ts +318 -0
  210. package/src/tree/util/should-operate.ts +78 -0
  211. package/src/tree/util/sourcemap.ts +37 -0
  212. package/src/types/config.ts +247 -0
  213. package/src/types/index.ts +12 -0
  214. package/{lib/types/modes.d.ts → src/types/modes.ts} +2 -1
  215. package/src/types.d.ts +9 -0
  216. package/src/types.ts +68 -0
  217. package/src/use-webpack-resolver.ts +56 -0
  218. package/src/visitor/__tests__/visitor.test.ts +136 -0
  219. package/src/visitor/index.ts +263 -0
  220. package/{lib/visitor/less-visitor.js → src/visitor/less-visitor.ts} +3 -2
  221. package/lib/context.d.ts +0 -352
  222. package/lib/context.d.ts.map +0 -1
  223. package/lib/context.js +0 -636
  224. package/lib/context.js.map +0 -1
  225. package/lib/conversions.d.ts +0 -73
  226. package/lib/conversions.d.ts.map +0 -1
  227. package/lib/conversions.js +0 -253
  228. package/lib/conversions.js.map +0 -1
  229. package/lib/debug-log.d.ts +0 -2
  230. package/lib/debug-log.d.ts.map +0 -1
  231. package/lib/debug-log.js +0 -27
  232. package/lib/debug-log.js.map +0 -1
  233. package/lib/define-function.d.ts +0 -587
  234. package/lib/define-function.d.ts.map +0 -1
  235. package/lib/define-function.js +0 -726
  236. package/lib/define-function.js.map +0 -1
  237. package/lib/deprecation.d.ts +0 -34
  238. package/lib/deprecation.d.ts.map +0 -1
  239. package/lib/deprecation.js +0 -57
  240. package/lib/deprecation.js.map +0 -1
  241. package/lib/jess-error.d.ts +0 -343
  242. package/lib/jess-error.d.ts.map +0 -1
  243. package/lib/jess-error.js +0 -508
  244. package/lib/jess-error.js.map +0 -1
  245. package/lib/logger/deprecation-processing.d.ts +0 -41
  246. package/lib/logger/deprecation-processing.d.ts.map +0 -1
  247. package/lib/logger/deprecation-processing.js +0 -81
  248. package/lib/logger/deprecation-processing.js.map +0 -1
  249. package/lib/logger.d.ts +0 -10
  250. package/lib/logger.d.ts.map +0 -1
  251. package/lib/logger.js +0 -20
  252. package/lib/logger.js.map +0 -1
  253. package/lib/plugin.d.ts +0 -94
  254. package/lib/plugin.d.ts.map +0 -1
  255. package/lib/plugin.js +0 -174
  256. package/lib/plugin.js.map +0 -1
  257. package/lib/tree/ampersand.d.ts +0 -98
  258. package/lib/tree/ampersand.d.ts.map +0 -1
  259. package/lib/tree/ampersand.js +0 -319
  260. package/lib/tree/ampersand.js.map +0 -1
  261. package/lib/tree/any.d.ts +0 -58
  262. package/lib/tree/any.d.ts.map +0 -1
  263. package/lib/tree/any.js +0 -104
  264. package/lib/tree/any.js.map +0 -1
  265. package/lib/tree/at-rule.d.ts +0 -53
  266. package/lib/tree/at-rule.d.ts.map +0 -1
  267. package/lib/tree/at-rule.js +0 -503
  268. package/lib/tree/at-rule.js.map +0 -1
  269. package/lib/tree/block.d.ts +0 -22
  270. package/lib/tree/block.d.ts.map +0 -1
  271. package/lib/tree/block.js +0 -24
  272. package/lib/tree/block.js.map +0 -1
  273. package/lib/tree/bool.d.ts +0 -18
  274. package/lib/tree/bool.d.ts.map +0 -1
  275. package/lib/tree/bool.js +0 -28
  276. package/lib/tree/bool.js.map +0 -1
  277. package/lib/tree/call.d.ts +0 -66
  278. package/lib/tree/call.d.ts.map +0 -1
  279. package/lib/tree/call.js +0 -306
  280. package/lib/tree/call.js.map +0 -1
  281. package/lib/tree/collection.d.ts +0 -30
  282. package/lib/tree/collection.d.ts.map +0 -1
  283. package/lib/tree/collection.js +0 -37
  284. package/lib/tree/collection.js.map +0 -1
  285. package/lib/tree/color.d.ts +0 -101
  286. package/lib/tree/color.d.ts.map +0 -1
  287. package/lib/tree/color.js +0 -513
  288. package/lib/tree/color.js.map +0 -1
  289. package/lib/tree/combinator.d.ts +0 -13
  290. package/lib/tree/combinator.d.ts.map +0 -1
  291. package/lib/tree/combinator.js +0 -12
  292. package/lib/tree/combinator.js.map +0 -1
  293. package/lib/tree/comment.d.ts +0 -20
  294. package/lib/tree/comment.d.ts.map +0 -1
  295. package/lib/tree/comment.js +0 -19
  296. package/lib/tree/comment.js.map +0 -1
  297. package/lib/tree/condition.d.ts +0 -31
  298. package/lib/tree/condition.d.ts.map +0 -1
  299. package/lib/tree/condition.js +0 -103
  300. package/lib/tree/condition.js.map +0 -1
  301. package/lib/tree/control.d.ts +0 -104
  302. package/lib/tree/control.d.ts.map +0 -1
  303. package/lib/tree/control.js +0 -430
  304. package/lib/tree/control.js.map +0 -1
  305. package/lib/tree/declaration-custom.d.ts +0 -18
  306. package/lib/tree/declaration-custom.d.ts.map +0 -1
  307. package/lib/tree/declaration-custom.js +0 -24
  308. package/lib/tree/declaration-custom.js.map +0 -1
  309. package/lib/tree/declaration-var.d.ts +0 -35
  310. package/lib/tree/declaration-var.d.ts.map +0 -1
  311. package/lib/tree/declaration-var.js +0 -63
  312. package/lib/tree/declaration-var.js.map +0 -1
  313. package/lib/tree/declaration.d.ts +0 -78
  314. package/lib/tree/declaration.d.ts.map +0 -1
  315. package/lib/tree/declaration.js +0 -286
  316. package/lib/tree/declaration.js.map +0 -1
  317. package/lib/tree/default-guard.d.ts +0 -15
  318. package/lib/tree/default-guard.d.ts.map +0 -1
  319. package/lib/tree/default-guard.js +0 -19
  320. package/lib/tree/default-guard.js.map +0 -1
  321. package/lib/tree/dimension.d.ts +0 -34
  322. package/lib/tree/dimension.d.ts.map +0 -1
  323. package/lib/tree/dimension.js +0 -294
  324. package/lib/tree/dimension.js.map +0 -1
  325. package/lib/tree/expression.d.ts +0 -25
  326. package/lib/tree/expression.d.ts.map +0 -1
  327. package/lib/tree/expression.js +0 -32
  328. package/lib/tree/expression.js.map +0 -1
  329. package/lib/tree/extend-list.d.ts +0 -23
  330. package/lib/tree/extend-list.d.ts.map +0 -1
  331. package/lib/tree/extend-list.js +0 -23
  332. package/lib/tree/extend-list.js.map +0 -1
  333. package/lib/tree/extend.d.ts +0 -47
  334. package/lib/tree/extend.d.ts.map +0 -1
  335. package/lib/tree/extend.js +0 -296
  336. package/lib/tree/extend.js.map +0 -1
  337. package/lib/tree/function.d.ts +0 -48
  338. package/lib/tree/function.d.ts.map +0 -1
  339. package/lib/tree/function.js +0 -74
  340. package/lib/tree/function.js.map +0 -1
  341. package/lib/tree/import-js.d.ts +0 -35
  342. package/lib/tree/import-js.d.ts.map +0 -1
  343. package/lib/tree/import-js.js +0 -45
  344. package/lib/tree/import-js.js.map +0 -1
  345. package/lib/tree/import-style.d.ts +0 -156
  346. package/lib/tree/import-style.d.ts.map +0 -1
  347. package/lib/tree/import-style.js +0 -566
  348. package/lib/tree/import-style.js.map +0 -1
  349. package/lib/tree/index.d.ts +0 -71
  350. package/lib/tree/index.d.ts.map +0 -1
  351. package/lib/tree/index.js.map +0 -1
  352. package/lib/tree/interpolated-reference.d.ts +0 -24
  353. package/lib/tree/interpolated-reference.d.ts.map +0 -1
  354. package/lib/tree/interpolated-reference.js +0 -37
  355. package/lib/tree/interpolated-reference.js.map +0 -1
  356. package/lib/tree/interpolated.d.ts +0 -62
  357. package/lib/tree/interpolated.d.ts.map +0 -1
  358. package/lib/tree/interpolated.js +0 -204
  359. package/lib/tree/interpolated.js.map +0 -1
  360. package/lib/tree/js-array.d.ts +0 -10
  361. package/lib/tree/js-array.d.ts.map +0 -1
  362. package/lib/tree/js-array.js +0 -10
  363. package/lib/tree/js-array.js.map +0 -1
  364. package/lib/tree/js-expr.d.ts +0 -23
  365. package/lib/tree/js-expr.d.ts.map +0 -1
  366. package/lib/tree/js-expr.js +0 -28
  367. package/lib/tree/js-expr.js.map +0 -1
  368. package/lib/tree/js-function.d.ts +0 -20
  369. package/lib/tree/js-function.d.ts.map +0 -1
  370. package/lib/tree/js-function.js +0 -16
  371. package/lib/tree/js-function.js.map +0 -1
  372. package/lib/tree/js-object.d.ts +0 -10
  373. package/lib/tree/js-object.d.ts.map +0 -1
  374. package/lib/tree/js-object.js +0 -10
  375. package/lib/tree/js-object.js.map +0 -1
  376. package/lib/tree/list.d.ts +0 -38
  377. package/lib/tree/list.d.ts.map +0 -1
  378. package/lib/tree/list.js +0 -83
  379. package/lib/tree/list.js.map +0 -1
  380. package/lib/tree/log.d.ts +0 -29
  381. package/lib/tree/log.d.ts.map +0 -1
  382. package/lib/tree/log.js +0 -56
  383. package/lib/tree/log.js.map +0 -1
  384. package/lib/tree/mixin.d.ts +0 -87
  385. package/lib/tree/mixin.d.ts.map +0 -1
  386. package/lib/tree/mixin.js +0 -112
  387. package/lib/tree/mixin.js.map +0 -1
  388. package/lib/tree/negative.d.ts +0 -17
  389. package/lib/tree/negative.d.ts.map +0 -1
  390. package/lib/tree/negative.js +0 -22
  391. package/lib/tree/negative.js.map +0 -1
  392. package/lib/tree/nil.d.ts +0 -30
  393. package/lib/tree/nil.d.ts.map +0 -1
  394. package/lib/tree/nil.js +0 -35
  395. package/lib/tree/nil.js.map +0 -1
  396. package/lib/tree/node-base.d.ts +0 -361
  397. package/lib/tree/node-base.d.ts.map +0 -1
  398. package/lib/tree/node-base.js +0 -930
  399. package/lib/tree/node-base.js.map +0 -1
  400. package/lib/tree/node.d.ts +0 -10
  401. package/lib/tree/node.d.ts.map +0 -1
  402. package/lib/tree/node.js +0 -45
  403. package/lib/tree/node.js.map +0 -1
  404. package/lib/tree/number.d.ts +0 -21
  405. package/lib/tree/number.d.ts.map +0 -1
  406. package/lib/tree/number.js +0 -27
  407. package/lib/tree/number.js.map +0 -1
  408. package/lib/tree/operation.d.ts +0 -26
  409. package/lib/tree/operation.d.ts.map +0 -1
  410. package/lib/tree/operation.js +0 -103
  411. package/lib/tree/operation.js.map +0 -1
  412. package/lib/tree/paren.d.ts +0 -19
  413. package/lib/tree/paren.d.ts.map +0 -1
  414. package/lib/tree/paren.js +0 -92
  415. package/lib/tree/paren.js.map +0 -1
  416. package/lib/tree/query-condition.d.ts +0 -17
  417. package/lib/tree/query-condition.d.ts.map +0 -1
  418. package/lib/tree/query-condition.js +0 -39
  419. package/lib/tree/query-condition.js.map +0 -1
  420. package/lib/tree/quoted.d.ts +0 -28
  421. package/lib/tree/quoted.d.ts.map +0 -1
  422. package/lib/tree/quoted.js +0 -75
  423. package/lib/tree/quoted.js.map +0 -1
  424. package/lib/tree/range.d.ts +0 -33
  425. package/lib/tree/range.d.ts.map +0 -1
  426. package/lib/tree/range.js +0 -47
  427. package/lib/tree/range.js.map +0 -1
  428. package/lib/tree/reference.d.ts +0 -76
  429. package/lib/tree/reference.d.ts.map +0 -1
  430. package/lib/tree/reference.js +0 -521
  431. package/lib/tree/reference.js.map +0 -1
  432. package/lib/tree/rest.d.ts +0 -15
  433. package/lib/tree/rest.d.ts.map +0 -1
  434. package/lib/tree/rest.js +0 -32
  435. package/lib/tree/rest.js.map +0 -1
  436. package/lib/tree/rules-raw.d.ts +0 -17
  437. package/lib/tree/rules-raw.d.ts.map +0 -1
  438. package/lib/tree/rules-raw.js +0 -37
  439. package/lib/tree/rules-raw.js.map +0 -1
  440. package/lib/tree/rules.d.ts +0 -262
  441. package/lib/tree/rules.d.ts.map +0 -1
  442. package/lib/tree/rules.js +0 -2359
  443. package/lib/tree/rules.js.map +0 -1
  444. package/lib/tree/ruleset.d.ts +0 -92
  445. package/lib/tree/ruleset.d.ts.map +0 -1
  446. package/lib/tree/ruleset.js +0 -528
  447. package/lib/tree/ruleset.js.map +0 -1
  448. package/lib/tree/selector-attr.d.ts +0 -31
  449. package/lib/tree/selector-attr.d.ts.map +0 -1
  450. package/lib/tree/selector-attr.js +0 -99
  451. package/lib/tree/selector-attr.js.map +0 -1
  452. package/lib/tree/selector-basic.d.ts +0 -24
  453. package/lib/tree/selector-basic.d.ts.map +0 -1
  454. package/lib/tree/selector-basic.js +0 -38
  455. package/lib/tree/selector-basic.js.map +0 -1
  456. package/lib/tree/selector-capture.d.ts +0 -23
  457. package/lib/tree/selector-capture.d.ts.map +0 -1
  458. package/lib/tree/selector-capture.js +0 -34
  459. package/lib/tree/selector-capture.js.map +0 -1
  460. package/lib/tree/selector-complex.d.ts +0 -40
  461. package/lib/tree/selector-complex.d.ts.map +0 -1
  462. package/lib/tree/selector-complex.js +0 -143
  463. package/lib/tree/selector-complex.js.map +0 -1
  464. package/lib/tree/selector-compound.d.ts +0 -16
  465. package/lib/tree/selector-compound.d.ts.map +0 -1
  466. package/lib/tree/selector-compound.js +0 -114
  467. package/lib/tree/selector-compound.js.map +0 -1
  468. package/lib/tree/selector-interpolated.d.ts +0 -23
  469. package/lib/tree/selector-interpolated.d.ts.map +0 -1
  470. package/lib/tree/selector-interpolated.js +0 -27
  471. package/lib/tree/selector-interpolated.js.map +0 -1
  472. package/lib/tree/selector-list.d.ts +0 -17
  473. package/lib/tree/selector-list.d.ts.map +0 -1
  474. package/lib/tree/selector-list.js +0 -174
  475. package/lib/tree/selector-list.js.map +0 -1
  476. package/lib/tree/selector-pseudo.d.ts +0 -42
  477. package/lib/tree/selector-pseudo.d.ts.map +0 -1
  478. package/lib/tree/selector-pseudo.js +0 -204
  479. package/lib/tree/selector-pseudo.js.map +0 -1
  480. package/lib/tree/selector-simple.d.ts +0 -5
  481. package/lib/tree/selector-simple.d.ts.map +0 -1
  482. package/lib/tree/selector-simple.js +0 -6
  483. package/lib/tree/selector-simple.js.map +0 -1
  484. package/lib/tree/selector.d.ts +0 -43
  485. package/lib/tree/selector.d.ts.map +0 -1
  486. package/lib/tree/selector.js +0 -56
  487. package/lib/tree/selector.js.map +0 -1
  488. package/lib/tree/sequence.d.ts +0 -43
  489. package/lib/tree/sequence.d.ts.map +0 -1
  490. package/lib/tree/sequence.js +0 -151
  491. package/lib/tree/sequence.js.map +0 -1
  492. package/lib/tree/tree.d.ts +0 -87
  493. package/lib/tree/tree.d.ts.map +0 -1
  494. package/lib/tree/tree.js +0 -2
  495. package/lib/tree/tree.js.map +0 -1
  496. package/lib/tree/url.d.ts +0 -18
  497. package/lib/tree/url.d.ts.map +0 -1
  498. package/lib/tree/url.js +0 -35
  499. package/lib/tree/url.js.map +0 -1
  500. package/lib/tree/util/__tests__/debug-log.d.ts +0 -1
  501. package/lib/tree/util/__tests__/debug-log.d.ts.map +0 -1
  502. package/lib/tree/util/__tests__/debug-log.js +0 -36
  503. package/lib/tree/util/__tests__/debug-log.js.map +0 -1
  504. package/lib/tree/util/calculate.d.ts +0 -3
  505. package/lib/tree/util/calculate.d.ts.map +0 -1
  506. package/lib/tree/util/calculate.js +0 -10
  507. package/lib/tree/util/calculate.js.map +0 -1
  508. package/lib/tree/util/cast.d.ts +0 -10
  509. package/lib/tree/util/cast.d.ts.map +0 -1
  510. package/lib/tree/util/cast.js +0 -87
  511. package/lib/tree/util/cast.js.map +0 -1
  512. package/lib/tree/util/cloning.d.ts +0 -4
  513. package/lib/tree/util/cloning.d.ts.map +0 -1
  514. package/lib/tree/util/cloning.js +0 -8
  515. package/lib/tree/util/cloning.js.map +0 -1
  516. package/lib/tree/util/collections.d.ts +0 -57
  517. package/lib/tree/util/collections.d.ts.map +0 -1
  518. package/lib/tree/util/collections.js +0 -136
  519. package/lib/tree/util/collections.js.map +0 -1
  520. package/lib/tree/util/compare.d.ts +0 -11
  521. package/lib/tree/util/compare.d.ts.map +0 -1
  522. package/lib/tree/util/compare.js +0 -89
  523. package/lib/tree/util/compare.js.map +0 -1
  524. package/lib/tree/util/extend-helpers.d.ts +0 -2
  525. package/lib/tree/util/extend-helpers.d.ts.map +0 -1
  526. package/lib/tree/util/extend-helpers.js +0 -2
  527. package/lib/tree/util/extend-helpers.js.map +0 -1
  528. package/lib/tree/util/extend-roots.d.ts +0 -37
  529. package/lib/tree/util/extend-roots.d.ts.map +0 -1
  530. package/lib/tree/util/extend-roots.js +0 -700
  531. package/lib/tree/util/extend-roots.js.map +0 -1
  532. package/lib/tree/util/extend-roots.old.d.ts +0 -132
  533. package/lib/tree/util/extend-roots.old.d.ts.map +0 -1
  534. package/lib/tree/util/extend-roots.old.js +0 -2272
  535. package/lib/tree/util/extend-roots.old.js.map +0 -1
  536. package/lib/tree/util/extend-trace-debug.d.ts +0 -13
  537. package/lib/tree/util/extend-trace-debug.d.ts.map +0 -1
  538. package/lib/tree/util/extend-trace-debug.js +0 -34
  539. package/lib/tree/util/extend-trace-debug.js.map +0 -1
  540. package/lib/tree/util/extend-walk.d.ts +0 -53
  541. package/lib/tree/util/extend-walk.d.ts.map +0 -1
  542. package/lib/tree/util/extend-walk.js +0 -881
  543. package/lib/tree/util/extend-walk.js.map +0 -1
  544. package/lib/tree/util/extend.d.ts +0 -218
  545. package/lib/tree/util/extend.d.ts.map +0 -1
  546. package/lib/tree/util/extend.js +0 -3182
  547. package/lib/tree/util/extend.js.map +0 -1
  548. package/lib/tree/util/find-extendable-locations.d.ts +0 -2
  549. package/lib/tree/util/find-extendable-locations.d.ts.map +0 -1
  550. package/lib/tree/util/find-extendable-locations.js +0 -2
  551. package/lib/tree/util/find-extendable-locations.js.map +0 -1
  552. package/lib/tree/util/format.d.ts +0 -20
  553. package/lib/tree/util/format.d.ts.map +0 -1
  554. package/lib/tree/util/format.js +0 -67
  555. package/lib/tree/util/format.js.map +0 -1
  556. package/lib/tree/util/is-node.d.ts +0 -13
  557. package/lib/tree/util/is-node.d.ts.map +0 -1
  558. package/lib/tree/util/is-node.js +0 -43
  559. package/lib/tree/util/is-node.js.map +0 -1
  560. package/lib/tree/util/print.d.ts +0 -80
  561. package/lib/tree/util/print.d.ts.map +0 -1
  562. package/lib/tree/util/print.js +0 -205
  563. package/lib/tree/util/print.js.map +0 -1
  564. package/lib/tree/util/process-leading-is.d.ts +0 -25
  565. package/lib/tree/util/process-leading-is.d.ts.map +0 -1
  566. package/lib/tree/util/process-leading-is.js +0 -364
  567. package/lib/tree/util/process-leading-is.js.map +0 -1
  568. package/lib/tree/util/recursion-helper.d.ts +0 -15
  569. package/lib/tree/util/recursion-helper.d.ts.map +0 -1
  570. package/lib/tree/util/recursion-helper.js +0 -43
  571. package/lib/tree/util/recursion-helper.js.map +0 -1
  572. package/lib/tree/util/regex.d.ts +0 -4
  573. package/lib/tree/util/regex.d.ts.map +0 -1
  574. package/lib/tree/util/regex.js +0 -4
  575. package/lib/tree/util/regex.js.map +0 -1
  576. package/lib/tree/util/registry-utils.d.ts +0 -192
  577. package/lib/tree/util/registry-utils.d.ts.map +0 -1
  578. package/lib/tree/util/registry-utils.js +0 -1214
  579. package/lib/tree/util/registry-utils.js.map +0 -1
  580. package/lib/tree/util/ruleset-trace.d.ts +0 -4
  581. package/lib/tree/util/ruleset-trace.d.ts.map +0 -1
  582. package/lib/tree/util/ruleset-trace.js +0 -14
  583. package/lib/tree/util/ruleset-trace.js.map +0 -1
  584. package/lib/tree/util/selector-compare.d.ts +0 -2
  585. package/lib/tree/util/selector-compare.d.ts.map +0 -1
  586. package/lib/tree/util/selector-compare.js +0 -2
  587. package/lib/tree/util/selector-compare.js.map +0 -1
  588. package/lib/tree/util/selector-match-core.d.ts +0 -184
  589. package/lib/tree/util/selector-match-core.d.ts.map +0 -1
  590. package/lib/tree/util/selector-match-core.js +0 -1603
  591. package/lib/tree/util/selector-match-core.js.map +0 -1
  592. package/lib/tree/util/selector-utils.d.ts +0 -30
  593. package/lib/tree/util/selector-utils.d.ts.map +0 -1
  594. package/lib/tree/util/selector-utils.js +0 -100
  595. package/lib/tree/util/selector-utils.js.map +0 -1
  596. package/lib/tree/util/serialize-helper.d.ts +0 -13
  597. package/lib/tree/util/serialize-helper.d.ts.map +0 -1
  598. package/lib/tree/util/serialize-helper.js +0 -387
  599. package/lib/tree/util/serialize-helper.js.map +0 -1
  600. package/lib/tree/util/serialize-types.d.ts +0 -9
  601. package/lib/tree/util/serialize-types.d.ts.map +0 -1
  602. package/lib/tree/util/serialize-types.js +0 -216
  603. package/lib/tree/util/serialize-types.js.map +0 -1
  604. package/lib/tree/util/should-operate.d.ts +0 -23
  605. package/lib/tree/util/should-operate.d.ts.map +0 -1
  606. package/lib/tree/util/should-operate.js +0 -46
  607. package/lib/tree/util/should-operate.js.map +0 -1
  608. package/lib/tree/util/sourcemap.d.ts +0 -7
  609. package/lib/tree/util/sourcemap.d.ts.map +0 -1
  610. package/lib/tree/util/sourcemap.js +0 -25
  611. package/lib/tree/util/sourcemap.js.map +0 -1
  612. package/lib/types/config.d.ts +0 -205
  613. package/lib/types/config.d.ts.map +0 -1
  614. package/lib/types/config.js +0 -2
  615. package/lib/types/config.js.map +0 -1
  616. package/lib/types/index.d.ts +0 -15
  617. package/lib/types/index.d.ts.map +0 -1
  618. package/lib/types/index.js +0 -3
  619. package/lib/types/index.js.map +0 -1
  620. package/lib/types/modes.d.ts.map +0 -1
  621. package/lib/types/modes.js +0 -2
  622. package/lib/types/modes.js.map +0 -1
  623. package/lib/types.d.ts +0 -61
  624. package/lib/types.d.ts.map +0 -1
  625. package/lib/types.js +0 -2
  626. package/lib/types.js.map +0 -1
  627. package/lib/use-webpack-resolver.d.ts +0 -9
  628. package/lib/use-webpack-resolver.d.ts.map +0 -1
  629. package/lib/use-webpack-resolver.js +0 -41
  630. package/lib/use-webpack-resolver.js.map +0 -1
  631. package/lib/visitor/index.d.ts +0 -136
  632. package/lib/visitor/index.d.ts.map +0 -1
  633. package/lib/visitor/index.js +0 -135
  634. package/lib/visitor/index.js.map +0 -1
  635. package/lib/visitor/less-visitor.d.ts +0 -7
  636. package/lib/visitor/less-visitor.d.ts.map +0 -1
  637. package/lib/visitor/less-visitor.js.map +0 -1
@@ -0,0 +1,2171 @@
1
+ import { mixin, rules, el, decl, any, condition, expr, ref, list, vardecl, Node, Rules, call, ruleset, Ruleset, rest, sel, co, compound, atrule, interpolated, interpolatedSelector, nil, num, dimension, seq, amp, sellist, defaultguard } from '../index.js';
2
+ import { Context } from '../../context.js';
3
+ import { getFunctionFromMixins } from '../rules.js';
4
+ import { isVisibleInContext } from '../node-base.js';
5
+ import { getParent, getSourceParent, setParent, setSourceParent } from '../util/field-helpers.js';
6
+
7
+ let context: Context;
8
+
9
+ // Helper to check for errors without serializing the resolved value
10
+ async function expectRejects<T>(
11
+ promiseOrValue: Promise<T> | T,
12
+ ErrorType?: new (...args: any[]) => Error,
13
+ messagePattern?: RegExp
14
+ ): Promise<void> {
15
+ let error: unknown;
16
+ try {
17
+ await Promise.resolve(promiseOrValue);
18
+ // Create error that will point to the call site
19
+ const err = new Error('Expected promise to reject, but it resolved');
20
+ // Remove this function from the stack trace so it points to the call site
21
+ if (Error.captureStackTrace) {
22
+ Error.captureStackTrace(err, expectRejects);
23
+ } else {
24
+ // Fallback: remove the first line (this function) from stack
25
+ const stack = err.stack?.split('\n');
26
+ if (stack && stack.length > 1) {
27
+ err.stack = stack.slice(1).join('\n');
28
+ }
29
+ }
30
+ throw err;
31
+ } catch (e) {
32
+ if (e instanceof Error && e.message === 'Expected promise to reject, but it resolved') {
33
+ throw e;
34
+ }
35
+ error = e;
36
+ }
37
+ if (!error) {
38
+ const err = new Error('Expected promise to reject, but it resolved');
39
+ if (Error.captureStackTrace) {
40
+ Error.captureStackTrace(err, expectRejects);
41
+ }
42
+ throw err;
43
+ }
44
+ if (ErrorType && !(error instanceof ErrorType)) {
45
+ const errorName = error instanceof Error ? error.constructor.name : 'unknown';
46
+ const errorMsg = error instanceof Error ? error.message : String(error);
47
+ const err = new Error(`Expected error to be instance of ${ErrorType.name}, but got ${errorName}: ${errorMsg}`);
48
+ if (Error.captureStackTrace) {
49
+ Error.captureStackTrace(err, expectRejects);
50
+ }
51
+ throw err;
52
+ }
53
+ if (messagePattern) {
54
+ const errorMsg = error instanceof Error ? error.message : String(error);
55
+ if (!messagePattern.test(errorMsg)) {
56
+ const err = new Error(`Expected error message to match ${messagePattern}, but got: ${errorMsg}`);
57
+ if (Error.captureStackTrace) {
58
+ Error.captureStackTrace(err, expectRejects);
59
+ }
60
+ throw err;
61
+ }
62
+ }
63
+ }
64
+
65
+ describe('Mixin', () => {
66
+ afterAll(() => {
67
+ Node.prototype.fullRender = false;
68
+ });
69
+ beforeEach(() => {
70
+ Node.prototype.fullRender = true;
71
+ context = new Context({
72
+ /** This is the default Less behavior */
73
+ leakyRules: true
74
+ });
75
+ context.depth = 2;
76
+ });
77
+
78
+ describe('calling', () => {
79
+ it('should call a simple mixin', async () => {
80
+ // Create a mixin definition: .my-mixin() { color: red; }
81
+ const mixinDef = mixin({
82
+ name: any('.my-mixin'),
83
+ rules: rules([
84
+ decl({ name: 'color', value: any('red') })
85
+ ])
86
+ });
87
+
88
+ // Create a ruleset that calls the mixin: .test { .my-mixin(); }
89
+ const testRuleset = ruleset({
90
+ selector: el('.test'),
91
+ rules: rules([
92
+ call({ name: ref({ key: '.my-mixin' }, { type: 'mixin' }) })
93
+ ])
94
+ });
95
+
96
+ // Create root rules containing both
97
+ const root = rules([mixinDef, testRuleset]);
98
+ context.root = root;
99
+
100
+ const evald = await root.eval(context);
101
+ const css = evald.render(context);
102
+
103
+ expect(css).toBeString(`
104
+ .test {
105
+ color: red;
106
+ }
107
+ `);
108
+ });
109
+
110
+ it('should call a ruleset as a mixin (no parens)', async () => {
111
+ // Create a ruleset that can be used as a mixin: .my-mixin { color: red; }
112
+ const mixinRuleset = ruleset({
113
+ selector: el('.my-mixin'),
114
+ rules: rules([
115
+ decl({ name: 'color', value: any('red') })
116
+ ])
117
+ });
118
+
119
+ // Create a ruleset that calls the mixin: .test { .my-mixin(); }
120
+ const testRuleset = ruleset({
121
+ selector: el('.test'),
122
+ rules: rules([
123
+ call({ name: ref({ key: '.my-mixin' }, { type: 'mixin-ruleset' }) }) // Use 'mixin-ruleset' to find both Mixins and Rulesets
124
+ ])
125
+ });
126
+
127
+ // Create root rules containing both
128
+ const root = rules([mixinRuleset, testRuleset]);
129
+ context.root = root;
130
+
131
+ const evald = await root.eval(context);
132
+ const css = evald.render(context);
133
+
134
+ expect(css).toBeString(`
135
+ .my-mixin {
136
+ color: red;
137
+ }
138
+ .test {
139
+ color: red;
140
+ }
141
+ `);
142
+ });
143
+
144
+ it('should call a mixin with parameters', async () => {
145
+ // Create a mixin with a parameter: .my-mixin(@color) { color: @color; }
146
+ const mixinDef = mixin({
147
+ name: any('.my-mixin'),
148
+ params: list([
149
+ any('color', { role: 'property' }) // Parameter without default is Any with role: 'property' (like variable names)
150
+ ]),
151
+ rules: rules([
152
+ decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
153
+ ])
154
+ });
155
+
156
+ // Create a ruleset that calls the mixin: .test { .my-mixin(blue); }
157
+ const testRuleset = ruleset({
158
+ selector: el('.test'),
159
+ rules: rules([
160
+ call({
161
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
162
+ args: list([any('blue')])
163
+ })
164
+ ])
165
+ });
166
+
167
+ // Create root rules containing both
168
+ const root = rules([mixinDef, testRuleset]);
169
+ context.root = root;
170
+
171
+ const evald = await root.eval(context);
172
+ const css = evald.render(context);
173
+
174
+ expect(css).toBeString(`
175
+ .test {
176
+ color: blue;
177
+ }
178
+ `);
179
+ });
180
+
181
+ // Removed: interpolated mixin names are resolved during eval before registration.
182
+ // A mixin with an unresolved interpolated name would never be in the registry.
183
+
184
+ it('should call a mixin with default parameter values', async () => {
185
+ // Create a mixin with a default parameter: .my-mixin(@color: red) { color: @color; }
186
+ const mixinDef = mixin({
187
+ name: any('.my-mixin'),
188
+ params: list([
189
+ vardecl({ name: 'color', value: any('red') }, { paramVar: true })
190
+ ]),
191
+ rules: rules([
192
+ decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
193
+ ])
194
+ });
195
+
196
+ // Create a ruleset that calls the mixin without args: .test { .my-mixin(); }
197
+ const testRuleset1 = ruleset({
198
+ selector: el('.test1'),
199
+ rules: rules([
200
+ call({ name: ref({ key: '.my-mixin' }, { type: 'mixin' }) })
201
+ ])
202
+ });
203
+
204
+ // Create a ruleset that calls the mixin with args: .test2 { .my-mixin(blue); }
205
+ const testRuleset2 = ruleset({
206
+ selector: el('.test2'),
207
+ rules: rules([
208
+ call({
209
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
210
+ args: list([any('blue')])
211
+ })
212
+ ])
213
+ });
214
+
215
+ const root = rules([mixinDef, testRuleset1, testRuleset2]);
216
+ context.root = root;
217
+
218
+ const evald = await root.eval(context);
219
+ // Position-backed trees require context for serialization
220
+ const css = evald.render(context);
221
+
222
+ expect(css).toBeString(`
223
+ .test1 {
224
+ color: red;
225
+ }
226
+ .test2 {
227
+ color: blue;
228
+ }
229
+ `);
230
+ });
231
+
232
+ it('should call a mixin with multiple parameters', async () => {
233
+ // Create a mixin with multiple parameters: .my-mixin(@color, @size) { color: @color; font-size: @size; }
234
+ const mixinDef = mixin({
235
+ name: any('.my-mixin'),
236
+ params: list([
237
+ any('color', { role: 'property' }),
238
+ any('size', { role: 'property' })
239
+ ]),
240
+ rules: rules([
241
+ decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) }),
242
+ decl({ name: 'font-size', value: ref({ key: 'size' }, { type: 'variable' }) })
243
+ ])
244
+ });
245
+
246
+ // Create a ruleset that calls the mixin: .test { .my-mixin(blue, 16px); }
247
+ const testRuleset = ruleset({
248
+ selector: el('.test'),
249
+ rules: rules([
250
+ call({
251
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
252
+ args: list([any('blue'), any('16px')])
253
+ })
254
+ ])
255
+ });
256
+
257
+ const root = rules([mixinDef, testRuleset]);
258
+ context.root = root;
259
+
260
+ const evald = await root.eval(context);
261
+ const css = evald.render(context);
262
+
263
+ expect(css).toBeString(`
264
+ .test {
265
+ color: blue;
266
+ font-size: 16px;
267
+ }
268
+ `);
269
+ });
270
+
271
+ it('should call a mixin with @arguments', async () => {
272
+ // Create a mixin that uses @arguments: .my-mixin(@a, @b) { margin: @arguments; }
273
+ const mixinDef = mixin({
274
+ name: any('.my-mixin'),
275
+ params: list([
276
+ any('a', { role: 'property' }),
277
+ any('b', { role: 'property' })
278
+ ]),
279
+ rules: rules([
280
+ decl({ name: 'margin', value: ref({ key: 'arguments' }, { type: 'variable' }) })
281
+ ])
282
+ });
283
+
284
+ // Use a real dimension node plus a unitless number so @arguments spacing
285
+ // is proven on actual numeric nodes.
286
+ const testRuleset = ruleset({
287
+ selector: el('.test'),
288
+ rules: rules([
289
+ call({
290
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
291
+ args: list([dimension([10, 'px']), num(20)])
292
+ })
293
+ ])
294
+ });
295
+
296
+ const root = rules([mixinDef, testRuleset]);
297
+ context.root = root;
298
+
299
+ const evald = await root.eval(context);
300
+ const css = evald.render(context);
301
+
302
+ expect(css).toBeString(`
303
+ .test {
304
+ margin: 10px 20;
305
+ }
306
+ `);
307
+ });
308
+
309
+ it('preEval sets private rulesVisibility on mixin body', async () => {
310
+ const localContext = new Context({ leakyRules: false });
311
+ localContext.depth = 2;
312
+
313
+ const mixinDef = mixin({
314
+ name: any('.my-mixin'),
315
+ rules: rules([
316
+ decl({ name: 'color', value: any('red') })
317
+ ])
318
+ });
319
+
320
+ const preEvald = await mixinDef.preEval(localContext);
321
+
322
+ const preEvaldRules = preEvald.get('rules');
323
+ expect(preEvaldRules.options.rulesVisibility.Mixin).toBe('private');
324
+ expect(preEvaldRules.options.rulesVisibility.VarDeclaration).toBe('private');
325
+ });
326
+
327
+ it('should call a mixin with a guard condition', async () => {
328
+ // Create a mixin with a guard: .my-mixin(@color) when (@color = red) { color: @color; }
329
+ const mixinDef = mixin({
330
+ name: any('.my-mixin'),
331
+ params: list([
332
+ any('color', { role: 'property' })
333
+ ]),
334
+ guard: condition([
335
+ expr(ref({ key: 'color' }, { type: 'variable' })),
336
+ '=',
337
+ any('red')
338
+ ]),
339
+ rules: rules([
340
+ decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
341
+ ])
342
+ });
343
+
344
+ // Create a ruleset that calls the mixin with matching condition: .test1 { .my-mixin(red); }
345
+ const testRuleset1 = ruleset({
346
+ selector: el('.test1'),
347
+ rules: rules([
348
+ call({
349
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
350
+ args: list([any('red')])
351
+ })
352
+ ])
353
+ });
354
+
355
+ // Create a ruleset that calls the mixin with non-matching condition: .test2 { .my-mixin(blue); }
356
+ const testRuleset2 = ruleset({
357
+ selector: el('.test2'),
358
+ rules: rules([
359
+ call({
360
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
361
+ args: list([any('blue')])
362
+ })
363
+ ])
364
+ });
365
+
366
+ const root = rules([mixinDef, testRuleset1, testRuleset2]);
367
+ context.root = root;
368
+
369
+ const evald = await root.eval(context);
370
+ const css = evald.render(context);
371
+
372
+ expect(css).toBeString(`
373
+ .test1 {
374
+ color: red;
375
+ }
376
+ `);
377
+ });
378
+
379
+ it('replays default() guard candidates with bound params intact', async () => {
380
+ const exactMixin = mixin({
381
+ name: any('.my-mixin'),
382
+ params: list([
383
+ any('color', { role: 'property' })
384
+ ]),
385
+ guard: condition([
386
+ expr(ref({ key: 'color' }, { type: 'variable' })),
387
+ '=',
388
+ any('red')
389
+ ]),
390
+ rules: rules([
391
+ decl({ name: 'color', value: any('exact') })
392
+ ])
393
+ });
394
+
395
+ const defaultMixin = mixin({
396
+ name: any('.my-mixin'),
397
+ params: list([
398
+ any('color', { role: 'property' })
399
+ ]),
400
+ guard: condition([defaultguard('default()')]),
401
+ rules: rules([
402
+ decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
403
+ ])
404
+ }, { hasDefault: true });
405
+
406
+ const exactCall = ruleset({
407
+ selector: el('.exact'),
408
+ rules: rules([
409
+ call({
410
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
411
+ args: list([any('red')])
412
+ })
413
+ ])
414
+ });
415
+
416
+ const fallbackCall = ruleset({
417
+ selector: el('.fallback'),
418
+ rules: rules([
419
+ call({
420
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
421
+ args: list([any('blue')])
422
+ })
423
+ ])
424
+ });
425
+
426
+ const root = rules([exactMixin, defaultMixin, exactCall, fallbackCall]);
427
+ context.root = root;
428
+
429
+ const evald = await root.eval(context);
430
+ const css = evald.render(context);
431
+
432
+ expect(css).toBeString(`
433
+ .exact {
434
+ color: exact;
435
+ }
436
+ .fallback {
437
+ color: blue;
438
+ }
439
+ `);
440
+ });
441
+
442
+ it('guard params resolve and output renders correctly with position isolation', async () => {
443
+ const mixinDef = mixin({
444
+ name: any('.my-mixin'),
445
+ params: list([
446
+ any('color', { role: 'property' })
447
+ ]),
448
+ guard: condition([
449
+ expr(ref({ key: 'color' }, { type: 'variable' })),
450
+ '=',
451
+ any('red')
452
+ ]),
453
+ rules: rules([
454
+ decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
455
+ ])
456
+ });
457
+ const testRuleset = ruleset({
458
+ selector: el('.test'),
459
+ rules: rules([
460
+ call({
461
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
462
+ args: list([any('red')])
463
+ })
464
+ ])
465
+ });
466
+ const root = rules([mixinDef, testRuleset]);
467
+ context.root = root;
468
+
469
+ const evald = await root.eval(context);
470
+ const css = evald.render(context);
471
+
472
+ expect(css).toBeString(`
473
+ .test {
474
+ color: red;
475
+ }
476
+ `);
477
+ });
478
+
479
+ it('blocks a mixin candidate when its failed guard ancestor exists only in the state parent chain', async () => {
480
+ const mixinDef = mixin({
481
+ name: any('.my-mixin'),
482
+ rules: rules([
483
+ decl({ name: 'color', value: any('red') })
484
+ ])
485
+ });
486
+ const failedAncestor = ruleset({
487
+ selector: el('.blocked'),
488
+ guard: nil(),
489
+ rules: rules([])
490
+ });
491
+ setParent(mixinDef, failedAncestor, context);
492
+
493
+ const fn = getFunctionFromMixins(mixinDef);
494
+
495
+ await expectRejects(fn.call(context), ReferenceError, /No matching mixins found/);
496
+ });
497
+
498
+ it('evaluates mixin args against a caller source scope that exists only in the state chain', async () => {
499
+ const mixinDef = mixin({
500
+ name: any('.my-mixin'),
501
+ params: list([
502
+ any('color', { role: 'property' })
503
+ ]),
504
+ rules: rules([
505
+ decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
506
+ ])
507
+ });
508
+ const mixinRoot = rules([mixinDef]);
509
+ context.root = mixinRoot;
510
+
511
+ const sourceAnchor = decl({ name: 'background', value: any('white') });
512
+ const sourceRules = rules([
513
+ vardecl({ name: 'theme', value: any('blue') }),
514
+ sourceAnchor
515
+ ]);
516
+ const caller = call({
517
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
518
+ args: list([ref({ key: 'theme' }, { type: 'variable' })])
519
+ });
520
+
521
+ setSourceParent((caller as any).name, sourceAnchor, context);
522
+ context.caller = caller;
523
+
524
+ const fn = getFunctionFromMixins(mixinDef);
525
+ const result = await fn.call(context, ref({ key: 'theme' }, { type: 'variable' }));
526
+
527
+ expect(result.render(context)).toBeString(`
528
+ color: blue;
529
+ `);
530
+ });
531
+
532
+ it('mixin with parameters renders correctly via eval', async () => {
533
+ const mixinDef = mixin({
534
+ name: any('.my-mixin'),
535
+ params: list([
536
+ any('color', { role: 'property' })
537
+ ]),
538
+ rules: rules([
539
+ decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
540
+ ])
541
+ });
542
+ const testRuleset = ruleset({
543
+ selector: el('.test'),
544
+ rules: rules([
545
+ call({
546
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
547
+ args: list([any('blue')])
548
+ })
549
+ ])
550
+ });
551
+ const root = rules([mixinDef, testRuleset]);
552
+ context.root = root;
553
+
554
+ const evald = await root.eval(context);
555
+ const css = evald.render(context);
556
+
557
+ expect(css).toBeString(`
558
+ .test {
559
+ color: blue;
560
+ }
561
+ `);
562
+ });
563
+
564
+ it('mixin with bound parameters renders values from call site', async () => {
565
+ const mixinDef = mixin({
566
+ name: any('.my-mixin'),
567
+ params: list([
568
+ any('color', { role: 'property' })
569
+ ]),
570
+ rules: rules([
571
+ decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
572
+ ])
573
+ });
574
+ const testRuleset = ruleset({
575
+ selector: el('.test'),
576
+ rules: rules([
577
+ call({
578
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
579
+ args: list([any('blue')])
580
+ })
581
+ ])
582
+ });
583
+ const root = rules([mixinDef, testRuleset]);
584
+ context.root = root;
585
+
586
+ const evald = await root.eval(context);
587
+ const css = evald.render(context);
588
+
589
+ expect(css).toBeString(`
590
+ .test {
591
+ color: blue;
592
+ }
593
+ `);
594
+ });
595
+
596
+ it('mixin with nested ruleset renders with position-resolved values', async () => {
597
+ const mixinDef = mixin({
598
+ name: any('.my-mixin'),
599
+ params: list([
600
+ any('color', { role: 'property' })
601
+ ]),
602
+ rules: rules([
603
+ ruleset({
604
+ selector: el('.inner'),
605
+ rules: rules([
606
+ decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
607
+ ])
608
+ })
609
+ ])
610
+ });
611
+ const testRuleset = ruleset({
612
+ selector: el('.test'),
613
+ rules: rules([
614
+ call({
615
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
616
+ args: list([any('blue')])
617
+ })
618
+ ])
619
+ });
620
+ const root = rules([mixinDef, testRuleset]);
621
+ context.root = root;
622
+
623
+ const evald = await root.eval(context);
624
+ const css = evald.render(context);
625
+
626
+ expect(css).toBeString(`
627
+ .test {
628
+ .inner {
629
+ color: blue;
630
+ }
631
+ }
632
+ `);
633
+ });
634
+
635
+ it('mixin with declarations and nested rulesets resolves params through position', async () => {
636
+ const mixinDef = mixin({
637
+ name: any('.my-mixin'),
638
+ params: list([
639
+ any('color', { role: 'property' })
640
+ ]),
641
+ rules: rules([
642
+ decl({ name: 'background', value: ref({ key: 'color' }, { type: 'variable' }) }),
643
+ ruleset({
644
+ selector: el('.inner'),
645
+ rules: rules([
646
+ decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
647
+ ])
648
+ })
649
+ ])
650
+ });
651
+ const testRuleset = ruleset({
652
+ selector: el('.test'),
653
+ rules: rules([
654
+ call({
655
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
656
+ args: list([any('blue')])
657
+ })
658
+ ])
659
+ });
660
+ const root = rules([mixinDef, testRuleset]);
661
+ context.root = root;
662
+
663
+ const evald = await root.eval(context);
664
+ const css = evald.render(context);
665
+
666
+ expect(css).toBeString(`
667
+ .test {
668
+ background: blue;
669
+ .inner {
670
+ color: blue;
671
+ }
672
+ }
673
+ `);
674
+ });
675
+
676
+ it('multi-candidate mixin renders output from all matching candidates', async () => {
677
+ const mixinA = mixin({
678
+ name: any('.my-mixin'),
679
+ rules: rules([
680
+ decl({ name: 'color', value: any('red') })
681
+ ])
682
+ });
683
+ const mixinB = mixin({
684
+ name: any('.my-mixin'),
685
+ rules: rules([
686
+ ruleset({
687
+ selector: el('.inner'),
688
+ rules: rules([
689
+ decl({ name: 'background', value: any('blue') })
690
+ ])
691
+ })
692
+ ])
693
+ });
694
+ const testRuleset = ruleset({
695
+ selector: el('.test'),
696
+ rules: rules([
697
+ call({
698
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' })
699
+ })
700
+ ])
701
+ });
702
+ const root = rules([mixinA, mixinB, testRuleset]);
703
+ context.root = root;
704
+
705
+ const evald = await root.eval(context);
706
+ const css = evald.render(context);
707
+
708
+ expect(css).toBeString(`
709
+ .test {
710
+ color: red;
711
+ .inner {
712
+ background: blue;
713
+ }
714
+ }
715
+ `);
716
+ });
717
+
718
+ it('should call a mixin that calls another mixin', async () => {
719
+ // Create a base mixin: .base-mixin(@color) { color: @color; }
720
+ const baseMixin = mixin({
721
+ name: any('.base-mixin'),
722
+ params: list([
723
+ any('color', { role: 'property' })
724
+ ]),
725
+ rules: rules([
726
+ decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
727
+ ])
728
+ });
729
+
730
+ // Create a mixin that calls the base mixin: .wrapper-mixin(@color) { .base-mixin(@color); }
731
+ const wrapperMixin = mixin({
732
+ name: any('.wrapper-mixin'),
733
+ params: list([
734
+ any('color', { role: 'property' })
735
+ ]),
736
+ rules: rules([
737
+ call({
738
+ name: ref({ key: '.base-mixin' }, { type: 'mixin' }),
739
+ args: list([ref({ key: 'color' }, { type: 'variable' })])
740
+ })
741
+ ])
742
+ });
743
+
744
+ // Create a ruleset that calls the wrapper mixin: .test { .wrapper-mixin(blue); }
745
+ const testRuleset = ruleset({
746
+ selector: el('.test'),
747
+ rules: rules([
748
+ call({
749
+ name: ref({ key: '.wrapper-mixin' }, { type: 'mixin' }),
750
+ args: list([any('blue')])
751
+ })
752
+ ])
753
+ });
754
+
755
+ const root = rules([baseMixin, wrapperMixin, testRuleset]);
756
+ context.root = root;
757
+
758
+ const evald = await root.eval(context);
759
+ const css = evald.render(context);
760
+
761
+ expect(css).toBeString(`
762
+ .test {
763
+ color: blue;
764
+ }
765
+ `);
766
+ });
767
+
768
+ it('nested mixin calls render correctly through position pipeline', async () => {
769
+ const baseMixin = mixin({
770
+ name: any('.base-mixin'),
771
+ params: list([
772
+ any('color', { role: 'property' })
773
+ ]),
774
+ rules: rules([
775
+ decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
776
+ ])
777
+ });
778
+
779
+ const wrapperMixin = mixin({
780
+ name: any('.wrapper-mixin'),
781
+ params: list([
782
+ any('color', { role: 'property' })
783
+ ]),
784
+ rules: rules([
785
+ call({
786
+ name: ref({ key: '.base-mixin' }, { type: 'mixin' }),
787
+ args: list([ref({ key: 'color' }, { type: 'variable' })])
788
+ })
789
+ ])
790
+ });
791
+
792
+ const testRuleset = ruleset({
793
+ selector: el('.test'),
794
+ rules: rules([
795
+ call({
796
+ name: ref({ key: '.wrapper-mixin' }, { type: 'mixin' }),
797
+ args: list([any('blue')])
798
+ })
799
+ ])
800
+ });
801
+
802
+ const root = rules([baseMixin, wrapperMixin, testRuleset]);
803
+ context.root = root;
804
+
805
+ const evald = await root.eval(context);
806
+ const css = evald.render(context);
807
+
808
+ expect(css).toBeString(`
809
+ .test {
810
+ color: blue;
811
+ }
812
+ `);
813
+ });
814
+
815
+ it('keeps outer mixin params available to nested emitted mixins through ruleset closure', async () => {
816
+ const personMixin = mixin({
817
+ name: any('.Person'),
818
+ params: list([
819
+ vardecl({ name: 'name', value: nil() }, { paramVar: true }),
820
+ vardecl({ name: 'gender_', value: nil() }, { paramVar: true })
821
+ ]),
822
+ rules: rules([
823
+ ruleset({
824
+ selector: sellist([
825
+ sel([
826
+ interpolatedSelector(interpolated({
827
+ source: '.%%',
828
+ replacements: [ref({ key: 'name' }, { type: 'variable' })]
829
+ }))
830
+ ])
831
+ ]),
832
+ rules: rules([
833
+ vardecl({
834
+ name: 'gender',
835
+ value: ref({ key: 'gender_' }, { type: 'variable' })
836
+ }),
837
+ mixin({
838
+ name: any('.sayGender'),
839
+ rules: rules([
840
+ decl({ name: 'gender', value: ref({ key: 'gender' }, { type: 'variable' }) })
841
+ ])
842
+ })
843
+ ])
844
+ })
845
+ ])
846
+ });
847
+
848
+ const testRuleset = ruleset({
849
+ selector: el('mi-test-d'),
850
+ rules: rules([
851
+ call({
852
+ name: ref({ key: '.Person' }, { type: 'mixin' }),
853
+ args: list([any('person'), any('"Male"')])
854
+ }),
855
+ call({
856
+ name: ref({ key: ['.person', '.sayGender'] }, { type: 'mixin-ruleset' })
857
+ })
858
+ ])
859
+ });
860
+
861
+ const root = rules([personMixin, testRuleset]);
862
+ context.root = root;
863
+
864
+ const evald = await root.eval(context);
865
+ const previousFullRender = Node.prototype.fullRender;
866
+ Node.prototype.fullRender = false;
867
+ let css: string;
868
+ try {
869
+ css = evald.render(context);
870
+ } finally {
871
+ Node.prototype.fullRender = previousFullRender;
872
+ }
873
+
874
+ expect(css).toBeString(`
875
+ mi-test-d {
876
+ gender: "Male";
877
+ }
878
+ `);
879
+ });
880
+
881
+ it('keeps emitted namespace rules available for lookup without making them render-visible', async () => {
882
+ const personMixin = mixin({
883
+ name: any('.Person'),
884
+ params: list([
885
+ vardecl({ name: 'name', value: nil() }, { paramVar: true }),
886
+ vardecl({ name: 'gender_', value: nil() }, { paramVar: true })
887
+ ]),
888
+ rules: rules([
889
+ ruleset({
890
+ selector: sellist([
891
+ sel([
892
+ interpolatedSelector(interpolated({
893
+ source: '.%%',
894
+ replacements: [ref({ key: 'name' }, { type: 'variable' })]
895
+ }))
896
+ ])
897
+ ]),
898
+ rules: rules([
899
+ vardecl({
900
+ name: 'gender',
901
+ value: ref({ key: 'gender_' }, { type: 'variable' })
902
+ }),
903
+ mixin({
904
+ name: any('.sayGender'),
905
+ rules: rules([
906
+ decl({ name: 'gender', value: ref({ key: 'gender' }, { type: 'variable' }) })
907
+ ])
908
+ })
909
+ ])
910
+ })
911
+ ])
912
+ });
913
+
914
+ const firstCall = call({
915
+ name: ref({ key: '.Person' }, { type: 'mixin' }),
916
+ args: list([any('person'), any('"Male"')])
917
+ });
918
+
919
+ const root = rules([personMixin, firstCall]);
920
+ context.root = root;
921
+
922
+ const evald = await root.eval(context);
923
+ const output = evald.get('value')[1];
924
+
925
+ expect(output).toBeInstanceOf(Rules);
926
+
927
+ const outputRules = output as Rules;
928
+ const outputContext = {
929
+ ...context,
930
+ renderKey: outputRules.renderKey,
931
+ rulesContext: outputRules
932
+ } as Context;
933
+ const children = outputRules.getRegistryChildren(outputContext);
934
+ expect(children).toHaveLength(1);
935
+ expect(children[0]).toBeInstanceOf(Ruleset);
936
+
937
+ const personRuleset = children[0] as Ruleset;
938
+ expect(personRuleset.valueOf()).toBe('.person');
939
+
940
+ const personScope = personRuleset.enterRules(outputContext);
941
+ const scopeChildren = personScope.getRegistryChildren(outputContext);
942
+ expect(scopeChildren.map(child => `${child.type}:${isVisibleInContext(child, outputContext)}`)).toEqual([
943
+ 'VarDeclaration:false',
944
+ 'Mixin:false'
945
+ ]);
946
+ expect(isVisibleInContext(personRuleset, outputContext)).toBe(false);
947
+ expect(personScope.find('declaration', 'gender', 'VarDeclaration', { context: outputContext })).toBeDefined();
948
+ expect(personScope.find('mixin', '.sayGender', 'Mixin', { context: outputContext })).toBeDefined();
949
+ });
950
+
951
+ it('keeps namespaced mixin call output isolated in the parent rules before final render', async () => {
952
+ const personMixin = mixin({
953
+ name: any('.Person'),
954
+ params: list([
955
+ vardecl({ name: 'name', value: nil() }, { paramVar: true }),
956
+ vardecl({ name: 'gender_', value: nil() }, { paramVar: true })
957
+ ]),
958
+ rules: rules([
959
+ ruleset({
960
+ selector: sellist([
961
+ sel([
962
+ interpolatedSelector(interpolated({
963
+ source: '.%%',
964
+ replacements: [ref({ key: 'name' }, { type: 'variable' })]
965
+ }))
966
+ ])
967
+ ]),
968
+ rules: rules([
969
+ vardecl({
970
+ name: 'gender',
971
+ value: ref({ key: 'gender_' }, { type: 'variable' })
972
+ }),
973
+ mixin({
974
+ name: any('.sayGender'),
975
+ rules: rules([
976
+ decl({ name: 'gender', value: ref({ key: 'gender' }, { type: 'variable' }) })
977
+ ])
978
+ })
979
+ ])
980
+ })
981
+ ])
982
+ });
983
+
984
+ const host = ruleset({
985
+ selector: el('mi-test-d'),
986
+ rules: rules([
987
+ call({
988
+ name: ref({ key: '.Person' }, { type: 'mixin' }),
989
+ args: list([any('person'), any('"Male"')])
990
+ }),
991
+ call({
992
+ name: ref({ key: ['.person', '.sayGender'] }, { type: 'mixin-ruleset' })
993
+ })
994
+ ])
995
+ });
996
+
997
+ const root = rules([personMixin, host]);
998
+ context.root = root;
999
+
1000
+ const evald = await root.eval(context);
1001
+ const hostRules = (evald.get('value')[1] as Ruleset).enterRules(context);
1002
+ const hostContext = {
1003
+ ...context,
1004
+ renderKey: hostRules.renderKey,
1005
+ rulesContext: hostRules
1006
+ } as Context;
1007
+
1008
+ expect(
1009
+ hostRules.getRegistryChildren(hostContext).map(child => `${child.type}:${child.type === 'Rules' ? (child as Rules).getRegistryChildren(hostContext).map(inner => inner.type).join(',') : child.valueOf()}`)
1010
+ ).toEqual([
1011
+ 'Rules:Ruleset',
1012
+ 'Rules:Declaration'
1013
+ ]);
1014
+
1015
+ const firstCallOutput = hostRules.getRegistryChildren(hostContext)[0] as Rules;
1016
+ const namespacedRuleset = firstCallOutput.getRegistryChildren({
1017
+ ...hostContext,
1018
+ renderKey: firstCallOutput.renderKey,
1019
+ rulesContext: firstCallOutput
1020
+ } as Context)[0] as Ruleset;
1021
+ expect(isVisibleInContext(namespacedRuleset, {
1022
+ ...hostContext,
1023
+ renderKey: firstCallOutput.renderKey,
1024
+ rulesContext: firstCallOutput
1025
+ } as Context)).toBe(false);
1026
+ });
1027
+
1028
+ it('keeps guard and default-param closure for emitted nested mixins', async () => {
1029
+ const lockMixin = mixin({
1030
+ name: any('.lock-mixin'),
1031
+ params: list([
1032
+ vardecl({ name: 'a', value: nil() }, { paramVar: true })
1033
+ ]),
1034
+ rules: rules([
1035
+ mixin({
1036
+ name: any('.inner-locked-mixin'),
1037
+ params: list([
1038
+ vardecl({
1039
+ name: 'x',
1040
+ value: ref({ key: 'a' }, { type: 'variable' })
1041
+ }, { paramVar: true })
1042
+ ]),
1043
+ guard: condition([
1044
+ expr(ref({ key: 'a' }, { type: 'variable' })),
1045
+ '=',
1046
+ num(1)
1047
+ ]),
1048
+ rules: rules([
1049
+ decl({ name: 'a', value: ref({ key: 'a' }, { type: 'variable' }) }),
1050
+ decl({ name: 'x', value: ref({ key: 'x' }, { type: 'variable' }) })
1051
+ ])
1052
+ })
1053
+ ])
1054
+ });
1055
+
1056
+ const host = ruleset({
1057
+ selector: el('.call-lock-mixin'),
1058
+ rules: rules([
1059
+ call({
1060
+ name: ref({ key: '.lock-mixin' }, { type: 'mixin' }),
1061
+ args: list([num(1)])
1062
+ }),
1063
+ ruleset({
1064
+ selector: el('.call-inner-lock-mixin'),
1065
+ rules: rules([
1066
+ call({
1067
+ name: ref({ key: '.inner-locked-mixin' }, { type: 'mixin' })
1068
+ })
1069
+ ])
1070
+ })
1071
+ ])
1072
+ });
1073
+
1074
+ const localContext = new Context({
1075
+ leakyRules: true,
1076
+ collapseNesting: true
1077
+ });
1078
+ localContext.depth = 2;
1079
+ const root = rules([lockMixin, host]);
1080
+ localContext.root = root;
1081
+
1082
+ const evald = await root.eval(localContext);
1083
+ const previousFullRender = Node.prototype.fullRender;
1084
+ Node.prototype.fullRender = false;
1085
+ let css: string;
1086
+ try {
1087
+ css = evald.render(localContext);
1088
+ } finally {
1089
+ Node.prototype.fullRender = previousFullRender;
1090
+ }
1091
+
1092
+ expect(css).toBeString(`
1093
+ .call-lock-mixin .call-inner-lock-mixin {
1094
+ a: 1;
1095
+ x: 1;
1096
+ }
1097
+ `);
1098
+ });
1099
+
1100
+ it('keeps emitted nested mixin closure ahead of same-named globals', async () => {
1101
+ const globalA = vardecl({ name: 'a', value: any('auto') });
1102
+
1103
+ const lockMixin = mixin({
1104
+ name: any('.lock-mixin'),
1105
+ params: list([
1106
+ vardecl({ name: 'a', value: nil() }, { paramVar: true })
1107
+ ]),
1108
+ rules: rules([
1109
+ mixin({
1110
+ name: any('.inner-locked-mixin'),
1111
+ params: list([
1112
+ vardecl({
1113
+ name: 'x',
1114
+ value: ref({ key: 'a' }, { type: 'variable' })
1115
+ }, { paramVar: true })
1116
+ ]),
1117
+ guard: condition([
1118
+ expr(ref({ key: 'a' }, { type: 'variable' })),
1119
+ '=',
1120
+ num(1)
1121
+ ]),
1122
+ rules: rules([
1123
+ decl({ name: 'a', value: ref({ key: 'a' }, { type: 'variable' }) }),
1124
+ decl({ name: 'x', value: ref({ key: 'x' }, { type: 'variable' }) })
1125
+ ])
1126
+ })
1127
+ ])
1128
+ });
1129
+
1130
+ const host = ruleset({
1131
+ selector: el('.call-lock-mixin'),
1132
+ rules: rules([
1133
+ call({
1134
+ name: ref({ key: '.lock-mixin' }, { type: 'mixin' }),
1135
+ args: list([num(1)])
1136
+ }),
1137
+ ruleset({
1138
+ selector: el('.call-inner-lock-mixin'),
1139
+ rules: rules([
1140
+ call({
1141
+ name: ref({ key: '.inner-locked-mixin' }, { type: 'mixin' })
1142
+ })
1143
+ ])
1144
+ })
1145
+ ])
1146
+ });
1147
+
1148
+ const localContext = new Context({
1149
+ leakyRules: true,
1150
+ collapseNesting: true
1151
+ });
1152
+ localContext.depth = 2;
1153
+ const root = rules([globalA, lockMixin, host]);
1154
+ localContext.root = root;
1155
+
1156
+ const evald = await root.eval(localContext);
1157
+ const previousFullRender = Node.prototype.fullRender;
1158
+ Node.prototype.fullRender = false;
1159
+ let css: string;
1160
+ try {
1161
+ css = evald.render(localContext);
1162
+ } finally {
1163
+ Node.prototype.fullRender = previousFullRender;
1164
+ }
1165
+
1166
+ expect(css).toBeString(`
1167
+ .call-lock-mixin .call-inner-lock-mixin {
1168
+ a: 1;
1169
+ x: 1;
1170
+ }
1171
+ `);
1172
+ });
1173
+
1174
+ it('still matches the emitted nested mixin guard ahead of same-named globals when args are explicit', async () => {
1175
+ const globalA = vardecl({ name: 'a', value: any('auto') });
1176
+
1177
+ const lockMixin = mixin({
1178
+ name: any('.lock-mixin'),
1179
+ params: list([
1180
+ vardecl({ name: 'a', value: nil() }, { paramVar: true })
1181
+ ]),
1182
+ rules: rules([
1183
+ mixin({
1184
+ name: any('.inner-locked-mixin'),
1185
+ params: list([
1186
+ vardecl({
1187
+ name: 'x',
1188
+ value: ref({ key: 'a' }, { type: 'variable' })
1189
+ }, { paramVar: true })
1190
+ ]),
1191
+ guard: condition([
1192
+ expr(ref({ key: 'a' }, { type: 'variable' })),
1193
+ '=',
1194
+ num(1)
1195
+ ]),
1196
+ rules: rules([
1197
+ decl({ name: 'a', value: ref({ key: 'a' }, { type: 'variable' }) }),
1198
+ decl({ name: 'x', value: ref({ key: 'x' }, { type: 'variable' }) })
1199
+ ])
1200
+ })
1201
+ ])
1202
+ });
1203
+
1204
+ const host = ruleset({
1205
+ selector: el('.call-lock-mixin'),
1206
+ rules: rules([
1207
+ call({
1208
+ name: ref({ key: '.lock-mixin' }, { type: 'mixin' }),
1209
+ args: list([num(1)])
1210
+ }),
1211
+ ruleset({
1212
+ selector: el('.call-inner-lock-mixin'),
1213
+ rules: rules([
1214
+ call({
1215
+ name: ref({ key: '.inner-locked-mixin' }, { type: 'mixin' }),
1216
+ args: list([num(1)])
1217
+ })
1218
+ ])
1219
+ })
1220
+ ])
1221
+ });
1222
+
1223
+ const localContext = new Context({
1224
+ leakyRules: true,
1225
+ collapseNesting: true
1226
+ });
1227
+ localContext.depth = 2;
1228
+ const root = rules([globalA, lockMixin, host]);
1229
+ localContext.root = root;
1230
+
1231
+ const evald = await root.eval(localContext);
1232
+ const previousFullRender = Node.prototype.fullRender;
1233
+ Node.prototype.fullRender = false;
1234
+ let css: string;
1235
+ try {
1236
+ css = evald.render(localContext);
1237
+ } finally {
1238
+ Node.prototype.fullRender = previousFullRender;
1239
+ }
1240
+
1241
+ expect(css).toBeString(`
1242
+ .call-lock-mixin .call-inner-lock-mixin {
1243
+ a: 1;
1244
+ x: 1;
1245
+ }
1246
+ `);
1247
+ });
1248
+
1249
+ it('should call a mixin with pattern matching by value', async () => {
1250
+ // Create mixins with pattern matching: .mixin(red) and .mixin(blue)
1251
+ const redMixin = mixin({
1252
+ name: any('.mixin'),
1253
+ params: list([
1254
+ any('red') // Pattern match - must be exactly 'red'
1255
+ ]),
1256
+ rules: rules([
1257
+ decl({ name: 'color', value: any('red') })
1258
+ ])
1259
+ });
1260
+
1261
+ const blueMixin = mixin({
1262
+ name: any('.mixin'),
1263
+ params: list([
1264
+ any('blue') // Pattern match - must be exactly 'blue'
1265
+ ]),
1266
+ rules: rules([
1267
+ decl({ name: 'color', value: any('blue') })
1268
+ ])
1269
+ });
1270
+
1271
+ // Create rulesets that call the mixin with different values
1272
+ const testRuleset1 = ruleset({
1273
+ selector: el('.test1'),
1274
+ rules: rules([
1275
+ call({
1276
+ name: ref({ key: '.mixin' }, { type: 'mixin' }),
1277
+ args: list([any('red')])
1278
+ })
1279
+ ])
1280
+ });
1281
+
1282
+ const testRuleset2 = ruleset({
1283
+ selector: el('.test2'),
1284
+ rules: rules([
1285
+ call({
1286
+ name: ref({ key: '.mixin' }, { type: 'mixin' }),
1287
+ args: list([any('blue')])
1288
+ })
1289
+ ])
1290
+ });
1291
+
1292
+ const root = rules([redMixin, blueMixin, testRuleset1, testRuleset2]);
1293
+ context.root = root;
1294
+
1295
+ const evald = await root.eval(context);
1296
+ const css = evald.render(context);
1297
+
1298
+ expect(css).toBeString(`
1299
+ .test1 {
1300
+ color: red;
1301
+ }
1302
+ .test2 {
1303
+ color: blue;
1304
+ }
1305
+ `);
1306
+ });
1307
+
1308
+ it('matches a sequence pattern parameter against a state-patched argument value', async () => {
1309
+ const buildRoot = () => {
1310
+ const mixinDef = mixin({
1311
+ name: any('.mixin'),
1312
+ params: list([
1313
+ seq([num(10), num(20)])
1314
+ ]),
1315
+ rules: rules([
1316
+ decl({ name: 'color', value: any('red') })
1317
+ ])
1318
+ });
1319
+
1320
+ const arg = seq([num(10), num(30)]);
1321
+ const testRuleset = ruleset({
1322
+ selector: el('.test'),
1323
+ rules: rules([
1324
+ call({
1325
+ name: ref({ key: '.mixin' }, { type: 'mixin' }),
1326
+ args: list([arg])
1327
+ })
1328
+ ])
1329
+ });
1330
+
1331
+ return {
1332
+ root: rules([mixinDef, testRuleset]),
1333
+ arg
1334
+ };
1335
+ };
1336
+
1337
+ const baseline = buildRoot();
1338
+ await expectRejects(baseline.root.eval(new Context({ leakyRules: true })), ReferenceError, /No matching mixins/);
1339
+ });
1340
+
1341
+ it('matches selector pattern params after preparing the selector operand before compare(context)', async () => {
1342
+ const buildRoot = (ctx: Context) => {
1343
+ const parent = ruleset({
1344
+ selector: el('.alpha'),
1345
+ rules: rules([])
1346
+ });
1347
+ parent.get('selector').keySetLibrary = ctx.selectorBits;
1348
+
1349
+ const patched = el('.beta');
1350
+ patched.keySetLibrary = ctx.selectorBits;
1351
+
1352
+ const find = sel([
1353
+ amp({ selectorContainer: parent as any }),
1354
+ co('>'),
1355
+ el('.tail')
1356
+ ]);
1357
+ find.keySetLibrary = ctx.selectorBits;
1358
+ for (const child of find.get('value') as any[]) {
1359
+ if ('keySetLibrary' in child) {
1360
+ child.keySetLibrary = ctx.selectorBits;
1361
+ }
1362
+ }
1363
+ const patternArg = sellist([find]);
1364
+ patternArg.keySetLibrary = ctx.selectorBits;
1365
+
1366
+ const target = sel([el('.beta'), co('>'), el('.tail')]);
1367
+ const otherBits = new Context().selectorBits;
1368
+ target.keySetLibrary = otherBits;
1369
+ for (const child of target.get('value') as any[]) {
1370
+ if ('keySetLibrary' in child) {
1371
+ child.keySetLibrary = otherBits;
1372
+ }
1373
+ }
1374
+
1375
+ const mixinDef = mixin({
1376
+ name: any('.mixin'),
1377
+ params: list([patternArg]),
1378
+ rules: rules([
1379
+ decl({ name: 'color', value: any('red') })
1380
+ ])
1381
+ });
1382
+
1383
+ const testRuleset = ruleset({
1384
+ selector: el('.test'),
1385
+ rules: rules([
1386
+ call({
1387
+ name: ref({ key: '.mixin' }, { type: 'mixin' }),
1388
+ args: list([target])
1389
+ })
1390
+ ])
1391
+ });
1392
+
1393
+ return {
1394
+ root: rules([mixinDef, testRuleset]),
1395
+ parent,
1396
+ patched,
1397
+ patternArg,
1398
+ target
1399
+ };
1400
+ };
1401
+ });
1402
+
1403
+ it('should call a mixin with rest parameters', async () => {
1404
+ // Create a mixin with a rest parameter: .my-mixin(@a, @rest...) { margin: @rest; }
1405
+ const mixinDef = mixin({
1406
+ name: any('.my-mixin'),
1407
+ params: list([
1408
+ any('a', { role: 'property' }),
1409
+ rest('rest') // Rest parameter collects remaining arguments
1410
+ ]),
1411
+ rules: rules([
1412
+ decl({ name: 'margin', value: ref({ key: 'rest' }, { type: 'variable' }) })
1413
+ ])
1414
+ });
1415
+
1416
+ // Create a ruleset that calls the mixin with multiple args: .test { .my-mixin(10px, 20px, 30px); }
1417
+ const testRuleset = ruleset({
1418
+ selector: el('.test'),
1419
+ rules: rules([
1420
+ call({
1421
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
1422
+ args: list([any('10px'), any('20px'), any('30px')])
1423
+ })
1424
+ ])
1425
+ });
1426
+
1427
+ const root = rules([mixinDef, testRuleset]);
1428
+ context.root = root;
1429
+
1430
+ const evald = await root.eval(context);
1431
+ const css = evald.render(context);
1432
+
1433
+ expect(css).toBeString(`
1434
+ .test {
1435
+ margin: 20px 30px;
1436
+ }
1437
+ `);
1438
+ });
1439
+
1440
+ it('should call a mixin with unnamed rest parameter (auto-generated name)', async () => {
1441
+ // Create a mixin with an unnamed rest parameter: .my-mixin(@a, ...) { margin: @rest; }
1442
+ // The name should be auto-generated as "rest"
1443
+ const mixinDef = mixin({
1444
+ name: any('.my-mixin'),
1445
+ params: list([
1446
+ any('a', { role: 'property' }),
1447
+ rest(undefined) // Unnamed rest parameter - should auto-generate "rest"
1448
+ ]),
1449
+ rules: rules([
1450
+ decl({ name: 'margin', value: ref({ key: 'rest' }, { type: 'variable' }) })
1451
+ ])
1452
+ });
1453
+
1454
+ // Create a ruleset that calls the mixin with multiple args: .test { .my-mixin(10px, 20px, 30px); }
1455
+ const testRuleset = ruleset({
1456
+ selector: el('.test'),
1457
+ rules: rules([
1458
+ call({
1459
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
1460
+ args: list([any('10px'), any('20px'), any('30px')])
1461
+ })
1462
+ ])
1463
+ });
1464
+
1465
+ const root = rules([mixinDef, testRuleset]);
1466
+ context.root = root;
1467
+
1468
+ const evald = await root.eval(context);
1469
+ const css = evald.render(context);
1470
+
1471
+ expect(css).toBeString(`
1472
+ .test {
1473
+ margin: 20px 30px;
1474
+ }
1475
+ `);
1476
+ });
1477
+
1478
+ it('should call a mixin with multiple nested compound selectors', async () => {
1479
+ // .do .re .mi .fa {
1480
+ // .sol .la {
1481
+ // .si {
1482
+ // color: cyan;
1483
+ // }
1484
+ // }
1485
+ // }
1486
+ // .mutli-selector-parents {
1487
+ // .do.re.mi.fa.sol.la.si();
1488
+ // }
1489
+ const node = rules([
1490
+ ruleset({
1491
+ selector: sel([el('.do'), co(' '), el('.re'), co(' '), el('.mi'), co(' '), el('.fa')]) as any,
1492
+ rules: rules([
1493
+ ruleset({
1494
+ selector: sel([el('.sol'), co(' '), el('.la')]) as any,
1495
+ rules: rules([
1496
+ ruleset({
1497
+ selector: sel([el('.si')]) as any,
1498
+ rules: rules([
1499
+ decl({ name: 'color', value: any('cyan') })
1500
+ ])
1501
+ })
1502
+ ])
1503
+ })
1504
+ ])
1505
+ }),
1506
+ ruleset({
1507
+ selector: el('.mutli-selector-parents'),
1508
+ rules: rules([
1509
+ call({ name: ref({ key: compound([el('.do'), el('.re'), el('.mi'), el('.fa'), el('.sol'), el('.la'), el('.si')]) }, { type: 'mixin-ruleset' }) })
1510
+ ])
1511
+ })
1512
+ ]);
1513
+ context.opts.collapseNesting = true;
1514
+ let evald = await node.eval(context);
1515
+ const css = evald.render(context);
1516
+ expect(css).toBeString(`
1517
+ .do .re .mi .fa .sol .la .si {
1518
+ color: cyan;
1519
+ }
1520
+ .mutli-selector-parents {
1521
+ color: cyan;
1522
+ }
1523
+ `);
1524
+ });
1525
+
1526
+ it('should call a mixin or ruleset with different nesting patterns', async () => {
1527
+ Node.prototype.fullRender = false;
1528
+ // #theme() {
1529
+ // .dark() {
1530
+ // .navbar() {
1531
+ // @color: cyan;
1532
+ // }
1533
+ // }
1534
+ // }
1535
+ // #theme.dark.navbar {
1536
+ // @color: blue;
1537
+ // }
1538
+ // .rule {
1539
+ // #theme.dark.navbar();
1540
+ // background-color: @color;
1541
+ // }
1542
+ const node = rules([
1543
+ mixin({
1544
+ name: any('#theme'),
1545
+ rules: rules([
1546
+ mixin({
1547
+ name: any('.dark'),
1548
+ rules: rules([
1549
+ mixin({
1550
+ name: any('.navbar'),
1551
+ rules: rules([
1552
+ vardecl({ name: 'color', value: any('cyan') })
1553
+ ])
1554
+ })
1555
+ ])
1556
+ })
1557
+ ])
1558
+ }),
1559
+ ruleset({
1560
+ selector: compound([el('#theme'), el('.dark'), el('.navbar')]),
1561
+ rules: rules([
1562
+ vardecl({ name: 'color', value: any('blue') })
1563
+ ])
1564
+ }),
1565
+ ruleset({
1566
+ selector: el('.rule'),
1567
+ rules: rules([
1568
+ call({ name: ref({ key: ['#theme', '.dark', '.navbar'] }, { type: 'mixin-ruleset' }) }),
1569
+ decl({ name: 'background-color', value: ref({ key: 'color' }, { type: 'variable' }) })
1570
+ ])
1571
+ })
1572
+ ]);
1573
+ let evald = await node.eval(context);
1574
+ const css = evald.render(context);
1575
+ expect(css).toBeString(`
1576
+ .rule {
1577
+ background-color: cyan;
1578
+ }
1579
+ `);
1580
+ });
1581
+
1582
+ it('should call a namespace mixin using ComplexSelector key (parser pattern)', async () => {
1583
+ // Mirrors the Less parser's AST for:
1584
+ // #theme {
1585
+ // > .mixin {
1586
+ // background-color: grey;
1587
+ // }
1588
+ // }
1589
+ // #container {
1590
+ // #theme > .mixin();
1591
+ // }
1592
+ const node = rules([
1593
+ ruleset({
1594
+ selector: el('#theme'),
1595
+ rules: rules([
1596
+ ruleset({
1597
+ selector: sel([co('>'), el('.mixin')]),
1598
+ rules: rules([
1599
+ decl({ name: 'background-color', value: any('grey') })
1600
+ ])
1601
+ })
1602
+ ])
1603
+ }),
1604
+ ruleset({
1605
+ selector: el('#container'),
1606
+ rules: rules([
1607
+ call({
1608
+ name: ref(
1609
+ { key: sel([el('#theme'), co('>'), el('.mixin')]) },
1610
+ { type: 'mixin-ruleset' }
1611
+ )
1612
+ })
1613
+ ])
1614
+ })
1615
+ ]);
1616
+ let evald = await node.eval(context);
1617
+ const css = evald.render(context);
1618
+ expect(css).toContain('background-color: grey');
1619
+ });
1620
+
1621
+ it('should call a namespace mixin using ComplexSelector key with collapseNesting', async () => {
1622
+ // Same as above but with collapseNesting: true (matching Less compiler behavior)
1623
+ const collapseContext = new Context({
1624
+ leakyRules: true,
1625
+ collapseNesting: true
1626
+ });
1627
+ const node = rules([
1628
+ ruleset({
1629
+ selector: el('#theme'),
1630
+ rules: rules([
1631
+ ruleset({
1632
+ selector: sel([co('>'), el('.mixin')]),
1633
+ rules: rules([
1634
+ decl({ name: 'background-color', value: any('grey') })
1635
+ ])
1636
+ })
1637
+ ])
1638
+ }),
1639
+ ruleset({
1640
+ selector: el('#container'),
1641
+ rules: rules([
1642
+ call({
1643
+ name: ref(
1644
+ { key: sel([el('#theme'), co('>'), el('.mixin')]) },
1645
+ { type: 'mixin-ruleset' }
1646
+ )
1647
+ })
1648
+ ])
1649
+ })
1650
+ ]);
1651
+ let evald = await node.eval(collapseContext);
1652
+ const css = evald.render(context);
1653
+ expect(css).toContain('background-color: grey');
1654
+ });
1655
+
1656
+ it('top-level ruleset-as-mixin output keeps nested descendants unbound from definition selector', async () => {
1657
+ const node = rules([
1658
+ ruleset({
1659
+ selector: el('.zz'),
1660
+ rules: rules([
1661
+ ruleset({
1662
+ selector: el('.y'),
1663
+ rules: rules([
1664
+ decl({ name: 'pulled-in', value: any('yes') })
1665
+ ])
1666
+ })
1667
+ ])
1668
+ }),
1669
+ call({
1670
+ name: ref({ key: '.zz' }, { type: 'mixin-ruleset' })
1671
+ })
1672
+ ]);
1673
+
1674
+ context.opts.collapseNesting = true;
1675
+
1676
+ const evald = await node.eval(context);
1677
+ const css = evald.render(context);
1678
+
1679
+ expect(css).toBeString(`
1680
+ .zz .y {
1681
+ pulled-in: yes;
1682
+ }
1683
+ .y {
1684
+ pulled-in: yes;
1685
+ }
1686
+ `);
1687
+ });
1688
+ });
1689
+
1690
+ describe('rest parameter matching and assignment', () => {
1691
+ it('should match a mixin with rest parameter and assign empty rest when no extra args provided', async () => {
1692
+ // Create a mixin with a rest parameter: .my-mixin(@a, @rest...) { padding: @rest; }
1693
+ const mixinDef = mixin({
1694
+ name: any('.my-mixin'),
1695
+ params: list([
1696
+ any('a', { role: 'property' }),
1697
+ rest('rest')
1698
+ ]),
1699
+ rules: rules([
1700
+ decl({ name: 'padding', value: ref({ key: 'rest' }, { type: 'variable' }) })
1701
+ ])
1702
+ });
1703
+
1704
+ // Create a ruleset that calls the mixin with only the required arg: .test { .my-mixin(10px); }
1705
+ const testRuleset = ruleset({
1706
+ selector: el('.test'),
1707
+ rules: rules([
1708
+ call({
1709
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
1710
+ args: list([any('10px')]) // Only one arg, rest should be empty
1711
+ })
1712
+ ])
1713
+ });
1714
+
1715
+ const root = rules([mixinDef, testRuleset]);
1716
+ context.root = root;
1717
+
1718
+ const evald = await root.eval(context);
1719
+ const css = evald.render(context);
1720
+
1721
+ expect(css).toBeString(`
1722
+ .test {
1723
+ padding: rest;
1724
+ }
1725
+ `);
1726
+ });
1727
+
1728
+ it('should match a mixin with rest parameter and assign single value to rest', async () => {
1729
+ // Create a mixin with a rest parameter: .my-mixin(@a, @rest...) { margin: @rest; }
1730
+ const mixinDef = mixin({
1731
+ name: any('.my-mixin'),
1732
+ params: list([
1733
+ any('a', { role: 'property' }),
1734
+ rest('rest')
1735
+ ]),
1736
+ rules: rules([
1737
+ decl({ name: 'margin', value: ref({ key: 'rest' }, { type: 'variable' }) })
1738
+ ])
1739
+ });
1740
+
1741
+ // Create a ruleset that calls the mixin with two args: .test { .my-mixin(10px, 20px); }
1742
+ const testRuleset = ruleset({
1743
+ selector: el('.test'),
1744
+ rules: rules([
1745
+ call({
1746
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
1747
+ args: list([any('10px'), any('20px')]) // Rest should contain 20px
1748
+ })
1749
+ ])
1750
+ });
1751
+
1752
+ const root = rules([mixinDef, testRuleset]);
1753
+ context.root = root;
1754
+
1755
+ const evald = await root.eval(context);
1756
+ const css = evald.render(context);
1757
+
1758
+ expect(css).toBeString(`
1759
+ .test {
1760
+ margin: 20px;
1761
+ }
1762
+ `);
1763
+ });
1764
+
1765
+ it('should match a mixin with rest parameter and assign multiple values to rest', async () => {
1766
+ // Create a mixin with a rest parameter: .my-mixin(@a, @rest...) { padding: @rest; }
1767
+ const mixinDef = mixin({
1768
+ name: any('.my-mixin'),
1769
+ params: list([
1770
+ any('a', { role: 'property' }),
1771
+ rest('rest')
1772
+ ]),
1773
+ rules: rules([
1774
+ decl({ name: 'padding', value: ref({ key: 'rest' }, { type: 'variable' }) })
1775
+ ])
1776
+ });
1777
+
1778
+ // Create a ruleset that calls the mixin with many args: .test { .my-mixin(10px, 20px, 30px, 40px); }
1779
+ const testRuleset = ruleset({
1780
+ selector: el('.test'),
1781
+ rules: rules([
1782
+ call({
1783
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
1784
+ args: list([any('10px'), any('20px'), any('30px'), any('40px')]) // Rest should contain 20px, 30px, 40px
1785
+ })
1786
+ ])
1787
+ });
1788
+
1789
+ const root = rules([mixinDef, testRuleset]);
1790
+ context.root = root;
1791
+
1792
+ const evald = await root.eval(context);
1793
+ const css = evald.render(context);
1794
+
1795
+ expect(css).toBeString(`
1796
+ .test {
1797
+ padding: 20px 30px 40px;
1798
+ }
1799
+ `);
1800
+ });
1801
+
1802
+ it('should match a mixin with rest parameter when multiple required params before rest', async () => {
1803
+ // Create a mixin with multiple params before rest: .my-mixin(@a, @b, @rest...) { margin: @rest; }
1804
+ const mixinDef = mixin({
1805
+ name: any('.my-mixin'),
1806
+ params: list([
1807
+ any('a', { role: 'property' }),
1808
+ any('b', { role: 'property' }),
1809
+ rest('rest')
1810
+ ]),
1811
+ rules: rules([
1812
+ decl({ name: 'margin', value: ref({ key: 'rest' }, { type: 'variable' }) })
1813
+ ])
1814
+ });
1815
+
1816
+ // Create a ruleset that calls the mixin: .test { .my-mixin(10px, 20px, 30px, 40px); }
1817
+ const testRuleset = ruleset({
1818
+ selector: el('.test'),
1819
+ rules: rules([
1820
+ call({
1821
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
1822
+ args: list([any('10px'), any('20px'), any('30px'), any('40px')]) // Rest should contain 30px, 40px
1823
+ })
1824
+ ])
1825
+ });
1826
+
1827
+ const root = rules([mixinDef, testRuleset]);
1828
+ context.root = root;
1829
+
1830
+ const evald = await root.eval(context);
1831
+ const css = evald.render(context);
1832
+
1833
+ expect(css).toBeString(`
1834
+ .test {
1835
+ margin: 30px 40px;
1836
+ }
1837
+ `);
1838
+ });
1839
+
1840
+ it('should use rest variable in multiple declarations within mixin', async () => {
1841
+ // Create a mixin that uses rest in multiple places: .my-mixin(@a, @rest...) { margin: @rest; padding: @rest; }
1842
+ const mixinDef = mixin({
1843
+ name: any('.my-mixin'),
1844
+ params: list([
1845
+ any('a', { role: 'property' }),
1846
+ rest('rest')
1847
+ ]),
1848
+ rules: rules([
1849
+ decl({ name: 'margin', value: ref({ key: 'rest' }, { type: 'variable' }) }),
1850
+ decl({ name: 'padding', value: ref({ key: 'rest' }, { type: 'variable' }) })
1851
+ ])
1852
+ });
1853
+
1854
+ // Create a ruleset that calls the mixin: .test { .my-mixin(10px, 20px, 30px); }
1855
+ const testRuleset = ruleset({
1856
+ selector: el('.test'),
1857
+ rules: rules([
1858
+ call({
1859
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
1860
+ args: list([any('10px'), any('20px'), any('30px')])
1861
+ })
1862
+ ])
1863
+ });
1864
+
1865
+ const root = rules([mixinDef, testRuleset]);
1866
+ context.root = root;
1867
+
1868
+ const evald = await root.eval(context);
1869
+ const css = evald.render(context);
1870
+
1871
+ expect(css).toBeString(`
1872
+ .test {
1873
+ margin: 20px 30px;
1874
+ padding: 20px 30px;
1875
+ }
1876
+ `);
1877
+ });
1878
+
1879
+ it('should match mixin with rest parameter over mixin without rest when both exist', async () => {
1880
+ // Create a mixin without rest: .my-mixin(@a, @b) { color: red; }
1881
+ const mixinWithoutRest = mixin({
1882
+ name: any('.my-mixin'),
1883
+ params: list([
1884
+ any('a', { role: 'property' }),
1885
+ any('b', { role: 'property' })
1886
+ ]),
1887
+ rules: rules([
1888
+ decl({ name: 'color', value: any('red') })
1889
+ ])
1890
+ });
1891
+
1892
+ // Create a mixin with rest: .my-mixin(@a, @rest...) { color: blue; }
1893
+ const mixinWithRest = mixin({
1894
+ name: any('.my-mixin'),
1895
+ params: list([
1896
+ any('a', { role: 'property' }),
1897
+ rest('rest')
1898
+ ]),
1899
+ rules: rules([
1900
+ decl({ name: 'color', value: any('blue') })
1901
+ ])
1902
+ });
1903
+
1904
+ // Create a ruleset that calls with exact 2 args: .test1 { .my-mixin(10px, 20px); }
1905
+ const testRuleset1 = ruleset({
1906
+ selector: el('.test1'),
1907
+ rules: rules([
1908
+ call({
1909
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
1910
+ args: list([any('10px'), any('20px')]) // Matches mixinWithoutRest exactly
1911
+ })
1912
+ ])
1913
+ });
1914
+
1915
+ // Create a ruleset that calls with 3 args: .test2 { .my-mixin(10px, 20px, 30px); }
1916
+ const testRuleset2 = ruleset({
1917
+ selector: el('.test2'),
1918
+ rules: rules([
1919
+ call({
1920
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
1921
+ args: list([any('10px'), any('20px'), any('30px')]) // Should match mixinWithRest
1922
+ })
1923
+ ])
1924
+ });
1925
+
1926
+ const root = rules([mixinWithoutRest, mixinWithRest, testRuleset1, testRuleset2]);
1927
+ context.root = root;
1928
+
1929
+ const evald = await root.eval(context);
1930
+ const css = evald.render(context);
1931
+
1932
+ expect(css).toBeString(`
1933
+ .test1 {
1934
+ color: red;
1935
+ color: blue;
1936
+ }
1937
+ .test2 {
1938
+ color: blue;
1939
+ }
1940
+ `);
1941
+ });
1942
+ });
1943
+
1944
+ describe('arity failures', () => {
1945
+ it('should fail when calling a mixin with too few arguments (missing required parameter)', async () => {
1946
+ // Create a mixin with a required parameter: .my-mixin(@color) { color: @color; }
1947
+ const mixinDef = mixin({
1948
+ name: any('.my-mixin'),
1949
+ params: list([
1950
+ any('color', { role: 'property' }) // Required parameter without default
1951
+ ]),
1952
+ rules: rules([
1953
+ decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) })
1954
+ ])
1955
+ });
1956
+
1957
+ // Create a ruleset that calls the mixin without args: .test { .my-mixin(); }
1958
+ const testRuleset = ruleset({
1959
+ selector: el('.test'),
1960
+ rules: rules([
1961
+ call({ name: ref({ key: '.my-mixin' }, { type: 'mixin' }) })
1962
+ ])
1963
+ });
1964
+
1965
+ const root = rules([mixinDef, testRuleset]);
1966
+ context.root = root;
1967
+
1968
+ await expectRejects(root.eval(context), ReferenceError, /No matching mixins/);
1969
+ });
1970
+
1971
+ it('should fail when calling a mixin with too few arguments (multiple required parameters)', async () => {
1972
+ // Create a mixin with multiple required parameters: .my-mixin(@color, @size) { color: @color; font-size: @size; }
1973
+ const mixinDef = mixin({
1974
+ name: any('.my-mixin'),
1975
+ params: list([
1976
+ any('color', { role: 'property' }),
1977
+ any('size', { role: 'property' })
1978
+ ]),
1979
+ rules: rules([
1980
+ decl({ name: 'color', value: ref({ key: 'color' }, { type: 'variable' }) }),
1981
+ decl({ name: 'font-size', value: ref({ key: 'size' }, { type: 'variable' }) })
1982
+ ])
1983
+ });
1984
+
1985
+ // Create a ruleset that calls the mixin with only one arg: .test { .my-mixin(red); }
1986
+ const testRuleset = ruleset({
1987
+ selector: el('.test'),
1988
+ rules: rules([
1989
+ call({
1990
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
1991
+ args: list([any('red')]) // Only one argument, but two are required
1992
+ })
1993
+ ])
1994
+ });
1995
+
1996
+ const root = rules([mixinDef, testRuleset]);
1997
+ context.root = root;
1998
+
1999
+ await expectRejects(root.eval(context), ReferenceError, /No matching mixins/);
2000
+ });
2001
+
2002
+ it('should fail when calling a mixin with no parameters but providing arguments', async () => {
2003
+ // Create a mixin with no parameters: .my-mixin() { color: red; }
2004
+ const mixinDef = mixin({
2005
+ name: any('.my-mixin'),
2006
+ rules: rules([
2007
+ decl({ name: 'color', value: any('red') })
2008
+ ])
2009
+ });
2010
+
2011
+ // Create a ruleset that calls the mixin with args: .test { .my-mixin(blue); }
2012
+ const testRuleset = ruleset({
2013
+ selector: el('.test'),
2014
+ rules: rules([
2015
+ call({
2016
+ name: ref({ key: '.my-mixin' }, { type: 'mixin' }),
2017
+ args: list([any('blue')]) // One argument, but mixin has no parameters
2018
+ })
2019
+ ])
2020
+ });
2021
+
2022
+ const root = rules([mixinDef, testRuleset]);
2023
+ context.root = root;
2024
+
2025
+ await expectRejects(root.eval(context), ReferenceError, /No matching mixins/);
2026
+ });
2027
+
2028
+ });
2029
+
2030
+ describe('serialization', () => {
2031
+ it('should serialize a mixin', () => {
2032
+ const rule = mixin({
2033
+ name: any('myMixin'),
2034
+ rules: rules([
2035
+ decl({ name: 'color', value: any('black') }),
2036
+ decl({ name: 'background-color', value: any('white') })
2037
+ ])
2038
+ });
2039
+ expect(`${rule}`).toBeString(`
2040
+ myMixin() {
2041
+ color: black;
2042
+ background-color: white;
2043
+ }
2044
+ `);
2045
+ });
2046
+
2047
+ it('should serialize a mixin with args', () => {
2048
+ const rule = mixin({
2049
+ name: any('my-mixin'),
2050
+ params: list([
2051
+ vardecl({ name: 'a', value: any('black') }, { paramVar: true }),
2052
+ vardecl({ name: 'b', value: any('white') }, { paramVar: true })
2053
+ ], { sep: ';' }),
2054
+ rules: rules([
2055
+ decl({ name: 'color', value: any('black') }),
2056
+ decl({ name: 'background-color', value: any('white') })
2057
+ ])
2058
+ });
2059
+ expect(`${rule}`).toBeString(`
2060
+ my-mixin($a: black; $b: white) {
2061
+ color: black;
2062
+ background-color: white;
2063
+ }
2064
+ `);
2065
+ });
2066
+
2067
+ it('should serialize a guard', () => {
2068
+ const rule = mixin({
2069
+ name: any('my-mixin'),
2070
+ params: list([
2071
+ vardecl({ name: 'a', value: any('black') }, { paramVar: true }),
2072
+ vardecl({ name: 'b', value: any('white') }, { paramVar: true })
2073
+ ], { sep: ';' }),
2074
+ guard: condition([expr(ref({ key: 'a' })), '=', expr(ref({ key: 'b' }))]),
2075
+ rules: rules([
2076
+ decl({ name: 'color', value: any('black') }),
2077
+ decl({ name: 'background-color', value: any('white') })
2078
+ ])
2079
+ });
2080
+ expect(`${rule}`).toBeString(`
2081
+ my-mixin($a: black; $b: white) when ($($a) = $($b)) {
2082
+ color: black;
2083
+ background-color: white;
2084
+ }
2085
+ `);
2086
+ });
2087
+ });
2088
+
2089
+ describe('parent selector composition in mixin calls', () => {
2090
+ it('should resolve > li to caller context, not mixin ancestor chain, inside @media', async () => {
2091
+ /**
2092
+ * Less input:
2093
+ * .nav-justified {
2094
+ * @media (min-width: 480px) {
2095
+ * > li { display: table-cell; }
2096
+ * }
2097
+ * }
2098
+ * .menu {
2099
+ * @media (min-width: 768px) {
2100
+ * .nav-justified();
2101
+ * }
2102
+ * }
2103
+ *
2104
+ * Expected CSS (collapsed):
2105
+ * @media (min-width: 480px) { .nav-justified > li { ... } }
2106
+ * @media (min-width: 768px) { @media (min-width: 480px) { .menu > li { ... } } }
2107
+ *
2108
+ * Bug: produces `.menu .nav-justified > li` — the mixin body's
2109
+ * `> li` ruleset still walks up the parent chain to `.nav-justified`
2110
+ * instead of using the caller's captured frames.
2111
+ */
2112
+ const navJustified = ruleset({
2113
+ selector: el('.nav-justified'),
2114
+ rules: rules([
2115
+ atrule({
2116
+ name: any('@media'),
2117
+ prelude: any('(min-width: 480px)'),
2118
+ rules: rules([
2119
+ ruleset({
2120
+ selector: sel([co('>'), el('li')]),
2121
+ rules: rules([
2122
+ decl({ name: 'display', value: any('table-cell') })
2123
+ ])
2124
+ })
2125
+ ])
2126
+ })
2127
+ ])
2128
+ });
2129
+
2130
+ const menu = ruleset({
2131
+ selector: el('.menu'),
2132
+ rules: rules([
2133
+ atrule({
2134
+ name: any('@media'),
2135
+ prelude: any('(min-width: 768px)'),
2136
+ rules: rules([
2137
+ call({ name: ref({ key: '.nav-justified' }, { type: 'mixin-ruleset' }) })
2138
+ ])
2139
+ })
2140
+ ])
2141
+ });
2142
+
2143
+ const root = rules([navJustified, menu]);
2144
+ context.root = root;
2145
+
2146
+ const evald = await root.eval(context);
2147
+ const css = evald.toString({ collapseNesting: true, context });
2148
+
2149
+ expect(css).toContain('.menu > li');
2150
+ expect(css).not.toContain('.menu .nav-justified');
2151
+ });
2152
+ });
2153
+
2154
+ // it('should serialize to a module', () => {
2155
+ // let rule = mixin({
2156
+ // name: ident('myMixin'),
2157
+ // value: ruleset([
2158
+ // decl({ name: 'color', value: any('black') }),
2159
+ // decl({ name: 'background-color', value: any('white') })
2160
+ // ])
2161
+ // })
2162
+ // rule.toModule(context, out)
2163
+ // expect(out.toString()).toBe(
2164
+ // 'let myMixin = function() { return $J.ruleset(\n (() => {\n const $OUT = []\n $OUT.push($J.decl({\n name: $J.any("color"),\n value: $J.any("black")\n }))\n $OUT.push($J.decl({\n name: $J.any("background-color"),\n value: $J.any("white")\n }))\n return $OUT\n })()\n)}'
2165
+ // )
2166
+ // expect(rule.value.obj()).toEqual({
2167
+ // color: 'black',
2168
+ // 'background-color': 'white'
2169
+ // })
2170
+ // })
2171
+ });