@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
package/lib/tree/rules.js DELETED
@@ -1,2359 +0,0 @@
1
- import { Node, defineType, F_STATIC, F_VISIBLE } from './node.js';
2
- import { Context } from '../context.js';
3
- import { isNode } from './util/is-node.js';
4
- import { comparePosition } from './util/compare.js';
5
- import { cast } from './util/cast.js';
6
- import { spaced, Sequence } from './sequence.js';
7
- import { getPrintOptions } from './util/print.js';
8
- import { atIndex } from './util/collections.js';
9
- import { Bool } from './bool.js';
10
- import * as Registries from './util/registry-utils.js';
11
- import { processExtends } from './util/extend-roots.js';
12
- import { pipe, isThenable, serialForEach } from '@jesscss/awaitable-pipe';
13
- import { Nil } from './nil.js';
14
- import { VarDeclaration } from './declaration-var.js';
15
- import { Any } from './any.js';
16
- import { List } from './list.js';
17
- import { indent, normalizeIndent } from './util/serialize-helper.js';
18
- import { freezeChildren } from './util/cloning.js';
19
- const { isArray } = Array;
20
- /**
21
- * The class representing a "declaration list".
22
- * CSS calls it this even though CSS Nesting
23
- * adds a bunch more things that aren't declarations.
24
- *
25
- * Used by Ruleset and Mixin. Additionally, imports / use statements
26
- * return rules.
27
- *
28
- * @example
29
- * [
30
- * (Declaration color: black;)
31
- * (Declaration background-color: white;)
32
- * ]
33
- */
34
- export class Rules extends Node {
35
- type = 'Rules';
36
- shortType = 'rules';
37
- allowRuleRoot = true;
38
- allowRoot = true;
39
- rulesetRegistry;
40
- mixinRegistry;
41
- declarationRegistry;
42
- functionRegistry;
43
- rulesIndexed = 0;
44
- _indexing = false;
45
- _indexRules() {
46
- if (this._indexing) {
47
- return; // Prevent recursive indexing
48
- }
49
- this._indexing = true;
50
- try {
51
- let value = this.value;
52
- let length = value.length;
53
- for (let i = this.rulesIndexed; i < length; i++) {
54
- const node = value[i];
55
- this.registerNode(node);
56
- }
57
- this.rulesIndexed = length;
58
- }
59
- finally {
60
- this._indexing = false;
61
- }
62
- }
63
- /**
64
- * Rules are often cloned during `preEval()` when `context.preserveOriginalNodes`
65
- * is enabled. If callers register functions/mixins/declarations on the parsed tree
66
- * before evaluation (e.g. via visitors), those registries must survive cloning so
67
- * lookups during evaluation work as expected.
68
- */
69
- clone(deep, cloneFn) {
70
- const newRules = super.clone(deep, cloneFn);
71
- // Only preserve *function* registry across clones.
72
- // This supports Less plugin compat, where plugins can inject functions into the registry
73
- // without creating AST nodes that would be re-registered on clone.
74
- //
75
- // Do NOT reuse declaration/mixin/ruleset registries across clones; those should always
76
- // be rebuilt from AST nodes via lazy indexing.
77
- if (this.functionRegistry) {
78
- newRules.functionRegistry = this.functionRegistry.cloneForRules(newRules);
79
- }
80
- // IMPORTANT: cloned Rules must re-index their own registries.
81
- // Otherwise, a clone can inherit `rulesIndexed` from the source Rules (often == value.length),
82
- // while having an empty/incorrect registry state, causing lookup misses (e.g. @c in detached-rulesets).
83
- newRules.rulesIndexed = 0;
84
- newRules._indexing = false;
85
- newRules._rulesSet = undefined;
86
- return newRules;
87
- }
88
- /**
89
- * Lazily create registries for types as needed.
90
- */
91
- register(type, node) {
92
- let registry = this[`${type}Registry`];
93
- if (!registry) {
94
- let className = `${type.charAt(0).toUpperCase()}${type.slice(1)}`;
95
- let RegistryClass = Registries[`${className}Registry`];
96
- registry = new RegistryClass(this);
97
- this[`${type}Registry`] = registry;
98
- }
99
- const result = registry.add(node);
100
- return result;
101
- }
102
- getRegistry(type) {
103
- let registry = this[`${type}Registry`];
104
- if (!registry) {
105
- /**
106
- * @note - Ideally we wouldn't create a registry object if we didn't have to,
107
- * just to find. But the find methods have complex logic for searching parent
108
- * and children rules / registries.
109
- */
110
- let className = `${type.charAt(0).toUpperCase()}${type.slice(1)}`;
111
- let RegistryClass = Registries[`${className}Registry`];
112
- registry = new RegistryClass(this);
113
- this[`${type}Registry`] = registry;
114
- }
115
- if (this.rulesIndexed < this.value.length) {
116
- this._indexRules();
117
- }
118
- return registry;
119
- }
120
- find(type, keys, filterType, options = {}) {
121
- let registry = this.getRegistry(type);
122
- return registry.find(keys, filterType, options);
123
- }
124
- toString(options) {
125
- if (!this.visible && !this.fullRender) {
126
- return '';
127
- }
128
- options = getPrintOptions(options);
129
- const w = options.writer;
130
- const depth = options.depth;
131
- const mark = w.mark();
132
- const ctx = options.context;
133
- const suppressedLeadingComments = [];
134
- if (depth === 0) {
135
- // Snapshot global emit-tracking so repeated `.toString()` calls remain stable.
136
- const prevCharsetEmitted = ctx?.charsetEmitted;
137
- const prevTopImports = ctx?.topImports ? [...ctx.topImports] : undefined;
138
- // @charset must be first
139
- if (ctx?.currentCharset && !ctx.charsetEmitted) {
140
- const charset = ctx.currentCharset;
141
- // Use capture to avoid double-writing (toTrimmedString writes to writer AND returns the string)
142
- const charsetStr = w.capture(() => charset.toTrimmedString(options));
143
- w.add(charsetStr, charset);
144
- w.add('\n');
145
- // Do not permanently flip `charsetEmitted` here; restore at end.
146
- ctx.charsetEmitted = true;
147
- }
148
- // Less keeps leading comments before hoisted @import output.
149
- const isCommentLike = (node) => {
150
- const text = String(node.valueOf?.() ?? '').trimStart();
151
- if (!text.startsWith('/*')) {
152
- return false;
153
- }
154
- return isNode(node, 'Comment') || isNode(node, 'Any');
155
- };
156
- if (ctx?.topImports?.length) {
157
- for (const node of this.value) {
158
- if (!isCommentLike(node)) {
159
- break;
160
- }
161
- const commentStr = w.capture(() => node.toTrimmedString(options));
162
- w.add(normalizeIndent(commentStr, ''), node);
163
- w.add('\n');
164
- const wasVisible = node.hasFlag(F_VISIBLE);
165
- suppressedLeadingComments.push({ node, visible: wasVisible });
166
- if (wasVisible) {
167
- node.removeFlag(F_VISIBLE);
168
- }
169
- }
170
- }
171
- // @import must come after @charset but before other rules
172
- if (ctx?.topImports?.length) {
173
- for (const importRule of ctx.topImports) {
174
- if (isNode(importRule, 'AtRule')) {
175
- const importPrelude = importRule.value.prelude;
176
- if (importPrelude && String(importPrelude.valueOf?.() ?? '').includes('$')) {
177
- const maybePrelude = importPrelude.eval(ctx);
178
- if (!isThenable(maybePrelude)) {
179
- importRule.value.prelude = maybePrelude;
180
- }
181
- }
182
- }
183
- const importStr = w.capture(() => importRule.toString(options));
184
- w.add(normalizeIndent(importStr, ''), importRule);
185
- w.add('\n');
186
- }
187
- // Do not permanently clear; restore at end.
188
- }
189
- // Restore global tracking (we only needed it during this print).
190
- if (ctx) {
191
- ctx.charsetEmitted = prevCharsetEmitted;
192
- if (prevTopImports) {
193
- ctx.topImports = prevTopImports;
194
- }
195
- }
196
- }
197
- this.processPrePost('pre', '', options);
198
- const bodyMark = w.mark();
199
- const bodyStr = this.toTrimmedString(options);
200
- const bodyEmitted = w.getSince(bodyMark);
201
- if (bodyEmitted.length === 0 && bodyStr) {
202
- w.add(bodyStr);
203
- }
204
- // At root level, ensure output ends with a single newline (standard for CSS files)
205
- // Don't propagate all the last child's post content (which may have extra whitespace)
206
- if (depth === 0) {
207
- for (const suppressed of suppressedLeadingComments) {
208
- if (suppressed.visible) {
209
- suppressed.node.addFlag(F_VISIBLE);
210
- }
211
- }
212
- const result = w.getSince(mark).trimEnd();
213
- // Ensure exactly one trailing newline (only if there's content)
214
- return result ? result + '\n' : '';
215
- }
216
- return w.getSince(mark);
217
- }
218
- pendingExtends = new Set();
219
- constructor(value, options, location, treeContext) {
220
- let rulesVisibility = options?.rulesVisibility ?? {};
221
- // Set defaults for API-created Rules. Parsers will override these as needed:
222
- // - Less mixins/rulesets: VarDeclaration = 'optional', Mixin = 'public'
223
- // - Sass mixins/rulesets: VarDeclaration = 'private', Mixin = 'private'
224
- // - Imports: VarDeclaration = 'public', Mixin = 'public'
225
- // Default to 'public' for API-created Rules (better DX - variables are accessible).
226
- // If you want nested Rules to be private, set it explicitly.
227
- rulesVisibility.Declaration ??= 'public';
228
- rulesVisibility.Ruleset ??= 'public';
229
- rulesVisibility.VarDeclaration ??= 'public';
230
- rulesVisibility.Mixin ??= 'public';
231
- // Merge with existing options to preserve rulesVisibility
232
- const mergedOptions = { ...options, rulesVisibility };
233
- super(value ?? [], mergedOptions, location, treeContext);
234
- }
235
- *[Symbol.iterator]() {
236
- let value = this.value;
237
- /**
238
- * This should always be the case? But at one point something somewhere
239
- * set the value to undefined I think, so just leaving this defensively.
240
- */
241
- if (isArray(value)) {
242
- yield* value.entries();
243
- }
244
- }
245
- /**
246
- * Used by Ruleset, Mixins, and AtRules etc to render
247
- * rules with braces.
248
- */
249
- toBraced(options) {
250
- let opts = getPrintOptions(options);
251
- // Use options.depth if provided, otherwise calculate from frameState
252
- const depth = opts.depth;
253
- const w = opts.writer;
254
- const mark = w.mark();
255
- let space = ''.padStart(depth * 2);
256
- w.add('{');
257
- // Set depth for _emitRulesBody - children should be one level deeper
258
- const childOptions = { ...opts, depth: depth + 1 };
259
- childOptions.writer.add('\n');
260
- this._emitRulesBody(childOptions);
261
- // ensure closing brace is on its own properly indented line
262
- w.add('\n');
263
- if (depth !== 0) {
264
- w.add(space);
265
- }
266
- w.add('}');
267
- // At root level (depth === 0), don't add a newline after the closing brace
268
- // The parent _emitRulesBody will add the newline before the next item
269
- // For nested rules (depth > 0), the newline is handled by the parent's _emitRulesBody
270
- return w.getSince(mark);
271
- }
272
- _emitRulesBody(options) {
273
- const w = options.writer;
274
- const depth = options.depth ?? 0;
275
- const space = indent(depth);
276
- const { value } = this;
277
- const referenceMode = Boolean(options.referenceMode);
278
- const referenceRenderEnabled = referenceMode ? Boolean(options.referenceRenderEnabled) : true;
279
- // Skip charset nodes - they are collected and prepended at root level
280
- // Nil nodes are now non-visible, so they're automatically filtered by n.visible
281
- const items = value.filter(n => n.visible);
282
- if (items.length === 0) {
283
- return;
284
- }
285
- // No spacing flags; writer.capture is used where needed
286
- const isInlineSourceRules = (node) => {
287
- if (node.type !== 'Rules') {
288
- return false;
289
- }
290
- const rulesNode = node;
291
- if (rulesNode.value.length !== 1) {
292
- return false;
293
- }
294
- const only = rulesNode.value[0];
295
- return only.type === 'Any' && only.options?.role === 'any';
296
- };
297
- let emittedCount = 0;
298
- let lastEmittedType;
299
- let lastEmittedWasInlineSourceRules = false;
300
- const isInMixinOutputScope = (node) => {
301
- const seen = new Set();
302
- const queue = [node];
303
- while (queue.length > 0) {
304
- const current = queue.shift();
305
- if (seen.has(current)) {
306
- continue;
307
- }
308
- seen.add(current);
309
- if (current.options?.isMixinOutput === true) {
310
- return true;
311
- }
312
- if (current.parent) {
313
- queue.push(current.parent);
314
- }
315
- if (current.sourceParent && isNode(current.sourceParent)) {
316
- queue.push(current.sourceParent);
317
- }
318
- }
319
- return false;
320
- };
321
- for (let idx = 0; idx < items.length; idx++) {
322
- const n = items[idx];
323
- const isContainer = n.type === 'Ruleset' || n.type === 'AtRule' || n.type === 'Rules';
324
- if (referenceMode && !referenceRenderEnabled && !isContainer) {
325
- continue;
326
- }
327
- if (emittedCount > 0) {
328
- // Check actual buffer state - not just previous captured output
329
- // Frame closing in serializeRulesContainer adds newlines that aren't in the capture
330
- const currentBuffer = w.getSince(0);
331
- const bufferEndsWithNewline = currentBuffer.endsWith('\n');
332
- const needsInlineBoundarySpacing = ((lastEmittedType === 'Any' && n.type !== 'Any')
333
- || (lastEmittedWasInlineSourceRules && n.type !== 'Any'));
334
- if (!bufferEndsWithNewline || needsInlineBoundarySpacing) {
335
- w.add('\n');
336
- }
337
- }
338
- const isChildRules = n.type === 'Rules';
339
- const isRulesetOrAtRule = n.type === 'Ruleset' || n.type === 'AtRule';
340
- // Add indentation only for simple nodes (declarations, etc.)
341
- // Ruleset and AtRule nodes indent themselves in renderOpening
342
- if (!isChildRules && !isRulesetOrAtRule && depth !== 0) {
343
- w.add(space);
344
- }
345
- // Emit directly to preserve source map segments
346
- // For child Rules nodes, pass the same depth (don't increment depth)
347
- // Rules nodes inside Rules nodes are at the same level
348
- let childOptions = isChildRules
349
- ? { ...options, depth }
350
- : { ...options, depth };
351
- if (isChildRules) {
352
- const inMixinOutputScope = isInMixinOutputScope(n);
353
- const sourceIsCall = (n.sourceParent?.type === 'Call'
354
- || n.sourceNode?.sourceParent?.type === 'Call');
355
- const ownReferenceMode = (n.options?.referenceMode === true
356
- && (!inMixinOutputScope || !sourceIsCall));
357
- const childReferenceMode = referenceMode || ownReferenceMode;
358
- const enteringReferenceMode = !referenceMode && ownReferenceMode;
359
- const childReferenceRenderEnabled = childReferenceMode
360
- ? (enteringReferenceMode ? false : referenceRenderEnabled)
361
- : true;
362
- childOptions = {
363
- ...childOptions,
364
- referenceMode: childReferenceMode,
365
- referenceRenderEnabled: childReferenceRenderEnabled
366
- };
367
- }
368
- let rule = w.capture(() => n.toTrimmedString(childOptions));
369
- if (!rule && (n.type === 'Ruleset' || n.type === 'AtRule' || n.type === 'Rules')) {
370
- continue;
371
- }
372
- w.add(rule, n); // Pass node as origin to preserve location info
373
- if (n.requiredSemi && n.options.semi !== false) {
374
- w.add(';', n);
375
- }
376
- emittedCount++;
377
- lastEmittedType = n.type;
378
- lastEmittedWasInlineSourceRules = isInlineSourceRules(n);
379
- }
380
- }
381
- toTrimmedString(options) {
382
- options = getPrintOptions(options);
383
- const w = options.writer;
384
- const mark = w.mark();
385
- this._emitRulesBody(options);
386
- return w.getSince(mark);
387
- }
388
- /** All rules, with nested rules flattened */
389
- flatRules(visibleOnly = false) {
390
- const finalRules = [];
391
- const iterateRules = (rules) => {
392
- for (let n of rules.value) {
393
- if (isNode(n, 'Rules')) {
394
- iterateRules(n);
395
- continue;
396
- }
397
- if (!visibleOnly || n.visible || n.fullRender) {
398
- finalRules.push(n);
399
- }
400
- }
401
- };
402
- iterateRules(this);
403
- return finalRules;
404
- }
405
- visibleRules() {
406
- return this.value.filter(n => n.visible);
407
- }
408
- toObject(convertToPrimitives = true) {
409
- let output = new Map();
410
- const iterateRules = (rules) => {
411
- for (let n of rules.value) {
412
- if (isNode(n, 'Declaration')) {
413
- let { name, value, important } = n.value;
414
- if (convertToPrimitives) {
415
- let primitive = value.valueOf();
416
- let outputValue = important ? `${primitive} ${important}` : primitive;
417
- if (outputValue === undefined) {
418
- continue;
419
- }
420
- output.set(name.toString(), outputValue);
421
- }
422
- else {
423
- let outputValue = important ? new Sequence([n, important]) : n;
424
- output.set(name.toString(), outputValue);
425
- }
426
- }
427
- else if (n instanceof Rules) {
428
- iterateRules(n);
429
- }
430
- }
431
- };
432
- iterateRules(this);
433
- return Object.fromEntries(output);
434
- }
435
- /** @todo - Refactor? */
436
- _rulesSet;
437
- get rulesSet() {
438
- return (this._rulesSet ??= []);
439
- }
440
- registerNode(node, options, _context) {
441
- if (isNode(node, 'Rules')) {
442
- // Use options if provided, otherwise use node's settings, otherwise empty
443
- // Then merge with node's settings to preserve any values not in options
444
- let optionsVisibility = options?.rulesVisibility;
445
- let nodeVisibility = node.options.rulesVisibility ?? {};
446
- let rulesVisibility = optionsVisibility
447
- ? { ...nodeVisibility, ...optionsVisibility }
448
- : nodeVisibility;
449
- /** Only Declaration and Ruleset are public by default.
450
- * VarDeclaration visibility should be set by the parser (optional for Less, private for Jess/Sass).
451
- * Mixin visibility should be set by the parser.
452
- */
453
- rulesVisibility.Declaration ??= 'public';
454
- rulesVisibility.Ruleset ??= 'public';
455
- rulesVisibility.Mixin ??= 'public';
456
- /** Either one set as readonly will win */
457
- let readonly = Boolean(options?.readonly || node.options.readonly);
458
- this.rulesSet.push({
459
- node,
460
- rulesVisibility,
461
- readonly
462
- });
463
- // Note: Rulesets from imported Rules are registered in treeRoot's registry
464
- // after evaluation completes (in evalNode), when treeRoot is guaranteed to be set
465
- }
466
- else if (isNode(node, 'Declaration')) {
467
- /**
468
- * setDefined works like Sass's !default flag - it finds the original variable
469
- * declaration and inserts a new declaration at the same rules level as the
470
- * found variable, but before the current nested node.
471
- */
472
- if (node.options?.setDefined) {
473
- // Skip setDefined logic if we're currently indexing to avoid recursive calls
474
- if (this._indexing) {
475
- // We'll handle setDefined after indexing is complete
476
- return;
477
- }
478
- let key = node.value.name?.toString();
479
- /** Don't set within sibling rules */
480
- let opts = {};
481
- opts.searchParents = true;
482
- // Don't use start when searching parents - we want to find variables in parent regardless of position
483
- // start is only relevant for finding variables before the current node in the same Rules
484
- opts.start = undefined;
485
- // node.type is 'VarDeclaration' or 'Declaration', use it directly as filterType
486
- let result = this.find('declaration', key, node.type, opts);
487
- if (result) {
488
- if (result.options?.readonly || opts.readonly) {
489
- throw new ReferenceError(`"${key}" is readonly`);
490
- }
491
- // Find the Rules node that contains the found declaration
492
- let foundRules = result.parent;
493
- if (!foundRules) {
494
- throw new Error(`Could not find parent Rules for declaration '${key}'`);
495
- }
496
- // Create a new declaration with the same name but our value
497
- const newDeclaration = node.copy();
498
- newDeclaration.options = { ...newDeclaration.options };
499
- newDeclaration.options.setDefined = undefined; // Remove setDefined flag
500
- // Adopt the new declaration to the found Rules
501
- foundRules.adopt(newDeclaration);
502
- // Add to the value array AFTER the found declaration
503
- // This ensures it shadows the original and is evaluated after it
504
- const foundIndex = foundRules.value.indexOf(result);
505
- if (foundIndex !== -1) {
506
- foundRules.value.splice(foundIndex + 1, 0, newDeclaration);
507
- }
508
- else {
509
- // If not found in array, add at the beginning
510
- foundRules.value.unshift(newDeclaration);
511
- }
512
- // Register it via registerNode to ensure it's properly indexed
513
- // Note: registerNode will call register('declaration', ...) which adds to registry
514
- // We skip setDefined processing since we already removed the flag
515
- foundRules.registerNode(newDeclaration);
516
- }
517
- else {
518
- throw new ReferenceError(`"${key}" is not defined`);
519
- }
520
- }
521
- this.register('declaration', node);
522
- }
523
- else if (isNode(node, 'Ruleset')) {
524
- // Register to 'mixin' for mixin calls
525
- // Always register - guard filtering happens at call time in getFunctionFromMixins
526
- // Note: 'ruleset' registration for extends now happens in Ruleset.preEval to the extend root's registry
527
- this.register('mixin', node);
528
- }
529
- else if (isNode(node, 'Mixin')) {
530
- this.register('mixin', node);
531
- }
532
- else if (isNode(node, 'Func')) {
533
- this.register('function', node);
534
- }
535
- }
536
- push(...nodes) {
537
- for (let node of nodes) {
538
- this.adopt(node);
539
- this.value.push(node);
540
- this.registerNode(node);
541
- }
542
- }
543
- at(index) {
544
- return atIndex(this.value, index);
545
- }
546
- /**
547
- * This traverses deeply to visit all nodes, but indexes locally.
548
- */
549
- preEval(context) {
550
- if (!this.preEvaluated) {
551
- context.depth++;
552
- let rules = this.maybeClone(context);
553
- // When this is the nestable at-rule wrapper (one child Ruleset(&)), do not clone so
554
- // inner rulesets register to the same object we push and register as extend root.
555
- const nestableAtRuleNames = new Set(['@media', '@supports', '@layer', '@container', '@scope']);
556
- const parentAtRule = this.parent?.type === 'AtRule' ? this.parent : null;
557
- const isNestableAtRuleBody = parentAtRule
558
- && nestableAtRuleNames.has(String(parentAtRule.value?.name?.valueOf?.() ?? ''));
559
- const first = rules.value?.[0];
560
- const isWrapper = isNestableAtRuleBody
561
- && rules.value?.length === 1
562
- && isNode(first, 'Ruleset')
563
- && isNode(first.value?.selector, 'Ampersand');
564
- if (isWrapper) {
565
- rules = this;
566
- }
567
- rules.preEvaluated = true;
568
- // Save current context and set up new context for variable lookups during preEval
569
- const saved = this._snapshotContext(context);
570
- this._setupContextForRules(context, rules);
571
- // Set context.root early if this is the main root
572
- const isMainRoot = !context.root;
573
- if (isMainRoot) {
574
- context.root = rules;
575
- }
576
- /**
577
- * I think maybe we can just set the index to the actual order?
578
- */
579
- for (let i = 0; i < rules.value.length; i++) {
580
- let n = rules.value[i];
581
- n.index = i;
582
- }
583
- // Preserve parent when cloning - if this Rules is inside a ruleset, maintain the parent relationship
584
- if (this.parent && !rules.parent) {
585
- this.parent.adopt(rules);
586
- }
587
- // Set context.root if not already set (needed for preEval visitors)
588
- if (!context.root) {
589
- context.root = rules;
590
- }
591
- // When getTree() set context.root to the original Rules but we're processing a clone,
592
- // use the clone as context.root so registerRoot/pushExtendRoot run and rulesets register to the clone (extend fix).
593
- if (context.root === this && this !== rules) {
594
- context.root = rules;
595
- }
596
- // Register main root as extend root if this is the root (needed for extends in preEval)
597
- // Check rules === context.root at registration time (not using stale isMainRoot)
598
- if (rules === context.root && !context.extendRoots.root) {
599
- context.extendRoots.registerRoot(rules);
600
- context.extendRoots.pushExtendRoot(rules);
601
- }
602
- // Always push nestable at-rule body so inner rulesets register to it (not document root).
603
- // Needed for both: wrapper (collapseNesting) and direct body (collapseNesting: false).
604
- if (isNestableAtRuleBody) {
605
- context.extendRoots.pushExtendRoot(rules);
606
- }
607
- // Multi-pass registration system for handling interpolated names
608
- const mp = this._multiPassPreEval(rules, context, saved);
609
- const popNestableBody = () => {
610
- if (isNestableAtRuleBody) {
611
- context.extendRoots.popExtendRoot();
612
- }
613
- };
614
- if (isThenable(mp)) {
615
- return mp.then((result) => {
616
- popNestableBody();
617
- return result;
618
- });
619
- }
620
- popNestableBody();
621
- return mp;
622
- }
623
- return this;
624
- }
625
- /**
626
- * Multi-pass preEval system to handle interpolated names and dependencies
627
- */
628
- _multiPassPreEval(rules, context, saved) {
629
- // First pass: Only register nodes with static names
630
- const staticNodes = [];
631
- const dynamicNodes = [];
632
- // Process each node with static name, handling both sync and async preEval
633
- const processResult = serialForEach(rules.value, (node, index) => {
634
- // Check if node has a static name (can be registered immediately)
635
- if (node.type === 'Any' && node.options.role === 'charset') {
636
- /** Special case where we register the charset node immediately */
637
- rules.value[index] = node.preEval(context);
638
- return;
639
- }
640
- // Nodes that don't register by name (Call, Expression, etc.) skip
641
- // both preEval and dynamic resolution — they're handled by the eval queue.
642
- if (!this._isRegisterableType(node)) {
643
- node.index = index;
644
- return;
645
- }
646
- if (this._hasStaticName(node)) {
647
- // Pre-evaluate nodes with static names before registration
648
- // This ensures selectors are evaluated and keySets are available for rulesets
649
- const preEvald = node.preEval(context);
650
- if (isThenable(preEvald)) {
651
- return preEvald.then((preEvaldNode) => {
652
- rules.value[index] = preEvaldNode;
653
- preEvaldNode.index = index;
654
- // After async preEval, check if it still has a static name
655
- if (this._hasStaticName(preEvaldNode)) {
656
- staticNodes.push(preEvaldNode);
657
- this._registerNodeIfEligible(rules, preEvaldNode, context);
658
- }
659
- else {
660
- dynamicNodes.push(preEvaldNode);
661
- }
662
- });
663
- }
664
- rules.value[index] = preEvald;
665
- preEvald.index = index;
666
- const nodeToRegister = preEvald;
667
- staticNodes.push(nodeToRegister);
668
- this._registerNodeIfEligible(rules, nodeToRegister, context);
669
- }
670
- else {
671
- dynamicNodes.push(node);
672
- }
673
- });
674
- const finish = () => {
675
- // If no dynamic nodes, we're done
676
- if (dynamicNodes.length === 0) {
677
- // Restore context after preEval is complete
678
- context.rulesContext = saved.rulesContext;
679
- context.treeRoot = saved.treeRoot;
680
- // Only restore context.root if saved.root is defined (not the outermost root)
681
- // If saved.root is undefined, it means we're at the outermost level, so keep context.root as is
682
- if (saved.root !== undefined) {
683
- context.root = saved.root;
684
- }
685
- return rules;
686
- }
687
- // Multi-pass resolution of dynamic nodes
688
- return this._resolveDynamicNodes(rules, context, saved, dynamicNodes);
689
- };
690
- if (isThenable(processResult)) {
691
- return processResult.then(() => finish());
692
- }
693
- return finish();
694
- }
695
- /**
696
- * Helper to check if a value is static (either a Node with F_STATIC flag or a primitive value)
697
- */
698
- _isStatic(value) {
699
- if (value && typeof value.hasFlag === 'function') {
700
- return value.hasFlag(F_STATIC);
701
- }
702
- // Primitive values (strings, numbers, etc.) are considered static
703
- return true;
704
- }
705
- /**
706
- * Check if a node type participates in name-based registration.
707
- * Only these node types have names/selectors that _resolveDynamicNodes
708
- * needs to resolve. Everything else (Call, Expression, Comment, etc.)
709
- * goes straight to the eval queue without preEval.
710
- */
711
- _isRegisterableType(node) {
712
- return isNode(node, ['VarDeclaration', 'Declaration', 'Mixin', 'Ruleset', 'StyleImport']);
713
- }
714
- /**
715
- * Check if a node has a static name that can be registered immediately
716
- */
717
- _hasStaticName(node) {
718
- if (isNode(node, 'VarDeclaration')) {
719
- const name = node.value.name;
720
- return this._isStatic(name);
721
- }
722
- if (isNode(node, 'Mixin')) {
723
- const name = node.value.name;
724
- return this._isStatic(name);
725
- }
726
- if (isNode(node, 'Declaration')) {
727
- const name = node.value.name;
728
- return this._isStatic(name);
729
- }
730
- if (isNode(node, 'StyleImport')) {
731
- const path = node.value.path;
732
- return this._isStatic(path);
733
- }
734
- if (isNode(node, 'Ruleset')) {
735
- const selector = node.value.selector;
736
- // BasicSelector, CompoundSelector, ComplexSelector etc. are always static
737
- // Only Interpolated selectors need resolution
738
- if (isNode(selector, ['BasicSelector', 'CompoundSelector', 'ComplexSelector', 'SelectorList'])) {
739
- return true;
740
- }
741
- // After preEval, the selector should be resolved to static identifiers
742
- if (node.preEvaluated) {
743
- return true;
744
- }
745
- // Check F_STATIC flag for other selector types
746
- if (selector && 'hasFlag' in selector && typeof selector.hasFlag === 'function') {
747
- return selector.hasFlag(F_STATIC);
748
- }
749
- return false;
750
- }
751
- // For other registerable node types, check the F_STATIC flag
752
- return node.hasFlag(F_STATIC);
753
- }
754
- /**
755
- * Register a node if it's eligible for registration
756
- */
757
- _registerNodeIfEligible(rules, node, _context) {
758
- if (isNode(node, 'Declaration')) {
759
- rules.registerNode(node);
760
- }
761
- else if (isNode(node, 'Mixin')) {
762
- rules.registerNode(node);
763
- }
764
- else if (isNode(node, 'Ruleset')) {
765
- // registerNode handles both 'mixin' and 'ruleset' registries
766
- rules.registerNode(node);
767
- }
768
- }
769
- /**
770
- * Multi-pass resolution of dynamic nodes with interpolated names
771
- */
772
- _resolveDynamicNodes(rules, context, saved, dynamicNodes) {
773
- const resolvedNodes = [];
774
- const handleResolvedNode = (resolvedNode, node, stillUnresolved) => {
775
- if (resolvedNode.index === undefined) {
776
- resolvedNode.index = node.index;
777
- }
778
- if (!resolvedNode.sourceNode) {
779
- resolvedNode.sourceNode = node.sourceNode ?? node;
780
- }
781
- if (resolvedNode.type === 'Ruleset') {
782
- rules.registerNode(resolvedNode);
783
- }
784
- if (isNode(resolvedNode, 'Nil') || this._hasStaticName(resolvedNode)) {
785
- resolvedNodes.push(resolvedNode);
786
- this._registerNodeIfEligible(rules, resolvedNode, context);
787
- return true; // made progress
788
- }
789
- else {
790
- stillUnresolved.push(resolvedNode);
791
- return false;
792
- }
793
- };
794
- const applyResolvedNodes = () => {
795
- for (let i = 0; i < rules.value.length; i++) {
796
- const node = rules.value[i];
797
- const resolvedNode = resolvedNodes.find(n => n.index === node.index);
798
- if (resolvedNode && resolvedNode !== node) {
799
- rules.value[i] = resolvedNode.inherit(node);
800
- rules.adopt(resolvedNode);
801
- }
802
- }
803
- };
804
- const finishResolution = () => {
805
- applyResolvedNodes();
806
- context.rulesContext = saved.rulesContext;
807
- context.treeRoot = saved.treeRoot;
808
- if (saved.root !== undefined) {
809
- context.root = saved.root;
810
- }
811
- return rules;
812
- };
813
- // Separate declarations (whose dynamic names might depend on each other)
814
- // from non-declarations (which depend on declaration VALUES, not names,
815
- // so retrying during preEval won't help).
816
- const isDeclarationType = (n) => isNode(n, 'VarDeclaration') || isNode(n, 'Declaration');
817
- const dynamicDeclarations = [];
818
- const otherDynamic = [];
819
- for (const node of dynamicNodes) {
820
- if (isDeclarationType(node)) {
821
- dynamicDeclarations.push(node);
822
- }
823
- else {
824
- otherDynamic.push(node);
825
- }
826
- }
827
- // Phase 1: Resolve declarations with dynamic names.
828
- // Retry because one declaration's name might depend on another's being registered.
829
- const MAX_DECL_RETRIES = 5;
830
- let declRetries = 0;
831
- const unresolvedDecls = [...dynamicDeclarations];
832
- const resolveDeclarations = () => {
833
- declRetries++;
834
- if (declRetries > MAX_DECL_RETRIES || unresolvedDecls.length === 0) {
835
- return;
836
- }
837
- const stillUnresolved = [];
838
- let madeProgress = false;
839
- for (let i = 0; i < unresolvedDecls.length; i++) {
840
- const node = unresolvedDecls[i];
841
- try {
842
- const result = node.preEval(context);
843
- if (isThenable(result)) {
844
- const remaining = unresolvedDecls.slice(i + 1);
845
- return result.then((resolvedNode) => {
846
- if (handleResolvedNode(resolvedNode, node, stillUnresolved)) {
847
- madeProgress = true;
848
- }
849
- unresolvedDecls.length = 0;
850
- unresolvedDecls.push(...stillUnresolved, ...remaining);
851
- if (madeProgress && unresolvedDecls.length > 0) {
852
- return resolveDeclarations();
853
- }
854
- });
855
- }
856
- if (handleResolvedNode(result, node, stillUnresolved)) {
857
- madeProgress = true;
858
- }
859
- }
860
- catch {
861
- stillUnresolved.push(node);
862
- }
863
- }
864
- if (madeProgress && stillUnresolved.length > 0) {
865
- unresolvedDecls.length = 0;
866
- unresolvedDecls.push(...stillUnresolved);
867
- return resolveDeclarations();
868
- }
869
- };
870
- // Phase 2: Try non-declarations once. Their interpolated names typically
871
- // depend on declaration VALUES (e.g. @infix from breakpoint-infix()),
872
- // which aren't evaluated until the eval phase. Retrying won't help.
873
- const resolveOtherOnce = () => {
874
- for (let i = 0; i < otherDynamic.length; i++) {
875
- const node = otherDynamic[i];
876
- try {
877
- const result = node.preEval(context);
878
- if (isThenable(result)) {
879
- const remaining = otherDynamic.slice(i + 1);
880
- return result.then((resolvedNode) => {
881
- handleResolvedNode(resolvedNode, node, []);
882
- // Continue with remaining nodes
883
- otherDynamic.length = 0;
884
- otherDynamic.push(...remaining);
885
- return resolveOtherOnce();
886
- });
887
- }
888
- handleResolvedNode(result, node, []);
889
- }
890
- catch {
891
- // Can't resolve during preEval — leave in place for eval phase
892
- }
893
- }
894
- };
895
- return pipe(() => resolveDeclarations(), () => {
896
- applyResolvedNodes();
897
- return resolveOtherOnce();
898
- }, () => finishResolution());
899
- }
900
- /**
901
- * Helper method to continue preEval'ing remaining children after an async preEval.
902
- */
903
- _preEvalRemainingChildren(rules, context, startIndex, saved) {
904
- for (let i = startIndex; i < rules.value.length; i++) {
905
- const node = rules.value[i];
906
- // Always call preEval to ensure deep traversal and name resolution
907
- const result = node.preEval(context);
908
- if (isThenable(result)) {
909
- // Handle async preEval by returning a promise that resolves after all children
910
- return result.then((resolvedNode) => {
911
- // Update the node if preEval returned a different instance
912
- if (resolvedNode !== node) {
913
- rules.value[i] = resolvedNode;
914
- rules.adopt(resolvedNode);
915
- }
916
- // Register the node after preEval (name resolution) if not already registered
917
- if (!isNode(node, 'VarDeclaration')) {
918
- rules.registerNode(resolvedNode);
919
- }
920
- // Continue with the rest of the children
921
- return this._preEvalRemainingChildren(rules, context, i + 1, saved);
922
- });
923
- }
924
- // Update the node if preEval returned a different instance
925
- if (result !== node) {
926
- rules.value[i] = result;
927
- rules.adopt(result);
928
- }
929
- // Register the node after preEval (name resolution) if not already registered
930
- if (!isNode(node, 'VarDeclaration')) {
931
- rules.registerNode(result);
932
- }
933
- }
934
- // Restore context after preEval is complete (for async case)
935
- if (saved) {
936
- context.rulesContext = saved.rulesContext;
937
- context.treeRoot = saved.treeRoot;
938
- // Only restore context.root if saved.root is defined (not the outermost root)
939
- // If saved.root is undefined, it means we're at the outermost level, so keep context.root as is
940
- if (saved.root !== undefined) {
941
- context.root = saved.root;
942
- }
943
- }
944
- return rules;
945
- }
946
- /** Save current context roots to restore later */
947
- _snapshotContext(context) {
948
- return {
949
- rulesContext: context.rulesContext,
950
- treeContext: context.treeContext,
951
- treeRoot: context.treeRoot,
952
- root: context.root,
953
- extendRootStackLength: context.extendRoots.extendRootStack.length
954
- };
955
- }
956
- /** Setup context for evaluating these rules */
957
- _setupContextForRules(context, rules) {
958
- const treeContext = context.treeContext;
959
- // Only switch treeContext if the rules have one AND it's different
960
- // Dynamically created Rules (e.g., mixin parameter wrappers) may not have treeContext
961
- // and we don't want to lose leakyRules and other settings
962
- // IMPORTANT: Check _treeContext (private field) not treeContext (getter that lazily creates)
963
- const rulesTreeContext = rules._treeContext;
964
- if (rulesTreeContext && (!treeContext || treeContext !== rulesTreeContext)) {
965
- context.allRoots.push(rules);
966
- context.treeContext = rulesTreeContext;
967
- context.treeRoot = rules;
968
- }
969
- // Always set root if not set - needed for extends to work with API-created Rules
970
- context.root ??= rules;
971
- context.rulesContext = rules;
972
- }
973
- /** Assign depth-first document order to every Ruleset under the given Rules (single walk, source order). */
974
- _assignDocumentOrderDepthFirst(rules, map, counter) {
975
- const value = rules.value;
976
- if (!isArray(value)) {
977
- return;
978
- }
979
- for (const node of value) {
980
- if (isNode(node, 'Ruleset')) {
981
- map.set(node, counter.value);
982
- counter.value++;
983
- }
984
- const innerRules = node.value?.rules;
985
- if (innerRules && isNode(innerRules, 'Rules')) {
986
- this._assignDocumentOrderDepthFirst(innerRules, map, counter);
987
- }
988
- }
989
- }
990
- /** Build the evaluation queue partitioned by priority */
991
- _buildEvalQueue(rules) {
992
- let evalQueue = new Map();
993
- for (let item of rules) {
994
- let [, rule] = item;
995
- let priority = NodeTypeToPriority.get(rule.type) ?? 0 /* Priority.None */;
996
- // Less variable-calls `@foo();` are parsed as Expression(Call(variable-ref)).
997
- // We *selectively* boost only those calls that "unlock mixins" (i.e. calling a variable whose
998
- // value is a detached ruleset containing mixin definitions). This avoids changing evaluation
999
- // order for regular detached rulesets like `@ruleset()` used for property blocks.
1000
- if (priority === 0 /* Priority.None */ && rules.treeContext?.leakyRules === true && isNode(rule, 'Expression')) {
1001
- const inner = rule.value;
1002
- if (isNode(inner, 'Call') && isNode(inner.value?.name, 'Reference')) {
1003
- const ref = inner.value.name;
1004
- const refType = String(ref?.options?.type ?? '');
1005
- if (refType === 'variable') {
1006
- const raw = ref.value?.key;
1007
- const keyStr = Array.isArray(raw) ? raw.join('') : String(raw?.valueOf?.() ?? raw ?? '');
1008
- // Only if variable exists and its value is a detached ruleset Mixin with nested Mixin definitions.
1009
- const decl = rules.find('declaration', keyStr, 'VarDeclaration');
1010
- const val = decl?.value?.value;
1011
- const hasNestedMixinDefinitions = isNode(val, 'Mixin')
1012
- && Array.isArray(val.value?.rules?.value)
1013
- && val.value.rules.value.some((n) => n?.type === 'Mixin');
1014
- if (hasNestedMixinDefinitions) {
1015
- priority = 3 /* Priority.High */;
1016
- }
1017
- }
1018
- }
1019
- }
1020
- let queue = evalQueue.get(priority) ?? [];
1021
- queue.push(item);
1022
- evalQueue.set(priority, queue);
1023
- }
1024
- return evalQueue;
1025
- }
1026
- /** Evaluate the built queues in priority order */
1027
- _evaluateQueue(rules, evalQueue, context) {
1028
- let rulesToHoist = false;
1029
- const scheduledPriority = new WeakMap();
1030
- const failuresByPriority = new WeakMap();
1031
- const priorities = Array.from({ length: 4 /* Priority.Highest */ + 1 }).map((_, i) => (4 /* Priority.Highest */ - i));
1032
- const runPriority = (p) => {
1033
- const queue = evalQueue.get(p);
1034
- if (!queue) {
1035
- return;
1036
- }
1037
- const enqueueRetry = (priority, item, rule) => {
1038
- const retryQueue = evalQueue.get(priority) ?? [];
1039
- retryQueue.push(item);
1040
- evalQueue.set(priority, retryQueue);
1041
- scheduledPriority.set(rule, priority);
1042
- };
1043
- const countFailure = (rule, priority) => {
1044
- const byPriority = failuresByPriority.get(rule) ?? new Map();
1045
- const nextCount = (byPriority.get(priority) ?? 0) + 1;
1046
- byPriority.set(priority, nextCount);
1047
- failuresByPriority.set(rule, byPriority);
1048
- return nextCount;
1049
- };
1050
- const runSingleEntry = (q) => {
1051
- const [idx, rule] = queue[q];
1052
- /**
1053
- * Var declarations have late evaluation, so they are skipped.
1054
- * (Meaning: they are not evaluated until they are referenced.)
1055
- */
1056
- if (isNode(rule, 'VarDeclaration')) {
1057
- return;
1058
- }
1059
- // Skip stale entries for nodes that were re-queued to a different priority.
1060
- const expectedPriority = scheduledPriority.get(rule);
1061
- if (expectedPriority !== undefined && expectedPriority !== p) {
1062
- return;
1063
- }
1064
- const onEvalError = (error) => {
1065
- // Most node failures are semantic failures and should throw immediately.
1066
- // Retry scheduling is reserved for StyleImport ordering/interpolation cases.
1067
- if (!isNode(rule, 'StyleImport')) {
1068
- throw error;
1069
- }
1070
- // Final pass: no retries remain.
1071
- if (p === 0 /* Priority.None */) {
1072
- throw error;
1073
- }
1074
- // Only retry when the import path itself couldn't be resolved
1075
- // (e.g. @import "@{theme}/file" where @theme isn't available yet).
1076
- // Path resolution is cheap (no cloning). Content evaluation errors
1077
- // (after cloning the import tree) are never retried — each retry
1078
- // would re-clone the entire tree, causing memory blowup.
1079
- const isPathError = error instanceof Error && error._isPathResolutionError;
1080
- if (!isPathError) {
1081
- throw error;
1082
- }
1083
- // Retry policy:
1084
- // 1) first failure at a priority -> retry once at same priority
1085
- // 2) second+ failure at that priority -> step down one level
1086
- const failures = countFailure(rule, p);
1087
- const nextPriority = failures === 1 ? p : (p - 1);
1088
- enqueueRetry(nextPriority, [idx, rule], rule);
1089
- return;
1090
- };
1091
- const tryStepResult = () => {
1092
- try {
1093
- const result = rule.eval(context);
1094
- if (isThenable(result)) {
1095
- return result.catch(onEvalError);
1096
- }
1097
- return result;
1098
- }
1099
- catch (error) {
1100
- return onEvalError(error);
1101
- }
1102
- };
1103
- const stepResult = pipe(tryStepResult, (result) => {
1104
- // Undefined means we re-queued this node for retry.
1105
- if (result === undefined) {
1106
- return;
1107
- }
1108
- scheduledPriority.delete(rule);
1109
- // Apply the result
1110
- if (result !== rule) {
1111
- rules.value[idx] = result;
1112
- queue[q] = [idx, result];
1113
- // If a StyleImport evaluated to Rules, register them in the parent's _rulesSet
1114
- // so variables from the import can be found by the parent
1115
- // Also register Rules from Call results (mixin calls) in the same way
1116
- if (isNode(result, 'Rules')) {
1117
- // Set the index of the imported Rules to the StyleImport's index
1118
- // so we can compare Rules indices when determining which variable was declared later
1119
- result.index = idx;
1120
- rules.adopt(result);
1121
- rules.registerNode(result, {
1122
- rulesVisibility: result.options.rulesVisibility,
1123
- readonly: result.options.readonly
1124
- }, context);
1125
- }
1126
- else {
1127
- // For non-Rules results, adopt them to set up parent chain
1128
- rules.adopt(result);
1129
- }
1130
- }
1131
- if (result.hoistToRoot) {
1132
- rulesToHoist = true;
1133
- }
1134
- return;
1135
- });
1136
- // If stepResult is a thenable, propagate any errors
1137
- if (isThenable(stepResult)) {
1138
- return stepResult;
1139
- }
1140
- return;
1141
- };
1142
- const runFromIndex = (q) => {
1143
- if (q >= queue.length) {
1144
- return;
1145
- }
1146
- const step = runSingleEntry(q);
1147
- if (isThenable(step)) {
1148
- return step.then(() => runFromIndex(q + 1));
1149
- }
1150
- return runFromIndex(q + 1);
1151
- };
1152
- return runFromIndex(0);
1153
- };
1154
- const phaseRun = serialForEach(priorities, runPriority);
1155
- if (isThenable(phaseRun)) {
1156
- return phaseRun.then(() => {
1157
- return rulesToHoist;
1158
- }).catch((error) => {
1159
- throw error;
1160
- });
1161
- }
1162
- return rulesToHoist;
1163
- }
1164
- /**
1165
- * Coalesce assignment-normalized declaration chains in one stage after evaluation.
1166
- * This handles both in-scope merges and merges that span call-produced Rules blocks.
1167
- */
1168
- _coalesceMergedDeclarations(rules) {
1169
- const isMergedAssign = (assign) => (assign === '+:' || assign === '&,:' || assign === '&_:');
1170
- const isDeclarationOnlyRules = (node) => (isNode(node, 'Rules')
1171
- && node.value.length > 0
1172
- && node.value.every(child => isNode(child, ['Declaration', 'Comment'])));
1173
- const composeMergedValue = (decl, prior, assign) => {
1174
- if (!isNode(decl, 'Declaration') || !isNode(prior, 'Declaration')) {
1175
- return;
1176
- }
1177
- const priorValue = prior.value.value.copy(true, freezeChildren);
1178
- const nextValue = decl.value.value.copy(true, freezeChildren);
1179
- decl.value.value = assign === '&_:'
1180
- ? spaced([priorValue, nextValue])
1181
- : new List([priorValue, nextValue]);
1182
- if (!decl.value.important && prior.value.important) {
1183
- decl.value.important = prior.value.important;
1184
- }
1185
- };
1186
- const normalizeMergedDeclarationValue = (node) => {
1187
- if (!isNode(node, 'Declaration')) {
1188
- return;
1189
- }
1190
- const current = node.value.value;
1191
- if (!isNode(current, 'List') || current.value.length === 0) {
1192
- return;
1193
- }
1194
- const [first, ...rest] = current.value;
1195
- let firstIsEmptyString = false;
1196
- try {
1197
- firstIsEmptyString = String(first?.valueOf?.() ?? '') === '';
1198
- }
1199
- catch {
1200
- firstIsEmptyString = false;
1201
- }
1202
- const isEmptyPlaceholder = Boolean(first
1203
- && (isNode(first, 'Nil')
1204
- || (isNode(first, 'List') && first.value.length === 0)
1205
- || firstIsEmptyString));
1206
- if (!isEmptyPlaceholder) {
1207
- return;
1208
- }
1209
- if (rest.length === 0) {
1210
- node.value.value = new Nil();
1211
- return;
1212
- }
1213
- if (rest.length === 1) {
1214
- node.value.value = rest[0].copy(true, freezeChildren);
1215
- return;
1216
- }
1217
- node.value.value = new List(rest.map(item => item.copy(true, freezeChildren)));
1218
- };
1219
- const lastVisibleByName = new Map();
1220
- const mergedAnchorByName = new Map();
1221
- const stream = [];
1222
- for (const node of rules.value) {
1223
- if (isNode(node, 'Declaration')) {
1224
- stream.push(node);
1225
- continue;
1226
- }
1227
- if (isDeclarationOnlyRules(node)) {
1228
- for (const child of node.value) {
1229
- if (isNode(child, 'Declaration')) {
1230
- stream.push(child);
1231
- }
1232
- }
1233
- }
1234
- }
1235
- for (const node of stream) {
1236
- if (!isNode(node, 'Declaration')) {
1237
- continue;
1238
- }
1239
- const name = String(node.value.name);
1240
- const assign = String(node.options.normalizedFromAssign ?? '');
1241
- const merged = isMergedAssign(assign);
1242
- if (!merged) {
1243
- mergedAnchorByName.delete(name);
1244
- if (node.visible) {
1245
- lastVisibleByName.set(name, node);
1246
- }
1247
- continue;
1248
- }
1249
- normalizeMergedDeclarationValue(node);
1250
- const prior = lastVisibleByName.get(name);
1251
- if (prior && prior !== node && prior.parent !== node.parent) {
1252
- composeMergedValue(node, prior, assign);
1253
- }
1254
- const existingAnchor = mergedAnchorByName.get(name);
1255
- if (existingAnchor && existingAnchor !== node && isNode(existingAnchor, 'Declaration')) {
1256
- existingAnchor.value.value = node.value.value.copy(true);
1257
- if (!existingAnchor.value.important && node.value.important) {
1258
- existingAnchor.value.important = node.value.important;
1259
- }
1260
- node.removeFlag(F_VISIBLE);
1261
- if (existingAnchor.visible) {
1262
- lastVisibleByName.set(name, existingAnchor);
1263
- }
1264
- continue;
1265
- }
1266
- mergedAnchorByName.set(name, node);
1267
- if (node.visible) {
1268
- lastVisibleByName.set(name, node);
1269
- }
1270
- }
1271
- }
1272
- /**
1273
- * Normalize call-produced declaration-only Rules ordering so declarations
1274
- * emitted from late-evaluated calls (e.g. each/$for) appear before nested
1275
- * rulesets/at-rules in the same parent Rules container.
1276
- *
1277
- * This runs after queue evaluation to avoid mutating rule indices mid-eval.
1278
- */
1279
- _normalizeCallDeclarationRulesOrder(rules) {
1280
- const firstNestedIdx = rules.value.findIndex(n => isNode(n, ['Ruleset', 'AtRule']));
1281
- if (firstNestedIdx < 0) {
1282
- return;
1283
- }
1284
- const beforeNested = rules.value.slice(0, firstNestedIdx);
1285
- const afterNested = rules.value.slice(firstNestedIdx);
1286
- const shouldMove = (n) => {
1287
- if (!isNode(n, 'Rules')
1288
- || !isNode(n.sourceParent, 'Call')
1289
- || n.value.length === 0
1290
- || !n.value.every(child => isNode(child, ['Declaration', 'Comment']))) {
1291
- return false;
1292
- }
1293
- const sourceName = n.sourceParent.value.name;
1294
- // Keep mixin-call declaration blocks in source order relative to nested rulesets.
1295
- if (isNode(sourceName, 'Reference')
1296
- && (sourceName.options?.type === 'mixin'
1297
- || sourceName.options?.type === 'mixin-ruleset'
1298
- || sourceName.options?.type === 'ruleset')) {
1299
- return false;
1300
- }
1301
- return true;
1302
- };
1303
- const moved = afterNested.filter(shouldMove);
1304
- if (moved.length === 0) {
1305
- return;
1306
- }
1307
- const remainder = afterNested.filter(n => !shouldMove(n));
1308
- rules.value = [...beforeNested, ...moved, ...remainder];
1309
- }
1310
- /**
1311
- * After preEval: ensure root on extend stack, build eval queue, run evaluation.
1312
- * Used by evalNode so that when eval() is called without preEval (e.g. jess compile()),
1313
- * we still have all rulesets registered and root set for extend lookups.
1314
- */
1315
- _afterPreEvalStep(rules, context) {
1316
- const isMainRoot = rules === context.root;
1317
- if (isMainRoot && context.extendRoots.extendRootStack.length === 0) {
1318
- if (!context.extendRoots.root) {
1319
- context.extendRoots.registerRoot(rules);
1320
- }
1321
- context.extendRoots.pushExtendRoot(rules);
1322
- }
1323
- if (rules.evaluated) {
1324
- return { rules, rulesToHoist: false };
1325
- }
1326
- if (rules === context.root) {
1327
- const map = new WeakMap();
1328
- context.documentOrderByRuleset = map;
1329
- this._assignDocumentOrderDepthFirst(rules, map, { value: 0 });
1330
- }
1331
- const evalQueue = this._buildEvalQueue(rules);
1332
- const maybeHoist = this._evaluateQueue(rules, evalQueue, context);
1333
- if (isThenable(maybeHoist)) {
1334
- return maybeHoist.then((rulesToHoist) => {
1335
- this._normalizeCallDeclarationRulesOrder(rules);
1336
- this._coalesceMergedDeclarations(rules);
1337
- return {
1338
- rules,
1339
- rulesToHoist
1340
- };
1341
- });
1342
- }
1343
- this._normalizeCallDeclarationRulesOrder(rules);
1344
- this._coalesceMergedDeclarations(rules);
1345
- return { rules, rulesToHoist: maybeHoist };
1346
- }
1347
- evalNode(context) {
1348
- const saved = this._snapshotContext(context);
1349
- context.rulesEvalStack.push(this.sourceNode);
1350
- const restoreContextOnError = () => {
1351
- context.rulesContext = saved.rulesContext;
1352
- if (saved.treeRoot !== undefined) {
1353
- context.treeRoot = saved.treeRoot;
1354
- }
1355
- if (saved.root !== undefined) {
1356
- context.root = saved.root;
1357
- }
1358
- const currentLength = context.extendRoots.extendRootStack.length;
1359
- if (saved.extendRootStackLength !== undefined && currentLength > saved.extendRootStackLength) {
1360
- while (context.extendRoots.extendRootStack.length > saved.extendRootStackLength) {
1361
- context.extendRoots.popExtendRoot();
1362
- }
1363
- }
1364
- if (context.rulesEvalStack[context.rulesEvalStack.length - 1] === this.sourceNode) {
1365
- context.rulesEvalStack.pop();
1366
- }
1367
- context.depth--;
1368
- };
1369
- let pipeResult;
1370
- try {
1371
- pipeResult = pipe(() => {
1372
- this._setupContextForRules(context, this);
1373
- // Run preEval first if not yet run (e.g. when jess compile() calls eval() without preEval).
1374
- // preEval registers the root and all nested rulesets so extend lookups find targets in child roots (e.g. .ma inside @media).
1375
- const runPreEvalIfNeeded = (rules) => {
1376
- if (rules.preEvaluated) {
1377
- return rules;
1378
- }
1379
- const result = rules.preEval(context);
1380
- return isThenable(result) ? result : result;
1381
- };
1382
- const rulesAfterPreEval = runPreEvalIfNeeded(this);
1383
- const afterPreEval = (rules) => {
1384
- // 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).
1385
- if (context.rulesEvalStack.length === 1) {
1386
- context.root = rules;
1387
- }
1388
- return this._afterPreEvalStep(rules, context);
1389
- };
1390
- if (isThenable(rulesAfterPreEval)) {
1391
- return rulesAfterPreEval.then(afterPreEval);
1392
- }
1393
- return afterPreEval(rulesAfterPreEval);
1394
- }, ({ rules }) => {
1395
- // Note: Rulesets from imported Rules are already registered to their own treeRoot
1396
- // during preEval when the imported Rules node is evaluated. The extend search
1397
- // loops through allRoots, so it should find them. The _searchRulesChildrenForRulesets
1398
- // method in RulesetRegistry also searches imported Rules' registries.
1399
- // After all evaluation stages, check if any variables in the current Rules
1400
- // shadow readonly variables from imported Rules (compose type) at the same level
1401
- // Only check direct children of the Rules node, not nested variables (e.g., inside rulesets)
1402
- if (rules.rulesSet.length > 0) {
1403
- let currentRegistry = rules.getRegistry('declaration');
1404
- currentRegistry.indexPendingItems();
1405
- for (const entry of rules.rulesSet) {
1406
- if (entry.readonly) {
1407
- let importedRegistry = entry.node.getRegistry('declaration');
1408
- importedRegistry.indexPendingItems();
1409
- for (const [key, declarations] of importedRegistry.index) {
1410
- for (const decl of declarations) {
1411
- if (isNode(decl, 'VarDeclaration')) {
1412
- // Check if a variable with this name exists in the current Rules' registry
1413
- let currentDeclarations = currentRegistry.index.get(key);
1414
- if (currentDeclarations) {
1415
- for (const currentDecl of currentDeclarations) {
1416
- if (isNode(currentDecl, 'VarDeclaration') && !currentDecl.options?.setDefined) {
1417
- // Only throw if the variable is a direct child of the Rules node (same level)
1418
- // Nested variables (e.g., inside rulesets) are allowed to shadow
1419
- if (currentDecl.parent === rules) {
1420
- throw new ReferenceError(`"${key}" is readonly`);
1421
- }
1422
- }
1423
- }
1424
- }
1425
- }
1426
- }
1427
- }
1428
- }
1429
- }
1430
- }
1431
- // Check if we're at the outermost level BEFORE restoring context
1432
- // Only process extends at the TRUE outermost root (context.root)
1433
- // This ensures extends are processed AFTER all evaluation completes,
1434
- // including imports and nested Rules
1435
- const isOutermost = rules === context.root;
1436
- if (isOutermost) {
1437
- // Process all registered extends using the extend roots registry system
1438
- processExtends(context);
1439
- }
1440
- /** Restore contexts */
1441
- context.rulesContext = saved.rulesContext;
1442
- // Only restore context.treeRoot if saved.treeRoot is defined and we're not at the outermost level
1443
- // If saved.treeRoot is undefined, it means we're at the outermost level, so keep context.treeRoot as is
1444
- // This ensures extends evaluated during selector evaluation can still access the correct treeRoot
1445
- if (saved.treeRoot !== undefined && !isOutermost) {
1446
- context.treeRoot = saved.treeRoot;
1447
- }
1448
- // Only restore context.root if we're not at the outermost level (where it was originally set)
1449
- // If saved.root is undefined, it means we're at the outermost level, so keep context.root as is
1450
- if (saved.root !== undefined && !isOutermost) {
1451
- context.root = saved.root;
1452
- }
1453
- // Restore extend root stack to its original length (if we're not the main root)
1454
- // The main root manages its own push/pop, but nested Rules should restore the stack
1455
- if (!isOutermost && saved.extendRootStackLength !== undefined) {
1456
- const currentLength = context.extendRoots.extendRootStack.length;
1457
- if (currentLength > saved.extendRootStackLength) {
1458
- // Pop any extend roots that were pushed during this Rules evaluation
1459
- while (context.extendRoots.extendRootStack.length > saved.extendRootStackLength) {
1460
- context.extendRoots.popExtendRoot();
1461
- }
1462
- }
1463
- }
1464
- // Pop extend root if we pushed it (check if this is still the root)
1465
- if (rules === context.root) {
1466
- context.extendRoots.popExtendRoot();
1467
- }
1468
- context.rulesEvalStack.pop();
1469
- context.depth--;
1470
- return rules;
1471
- });
1472
- }
1473
- catch (error) {
1474
- restoreContextOnError();
1475
- throw error;
1476
- }
1477
- if (isThenable(pipeResult)) {
1478
- return pipeResult.catch((error) => {
1479
- restoreContextOnError();
1480
- throw error;
1481
- });
1482
- }
1483
- return pipeResult;
1484
- }
1485
- }
1486
- export const rules = defineType(Rules, 'Rules');
1487
- /**
1488
- * @todo - Will need lots of massaging, to resolve things like
1489
- * mixins which rely on variables which have interpolated names,
1490
- * and variables with interpolated names that rely on mixins.
1491
- *
1492
- * @note - Registration of declaration names and mixins / selectors
1493
- * should have already happened in pre-eval.
1494
- */
1495
- const NodeTypeToPriority = new Map([
1496
- /** First, resolve imports */
1497
- ['StyleImport', 4 /* Priority.Highest */],
1498
- /** Then, resolve calls */
1499
- ['Call', 3 /* Priority.High */],
1500
- /** Then, resolve declarations */
1501
- ['VarDeclaration', 2 /* Priority.Medium */],
1502
- ['Declaration', 2 /* Priority.Medium */],
1503
- /** Then... */
1504
- ['Mixin', 1 /* Priority.Low */],
1505
- ['Ruleset', 1 /* Priority.Low */],
1506
- /** Extend should evaluate at the same priority as Ruleset to ensure it evaluates before nested rulesets */
1507
- ['Extend', 1 /* Priority.Low */],
1508
- /** AtRule (e.g., @media) should evaluate at the same priority as Ruleset to preserve source order */
1509
- ['AtRule', 1 /* Priority.Low */]
1510
- /** Then, everything else? */
1511
- ]);
1512
- /**
1513
- * Returns a plain JS function for calling a set of mixins
1514
- *
1515
- * This is in the same file as Rules to avoid circular dependencies.
1516
- *
1517
- * @note this will be called as a result after a mixin find is executed.
1518
- */
1519
- export function getFunctionFromMixins(mixins) {
1520
- let mixinArr = isArray(mixins) ? mixins : [mixins];
1521
- async function returnFunc(...args) {
1522
- const mixinLength = mixinArr.length;
1523
- let mixinCandidates = [];
1524
- let evalCandidates;
1525
- // When called via callWithContext, 'this' is functionThis, not Context
1526
- // We need to extract the context from functionThis or use a fallback
1527
- let thisContext;
1528
- if (this instanceof Context) {
1529
- thisContext = this;
1530
- }
1531
- else if (this && typeof this === 'object' && 'context' in this) {
1532
- // This is functionThis from callWithContext
1533
- thisContext = this.context;
1534
- }
1535
- else {
1536
- thisContext = new Context();
1537
- }
1538
- let caller = thisContext.caller;
1539
- let sourceParent = caller?.value.name instanceof Node
1540
- ? caller.value.name.sourceParent
1541
- : caller?.sourceParent;
1542
- let nodeArgs = [];
1543
- const savedRulesContext = thisContext.rulesContext;
1544
- const argEvalRulesContext = caller?.rulesParent ?? caller?.sourceRulesParent ?? savedRulesContext;
1545
- thisContext.rulesContext = argEvalRulesContext;
1546
- try {
1547
- for (let arg of args) {
1548
- /**
1549
- * I think they should always be nodes?
1550
- * But leaving this for future expansion.
1551
- */
1552
- if (isNode(arg)) {
1553
- // IMPORTANT: Do not evaluate VarDeclaration args (named arguments) here.
1554
- // Evaluating them can register/override variables in the current scope.
1555
- // They should only be used for parameter binding.
1556
- if (isNode(arg, 'VarDeclaration')) {
1557
- const cloned = arg.copy(true, freezeChildren);
1558
- cloned.frozen = true;
1559
- nodeArgs.push(cloned);
1560
- continue;
1561
- }
1562
- try {
1563
- const evald = await arg.clonedEval(thisContext);
1564
- if (isNode(evald, 'Rest')) {
1565
- const restValue = evald.value;
1566
- if (isNode(restValue, 'Sequence') || isNode(restValue, 'List')) {
1567
- for (const restArg of restValue.value) {
1568
- const frozenRestArg = restArg.copy(true, freezeChildren);
1569
- frozenRestArg.frozen = true;
1570
- nodeArgs.push(frozenRestArg);
1571
- }
1572
- continue;
1573
- }
1574
- }
1575
- evald.frozen = true;
1576
- nodeArgs.push(evald);
1577
- }
1578
- catch (error) {
1579
- throw error;
1580
- }
1581
- }
1582
- else {
1583
- nodeArgs.push(cast(arg));
1584
- }
1585
- }
1586
- }
1587
- finally {
1588
- thisContext.rulesContext = savedRulesContext;
1589
- }
1590
- /**
1591
- * Check named and positional arguments
1592
- * against mixins, to see which ones match.
1593
- * (Any mixin with a mis-match of
1594
- * arguments fails.)
1595
- */
1596
- const normalizeBoundLeadingItemWhitespace = (node) => {
1597
- if (!isNode(node, ['List', 'Sequence'])) {
1598
- return;
1599
- }
1600
- const items = node.value;
1601
- if (items.length > 0) {
1602
- items[0].pre = 0;
1603
- }
1604
- for (const item of items) {
1605
- if (isNode(item, ['List', 'Sequence'])) {
1606
- normalizeBoundLeadingItemWhitespace(item);
1607
- }
1608
- }
1609
- };
1610
- for (let i = 0; i < mixinLength; i++) {
1611
- let mixin = mixinArr[i];
1612
- let isPlainRule = isNode(mixin, 'Rules');
1613
- let paramLength = isPlainRule ? 0 : mixin.value.params?.length ?? 0;
1614
- if (!paramLength) {
1615
- /** Exit early if args were passed in, but no args are possible */
1616
- if (args.length) {
1617
- continue;
1618
- }
1619
- mixinCandidates.push(mixin);
1620
- }
1621
- else {
1622
- /** The mixin has parameters, so let's check args to see if there's a match */
1623
- let params = mixin.value.params.copy(true);
1624
- const hasRestParamOriginal = mixin.value.params.value.some(p => isNode(p, 'Rest'));
1625
- const maxPositionalArgs = hasRestParamOriginal ? Number.POSITIVE_INFINITY : params.length;
1626
- let positions = params.length;
1627
- let requiredPositions = 0;
1628
- for (let param of params.value) {
1629
- if (isNode(param, 'VarDeclaration')) {
1630
- if (param.value.value instanceof Nil) {
1631
- requiredPositions++;
1632
- }
1633
- }
1634
- else if (isNode(param, 'Any') && param.options.role === 'property') {
1635
- // Any with role: 'property' is a parameter without default (consistent with variable names)
1636
- requiredPositions++;
1637
- }
1638
- else if (!isNode(param, 'Rest')) {
1639
- requiredPositions++;
1640
- }
1641
- }
1642
- let argPos = 0;
1643
- let match = true;
1644
- for (let i = 0; i < positions; i++) {
1645
- let arg = nodeArgs[argPos];
1646
- if (!arg) {
1647
- continue;
1648
- }
1649
- let param;
1650
- let argValue;
1651
- if (isNode(arg, 'VarDeclaration')) {
1652
- param = params.value.find((p) => {
1653
- if (isNode(p, 'VarDeclaration')) {
1654
- return p.value.name.valueOf() === arg.value.name.valueOf();
1655
- }
1656
- if (isNode(p, 'Any') && p.options.role === 'property') {
1657
- return p.valueOf() === arg.value.name.valueOf();
1658
- }
1659
- return false;
1660
- });
1661
- if (param) {
1662
- argValue = arg.value.value;
1663
- }
1664
- else {
1665
- match = false;
1666
- break;
1667
- }
1668
- }
1669
- else {
1670
- param = params.value[i];
1671
- if (!param) {
1672
- match = false;
1673
- break;
1674
- }
1675
- argValue = arg;
1676
- }
1677
- if (!param) {
1678
- match = false;
1679
- break;
1680
- }
1681
- if (isNode(param, 'VarDeclaration')) {
1682
- const boundValue = argValue.copy(true, freezeChildren);
1683
- boundValue.frozen = true;
1684
- normalizeBoundLeadingItemWhitespace(boundValue);
1685
- param.value.value = boundValue;
1686
- }
1687
- else if (isNode(param, 'Any') && param.options.role === 'property') {
1688
- // Convert Any with role: 'property' to VarDeclaration for registration
1689
- const boundValue = argValue.copy(true, freezeChildren);
1690
- boundValue.frozen = true;
1691
- normalizeBoundLeadingItemWhitespace(boundValue);
1692
- const varDecl = new VarDeclaration({
1693
- name: param,
1694
- value: boundValue
1695
- }, { paramVar: true });
1696
- params.value[i] = varDecl;
1697
- }
1698
- else if (isNode(param, 'Rest')) {
1699
- /** We assume that the rest args are values */
1700
- const rest = nodeArgs.slice(argPos).map((restArg) => {
1701
- const cloned = restArg.copy(true, freezeChildren);
1702
- cloned.frozen = true;
1703
- return cloned;
1704
- });
1705
- /** Create a new variable with the rest name */
1706
- params.value[i] = new VarDeclaration({
1707
- name: new Any(param.value ? `${param.value}` : `rest${i}`, { role: 'property' }),
1708
- value: new Sequence(rest)
1709
- });
1710
- /** Check a pattern-matching node */
1711
- }
1712
- else {
1713
- if (param.compare(argValue) !== 0) {
1714
- /** This mixin is not a match */
1715
- match = false;
1716
- break;
1717
- }
1718
- }
1719
- argPos++;
1720
- }
1721
- const positionalArgCount = nodeArgs.filter(argNode => !isNode(argNode, 'VarDeclaration')).length;
1722
- if (positionalArgCount > maxPositionalArgs) {
1723
- continue;
1724
- }
1725
- /**
1726
- * Now we can check remaining positional matches
1727
- * against the remaining parameters.
1728
- */
1729
- if (argPos < requiredPositions) {
1730
- /** This mixin is not a match */
1731
- continue;
1732
- }
1733
- if (nodeArgs.length > 1 && params.value.length === 1 && requiredPositions === 1) {
1734
- // Less should not match single required-parameter overloads against extra positional args.
1735
- continue;
1736
- }
1737
- if (match) {
1738
- /** Make a shallow copy to attach our resolved params (w/ args) */
1739
- let originalMixin = mixin;
1740
- mixin = mixin.copy();
1741
- originalMixin.parent.adopt(mixin);
1742
- mixin.value.params = params;
1743
- mixinCandidates.push(mixin);
1744
- }
1745
- }
1746
- }
1747
- /**
1748
- * Alright, we have mixin candidates (mixins that match
1749
- * by arity, pattern, and/or named arguments), now what?
1750
- *
1751
- * First, let's make an evaluation order that evaluates
1752
- * default guards last.
1753
- */
1754
- let hasDefault = false;
1755
- const guardContainsDefault = (node) => {
1756
- if (!node) {
1757
- return false;
1758
- }
1759
- if (node.type === 'DefaultGuard') {
1760
- return true;
1761
- }
1762
- if (node.type === 'Call') {
1763
- const callName = String(node.value?.name?.valueOf?.() ?? node.value?.name ?? '');
1764
- if (callName === 'default' || callName === '??') {
1765
- return true;
1766
- }
1767
- }
1768
- const value = node.value;
1769
- if (Array.isArray(value)) {
1770
- for (const item of value) {
1771
- if (isNode(item) && guardContainsDefault(item)) {
1772
- return true;
1773
- }
1774
- }
1775
- return false;
1776
- }
1777
- if (value && typeof value === 'object') {
1778
- const record = value;
1779
- for (const item of Object.values(record)) {
1780
- if (isNode(item) && guardContainsDefault(item)) {
1781
- return true;
1782
- }
1783
- if (Array.isArray(item)) {
1784
- for (const child of item) {
1785
- if (isNode(child) && guardContainsDefault(child)) {
1786
- return true;
1787
- }
1788
- }
1789
- }
1790
- }
1791
- }
1792
- return false;
1793
- };
1794
- const hasFailedGuardAncestor = (node) => {
1795
- let current = node.parent;
1796
- while (current) {
1797
- if (isNode(current, 'Ruleset')) {
1798
- const guardNode = current.value.guard;
1799
- if (guardNode instanceof Nil) {
1800
- return true;
1801
- }
1802
- }
1803
- current = current.parent;
1804
- }
1805
- return false;
1806
- };
1807
- evalCandidates = mixinCandidates
1808
- .filter((candidate) => {
1809
- const inStack = thisContext.rulesEvalStack.includes(candidate.value.rules.sourceNode);
1810
- const blockedByFailedGuardAncestor = hasFailedGuardAncestor(candidate);
1811
- return !inStack && !blockedByFailedGuardAncestor;
1812
- })
1813
- .map((candidate) => {
1814
- const hasDefaultGuard = Boolean(candidate.options?.hasDefault) || guardContainsDefault(candidate.value.guard);
1815
- if (hasDefaultGuard) {
1816
- candidate.options ??= {};
1817
- candidate.options.hasDefault = true;
1818
- hasDefault = true;
1819
- }
1820
- return candidate;
1821
- });
1822
- if (hasDefault) {
1823
- /** There is a default guard, so sort candidates */
1824
- evalCandidates = evalCandidates.slice(0).sort((a, b) => {
1825
- let aDefault = a.options?.hasDefault;
1826
- let bDefault = b.options?.hasDefault;
1827
- /** No guard (or is just a plain ruleset) */
1828
- if (!aDefault && !bDefault) {
1829
- return 0;
1830
- }
1831
- if (!aDefault) {
1832
- return -1;
1833
- }
1834
- if (!bDefault) {
1835
- return 1;
1836
- }
1837
- return 0;
1838
- });
1839
- }
1840
- if (evalCandidates.length === 0) {
1841
- throw new ReferenceError('No matching mixins found.');
1842
- }
1843
- /**
1844
- * Now we have a set of mixins that can return rulesets,
1845
- * but first we need to create a new scope for each mixin,
1846
- * and create variable declarations for each parameter.
1847
- */
1848
- let outputRules = [];
1849
- const restrictMixinOutputLookup = thisContext.leakyRules !== true;
1850
- const originatesFromReferenceImport = (node) => {
1851
- const queue = [node, node.sourceNode, node.sourceParent];
1852
- const seen = new Set();
1853
- while (queue.length > 0) {
1854
- const current = queue.shift();
1855
- if (!current || seen.has(current)) {
1856
- continue;
1857
- }
1858
- seen.add(current);
1859
- if (current.type === 'StyleImport') {
1860
- const importOptions = current.options?.importOptions;
1861
- if (importOptions?.reference === true || importOptions?._dedupe === true) {
1862
- return true;
1863
- }
1864
- }
1865
- queue.push(current.sourceNode, current.sourceParent, current.parent);
1866
- }
1867
- return false;
1868
- };
1869
- const clearReferenceModeForMixinOutput = (node) => {
1870
- if (originatesFromReferenceImport(node)) {
1871
- return;
1872
- }
1873
- if (node.options?.referenceMode === true) {
1874
- node.options.referenceMode = false;
1875
- }
1876
- const nestedRules = node.value?.rules;
1877
- if (nestedRules && isNode(nestedRules, 'Rules')) {
1878
- clearReferenceModeForMixinOutput(nestedRules);
1879
- }
1880
- const children = node.value;
1881
- if (Array.isArray(children)) {
1882
- for (const child of children) {
1883
- if (isNode(child, ['Rules', 'Ruleset', 'AtRule'])) {
1884
- clearReferenceModeForMixinOutput(child);
1885
- }
1886
- }
1887
- }
1888
- };
1889
- const resetEvalStateDeep = (node) => {
1890
- node.preEvaluated = false;
1891
- node.evaluated = false;
1892
- if (isNode(node, 'Ruleset')) {
1893
- const rulesetNode = node;
1894
- const selector = rulesetNode.value.selector;
1895
- const sourceSelector = selector?.sourceNode;
1896
- if (sourceSelector && isNode(sourceSelector)) {
1897
- // Recover definition-time selector shape (e.g. raw `&`) so call-site
1898
- // preEval can rebuild selectors in the caller's frame.
1899
- rulesetNode.value.selector = sourceSelector.copy(true);
1900
- rulesetNode.value.selector.sourceNode = sourceSelector;
1901
- }
1902
- }
1903
- if (isNode(node, 'Ampersand')) {
1904
- // Ampersands cloned from mixin definitions can carry a stale selector container
1905
- // that points at definition-time selectors. Clear it so call-site frames rebind `&`.
1906
- const ampNode = node;
1907
- ampNode._selectorContainer = undefined;
1908
- ampNode._storedSelector = undefined;
1909
- }
1910
- const value = node.value;
1911
- if (Array.isArray(value)) {
1912
- for (const child of value) {
1913
- if (isNode(child)) {
1914
- resetEvalStateDeep(child);
1915
- }
1916
- }
1917
- return;
1918
- }
1919
- if (value && typeof value === 'object') {
1920
- const record = value;
1921
- for (const propValue of Object.values(record)) {
1922
- if (isNode(propValue)) {
1923
- resetEvalStateDeep(propValue);
1924
- continue;
1925
- }
1926
- if (Array.isArray(propValue)) {
1927
- for (const item of propValue) {
1928
- if (isNode(item)) {
1929
- resetEvalStateDeep(item);
1930
- }
1931
- }
1932
- }
1933
- }
1934
- }
1935
- };
1936
- const getRootSourceRules = (rules) => {
1937
- let current = rules;
1938
- const seen = new Set();
1939
- while (current.sourceNode && isNode(current.sourceNode, 'Rules')) {
1940
- const next = current.sourceNode;
1941
- if (next === current || seen.has(next)) {
1942
- break;
1943
- }
1944
- seen.add(current);
1945
- current = next;
1946
- }
1947
- return current;
1948
- };
1949
- const DEF_FALSE_EITHER = -1;
1950
- const DEF_NONE = 0;
1951
- const DEF_TRUE = 1;
1952
- const DEF_FALSE = 2;
1953
- const pendingDefaultCandidates = [];
1954
- let hasDefNoneCandidate = false;
1955
- const evaluateCandidateOutput = async (candidate, rules, outerRules, params) => {
1956
- const currentCall = thisContext.callStack.at(-1);
1957
- // to prevent infinite loops (e.g., .recursion { .recursion(); })
1958
- if (currentCall && thisContext.callMap.add(currentCall, params)) {
1959
- // Recursive call detected - skip this candidate (don't add to outputRules)
1960
- // This allows other candidates to still match
1961
- return;
1962
- }
1963
- try {
1964
- let newRules;
1965
- if (!outerRules) {
1966
- candidate.parent.adopt(rules);
1967
- newRules = await rules.eval(thisContext);
1968
- }
1969
- else {
1970
- // Evaluate in the wrapper scope so params are visible, but preserve the wrapper's
1971
- // rulesVisibility (it keeps VarDeclaration public). Overwriting visibility here can
1972
- // hide param vars from registry-based lookup.
1973
- outerRules.push(...rules.value);
1974
- newRules = await outerRules.eval(thisContext);
1975
- }
1976
- candidate.parent.adopt(newRules);
1977
- // Rules should have index from eval, but ensure it matches candidate for sorting
1978
- newRules.index = candidate.index;
1979
- // Visibility should be preserved by Rules.eval - no need to set it explicitly here
1980
- // The eval'd rules should already have their nodes registered
1981
- // Ensure the registry is indexed before checking
1982
- // Mark output Rules as mixin output - accessible only when lookup has a target
1983
- newRules.options.isMixinOutput = restrictMixinOutputLookup;
1984
- newRules.options.referenceMode = false;
1985
- clearReferenceModeForMixinOutput(newRules);
1986
- if (thisContext.treeContext?.file) {
1987
- /**
1988
- * NOTE (debug policy):
1989
- * `hasParamVar` / `hasNestedMixin` visibility branching was removed and
1990
- * should NOT be reintroduced.
1991
- *
1992
- * If this causes regressions, fix lookup/parenting behavior instead:
1993
- * - declaration/mixin registry traversal semantics
1994
- * - sourceParent/rulesParent/sourceRulesParent propagation
1995
- *
1996
- * Do not solve those regressions by adding new visibility heuristics based on
1997
- * "contains param vars" or "contains nested mixins".
1998
- */
1999
- newRules.options.rulesVisibility ??= {};
2000
- newRules.options.rulesVisibility.VarDeclaration = 'private';
2001
- }
2002
- outputRules.push(newRules);
2003
- }
2004
- catch (error) {
2005
- // If recursion was detected (ReferenceError), skip this candidate
2006
- // This allows other candidates to still match
2007
- if (error instanceof ReferenceError && error.message?.includes('Recursive mixin call')) {
2008
- // Skip this candidate - recursion detected
2009
- return;
2010
- }
2011
- // Re-throw other errors
2012
- throw error;
2013
- }
2014
- finally {
2015
- if (currentCall) {
2016
- thisContext.callMap.delete(currentCall);
2017
- }
2018
- }
2019
- };
2020
- for (let candidate of evalCandidates) {
2021
- if (isNode(candidate, 'Ruleset')) {
2022
- // For Rulesets, guard was already evaluated at definition time in Ruleset.evalNode
2023
- // guard === undefined means passed, guard instanceof Nil means failed
2024
- const rulesetGuard = candidate.value.guard;
2025
- if (rulesetGuard instanceof Nil) {
2026
- // Guard failed at definition time - skip this ruleset
2027
- continue;
2028
- }
2029
- const candidateRules = candidate.value.rules;
2030
- const sourceRules = getRootSourceRules(candidateRules);
2031
- let rules = sourceRules.clone(true);
2032
- resetEvalStateDeep(rules);
2033
- /** Adopt for lookup, then adopt for sorting */
2034
- candidate.parent.adopt(rules);
2035
- rules.sourceParent = sourceParent;
2036
- let originalContext = thisContext.rulesContext;
2037
- thisContext.rulesContext = rules;
2038
- rules = await rules.eval(thisContext);
2039
- thisContext.rulesContext = originalContext;
2040
- candidate.parent.adopt(rules);
2041
- // Rules should have index from eval, but ensure it matches candidate for sorting
2042
- rules.index = candidate.index;
2043
- // Skip empty Rules (e.g., containing only invisible nodes like comments)
2044
- // Mark output Rules as mixin output - accessible only when lookup has a target
2045
- rules.options.isMixinOutput = restrictMixinOutputLookup;
2046
- rules.options.referenceMode = false;
2047
- clearReferenceModeForMixinOutput(rules);
2048
- outputRules.push(rules);
2049
- continue;
2050
- }
2051
- // Less detached rulesets are represented as anonymous mixins (name is undefined).
2052
- // Calling `@rulesetVar();` should *unlock* the rules into scope (including mixin definitions),
2053
- // not eagerly execute/flatten them.
2054
- if (!candidate.value.name && !candidate.value.params && !candidate.value.guard) {
2055
- const sourceRules = getRootSourceRules(candidate.value.rules);
2056
- let unlocked = sourceRules.clone(true);
2057
- resetEvalStateDeep(unlocked);
2058
- candidate.parent.adopt(unlocked);
2059
- unlocked.sourceParent = sourceParent ?? caller;
2060
- // Mark as mixin output; caller may override when leakyRules=true
2061
- unlocked.options.isMixinOutput = restrictMixinOutputLookup;
2062
- unlocked.options.referenceMode = false;
2063
- clearReferenceModeForMixinOutput(unlocked);
2064
- unlocked.index = candidate.index;
2065
- outputRules.push(unlocked);
2066
- continue;
2067
- }
2068
- let rules = candidate.value.rules;
2069
- /** Create new rules, and add the candidate rules, to add to scope */
2070
- rules = rules.clone(true);
2071
- resetEvalStateDeep(rules);
2072
- // During mixin evaluation, local declarations must be directly visible in the current scope
2073
- // so they properly shadow outer params/variables while the body executes.
2074
- rules.options.rulesVisibility ??= {};
2075
- rules.options.rulesVisibility.VarDeclaration = 'public';
2076
- candidate.parent.adopt(rules);
2077
- rules.sourceParent = sourceParent;
2078
- // Don't set index before evaluation - let evaluation assign the correct index
2079
- /**
2080
- * If we have params or a guard, we need to create a wrapper rules object,
2081
- * so that the lookups of params and guard do not look at the cloned rules,
2082
- * but instead look upwards / outwards.
2083
- */
2084
- let outerRules;
2085
- /** Now we need to add our parameters, if any */
2086
- let params = candidate.value.params;
2087
- if (params) {
2088
- outerRules = Rules.create([], {
2089
- rulesVisibility: {
2090
- Ruleset: 'public',
2091
- Declaration: 'public',
2092
- VarDeclaration: 'public',
2093
- Mixin: 'public'
2094
- }
2095
- });
2096
- (thisContext.rulesContext ?? candidate.parent).adopt(outerRules);
2097
- outerRules.index = candidate.index;
2098
- for (let i = 0; i < params.value.length; i++) {
2099
- let param = params.value[i];
2100
- if (isNode(param, 'Rest')) {
2101
- // Rest parameters need to be converted to VarDeclaration for registration
2102
- // Auto-generate a name if Rest doesn't have one (Less allows unnamed rest params)
2103
- let restName;
2104
- if (typeof param.value === 'string') {
2105
- restName = param.value;
2106
- }
2107
- else {
2108
- // Auto-generate name: "rest", "rest1", "rest2", etc. based on position
2109
- // Check if there are other rest params to avoid conflicts
2110
- let restCount = 0;
2111
- for (let j = 0; j < i; j++) {
2112
- const p = params.value[j];
2113
- if (isNode(p, 'Rest')) {
2114
- restCount++;
2115
- }
2116
- }
2117
- restName = restCount === 0 ? 'rest' : `rest${restCount + 1}`;
2118
- }
2119
- // Convert Rest to VarDeclaration so it can be registered and referenced.
2120
- // If matching did not populate a node value, default to an empty sequence
2121
- // (not a literal name/Nil), so @tail... behaves as "no remaining args".
2122
- const restValue = isNode(param.value)
2123
- ? param.value
2124
- : (thisContext.treeContext?.file
2125
- ? new Sequence([])
2126
- : new Any(restName, { role: 'property' }));
2127
- const restVarDecl = new VarDeclaration({
2128
- name: new Any(restName, { role: 'property' }),
2129
- value: restValue
2130
- }, { paramVar: true });
2131
- // Replace Rest with VarDeclaration in params
2132
- params.value[i] = restVarDecl;
2133
- param = restVarDecl;
2134
- }
2135
- if (isNode(param, 'VarDeclaration')) {
2136
- // Assign negative indices so they're conceptually "before" the rules and found first
2137
- if (param.index === undefined) {
2138
- // Use negative indices starting from -1, -2, etc. so they sort before regular rules
2139
- param.index = -(i + 1);
2140
- }
2141
- // Mark as parameter var so it can be stripped from mixin output after evaluation.
2142
- param.options ??= {};
2143
- param.options.paramVar = true;
2144
- // Keep parameter vars lookupable but hidden in normal output.
2145
- // They still render in tests that set Node.fullRender=true.
2146
- param.removeFlag(F_VISIBLE);
2147
- outerRules.push(param);
2148
- }
2149
- // Note: Any with role: 'property' should have been converted to VarDeclaration during matching
2150
- // If we see one here, it's an error - params should all be VarDeclaration by now
2151
- }
2152
- const shouldDefineArguments = Boolean(thisContext.treeContext?.file);
2153
- if (shouldDefineArguments) {
2154
- const argumentsArgs = [];
2155
- const argumentsDecl = new VarDeclaration({
2156
- name: new Any('arguments', { role: 'property' }),
2157
- value: new Sequence(argumentsArgs)
2158
- }, { readonly: true, paramVar: true });
2159
- argumentsDecl.removeFlag(F_VISIBLE);
2160
- outerRules.push(argumentsDecl);
2161
- const paramValues = params?.value
2162
- .filter((p) => isNode(p, 'VarDeclaration'))
2163
- .map(p => p.value.value);
2164
- const argumentNodes = (paramValues && paramValues.length > 0) ? paramValues : nodeArgs;
2165
- for (const argNode of argumentNodes) {
2166
- // If a Rest param collected args into a Sequence, spread its items
2167
- // so @arguments reflects the actual argument count
2168
- if (isNode(argNode, 'Sequence')) {
2169
- for (const item of argNode.value) {
2170
- const cloned = item.copy(true, freezeChildren);
2171
- cloned.frozen = true;
2172
- argumentsArgs.push(cloned);
2173
- }
2174
- }
2175
- else {
2176
- const cloned = argNode.copy(true, freezeChildren);
2177
- cloned.frozen = true;
2178
- argumentsArgs.push(cloned);
2179
- }
2180
- }
2181
- }
2182
- }
2183
- /** Now we can evaluate our guards, if any */
2184
- let guard = candidate.value.guard?.copy(true);
2185
- let passes = true;
2186
- let rulesContext = thisContext.rulesContext;
2187
- // Call-time resolution is handled by the current context.rulesContext
2188
- thisContext.rulesContext = outerRules ?? rules;
2189
- try {
2190
- if (guard) {
2191
- outerRules ??= Rules.create([]);
2192
- outerRules.adopt(guard);
2193
- candidate.parent.adopt(outerRules);
2194
- /** Allow lookup on the inherited rules */
2195
- passes = false;
2196
- let guardPasses = false;
2197
- let defaultGroup = DEF_FALSE_EITHER;
2198
- if (hasDefault) {
2199
- const originalIsDefault = thisContext.isDefault;
2200
- const evalWithDefault = async (isDefaultValue) => {
2201
- const probeGuard = candidate.value.guard?.copy(true);
2202
- if (!probeGuard) {
2203
- return false;
2204
- }
2205
- outerRules.adopt(probeGuard);
2206
- thisContext.isDefault = isDefaultValue;
2207
- const probeResult = await probeGuard.eval(thisContext);
2208
- return probeResult instanceof Bool && probeResult.value === true;
2209
- };
2210
- const passWhenDefaultFalse = await evalWithDefault(false);
2211
- const passWhenDefaultTrue = await evalWithDefault(true);
2212
- thisContext.isDefault = originalIsDefault;
2213
- if (passWhenDefaultFalse || passWhenDefaultTrue) {
2214
- passes = true;
2215
- if (passWhenDefaultFalse && passWhenDefaultTrue) {
2216
- defaultGroup = DEF_NONE;
2217
- hasDefNoneCandidate = true;
2218
- }
2219
- else {
2220
- defaultGroup = passWhenDefaultTrue ? DEF_TRUE : DEF_FALSE;
2221
- }
2222
- }
2223
- guardPasses = passes;
2224
- if (passes) {
2225
- pendingDefaultCandidates.push({
2226
- candidate: candidate,
2227
- rules,
2228
- outerRules,
2229
- params,
2230
- group: defaultGroup
2231
- });
2232
- }
2233
- }
2234
- else {
2235
- /** All nodes need context to be evaluated */
2236
- thisContext.isDefault = false;
2237
- guard = await guard.eval(thisContext);
2238
- /** Less guards only pass on explicit Bool(true), never JS truthiness. */
2239
- guardPasses = guard instanceof Bool && guard.value === true;
2240
- if (guardPasses) {
2241
- passes = true;
2242
- hasDefNoneCandidate = true;
2243
- }
2244
- }
2245
- }
2246
- if (!passes) {
2247
- continue;
2248
- }
2249
- if (!guard || !hasDefault) {
2250
- // Non-default candidates are equivalent to Less's defNone group
2251
- // (match regardless of default() assumption), so they suppress ambiguity.
2252
- hasDefNoneCandidate = true;
2253
- }
2254
- if (guard && hasDefault) {
2255
- continue;
2256
- }
2257
- await evaluateCandidateOutput(candidate, rules, outerRules, params);
2258
- }
2259
- finally {
2260
- thisContext.rulesContext = rulesContext;
2261
- }
2262
- }
2263
- if (pendingDefaultCandidates.length > 0) {
2264
- let defTrueCount = 0;
2265
- let defFalseCount = 0;
2266
- for (const pending of pendingDefaultCandidates) {
2267
- if (pending.group === DEF_TRUE) {
2268
- defTrueCount++;
2269
- }
2270
- else if (pending.group === DEF_FALSE) {
2271
- defFalseCount++;
2272
- }
2273
- else if (pending.group === DEF_NONE) {
2274
- hasDefNoneCandidate = true;
2275
- }
2276
- }
2277
- const defaultResult = hasDefNoneCandidate ? DEF_FALSE : DEF_TRUE;
2278
- if (!hasDefNoneCandidate && (defTrueCount + defFalseCount) > 1) {
2279
- throw new ReferenceError('Ambiguous use of default() while matching mixins.');
2280
- }
2281
- for (const pending of pendingDefaultCandidates) {
2282
- if (pending.group !== DEF_NONE && pending.group !== defaultResult) {
2283
- continue;
2284
- }
2285
- const previousRulesContext = thisContext.rulesContext;
2286
- thisContext.rulesContext = pending.outerRules ?? pending.rules;
2287
- try {
2288
- await evaluateCandidateOutput(pending.candidate, pending.rules, pending.outerRules, pending.params);
2289
- }
2290
- finally {
2291
- thisContext.rulesContext = previousRulesContext;
2292
- }
2293
- }
2294
- }
2295
- /**
2296
- * Now that we have output rules, sort them by
2297
- * their original order
2298
- */
2299
- outputRules.sort(comparePosition);
2300
- /** Create a rules wrapper - but optimize to avoid unnecessary nesting */
2301
- let output;
2302
- if (outputRules.length === 1) {
2303
- output = outputRules[0];
2304
- // Ensure single output rule is marked as mixin output
2305
- output.options.isMixinOutput = restrictMixinOutputLookup;
2306
- output.options.referenceMode = false;
2307
- clearReferenceModeForMixinOutput(output);
2308
- }
2309
- else {
2310
- /**
2311
- * Wrap these in rules marked as mixin output - accessible only when lookup has a target.
2312
- * This prevents mixin output from being searched by untargeted lookups.
2313
- */
2314
- output = Rules.create([], {
2315
- rulesVisibility: {
2316
- Ruleset: 'public',
2317
- Declaration: 'public',
2318
- VarDeclaration: 'public',
2319
- Mixin: 'public'
2320
- },
2321
- isMixinOutput: restrictMixinOutputLookup,
2322
- referenceMode: false
2323
- });
2324
- /**
2325
- * Add rules but keep their original parents for further lazy lookups.
2326
- * Ensure each rule has VarDeclaration: 'optional' before pushing (registerNode uses node's own rulesVisibility)
2327
- */
2328
- for (let i = 0; i < outputRules.length; i++) {
2329
- let rule = outputRules[i];
2330
- rule.frozen = true;
2331
- /** Set a sequential index for lookup sorting */
2332
- rule.index = i;
2333
- output.push(rule);
2334
- }
2335
- }
2336
- /**
2337
- * IMPORTANT: Do NOT force `output` to be evaluated here.
2338
- *
2339
- * Even though candidate rule bodies are usually evaluated during mixin execution, callers
2340
- * (e.g. `Call.evalNode`) rely on `.eval(context)` to finish evaluation. Marking these flags
2341
- * true can skip evaluation and leak unevaluated nodes (like `Call`) into serialization.
2342
- */
2343
- /** Now push all rules into the rules value */
2344
- if (this instanceof Context) {
2345
- output.index ??= this.ruleCounter++;
2346
- // If the output Rules is empty, return Nil instead
2347
- if (output.value.length === 0) {
2348
- return new Nil();
2349
- }
2350
- return output;
2351
- }
2352
- else {
2353
- const obj = output.toObject();
2354
- return obj;
2355
- }
2356
- }
2357
- return returnFunc;
2358
- }
2359
- //# sourceMappingURL=rules.js.map