@jesscss/core 2.0.0-alpha.4 → 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 -94
  258. package/lib/tree/ampersand.d.ts.map +0 -1
  259. package/lib/tree/ampersand.js +0 -269
  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,2896 @@
1
+ import {
2
+ Node,
3
+ defineType,
4
+ type NodeOptions,
5
+ type LocationInfo, type OptionalLocation,
6
+ type RenderKey,
7
+ CANONICAL,
8
+ EVAL,
9
+ type TreeContext,
10
+ F_STATIC,
11
+ F_VISIBLE,
12
+ isVisibleInContext
13
+ } from './node.js';
14
+ import { Context } from '../context.js';
15
+ import { isNode } from './util/is-node.js';
16
+ import { N } from './node-type.js';
17
+ import { type Ruleset } from './ruleset.js';
18
+ import { type Mixin } from './mixin.js';
19
+ import type { Selector } from './selector.js';
20
+ import { spaced, Sequence } from './sequence.js';
21
+ import { type PrintOptions, getPrintOptions } from './util/print.js';
22
+
23
+ import { atIndex } from './util/collections.js';
24
+ import * as Registries from './util/registry-utils.js';
25
+ import { processExtends } from './util/extend-roots.js';
26
+ import { type MaybePromise, pipe, isThenable, serialForEach } from '@jesscss/awaitable-pipe';
27
+ import { Nil } from './nil.js';
28
+ import { VarDeclaration } from './declaration-var.js';
29
+ import type { Declaration } from './declaration.js';
30
+ import { Any } from './any.js';
31
+ import { List } from './list.js';
32
+ import { indent, normalizeIndent } from './util/serialize-helper.js';
33
+ import { addEdge, addEdgeAt, addParentEdge, getEdgeAt } from './util/cursor.js';
34
+ import { getCurrentParentNode } from './util/selector-utils.js';
35
+ import {
36
+ getChildren,
37
+ getParent,
38
+ getSourceParent,
39
+ setChildren,
40
+ setChildAt,
41
+ getIndex,
42
+ setIndex,
43
+ setParent,
44
+ isPreEvaluated,
45
+ isEvaluated
46
+ } from './util/field-helpers.js';
47
+ import {
48
+ dispatchMixinEvalCandidates,
49
+ evaluateCandidateOutput,
50
+ evaluateMixinArgs,
51
+ filterAndSortMixinEvalCandidates,
52
+ finalizeMixinInvocationReturn,
53
+ getCandidateParent,
54
+ matchMixinCandidates,
55
+ type EvaluateCandidateOutputOptions
56
+ } from './util/mixin-instance-primitives.js';
57
+ import type { Func } from './function.js';
58
+ import type { Call } from './call.js';
59
+ const { isArray } = Array;
60
+
61
+ export const enum Priority {
62
+ None = 0,
63
+ Low = 1,
64
+ Medium = 2,
65
+ High = 3,
66
+ Highest = 4
67
+ }
68
+ export type RulesVisibility = 'public' | 'optional' | 'private';
69
+ export type FlatRulePosition = {
70
+ renderKey?: RenderKey;
71
+ };
72
+
73
+ export type RulesOptions = {
74
+ /**
75
+ * - public = all members are considered in lookup algorithms
76
+ * - optional = members are only considered if not found in the lookup tree
77
+ * - private = can't be looked up
78
+ * - local = only visible in the current scope
79
+ *
80
+ * Different types may have different defaults
81
+ *
82
+ * For Less:
83
+ * - When mixins are parsed, their rules body is set to:
84
+ * visibility: {
85
+ * Ruleset: 'public',
86
+ * Declaration: 'public',
87
+ * VarDeclaration: 'optional',
88
+ * Mixin: 'public'
89
+ * }
90
+ * - When detached rulesets are parsed, their rules body is set to:
91
+ * visibility: {
92
+ * Ruleset: 'public',
93
+ * Declaration: 'public',
94
+ * VarDeclaration: 'private', <-- the one notable difference
95
+ * Mixin: 'public'
96
+ * }
97
+ * @note - The reason Less has "optionality" is likely because it tries
98
+ * to eagerly resolve variables, so even though its in a
99
+ * child scope, it will still be considered if nothing else in the
100
+ * scope is found. I'm guessing this is because "overwriting" a local
101
+ * variable from something like a mixin call would be counter-intuitive,
102
+ * but at the same time, I guess Alexis thought that eagerly resolving
103
+ * the variable might be useful.
104
+ *
105
+ * Note that right now, only Declarations being set to "optional"
106
+ * are supported. Everything else must be public or private.
107
+ *
108
+ * For Imports, the rules body is set to:
109
+ * visibility: {
110
+ * Ruleset: 'public',
111
+ * Declaration: 'public',
112
+ * VarDeclaration: 'public',
113
+ * Mixin: 'public'
114
+ * }
115
+ */
116
+ rulesVisibility?: Record<string, RulesVisibility>;
117
+ /**
118
+ * If true, this Rules node is output from a mixin call.
119
+ * References with a target (e.g., #ns[@foo]) have public access to all nodes in these Rules.
120
+ * References without a target (e.g., @foo) cannot access these Rules.
121
+ */
122
+ isMixinOutput?: boolean;
123
+ readonly?: boolean;
124
+ /**
125
+ * all imports other than classic `@import` set returned rules to local.
126
+ * The reason is that variables are not transitive, and you need to re-use
127
+ * modules to get the same variables.
128
+ */
129
+ local?: boolean;
130
+ /**
131
+ * Sass `@forward` semantics: this Rules node exists as an export surface for downstream
132
+ * consumers, but should not be visible to lookups within the current stylesheet scope.
133
+ */
134
+ forward?: boolean;
135
+ /** Render gating marker for referenced imports/usages (serializer-time only). */
136
+ referenceMode?: boolean;
137
+ /** Explicit reference imports may render extended descendants; deduped imports may not. */
138
+ referenceRenderOnExtend?: boolean;
139
+ };
140
+
141
+ export interface Rules extends Node<Node[], RulesOptions & NodeOptions> {
142
+ readonly value: readonly Node[];
143
+ get options(): RulesOptions & NodeOptions & {
144
+ rulesVisibility: Record<string, RulesVisibility>;
145
+ };
146
+ set options(options: RulesOptions & NodeOptions & {
147
+ rulesVisibility: Record<string, RulesVisibility>;
148
+ });
149
+ eval(context: Context): MaybePromise<this>;
150
+ }
151
+
152
+ export type InvocationBindingFactory = (rules: Rules, context?: Context) => VarDeclaration;
153
+
154
+ export type InvocationBinding = {
155
+ template?: VarDeclaration;
156
+ factory?: InvocationBindingFactory;
157
+ declaration?: VarDeclaration;
158
+ };
159
+ /**
160
+ * The class representing a "declaration list".
161
+ * CSS calls it this even though CSS Nesting
162
+ * adds a bunch more things that aren't declarations.
163
+ *
164
+ * Used by Ruleset and Mixin. Additionally, imports / use statements
165
+ * return rules.
166
+ *
167
+ * @example
168
+ * [
169
+ * (Declaration color: black;)
170
+ * (Declaration background-color: white;)
171
+ * ]
172
+ */
173
+ export interface Rules {
174
+ type: 'Rules' | 'RawRules' | 'Collection';
175
+ shortType: 'rules' | 'rules-raw' | 'coll';
176
+ renderKey: RenderKey;
177
+ }
178
+ export class Rules extends Node<Node[], RulesOptions & NodeOptions> {
179
+ static override childKeys = ['value'] as const;
180
+
181
+ readonly value!: readonly Node[];
182
+ declare renderKey: RenderKey;
183
+ private _wrapperRegistrySeeded = false;
184
+ private _wrapperRegistrySeeding = false;
185
+ private _invocationBindings?: Map<string, InvocationBinding>;
186
+
187
+ functionRegistry: Registries.FunctionRegistry | undefined;
188
+
189
+ private _withOwnRenderKey<T>(
190
+ context: Context | undefined,
191
+ fn: () => T
192
+ ): T {
193
+ if (!context) {
194
+ return fn();
195
+ }
196
+ const targetRenderKey = this.renderKey === CANONICAL
197
+ ? (context.renderKey ?? this.renderKey)
198
+ : this.renderKey;
199
+ const needsRenderKey = context.renderKey !== targetRenderKey;
200
+ const needsRulesContext = context.rulesContext !== this;
201
+ if (!needsRenderKey && !needsRulesContext) {
202
+ return fn();
203
+ }
204
+
205
+ const previousRenderKey = context.renderKey;
206
+ const previousRulesContext = context.rulesContext;
207
+ if (needsRenderKey) {
208
+ context.renderKey = targetRenderKey;
209
+ }
210
+ if (needsRulesContext) {
211
+ context.rulesContext = this;
212
+ }
213
+ try {
214
+ return fn();
215
+ } finally {
216
+ if (needsRulesContext) {
217
+ context.rulesContext = previousRulesContext;
218
+ }
219
+ if (needsRenderKey) {
220
+ context.renderKey = previousRenderKey;
221
+ }
222
+ }
223
+ }
224
+
225
+ private _cloneOptionsForContext(_context?: Context): (RulesOptions & NodeOptions) | undefined {
226
+ const options = this.options
227
+ ?? (this as any)._meta?.options as (RulesOptions & NodeOptions) | undefined;
228
+ if (!options) {
229
+ return undefined;
230
+ }
231
+ return {
232
+ ...options,
233
+ rulesVisibility: options.rulesVisibility
234
+ ? { ...options.rulesVisibility }
235
+ : options.rulesVisibility
236
+ };
237
+ }
238
+
239
+ private _cloneInvocationBindings(): Map<string, InvocationBinding> | undefined {
240
+ if (!this._invocationBindings || this._invocationBindings.size === 0) {
241
+ return undefined;
242
+ }
243
+ const next = new Map<string, InvocationBinding>();
244
+ for (const [key, binding] of this._invocationBindings) {
245
+ next.set(key, {
246
+ template: binding.declaration ?? binding.template,
247
+ factory: binding.factory
248
+ });
249
+ }
250
+ return next;
251
+ }
252
+
253
+ /**
254
+ * Rules are often cloned during `preEval()` when a session is active.
255
+ * If callers register functions/mixins/declarations on the parsed tree
256
+ * before evaluation (e.g. via visitors), those registries must survive cloning so
257
+ * lookups during evaluation work as expected.
258
+ */
259
+ override clone(deep?: boolean, cloneFn?: (n: Node) => Node, ctx?: Context): this {
260
+ const options = this._cloneOptionsForContext(ctx);
261
+ const location = Array.isArray(this.location) && this.location.length === 6
262
+ ? this.location as LocationInfo
263
+ : undefined;
264
+ const detachedRenderKey = (
265
+ !deep
266
+ && this.renderKey === CANONICAL
267
+ && ctx
268
+ ? ctx.nextRenderKey()
269
+ : this.renderKey
270
+ );
271
+ const newRules = deep
272
+ ? super.clone(deep, cloneFn, ctx)
273
+ : (() => {
274
+ const wrapper = new (this.constructor as typeof Rules)(
275
+ [],
276
+ options ? { ...options } : undefined,
277
+ location,
278
+ this.treeContext
279
+ ) as this;
280
+ wrapper._setValueArray(this.value as Node[]);
281
+ wrapper.inherit(this);
282
+ wrapper.renderKey = detachedRenderKey;
283
+ if (wrapper.renderKey !== CANONICAL) {
284
+ wrapper._connectSharedChildren(wrapper.renderKey);
285
+ }
286
+ return wrapper;
287
+ })();
288
+
289
+ if (deep && options) {
290
+ newRules.options = options as typeof newRules.options;
291
+ }
292
+
293
+ if (!deep) {
294
+ newRules.inherit(this);
295
+ }
296
+
297
+ const invocationBindings = this._cloneInvocationBindings();
298
+ if (invocationBindings) {
299
+ newRules._invocationBindings = invocationBindings;
300
+ }
301
+
302
+ if (ctx) {
303
+ const parent = getCurrentParentNode(this, ctx);
304
+ if (parent) {
305
+ if (newRules.renderKey !== CANONICAL) {
306
+ setParent(newRules, parent, { ...ctx, renderKey: newRules.renderKey });
307
+ } else {
308
+ setParent(newRules, parent, ctx);
309
+ }
310
+ }
311
+ }
312
+
313
+ // Only preserve *function* registry across clones.
314
+ // This supports Less plugin compat, where plugins can inject functions into the registry
315
+ // without creating AST nodes that would be re-registered on clone.
316
+ //
317
+ // Do NOT reuse declaration/mixin/ruleset registries across clones; those should always
318
+ // be rebuilt from AST nodes via lazy indexing.
319
+ if (this.functionRegistry) {
320
+ newRules.functionRegistry = this.functionRegistry.cloneForRules(newRules);
321
+ }
322
+
323
+ return newRules;
324
+ }
325
+
326
+ /**
327
+ * Detached ruleset calls unlock shared top-level children into the active
328
+ * lookup scope, but must not canonically reparent those children.
329
+ *
330
+ * Keep this seam local to Rules so the detached-ruleset path does not need
331
+ * to rely on raw clone(false) semantics.
332
+ */
333
+ cloneDetachedUnlockWrapper(ctx: Context): this {
334
+ const wrapper = this.createShallowBodyWrapper(ctx) as this;
335
+ return wrapper;
336
+ }
337
+
338
+ /**
339
+ * Scope-isolation callers need copied Rules options/visibility while keeping
340
+ * shared top-level children canonically parented. Active lookups should still
341
+ * resolve through the wrapper during the current session.
342
+ */
343
+ cloneVisibilityIsolationWrapper(ctx: Context): this {
344
+ const wrapper = this.createShallowBodyWrapper(ctx) as this;
345
+ return wrapper;
346
+ }
347
+
348
+ /**
349
+ * Lazily create registries for types as needed.
350
+ */
351
+ private static _registryKey(type: string): 'rulesetRegistry' | 'mixinRegistry' | 'declarationRegistry' | 'functionRegistry' {
352
+ return `${type}Registry` as any;
353
+ }
354
+
355
+ private static _registryClass(type: string) {
356
+ return Registries[`${type.charAt(0).toUpperCase()}${type.slice(1)}Registry` as 'RulesetRegistry' | 'MixinRegistry' | 'DeclarationRegistry' | 'FunctionRegistry'];
357
+ }
358
+
359
+ private _ensureDirectRegistry(
360
+ type: 'ruleset' | 'declaration' | 'mixin' | 'function',
361
+ context?: Context
362
+ ) {
363
+ const key = Rules._registryKey(type);
364
+ let registry = (this as any)[key];
365
+ if (!registry) {
366
+ registry = new (Rules._registryClass(type))(this, context);
367
+ (this as any)[key] = registry;
368
+ } else if (context && !(registry as any).context) {
369
+ (registry as any).context = context;
370
+ }
371
+ return registry;
372
+ }
373
+
374
+ private _isWrapperRegistryOwner(): boolean {
375
+ return this.renderKey !== CANONICAL;
376
+ }
377
+
378
+ private _connectSharedChildren(renderKey: RenderKey): void {
379
+ const seen = new Set<Node>();
380
+ const connectDescendants = (parent: Node, child: Node): void => {
381
+ addParentEdge(child, renderKey, parent);
382
+ if (seen.has(child)) {
383
+ return;
384
+ }
385
+ seen.add(child);
386
+ const childKeys = (child.constructor as typeof Node).childKeys;
387
+ if (!childKeys) {
388
+ return;
389
+ }
390
+ for (const key of childKeys) {
391
+ const value = (child as unknown as Record<string, unknown>)[key];
392
+ if (Array.isArray(value)) {
393
+ for (const item of value) {
394
+ if (item instanceof Node) {
395
+ connectDescendants(child, item);
396
+ }
397
+ }
398
+ continue;
399
+ }
400
+ if (value instanceof Node) {
401
+ connectDescendants(child, value);
402
+ }
403
+ }
404
+ };
405
+
406
+ for (const child of this.value) {
407
+ if (child instanceof Node) {
408
+ connectDescendants(this, child);
409
+ }
410
+ }
411
+ }
412
+
413
+ private _ensureWrapperRegistrySeeded(context?: Context): void {
414
+ if (!this._isWrapperRegistryOwner() || this._wrapperRegistrySeeded || this._wrapperRegistrySeeding) {
415
+ return;
416
+ }
417
+
418
+ this._wrapperRegistrySeeding = true;
419
+ try {
420
+ (this as any).rulesetRegistry = undefined;
421
+ (this as any).mixinRegistry = undefined;
422
+ (this as any).declarationRegistry = undefined;
423
+ this._rulesSet = [];
424
+
425
+ for (const child of this.getRegistryChildren(context)) {
426
+ this.registerNode(child, undefined, context);
427
+ }
428
+
429
+ this._wrapperRegistrySeeded = true;
430
+ } finally {
431
+ this._wrapperRegistrySeeding = false;
432
+ }
433
+ }
434
+
435
+ /**
436
+ * Register a child node into the appropriate registry.
437
+ * Creates the registry lazily on first registration.
438
+ * Wrapper/derived Rules own their registries directly.
439
+ * Canonical Rules still fall back to direct per-node registries until the
440
+ * remaining lookup paths are fully converged on render-path ownership.
441
+ */
442
+ register(
443
+ type: 'ruleset' | 'declaration' | 'mixin' | 'function',
444
+ node: Node,
445
+ context?: Context
446
+ ) {
447
+ return this._withOwnRenderKey(context, () => {
448
+ this._ensureWrapperRegistrySeeded(context);
449
+ const registry = this._ensureDirectRegistry(type, context);
450
+ return registry.add(node);
451
+ });
452
+ }
453
+
454
+ /**
455
+ * Get a registry for lookups. Read-only — returns undefined if no
456
+ * registry was ever created (meaning nothing was registered).
457
+ */
458
+ getRegistry(type: 'ruleset', context?: Context): Registries.RulesetRegistry;
459
+ getRegistry(type: 'declaration', context?: Context): Registries.DeclarationRegistry;
460
+ getRegistry(type: 'mixin', context?: Context): Registries.MixinRegistry;
461
+ getRegistry(type: 'function', context?: Context): Registries.FunctionRegistry;
462
+ getRegistry(type: 'ruleset' | 'declaration' | 'mixin' | 'function', context?: Context): Registries.RulesetRegistry | Registries.DeclarationRegistry | Registries.MixinRegistry | Registries.FunctionRegistry;
463
+ getRegistry(type: 'ruleset' | 'declaration' | 'mixin' | 'function', context?: Context) {
464
+ return this._withOwnRenderKey(context, () => {
465
+ this._ensureWrapperRegistrySeeded(context);
466
+ return this._ensureDirectRegistry(type, context);
467
+ });
468
+ }
469
+
470
+ getRegistryParent(context?: Context): Rules | undefined {
471
+ return this._withOwnRenderKey(context, () => {
472
+ let parent = getCurrentParentNode(this, context);
473
+ while (parent && parent.type !== 'Rules') {
474
+ parent = getCurrentParentNode(parent, context);
475
+ }
476
+ if (parent) {
477
+ return parent as Rules | undefined;
478
+ }
479
+ let sourceParent = getSourceParent(this, context);
480
+ while (sourceParent && sourceParent.type !== 'Rules') {
481
+ sourceParent = getCurrentParentNode(sourceParent, context);
482
+ }
483
+ return sourceParent as Rules | undefined;
484
+ });
485
+ }
486
+
487
+ /**
488
+ * This wrapper is used so we don't prematurely create a registry
489
+ * just to search it.
490
+ */
491
+ find(type: 'ruleset', keys: string | string[] | Set<string>, filterType?: string, options?: Registries.FindOptions): ReturnType<Registries.RulesetRegistry['find']> | undefined;
492
+ find(type: 'declaration', keys: string, filterType?: string, options?: Registries.DeclarationFindOptions): ReturnType<Registries.DeclarationRegistry['find']> | undefined;
493
+ find(type: 'mixin', keys: string | string[], filterType?: string, options?: Registries.FindOptions): ReturnType<Registries.MixinRegistry['find']> | undefined;
494
+ find(type: 'function', keys: string, filterType?: string, options?: Registries.FindOptions): ReturnType<Registries.FunctionRegistry['find']> | undefined;
495
+ find(type: 'ruleset' | 'declaration' | 'mixin' | 'function', key: string, filterType: string, options?: Registries.FindOptions): ReturnType<Registries.RulesetRegistry['find']> | ReturnType<Registries.DeclarationRegistry['find']> | ReturnType<Registries.MixinRegistry['find']> | ReturnType<Registries.FunctionRegistry['find']> | undefined;
496
+ find(
497
+ type: 'ruleset' | 'declaration' | 'mixin' | 'function',
498
+ keys: string | string[] | Set<string>,
499
+ filterType?: string,
500
+ options: Registries.FindOptions = {}
501
+ ): ReturnType<Registries.RulesetRegistry['find']> | ReturnType<Registries.DeclarationRegistry['find']> | ReturnType<Registries.MixinRegistry['find']> | ReturnType<Registries.FunctionRegistry['find']> | undefined {
502
+ return this._withOwnRenderKey(options.context, () => {
503
+ const registry = this.getRegistry(type, options.context);
504
+ return (registry.find as Function)(keys, filterType, options);
505
+ });
506
+ }
507
+
508
+ findStatePatchedFunction(
509
+ name: string,
510
+ options: Registries.FindOptions = {}
511
+ ): ReturnType<Registries.FunctionRegistry['find']> | undefined {
512
+ const { filter, context, searchParents = true } = options;
513
+ return this._withOwnRenderKey(context, () => {
514
+ let rules: Rules | undefined = this;
515
+ let findRoot = false;
516
+
517
+ while (rules) {
518
+ for (const child of rules.getRegistryChildren(context)) {
519
+ if (!isNode(child, N.Func)) {
520
+ continue;
521
+ }
522
+ if (filter && !filter(child)) {
523
+ continue;
524
+ }
525
+ if ((child as Func).getNameKey(context) === name) {
526
+ return child as Func;
527
+ }
528
+ }
529
+
530
+ if (!searchParents) {
531
+ break;
532
+ }
533
+
534
+ do {
535
+ rules = rules?.getRegistryParent(context);
536
+ if (findRoot && rules?.type === 'Rules' && rules.getRegistryParent(context) === undefined) {
537
+ break;
538
+ }
539
+ if (rules && rules.sourceNode?.type === 'StyleImport' && rules.sourceNode.options.type !== 'import') {
540
+ findRoot = true;
541
+ }
542
+ } while (!findRoot && rules && rules.type !== 'Rules');
543
+ }
544
+
545
+ return undefined;
546
+ });
547
+ }
548
+
549
+ override toString(options?: PrintOptions): string {
550
+ if (!isVisibleInContext(this, options?.context) && !this.fullRender) {
551
+ return '';
552
+ }
553
+ options = getPrintOptions(options);
554
+ const w = options.writer!;
555
+ const depth = options.depth!;
556
+ const mark = w.mark();
557
+
558
+ const ctx = options.context;
559
+ return this._withOwnRenderKey(ctx, () => {
560
+ const suppressedLeadingComments: Array<{ node: Node; visible: boolean }> = [];
561
+ if (depth === 0) {
562
+ // Snapshot global emit-tracking so repeated `.toString()` calls remain stable.
563
+ const prevCharsetEmitted = ctx?.charsetEmitted;
564
+ const prevTopImports = ctx?.topImports ? [...ctx.topImports] : undefined;
565
+ // @charset must be first
566
+ if (ctx?.currentCharset && !ctx.charsetEmitted) {
567
+ const charset = ctx.currentCharset;
568
+ // Use capture to avoid double-writing (toTrimmedString writes to writer AND returns the string)
569
+ const charsetStr = w.capture(() => charset.toTrimmedString(options));
570
+ w.add(charsetStr, charset);
571
+ w.add('\n');
572
+ // Do not permanently flip `charsetEmitted` here; restore at end.
573
+ ctx.charsetEmitted = true;
574
+ }
575
+ // Less keeps leading comments before hoisted @import output.
576
+ const isCommentLike = (node: Node): boolean => {
577
+ const text = String(node.valueOf?.() ?? '').trimStart();
578
+ if (!text.startsWith('/*')) {
579
+ return false;
580
+ }
581
+ return isNode(node, N.Comment) || isNode(node, N.Any);
582
+ };
583
+ if (ctx?.topImports?.length) {
584
+ for (const node of this._getRenderChildren(ctx)) {
585
+ if (!isCommentLike(node)) {
586
+ break;
587
+ }
588
+ const commentStr = w.capture(() => node.toTrimmedString(options));
589
+ w.add(normalizeIndent(commentStr, ''), node);
590
+ w.add('\n');
591
+ const wasVisible = node.hasFlag(F_VISIBLE);
592
+ suppressedLeadingComments.push({ node, visible: wasVisible });
593
+ if (wasVisible) {
594
+ node.removeFlag(F_VISIBLE);
595
+ }
596
+ }
597
+ }
598
+ // @import must come after @charset but before other rules
599
+ if (ctx?.topImports?.length) {
600
+ for (const importRule of ctx.topImports) {
601
+ const importStr = w.capture(() => importRule.toString(options));
602
+ w.add(normalizeIndent(importStr, ''), importRule);
603
+ w.add('\n');
604
+ }
605
+ // Do not permanently clear; restore at end.
606
+ }
607
+ // Restore global tracking (we only needed it during this print).
608
+ if (ctx) {
609
+ ctx.charsetEmitted = prevCharsetEmitted;
610
+ if (prevTopImports) {
611
+ ctx.topImports = prevTopImports;
612
+ }
613
+ }
614
+ }
615
+
616
+ this.processPrePost('pre', '', options);
617
+ const bodyMark = w.mark();
618
+ const bodyStr = this.toTrimmedString(options);
619
+ const bodyEmitted = w.getSince(bodyMark);
620
+ if (bodyEmitted.length === 0 && bodyStr) {
621
+ w.add(bodyStr);
622
+ }
623
+ // At root level, ensure output ends with a single newline (standard for CSS files)
624
+ // Don't propagate all the last child's post content (which may have extra whitespace)
625
+ if (depth === 0) {
626
+ for (const suppressed of suppressedLeadingComments) {
627
+ if (suppressed.visible) {
628
+ suppressed.node.addFlag(F_VISIBLE);
629
+ }
630
+ }
631
+ const result = w.getSince(mark).trimEnd();
632
+ // Ensure exactly one trailing newline (only if there's content)
633
+ return result ? result + '\n' : '';
634
+ }
635
+ return w.getSince(mark);
636
+ });
637
+ }
638
+
639
+ pendingExtends = new Set<[find: Selector, extendWith: Selector, partial: boolean]>();
640
+
641
+ _setValueArray(value: Node[]): void {
642
+ (this as unknown as { value: Node[] }).value = value;
643
+ }
644
+
645
+ prependWrapperChildren(...items: Node[]): void {
646
+ if (items.length === 0) {
647
+ return;
648
+ }
649
+ this._setValueArray([...items, ...(this.value as Node[])]);
650
+ for (const item of items) {
651
+ if (item instanceof Node) {
652
+ this.adopt(item);
653
+ this.registerNode(item);
654
+ }
655
+ }
656
+ }
657
+
658
+ /**
659
+ * Create a shallow body wrapper for mixin eval.
660
+ *
661
+ * Creates a new Rules that SHARES the current children array and does NOT adopt
662
+ * children canonically (their canonical `.parent` stays unchanged). Render-key
663
+ * parent edges carry the per-placement parent chain.
664
+ *
665
+ * This replaces clone(true) in the mixin body path for massive perf improvement:
666
+ * a mixin body with 100 declarations creates 1 Rules wrapper
667
+ * instead of recursively cloning all 100+ nodes.
668
+ */
669
+ createShallowBodyWrapper(ctx?: Context, renderKey?: RenderKey): Rules {
670
+ const options = this._cloneOptionsForContext(ctx);
671
+ const location = Array.isArray(this.location) && this.location.length === 6
672
+ ? this.location as LocationInfo
673
+ : undefined;
674
+ const nextRenderKey = renderKey ?? EVAL;
675
+ // Create a new Rules with empty children — bypass constructor adoption
676
+ const wrapper = new (this.constructor as typeof Rules)(
677
+ [],
678
+ options ? { ...options } : undefined,
679
+ location,
680
+ this.treeContext
681
+ );
682
+ // Reuse the same child array directly — NOT through the constructor
683
+ // so adopt() is NOT called on canonical children.
684
+ wrapper._setValueArray(this.value as Node[]);
685
+ wrapper.inherit(this);
686
+ wrapper.renderKey = nextRenderKey;
687
+ const invocationBindings = this._cloneInvocationBindings();
688
+ if (invocationBindings) {
689
+ wrapper._invocationBindings = invocationBindings;
690
+ }
691
+ if (this.functionRegistry) {
692
+ wrapper.functionRegistry = this.functionRegistry.cloneForRules(wrapper);
693
+ }
694
+ wrapper._connectSharedChildren(wrapper.renderKey);
695
+ const sourceValueEdges = (this as unknown as { valueEdges?: Array<Map<RenderKey, Node> | undefined> }).valueEdges;
696
+ if (sourceValueEdges) {
697
+ for (let index = 0; index < sourceValueEdges.length; index++) {
698
+ const override = sourceValueEdges[index]?.get(nextRenderKey);
699
+ if (!override) {
700
+ continue;
701
+ }
702
+ addEdgeAt(wrapper, 'value', index, nextRenderKey, override);
703
+ addParentEdge(override, nextRenderKey, wrapper);
704
+ }
705
+ }
706
+ return wrapper;
707
+ }
708
+
709
+ createPlacementWrapper(ctx?: Context, renderKey?: RenderKey): Rules {
710
+ const options = this._cloneOptionsForContext(ctx);
711
+ const location = Array.isArray(this.location) && this.location.length === 6
712
+ ? this.location as LocationInfo
713
+ : undefined;
714
+ const nextRenderKey = renderKey ?? EVAL;
715
+ const wrapper = new (this.constructor as typeof Rules)(
716
+ [],
717
+ options ? { ...options } : undefined,
718
+ location,
719
+ this.treeContext
720
+ );
721
+ const previousRenderKey = ctx?.renderKey;
722
+ if (ctx && this.renderKey !== CANONICAL) {
723
+ ctx.renderKey = this.renderKey;
724
+ }
725
+ try {
726
+ wrapper._setValueArray([...getChildren(this, ctx)] as Node[]);
727
+ } finally {
728
+ if (ctx) {
729
+ ctx.renderKey = previousRenderKey;
730
+ }
731
+ }
732
+ wrapper.inherit(this);
733
+ wrapper.renderKey = nextRenderKey;
734
+ const invocationBindings = this._cloneInvocationBindings();
735
+ if (invocationBindings) {
736
+ wrapper._invocationBindings = invocationBindings;
737
+ }
738
+ if (this.functionRegistry) {
739
+ wrapper.functionRegistry = this.functionRegistry.cloneForRules(wrapper);
740
+ }
741
+ wrapper._connectSharedChildren(wrapper.renderKey);
742
+ return wrapper;
743
+ }
744
+
745
+ createPlacementWrapperWithChildren(children: readonly Node[], renderKey: RenderKey = EVAL): Rules {
746
+ const options = this._cloneOptionsForContext(undefined);
747
+ const location = Array.isArray(this.location) && this.location.length === 6
748
+ ? this.location as LocationInfo
749
+ : undefined;
750
+ const wrapper = new (this.constructor as typeof Rules)(
751
+ [],
752
+ options ? { ...options } : undefined,
753
+ location,
754
+ this.treeContext
755
+ );
756
+ wrapper._setValueArray([...children] as Node[]);
757
+ wrapper.inherit(this);
758
+ wrapper.renderKey = renderKey;
759
+ const invocationBindings = this._cloneInvocationBindings();
760
+ if (invocationBindings) {
761
+ wrapper._invocationBindings = invocationBindings;
762
+ }
763
+ if (this.functionRegistry) {
764
+ wrapper.functionRegistry = this.functionRegistry.cloneForRules(wrapper);
765
+ }
766
+ wrapper._connectSharedChildren(wrapper.renderKey);
767
+ return wrapper;
768
+ }
769
+
770
+ withRenderOwner(
771
+ owner: Node,
772
+ renderKey?: RenderKey,
773
+ context?: Context
774
+ ): Rules {
775
+ let rules: Rules = this;
776
+ const effectiveRenderKey = renderKey ?? owner.renderKey;
777
+ if (effectiveRenderKey !== undefined && effectiveRenderKey !== CANONICAL && rules.renderKey === CANONICAL) {
778
+ const existing = ((owner as unknown as Record<string, unknown>).rulesEdge as Map<RenderKey, Rules> | undefined)
779
+ ?.get(effectiveRenderKey);
780
+ if (existing) {
781
+ rules = existing;
782
+ } else {
783
+ const wrapped = rules.createShallowBodyWrapper(undefined, effectiveRenderKey);
784
+ wrapped.parent = owner;
785
+ addEdge(owner, 'rules', effectiveRenderKey, wrapped);
786
+ if (owner.renderKey === effectiveRenderKey) {
787
+ (owner as unknown as { rules: Rules }).rules = wrapped;
788
+ }
789
+ rules = wrapped;
790
+ }
791
+ }
792
+ if (context && getCurrentParentNode(rules, context) !== owner) {
793
+ owner.adopt(rules, context);
794
+ }
795
+ return rules;
796
+ }
797
+
798
+ constructor(
799
+ value: readonly Node[],
800
+ options?: RulesOptions & NodeOptions,
801
+ location?: OptionalLocation,
802
+ context?: Context | TreeContext
803
+ ) {
804
+ const treeContext = context instanceof Context
805
+ ? context.treeContext
806
+ : context;
807
+ const ctx = context instanceof Context
808
+ ? context
809
+ : undefined;
810
+
811
+ let rulesVisibility = options?.rulesVisibility ?? {};
812
+ rulesVisibility.Declaration ??= 'public';
813
+ rulesVisibility.Ruleset ??= 'public';
814
+ rulesVisibility.VarDeclaration ??= 'public';
815
+ rulesVisibility.Mixin ??= 'public';
816
+ const mergedOptions = { ...options, rulesVisibility };
817
+ const normalized = (value ?? []) as Node[];
818
+ super(normalized, mergedOptions, location, treeContext);
819
+ this._setValueArray(normalized);
820
+ for (const child of normalized) {
821
+ if (child instanceof Node) {
822
+ this.adopt(child, ctx);
823
+ this.registerNode(child);
824
+ }
825
+ }
826
+ this.allowRoot = true;
827
+ this.allowRuleRoot = true;
828
+ }
829
+
830
+ setInvocationBinding(name: string, binding: InvocationBinding): void {
831
+ (this._invocationBindings ??= new Map()).set(name, { ...binding });
832
+ }
833
+
834
+ getInvocationBinding(name: string, context?: Context): VarDeclaration | undefined {
835
+ const binding = this._invocationBindings?.get(name);
836
+ if (!binding) {
837
+ return undefined;
838
+ }
839
+ if (binding.declaration) {
840
+ return binding.declaration;
841
+ }
842
+ const bindingContext = context
843
+ ? {
844
+ ...context,
845
+ rulesContext: this,
846
+ renderKey: context.renderKey ?? this.renderKey
847
+ } as Context
848
+ : undefined;
849
+ const declaration = binding.factory
850
+ ? binding.factory(this, bindingContext)
851
+ : binding.template?.clone(false, undefined, bindingContext);
852
+ if (!declaration) {
853
+ return undefined;
854
+ }
855
+ declaration.renderKey = bindingContext?.renderKey ?? this.renderKey ?? declaration.renderKey;
856
+ if (bindingContext) {
857
+ setParent(declaration, this, bindingContext);
858
+ } else {
859
+ declaration.parent = this;
860
+ }
861
+ binding.declaration = declaration;
862
+ return declaration;
863
+ }
864
+
865
+ * [Symbol.iterator]() {
866
+ let value = this.value;
867
+ /**
868
+ * This should always be the case? But at one point something somewhere
869
+ * set the value to undefined I think, so just leaving this defensively.
870
+ */
871
+ if (isArray(value)) {
872
+ yield* value.entries();
873
+ }
874
+ }
875
+
876
+ private _getRenderVisibleChildAt(index: number, _context?: Context): Node | undefined {
877
+ const canonical = this.value[index];
878
+ if (this.renderKey === CANONICAL) {
879
+ return canonical;
880
+ }
881
+
882
+ const cursor = { node: this as Node, renderKey: this.renderKey };
883
+ const existing = getEdgeAt(cursor, 'value', index)?.node;
884
+ if (existing) {
885
+ return existing;
886
+ }
887
+
888
+ return canonical;
889
+ }
890
+
891
+ private _getRenderChildren(context?: Context): readonly Node[] {
892
+ return this._withOwnRenderKey(context, () => {
893
+ if (this.renderKey === CANONICAL) {
894
+ return context ? getChildren(this, context) : this.value;
895
+ }
896
+
897
+ const children: Node[] = [];
898
+ for (let i = 0; i < this.value.length; i++) {
899
+ const child = this._getRenderVisibleChildAt(i, context);
900
+ if (child) {
901
+ children.push(child);
902
+ }
903
+ }
904
+ return children;
905
+ });
906
+ }
907
+
908
+ getRegistryChildren(context?: Context): readonly Node[] {
909
+ return this._getRenderChildren(context);
910
+ }
911
+
912
+ ensureCurrentRenderRulesRegistered(context?: Context): void {
913
+ const currentRenderRules = this
914
+ .getRegistryChildren(context)
915
+ .filter((child): child is Rules => isNode(child, N.Rules));
916
+
917
+ for (let i = this.rulesSet.length; i < currentRenderRules.length; i++) {
918
+ this.registerNode(currentRenderRules[i]!, undefined, context);
919
+ }
920
+ }
921
+
922
+ private _setChildren(value: readonly Node[], context?: Context, markDirty: boolean = true): void {
923
+ if (context) {
924
+ setChildren(this, value, context, { markDirty });
925
+ return;
926
+ }
927
+ const nextValue = [...value];
928
+ this._setValueArray(nextValue);
929
+ for (const child of nextValue) {
930
+ this.adopt(child);
931
+ }
932
+ }
933
+
934
+ private _setChildAt(index: number, node: Node, context?: Context, markDirty: boolean = true): void {
935
+ if (context) {
936
+ setChildAt(this, index, node, context, { markDirty });
937
+ return;
938
+ }
939
+ const nextValue = [...this.value];
940
+ nextValue[index] = node;
941
+ this._setValueArray(nextValue);
942
+ this.adopt(node);
943
+ }
944
+
945
+ /**
946
+ * Used by Ruleset, Mixins, and AtRules etc to render
947
+ * rules with braces.
948
+ */
949
+ toBraced(options?: PrintOptions) {
950
+ let opts = getPrintOptions(options);
951
+ // Use options.depth if provided, otherwise calculate from frameState
952
+ const depth = opts.depth!;
953
+ const w = opts.writer!;
954
+ const mark = w.mark();
955
+ let space = ''.padStart(depth * 2);
956
+ return this._withOwnRenderKey(opts.context, () => {
957
+ w.add('{');
958
+ // Children render one level deeper inside braces.
959
+ const childOptions = { ...opts, depth: depth + 1 };
960
+ childOptions.writer!.add('\n');
961
+ Rules.prototype.toTrimmedString.call(this, childOptions);
962
+ // ensure closing brace is on its own properly indented line
963
+ w.add('\n');
964
+ if (depth !== 0) {
965
+ w.add(space);
966
+ }
967
+ w.add('}');
968
+ // At root level (depth === 0), don't add a newline after the closing brace
969
+ // The parent _emitRulesBody will add the newline before the next item
970
+ // For nested rules (depth > 0), the newline is handled by the parent's _emitRulesBody
971
+ return w.getSince(mark);
972
+ });
973
+ }
974
+
975
+ override toTrimmedString(options?: PrintOptions) {
976
+ options = getPrintOptions(options);
977
+ const w = options.writer!;
978
+ const mark = w.mark();
979
+ const ctx = options.context;
980
+ return this._withOwnRenderKey(ctx, () => {
981
+ const depth = options.depth ?? 0;
982
+ const space = indent(depth);
983
+ const value = this._getRenderChildren(options.context);
984
+ const referenceMode = Boolean(options.referenceMode);
985
+ const referenceRenderEnabled = referenceMode ? Boolean(options.referenceRenderEnabled) : true;
986
+ const items = value.filter(n => isVisibleInContext(n, options.context));
987
+
988
+ const isInlineSourceRules = (node: Node): boolean => {
989
+ if (node.type !== 'Rules') {
990
+ return false;
991
+ }
992
+ const rulesNode = node as Rules;
993
+ const rulesValue = rulesNode._getRenderChildren(options.context);
994
+ if (rulesValue.length !== 1) {
995
+ return false;
996
+ }
997
+ const only = rulesValue[0]!;
998
+ return only.type === 'Any' && (only as Any).role === 'any';
999
+ };
1000
+
1001
+ let emittedCount = 0;
1002
+ let lastEmittedType: string | undefined;
1003
+ let lastEmittedWasInlineSourceRules = false;
1004
+ for (const n of items) {
1005
+ const isContainer = n.type === 'Ruleset' || n.type === 'AtRule' || n.type === 'Rules';
1006
+ if (referenceMode && !referenceRenderEnabled && !isContainer) {
1007
+ continue;
1008
+ }
1009
+ if (emittedCount > 0) {
1010
+ const currentBuffer = w.getSince(0);
1011
+ const bufferEndsWithNewline = currentBuffer.endsWith('\n');
1012
+ const needsInlineBoundarySpacing = (
1013
+ (lastEmittedType === 'Any' && n.type !== 'Any')
1014
+ || (lastEmittedWasInlineSourceRules && n.type !== 'Any')
1015
+ );
1016
+ if (!bufferEndsWithNewline || needsInlineBoundarySpacing) {
1017
+ w.add('\n');
1018
+ }
1019
+ }
1020
+
1021
+ const isChildRules = n.type === 'Rules';
1022
+ const isRulesetOrAtRule = n.type === 'Ruleset' || n.type === 'AtRule';
1023
+ if (!isChildRules && !isRulesetOrAtRule && depth !== 0) {
1024
+ w.add(space);
1025
+ }
1026
+
1027
+ let childOptions = { ...options, depth };
1028
+ if (isChildRules) {
1029
+ if (referenceMode && referenceRenderEnabled) {
1030
+ childOptions = {
1031
+ ...childOptions,
1032
+ referenceMode: false,
1033
+ referenceRenderEnabled: true
1034
+ };
1035
+ } else {
1036
+ const ownReferenceMode = (n.options as any)?.referenceMode === true;
1037
+ const childReferenceMode = referenceMode || ownReferenceMode;
1038
+ const enteringReferenceMode = !referenceMode && ownReferenceMode;
1039
+ const ownReferenceRenderOnExtend = (n.options as RulesOptions | undefined)?.referenceRenderOnExtend !== false;
1040
+ const childReferenceRenderOnExtend = childReferenceMode
1041
+ ? (enteringReferenceMode ? ownReferenceRenderOnExtend : options.referenceRenderOnExtend !== false)
1042
+ : true;
1043
+ const childReferenceRenderEnabled = childReferenceMode
1044
+ ? (enteringReferenceMode ? false : referenceRenderEnabled)
1045
+ : true;
1046
+ childOptions = {
1047
+ ...childOptions,
1048
+ referenceMode: childReferenceMode,
1049
+ referenceRenderEnabled: childReferenceRenderEnabled,
1050
+ referenceRenderOnExtend: childReferenceRenderOnExtend
1051
+ };
1052
+ }
1053
+ } else if (
1054
+ referenceMode
1055
+ && referenceRenderEnabled
1056
+ && isRulesetOrAtRule
1057
+ ) {
1058
+ const keepReferenceFiltering = (
1059
+ isNode(n, N.Ruleset)
1060
+ && (() => {
1061
+ const ownSelector = (n as Ruleset).getOwnSelector();
1062
+ return Boolean(
1063
+ ownSelector
1064
+ && !(ownSelector instanceof Nil)
1065
+ && isBareAmpersandOwnSelector(ownSelector)
1066
+ );
1067
+ })()
1068
+ );
1069
+ childOptions = {
1070
+ ...childOptions,
1071
+ referenceMode: keepReferenceFiltering,
1072
+ referenceRenderEnabled: true
1073
+ };
1074
+ }
1075
+
1076
+ const rule = w.capture(() => n.toTrimmedString(childOptions));
1077
+ if (!rule && isContainer) {
1078
+ continue;
1079
+ }
1080
+ w.add(rule, n);
1081
+ const needsSemi = isNode(n, N.Declaration | N.VarDeclaration)
1082
+ ? (n as Declaration).requiresSemi(childOptions.context)
1083
+ : (n as Node).requiredSemi;
1084
+ if (needsSemi && n.options.semi !== false) {
1085
+ w.add(';', n);
1086
+ }
1087
+ emittedCount++;
1088
+ lastEmittedType = n.type;
1089
+ lastEmittedWasInlineSourceRules = isInlineSourceRules(n);
1090
+ }
1091
+ return w.getSince(mark);
1092
+ });
1093
+ }
1094
+
1095
+ /** All rules, with nested rules flattened */
1096
+ flatRules(visibleOnly: boolean = false, context?: Context, positionMap?: WeakMap<Node, FlatRulePosition>) {
1097
+ const finalRules: Node[] = [];
1098
+ const iterateRules = (
1099
+ rules: Rules,
1100
+ inheritedRenderKey?: RenderKey
1101
+ ) => {
1102
+ type DeferredEntry =
1103
+ | {
1104
+ kind: 'node';
1105
+ node: Node;
1106
+ renderKey?: RenderKey;
1107
+ }
1108
+ | {
1109
+ kind: 'flatten';
1110
+ rules: Rules;
1111
+ inheritedRenderKey?: RenderKey;
1112
+ };
1113
+ const renderKey = rules.renderKey ?? inheritedRenderKey;
1114
+ const scopedContext = context
1115
+ ? (
1116
+ renderKey !== undefined
1117
+ && context.renderKey !== renderKey
1118
+ ? { ...context, renderKey, rulesContext: rules } as Context
1119
+ : (context.rulesContext !== rules
1120
+ ? { ...context, rulesContext: rules } as Context
1121
+ : context)
1122
+ )
1123
+ : undefined;
1124
+ const pendingDescendants: DeferredEntry[] = [];
1125
+ let hasEmittedLocalNonContainer = false;
1126
+ const emitNode = (node: Node, nodeRenderKey?: RenderKey): void => {
1127
+ if (positionMap && nodeRenderKey !== CANONICAL && nodeRenderKey !== undefined) {
1128
+ positionMap.set(node, { renderKey: nodeRenderKey });
1129
+ }
1130
+ if (!isNode(node, N.Ruleset | N.AtRule | N.Rules)) {
1131
+ hasEmittedLocalNonContainer = true;
1132
+ }
1133
+ finalRules.push(node);
1134
+ };
1135
+ const flushPendingDescendants = (): void => {
1136
+ for (const entry of pendingDescendants) {
1137
+ if (entry.kind === 'flatten') {
1138
+ iterateRules(entry.rules, entry.inheritedRenderKey);
1139
+ continue;
1140
+ }
1141
+ emitNode(entry.node, entry.renderKey);
1142
+ }
1143
+ pendingDescendants.length = 0;
1144
+ };
1145
+
1146
+ for (let n of rules._getRenderChildren(scopedContext)) {
1147
+ if (isNode(n, N.Rules)) {
1148
+ if ((n.options as RulesOptions)?.referenceMode === true) {
1149
+ flushPendingDescendants();
1150
+ emitNode(
1151
+ n,
1152
+ (n as Rules).renderKey ?? renderKey
1153
+ );
1154
+ } else {
1155
+ if (pendingDescendants.length > 0 && !hasEmittedLocalNonContainer) {
1156
+ pendingDescendants.push({
1157
+ kind: 'flatten',
1158
+ rules: n as Rules,
1159
+ inheritedRenderKey: renderKey
1160
+ });
1161
+ } else {
1162
+ iterateRules(n, renderKey);
1163
+ }
1164
+ }
1165
+ continue;
1166
+ }
1167
+ if (
1168
+ visibleOnly
1169
+ && isNode(n, N.Ruleset)
1170
+ && !isVisibleInContext(n, scopedContext)
1171
+ && !n.fullRender
1172
+ ) {
1173
+ pendingDescendants.push({
1174
+ kind: 'flatten',
1175
+ rules: (n as Ruleset).enterRules(scopedContext),
1176
+ inheritedRenderKey: renderKey
1177
+ });
1178
+ continue;
1179
+ }
1180
+ if (!visibleOnly || isVisibleInContext(n, scopedContext) || n.fullRender) {
1181
+ if (isNode(n, N.Ruleset)) {
1182
+ pendingDescendants.push({
1183
+ kind: 'node',
1184
+ node: n,
1185
+ renderKey
1186
+ });
1187
+ } else if (isNode(n, N.AtRule)) {
1188
+ flushPendingDescendants();
1189
+ emitNode(n, renderKey);
1190
+ } else {
1191
+ emitNode(n, renderKey);
1192
+ }
1193
+ }
1194
+ }
1195
+
1196
+ flushPendingDescendants();
1197
+ };
1198
+ iterateRules(this);
1199
+ return finalRules;
1200
+ }
1201
+
1202
+ visibleRules(context?: Context) {
1203
+ return this._getRenderChildren(context).filter(n => isVisibleInContext(n, context));
1204
+ }
1205
+
1206
+ /**
1207
+ * Return an object representation of a ruleset
1208
+ */
1209
+ toObject(convertToPrimitives: true, context?: Context): Record<string, string | number | boolean>;
1210
+ toObject(convertToPrimitives: false, context?: Context): Record<string, Node>;
1211
+ toObject(convertToPrimitives?: boolean, context?: Context): Record<string, string | number | boolean | Node>;
1212
+ toObject(convertToPrimitives: boolean = true, context?: Context): Record<string, string | number | boolean | Node> {
1213
+ let output = new Map<string, boolean | string | number | Node>();
1214
+ const iterateRules = (rules: Rules) => {
1215
+ for (let n of rules._getRenderChildren(context)) {
1216
+ if (isNode(n, N.Declaration)) {
1217
+ let { name, value, important } = n as any;
1218
+ if (convertToPrimitives) {
1219
+ let primitive = value.valueOf();
1220
+ let outputValue = important ? `${primitive} ${important}` : primitive;
1221
+ if (outputValue === undefined) {
1222
+ continue;
1223
+ }
1224
+ output.set(name.toString(), outputValue);
1225
+ } else {
1226
+ let outputValue = important ? new Sequence([n, important]) : n;
1227
+ output.set(name.toString(), outputValue);
1228
+ }
1229
+ } else if (n instanceof Rules) {
1230
+ iterateRules(n);
1231
+ }
1232
+ }
1233
+ };
1234
+ iterateRules(this as unknown as Rules);
1235
+ return Object.fromEntries(output);
1236
+ }
1237
+
1238
+ /** @todo - Refactor? */
1239
+ _rulesSet: RulesEntry[] | undefined;
1240
+ get rulesSet(): RulesEntry[] {
1241
+ return (this._rulesSet ??= []);
1242
+ }
1243
+
1244
+ registerNode(node: Node, options?: Record<string, any>, context?: Context) {
1245
+ if (isNode(node, N.Rules)) {
1246
+ const nodeOptions = (node as Rules).options;
1247
+ // Use options if provided, otherwise use node's settings, otherwise empty
1248
+ // Then merge with node's settings to preserve any values not in options
1249
+ let optionsVisibility = options?.rulesVisibility;
1250
+ let nodeVisibility = nodeOptions.rulesVisibility ?? {};
1251
+ let rulesVisibility = optionsVisibility
1252
+ ? { ...nodeVisibility, ...optionsVisibility }
1253
+ : nodeVisibility;
1254
+
1255
+ /** Only Declaration and Ruleset are public by default.
1256
+ * VarDeclaration visibility should be set by the parser (optional for Less, private for Jess/Sass).
1257
+ * Mixin visibility should be set by the parser.
1258
+ */
1259
+ rulesVisibility.Declaration ??= 'public';
1260
+ rulesVisibility.Ruleset ??= 'public';
1261
+ rulesVisibility.Mixin ??= 'public';
1262
+
1263
+ /** Either one set as readonly will win */
1264
+ let readonly = Boolean(options?.readonly || nodeOptions.readonly);
1265
+ this.rulesSet.push({
1266
+ node,
1267
+ rulesVisibility,
1268
+ readonly
1269
+ });
1270
+
1271
+ // Note: Rulesets from imported Rules are registered in treeRoot's registry
1272
+ // after evaluation completes (in evalNode), when treeRoot is guaranteed to be set
1273
+ } else if (isNode(node, N.Declaration)) {
1274
+ /**
1275
+ * setDefined works like Sass's !default flag - it finds the original variable
1276
+ * declaration and inserts a new declaration at the same rules level as the
1277
+ * found variable, but before the current nested node.
1278
+ */
1279
+ if (node.options?.setDefined && context) {
1280
+ const key = (node as any).name?.toString();
1281
+ const sourceNode = node.sourceNode ?? node;
1282
+ let opts: Registries.FindOptions = {};
1283
+ opts.searchParents = true;
1284
+ opts.context = context;
1285
+ opts.start = undefined;
1286
+ // Exclude the current declaration by source identity so a derived eval node
1287
+ // cannot resolve its own canonical registry entry as the "existing" declaration.
1288
+ opts.filter = (n: Node) => (n.sourceNode ?? n) !== sourceNode;
1289
+ let result = this.find('declaration', key, node.type as 'VarDeclaration' | 'Declaration', opts);
1290
+ if (result) {
1291
+ if (result.options?.readonly || opts.readonly) {
1292
+ throw new ReferenceError(`"${key}" is readonly`);
1293
+ }
1294
+
1295
+ // Find the Rules node that contains the found declaration
1296
+ let foundRules = getCurrentParentNode(result, context) as Rules | undefined;
1297
+
1298
+ if (!foundRules) {
1299
+ throw new Error(`Could not find parent Rules for declaration '${key}'`);
1300
+ }
1301
+
1302
+ // Create a new declaration with the same name but our value
1303
+ const newDeclaration = node.copy();
1304
+ newDeclaration.options = { ...newDeclaration.options };
1305
+ newDeclaration.options.setDefined = undefined; // Remove setDefined flag
1306
+
1307
+ // Adopt the new declaration to the found Rules
1308
+ foundRules.adopt(newDeclaration);
1309
+
1310
+ // Add to the value array AFTER the found declaration
1311
+ // This ensures it shadows the original and is evaluated after it
1312
+ const foundIndex = foundRules.value.indexOf(result);
1313
+ if (foundIndex !== -1) {
1314
+ if (context) {
1315
+ foundRules.splice(context, foundIndex + 1, 0, newDeclaration);
1316
+ } else {
1317
+ foundRules.splice(foundIndex + 1, 0, newDeclaration);
1318
+ }
1319
+ } else {
1320
+ // If not found in array, add at the beginning
1321
+ if (context) {
1322
+ foundRules.unshift(context, newDeclaration);
1323
+ } else {
1324
+ foundRules.unshift(newDeclaration);
1325
+ }
1326
+ }
1327
+
1328
+ // Register it via registerNode to ensure it's properly indexed
1329
+ // Note: registerNode will call register('declaration', ...) which adds to registry
1330
+ // We skip setDefined processing since we already removed the flag
1331
+ foundRules.registerNode(newDeclaration, undefined, context);
1332
+ } else {
1333
+ throw new ReferenceError(`"${key}" is not defined`);
1334
+ }
1335
+ }
1336
+
1337
+ this.register('declaration', node, context);
1338
+ } else if (isNode(node, N.Ruleset)) {
1339
+ // Register to 'mixin' for mixin calls
1340
+ // Always register - guard filtering happens at call time in getFunctionFromMixins
1341
+ // Note: 'ruleset' registration for extends now happens in Ruleset.preEval to the extend root's registry
1342
+ this.register('mixin', node, context);
1343
+ } else if (isNode(node, N.Mixin)) {
1344
+ this.register('mixin', node, context);
1345
+ } else if (isNode(node, N.Func)) {
1346
+ this.register('function', node, context);
1347
+ }
1348
+ }
1349
+
1350
+ override push(...nodes: Node[]): void;
1351
+ override push(ctx: Context, ...nodes: Node[]): void;
1352
+ override push(...args: [Context, ...Node[]] | Node[]): void {
1353
+ const hasCtx = args.length > 0 && args[0] instanceof Context;
1354
+ const ctx = hasCtx ? args[0] as Context : undefined;
1355
+ const nodes = (hasCtx ? args.slice(1) : args) as Node[];
1356
+ // Route through _getChildren/_setChildren overlay when context is active
1357
+ if (ctx) {
1358
+ const nextValue = [...this._getRenderChildren(ctx)];
1359
+ for (const node of nodes) {
1360
+ this.adopt(node, ctx);
1361
+ nextValue.push(node);
1362
+ }
1363
+ this._setChildren(nextValue, ctx);
1364
+ for (const node of nodes) {
1365
+ this.registerNode(node, undefined, ctx);
1366
+ }
1367
+ return;
1368
+ }
1369
+ this._setValueArray([...this.value]);
1370
+ for (const node of nodes) {
1371
+ this.adopt(node, ctx);
1372
+ (this.value as Node[]).push(node);
1373
+ this.registerNode(node, undefined, ctx);
1374
+ }
1375
+ }
1376
+
1377
+ override splice(start: number, deleteCount: number, ...items: Node[]): Node[];
1378
+ override splice(ctx: Context, start: number, deleteCount: number, ...items: Node[]): Node[];
1379
+ override splice(...args: [Context, number, number, ...Node[]] | [number, number, ...Node[]]): Node[] {
1380
+ const hasCtx = args[0] instanceof Context;
1381
+ const ctx = hasCtx ? args[0] as Context : undefined;
1382
+ const [start, deleteCount, ...items] = (hasCtx ? args.slice(1) : args) as [number, number, ...Node[]];
1383
+ // Route through overlay when context is active
1384
+ if (ctx) {
1385
+ const nextValue = [...this._getRenderChildren(ctx)];
1386
+ const removed = nextValue.splice(start, deleteCount, ...items);
1387
+ for (const item of items) {
1388
+ if (item instanceof Node) {
1389
+ this.adopt(item, ctx);
1390
+ }
1391
+ }
1392
+ this._setChildren(nextValue, ctx);
1393
+ for (const item of items) {
1394
+ if (item instanceof Node) {
1395
+ this.registerNode(item, undefined, ctx);
1396
+ }
1397
+ }
1398
+ (this as unknown as { _invalidateValueOf: () => void })._invalidateValueOf();
1399
+ return removed as Node[];
1400
+ }
1401
+ const nextValue = [...this.value];
1402
+ const removed = nextValue.splice(start, deleteCount, ...items);
1403
+ this._setValueArray(nextValue);
1404
+ for (const item of items) {
1405
+ if (item instanceof Node) {
1406
+ this.adopt(item, ctx);
1407
+ this.registerNode(item, undefined, ctx);
1408
+ }
1409
+ }
1410
+ (this as unknown as { _invalidateValueOf: () => void })._invalidateValueOf();
1411
+ return removed as Node[];
1412
+ }
1413
+
1414
+ override unshift(...items: Node[]): void;
1415
+ override unshift(ctx: Context, ...items: Node[]): void;
1416
+ override unshift(...args: [Context, ...Node[]] | Node[]): void {
1417
+ const hasCtx = args.length > 0 && args[0] instanceof Context;
1418
+ const ctx = hasCtx ? args[0] as Context : undefined;
1419
+ const items = (hasCtx ? args.slice(1) : args) as Node[];
1420
+ // Route through overlay when context is active
1421
+ if (ctx) {
1422
+ for (const item of items) {
1423
+ if (item instanceof Node) {
1424
+ this.adopt(item, ctx);
1425
+ }
1426
+ }
1427
+ this._setChildren([...items, ...this._getRenderChildren(ctx)], ctx);
1428
+ for (const item of items) {
1429
+ if (item instanceof Node) {
1430
+ this.registerNode(item, undefined, ctx);
1431
+ }
1432
+ }
1433
+ (this as unknown as { _invalidateValueOf: () => void })._invalidateValueOf();
1434
+ return;
1435
+ }
1436
+ this._setValueArray([...this.value]);
1437
+ (this.value as Node[]).unshift(...items);
1438
+ for (const item of items) {
1439
+ if (item instanceof Node) {
1440
+ this.adopt(item, ctx);
1441
+ this.registerNode(item, undefined, ctx);
1442
+ }
1443
+ }
1444
+ (this as unknown as { _invalidateValueOf: () => void })._invalidateValueOf();
1445
+ }
1446
+
1447
+ at(index: number, context?: Context) {
1448
+ return atIndex(this._getRenderChildren(context), index);
1449
+ }
1450
+
1451
+ /**
1452
+ * This traverses deeply to visit all nodes, but indexes locally.
1453
+ */
1454
+ override preEval(context: Context) {
1455
+ if (!this.preEvaluated) {
1456
+ context.depth++;
1457
+ let rules = this;
1458
+ // When this is the nestable at-rule wrapper (one child Ruleset(&)), do not clone so
1459
+ // inner rulesets register to the same object we push and register as extend root.
1460
+ const nestableAtRuleNames = new Set(['@media', '@supports', '@layer', '@container', '@scope']);
1461
+ const activeParent = getCurrentParentNode(this, context);
1462
+ const sourceParent = getSourceParent(this, context);
1463
+ const parentAtRule = activeParent?.type === 'AtRule'
1464
+ ? activeParent
1465
+ : (sourceParent?.type === 'AtRule' ? sourceParent : null);
1466
+ const isNestableAtRuleBody =
1467
+ parentAtRule
1468
+ && nestableAtRuleNames.has(String((parentAtRule as any).name?.valueOf?.() ?? ''));
1469
+ const children = rules._getRenderChildren(context);
1470
+ const first = children[0];
1471
+ const isWrapper =
1472
+ isNestableAtRuleBody
1473
+ && children.length === 1
1474
+ && isNode(first, N.Ruleset)
1475
+ && isNode((first as Ruleset).get('selector'), N.Ampersand);
1476
+ if (isWrapper) {
1477
+ rules = this;
1478
+ }
1479
+ rules.preEvaluated = true;
1480
+ // Save current context and set up new context for variable lookups during preEval
1481
+ const saved = this._snapshotContext(context);
1482
+ this._setupContextForRules(context, rules);
1483
+
1484
+ // Set context.root early if this is the main root
1485
+ const isMainRoot = !context.root;
1486
+ if (isMainRoot) {
1487
+ context.root = rules;
1488
+ }
1489
+
1490
+ /**
1491
+ * I think maybe we can just set the index to the actual order?
1492
+ */
1493
+ for (let i = 0; i < children.length; i++) {
1494
+ let n = children[i]!;
1495
+ setIndex(n, i, context);
1496
+ }
1497
+ // Preserve parent when cloning - if this Rules is inside a ruleset, maintain the parent relationship
1498
+ const parent = getCurrentParentNode(this, context);
1499
+ if (parent && !getCurrentParentNode(rules, context)) {
1500
+ parent.adopt(rules, context);
1501
+ }
1502
+
1503
+ // Set context.root if not already set (needed for preEval visitors)
1504
+ if (!context.root) {
1505
+ context.root = rules;
1506
+ }
1507
+ // When getTree() set context.root to the original Rules but we're processing a clone,
1508
+ // use the clone as context.root so registerRoot/pushExtendRoot run and rulesets register to the clone (extend fix).
1509
+ if (context.root === this && this !== rules) {
1510
+ context.root = rules;
1511
+ }
1512
+
1513
+ // Register main root as extend root if this is the root (needed for extends in preEval)
1514
+ // Check rules === context.root at registration time (not using stale isMainRoot)
1515
+ if (rules === context.root && !context.extendRoots.root) {
1516
+ context.extendRoots.registerRoot(rules);
1517
+ context.extendRoots.pushExtendRoot(rules);
1518
+ }
1519
+
1520
+ // Always push nestable at-rule body so inner rulesets register to it (not document root).
1521
+ // Needed for both: wrapper (collapseNesting) and direct body (collapseNesting: false).
1522
+ if (isNestableAtRuleBody) {
1523
+ context.extendRoots.pushExtendRoot(rules);
1524
+ }
1525
+
1526
+ // Multi-pass registration system for handling interpolated names
1527
+ const mp = this._multiPassPreEval(rules, context, saved);
1528
+ const popNestableBody = () => {
1529
+ if (isNestableAtRuleBody) {
1530
+ context.extendRoots.popExtendRoot();
1531
+ }
1532
+ };
1533
+ if (isThenable(mp)) {
1534
+ return (mp as Promise<this>).then((result) => {
1535
+ popNestableBody();
1536
+ return result;
1537
+ });
1538
+ }
1539
+ popNestableBody();
1540
+ return mp;
1541
+ }
1542
+ return this;
1543
+ }
1544
+
1545
+ /**
1546
+ * Multi-pass preEval system to handle interpolated names and dependencies
1547
+ */
1548
+ private _multiPassPreEval(rules: Rules, context: Context, saved: any): MaybePromise<this> {
1549
+ // First pass: Only register nodes with static names
1550
+ const staticNodes: Node[] = [];
1551
+ const dynamicNodes: Node[] = [];
1552
+
1553
+ // Process each node with static name, handling both sync and async preEval
1554
+ const processResult = serialForEach(rules._getRenderChildren(context), (node, index) => {
1555
+ // Check if node has a static name (can be registered immediately)
1556
+ if (node.type === 'Any' && (node as any).role === 'charset') {
1557
+ /** Special case where we register the charset node immediately */
1558
+ const charsetNode = (node as Any).preEval(context);
1559
+ rules._setChildAt(index, charsetNode, context, false);
1560
+ rules.adopt(charsetNode, context);
1561
+ return;
1562
+ }
1563
+ // Nodes that don't register by name (Call, Expression, etc.) skip
1564
+ // both preEval and dynamic resolution — they're handled by the eval queue.
1565
+ if (!this._isRegisterableType(node)) {
1566
+ setIndex(node, index, context);
1567
+ return;
1568
+ }
1569
+ if (this._hasStaticName(node, context)) {
1570
+ // Pre-evaluate nodes with static names before registration
1571
+ // This ensures selectors are evaluated and keySets are available for rulesets
1572
+ const preEvald = node.preEval(context);
1573
+ if (isThenable(preEvald)) {
1574
+ return (preEvald as Promise<Node>).then((preEvaldNode) => {
1575
+ rules._setChildAt(index, preEvaldNode, context, false);
1576
+ rules.adopt(preEvaldNode, context);
1577
+ setIndex(preEvaldNode as Node, index, context);
1578
+ // After async preEval, check if it still has a static name
1579
+ if (this._hasStaticName(preEvaldNode, context)) {
1580
+ staticNodes.push(preEvaldNode);
1581
+ this._registerNodeIfEligible(rules, preEvaldNode, context);
1582
+ } else {
1583
+ dynamicNodes.push(preEvaldNode);
1584
+ }
1585
+ });
1586
+ }
1587
+ rules._setChildAt(index, preEvald as Node, context, false);
1588
+ rules.adopt(preEvald as Node, context);
1589
+ setIndex(preEvald as Node, index, context);
1590
+ const nodeToRegister = preEvald as Node;
1591
+ staticNodes.push(nodeToRegister);
1592
+ this._registerNodeIfEligible(rules, nodeToRegister, context);
1593
+ } else {
1594
+ dynamicNodes.push(node);
1595
+ }
1596
+ });
1597
+
1598
+ const finish = () => {
1599
+ // If no dynamic nodes, we're done
1600
+ if (dynamicNodes.length === 0) {
1601
+ // Restore context after preEval is complete
1602
+ context.rulesContext = saved.rulesContext;
1603
+ context.renderKey = saved.renderKey;
1604
+ context.treeRoot = saved.treeRoot;
1605
+ // Only restore context.root if saved.root is defined (not the outermost root)
1606
+ // If saved.root is undefined, it means we're at the outermost level, so keep context.root as is
1607
+ if (saved.root !== undefined) {
1608
+ context.root = saved.root;
1609
+ }
1610
+ return rules as this;
1611
+ }
1612
+ // Multi-pass resolution of dynamic nodes
1613
+ return this._resolveDynamicNodes(rules, context, saved, dynamicNodes);
1614
+ };
1615
+
1616
+ if (isThenable(processResult)) {
1617
+ return (processResult as Promise<void>).then(() => finish());
1618
+ }
1619
+ return finish();
1620
+ }
1621
+
1622
+ /**
1623
+ * Helper to check if a value is static (either a Node with F_STATIC flag or a primitive value)
1624
+ */
1625
+ private _isStatic(value: any): boolean {
1626
+ if (value && typeof value.hasFlag === 'function') {
1627
+ return value.hasFlag(F_STATIC);
1628
+ }
1629
+ // Primitive values (strings, numbers, etc.) are considered static
1630
+ return true;
1631
+ }
1632
+
1633
+ /**
1634
+ * Check if a node type participates in name-based registration.
1635
+ * Only these node types have names/selectors that _resolveDynamicNodes
1636
+ * needs to resolve. Everything else (Call, Expression, Comment, etc.)
1637
+ * goes straight to the eval queue without preEval.
1638
+ */
1639
+ private _isRegisterableType(node: Node): boolean {
1640
+ return isNode(node, N.VarDeclaration | N.Declaration | N.Mixin | N.Ruleset) || (node as Node).type === 'StyleImport';
1641
+ }
1642
+
1643
+ /**
1644
+ * Check if a node has a static name that can be registered immediately
1645
+ */
1646
+ private _hasStaticName(node: Node, context?: Context): boolean {
1647
+ if (isNode(node, N.VarDeclaration)) {
1648
+ return this._isStatic(node.get('name'));
1649
+ }
1650
+ if (isNode(node, N.Mixin)) {
1651
+ // Check position-patched name: preEval may have resolved an interpolated name
1652
+ const name = node.get('name', context);
1653
+ return this._isStatic(name);
1654
+ }
1655
+ if (isNode(node, N.Declaration)) {
1656
+ return this._isStatic(node.get('name'));
1657
+ }
1658
+ if (node.type === 'StyleImport') {
1659
+ return this._isStatic((node as Node & { path: unknown }).path);
1660
+ }
1661
+ if (isNode(node, N.Ruleset)) {
1662
+ const selector: Node = (node as Ruleset).get('selector');
1663
+ if (isNode(selector, N.BasicSelector | N.CompoundSelector | N.ComplexSelector | N.SelectorList | N.Nil)) {
1664
+ return true;
1665
+ }
1666
+ if (context && isPreEvaluated(node, context)) {
1667
+ return true;
1668
+ }
1669
+ return (selector as Node).hasFlag(F_STATIC);
1670
+ }
1671
+ return node.hasFlag(F_STATIC);
1672
+ }
1673
+
1674
+ /**
1675
+ * Register a node if it's eligible for registration
1676
+ */
1677
+ private _registerNodeIfEligible(rules: Rules, node: Node, context: Context) {
1678
+ if (isNode(node, N.Declaration)) {
1679
+ rules.registerNode(node, undefined, context);
1680
+ } else if (isNode(node, N.Mixin)) {
1681
+ rules.registerNode(node, undefined, context);
1682
+ } else if (isNode(node, N.Ruleset)) {
1683
+ // registerNode handles both 'mixin' and 'ruleset' registries
1684
+ rules.registerNode(node, undefined, context);
1685
+ }
1686
+ }
1687
+
1688
+ /**
1689
+ * Multi-pass resolution of dynamic nodes with interpolated names
1690
+ */
1691
+ private _resolveDynamicNodes(rules: Rules, context: Context, saved: any, dynamicNodes: Node[]): MaybePromise<this> {
1692
+ const resolvedNodes: Node[] = [];
1693
+
1694
+ const handleResolvedNode = (resolvedNode: Node, node: Node, stillUnresolved: Node[]): boolean => {
1695
+ if (resolvedNode.index === undefined) {
1696
+ resolvedNode.index = node.index;
1697
+ }
1698
+ if (!resolvedNode.sourceNode) {
1699
+ resolvedNode.sourceNode = node.sourceNode ?? node;
1700
+ }
1701
+ if (resolvedNode.type === 'Ruleset') {
1702
+ rules.registerNode(resolvedNode, undefined, context);
1703
+ }
1704
+ if (isNode(resolvedNode, N.Nil) || this._hasStaticName(resolvedNode, context)) {
1705
+ resolvedNodes.push(resolvedNode);
1706
+ this._registerNodeIfEligible(rules, resolvedNode, context);
1707
+ return true; // made progress
1708
+ } else {
1709
+ stillUnresolved.push(resolvedNode);
1710
+ return false;
1711
+ }
1712
+ };
1713
+
1714
+ const applyResolvedNodes = () => {
1715
+ const children = rules._getRenderChildren(context);
1716
+ for (let i = 0; i < children.length; i++) {
1717
+ const node = children[i]!;
1718
+ const nodeIdx = getIndex(node, context);
1719
+ const resolvedNode = resolvedNodes.find(n => getIndex(n, context) === nodeIdx);
1720
+ if (resolvedNode && resolvedNode !== node) {
1721
+ rules._setChildAt(i, resolvedNode.inherit(node), context, false);
1722
+ rules.adopt(resolvedNode, context);
1723
+ }
1724
+ }
1725
+ };
1726
+
1727
+ const finishResolution = (): this => {
1728
+ applyResolvedNodes();
1729
+ context.rulesContext = saved.rulesContext;
1730
+ context.renderKey = saved.renderKey;
1731
+ context.treeRoot = saved.treeRoot;
1732
+ if (saved.root !== undefined) {
1733
+ context.root = saved.root;
1734
+ }
1735
+ return rules as this;
1736
+ };
1737
+
1738
+ // Separate declarations (whose dynamic names might depend on each other)
1739
+ // from non-declarations (which depend on declaration VALUES, not names,
1740
+ // so retrying during preEval won't help).
1741
+ const isDeclarationType = (n: Node) =>
1742
+ isNode(n, N.VarDeclaration) || isNode(n, N.Declaration);
1743
+
1744
+ const dynamicDeclarations: Node[] = [];
1745
+ const otherDynamic: Node[] = [];
1746
+ for (const node of dynamicNodes) {
1747
+ if (isDeclarationType(node)) {
1748
+ dynamicDeclarations.push(node);
1749
+ } else {
1750
+ otherDynamic.push(node);
1751
+ }
1752
+ }
1753
+
1754
+ // Phase 1: Resolve declarations with dynamic names.
1755
+ // Retry because one declaration's name might depend on another's being registered.
1756
+ const MAX_DECL_RETRIES = 5;
1757
+ let declRetries = 0;
1758
+ const unresolvedDecls: Node[] = [...dynamicDeclarations];
1759
+
1760
+ const resolveDeclarations = (): MaybePromise<void> => {
1761
+ declRetries++;
1762
+ if (declRetries > MAX_DECL_RETRIES || unresolvedDecls.length === 0) {
1763
+ return;
1764
+ }
1765
+ const stillUnresolved: Node[] = [];
1766
+ let madeProgress = false;
1767
+
1768
+ for (let i = 0; i < unresolvedDecls.length; i++) {
1769
+ const node = unresolvedDecls[i]!;
1770
+ try {
1771
+ const result = node.preEval(context);
1772
+
1773
+ if (isThenable(result)) {
1774
+ const remaining = unresolvedDecls.slice(i + 1);
1775
+ return (result as Promise<Node>).then((resolvedNode) => {
1776
+ if (handleResolvedNode(resolvedNode, node, stillUnresolved)) {
1777
+ madeProgress = true;
1778
+ }
1779
+ unresolvedDecls.length = 0;
1780
+ unresolvedDecls.push(...stillUnresolved, ...remaining);
1781
+ if (madeProgress && unresolvedDecls.length > 0) {
1782
+ return resolveDeclarations();
1783
+ }
1784
+ });
1785
+ }
1786
+
1787
+ if (handleResolvedNode(result as Node, node, stillUnresolved)) {
1788
+ madeProgress = true;
1789
+ }
1790
+ } catch {
1791
+ stillUnresolved.push(node);
1792
+ }
1793
+ }
1794
+
1795
+ if (madeProgress && stillUnresolved.length > 0) {
1796
+ unresolvedDecls.length = 0;
1797
+ unresolvedDecls.push(...stillUnresolved);
1798
+ return resolveDeclarations();
1799
+ }
1800
+ };
1801
+
1802
+ // Phase 2: Try non-declarations once. Their interpolated names typically
1803
+ // depend on declaration VALUES (e.g. @infix from breakpoint-infix()),
1804
+ // which aren't evaluated until the eval phase. Retrying won't help.
1805
+ const resolveOtherOnce = (): MaybePromise<void> => {
1806
+ for (let i = 0; i < otherDynamic.length; i++) {
1807
+ const node = otherDynamic[i]!;
1808
+ try {
1809
+ const result = node.preEval(context);
1810
+
1811
+ if (isThenable(result)) {
1812
+ const remaining = otherDynamic.slice(i + 1);
1813
+ return (result as Promise<Node>).then((resolvedNode) => {
1814
+ handleResolvedNode(resolvedNode, node, []);
1815
+ // Continue with remaining nodes
1816
+ otherDynamic.length = 0;
1817
+ otherDynamic.push(...remaining);
1818
+ return resolveOtherOnce();
1819
+ });
1820
+ }
1821
+
1822
+ handleResolvedNode(result as Node, node, []);
1823
+ } catch {
1824
+ // Can't resolve during preEval — leave in place for eval phase
1825
+ }
1826
+ }
1827
+ };
1828
+
1829
+ return pipe(
1830
+ () => resolveDeclarations(),
1831
+ () => {
1832
+ applyResolvedNodes();
1833
+ return resolveOtherOnce();
1834
+ },
1835
+ () => finishResolution()
1836
+ );
1837
+ }
1838
+
1839
+ /**
1840
+ * Helper method to continue preEval'ing remaining children after an async preEval.
1841
+ */
1842
+ private _preEvalRemainingChildren(rules: Rules, context: Context, startIndex: number, saved?: any): MaybePromise<this> {
1843
+ const children = rules._getRenderChildren(context);
1844
+ for (let i = startIndex; i < children.length; i++) {
1845
+ const node = children[i]!;
1846
+
1847
+ // Always call preEval to ensure deep traversal and name resolution
1848
+ const result = node.preEval(context);
1849
+ if (isThenable(result)) {
1850
+ // Handle async preEval by returning a promise that resolves after all children
1851
+ return result.then((resolvedNode) => {
1852
+ // Update the node if preEval returned a different instance
1853
+ if (resolvedNode !== node) {
1854
+ rules._setChildAt(i, resolvedNode, context, false);
1855
+ rules.adopt(resolvedNode, context);
1856
+ }
1857
+
1858
+ // Register the node after preEval (name resolution) if not already registered
1859
+ if (!isNode(node, N.VarDeclaration)) {
1860
+ rules.registerNode(resolvedNode, undefined, context);
1861
+ }
1862
+
1863
+ // Continue with the rest of the children
1864
+ return this._preEvalRemainingChildren(rules, context, i + 1, saved);
1865
+ });
1866
+ }
1867
+
1868
+ // Update the node if preEval returned a different instance
1869
+ if (result !== node) {
1870
+ rules._setChildAt(i, result, context, false);
1871
+ rules.adopt(result, context);
1872
+ }
1873
+
1874
+ // Register the node after preEval (name resolution) if not already registered
1875
+ if (!isNode(node, N.VarDeclaration)) {
1876
+ rules.registerNode(result, undefined, context);
1877
+ }
1878
+ }
1879
+
1880
+ // Restore context after preEval is complete (for async case)
1881
+ if (saved) {
1882
+ context.rulesContext = saved.rulesContext;
1883
+ context.renderKey = saved.renderKey;
1884
+ context.treeRoot = saved.treeRoot;
1885
+ // Only restore context.root if saved.root is defined (not the outermost root)
1886
+ // If saved.root is undefined, it means we're at the outermost level, so keep context.root as is
1887
+ if (saved.root !== undefined) {
1888
+ context.root = saved.root;
1889
+ }
1890
+ }
1891
+ return rules as this;
1892
+ }
1893
+
1894
+ /** Save current context roots to restore later */
1895
+ private _snapshotContext(context: Context) {
1896
+ return {
1897
+ rulesContext: context.rulesContext,
1898
+ renderKey: context.renderKey,
1899
+ treeContext: context.treeContext,
1900
+ treeRoot: context.treeRoot,
1901
+ root: context.root,
1902
+ extendRootStackLength: context.extendRoots.extendRootStack.length
1903
+ } as const;
1904
+ }
1905
+
1906
+ /** Setup context for evaluating these rules */
1907
+ private _setupContextForRules(context: Context, rules: Rules) {
1908
+ const treeContext = context.treeContext;
1909
+ // Only switch treeContext if the rules have one AND it's different
1910
+ // Dynamically created Rules (e.g., mixin parameter wrappers) may not have treeContext
1911
+ // and we don't want to lose leakyRules and other settings
1912
+ // Check _meta.treeContext (private field) not treeContext (getter that lazily creates)
1913
+ const rulesTreeContext = (rules as any)._meta?.treeContext as TreeContext | undefined;
1914
+ if (rulesTreeContext && (!treeContext || treeContext !== rulesTreeContext)) {
1915
+ context.allRoots.push(rules);
1916
+ context.treeContext = rulesTreeContext;
1917
+ context.treeRoot = rules;
1918
+ }
1919
+ // Always set root if not set - needed for extends to work with API-created Rules
1920
+ context.root ??= rules;
1921
+ context.rulesContext = rules;
1922
+ if (rules.renderKey !== CANONICAL) {
1923
+ context.renderKey = rules.renderKey;
1924
+ }
1925
+ }
1926
+
1927
+ /** Assign depth-first document order to every Ruleset under the given Rules (single walk, source order). */
1928
+ private _assignDocumentOrderDepthFirst(
1929
+ rules: Rules,
1930
+ map: WeakMap<Ruleset, number>,
1931
+ counter: { value: number },
1932
+ context?: Context
1933
+ ): void {
1934
+ const value = rules._getRenderChildren(context);
1935
+ if (!isArray(value)) {
1936
+ return;
1937
+ }
1938
+ for (const node of value) {
1939
+ if (isNode(node, N.Ruleset)) {
1940
+ map.set(node as Ruleset, counter.value);
1941
+ counter.value++;
1942
+ }
1943
+ const innerRules = (node as any).rules;
1944
+ if (innerRules && isNode(innerRules, N.Rules)) {
1945
+ this._assignDocumentOrderDepthFirst(innerRules as Rules, map, counter, context);
1946
+ }
1947
+ }
1948
+ }
1949
+
1950
+ /** Build the evaluation queue partitioned by priority */
1951
+ private _buildEvalQueue(rules: Rules, context: Context): EvalQueueMap {
1952
+ let evalQueue: EvalQueueMap = new Map();
1953
+ for (const item of rules._getRenderChildren(context).entries()) {
1954
+ let [idx, rule] = item;
1955
+ if (rule.index === undefined) {
1956
+ rule.index = idx;
1957
+ }
1958
+ let priority = NodeTypeToPriority.get(rule.type) ?? Priority.None;
1959
+ // Less variable-calls `@foo();` are parsed as Expression(Call(variable-ref)).
1960
+ // We *selectively* boost only those calls that "unlock mixins" (i.e. calling a variable whose
1961
+ // value is a detached ruleset containing mixin definitions). This avoids changing evaluation
1962
+ // order for regular detached rulesets like `@ruleset()` used for property blocks.
1963
+ if (priority === Priority.None && rules.treeContext?.leakyRules === true && isNode(rule, N.Expression)) {
1964
+ const inner = (rule as any).value;
1965
+ if (isNode(inner, N.Call) && isNode((inner as any).name, N.Reference)) {
1966
+ const ref = (inner as any).name;
1967
+ const refType = String(ref?.options?.type ?? '');
1968
+ if (refType === 'variable') {
1969
+ const raw = ref.key;
1970
+ const keyStr = Array.isArray(raw) ? raw.join('') : String(raw?.valueOf?.() ?? raw ?? '');
1971
+ // Only if variable exists and its value is a detached ruleset Mixin with nested Mixin definitions.
1972
+ const decl = rules.find('declaration', keyStr, 'VarDeclaration', { context }) as any;
1973
+ const val = decl?.value;
1974
+ const hasNestedMixinDefinitions =
1975
+ isNode(val, N.Mixin)
1976
+ && isNode((val as any).rules, N.Rules)
1977
+ && (val as any).rules._getRenderChildren(context).some((n: any) => n?.type === 'Mixin');
1978
+ if (hasNestedMixinDefinitions) {
1979
+ priority = Priority.High;
1980
+ }
1981
+ }
1982
+ }
1983
+ }
1984
+ let queue = evalQueue.get(priority) ?? [];
1985
+ queue.push(item as [number, Node]);
1986
+ evalQueue.set(priority, queue);
1987
+ }
1988
+ return evalQueue;
1989
+ }
1990
+
1991
+ /** Evaluate the built queues in priority order */
1992
+ private _evaluateQueue(rules: Rules, evalQueue: EvalQueueMap, context: Context): MaybePromise<boolean> {
1993
+ let rulesToHoist = false;
1994
+ const scheduledPriority = new WeakMap<Node, Priority>();
1995
+ const failuresByPriority = new WeakMap<Node, Map<Priority, number>>();
1996
+
1997
+ const priorities: Priority[] = Array.from({ length: Priority.Highest + 1 }).map((_, i) => (Priority.Highest - i) as Priority);
1998
+ const runPriority = (p: Priority): MaybePromise<void> => {
1999
+ const queue = evalQueue.get(p);
2000
+ if (!queue) {
2001
+ return;
2002
+ }
2003
+ const enqueueRetry = (priority: Priority, item: [number, Node], rule: Node): void => {
2004
+ const retryQueue = evalQueue.get(priority) ?? [];
2005
+ retryQueue.push(item);
2006
+ evalQueue.set(priority, retryQueue);
2007
+ scheduledPriority.set(rule, priority);
2008
+ };
2009
+ const countFailure = (rule: Node, priority: Priority): number => {
2010
+ const byPriority = failuresByPriority.get(rule) ?? new Map<Priority, number>();
2011
+ const nextCount = (byPriority.get(priority) ?? 0) + 1;
2012
+ byPriority.set(priority, nextCount);
2013
+ failuresByPriority.set(rule, byPriority);
2014
+ return nextCount;
2015
+ };
2016
+ const runSingleEntry = (q: number): MaybePromise<void | undefined> => {
2017
+ const [idx, rule] = queue[q]!;
2018
+
2019
+ /**
2020
+ * Var declarations have late evaluation, so they are skipped.
2021
+ * (Meaning: they are not evaluated until they are referenced.)
2022
+ */
2023
+ if (isNode(rule, N.VarDeclaration)) {
2024
+ return;
2025
+ }
2026
+
2027
+ // Skip stale entries for nodes that were re-queued to a different priority.
2028
+ const expectedPriority = scheduledPriority.get(rule);
2029
+ if (expectedPriority !== undefined && expectedPriority !== p) {
2030
+ return;
2031
+ }
2032
+
2033
+ const onEvalError = (error: unknown): Node | undefined => {
2034
+ // Most node failures are semantic failures and should throw immediately.
2035
+ // Retry scheduling is reserved for StyleImport ordering/interpolation cases.
2036
+ if (rule.type !== 'StyleImport') {
2037
+ throw error;
2038
+ }
2039
+ // Final pass: no retries remain.
2040
+ if (p === Priority.None) {
2041
+ throw error;
2042
+ }
2043
+
2044
+ // Only retry when the import path itself couldn't be resolved
2045
+ // (e.g. @import "@{theme}/file" where @theme isn't available yet).
2046
+ // Path resolution is cheap (no cloning). Content evaluation errors
2047
+ // (after cloning the import tree) are never retried — each retry
2048
+ // would re-clone the entire tree, causing memory blowup.
2049
+ const isPathError = error instanceof Error && (error as any)._isPathResolutionError;
2050
+ if (!isPathError) {
2051
+ throw error;
2052
+ }
2053
+
2054
+ // Retry policy:
2055
+ // 1) first failure at a priority -> retry once at same priority
2056
+ // 2) second+ failure at that priority -> step down one level
2057
+ const failures = countFailure(rule, p);
2058
+ const nextPriority = failures === 1 ? p : (p - 1) as Priority;
2059
+ enqueueRetry(nextPriority, [idx, rule], rule);
2060
+ return;
2061
+ };
2062
+ const tryStepResult = (): MaybePromise<Node | undefined> => {
2063
+ try {
2064
+ const result = rule.eval(context);
2065
+ if (isThenable(result)) {
2066
+ return (result as Promise<Node>).catch(onEvalError);
2067
+ }
2068
+ return result as Node;
2069
+ } catch (error) {
2070
+ return onEvalError(error);
2071
+ }
2072
+ };
2073
+ const stepResult = pipe(
2074
+ tryStepResult,
2075
+ (result: Node | undefined) => {
2076
+ // Undefined means we re-queued this node for retry.
2077
+ if (result === undefined) {
2078
+ return;
2079
+ }
2080
+ scheduledPriority.delete(rule);
2081
+ // Apply the result
2082
+ if (result !== rule) {
2083
+ rules._setChildAt(idx, result, context, false);
2084
+ queue[q] = [idx, result];
2085
+ // If a StyleImport evaluated to Rules, register them in the parent's _rulesSet
2086
+ // so variables from the import can be found by the parent
2087
+ // Also register Rules from Call results (mixin calls) in the same way
2088
+ if (isNode(result, N.Rules)) {
2089
+ // Set the index of the imported Rules to the StyleImport's index
2090
+ // so we can compare Rules indices when determining which variable was declared later
2091
+ setIndex(result, idx, context);
2092
+ rules.adopt(result, context);
2093
+ rules.registerNode(result, {
2094
+ rulesVisibility: result.options.rulesVisibility,
2095
+ readonly: result.options.readonly
2096
+ }, context);
2097
+ if (result.sourceNode?.type === 'StyleImport') {
2098
+ result.getRegistry('declaration')?.indexPendingItems();
2099
+ result.getRegistry('mixin')?.indexPendingItems();
2100
+ }
2101
+ } else {
2102
+ // For non-Rules results, adopt them to set up parent chain
2103
+ rules.adopt(result, context);
2104
+ }
2105
+ }
2106
+ if (result.hoistToRoot) {
2107
+ rulesToHoist = true;
2108
+ }
2109
+ return;
2110
+ }
2111
+ );
2112
+ // If stepResult is a thenable, propagate any errors
2113
+ if (isThenable(stepResult)) {
2114
+ return stepResult;
2115
+ }
2116
+ return;
2117
+ };
2118
+ const runFromIndex = (q: number): MaybePromise<void> => {
2119
+ if (q >= queue.length) {
2120
+ return;
2121
+ }
2122
+ const step = runSingleEntry(q);
2123
+ if (isThenable(step)) {
2124
+ return (step as Promise<void>).then(() => runFromIndex(q + 1));
2125
+ }
2126
+ return runFromIndex(q + 1);
2127
+ };
2128
+ return runFromIndex(0);
2129
+ };
2130
+ const phaseRun = serialForEach(priorities, runPriority);
2131
+
2132
+ if (isThenable(phaseRun)) {
2133
+ return (phaseRun as Promise<void>).then(() => {
2134
+ return rulesToHoist;
2135
+ }).catch((error) => {
2136
+ throw error;
2137
+ });
2138
+ }
2139
+ return rulesToHoist;
2140
+ }
2141
+
2142
+ /**
2143
+ * Coalesce assignment-normalized declaration chains in one stage after evaluation.
2144
+ * This handles both in-scope merges and merges that span call-produced Rules blocks.
2145
+ */
2146
+ private _coalesceMergedDeclarations(rules: Rules, context?: Context): void {
2147
+ const getDeclValue = (node: Declaration): Node => node.getCurrentValue(context);
2148
+ const getDeclImportant = (node: Declaration): Node | undefined => node.getCurrentImportant(context);
2149
+ const getDeclName = (node: Declaration): string => {
2150
+ const name = node.getCurrentName(context);
2151
+ return String(name?.valueOf?.() ?? name);
2152
+ };
2153
+ const getDeclAssign = (node: Declaration): string => {
2154
+ const options = node.options;
2155
+ return String(options?.normalizedFromAssign ?? '');
2156
+ };
2157
+ const setDeclValue = (node: Declaration, value: Node): void => {
2158
+ node.setCurrentValue(value, context);
2159
+ };
2160
+ const setDeclImportant = (node: Declaration, value: Node | undefined): void => {
2161
+ node.setCurrentImportant(value as Declaration['important'], context);
2162
+ };
2163
+ const removeVisibleFlag = (node: Node): void => {
2164
+ if (context) {
2165
+ node._removeFlag(F_VISIBLE, context);
2166
+ return;
2167
+ }
2168
+ node.removeFlag(F_VISIBLE);
2169
+ };
2170
+ const isMergedAssign = (assign: unknown): boolean => (
2171
+ assign === '+:' || assign === '&,:' || assign === '&_:'
2172
+ );
2173
+ const cloneMergeValuePart = (value: Node): Node => {
2174
+ return context
2175
+ ? value.clone(false, undefined, context)
2176
+ : value.clone(false);
2177
+ };
2178
+ const collectAddMergeParts = (value: Node): Node[] => {
2179
+ if (isNode(value, N.Nil)) {
2180
+ return [];
2181
+ }
2182
+ if (isNode(value, N.List)) {
2183
+ return value.get('value', context).map(part => cloneMergeValuePart(part as Node));
2184
+ }
2185
+ return [cloneMergeValuePart(value)];
2186
+ };
2187
+ const rebaseLinearMergedValue = (anchor: Declaration, value: Node, assign: string): Node | undefined => {
2188
+ if (assign === '&_:') {
2189
+ if (!isNode(value, N.Sequence)) {
2190
+ return undefined;
2191
+ }
2192
+ const parts = value.get('value', context);
2193
+ if (
2194
+ parts.length < 2
2195
+ || !isNode(parts[0]!, N.Reference)
2196
+ || parts[0]!.options?.resolution !== 'linear'
2197
+ ) {
2198
+ return undefined;
2199
+ }
2200
+ return spaced([
2201
+ getDeclValue(anchor),
2202
+ ...parts.slice(1).map(part => cloneMergeValuePart(part as Node))
2203
+ ]);
2204
+ }
2205
+ if (!isNode(value, N.List)) {
2206
+ return undefined;
2207
+ }
2208
+ const parts = value.get('value', context);
2209
+ if (
2210
+ parts.length < 2
2211
+ || !isNode(parts[0]!, N.Reference)
2212
+ || parts[0]!.options?.resolution !== 'linear'
2213
+ ) {
2214
+ return undefined;
2215
+ }
2216
+ return new List([
2217
+ getDeclValue(anchor),
2218
+ ...parts.slice(1).map(part => cloneMergeValuePart(part as Node))
2219
+ ]);
2220
+ };
2221
+ const appendMergedValue = (anchor: Declaration, value: Node, assign: string): Node => {
2222
+ if (assign === '&_:') {
2223
+ return isNode(value, N.Sequence)
2224
+ ? spaced([getDeclValue(anchor), ...value.get('value', context).map(part => cloneMergeValuePart(part as Node))])
2225
+ : spaced([getDeclValue(anchor), cloneMergeValuePart(value)]);
2226
+ }
2227
+ return isNode(value, N.List)
2228
+ ? new List([
2229
+ ...collectAddMergeParts(getDeclValue(anchor)),
2230
+ ...value.get('value', context).map(part => cloneMergeValuePart(part as Node))
2231
+ ])
2232
+ : new List([
2233
+ ...collectAddMergeParts(getDeclValue(anchor)),
2234
+ cloneMergeValuePart(value)
2235
+ ]);
2236
+ };
2237
+ const isDeclarationOnlyRules = (node: Node): node is Rules => (
2238
+ isNode(node, N.Rules)
2239
+ && node._getRenderChildren(context).length > 0
2240
+ && node._getRenderChildren(context).every(child => isNode(child, N.Declaration | N.Comment))
2241
+ );
2242
+ const composeMergedValue = (decl: Declaration, prior: Declaration, assign: string): void => {
2243
+ if (!isNode(decl, N.Declaration) || !isNode(prior, N.Declaration)) {
2244
+ return;
2245
+ }
2246
+ const priorValue = getDeclValue(prior);
2247
+ const nextValue = getDeclValue(decl);
2248
+ setDeclValue(decl, assign === '&_:'
2249
+ ? spaced([priorValue, nextValue])
2250
+ : new List([priorValue, nextValue]));
2251
+ if (!getDeclImportant(decl) && getDeclImportant(prior)) {
2252
+ setDeclImportant(decl, getDeclImportant(prior));
2253
+ }
2254
+ };
2255
+ const normalizeMergedDeclarationValue = (node: Node): void => {
2256
+ if (!isNode(node, N.Declaration)) {
2257
+ return;
2258
+ }
2259
+ const current = getDeclValue(node);
2260
+ if (!isNode(current, N.List) || current.get('value').length === 0) {
2261
+ return;
2262
+ }
2263
+ const [first, ...rest] = current.get('value');
2264
+ const isEmptyPlaceholder = Boolean(
2265
+ first
2266
+ && (
2267
+ isNode(first, N.Nil)
2268
+ || (isNode(first, N.List) && first.get('value').length === 0)
2269
+ )
2270
+ );
2271
+ if (!isEmptyPlaceholder) {
2272
+ return;
2273
+ }
2274
+ if (rest.length === 0) {
2275
+ setDeclValue(node, new Nil());
2276
+ return;
2277
+ }
2278
+ if (rest.length === 1) {
2279
+ setDeclValue(node, rest[0]!);
2280
+ return;
2281
+ }
2282
+ setDeclValue(node, new List(rest));
2283
+ };
2284
+
2285
+ const lastVisibleByName = new Map<string, Node>();
2286
+ const mergedAnchorByName = new Map<string, Node>();
2287
+ const stream: Node[] = [];
2288
+
2289
+ for (const node of rules._getRenderChildren(context)) {
2290
+ if (isNode(node, N.Declaration)) {
2291
+ stream.push(node);
2292
+ continue;
2293
+ }
2294
+ if (isDeclarationOnlyRules(node)) {
2295
+ for (const child of node._getRenderChildren(context)) {
2296
+ if (isNode(child, N.Declaration)) {
2297
+ stream.push(child);
2298
+ }
2299
+ }
2300
+ }
2301
+ }
2302
+
2303
+ for (const node of stream) {
2304
+ if (!isNode(node, N.Declaration)) {
2305
+ continue;
2306
+ }
2307
+ const name = getDeclName(node);
2308
+ const assign = getDeclAssign(node);
2309
+ const merged = isMergedAssign(assign);
2310
+
2311
+ if (!merged) {
2312
+ mergedAnchorByName.delete(name);
2313
+ if (isVisibleInContext(node, context)) {
2314
+ lastVisibleByName.set(name, node);
2315
+ }
2316
+ continue;
2317
+ }
2318
+ normalizeMergedDeclarationValue(node);
2319
+
2320
+ const prior = lastVisibleByName.get(name);
2321
+ if (
2322
+ prior
2323
+ && prior !== node
2324
+ && (
2325
+ context
2326
+ ? getCurrentParentNode(prior, context) !== getCurrentParentNode(node, context)
2327
+ : prior.parent !== node.parent
2328
+ )
2329
+ ) {
2330
+ composeMergedValue(node, prior, assign);
2331
+ }
2332
+
2333
+ const existingAnchor = mergedAnchorByName.get(name);
2334
+ if (existingAnchor && existingAnchor !== node && isNode(existingAnchor, N.Declaration)) {
2335
+ // @todo — copy(true) was used here for comment suppression (stripping
2336
+ // pre/post comments from merged values). Need a position-aware
2337
+ // alternative: either a serialization-time comment suppression flag
2338
+ // or field patches on pre/post.
2339
+ const currentValue = getDeclValue(node);
2340
+ const nextAnchorValue = assign === '+:'
2341
+ ? new List([
2342
+ ...collectAddMergeParts(getDeclValue(existingAnchor)),
2343
+ ...collectAddMergeParts(currentValue)
2344
+ ])
2345
+ : rebaseLinearMergedValue(existingAnchor, currentValue, assign)
2346
+ ?? appendMergedValue(existingAnchor, currentValue, assign);
2347
+ setDeclValue(
2348
+ existingAnchor,
2349
+ nextAnchorValue
2350
+ );
2351
+ if (!getDeclImportant(existingAnchor) && getDeclImportant(node)) {
2352
+ setDeclImportant(existingAnchor, getDeclImportant(node));
2353
+ }
2354
+ removeVisibleFlag(node);
2355
+ if (isVisibleInContext(existingAnchor, context)) {
2356
+ lastVisibleByName.set(name, existingAnchor);
2357
+ }
2358
+ continue;
2359
+ }
2360
+
2361
+ mergedAnchorByName.set(name, node);
2362
+ if (isVisibleInContext(node, context)) {
2363
+ lastVisibleByName.set(name, node);
2364
+ }
2365
+ }
2366
+ }
2367
+
2368
+ /**
2369
+ * Normalize call-produced declaration-only Rules ordering so declarations
2370
+ * emitted from late-evaluated calls (e.g. each/$for) appear before nested
2371
+ * rulesets/at-rules in the same parent Rules container.
2372
+ *
2373
+ * This runs after queue evaluation to avoid mutating rule indices mid-eval.
2374
+ */
2375
+ private _normalizeCallDeclarationRulesOrder(rules: Rules, context?: Context): void {
2376
+ const children = rules._getRenderChildren(context);
2377
+ const firstNestedIdx = children.findIndex(n => isNode(n, N.Ruleset | N.AtRule));
2378
+ if (firstNestedIdx < 0) {
2379
+ return;
2380
+ }
2381
+ const beforeNested = children.slice(0, firstNestedIdx);
2382
+ const afterNested = children.slice(firstNestedIdx);
2383
+ const shouldMove = (n: Node) => {
2384
+ const sourceParent = context
2385
+ ? getSourceParent(n, context)
2386
+ : n.sourceParent;
2387
+ if (
2388
+ !isNode(n, N.Rules)
2389
+ || !isNode(sourceParent, N.Call)
2390
+ || n._getRenderChildren(context).length === 0
2391
+ || !n._getRenderChildren(context).every(child => isNode(child, N.Declaration | N.Comment))
2392
+ ) {
2393
+ return false;
2394
+ }
2395
+ const sourceName = (sourceParent as any).name;
2396
+ // Keep mixin-call declaration blocks in source order relative to nested rulesets.
2397
+ if (
2398
+ isNode(sourceName, N.Reference)
2399
+ && (sourceName.options?.type === 'mixin'
2400
+ || sourceName.options?.type === 'mixin-ruleset'
2401
+ || sourceName.options?.type === 'ruleset')
2402
+ ) {
2403
+ return false;
2404
+ }
2405
+ return true;
2406
+ };
2407
+ const moved = afterNested.filter(shouldMove);
2408
+ if (moved.length === 0) {
2409
+ return;
2410
+ }
2411
+ const remainder = afterNested.filter(n => !shouldMove(n));
2412
+ rules._setChildren([...beforeNested, ...moved, ...remainder], context, false);
2413
+ }
2414
+
2415
+ private _evaluateQueuedTopImports(context: Context): MaybePromise<void> {
2416
+ const queued = context.topImports;
2417
+ if (!queued?.length) {
2418
+ return;
2419
+ }
2420
+
2421
+ const evaluated: Node[] = [];
2422
+ const evaluateOne = (importRule: Node): MaybePromise<void> => {
2423
+ if (!isNode(importRule, N.AtRule)) {
2424
+ evaluated.push(importRule);
2425
+ return;
2426
+ }
2427
+
2428
+ const evaldImport = importRule.clone(false, undefined, context);
2429
+ evaldImport.preEvaluated = true;
2430
+ const out = evaldImport.eval(context);
2431
+ if (isThenable(out)) {
2432
+ return (out as Promise<Node | Nil>).then((result) => {
2433
+ if (!(result instanceof Nil)) {
2434
+ evaluated.push(result);
2435
+ }
2436
+ });
2437
+ }
2438
+ if (!(out instanceof Nil)) {
2439
+ evaluated.push(out as Node);
2440
+ }
2441
+ };
2442
+
2443
+ const out = serialForEach(queued, evaluateOne);
2444
+ if (isThenable(out)) {
2445
+ return (out as Promise<void>).then(() => {
2446
+ context.topImports = evaluated;
2447
+ });
2448
+ }
2449
+ context.topImports = evaluated;
2450
+ }
2451
+
2452
+ /**
2453
+ * After preEval: ensure root on extend stack, build eval queue, run evaluation.
2454
+ * Used by evalNode so that when eval() is called without preEval (e.g. jess compile()),
2455
+ * we still have all rulesets registered and root set for extend lookups.
2456
+ */
2457
+ private _afterPreEvalStep(rules: Rules, context: Context): MaybePromise<{ rules: Rules; rulesToHoist: boolean }> {
2458
+ if (
2459
+ rules === context.root
2460
+ && rules.renderKey === CANONICAL
2461
+ && !rules.hasFlag(F_STATIC)
2462
+ && (context.renderKey === undefined || context.renderKey === CANONICAL)
2463
+ && getCurrentParentNode(rules, context) === undefined
2464
+ ) {
2465
+ const evalRoot = rules.createShallowBodyWrapper(context, EVAL);
2466
+ context.root = evalRoot;
2467
+ context.rulesContext = evalRoot;
2468
+ context.renderKey = evalRoot.renderKey;
2469
+ rules = evalRoot;
2470
+ }
2471
+
2472
+ const isMainRoot = rules === context.root;
2473
+ if (isMainRoot && context.extendRoots.extendRootStack.length === 0) {
2474
+ if (!context.extendRoots.root) {
2475
+ context.extendRoots.registerRoot(rules);
2476
+ }
2477
+ context.extendRoots.pushExtendRoot(rules);
2478
+ }
2479
+ if (isEvaluated(rules, context)) {
2480
+ return { rules, rulesToHoist: false };
2481
+ }
2482
+ if (rules === context.root) {
2483
+ const map = new WeakMap<Ruleset, number>();
2484
+ context.documentOrderByRuleset = map;
2485
+ this._assignDocumentOrderDepthFirst(rules, map, { value: 0 }, context);
2486
+ }
2487
+ const evalQueue = this._buildEvalQueue(rules, context);
2488
+ const maybeHoist = this._evaluateQueue(rules, evalQueue, context);
2489
+ if (isThenable(maybeHoist)) {
2490
+ return (maybeHoist as Promise<boolean>).then((rulesToHoist) => {
2491
+ const finalize = () => {
2492
+ this._normalizeCallDeclarationRulesOrder(rules, context);
2493
+ this._coalesceMergedDeclarations(rules, context);
2494
+ return {
2495
+ rules,
2496
+ rulesToHoist
2497
+ };
2498
+ };
2499
+ if (rules === context.root && context.topImports?.length) {
2500
+ const maybeEvalTopImports = this._evaluateQueuedTopImports(context);
2501
+ if (isThenable(maybeEvalTopImports)) {
2502
+ return (maybeEvalTopImports as Promise<void>).then(finalize);
2503
+ }
2504
+ }
2505
+ return finalize();
2506
+ });
2507
+ }
2508
+ if (rules === context.root && context.topImports?.length) {
2509
+ const maybeEvalTopImports = this._evaluateQueuedTopImports(context);
2510
+ if (isThenable(maybeEvalTopImports)) {
2511
+ return (maybeEvalTopImports as Promise<void>).then(() => {
2512
+ this._normalizeCallDeclarationRulesOrder(rules, context);
2513
+ this._coalesceMergedDeclarations(rules, context);
2514
+ return { rules, rulesToHoist: maybeHoist as boolean };
2515
+ });
2516
+ }
2517
+ }
2518
+ this._normalizeCallDeclarationRulesOrder(rules, context);
2519
+ this._coalesceMergedDeclarations(rules, context);
2520
+ return { rules, rulesToHoist: maybeHoist as boolean };
2521
+ }
2522
+
2523
+ override evalNode(context: Context): MaybePromise<this> {
2524
+ const saved = this._snapshotContext(context);
2525
+ context.rulesEvalStack.push(this.sourceNode as Rules);
2526
+ const restoreContextOnError = () => {
2527
+ context.rulesContext = saved.rulesContext;
2528
+ context.renderKey = saved.renderKey;
2529
+ if (saved.treeRoot !== undefined) {
2530
+ context.treeRoot = saved.treeRoot;
2531
+ }
2532
+ if (saved.root !== undefined) {
2533
+ context.root = saved.root;
2534
+ }
2535
+ const currentLength = context.extendRoots.extendRootStack.length;
2536
+ if (saved.extendRootStackLength !== undefined && currentLength > saved.extendRootStackLength) {
2537
+ while (context.extendRoots.extendRootStack.length > saved.extendRootStackLength) {
2538
+ context.extendRoots.popExtendRoot();
2539
+ }
2540
+ }
2541
+ if (context.rulesEvalStack[context.rulesEvalStack.length - 1] === (this.sourceNode as Rules)) {
2542
+ context.rulesEvalStack.pop();
2543
+ }
2544
+ context.depth--;
2545
+ };
2546
+ let pipeResult: MaybePromise<this>;
2547
+ try {
2548
+ pipeResult = pipe(
2549
+ () => {
2550
+ this._setupContextForRules(context, this);
2551
+ // Run preEval first if not yet run (e.g. when jess compile() calls eval() without preEval).
2552
+ // preEval registers the root and all nested rulesets so extend lookups find targets in child roots (e.g. .ma inside @media).
2553
+ const runPreEvalIfNeeded = (rules: Rules): MaybePromise<Rules> => {
2554
+ if (rules.preEvaluated) {
2555
+ return rules;
2556
+ }
2557
+ const result = rules.preEval(context);
2558
+ return isThenable(result) ? (result as Promise<Rules>) : result;
2559
+ };
2560
+ const rulesAfterPreEval = runPreEvalIfNeeded(this);
2561
+ const afterPreEval = (rules: Rules) => {
2562
+ // When we're the outermost Rules, use the tree we're evaling as root (may differ from context.root set in getTree, or be preEval's clone).
2563
+ if (context.rulesEvalStack.length === 1) {
2564
+ context.root = rules;
2565
+ }
2566
+ return this._afterPreEvalStep(rules, context);
2567
+ };
2568
+ if (isThenable(rulesAfterPreEval)) {
2569
+ return (rulesAfterPreEval as Promise<Rules>).then(afterPreEval);
2570
+ }
2571
+ return afterPreEval(rulesAfterPreEval as Rules);
2572
+ },
2573
+ ({ rules }: { rules: Rules; rulesToHoist: boolean }) => {
2574
+ // Note: Rulesets from imported Rules are already registered to their own treeRoot
2575
+ // during preEval when the imported Rules node is evaluated. The extend search
2576
+ // loops through allRoots, so it should find them. The _searchRulesChildrenForRulesets
2577
+ // method in RulesetRegistry also searches imported Rules' registries.
2578
+
2579
+ // After all evaluation stages, check if any variables in the current Rules
2580
+ // shadow readonly variables from imported Rules (compose type) at the same level
2581
+ // Only check direct children of the Rules node, not nested variables (e.g., inside rulesets)
2582
+ if (rules.rulesSet.length > 0) {
2583
+ for (const entry of rules.rulesSet) {
2584
+ if (entry.readonly) {
2585
+ const importedVars = Registries
2586
+ .getDirectDeclarationsByKey(entry.node, undefined, context)
2587
+ .filter((decl): decl is VarDeclaration => isNode(decl, N.VarDeclaration));
2588
+ for (const decl of importedVars) {
2589
+ const key = decl.get('name').toString();
2590
+ const currentDeclarations = Registries.getDirectDeclarationsByKey(rules, key, context);
2591
+ for (const currentDecl of currentDeclarations) {
2592
+ if (isNode(currentDecl, N.VarDeclaration) && !currentDecl.options?.setDefined) {
2593
+ // Only throw if the variable is a direct child of the Rules node (same level)
2594
+ // Nested variables (e.g., inside rulesets) are allowed to shadow
2595
+ if (getCurrentParentNode(currentDecl, context) === rules) {
2596
+ throw new ReferenceError(`"${key}" is readonly`);
2597
+ }
2598
+ }
2599
+ }
2600
+ }
2601
+ }
2602
+ }
2603
+ }
2604
+
2605
+ // Check if we're at the outermost level BEFORE restoring context
2606
+ // Only process extends at the TRUE outermost root (context.root)
2607
+ // This ensures extends are processed AFTER all evaluation completes,
2608
+ // including imports and nested Rules
2609
+ const isOutermost = rules === context.root;
2610
+
2611
+ if (isOutermost) {
2612
+ processExtends(context);
2613
+ }
2614
+ /** Restore contexts */
2615
+ context.rulesContext = saved.rulesContext;
2616
+ context.renderKey = saved.renderKey;
2617
+ // Only restore context.treeRoot if saved.treeRoot is defined and we're not at the outermost level
2618
+ // If saved.treeRoot is undefined, it means we're at the outermost level, so keep context.treeRoot as is
2619
+ // This ensures extends evaluated during selector evaluation can still access the correct treeRoot
2620
+ if (saved.treeRoot !== undefined && !isOutermost) {
2621
+ context.treeRoot = saved.treeRoot;
2622
+ }
2623
+ // Only restore context.root if we're not at the outermost level (where it was originally set)
2624
+ // If saved.root is undefined, it means we're at the outermost level, so keep context.root as is
2625
+ if (saved.root !== undefined && !isOutermost) {
2626
+ context.root = saved.root;
2627
+ }
2628
+ // Restore extend root stack to its original length (if we're not the main root)
2629
+ // The main root manages its own push/pop, but nested Rules should restore the stack
2630
+ if (!isOutermost && saved.extendRootStackLength !== undefined) {
2631
+ const currentLength = context.extendRoots.extendRootStack.length;
2632
+ if (currentLength > saved.extendRootStackLength) {
2633
+ // Pop any extend roots that were pushed during this Rules evaluation
2634
+ while (context.extendRoots.extendRootStack.length > saved.extendRootStackLength) {
2635
+ context.extendRoots.popExtendRoot();
2636
+ }
2637
+ }
2638
+ }
2639
+ // Pop extend root if we pushed it (check if this is still the root)
2640
+ if (rules === context.root) {
2641
+ context.extendRoots.popExtendRoot();
2642
+ }
2643
+ context.rulesEvalStack.pop();
2644
+ context.depth--;
2645
+ return rules;
2646
+ }
2647
+ ) as MaybePromise<this>;
2648
+ } catch (error) {
2649
+ restoreContextOnError();
2650
+ throw error;
2651
+ }
2652
+ if (isThenable(pipeResult)) {
2653
+ return (pipeResult as Promise<this>).catch((error) => {
2654
+ restoreContextOnError();
2655
+ throw error;
2656
+ });
2657
+ }
2658
+ return pipeResult as MaybePromise<this>;
2659
+ }
2660
+ }
2661
+
2662
+ export const rules = defineType(Rules, 'Rules');
2663
+
2664
+ type EvalQueueMap = Map<Priority, Array<[number, Node]>>;
2665
+
2666
+ /**
2667
+ * @todo - Will need lots of massaging, to resolve things like
2668
+ * mixins which rely on variables which have interpolated names,
2669
+ * and variables with interpolated names that rely on mixins.
2670
+ *
2671
+ * @note - Registration of declaration names and mixins / selectors
2672
+ * should have already happened in pre-eval.
2673
+ */
2674
+ const NodeTypeToPriority = new Map([
2675
+ /** First, resolve imports */
2676
+ ['StyleImport', Priority.Highest],
2677
+ /** Then, resolve calls */
2678
+ ['Call', Priority.High],
2679
+ /** Then, resolve declarations */
2680
+ ['VarDeclaration', Priority.Medium],
2681
+ ['Declaration', Priority.Medium],
2682
+ /** Then... */
2683
+ ['Mixin', Priority.Low],
2684
+ ['Ruleset', Priority.Low],
2685
+ /** Extend should evaluate at the same priority as Ruleset to ensure it evaluates before nested rulesets */
2686
+ ['Extend', Priority.Low],
2687
+ /** AtRule (e.g., @media) should evaluate at the same priority as Ruleset to preserve source order */
2688
+ ['AtRule', Priority.Low]
2689
+ /** Then, everything else? */
2690
+ ]);
2691
+
2692
+ // const TypeToNodeType = new Map([
2693
+ // ['Mixin', NodeType.MIXIN],
2694
+ // ['Ruleset', NodeType.RULESET],
2695
+ // ['Declaration', NodeType.PROPERTY],
2696
+ // ['VarDeclaration', NodeType.VARIABLE],
2697
+ // ['Rules', NodeType.RULES]
2698
+ // ])
2699
+
2700
+ // export const enum NodeTypeIndex {
2701
+ // NONE = 0b000000,
2702
+ // MIXIN = 0b000001,
2703
+ // RULESET = 0b000010,
2704
+ // MIXIN_OR_RULESET = 0b000011,
2705
+ // PROPERTY = 0b000100,
2706
+ // VARIABLE = 0b001000,
2707
+ // VAR_OR_PROP = 0b001100,
2708
+ // /**
2709
+ // * Variables and mixins can leak
2710
+ // */
2711
+ // LEAKY_RULES = 0b010000,
2712
+ // /** @note - Properties and rulesets are always visible. */
2713
+ // PRIVATE_RULES = 0b100000,
2714
+ // RULES = 0b110000
2715
+ // }
2716
+
2717
+ // type IndexKey = `${NodeType}${string}`
2718
+
2719
+ interface RulesEntry {
2720
+ node: Rules;
2721
+ rulesVisibility?: RulesOptions['rulesVisibility'];
2722
+ /**
2723
+ * These are from use, from, and import statements. Can't be assigned with $$
2724
+ * (verify that this is not possible with SCSS).
2725
+ */
2726
+ readonly?: boolean;
2727
+ }
2728
+
2729
+ /**
2730
+ * Right now, the only nodes that can be registered to the scope for lookups
2731
+ */
2732
+ // type ScopeNodes = Declaration | VarDeclaration | Mixin | Ruleset | Rules
2733
+ export type MixinEntry = Mixin | Ruleset;
2734
+
2735
+ /**
2736
+ * Returns a plain JS function for calling a set of mixins
2737
+ *
2738
+ * This is in the same file as Rules to avoid circular dependencies.
2739
+ *
2740
+ * @note this will be called as a result after a mixin find is executed.
2741
+ */
2742
+ export function getFunctionFromMixins(mixins: MixinEntry | MixinEntry[]) {
2743
+ let mixinArr = isArray(mixins) ? mixins : [mixins];
2744
+ /**
2745
+ * This will be called by a mixin call or by JavaScript
2746
+ *
2747
+ * @note - Mixins resolve to async functions because they
2748
+ * can contain dynamic imports.
2749
+ */
2750
+ async function returnFunc(this: unknown, ...args: any[]): Promise<Rules | Record<string, string>>;
2751
+ async function returnFunc(this: Context, ...args: any[]): Promise<Rules>;
2752
+ async function returnFunc(this: Context | unknown, ...args: any[]) {
2753
+ // When called via callWithContext, 'this' is functionThis, not Context
2754
+ // We need to extract the context from functionThis or use a fallback
2755
+ let thisContext: Context;
2756
+
2757
+ if (this instanceof Context) {
2758
+ thisContext = this;
2759
+ } else if (this && typeof this === 'object' && 'context' in this) {
2760
+ // This is functionThis from callWithContext
2761
+ thisContext = (this as any).context;
2762
+ } else {
2763
+ thisContext = new Context();
2764
+ }
2765
+ let caller = thisContext.caller;
2766
+ const callerSourceNode = (caller as any)?.name instanceof Node
2767
+ ? (caller as any).name
2768
+ : caller;
2769
+ let sourceParent = callerSourceNode
2770
+ ? getSourceParent(callerSourceNode, thisContext)
2771
+ : undefined;
2772
+ if (sourceParent && isNode(sourceParent, N.Reference | N.Call)) {
2773
+ sourceParent = caller;
2774
+ }
2775
+ sourceParent ??= caller;
2776
+ const invocationParent = thisContext.rulesContext
2777
+ ?? (caller ? getParent(caller, thisContext) : undefined);
2778
+
2779
+ const nodeArgs = await evaluateMixinArgs(args, caller, thisContext);
2780
+ const mixinCandidates = await matchMixinCandidates(mixinArr, nodeArgs, caller, sourceParent, thisContext);
2781
+ const { evalCandidates, hasDefault } = filterAndSortMixinEvalCandidates(mixinCandidates, thisContext);
2782
+
2783
+ const outputRules: Rules[] = [];
2784
+ const candidateOutputOpts: EvaluateCandidateOutputOptions = {
2785
+ sourceParent,
2786
+ invocationParent,
2787
+ restrictMixinOutputLookup: thisContext.leakyRules !== true,
2788
+ outputRules,
2789
+ getCandidateParent: node => getCandidateParent(node, thisContext)
2790
+ };
2791
+
2792
+ const output = await dispatchMixinEvalCandidates({
2793
+ evalCandidates,
2794
+ hasDefault,
2795
+ nodeArgs,
2796
+ sourceParent,
2797
+ invocationParent,
2798
+ caller,
2799
+ restrictMixinOutputLookup: candidateOutputOpts.restrictMixinOutputLookup,
2800
+ outputRules,
2801
+ getCandidateParent: candidateOutputOpts.getCandidateParent,
2802
+ evaluateCandidateOutput: (candidate, rules, outerRules, params) =>
2803
+ evaluateCandidateOutput(candidate, rules, outerRules, params, thisContext, candidateOutputOpts)
2804
+ }, thisContext);
2805
+
2806
+ return finalizeMixinInvocationReturn(output, this instanceof Context ? this : thisContext);
2807
+ }
2808
+
2809
+ return returnFunc;
2810
+ }
2811
+
2812
+ /**
2813
+ * Direct mixin invocation — calls dispatch primitives without the
2814
+ * getFunctionFromMixins → callWithContext → returnFunc indirection.
2815
+ *
2816
+ * The result is already fully evaluated (each candidate's body was
2817
+ * evaluated before being assembled into the return Rules). Callers must NOT
2818
+ * re-evaluate the result.
2819
+ */
2820
+ export async function evalMixinDirect(
2821
+ context: Context,
2822
+ mixins: MixinEntry | MixinEntry[],
2823
+ args: List<Node> | undefined
2824
+ ): Promise<Rules | Nil> {
2825
+ const mixinArr = isArray(mixins) ? mixins : [mixins];
2826
+ const caller = context.caller;
2827
+ const callerSourceNode = caller && isNode(caller, N.Call) && caller.get('name') instanceof Node
2828
+ ? caller.get('name')
2829
+ : caller;
2830
+ let sourceParent = callerSourceNode
2831
+ ? getSourceParent(callerSourceNode as Node, context)
2832
+ : undefined;
2833
+ if (sourceParent && isNode(sourceParent, N.Reference | N.Call)) {
2834
+ sourceParent = caller;
2835
+ }
2836
+ sourceParent ??= caller;
2837
+ const invocationParent = context.rulesContext
2838
+ ?? (caller ? getParent(caller as Node, context) : undefined);
2839
+
2840
+ const nodeArgs = await evaluateMixinArgs(
2841
+ args ? [...args.get('value', context)] : [],
2842
+ caller,
2843
+ context
2844
+ );
2845
+ const mixinCandidates = await matchMixinCandidates(
2846
+ mixinArr, nodeArgs, caller, sourceParent, context
2847
+ );
2848
+ const { evalCandidates, hasDefault } = filterAndSortMixinEvalCandidates(
2849
+ mixinCandidates, context
2850
+ );
2851
+
2852
+ const outputRules: Rules[] = [];
2853
+ if (process.env.JESS_DEBUG_LOCK === 'throw-direct') {
2854
+ const callerName = caller && isNode(caller, N.Call) && (caller as Call).name instanceof Node
2855
+ ? (caller as Call).name
2856
+ : undefined;
2857
+ const callerKey = isNode(callerName, N.Reference)
2858
+ ? String(callerName.key?.valueOf?.() ?? '')
2859
+ : '';
2860
+ if (callerKey.includes('inner-locked-mixin')) {
2861
+ throw new Error(`[lock-direct] ${JSON.stringify({
2862
+ callerKey,
2863
+ mixinCount: mixinArr.length,
2864
+ matchedCount: mixinCandidates.length,
2865
+ evalCandidateCount: evalCandidates.length,
2866
+ hasDefault,
2867
+ sourceParent: sourceParent?.type,
2868
+ invocationParent: invocationParent?.type,
2869
+ matchNames: mixinCandidates.map(candidate => String((candidate as Mixin).get?.('name')?.valueOf?.() ?? candidate.type))
2870
+ })}`);
2871
+ }
2872
+ }
2873
+ const candidateOutputOpts: EvaluateCandidateOutputOptions = {
2874
+ sourceParent,
2875
+ invocationParent,
2876
+ restrictMixinOutputLookup: context.leakyRules !== true,
2877
+ outputRules,
2878
+ getCandidateParent: node => getCandidateParent(node, context)
2879
+ };
2880
+
2881
+ const output = await dispatchMixinEvalCandidates({
2882
+ evalCandidates,
2883
+ hasDefault,
2884
+ nodeArgs,
2885
+ sourceParent,
2886
+ invocationParent,
2887
+ caller,
2888
+ restrictMixinOutputLookup: candidateOutputOpts.restrictMixinOutputLookup,
2889
+ outputRules,
2890
+ getCandidateParent: candidateOutputOpts.getCandidateParent,
2891
+ evaluateCandidateOutput: (candidate, rules, outerRules, params) =>
2892
+ evaluateCandidateOutput(candidate, rules, outerRules, params, context, candidateOutputOpts)
2893
+ }, context);
2894
+
2895
+ return finalizeMixinInvocationReturn(output, context) as Rules | Nil;
2896
+ }