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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (637) hide show
  1. package/lib/index.cjs +20159 -0
  2. package/lib/index.d.cts +5993 -0
  3. package/lib/index.d.cts.map +1 -0
  4. package/lib/index.d.ts +5992 -21
  5. package/lib/index.d.ts.map +1 -1
  6. package/lib/index.js +19926 -22
  7. package/lib/index.js.map +1 -1
  8. package/package.json +15 -14
  9. package/src/__tests__/define-function-record.test.ts +58 -0
  10. package/src/__tests__/define-function-simple.test.ts +55 -0
  11. package/src/__tests__/define-function-split-sequence.test.ts +547 -0
  12. package/src/__tests__/define-function-type-parity.test.ts +9 -0
  13. package/src/__tests__/define-function.test.ts +763 -0
  14. package/src/__tests__/num-operations.test.ts +91 -0
  15. package/src/__tests__/safe-parse.test.ts +374 -0
  16. package/src/context.ts +896 -0
  17. package/src/conversions.ts +282 -0
  18. package/src/debug-log.ts +29 -0
  19. package/src/define-function.ts +1006 -0
  20. package/src/deprecation.ts +67 -0
  21. package/src/globals.d.ts +26 -0
  22. package/src/index.ts +31 -0
  23. package/src/jess-error.ts +773 -0
  24. package/src/logger/deprecation-processing.ts +109 -0
  25. package/src/logger.ts +31 -0
  26. package/src/plugin.ts +292 -0
  27. package/src/tree/LOOKUP_CHAINS.md +35 -0
  28. package/src/tree/README.md +18 -0
  29. package/src/tree/__tests__/__snapshots__/extend-eval-integration.test.ts.snap +1455 -0
  30. package/src/tree/__tests__/ampersand.test.ts +382 -0
  31. package/src/tree/__tests__/at-rule.test.ts +2047 -0
  32. package/src/tree/__tests__/basic-render.test.ts +212 -0
  33. package/src/tree/__tests__/block.test.ts +40 -0
  34. package/src/tree/__tests__/call.test.ts +346 -0
  35. package/src/tree/__tests__/color.test.ts +537 -0
  36. package/src/tree/__tests__/condition.test.ts +186 -0
  37. package/src/tree/__tests__/control.test.ts +564 -0
  38. package/src/tree/__tests__/declaration.test.ts +253 -0
  39. package/src/tree/__tests__/dependency-graph.test.ts +177 -0
  40. package/src/tree/__tests__/detached-rulesets.test.ts +213 -0
  41. package/src/tree/__tests__/dimension.test.ts +236 -0
  42. package/src/tree/__tests__/expression.test.ts +73 -0
  43. package/src/tree/__tests__/ext-node.test.ts +31 -0
  44. package/src/tree/__tests__/extend-eval-integration.test.ts +1033 -0
  45. package/src/tree/__tests__/extend-import-style.test.ts +929 -0
  46. package/src/tree/__tests__/extend-less-fixtures.test.ts +851 -0
  47. package/src/tree/__tests__/extend-list.test.ts +31 -0
  48. package/src/tree/__tests__/extend-roots.test.ts +1045 -0
  49. package/src/tree/__tests__/extend-rules.test.ts +740 -0
  50. package/src/tree/__tests__/func.test.ts +171 -0
  51. package/src/tree/__tests__/import-js.test.ts +33 -0
  52. package/src/tree/__tests__/import-style-test-helpers.ts +56 -0
  53. package/src/tree/__tests__/import-style.test.ts +1967 -0
  54. package/src/tree/__tests__/interpolated-reference.test.ts +44 -0
  55. package/src/tree/__tests__/interpolated.test.ts +41 -0
  56. package/src/tree/__tests__/list.test.ts +177 -0
  57. package/src/tree/__tests__/log.test.ts +83 -0
  58. package/src/tree/__tests__/mixin-recursion.test.ts +639 -0
  59. package/src/tree/__tests__/mixin.test.ts +2171 -0
  60. package/src/tree/__tests__/negative.test.ts +45 -0
  61. package/src/tree/__tests__/nesting-collapse.test.ts +519 -0
  62. package/src/tree/__tests__/node-flags-perf.test.ts +195 -0
  63. package/src/tree/__tests__/node-flags.test.ts +410 -0
  64. package/src/tree/__tests__/node-graph.test.ts +598 -0
  65. package/src/tree/__tests__/node-mutation.test.ts +182 -0
  66. package/src/tree/__tests__/operation.test.ts +18 -0
  67. package/src/tree/__tests__/paren.test.ts +90 -0
  68. package/src/tree/__tests__/preserve-mode-output.test.ts +50 -0
  69. package/src/tree/__tests__/quoted.test.ts +72 -0
  70. package/src/tree/__tests__/range.test.ts +59 -0
  71. package/src/tree/__tests__/reference.test.ts +743 -0
  72. package/src/tree/__tests__/rest.test.ts +29 -0
  73. package/src/tree/__tests__/rules-raw.test.ts +14 -0
  74. package/src/tree/__tests__/rules.test.ts +1271 -0
  75. package/src/tree/__tests__/ruleset.test.ts +597 -0
  76. package/src/tree/__tests__/selector-attr.test.ts +50 -0
  77. package/src/tree/__tests__/selector-basic.test.ts +44 -0
  78. package/src/tree/__tests__/selector-capture.test.ts +22 -0
  79. package/src/tree/__tests__/selector-complex.test.ts +120 -0
  80. package/src/tree/__tests__/selector-compound.test.ts +74 -0
  81. package/src/tree/__tests__/selector-interpolated.test.ts +50 -0
  82. package/src/tree/__tests__/selector-list.test.ts +59 -0
  83. package/src/tree/__tests__/selector-pseudo.test.ts +23 -0
  84. package/src/tree/__tests__/selector.test.ts +182 -0
  85. package/src/tree/__tests__/sequence.test.ts +226 -0
  86. package/src/tree/__tests__/serialize-types.test.ts +529 -0
  87. package/src/tree/__tests__/spaced.test.ts +8 -0
  88. package/src/tree/__tests__/url.test.ts +72 -0
  89. package/src/tree/__tests__/var-declaration.test.ts +90 -0
  90. package/src/tree/ampersand.ts +538 -0
  91. package/src/tree/any.ts +169 -0
  92. package/src/tree/at-rule.ts +760 -0
  93. package/src/tree/block.ts +72 -0
  94. package/src/tree/bool.ts +46 -0
  95. package/src/tree/call.ts +593 -0
  96. package/src/tree/collection.ts +52 -0
  97. package/src/tree/color.ts +629 -0
  98. package/src/tree/combinator.ts +30 -0
  99. package/src/tree/comment.ts +36 -0
  100. package/src/tree/condition.ts +194 -0
  101. package/src/tree/control.ts +452 -0
  102. package/src/tree/declaration-custom.ts +56 -0
  103. package/src/tree/declaration-var.ts +87 -0
  104. package/src/tree/declaration.ts +742 -0
  105. package/src/tree/default-guard.ts +35 -0
  106. package/src/tree/dimension.ts +392 -0
  107. package/src/tree/expression.ts +97 -0
  108. package/src/tree/extend-list.ts +51 -0
  109. package/src/tree/extend.ts +391 -0
  110. package/src/tree/function.ts +254 -0
  111. package/src/tree/import-js.ts +130 -0
  112. package/src/tree/import-style.ts +875 -0
  113. package/{lib/tree/index.js → src/tree/index.ts} +49 -22
  114. package/src/tree/interpolated.ts +346 -0
  115. package/src/tree/js-array.ts +21 -0
  116. package/src/tree/js-expr.ts +50 -0
  117. package/src/tree/js-function.ts +31 -0
  118. package/src/tree/js-object.ts +22 -0
  119. package/src/tree/list.ts +415 -0
  120. package/src/tree/log.ts +89 -0
  121. package/src/tree/mixin.ts +331 -0
  122. package/src/tree/negative.ts +58 -0
  123. package/src/tree/nil.ts +57 -0
  124. package/src/tree/node-base.ts +1716 -0
  125. package/src/tree/node-type.ts +122 -0
  126. package/src/tree/node.ts +118 -0
  127. package/src/tree/number.ts +54 -0
  128. package/src/tree/operation.ts +187 -0
  129. package/src/tree/paren.ts +132 -0
  130. package/src/tree/query-condition.ts +47 -0
  131. package/src/tree/quoted.ts +119 -0
  132. package/src/tree/range.ts +101 -0
  133. package/src/tree/reference.ts +1099 -0
  134. package/src/tree/rest.ts +55 -0
  135. package/src/tree/rules-raw.ts +52 -0
  136. package/src/tree/rules.ts +2896 -0
  137. package/src/tree/ruleset.ts +1217 -0
  138. package/src/tree/selector-attr.ts +172 -0
  139. package/src/tree/selector-basic.ts +75 -0
  140. package/src/tree/selector-capture.ts +85 -0
  141. package/src/tree/selector-complex.ts +189 -0
  142. package/src/tree/selector-compound.ts +205 -0
  143. package/src/tree/selector-interpolated.ts +95 -0
  144. package/src/tree/selector-list.ts +245 -0
  145. package/src/tree/selector-pseudo.ts +173 -0
  146. package/src/tree/selector-simple.ts +10 -0
  147. package/src/tree/selector.ts +152 -0
  148. package/src/tree/sequence.ts +463 -0
  149. package/src/tree/tree.ts +130 -0
  150. package/src/tree/url.ts +95 -0
  151. package/src/tree/util/EXTEND_ARCHITECTURE_ANALYSIS.md +215 -0
  152. package/src/tree/util/EXTEND_AUDIT.md +233 -0
  153. package/src/tree/util/EXTEND_BASELINE.md +64 -0
  154. package/src/tree/util/EXTEND_CALL_GRAPH_ANALYSIS.md +244 -0
  155. package/src/tree/util/EXTEND_DOCS.md +24 -0
  156. package/src/tree/util/EXTEND_FINAL_SUMMARY.md +95 -0
  157. package/src/tree/util/EXTEND_FUNCTION_AUDIT.md +1433 -0
  158. package/src/tree/util/EXTEND_OPTIMIZATION_PLAN.md +114 -0
  159. package/src/tree/util/EXTEND_REFACTORING_SUMMARY.md +152 -0
  160. package/src/tree/util/EXTEND_RULES.md +74 -0
  161. package/src/tree/util/EXTEND_UNUSED_FUNCTIONS.md +127 -0
  162. package/src/tree/util/EXTEND_UNUSED_FUNCTIONS_ANALYSIS.md +227 -0
  163. package/src/tree/util/NODE_COPY_REDUCTION_PLAN.md +12 -0
  164. package/src/tree/util/__tests__/EXTEND_TEST_INDEX.md +59 -0
  165. package/src/tree/util/__tests__/OPTIMIZATION-ANALYSIS.md +130 -0
  166. package/src/tree/util/__tests__/WALK_AND_CONSUME_DESIGN.md +138 -0
  167. package/src/tree/util/__tests__/_archive/2026-02-09__OPTIMIZATION-ANALYSIS.md +9 -0
  168. package/src/tree/util/__tests__/_archive/README.md +4 -0
  169. package/src/tree/util/__tests__/bitset.test.ts +142 -0
  170. package/src/tree/util/__tests__/debug-log.ts +50 -0
  171. package/src/tree/util/__tests__/extend-comment-handling.test.ts +187 -0
  172. package/src/tree/util/__tests__/extend-core-unit.test.ts +941 -0
  173. package/src/tree/util/__tests__/extend-pipeline-bench.test.ts +154 -0
  174. package/src/tree/util/__tests__/extend-pipeline-bench.ts +190 -0
  175. package/src/tree/util/__tests__/fast-reject.test.ts +377 -0
  176. package/src/tree/util/__tests__/is-node.test.ts +63 -0
  177. package/src/tree/util/__tests__/list-like.test.ts +63 -0
  178. package/src/tree/util/__tests__/outputwriter.test.ts +523 -0
  179. package/src/tree/util/__tests__/print.test.ts +183 -0
  180. package/src/tree/util/__tests__/process-extends.test.ts +226 -0
  181. package/src/tree/util/__tests__/process-leading-is.test.ts +205 -0
  182. package/src/tree/util/__tests__/recursion-helper.test.ts +184 -0
  183. package/src/tree/util/__tests__/selector-match-unit.test.ts +1427 -0
  184. package/src/tree/util/__tests__/sourcemap.test.ts +117 -0
  185. package/src/tree/util/ampersand-template.ts +9 -0
  186. package/src/tree/util/bitset.ts +194 -0
  187. package/src/tree/util/calculate.ts +11 -0
  188. package/src/tree/util/cast.ts +89 -0
  189. package/src/tree/util/cloning.ts +8 -0
  190. package/src/tree/util/collections.ts +299 -0
  191. package/src/tree/util/compare.ts +90 -0
  192. package/src/tree/util/cursor.ts +171 -0
  193. package/src/tree/util/extend-core.ts +2139 -0
  194. package/src/tree/util/extend-roots.ts +1108 -0
  195. package/src/tree/util/field-helpers.ts +354 -0
  196. package/src/tree/util/is-node.ts +43 -0
  197. package/src/tree/util/list-like.ts +93 -0
  198. package/src/tree/util/mixin-instance-primitives.ts +2020 -0
  199. package/src/tree/util/print.ts +303 -0
  200. package/src/tree/util/process-leading-is.ts +421 -0
  201. package/src/tree/util/recursion-helper.ts +54 -0
  202. package/src/tree/util/regex.ts +2 -0
  203. package/src/tree/util/registry-utils.ts +1953 -0
  204. package/src/tree/util/ruleset-trace.ts +17 -0
  205. package/src/tree/util/scoped-body-eval.ts +320 -0
  206. package/src/tree/util/selector-match-core.ts +2005 -0
  207. package/src/tree/util/selector-utils.ts +757 -0
  208. package/src/tree/util/serialize-helper.ts +535 -0
  209. package/src/tree/util/serialize-types.ts +318 -0
  210. package/src/tree/util/should-operate.ts +78 -0
  211. package/src/tree/util/sourcemap.ts +37 -0
  212. package/src/types/config.ts +247 -0
  213. package/src/types/index.ts +12 -0
  214. package/{lib/types/modes.d.ts → src/types/modes.ts} +2 -1
  215. package/src/types.d.ts +9 -0
  216. package/src/types.ts +68 -0
  217. package/src/use-webpack-resolver.ts +56 -0
  218. package/src/visitor/__tests__/visitor.test.ts +136 -0
  219. package/src/visitor/index.ts +263 -0
  220. package/{lib/visitor/less-visitor.js → src/visitor/less-visitor.ts} +3 -2
  221. package/lib/context.d.ts +0 -352
  222. package/lib/context.d.ts.map +0 -1
  223. package/lib/context.js +0 -636
  224. package/lib/context.js.map +0 -1
  225. package/lib/conversions.d.ts +0 -73
  226. package/lib/conversions.d.ts.map +0 -1
  227. package/lib/conversions.js +0 -253
  228. package/lib/conversions.js.map +0 -1
  229. package/lib/debug-log.d.ts +0 -2
  230. package/lib/debug-log.d.ts.map +0 -1
  231. package/lib/debug-log.js +0 -27
  232. package/lib/debug-log.js.map +0 -1
  233. package/lib/define-function.d.ts +0 -587
  234. package/lib/define-function.d.ts.map +0 -1
  235. package/lib/define-function.js +0 -726
  236. package/lib/define-function.js.map +0 -1
  237. package/lib/deprecation.d.ts +0 -34
  238. package/lib/deprecation.d.ts.map +0 -1
  239. package/lib/deprecation.js +0 -57
  240. package/lib/deprecation.js.map +0 -1
  241. package/lib/jess-error.d.ts +0 -343
  242. package/lib/jess-error.d.ts.map +0 -1
  243. package/lib/jess-error.js +0 -508
  244. package/lib/jess-error.js.map +0 -1
  245. package/lib/logger/deprecation-processing.d.ts +0 -41
  246. package/lib/logger/deprecation-processing.d.ts.map +0 -1
  247. package/lib/logger/deprecation-processing.js +0 -81
  248. package/lib/logger/deprecation-processing.js.map +0 -1
  249. package/lib/logger.d.ts +0 -10
  250. package/lib/logger.d.ts.map +0 -1
  251. package/lib/logger.js +0 -20
  252. package/lib/logger.js.map +0 -1
  253. package/lib/plugin.d.ts +0 -94
  254. package/lib/plugin.d.ts.map +0 -1
  255. package/lib/plugin.js +0 -174
  256. package/lib/plugin.js.map +0 -1
  257. package/lib/tree/ampersand.d.ts +0 -94
  258. package/lib/tree/ampersand.d.ts.map +0 -1
  259. package/lib/tree/ampersand.js +0 -269
  260. package/lib/tree/ampersand.js.map +0 -1
  261. package/lib/tree/any.d.ts +0 -58
  262. package/lib/tree/any.d.ts.map +0 -1
  263. package/lib/tree/any.js +0 -104
  264. package/lib/tree/any.js.map +0 -1
  265. package/lib/tree/at-rule.d.ts +0 -53
  266. package/lib/tree/at-rule.d.ts.map +0 -1
  267. package/lib/tree/at-rule.js +0 -503
  268. package/lib/tree/at-rule.js.map +0 -1
  269. package/lib/tree/block.d.ts +0 -22
  270. package/lib/tree/block.d.ts.map +0 -1
  271. package/lib/tree/block.js +0 -24
  272. package/lib/tree/block.js.map +0 -1
  273. package/lib/tree/bool.d.ts +0 -18
  274. package/lib/tree/bool.d.ts.map +0 -1
  275. package/lib/tree/bool.js +0 -28
  276. package/lib/tree/bool.js.map +0 -1
  277. package/lib/tree/call.d.ts +0 -66
  278. package/lib/tree/call.d.ts.map +0 -1
  279. package/lib/tree/call.js +0 -306
  280. package/lib/tree/call.js.map +0 -1
  281. package/lib/tree/collection.d.ts +0 -30
  282. package/lib/tree/collection.d.ts.map +0 -1
  283. package/lib/tree/collection.js +0 -37
  284. package/lib/tree/collection.js.map +0 -1
  285. package/lib/tree/color.d.ts +0 -101
  286. package/lib/tree/color.d.ts.map +0 -1
  287. package/lib/tree/color.js +0 -513
  288. package/lib/tree/color.js.map +0 -1
  289. package/lib/tree/combinator.d.ts +0 -13
  290. package/lib/tree/combinator.d.ts.map +0 -1
  291. package/lib/tree/combinator.js +0 -12
  292. package/lib/tree/combinator.js.map +0 -1
  293. package/lib/tree/comment.d.ts +0 -20
  294. package/lib/tree/comment.d.ts.map +0 -1
  295. package/lib/tree/comment.js +0 -19
  296. package/lib/tree/comment.js.map +0 -1
  297. package/lib/tree/condition.d.ts +0 -31
  298. package/lib/tree/condition.d.ts.map +0 -1
  299. package/lib/tree/condition.js +0 -103
  300. package/lib/tree/condition.js.map +0 -1
  301. package/lib/tree/control.d.ts +0 -104
  302. package/lib/tree/control.d.ts.map +0 -1
  303. package/lib/tree/control.js +0 -430
  304. package/lib/tree/control.js.map +0 -1
  305. package/lib/tree/declaration-custom.d.ts +0 -18
  306. package/lib/tree/declaration-custom.d.ts.map +0 -1
  307. package/lib/tree/declaration-custom.js +0 -24
  308. package/lib/tree/declaration-custom.js.map +0 -1
  309. package/lib/tree/declaration-var.d.ts +0 -35
  310. package/lib/tree/declaration-var.d.ts.map +0 -1
  311. package/lib/tree/declaration-var.js +0 -63
  312. package/lib/tree/declaration-var.js.map +0 -1
  313. package/lib/tree/declaration.d.ts +0 -78
  314. package/lib/tree/declaration.d.ts.map +0 -1
  315. package/lib/tree/declaration.js +0 -286
  316. package/lib/tree/declaration.js.map +0 -1
  317. package/lib/tree/default-guard.d.ts +0 -15
  318. package/lib/tree/default-guard.d.ts.map +0 -1
  319. package/lib/tree/default-guard.js +0 -19
  320. package/lib/tree/default-guard.js.map +0 -1
  321. package/lib/tree/dimension.d.ts +0 -34
  322. package/lib/tree/dimension.d.ts.map +0 -1
  323. package/lib/tree/dimension.js +0 -294
  324. package/lib/tree/dimension.js.map +0 -1
  325. package/lib/tree/expression.d.ts +0 -25
  326. package/lib/tree/expression.d.ts.map +0 -1
  327. package/lib/tree/expression.js +0 -32
  328. package/lib/tree/expression.js.map +0 -1
  329. package/lib/tree/extend-list.d.ts +0 -23
  330. package/lib/tree/extend-list.d.ts.map +0 -1
  331. package/lib/tree/extend-list.js +0 -23
  332. package/lib/tree/extend-list.js.map +0 -1
  333. package/lib/tree/extend.d.ts +0 -47
  334. package/lib/tree/extend.d.ts.map +0 -1
  335. package/lib/tree/extend.js +0 -296
  336. package/lib/tree/extend.js.map +0 -1
  337. package/lib/tree/function.d.ts +0 -48
  338. package/lib/tree/function.d.ts.map +0 -1
  339. package/lib/tree/function.js +0 -74
  340. package/lib/tree/function.js.map +0 -1
  341. package/lib/tree/import-js.d.ts +0 -35
  342. package/lib/tree/import-js.d.ts.map +0 -1
  343. package/lib/tree/import-js.js +0 -45
  344. package/lib/tree/import-js.js.map +0 -1
  345. package/lib/tree/import-style.d.ts +0 -156
  346. package/lib/tree/import-style.d.ts.map +0 -1
  347. package/lib/tree/import-style.js +0 -566
  348. package/lib/tree/import-style.js.map +0 -1
  349. package/lib/tree/index.d.ts +0 -71
  350. package/lib/tree/index.d.ts.map +0 -1
  351. package/lib/tree/index.js.map +0 -1
  352. package/lib/tree/interpolated-reference.d.ts +0 -24
  353. package/lib/tree/interpolated-reference.d.ts.map +0 -1
  354. package/lib/tree/interpolated-reference.js +0 -37
  355. package/lib/tree/interpolated-reference.js.map +0 -1
  356. package/lib/tree/interpolated.d.ts +0 -62
  357. package/lib/tree/interpolated.d.ts.map +0 -1
  358. package/lib/tree/interpolated.js +0 -204
  359. package/lib/tree/interpolated.js.map +0 -1
  360. package/lib/tree/js-array.d.ts +0 -10
  361. package/lib/tree/js-array.d.ts.map +0 -1
  362. package/lib/tree/js-array.js +0 -10
  363. package/lib/tree/js-array.js.map +0 -1
  364. package/lib/tree/js-expr.d.ts +0 -23
  365. package/lib/tree/js-expr.d.ts.map +0 -1
  366. package/lib/tree/js-expr.js +0 -28
  367. package/lib/tree/js-expr.js.map +0 -1
  368. package/lib/tree/js-function.d.ts +0 -20
  369. package/lib/tree/js-function.d.ts.map +0 -1
  370. package/lib/tree/js-function.js +0 -16
  371. package/lib/tree/js-function.js.map +0 -1
  372. package/lib/tree/js-object.d.ts +0 -10
  373. package/lib/tree/js-object.d.ts.map +0 -1
  374. package/lib/tree/js-object.js +0 -10
  375. package/lib/tree/js-object.js.map +0 -1
  376. package/lib/tree/list.d.ts +0 -38
  377. package/lib/tree/list.d.ts.map +0 -1
  378. package/lib/tree/list.js +0 -83
  379. package/lib/tree/list.js.map +0 -1
  380. package/lib/tree/log.d.ts +0 -29
  381. package/lib/tree/log.d.ts.map +0 -1
  382. package/lib/tree/log.js +0 -56
  383. package/lib/tree/log.js.map +0 -1
  384. package/lib/tree/mixin.d.ts +0 -87
  385. package/lib/tree/mixin.d.ts.map +0 -1
  386. package/lib/tree/mixin.js +0 -112
  387. package/lib/tree/mixin.js.map +0 -1
  388. package/lib/tree/negative.d.ts +0 -17
  389. package/lib/tree/negative.d.ts.map +0 -1
  390. package/lib/tree/negative.js +0 -22
  391. package/lib/tree/negative.js.map +0 -1
  392. package/lib/tree/nil.d.ts +0 -30
  393. package/lib/tree/nil.d.ts.map +0 -1
  394. package/lib/tree/nil.js +0 -35
  395. package/lib/tree/nil.js.map +0 -1
  396. package/lib/tree/node-base.d.ts +0 -361
  397. package/lib/tree/node-base.d.ts.map +0 -1
  398. package/lib/tree/node-base.js +0 -930
  399. package/lib/tree/node-base.js.map +0 -1
  400. package/lib/tree/node.d.ts +0 -10
  401. package/lib/tree/node.d.ts.map +0 -1
  402. package/lib/tree/node.js +0 -45
  403. package/lib/tree/node.js.map +0 -1
  404. package/lib/tree/number.d.ts +0 -21
  405. package/lib/tree/number.d.ts.map +0 -1
  406. package/lib/tree/number.js +0 -27
  407. package/lib/tree/number.js.map +0 -1
  408. package/lib/tree/operation.d.ts +0 -26
  409. package/lib/tree/operation.d.ts.map +0 -1
  410. package/lib/tree/operation.js +0 -103
  411. package/lib/tree/operation.js.map +0 -1
  412. package/lib/tree/paren.d.ts +0 -19
  413. package/lib/tree/paren.d.ts.map +0 -1
  414. package/lib/tree/paren.js +0 -92
  415. package/lib/tree/paren.js.map +0 -1
  416. package/lib/tree/query-condition.d.ts +0 -17
  417. package/lib/tree/query-condition.d.ts.map +0 -1
  418. package/lib/tree/query-condition.js +0 -39
  419. package/lib/tree/query-condition.js.map +0 -1
  420. package/lib/tree/quoted.d.ts +0 -28
  421. package/lib/tree/quoted.d.ts.map +0 -1
  422. package/lib/tree/quoted.js +0 -75
  423. package/lib/tree/quoted.js.map +0 -1
  424. package/lib/tree/range.d.ts +0 -33
  425. package/lib/tree/range.d.ts.map +0 -1
  426. package/lib/tree/range.js +0 -47
  427. package/lib/tree/range.js.map +0 -1
  428. package/lib/tree/reference.d.ts +0 -76
  429. package/lib/tree/reference.d.ts.map +0 -1
  430. package/lib/tree/reference.js +0 -521
  431. package/lib/tree/reference.js.map +0 -1
  432. package/lib/tree/rest.d.ts +0 -15
  433. package/lib/tree/rest.d.ts.map +0 -1
  434. package/lib/tree/rest.js +0 -32
  435. package/lib/tree/rest.js.map +0 -1
  436. package/lib/tree/rules-raw.d.ts +0 -17
  437. package/lib/tree/rules-raw.d.ts.map +0 -1
  438. package/lib/tree/rules-raw.js +0 -37
  439. package/lib/tree/rules-raw.js.map +0 -1
  440. package/lib/tree/rules.d.ts +0 -262
  441. package/lib/tree/rules.d.ts.map +0 -1
  442. package/lib/tree/rules.js +0 -2359
  443. package/lib/tree/rules.js.map +0 -1
  444. package/lib/tree/ruleset.d.ts +0 -92
  445. package/lib/tree/ruleset.d.ts.map +0 -1
  446. package/lib/tree/ruleset.js +0 -528
  447. package/lib/tree/ruleset.js.map +0 -1
  448. package/lib/tree/selector-attr.d.ts +0 -31
  449. package/lib/tree/selector-attr.d.ts.map +0 -1
  450. package/lib/tree/selector-attr.js +0 -99
  451. package/lib/tree/selector-attr.js.map +0 -1
  452. package/lib/tree/selector-basic.d.ts +0 -24
  453. package/lib/tree/selector-basic.d.ts.map +0 -1
  454. package/lib/tree/selector-basic.js +0 -38
  455. package/lib/tree/selector-basic.js.map +0 -1
  456. package/lib/tree/selector-capture.d.ts +0 -23
  457. package/lib/tree/selector-capture.d.ts.map +0 -1
  458. package/lib/tree/selector-capture.js +0 -34
  459. package/lib/tree/selector-capture.js.map +0 -1
  460. package/lib/tree/selector-complex.d.ts +0 -40
  461. package/lib/tree/selector-complex.d.ts.map +0 -1
  462. package/lib/tree/selector-complex.js +0 -143
  463. package/lib/tree/selector-complex.js.map +0 -1
  464. package/lib/tree/selector-compound.d.ts +0 -16
  465. package/lib/tree/selector-compound.d.ts.map +0 -1
  466. package/lib/tree/selector-compound.js +0 -114
  467. package/lib/tree/selector-compound.js.map +0 -1
  468. package/lib/tree/selector-interpolated.d.ts +0 -23
  469. package/lib/tree/selector-interpolated.d.ts.map +0 -1
  470. package/lib/tree/selector-interpolated.js +0 -27
  471. package/lib/tree/selector-interpolated.js.map +0 -1
  472. package/lib/tree/selector-list.d.ts +0 -17
  473. package/lib/tree/selector-list.d.ts.map +0 -1
  474. package/lib/tree/selector-list.js +0 -174
  475. package/lib/tree/selector-list.js.map +0 -1
  476. package/lib/tree/selector-pseudo.d.ts +0 -42
  477. package/lib/tree/selector-pseudo.d.ts.map +0 -1
  478. package/lib/tree/selector-pseudo.js +0 -204
  479. package/lib/tree/selector-pseudo.js.map +0 -1
  480. package/lib/tree/selector-simple.d.ts +0 -5
  481. package/lib/tree/selector-simple.d.ts.map +0 -1
  482. package/lib/tree/selector-simple.js +0 -6
  483. package/lib/tree/selector-simple.js.map +0 -1
  484. package/lib/tree/selector.d.ts +0 -43
  485. package/lib/tree/selector.d.ts.map +0 -1
  486. package/lib/tree/selector.js +0 -56
  487. package/lib/tree/selector.js.map +0 -1
  488. package/lib/tree/sequence.d.ts +0 -43
  489. package/lib/tree/sequence.d.ts.map +0 -1
  490. package/lib/tree/sequence.js +0 -151
  491. package/lib/tree/sequence.js.map +0 -1
  492. package/lib/tree/tree.d.ts +0 -87
  493. package/lib/tree/tree.d.ts.map +0 -1
  494. package/lib/tree/tree.js +0 -2
  495. package/lib/tree/tree.js.map +0 -1
  496. package/lib/tree/url.d.ts +0 -18
  497. package/lib/tree/url.d.ts.map +0 -1
  498. package/lib/tree/url.js +0 -35
  499. package/lib/tree/url.js.map +0 -1
  500. package/lib/tree/util/__tests__/debug-log.d.ts +0 -1
  501. package/lib/tree/util/__tests__/debug-log.d.ts.map +0 -1
  502. package/lib/tree/util/__tests__/debug-log.js +0 -36
  503. package/lib/tree/util/__tests__/debug-log.js.map +0 -1
  504. package/lib/tree/util/calculate.d.ts +0 -3
  505. package/lib/tree/util/calculate.d.ts.map +0 -1
  506. package/lib/tree/util/calculate.js +0 -10
  507. package/lib/tree/util/calculate.js.map +0 -1
  508. package/lib/tree/util/cast.d.ts +0 -10
  509. package/lib/tree/util/cast.d.ts.map +0 -1
  510. package/lib/tree/util/cast.js +0 -87
  511. package/lib/tree/util/cast.js.map +0 -1
  512. package/lib/tree/util/cloning.d.ts +0 -4
  513. package/lib/tree/util/cloning.d.ts.map +0 -1
  514. package/lib/tree/util/cloning.js +0 -8
  515. package/lib/tree/util/cloning.js.map +0 -1
  516. package/lib/tree/util/collections.d.ts +0 -57
  517. package/lib/tree/util/collections.d.ts.map +0 -1
  518. package/lib/tree/util/collections.js +0 -136
  519. package/lib/tree/util/collections.js.map +0 -1
  520. package/lib/tree/util/compare.d.ts +0 -11
  521. package/lib/tree/util/compare.d.ts.map +0 -1
  522. package/lib/tree/util/compare.js +0 -89
  523. package/lib/tree/util/compare.js.map +0 -1
  524. package/lib/tree/util/extend-helpers.d.ts +0 -2
  525. package/lib/tree/util/extend-helpers.d.ts.map +0 -1
  526. package/lib/tree/util/extend-helpers.js +0 -2
  527. package/lib/tree/util/extend-helpers.js.map +0 -1
  528. package/lib/tree/util/extend-roots.d.ts +0 -37
  529. package/lib/tree/util/extend-roots.d.ts.map +0 -1
  530. package/lib/tree/util/extend-roots.js +0 -700
  531. package/lib/tree/util/extend-roots.js.map +0 -1
  532. package/lib/tree/util/extend-roots.old.d.ts +0 -132
  533. package/lib/tree/util/extend-roots.old.d.ts.map +0 -1
  534. package/lib/tree/util/extend-roots.old.js +0 -2272
  535. package/lib/tree/util/extend-roots.old.js.map +0 -1
  536. package/lib/tree/util/extend-trace-debug.d.ts +0 -13
  537. package/lib/tree/util/extend-trace-debug.d.ts.map +0 -1
  538. package/lib/tree/util/extend-trace-debug.js +0 -34
  539. package/lib/tree/util/extend-trace-debug.js.map +0 -1
  540. package/lib/tree/util/extend-walk.d.ts +0 -53
  541. package/lib/tree/util/extend-walk.d.ts.map +0 -1
  542. package/lib/tree/util/extend-walk.js +0 -881
  543. package/lib/tree/util/extend-walk.js.map +0 -1
  544. package/lib/tree/util/extend.d.ts +0 -218
  545. package/lib/tree/util/extend.d.ts.map +0 -1
  546. package/lib/tree/util/extend.js +0 -3182
  547. package/lib/tree/util/extend.js.map +0 -1
  548. package/lib/tree/util/find-extendable-locations.d.ts +0 -2
  549. package/lib/tree/util/find-extendable-locations.d.ts.map +0 -1
  550. package/lib/tree/util/find-extendable-locations.js +0 -2
  551. package/lib/tree/util/find-extendable-locations.js.map +0 -1
  552. package/lib/tree/util/format.d.ts +0 -20
  553. package/lib/tree/util/format.d.ts.map +0 -1
  554. package/lib/tree/util/format.js +0 -67
  555. package/lib/tree/util/format.js.map +0 -1
  556. package/lib/tree/util/is-node.d.ts +0 -13
  557. package/lib/tree/util/is-node.d.ts.map +0 -1
  558. package/lib/tree/util/is-node.js +0 -43
  559. package/lib/tree/util/is-node.js.map +0 -1
  560. package/lib/tree/util/print.d.ts +0 -80
  561. package/lib/tree/util/print.d.ts.map +0 -1
  562. package/lib/tree/util/print.js +0 -205
  563. package/lib/tree/util/print.js.map +0 -1
  564. package/lib/tree/util/process-leading-is.d.ts +0 -25
  565. package/lib/tree/util/process-leading-is.d.ts.map +0 -1
  566. package/lib/tree/util/process-leading-is.js +0 -364
  567. package/lib/tree/util/process-leading-is.js.map +0 -1
  568. package/lib/tree/util/recursion-helper.d.ts +0 -15
  569. package/lib/tree/util/recursion-helper.d.ts.map +0 -1
  570. package/lib/tree/util/recursion-helper.js +0 -43
  571. package/lib/tree/util/recursion-helper.js.map +0 -1
  572. package/lib/tree/util/regex.d.ts +0 -4
  573. package/lib/tree/util/regex.d.ts.map +0 -1
  574. package/lib/tree/util/regex.js +0 -4
  575. package/lib/tree/util/regex.js.map +0 -1
  576. package/lib/tree/util/registry-utils.d.ts +0 -192
  577. package/lib/tree/util/registry-utils.d.ts.map +0 -1
  578. package/lib/tree/util/registry-utils.js +0 -1214
  579. package/lib/tree/util/registry-utils.js.map +0 -1
  580. package/lib/tree/util/ruleset-trace.d.ts +0 -4
  581. package/lib/tree/util/ruleset-trace.d.ts.map +0 -1
  582. package/lib/tree/util/ruleset-trace.js +0 -14
  583. package/lib/tree/util/ruleset-trace.js.map +0 -1
  584. package/lib/tree/util/selector-compare.d.ts +0 -2
  585. package/lib/tree/util/selector-compare.d.ts.map +0 -1
  586. package/lib/tree/util/selector-compare.js +0 -2
  587. package/lib/tree/util/selector-compare.js.map +0 -1
  588. package/lib/tree/util/selector-match-core.d.ts +0 -184
  589. package/lib/tree/util/selector-match-core.d.ts.map +0 -1
  590. package/lib/tree/util/selector-match-core.js +0 -1603
  591. package/lib/tree/util/selector-match-core.js.map +0 -1
  592. package/lib/tree/util/selector-utils.d.ts +0 -30
  593. package/lib/tree/util/selector-utils.d.ts.map +0 -1
  594. package/lib/tree/util/selector-utils.js +0 -100
  595. package/lib/tree/util/selector-utils.js.map +0 -1
  596. package/lib/tree/util/serialize-helper.d.ts +0 -13
  597. package/lib/tree/util/serialize-helper.d.ts.map +0 -1
  598. package/lib/tree/util/serialize-helper.js +0 -387
  599. package/lib/tree/util/serialize-helper.js.map +0 -1
  600. package/lib/tree/util/serialize-types.d.ts +0 -9
  601. package/lib/tree/util/serialize-types.d.ts.map +0 -1
  602. package/lib/tree/util/serialize-types.js +0 -216
  603. package/lib/tree/util/serialize-types.js.map +0 -1
  604. package/lib/tree/util/should-operate.d.ts +0 -23
  605. package/lib/tree/util/should-operate.d.ts.map +0 -1
  606. package/lib/tree/util/should-operate.js +0 -46
  607. package/lib/tree/util/should-operate.js.map +0 -1
  608. package/lib/tree/util/sourcemap.d.ts +0 -7
  609. package/lib/tree/util/sourcemap.d.ts.map +0 -1
  610. package/lib/tree/util/sourcemap.js +0 -25
  611. package/lib/tree/util/sourcemap.js.map +0 -1
  612. package/lib/types/config.d.ts +0 -205
  613. package/lib/types/config.d.ts.map +0 -1
  614. package/lib/types/config.js +0 -2
  615. package/lib/types/config.js.map +0 -1
  616. package/lib/types/index.d.ts +0 -15
  617. package/lib/types/index.d.ts.map +0 -1
  618. package/lib/types/index.js +0 -3
  619. package/lib/types/index.js.map +0 -1
  620. package/lib/types/modes.d.ts.map +0 -1
  621. package/lib/types/modes.js +0 -2
  622. package/lib/types/modes.js.map +0 -1
  623. package/lib/types.d.ts +0 -61
  624. package/lib/types.d.ts.map +0 -1
  625. package/lib/types.js +0 -2
  626. package/lib/types.js.map +0 -1
  627. package/lib/use-webpack-resolver.d.ts +0 -9
  628. package/lib/use-webpack-resolver.d.ts.map +0 -1
  629. package/lib/use-webpack-resolver.js +0 -41
  630. package/lib/use-webpack-resolver.js.map +0 -1
  631. package/lib/visitor/index.d.ts +0 -136
  632. package/lib/visitor/index.d.ts.map +0 -1
  633. package/lib/visitor/index.js +0 -135
  634. package/lib/visitor/index.js.map +0 -1
  635. package/lib/visitor/less-visitor.d.ts +0 -7
  636. package/lib/visitor/less-visitor.d.ts.map +0 -1
  637. package/lib/visitor/less-visitor.js.map +0 -1
@@ -0,0 +1,1967 @@
1
+ import { describe, it, expect, beforeEach, beforeAll, vi } from 'vitest';
2
+ import {
3
+ style,
4
+ rules,
5
+ sel,
6
+ el,
7
+ compound,
8
+ sellist,
9
+ decl,
10
+ vardecl,
11
+ any,
12
+ ref,
13
+ ruleset,
14
+ mixin,
15
+ call,
16
+ list,
17
+ amp,
18
+ co,
19
+ pseudo,
20
+ quoted,
21
+ url,
22
+ Interpolated,
23
+ INTERPOLATION_PLACEHOLDER,
24
+ type Rules,
25
+ Node,
26
+ Ruleset
27
+ } from '../index.js';
28
+ import { isNode } from '../util/is-node.js';
29
+ import { N } from '../node-type.js';
30
+ import { Context } from '../../context.js';
31
+ import type { FindOptions } from '../util/registry-utils.js';
32
+ import { resolve } from 'node:path';
33
+ import { createTestContext } from './import-style-test-helpers.js';
34
+ import { getParent } from '../util/field-helpers.js';
35
+ import { addEdge } from '../util/cursor.js';
36
+
37
+ let context: Context;
38
+
39
+ function getVarWithContext(context: Context, n: Rules, key: string, opts: FindOptions = {}) {
40
+ context.rulesContext = n;
41
+ opts.context ??= context;
42
+ opts.searchParents = true;
43
+ return n.find('declaration', key, 'VarDeclaration', opts);
44
+ }
45
+
46
+ function getMixinWithContext(context: Context, n: Rules, key: string, opts: FindOptions = {}) {
47
+ context.rulesContext = n;
48
+ opts.context ??= context;
49
+ opts.searchParents = true;
50
+ return n.find('mixin', key, 'Mixin', opts);
51
+ }
52
+
53
+ function getRulesetWithContext(context: Context, n: Rules, keys: string | string[], opts: FindOptions = {}) {
54
+ context.rulesContext = n;
55
+ opts.context ??= context;
56
+ opts.searchParents = true;
57
+ const keySet = typeof keys === 'string' ? [keys] : keys;
58
+ return n.find('ruleset', keySet, undefined, opts);
59
+ }
60
+
61
+ function asRulesContext(context: Context, rules: Rules): Context {
62
+ return {
63
+ ...context,
64
+ renderKey: rules.renderKey,
65
+ rulesContext: rules
66
+ } as Context;
67
+ }
68
+
69
+ describe('Style import', () => {
70
+ beforeAll(() => {
71
+ Node.prototype.fullRender = true;
72
+ });
73
+
74
+ beforeEach(() => {
75
+ context = createTestContext();
76
+ });
77
+
78
+ describe('variable visibility', () => {
79
+ it('import type can see parent rules variables', async () => {
80
+ // Set up imported file - use absolute path
81
+ const importedPath = resolve(process.cwd(), 'imported.jess');
82
+ context.sourceTrees.set(importedPath, rules([
83
+ ruleset({
84
+ selector: sellist([sel([el('.imported')])]),
85
+ rules: rules([
86
+ decl({ name: any('color'), value: ref('parentVar', { type: 'variable' }) })
87
+ ])
88
+ })
89
+ ]));
90
+
91
+ // Parent file with variable
92
+ const parentVar = vardecl({ name: 'parentVar', value: any('red') });
93
+ const node = rules([
94
+ parentVar,
95
+ style({
96
+ path: quoted(any('imported.jess'))
97
+ }, {
98
+ type: 'import'
99
+ })
100
+ ]);
101
+
102
+ const evald = await node.eval(context);
103
+ const importedRules = evald.at(1, context) as Rules;
104
+ const importedRuleset = importedRules.at(0, context);
105
+ const importedCtx = asRulesContext(context, (importedRuleset as any).rules);
106
+
107
+ // The imported ruleset should be able to reference the parent variable
108
+ // The declaration should already be evaluated as part of the ruleset evaluation
109
+ const importedDecl = (importedRuleset as any).rules.at(0, importedCtx);
110
+ expect(importedDecl.toTrimmedString({ context: importedCtx })).toBe('color: red');
111
+ });
112
+
113
+ it('import placements can reuse canonical imported rulesets while resolving different parent vars', async () => {
114
+ const importedPath = resolve(process.cwd(), 'imported-shared-ambient.jess');
115
+ const sourceRuleset = ruleset({
116
+ selector: sellist([sel([el('.imported')])]),
117
+ rules: rules([
118
+ decl({ name: any('color'), value: ref('parentVar', { type: 'variable' }) })
119
+ ])
120
+ });
121
+ const sourceBody = sourceRuleset.get('rules');
122
+ const sourceDecl = sourceBody.at(0, context);
123
+ const sourceTree = rules([sourceRuleset]);
124
+
125
+ const evaluateImport = async (color: string) => {
126
+ const localContext = createTestContext();
127
+ localContext.sourceTrees.set(importedPath, sourceTree);
128
+ const entry = rules([
129
+ vardecl({ name: 'parentVar', value: any(color) }),
130
+ style({ path: quoted(any('imported-shared-ambient.jess')) }, { type: 'import' })
131
+ ]);
132
+ const evald = await entry.eval(localContext);
133
+ return {
134
+ localContext,
135
+ evald,
136
+ importedRules: evald.at(1, localContext) as Rules,
137
+ importedRuleset: (evald.at(1, localContext) as Rules).at(0, localContext) as Ruleset
138
+ };
139
+ };
140
+
141
+ const red = await evaluateImport('red');
142
+ const blue = await evaluateImport('blue');
143
+
144
+ const redDecl = (red.importedRuleset as any).rules.at(0, red.localContext);
145
+ const blueDecl = (blue.importedRuleset as any).rules.at(0, blue.localContext);
146
+
147
+ expect(red.importedRuleset).not.toBe(blue.importedRuleset);
148
+ expect(red.importedRuleset.sourceNode).toBe(sourceRuleset);
149
+ expect(blue.importedRuleset.sourceNode).toBe(sourceRuleset);
150
+ expect((red.importedRuleset as any).rules.sourceNode).toBe(sourceBody);
151
+ expect((blue.importedRuleset as any).rules.sourceNode).toBe(sourceBody);
152
+ expect(redDecl.sourceNode).toBe(sourceDecl);
153
+ expect(blueDecl.sourceNode).toBe(sourceDecl);
154
+ expect(red.evald.render(red.localContext)).toContain('color: red');
155
+ expect(blue.evald.render(blue.localContext)).toContain('color: blue');
156
+ expect(sourceRuleset.parent).toBe(sourceTree);
157
+ expect(sourceBody.parent).toBe(sourceRuleset);
158
+ expect(sourceDecl.parent).toBe(sourceBody);
159
+ });
160
+
161
+ it('import returned trees already preserve descendant parent chains to the returned Rules', async () => {
162
+ const importedPath = resolve(process.cwd(), 'imported-parent-chain.jess');
163
+ context.sourceTrees.set(importedPath, rules([
164
+ ruleset({
165
+ selector: sellist([sel([el('.imported')])]),
166
+ rules: rules([
167
+ decl({ name: any('color'), value: any('red') })
168
+ ])
169
+ })
170
+ ]));
171
+
172
+ const node = rules([
173
+ style({
174
+ path: quoted(any('imported-parent-chain.jess'))
175
+ }, {
176
+ type: 'import'
177
+ })
178
+ ]);
179
+
180
+ const evald = await node.eval(context);
181
+ const importedRules = evald.at(0, context) as Rules;
182
+ const importedRuleset = importedRules.at(0, context);
183
+ const importedDecl = (importedRuleset as any).rules.at(0, context);
184
+ const rootCtx = asRulesContext(context, evald);
185
+ const importedCtx = asRulesContext(context, importedRules);
186
+
187
+ expect(getParent(importedRules, rootCtx)).toBe(evald);
188
+ expect(getParent(importedRuleset, importedCtx)).toBe(importedRules);
189
+ expect(getParent((importedRuleset as any).rules, importedCtx)).toBe(importedRuleset);
190
+ expect(getParent(importedDecl, importedCtx)).toBe((importedRuleset as any).rules);
191
+ });
192
+
193
+ it('compose type cannot see parent rules variables', async () => {
194
+ // Set up imported file
195
+ const composedPath = resolve(process.cwd(), 'composed.jess');
196
+ context.sourceTrees.set(composedPath, rules([
197
+ ruleset({
198
+ selector: sellist([sel([el('.composed')])]),
199
+ rules: rules([
200
+ decl({ name: any('color'), value: ref('parentVar', { type: 'variable', fallbackValue: any('blue') }) })
201
+ ])
202
+ })
203
+ ]));
204
+
205
+ // Parent file with variable
206
+ const parentVar = vardecl({ name: 'parentVar', value: any('red') });
207
+ const node = rules([
208
+ parentVar,
209
+ style({
210
+ path: quoted(any('composed.jess'))
211
+ }, {
212
+ type: 'compose',
213
+ namespace: '*'
214
+ })
215
+ ]);
216
+
217
+ const evald = await node.eval(context);
218
+ const composedRules = evald.at(1, context) as Rules;
219
+ const composedRuleset = composedRules.at(0, context);
220
+ const composedCtx = asRulesContext(context, (composedRuleset as any).rules);
221
+
222
+ // The composed ruleset should NOT be able to reference the parent variable
223
+ // It should use the fallback value instead
224
+ const composedDecl = (composedRuleset as any).rules.at(0, composedCtx);
225
+ const resolved = await composedDecl.eval(composedCtx);
226
+ expect(resolved.toTrimmedString({ context: composedCtx })).toBe('color: blue');
227
+ });
228
+
229
+ it('import type variables are visible to parent', async () => {
230
+ context.sourceTrees.set('imported.jess', rules([
231
+ vardecl({ name: 'importedVar', value: any('green') })
232
+ ]));
233
+
234
+ const node = rules([
235
+ style({
236
+ path: quoted(any('imported.jess'))
237
+ }, {
238
+ type: 'import'
239
+ }),
240
+ ruleset({
241
+ selector: sellist([sel([el('.parent')])]),
242
+ rules: rules([
243
+ decl({ name: any('color'), value: ref('importedVar', { type: 'variable' }) })
244
+ ])
245
+ })
246
+ ]);
247
+
248
+ const evald = await node.eval(context);
249
+ const parentRuleset = evald.at(1, context);
250
+ const parentCtx = asRulesContext(context, (parentRuleset as any).rules);
251
+ const parentDecl = (parentRuleset as any).rules.at(0, parentCtx);
252
+ const resolved = await parentDecl.eval(parentCtx);
253
+ expect(resolved.toTrimmedString({ context: parentCtx })).toBe('color: green');
254
+ });
255
+
256
+ it('compose type variables are visible to parent', async () => {
257
+ const composedPath = resolve(process.cwd(), 'composed.jess');
258
+ context.sourceTrees.set(composedPath, rules([
259
+ vardecl({ name: 'composedVar', value: any('purple') })
260
+ ]));
261
+
262
+ const node = rules([
263
+ style({
264
+ path: quoted(any('composed.jess'))
265
+ }, {
266
+ type: 'compose',
267
+ namespace: '*'
268
+ }),
269
+ ruleset({
270
+ selector: sellist([sel([el('.parent')])]),
271
+ rules: rules([
272
+ decl({ name: any('color'), value: ref('composedVar', { type: 'variable' }) })
273
+ ])
274
+ })
275
+ ]);
276
+
277
+ const evald = await node.eval(context);
278
+ const parentRuleset = evald.at(1, context);
279
+ const parentCtx = asRulesContext(context, (parentRuleset as any).rules);
280
+ const parentDecl = (parentRuleset as any).rules.at(0, parentCtx);
281
+ const resolved = await parentDecl.eval(parentCtx);
282
+ // Should use composedVar from the compose
283
+ expect(resolved.toTrimmedString({ context: parentCtx })).toBe('color: purple');
284
+ });
285
+ });
286
+
287
+ describe('mixin visibility', () => {
288
+ it('import type mixins are visible to parent', async () => {
289
+ context.sourceTrees.set('imported.jess', rules([
290
+ mixin({
291
+ name: any('importedMixin'),
292
+ rules: rules([
293
+ decl({ name: any('color'), value: any('blue') })
294
+ ])
295
+ })
296
+ ]));
297
+
298
+ const node = rules([
299
+ style({
300
+ path: quoted(any('imported.jess'))
301
+ }, {
302
+ type: 'import'
303
+ }),
304
+ ruleset({
305
+ selector: sellist([sel([el('.parent')])]),
306
+ rules: rules([
307
+ call({ name: ref('importedMixin', { type: 'mixin' }) })
308
+ ])
309
+ })
310
+ ]);
311
+
312
+ const evald = await node.eval(context);
313
+ const parentRuleset = evald.at(1, context);
314
+ const mixinCall = (parentRuleset as any).rules.at(0, context);
315
+ const resolved = await mixinCall.eval(context);
316
+ expect(resolved.toTrimmedString({ context })).toContainString('color: blue');
317
+ });
318
+
319
+ it('compose type mixins are visible to parent', async () => {
320
+ const composedPath = resolve(process.cwd(), 'composed.jess');
321
+ context.sourceTrees.set(composedPath, rules([
322
+ mixin({
323
+ name: any('composedMixin'),
324
+ rules: rules([
325
+ decl({ name: any('color'), value: any('yellow') })
326
+ ])
327
+ })
328
+ ]));
329
+
330
+ const node = rules([
331
+ style({
332
+ path: quoted(any('composed.jess'))
333
+ }, {
334
+ type: 'compose',
335
+ namespace: '*'
336
+ }),
337
+ ruleset({
338
+ selector: sellist([sel([el('.parent')])]),
339
+ rules: rules([
340
+ call({ name: ref('composedMixin', { type: 'mixin' }) })
341
+ ])
342
+ })
343
+ ]);
344
+
345
+ const evald = await node.eval(context);
346
+ const parentRuleset = evald.at(1, context);
347
+ const mixinCall = (parentRuleset as any).rules.at(0, context);
348
+ const resolved = await mixinCall.eval(context);
349
+ expect(resolved.toTrimmedString({ context })).toContainString('color: yellow');
350
+ });
351
+
352
+ it('reference import makes mixins optional', async () => {
353
+ const referencedPath = resolve(process.cwd(), 'referenced.jess');
354
+ context.sourceTrees.set(referencedPath, rules([
355
+ mixin({
356
+ name: any('referencedMixin'),
357
+ rules: rules([
358
+ decl({ name: any('color'), value: any('white') })
359
+ ])
360
+ })
361
+ ]));
362
+
363
+ // First try to use it directly - should work
364
+ const node1 = rules([
365
+ style({
366
+ path: quoted(any('referenced.jess'))
367
+ }, {
368
+ type: 'import',
369
+ importOptions: { reference: true }
370
+ }),
371
+ ruleset({
372
+ selector: sellist([sel([el('.parent')])]),
373
+ rules: rules([
374
+ call({ name: ref('referencedMixin', { type: 'mixin' }) })
375
+ ])
376
+ })
377
+ ]);
378
+
379
+ const evald1 = await node1.eval(context);
380
+ const parentRuleset1 = evald1.at(1, context);
381
+ const mixinCall1 = (parentRuleset1 as any).rules.at(0, context);
382
+ const resolved1 = await mixinCall1.eval(context);
383
+ expect(resolved1.toTrimmedString({ context })).toContainString('color: white');
384
+ });
385
+ });
386
+
387
+ describe('ruleset visibility', () => {
388
+ it('import type rulesets are visible to parent', async () => {
389
+ context.sourceTrees.set('imported.jess', rules([
390
+ ruleset({
391
+ selector: sellist([sel([el('.imported')])]),
392
+ rules: rules([
393
+ decl({ name: any('color'), value: any('red') })
394
+ ])
395
+ })
396
+ ]));
397
+
398
+ const node = rules([
399
+ style({
400
+ path: quoted(any('imported.jess'))
401
+ }, {
402
+ type: 'import'
403
+ })
404
+ ]);
405
+
406
+ const evald = await node.eval(context);
407
+ const importedRules = evald.at(0, context) as Rules;
408
+ const importedRuleset = importedRules.at(0, context);
409
+ expect(importedRuleset).toBeDefined();
410
+ expect(importedRuleset.toTrimmedString({ context })).toContainString('.imported');
411
+ });
412
+
413
+ it('non-mutable import makes rulesets private', async () => {
414
+ const protectedPath = resolve(process.cwd(), 'protected.jess');
415
+ context.sourceTrees.set(protectedPath, rules([
416
+ ruleset({
417
+ selector: sellist([sel([el('.protected')])]),
418
+ rules: rules([
419
+ decl({ name: any('color'), value: any('green') })
420
+ ])
421
+ })
422
+ ]));
423
+
424
+ const node = rules([
425
+ style({
426
+ path: quoted(any('protected.jess'))
427
+ }, {
428
+ type: 'import',
429
+ importOptions: { mutable: false }
430
+ })
431
+ ]);
432
+
433
+ const evald = await node.eval(context);
434
+ const importedRules = evald.at(0, context) as Rules;
435
+ // Ruleset should still exist but be private
436
+ const protectedRuleset = importedRules.at(0, context);
437
+ expect(protectedRuleset).toBeDefined();
438
+ // But it should not be findable via registry lookup
439
+ const found = getRulesetWithContext(context, evald, '.protected');
440
+ expect(found).toBeUndefined();
441
+ });
442
+
443
+ it('reference import makes rulesets optional', async () => {
444
+ const referencedPath = resolve(process.cwd(), 'referenced.jess');
445
+ context.sourceTrees.set(referencedPath, rules([
446
+ ruleset({
447
+ selector: sellist([sel([el('.referenced')])]),
448
+ rules: rules([
449
+ decl({ name: any('color'), value: any('blue') })
450
+ ])
451
+ })
452
+ ]));
453
+
454
+ const node = rules([
455
+ style({
456
+ path: quoted(any('referenced.jess'))
457
+ }, {
458
+ type: 'import',
459
+ importOptions: { reference: true }
460
+ })
461
+ ]);
462
+
463
+ const evald = await node.eval(context);
464
+ const importedRules = evald.at(0, context) as Rules;
465
+ const referencedRuleset = importedRules.at(0, context);
466
+ expect(referencedRuleset).toBeDefined();
467
+ // Optional means it's only considered if not found elsewhere
468
+ // This is mainly for extend behavior
469
+ });
470
+ });
471
+
472
+ describe('readonly behavior', () => {
473
+ it('compose type is readonly by default', async () => {
474
+ const composedPath = resolve(process.cwd(), 'composed.jess');
475
+ context.sourceTrees.set(composedPath, rules([
476
+ vardecl({ name: 'composedVar', value: any('initial') })
477
+ ]));
478
+
479
+ const node = rules([
480
+ style({
481
+ path: quoted(any('composed.jess'))
482
+ }, {
483
+ type: 'compose',
484
+ namespace: '*'
485
+ }),
486
+ vardecl({ name: 'composedVar', value: any('modified') })
487
+ ]);
488
+
489
+ // Should throw because we're trying to shadow a readonly variable at the same level
490
+ await expect(async () => {
491
+ await node.eval(context);
492
+ }).rejects.toThrowError('"composedVar" is readonly');
493
+ });
494
+
495
+ it('import type is NOT readonly by default', async () => {
496
+ context.sourceTrees.set('imported.jess', rules([
497
+ vardecl({ name: 'importedVar', value: any('initial') })
498
+ ]));
499
+
500
+ const node = rules([
501
+ style({
502
+ path: quoted(any('imported.jess'))
503
+ }, {
504
+ type: 'import'
505
+ }),
506
+ vardecl({ name: 'importedVar', value: any('modified') })
507
+ ]);
508
+
509
+ const evald = await node.eval(context);
510
+ const importedRules = evald.at(0, context) as Rules;
511
+ const varDecl = getVarWithContext(context, evald, 'importedVar');
512
+
513
+ // Should have modified value because it's not readonly
514
+ expect(varDecl).toBeDefined();
515
+ // The variable lookup should return the local variable (index 1) which wins over the imported variable (index 0)
516
+ // because local variables in the current Rules are treated as having the highest index (Number.MAX_SAFE_INTEGER)
517
+ expect(varDecl.toTrimmedString({ context })).toBe('$importedVar: modified');
518
+ });
519
+
520
+ it.skip('readonly can be overridden for compose', async () => {
521
+ // Skipped: There may not be a syntactic way to set @-compose to readonly: false
522
+ const composedPath = resolve(process.cwd(), 'composed.jess');
523
+ context.sourceTrees.set(composedPath, rules([
524
+ vardecl({ name: 'composedVar', value: any('initial') })
525
+ ]));
526
+
527
+ const node = rules([
528
+ style({
529
+ path: quoted(any('composed.jess'))
530
+ }, {
531
+ type: 'compose',
532
+ namespace: '*',
533
+ importOptions: { readonly: false }
534
+ }),
535
+ vardecl({ name: 'composedVar', value: any('modified') })
536
+ ]);
537
+
538
+ const evald = await node.eval(context);
539
+ const varDecl = getVarWithContext(context, evald, 'composedVar');
540
+
541
+ // Should have modified value because readonly was overridden
542
+ expect(varDecl.toTrimmedString({ context })).toBe('$composedVar: modified');
543
+ });
544
+
545
+ it('readonly can be set for import', async () => {
546
+ const importedPath = resolve(process.cwd(), 'imported.jess');
547
+ context.sourceTrees.set(importedPath, rules([
548
+ vardecl({ name: 'importedVar', value: any('initial') })
549
+ ]));
550
+
551
+ const node = rules([
552
+ style({
553
+ path: quoted(any('imported.jess'))
554
+ }, {
555
+ type: 'import',
556
+ importOptions: { readonly: true }
557
+ }),
558
+ vardecl({ name: 'importedVar', value: any('modified') })
559
+ ]);
560
+
561
+ // Should throw because we're trying to shadow a readonly variable at the same level
562
+ await expect(async () => {
563
+ await node.eval(context);
564
+ }).rejects.toThrowError('"importedVar" is readonly');
565
+ });
566
+ });
567
+
568
+ describe('forward behavior', () => {
569
+ it.skip('forwarded members are not visible locally, but are visible downstream', async () => {
570
+ const forwardedPath = resolve(process.cwd(), 'forwarded.jess');
571
+ context.sourceTrees.set(forwardedPath, rules([
572
+ vardecl({ name: 'forwardedVar', value: any('ok') })
573
+ ]));
574
+
575
+ const forwarderPath = resolve(process.cwd(), 'forwarder.jess');
576
+ context.sourceTrees.set(forwarderPath, rules([
577
+ style(
578
+ { path: quoted(any('forwarded.jess')) },
579
+ { type: 'compose', namespace: '*', importOptions: { forward: true } }
580
+ )
581
+ // Nothing else in this module.
582
+ ]));
583
+
584
+ // Evaluate forwarder module (as if compiling that file directly)
585
+ const evaldForwarder = await rules([
586
+ style({ path: quoted(any('forwarder.jess')) }, { type: 'import' })
587
+ ]).eval(context);
588
+
589
+ // Locally (inside the forwarder module), forwarded members should NOT be visible.
590
+ const forwarderRules = evaldForwarder.at(0, context) as Rules;
591
+ const localLookup = getVarWithContext(context, forwarderRules, 'forwardedVar');
592
+ expect(localLookup).toBeUndefined();
593
+
594
+ // Downstream (a consumer importing the forwarder), forwarded members SHOULD be visible.
595
+ const consumer = rules([
596
+ style({ path: quoted(any('forwarder.jess')) }, { type: 'import' })
597
+ ]);
598
+ const evaldConsumer = await consumer.eval(context);
599
+ const consumerImport = evaldConsumer.at(0, context) as Rules;
600
+ const consumerImportChildren = Array.from((consumerImport as any).value ?? []);
601
+ const nestedForward = consumerImportChildren[0];
602
+ const downstreamLookup = getVarWithContext(context, evaldConsumer, 'forwardedVar');
603
+ context.rulesContext = evaldConsumer;
604
+ const importLookupFromConsumerContext = consumerImport.find('declaration', 'forwardedVar', 'VarDeclaration', {
605
+ context,
606
+ searchParents: true
607
+ });
608
+ context.rulesContext = consumerImport;
609
+ const importLookupFromImportContext = consumerImport.find('declaration', 'forwardedVar', 'VarDeclaration', {
610
+ context,
611
+ searchParents: true
612
+ });
613
+ expect(downstreamLookup).toBeDefined();
614
+ expect(downstreamLookup.toTrimmedString({ context })).toBe('$forwardedVar: ok');
615
+ });
616
+ });
617
+
618
+ describe('with values', () => {
619
+ it('can inject variables with "with" type', async () => {
620
+ const libraryPath = resolve(process.cwd(), 'library.jess');
621
+ context.sourceTrees.set(libraryPath, rules([
622
+ ruleset({
623
+ selector: sellist([sel([el('.box')])]),
624
+ rules: rules([
625
+ decl({ name: any('color'), value: ref('primaryColor', { type: 'variable' }) })
626
+ ])
627
+ })
628
+ ]));
629
+
630
+ const node = rules([
631
+ style({
632
+ path: quoted(any('library.jess')),
633
+ withNode: rules([
634
+ vardecl({ name: 'primaryColor', value: any('purple') })
635
+ ]) as any,
636
+ withType: 'with'
637
+ }, {
638
+ type: 'compose',
639
+ namespace: '*'
640
+ })
641
+ ]);
642
+
643
+ const evald = await node.eval(context);
644
+ const composedRules = evald.at(0, context) as Rules;
645
+
646
+ // Test 1: Verify injected variables are accessible
647
+ const injectedVar = getVarWithContext(context, composedRules, 'primaryColor');
648
+ expect(injectedVar).toBeDefined();
649
+ // The variable declaration exists, which means the injection worked
650
+ // We can verify the value by evaluating the variable's value property
651
+ const injectedVarValueNode = (injectedVar as any).value;
652
+ const injectedVarValue = await injectedVarValueNode.eval(context);
653
+ expect(injectedVarValue.toTrimmedString({ context })).toBe('purple');
654
+
655
+ // Test 2: Verify computed values based on injected variables are correct
656
+ // Find the ruleset and its declaration
657
+ const foundRuleset = Array.from(composedRules.value).find(
658
+ node => isNode(node, N.Ruleset)
659
+ );
660
+ expect(foundRuleset).toBeDefined();
661
+ const foundCtx = asRulesContext(context, (foundRuleset as any).rules);
662
+ const foundDecl = (foundRuleset as any).rules.at(0, foundCtx);
663
+ expect(foundDecl).toBeDefined();
664
+ const resolved = await foundDecl.eval(foundCtx);
665
+ expect(resolved.toTrimmedString({ context: foundCtx })).toBe('color: purple');
666
+ });
667
+
668
+ it('configured compose returned trees already preserve descendant parent chains to the returned Rules', async () => {
669
+ const libraryPath = resolve(process.cwd(), 'library-parent-chain.jess');
670
+ context.sourceTrees.set(libraryPath, rules([
671
+ ruleset({
672
+ selector: sellist([sel([el('.box')])]),
673
+ rules: rules([
674
+ decl({ name: any('color'), value: any('purple') })
675
+ ])
676
+ })
677
+ ]));
678
+
679
+ const node = rules([
680
+ style({
681
+ path: quoted(any('library-parent-chain.jess')),
682
+ withNode: rules([
683
+ vardecl({ name: 'primaryColor', value: any('purple') })
684
+ ]) as any,
685
+ withType: 'with'
686
+ }, {
687
+ type: 'compose',
688
+ namespace: '*'
689
+ })
690
+ ]);
691
+
692
+ const evald = await node.eval(context);
693
+ const composedRules = evald.at(0, context) as Rules;
694
+ const foundRuleset = Array.from(composedRules.value).find(
695
+ node => isNode(node, N.Ruleset)
696
+ );
697
+ const foundDecl = (foundRuleset as any).rules.at(0, context);
698
+ const composedCtx = asRulesContext(context, composedRules);
699
+
700
+ expect(getParent(foundRuleset!, composedCtx)).toBe(composedRules);
701
+ expect(getParent((foundRuleset as any).rules, composedCtx)).toBe(foundRuleset);
702
+ expect(getParent(foundDecl, composedCtx)).toBe((foundRuleset as any).rules);
703
+ });
704
+
705
+ it('can inject variables with "set" type', async () => {
706
+ const libraryPath = resolve(process.cwd(), 'library.jess');
707
+ context.sourceTrees.set(libraryPath, rules([
708
+ ruleset({
709
+ selector: sellist([sel([el('.box')])]),
710
+ rules: rules([
711
+ decl({ name: any('color'), value: ref('primaryColor', { type: 'variable' }) })
712
+ ])
713
+ })
714
+ ]));
715
+
716
+ const node = rules([
717
+ style({
718
+ path: quoted(any('library.jess')),
719
+ withNode: rules([
720
+ vardecl({ name: 'primaryColor', value: any('orange') })
721
+ ]) as any,
722
+ withType: 'set'
723
+ }, {
724
+ type: 'compose',
725
+ namespace: '*'
726
+ })
727
+ ]);
728
+
729
+ const evald = await node.eval(context);
730
+ const composedRules = evald.at(0, context) as Rules;
731
+
732
+ // Test 1: Verify injected variables are accessible
733
+ const injectedVar = getVarWithContext(context, composedRules, 'primaryColor');
734
+ expect(injectedVar).toBeDefined();
735
+ // The variable declaration exists, which means the injection worked
736
+ // We can verify the value by evaluating the variable's value property
737
+ const injectedVarValueNode = (injectedVar as any).value;
738
+ const injectedVarValue = await injectedVarValueNode.eval(context);
739
+ expect(injectedVarValue.toTrimmedString({ context })).toBe('orange');
740
+
741
+ // Test 2: Verify computed values based on injected variables are correct
742
+ // Find the ruleset and its declaration
743
+ const foundRuleset = Array.from(composedRules.value).find(
744
+ node => isNode(node, N.Ruleset)
745
+ );
746
+ expect(foundRuleset).toBeDefined();
747
+ const foundCtx = asRulesContext(context, (foundRuleset as any).rules);
748
+ const foundDecl = (foundRuleset as any).rules.at(0, foundCtx);
749
+ expect(foundDecl).toBeDefined();
750
+ const resolved = await foundDecl.eval(foundCtx);
751
+ expect(resolved.toTrimmedString({ context: foundCtx })).toBe('color: orange');
752
+ });
753
+
754
+ it('updates computed variables with "with" type - scope lookup', async () => {
755
+ // Test that when we inject a variable, dependent variables are updated
756
+ // This tests scope-based lookup ($var)
757
+ const libraryPath = resolve(process.cwd(), 'library.jess');
758
+ context.sourceTrees.set(libraryPath, rules([
759
+ vardecl({ name: 'baseColor', value: any('red') }),
760
+ vardecl({ name: 'derivedColor', value: ref('baseColor', { type: 'variable' }) })
761
+ ]));
762
+
763
+ const node = rules([
764
+ style({
765
+ path: quoted(any('library.jess')),
766
+ withNode: rules([
767
+ vardecl({ name: 'baseColor', value: any('blue') })
768
+ ]) as any,
769
+ withType: 'with'
770
+ }, {
771
+ type: 'compose',
772
+ namespace: '*'
773
+ })
774
+ ]);
775
+
776
+ const evald = await node.eval(context);
777
+ const composedRules = evald.at(0, context) as Rules;
778
+
779
+ // Verify baseColor has the injected value
780
+ const baseColor = getVarWithContext(context, composedRules, 'baseColor');
781
+ expect(baseColor).toBeDefined();
782
+ const baseColorValue = await (baseColor as any).value.eval(context);
783
+ expect(baseColorValue.toTrimmedString({ context })).toBe('blue');
784
+
785
+ // Verify derivedColor reflects the injected value (scope lookup)
786
+ const derivedColor = getVarWithContext(context, composedRules, 'derivedColor');
787
+ expect(derivedColor).toBeDefined();
788
+ const derivedColorValue = await (derivedColor as any).value.eval(context);
789
+ expect(derivedColorValue.toTrimmedString({ context })).toBe('blue');
790
+ });
791
+
792
+ it('updates computed variables with "with" type - linear lookup', async () => {
793
+ // Test that when we inject a variable, dependent variables are updated
794
+ // This tests linear lookup ($^var)
795
+ const libraryPath = resolve(process.cwd(), 'library.jess');
796
+ context.sourceTrees.set(libraryPath, rules([
797
+ vardecl({ name: 'baseColor', value: any('red') }),
798
+ vardecl({ name: 'derivedColor', value: ref('baseColor', { type: 'variable', resolution: 'linear' }) })
799
+ ]));
800
+
801
+ const node = rules([
802
+ style({
803
+ path: quoted(any('library.jess')),
804
+ withNode: rules([
805
+ vardecl({ name: 'baseColor', value: any('green') })
806
+ ]) as any,
807
+ withType: 'with'
808
+ }, {
809
+ type: 'compose',
810
+ namespace: '*'
811
+ })
812
+ ]);
813
+
814
+ const evald = await node.eval(context);
815
+ const composedRules = evald.at(0, context) as Rules;
816
+
817
+ // Verify baseColor has the injected value
818
+ const baseColor = getVarWithContext(context, composedRules, 'baseColor');
819
+ expect(baseColor).toBeDefined();
820
+ const baseColorValue = await (baseColor as any).value.eval(context);
821
+ expect(baseColorValue.toTrimmedString({ context })).toBe('green');
822
+
823
+ // Verify derivedColor reflects the injected value (linear lookup)
824
+ const derivedColor = getVarWithContext(context, composedRules, 'derivedColor');
825
+ expect(derivedColor).toBeDefined();
826
+ const derivedColorValue = await (derivedColor as any).value.eval(context);
827
+ expect(derivedColorValue.toTrimmedString({ context })).toBe('green');
828
+ });
829
+
830
+ it('updates computed variables with "set" type - scope lookup', async () => {
831
+ // Test that when we inject a variable with "set", dependent variables are updated
832
+ // This tests scope-based lookup ($var)
833
+ const libraryPath = resolve(process.cwd(), 'library.jess');
834
+ context.sourceTrees.set(libraryPath, rules([
835
+ vardecl({ name: 'baseColor', value: any('red') }),
836
+ vardecl({ name: 'derivedColor', value: ref('baseColor', { type: 'variable' }) })
837
+ ]));
838
+
839
+ const node = rules([
840
+ style({
841
+ path: quoted(any('library.jess')),
842
+ withNode: rules([
843
+ vardecl({ name: 'baseColor', value: any('yellow') })
844
+ ]) as any,
845
+ withType: 'set'
846
+ }, {
847
+ type: 'compose',
848
+ namespace: '*'
849
+ })
850
+ ]);
851
+
852
+ const evald = await node.eval(context);
853
+ const composedRules = evald.at(0, context) as Rules;
854
+
855
+ // Verify baseColor has the injected value
856
+ const baseColor = getVarWithContext(context, composedRules, 'baseColor');
857
+ expect(baseColor).toBeDefined();
858
+ const baseColorValue = await (baseColor as any).value.eval(context);
859
+ expect(baseColorValue.toTrimmedString({ context })).toBe('yellow');
860
+
861
+ // Verify derivedColor reflects the injected value (scope lookup)
862
+ const derivedColor = getVarWithContext(context, composedRules, 'derivedColor');
863
+ expect(derivedColor).toBeDefined();
864
+ const derivedColorValue = await (derivedColor as any).value.eval(context);
865
+ expect(derivedColorValue.toTrimmedString({ context })).toBe('yellow');
866
+ });
867
+
868
+ it('updates computed variables with "set" type - linear lookup', async () => {
869
+ // Test that when we inject a variable with "set", dependent variables are updated
870
+ // This tests linear lookup ($^var)
871
+ const libraryPath = resolve(process.cwd(), 'library.jess');
872
+ context.sourceTrees.set(libraryPath, rules([
873
+ vardecl({ name: 'baseColor', value: any('red') }),
874
+ vardecl({ name: 'derivedColor', value: ref('baseColor', { type: 'variable', resolution: 'linear' }) })
875
+ ]));
876
+
877
+ const node = rules([
878
+ style({
879
+ path: quoted(any('library.jess')),
880
+ withNode: rules([
881
+ vardecl({ name: 'baseColor', value: any('cyan') })
882
+ ]) as any,
883
+ withType: 'set'
884
+ }, {
885
+ type: 'compose',
886
+ namespace: '*'
887
+ })
888
+ ]);
889
+
890
+ const evald = await node.eval(context);
891
+ const composedRules = evald.at(0, context) as Rules;
892
+
893
+ // When 'set' is used, structure is flattened:
894
+ // [new injected variables (not found), ...all nodes from imported rules (with replacements)]
895
+ // All variables are in the same Rules scope
896
+
897
+ // Verify baseColor was injected (should be the injected one, not the original)
898
+ const baseColor = getVarWithContext(context, composedRules, 'baseColor');
899
+ expect(baseColor).toBeDefined();
900
+ const baseColorValue = await (baseColor as any).value.eval(context);
901
+ expect(baseColorValue.toTrimmedString({ context })).toBe('cyan');
902
+
903
+ // Verify derivedColor reflects the injected value (linear lookup)
904
+ // derivedColor is in the composed rules (flattened structure)
905
+ // It should be able to find the injected baseColor in the same scope
906
+ const derivedColor = getVarWithContext(context, composedRules, 'derivedColor');
907
+ expect(derivedColor).toBeDefined();
908
+ // The value should already be evaluated during the import evaluation
909
+ // and should have used the injected baseColor
910
+ const derivedColorValue = await (derivedColor as any).value.eval(context);
911
+ expect(derivedColorValue.toTrimmedString({ context })).toBe('cyan');
912
+ });
913
+
914
+ it('throws if "set" is used more than once', async () => {
915
+ const libraryPath = resolve(process.cwd(), 'library.jess');
916
+ context.sourceTrees.set(libraryPath, rules([
917
+ vardecl({ name: 'var', value: any('value') })
918
+ ]));
919
+
920
+ // First use
921
+ const node1 = rules([
922
+ style({
923
+ path: quoted(any('library.jess')),
924
+ withNode: rules([
925
+ vardecl({ name: 'var', value: any('first') })
926
+ ]) as any,
927
+ withType: 'set'
928
+ }, {
929
+ type: 'compose',
930
+ namespace: '*'
931
+ })
932
+ ]);
933
+ await node1.eval(context);
934
+
935
+ // Second use - should throw
936
+ const node2 = rules([
937
+ style({
938
+ path: quoted(any('library.jess')),
939
+ withNode: rules([
940
+ vardecl({ name: 'var', value: any('second') })
941
+ ]) as any,
942
+ withType: 'set'
943
+ }, {
944
+ type: 'compose',
945
+ namespace: '*'
946
+ })
947
+ ]);
948
+
949
+ await expect(async () => {
950
+ await node2.eval(context);
951
+ }).rejects.toThrow('Cannot configure a stylesheet more than once');
952
+ });
953
+
954
+ it('two sequential "with" imports resolve independently via rendered output', async () => {
955
+ const libraryPath = resolve(process.cwd(), 'library-with-seq.jess');
956
+ context.sourceTrees.set(libraryPath, rules([
957
+ vardecl({ name: 'baseColor', value: any('red') }),
958
+ ruleset({
959
+ selector: sellist([sel([el('.box')])]),
960
+ rules: rules([
961
+ decl({ name: any('color'), value: ref('baseColor', { type: 'variable' }) })
962
+ ])
963
+ })
964
+ ]));
965
+
966
+ // First import: baseColor = blue
967
+ const node1 = rules([
968
+ style({
969
+ path: quoted(any('library-with-seq.jess')),
970
+ withNode: rules([
971
+ vardecl({ name: 'baseColor', value: any('blue') })
972
+ ]) as any,
973
+ withType: 'with'
974
+ }, { type: 'compose', namespace: '*' })
975
+ ]);
976
+ const evald1 = await node1.eval(context);
977
+ expect(evald1.render(context)).toContain('color: blue');
978
+
979
+ // Second import (fresh context): baseColor = green — must not see blue
980
+ const context2 = createTestContext();
981
+ context2.sourceTrees.set(libraryPath, context.sourceTrees.get(libraryPath)!);
982
+ const node2 = rules([
983
+ style({
984
+ path: quoted(any('library-with-seq.jess')),
985
+ withNode: rules([
986
+ vardecl({ name: 'baseColor', value: any('green') })
987
+ ]) as any,
988
+ withType: 'with'
989
+ }, { type: 'compose', namespace: '*' })
990
+ ]);
991
+ const evald2 = await node2.eval(context2);
992
+ expect(evald2.render(context2)).toContain('color: green');
993
+ expect(evald2.render(context2)).not.toContain('color: blue');
994
+ });
995
+
996
+ it('configured compose placements can reuse canonical imported rulesets while rendering with different values', async () => {
997
+ const libraryPath = resolve(process.cwd(), 'library-with-canonical-reuse.jess');
998
+ const sourceRuleset = ruleset({
999
+ selector: sellist([sel([el('.box')])]),
1000
+ rules: rules([
1001
+ decl({ name: any('color'), value: ref('baseColor', { type: 'variable' }) })
1002
+ ])
1003
+ });
1004
+ const sourceBody = sourceRuleset.get('rules');
1005
+ const sourceDecl = sourceBody.at(0, context);
1006
+ const sourceTree = rules([
1007
+ vardecl({ name: 'baseColor', value: any('red') }),
1008
+ sourceRuleset
1009
+ ]);
1010
+
1011
+ const evaluateCompose = async (color: string) => {
1012
+ const localContext = createTestContext();
1013
+ localContext.sourceTrees.set(libraryPath, sourceTree);
1014
+ const entry = rules([
1015
+ style({
1016
+ path: quoted(any('library-with-canonical-reuse.jess')),
1017
+ withNode: rules([
1018
+ vardecl({ name: 'baseColor', value: any(color) })
1019
+ ]) as any,
1020
+ withType: 'with'
1021
+ }, { type: 'compose', namespace: '*' })
1022
+ ]);
1023
+ const evald = await entry.eval(localContext);
1024
+ const importedRules = evald.at(0, localContext) as Rules;
1025
+ const importedRuleset = Array.from(importedRules.value).find(node => isNode(node, N.Ruleset)) as Ruleset;
1026
+ return { localContext, evald, importedRules, importedRuleset };
1027
+ };
1028
+
1029
+ const blue = await evaluateCompose('blue');
1030
+ const green = await evaluateCompose('green');
1031
+
1032
+ const blueDecl = (blue.importedRuleset as any).rules.at(0, blue.localContext);
1033
+ const greenDecl = (green.importedRuleset as any).rules.at(0, green.localContext);
1034
+
1035
+ expect(blue.importedRuleset).not.toBe(green.importedRuleset);
1036
+ expect(blue.importedRuleset.sourceNode).toBe(sourceRuleset);
1037
+ expect(green.importedRuleset.sourceNode).toBe(sourceRuleset);
1038
+ expect((blue.importedRuleset as any).rules.sourceNode).toBe(sourceBody);
1039
+ expect((green.importedRuleset as any).rules.sourceNode).toBe(sourceBody);
1040
+ expect(blueDecl.sourceNode).toBe(sourceDecl);
1041
+ expect(greenDecl.sourceNode).toBe(sourceDecl);
1042
+ expect(blue.evald.render(blue.localContext)).toContain('color: blue');
1043
+ expect(green.evald.render(green.localContext)).toContain('color: green');
1044
+ expect(sourceRuleset.parent).toBe(sourceTree);
1045
+ expect(sourceBody.parent).toBe(sourceRuleset);
1046
+ expect(sourceDecl.parent).toBe(sourceBody);
1047
+ });
1048
+
1049
+ it('with replaces existing root vars and nested rules resolve the replacement', async () => {
1050
+ const libraryPath = resolve(process.cwd(), 'library-with-replace-root-var.jess');
1051
+ context.sourceTrees.set(libraryPath, rules([
1052
+ vardecl({ name: 'baseColor', value: any('red') }),
1053
+ ruleset({
1054
+ selector: sellist([sel([el('.box')])]),
1055
+ rules: rules([
1056
+ decl({ name: any('color'), value: ref('baseColor', { type: 'variable' }) })
1057
+ ])
1058
+ })
1059
+ ]));
1060
+
1061
+ const node = rules([
1062
+ style({
1063
+ path: quoted(any('library-with-replace-root-var.jess')),
1064
+ withNode: rules([
1065
+ vardecl({ name: 'baseColor', value: any('blue') })
1066
+ ]) as any,
1067
+ withType: 'with'
1068
+ }, { type: 'compose', namespace: '*' })
1069
+ ]);
1070
+
1071
+ const evald = await node.eval(context);
1072
+ expect(evald.render(context)).toContain('color: blue');
1073
+ expect(evald.render(context)).not.toContain('color: red');
1074
+ });
1075
+
1076
+ it('with injects missing vars and nested rules can reference them during evaluation', async () => {
1077
+ const libraryPath = resolve(process.cwd(), 'library-with-inject-var.jess');
1078
+ context.sourceTrees.set(libraryPath, rules([
1079
+ ruleset({
1080
+ selector: sellist([sel([el('.box')])]),
1081
+ rules: rules([
1082
+ decl({ name: any('color'), value: ref('injectedColor', { type: 'variable' }) })
1083
+ ])
1084
+ })
1085
+ ]));
1086
+
1087
+ const node = rules([
1088
+ style({
1089
+ path: quoted(any('library-with-inject-var.jess')),
1090
+ withNode: rules([
1091
+ vardecl({ name: 'injectedColor', value: any('purple') })
1092
+ ]) as any,
1093
+ withType: 'with'
1094
+ }, { type: 'compose', namespace: '*' })
1095
+ ]);
1096
+
1097
+ const evald = await node.eval(context);
1098
+ expect(evald.render(context)).toContain('color: purple');
1099
+ });
1100
+
1101
+ it('set replaces existing root vars and nested rules resolve the replacement', async () => {
1102
+ const libraryPath = resolve(process.cwd(), 'library-set-replace-root-var.jess');
1103
+ context.sourceTrees.set(libraryPath, rules([
1104
+ vardecl({ name: 'baseColor', value: any('red') }),
1105
+ ruleset({
1106
+ selector: sellist([sel([el('.box')])]),
1107
+ rules: rules([
1108
+ decl({ name: any('color'), value: ref('baseColor', { type: 'variable' }) })
1109
+ ])
1110
+ })
1111
+ ]));
1112
+
1113
+ const node = rules([
1114
+ style({
1115
+ path: quoted(any('library-set-replace-root-var.jess')),
1116
+ withNode: rules([
1117
+ vardecl({ name: 'baseColor', value: any('orange') })
1118
+ ]) as any,
1119
+ withType: 'set'
1120
+ }, { type: 'compose', namespace: '*' })
1121
+ ]);
1122
+
1123
+ const evald = await node.eval(context);
1124
+ expect(evald.render(context)).toContain('color: orange');
1125
+ expect(evald.render(context)).not.toContain('color: red');
1126
+ });
1127
+
1128
+ it('set injects missing vars and nested rules can reference them during evaluation', async () => {
1129
+ const libraryPath = resolve(process.cwd(), 'library-set-inject-var.jess');
1130
+ context.sourceTrees.set(libraryPath, rules([
1131
+ ruleset({
1132
+ selector: sellist([sel([el('.box')])]),
1133
+ rules: rules([
1134
+ decl({ name: any('color'), value: ref('injectedColor', { type: 'variable' }) })
1135
+ ])
1136
+ })
1137
+ ]));
1138
+
1139
+ const node = rules([
1140
+ style({
1141
+ path: quoted(any('library-set-inject-var.jess')),
1142
+ withNode: rules([
1143
+ vardecl({ name: 'injectedColor', value: any('teal') })
1144
+ ]) as any,
1145
+ withType: 'set'
1146
+ }, { type: 'compose', namespace: '*' })
1147
+ ]);
1148
+
1149
+ const evald = await node.eval(context);
1150
+ expect(evald.render(context)).toContain('color: teal');
1151
+ });
1152
+
1153
+ it('set values become the baseline for later compose imports of the same file', async () => {
1154
+ const libraryPath = resolve(process.cwd(), 'library-set-baseline.jess');
1155
+ context.sourceTrees.set(libraryPath, rules([
1156
+ vardecl({ name: 'baseColor', value: any('red') }),
1157
+ ruleset({
1158
+ selector: sellist([sel([el('.box')])]),
1159
+ rules: rules([
1160
+ decl({ name: any('color'), value: ref('baseColor', { type: 'variable' }) })
1161
+ ])
1162
+ })
1163
+ ]));
1164
+
1165
+ const node = rules([
1166
+ style({
1167
+ path: quoted(any('library-set-baseline.jess')),
1168
+ withNode: rules([
1169
+ vardecl({ name: 'baseColor', value: any('blue') })
1170
+ ]) as any,
1171
+ withType: 'set'
1172
+ }, { type: 'compose', namespace: '*' }),
1173
+ style({
1174
+ path: quoted(any('library-set-baseline.jess'))
1175
+ }, {
1176
+ type: 'compose',
1177
+ namespace: '*',
1178
+ importOptions: { multiple: true }
1179
+ })
1180
+ ]);
1181
+
1182
+ const evald = await node.eval(context);
1183
+ const first = evald.at(0, context) as Rules;
1184
+ const second = evald.at(1, context) as Rules;
1185
+
1186
+ const firstBaseColor = getVarWithContext(context, first, 'baseColor');
1187
+ const secondBaseColor = getVarWithContext(context, second, 'baseColor');
1188
+
1189
+ expect((await (firstBaseColor as any).value.eval(context)).toTrimmedString({ context })).toBe('blue');
1190
+ expect((await (secondBaseColor as any).value.eval(context)).toTrimmedString({ context })).toBe('blue');
1191
+ expect(second.render(context)).toContain('color: blue');
1192
+ });
1193
+
1194
+ it('with can override a prior set baseline for the same file', async () => {
1195
+ const libraryPath = resolve(process.cwd(), 'library-set-with-override.jess');
1196
+ context.sourceTrees.set(libraryPath, rules([
1197
+ vardecl({ name: 'baseColor', value: any('red') }),
1198
+ ruleset({
1199
+ selector: sellist([sel([el('.box')])]),
1200
+ rules: rules([
1201
+ decl({ name: any('color'), value: ref('baseColor', { type: 'variable' }) })
1202
+ ])
1203
+ })
1204
+ ]));
1205
+
1206
+ const node = rules([
1207
+ style({
1208
+ path: quoted(any('library-set-with-override.jess')),
1209
+ withNode: rules([
1210
+ vardecl({ name: 'baseColor', value: any('blue') })
1211
+ ]) as any,
1212
+ withType: 'set'
1213
+ }, { type: 'compose', namespace: '*' }),
1214
+ style({
1215
+ path: quoted(any('library-set-with-override.jess')),
1216
+ withNode: rules([
1217
+ vardecl({ name: 'baseColor', value: any('green') })
1218
+ ]) as any,
1219
+ withType: 'with'
1220
+ }, {
1221
+ type: 'compose',
1222
+ namespace: '*',
1223
+ importOptions: { multiple: true }
1224
+ })
1225
+ ]);
1226
+
1227
+ const evald = await node.eval(context);
1228
+ const first = evald.at(0, context) as Rules;
1229
+ const second = evald.at(1, context) as Rules;
1230
+
1231
+ const firstBaseColor = getVarWithContext(context, first, 'baseColor');
1232
+ const secondBaseColor = getVarWithContext(context, second, 'baseColor');
1233
+
1234
+ expect((await (firstBaseColor as any).value.eval(context)).toTrimmedString({ context })).toBe('blue');
1235
+ expect((await (secondBaseColor as any).value.eval(context)).toTrimmedString({ context })).toBe('green');
1236
+ expect(first.render(context)).toContain('color: blue');
1237
+ expect(second.render(context)).toContain('color: green');
1238
+ });
1239
+ });
1240
+
1241
+ describe('multiple imports', () => {
1242
+ it('import type can be imported multiple times', async () => {
1243
+ context.sourceTrees.set('imported.jess', rules([
1244
+ ruleset({
1245
+ selector: sellist([sel([el('.imported')])]),
1246
+ rules: rules([
1247
+ decl({ name: any('color'), value: any('red') })
1248
+ ])
1249
+ })
1250
+ ]));
1251
+
1252
+ const node = rules([
1253
+ style({
1254
+ path: quoted(any('imported.jess'))
1255
+ }, {
1256
+ type: 'import'
1257
+ }),
1258
+ style({
1259
+ path: quoted(any('imported.jess'))
1260
+ }, {
1261
+ type: 'import',
1262
+ importOptions: { multiple: true }
1263
+ })
1264
+ ]);
1265
+
1266
+ const evald = await node.eval(context);
1267
+ // Both imports should be present
1268
+ expect(evald.value.length).toBe(2);
1269
+ });
1270
+ });
1271
+
1272
+ describe('less import fixture regressions', () => {
1273
+ it('import-inline: inline import with media postlude inlines source content', async () => {
1274
+ const inlinePath = resolve(process.cwd(), 'inline-source.css');
1275
+ const inlineContext = new Context({}, [{
1276
+ name: 'inline-plugin',
1277
+ supportedExtensions: ['.css'],
1278
+ resolve(filePath: string | string[]) {
1279
+ const paths = Array.isArray(filePath) ? filePath : [filePath];
1280
+ return paths.map(p => (p.endsWith('.css') ? p : `${p}.css`));
1281
+ },
1282
+ locate(pathCandidates: string[], currentDir: string) {
1283
+ for (const candidate of pathCandidates) {
1284
+ const abs = candidate.startsWith('/') ? candidate : resolve(currentDir, candidate);
1285
+ if (abs === inlinePath) {
1286
+ return abs;
1287
+ }
1288
+ }
1289
+ return null;
1290
+ },
1291
+ async getSource() {
1292
+ return '#css { color: yellow; }\n';
1293
+ }
1294
+ }]);
1295
+ inlineContext.treeContext = {
1296
+ file: { name: 'entry.less', path: process.cwd(), fullPath: resolve(process.cwd(), 'entry.less') }
1297
+ } as any;
1298
+
1299
+ const node = rules([
1300
+ style({ path: quoted(any('inline-source.css')) }, {
1301
+ type: 'import',
1302
+ importOptions: {
1303
+ inline: true,
1304
+ postlude: any('(min-width: 600px)')
1305
+ }
1306
+ })
1307
+ ]);
1308
+ const evald = await node.eval(inlineContext);
1309
+ expect(evald.toString({ context: inlineContext })).toContain('@media (min-width: 600px)');
1310
+ expect(evald.toString({ context: inlineContext })).toContain('#css { color: yellow; }');
1311
+ });
1312
+
1313
+ it('import-inline: supports/layer postludes wrap inline source in order', async () => {
1314
+ const inlinePath = resolve(process.cwd(), 'inline-postlude.css');
1315
+ const inlineContext = new Context({}, [{
1316
+ name: 'inline-plugin',
1317
+ supportedExtensions: ['.css'],
1318
+ resolve(filePath: string | string[]) {
1319
+ const paths = Array.isArray(filePath) ? filePath : [filePath];
1320
+ return paths.map(p => (p.endsWith('.css') ? p : `${p}.css`));
1321
+ },
1322
+ locate(pathCandidates: string[], currentDir: string) {
1323
+ for (const candidate of pathCandidates) {
1324
+ const abs = candidate.startsWith('/') ? candidate : resolve(currentDir, candidate);
1325
+ if (abs === inlinePath) {
1326
+ return abs;
1327
+ }
1328
+ }
1329
+ return null;
1330
+ },
1331
+ async getSource() {
1332
+ return '#css { color: yellow; }\n';
1333
+ }
1334
+ }]);
1335
+ inlineContext.treeContext = {
1336
+ file: { name: 'entry.less', path: process.cwd(), fullPath: resolve(process.cwd(), 'entry.less') }
1337
+ } as any;
1338
+
1339
+ const postlude = list([
1340
+ call({ name: 'layer', args: list([any('theme')]) }),
1341
+ call({ name: 'supports', args: list([any('(display: grid)')]) }),
1342
+ any('screen and (min-width: 600px)')
1343
+ ], { sep: ' ' as any });
1344
+
1345
+ const node = rules([
1346
+ style({ path: quoted(any('inline-postlude.css')) }, {
1347
+ type: 'import',
1348
+ importOptions: {
1349
+ inline: true,
1350
+ postlude
1351
+ }
1352
+ })
1353
+ ]);
1354
+
1355
+ const css = (await node.eval(inlineContext)).toString({ context: inlineContext });
1356
+ expect(css).toContain('@layer theme');
1357
+ expect(css).toContain('@supports (display: grid)');
1358
+ expect(css).toContain('@media screen and (min-width: 600px)');
1359
+ expect(css).toContain('#css { color: yellow; }');
1360
+ });
1361
+
1362
+ it('evaluated postlude wrapping does not corrupt canonical postlude parent pointers', async () => {
1363
+ const importPath = resolve(process.cwd(), 'postlude-parent.jess');
1364
+ context.sourceTrees.set(importPath, rules([
1365
+ ruleset({
1366
+ selector: sellist([sel([el('.postlude-parent')])]),
1367
+ rules: rules([
1368
+ decl({ name: any('color'), value: any('red') })
1369
+ ])
1370
+ })
1371
+ ]));
1372
+
1373
+ const layerArg = any('theme');
1374
+ const layerArgs = list([layerArg]);
1375
+ const supportsArgs = list([any('(display: grid)'), any('and (hover: hover)')]);
1376
+ const mediaQuery = any('screen and (min-width: 600px)');
1377
+ const postlude = list([
1378
+ call({ name: 'layer', args: layerArgs }),
1379
+ call({ name: 'supports', args: supportsArgs }),
1380
+ mediaQuery
1381
+ ], { sep: ' ' as any });
1382
+
1383
+ expect(layerArg.parent).toBe(layerArgs);
1384
+ expect(supportsArgs.parent).toBe(postlude.get('value')[1]);
1385
+ expect(mediaQuery.parent).toBe(postlude);
1386
+
1387
+ await rules([
1388
+ style({ path: quoted(any('postlude-parent.jess')) }, {
1389
+ type: 'import',
1390
+ importOptions: { postlude }
1391
+ })
1392
+ ]).eval(context);
1393
+
1394
+ expect(layerArg.parent).toBe(layerArgs);
1395
+ expect(supportsArgs.parent).toBe(postlude.get('value')[1]);
1396
+ expect(mediaQuery.parent).toBe(postlude);
1397
+ });
1398
+
1399
+ it('import-interpolation: resolves vars from later imports on retry', async () => {
1400
+ const interpolationImportPath = resolve(process.cwd(), 'import/import-interpolation.jess');
1401
+ const interpolationVarsPath = resolve(process.cwd(), 'import/interpolation-vars.jess');
1402
+
1403
+ context.sourceTrees.set(interpolationImportPath, rules([
1404
+ vardecl({ name: 'interpolationResolved', value: any('ok') })
1405
+ ]));
1406
+ context.sourceTrees.set(interpolationVarsPath, rules([
1407
+ vardecl({ name: 'segmentA', value: any('in') }),
1408
+ vardecl({ name: 'segmentB', value: any('terpolation') })
1409
+ ]));
1410
+
1411
+ const interpolatedPath = new Interpolated({
1412
+ source: `import/import-${INTERPOLATION_PLACEHOLDER}${INTERPOLATION_PLACEHOLDER}.jess`,
1413
+ replacements: [ref('segmentA', { type: 'variable' }), ref('segmentB', { type: 'variable' })]
1414
+ }, { role: 'ident' });
1415
+
1416
+ const node = rules([
1417
+ style({ path: quoted(interpolatedPath) }, { type: 'import', importOptions: { optional: false } }),
1418
+ style({ path: quoted(any('import/interpolation-vars.jess')) }, { type: 'import' })
1419
+ ]);
1420
+
1421
+ const evald = await node.eval(context);
1422
+ const resolvedFromInterpolatedImport = getVarWithContext(context, evald, 'interpolationResolved');
1423
+ expect(resolvedFromInterpolatedImport).toBeDefined();
1424
+ expect(resolvedFromInterpolatedImport.toTrimmedString({ context })).toBe('$interpolationResolved: ok');
1425
+ });
1426
+
1427
+ it('import path resolution uses a state-evaluated Url path value', async () => {
1428
+ const resolvedImportPath = resolve(process.cwd(), 'import/url-state-path.jess');
1429
+ context.sourceTrees.set(resolvedImportPath, rules([
1430
+ vardecl({ name: 'resolvedFromUrl', value: any('ok') })
1431
+ ]));
1432
+
1433
+ const importNode = style({
1434
+ path: url(quoted('wrong-path.jess'))
1435
+ }, { type: 'import' });
1436
+ const renderKey = context.nextRenderKey();
1437
+ addEdge(importNode, 'path', renderKey, url(quoted('import/url-state-path.jess')));
1438
+ importNode.renderKey = renderKey;
1439
+ const node = rules([
1440
+ importNode
1441
+ ]);
1442
+ const preEvald = await (node as any).preEval(context);
1443
+ const preEvaldImport = preEvald.at(0, context);
1444
+
1445
+ const evald = await node.eval(context);
1446
+ const resolvedFromUrl = getVarWithContext(context, evald, 'resolvedFromUrl');
1447
+
1448
+ expect(resolvedFromUrl).toBeDefined();
1449
+ expect(resolvedFromUrl.toTrimmedString({ context })).toBe('$resolvedFromUrl: ok');
1450
+ });
1451
+
1452
+ it('import-module: context can resolve bare module-like specifiers', async () => {
1453
+ const moduleContext = new Context();
1454
+ moduleContext.treeContext = {
1455
+ file: { name: 'entry.less', path: process.cwd(), fullPath: resolve(process.cwd(), 'entry.less') }
1456
+ } as any;
1457
+ const result = await (moduleContext as any)._getPath('lodash-es');
1458
+ expect(typeof result.resolvedPath).toBe('string');
1459
+ expect(result.resolvedPath.length).toBeGreaterThan(0);
1460
+ });
1461
+
1462
+ it('import-once: default once semantics de-dupe repeated imports', async () => {
1463
+ const oncePath = resolve(process.cwd(), 'once.jess');
1464
+ context.sourceTrees.set(oncePath, rules([
1465
+ ruleset({
1466
+ selector: sellist([sel([el('.once')])]),
1467
+ rules: rules([decl({ name: any('color'), value: any('red') })])
1468
+ })
1469
+ ]));
1470
+
1471
+ const node = rules([
1472
+ style({ path: quoted(any('once.jess')) }, { type: 'import' }),
1473
+ style({ path: quoted(any('once.jess')) }, { type: 'import' })
1474
+ ]);
1475
+ const evald = await node.eval(context);
1476
+ expect(evald.render(context).split('.once').length - 1).toBe(1);
1477
+ });
1478
+
1479
+ it('second same-file import defaults to reference mode without multiple', async () => {
1480
+ const libraryPath = resolve(process.cwd(), 'import-reference-default.jess');
1481
+ context.sourceTrees.set(libraryPath, rules([
1482
+ ruleset({
1483
+ selector: sellist([sel([el('.once-ref')])]),
1484
+ rules: rules([decl({ name: any('color'), value: any('red') })])
1485
+ })
1486
+ ]));
1487
+
1488
+ const node = rules([
1489
+ style({ path: quoted(any('import-reference-default.jess')) }, { type: 'import' }),
1490
+ style({ path: quoted(any('import-reference-default.jess')) }, { type: 'import' })
1491
+ ]);
1492
+
1493
+ const evald = await node.eval(context);
1494
+ const first = evald.at(0, context) as Rules;
1495
+ const second = evald.at(1, context) as Rules;
1496
+
1497
+ expect(first.options.referenceMode).not.toBe(true);
1498
+ expect(second.options.referenceMode).toBe(true);
1499
+ expect(evald.render(context).split('.once-ref').length - 1).toBe(1);
1500
+ });
1501
+
1502
+ it('import-reference-issues: reference imports are optional visibility', async () => {
1503
+ const referencedPath = resolve(process.cwd(), 'reference-issues.jess');
1504
+ context.sourceTrees.set(referencedPath, rules([
1505
+ ruleset({
1506
+ selector: sellist([sel([el('.hidden')])]),
1507
+ rules: rules([decl({ name: any('color'), value: any('red') })])
1508
+ })
1509
+ ]));
1510
+ const node = rules([
1511
+ style({ path: quoted(any('reference-issues.jess')) }, { type: 'import', importOptions: { reference: true } })
1512
+ ]);
1513
+ const evald = await node.eval(context);
1514
+ const imported = evald.at(0, context) as Rules;
1515
+ expect(imported.options.rulesVisibility?.Ruleset).toBe('optional');
1516
+ });
1517
+
1518
+ it('import-reference: reference imports remain discoverable for lookups', async () => {
1519
+ const referencedPath = resolve(process.cwd(), 'reference.jess');
1520
+ context.sourceTrees.set(referencedPath, rules([
1521
+ vardecl({ name: 'fromRef', value: any('42') })
1522
+ ]));
1523
+ const node = rules([
1524
+ style({ path: quoted(any('reference.jess')) }, { type: 'import', importOptions: { reference: true } }),
1525
+ decl({ name: any('value'), value: ref('fromRef', { type: 'variable' }) })
1526
+ ]);
1527
+ const evald = await node.eval(context);
1528
+ const declaration = evald.at(1, context) as any;
1529
+ const evaldCtx = asRulesContext(context, evald);
1530
+ const resolved = await declaration.eval(evaldCtx);
1531
+ expect(resolved.toTrimmedString({ context: evaldCtx })).toBe('value: 42');
1532
+ });
1533
+
1534
+ it('reference import call output rebinds nested rulesets to the caller instead of the definition', async () => {
1535
+ const referencedPath = resolve(process.cwd(), 'reference-call-shape.jess');
1536
+ context.sourceTrees.set(referencedPath, rules([
1537
+ ruleset({
1538
+ selector: sellist([sel([el('.z')])]),
1539
+ rules: rules([
1540
+ decl({ name: any('color'), value: any('red') }),
1541
+ ruleset({
1542
+ selector: sellist([sel([el('.c')])]),
1543
+ rules: rules([decl({ name: any('color'), value: any('green') })])
1544
+ })
1545
+ ])
1546
+ }),
1547
+ ruleset({
1548
+ selector: sellist([sel([el('.only-with-visible')]), sel([el('.z')])]),
1549
+ rules: rules([
1550
+ decl({ name: any('color'), value: any('green') }),
1551
+ ruleset({
1552
+ selector: sellist([compound([amp(), pseudo({ name: ':hover' })])]),
1553
+ rules: rules([decl({ name: any('color'), value: any('green') })])
1554
+ }),
1555
+ ruleset({
1556
+ selector: sellist([sel([amp()])]),
1557
+ rules: rules([decl({ name: any('color'), value: any('green') })])
1558
+ }),
1559
+ ruleset({
1560
+ selector: sellist([sel([amp(), co('+'), amp()])]),
1561
+ rules: rules([
1562
+ decl({ name: any('color'), value: any('green') }),
1563
+ ruleset({
1564
+ selector: sellist([sel([el('.sub')])]),
1565
+ rules: rules([decl({ name: any('color'), value: any('green') })])
1566
+ })
1567
+ ])
1568
+ })
1569
+ ])
1570
+ }),
1571
+ ruleset({
1572
+ selector: sellist([sel([el('.zz')])]),
1573
+ rules: rules([
1574
+ ruleset({
1575
+ selector: sellist([sel([el('.y')])]),
1576
+ rules: rules([
1577
+ decl({ name: any('pulled-in'), value: any('yes') })
1578
+ ])
1579
+ })
1580
+ ])
1581
+ })
1582
+ ]));
1583
+
1584
+ const node = rules([
1585
+ style({ path: quoted(any('reference-call-shape.jess')) }, {
1586
+ type: 'import',
1587
+ importOptions: { reference: true }
1588
+ }),
1589
+ ruleset({
1590
+ selector: sellist([sel([el('.b')])]),
1591
+ rules: rules([
1592
+ call({ name: ref({ key: '.z' }, { type: 'mixin-ruleset' }) })
1593
+ ])
1594
+ }),
1595
+ call({ name: ref({ key: '.zz' }, { type: 'mixin-ruleset' }) })
1596
+ ]);
1597
+
1598
+ context.opts.collapseNesting = true;
1599
+
1600
+ const evald = await node.eval(context);
1601
+ const css = evald.toString({ context });
1602
+
1603
+ expect(css).toBeString(`
1604
+ .b {
1605
+ color: red;
1606
+ }
1607
+ .b .c {
1608
+ color: green;
1609
+ }
1610
+ .b {
1611
+ color: green;
1612
+ }
1613
+ .b:hover {
1614
+ color: green;
1615
+ }
1616
+ .b {
1617
+ color: green;
1618
+ }
1619
+ .b + .b {
1620
+ color: green;
1621
+ }
1622
+ .b + .b .sub {
1623
+ color: green;
1624
+ }
1625
+ .y {
1626
+ pulled-in: yes;
1627
+ }
1628
+ `);
1629
+ });
1630
+
1631
+ it('import-remote: mapped remote package paths can be resolved as module-like imports', async () => {
1632
+ const remoteContext = new Context({}, [{
1633
+ name: 'remote-map',
1634
+ supportedExtensions: ['.less'],
1635
+ resolve(filePath: string | string[], currentDir: string, searchPaths: string[]) {
1636
+ const paths = Array.isArray(filePath) ? filePath : [filePath];
1637
+ const mapped = paths.map((candidate) => {
1638
+ const m = candidate.match(/^https?:\/\/cdn\.jsdelivr\.net\/npm\/([^?#]+)(?:[?#].*)?$/i);
1639
+ return m?.[1] ?? candidate;
1640
+ });
1641
+ void currentDir;
1642
+ void searchPaths;
1643
+ return mapped;
1644
+ },
1645
+ locate() {
1646
+ return null;
1647
+ }
1648
+ }]);
1649
+ remoteContext.treeContext = {
1650
+ file: { name: 'entry.less', path: process.cwd(), fullPath: resolve(process.cwd(), 'entry.less') }
1651
+ } as any;
1652
+ const result = await (remoteContext as any)._getPath('https://cdn.jsdelivr.net/npm/lodash-es/lodash.js');
1653
+ expect(typeof result.resolvedPath).toBe('string');
1654
+ expect(result.resolvedPath.length).toBeGreaterThan(0);
1655
+ });
1656
+
1657
+ it('import.less: optional missing imports do not throw and produce empty rules', async () => {
1658
+ const node = rules([
1659
+ style({ path: quoted(any('missing-file.jess')) }, { type: 'import', importOptions: { optional: true } })
1660
+ ]);
1661
+ const evald = await node.eval(context);
1662
+ expect(evald.value.length).toBe(1);
1663
+ const imported = evald.at(0, context) as Rules;
1664
+ expect(imported.value.length).toBe(0);
1665
+ });
1666
+ });
1667
+
1668
+ describe('reference/multiple dedupe matrix', () => {
1669
+ const countSelector = (css: string, selector: string) => css.split(selector).length - 1;
1670
+
1671
+ it('import once:false renders repeated imports', async () => {
1672
+ context.sourceTrees.set('repeat.jess', rules([
1673
+ ruleset({
1674
+ selector: sellist([sel([el('.repeat')])]),
1675
+ rules: rules([decl({ name: any('color'), value: any('red') })])
1676
+ })
1677
+ ]));
1678
+ const node = rules([
1679
+ style({ path: quoted(any('repeat.jess')) }, { type: 'import', importOptions: { once: false } }),
1680
+ style({ path: quoted(any('repeat.jess')) }, { type: 'import', importOptions: { once: false } })
1681
+ ]);
1682
+ const evald = await node.eval(context);
1683
+ expect(countSelector(evald.render(context), '.repeat')).toBe(2);
1684
+ });
1685
+
1686
+ it('plain import followed by reference import renders once', async () => {
1687
+ context.sourceTrees.set('mix-order.jess', rules([
1688
+ ruleset({
1689
+ selector: sellist([sel([el('.mix-order')])]),
1690
+ rules: rules([decl({ name: any('color'), value: any('red') })])
1691
+ })
1692
+ ]));
1693
+ const node = rules([
1694
+ style({ path: quoted(any('mix-order.jess')) }, { type: 'import' }),
1695
+ style(
1696
+ { path: quoted(any('mix-order.jess')) },
1697
+ { type: 'import', importOptions: { reference: true } }
1698
+ )
1699
+ ]);
1700
+ const evald = await node.eval(context);
1701
+ expect(countSelector(evald.render(context), '.mix-order')).toBe(1);
1702
+ });
1703
+
1704
+ it('reference import followed by plain import stays suppressed without multiple', async () => {
1705
+ context.sourceTrees.set('mix-order-rev.jess', rules([
1706
+ ruleset({
1707
+ selector: sellist([sel([el('.mix-order-rev')])]),
1708
+ rules: rules([decl({ name: any('color'), value: any('red') })])
1709
+ })
1710
+ ]));
1711
+ const node = rules([
1712
+ style(
1713
+ { path: quoted(any('mix-order-rev.jess')) },
1714
+ { type: 'import', importOptions: { reference: true } }
1715
+ ),
1716
+ style({ path: quoted(any('mix-order-rev.jess')) }, { type: 'import' })
1717
+ ]);
1718
+ const evald = await node.eval(context);
1719
+ expect(countSelector(evald.render(context), '.mix-order-rev')).toBe(0);
1720
+ });
1721
+
1722
+ it('deduped imports do not corrupt canonical top-level ruleset child parents', async () => {
1723
+ const importedRuleset = ruleset({
1724
+ selector: sellist([sel([el('.dedupe-parent')])]),
1725
+ rules: rules([decl({ name: any('color'), value: any('red') })])
1726
+ });
1727
+ const sourceRules = rules([importedRuleset]);
1728
+ context.sourceTrees.set('dedupe-parents.jess', sourceRules);
1729
+
1730
+ expect(importedRuleset.get('rules').parent).toBe(importedRuleset);
1731
+ expect(importedRuleset.get('selector').parent).toBe(importedRuleset);
1732
+
1733
+ const node = rules([
1734
+ style({ path: quoted(any('dedupe-parents.jess')) }, { type: 'import' }),
1735
+ style({ path: quoted(any('dedupe-parents.jess')) }, { type: 'import' })
1736
+ ]);
1737
+
1738
+ await node.eval(context);
1739
+
1740
+ expect(importedRuleset.get('rules').parent).toBe(importedRuleset);
1741
+ expect(importedRuleset.get('selector').parent).toBe(importedRuleset);
1742
+ });
1743
+
1744
+ it('deduped imports materialize top-level declaration parents in returned trees without corrupting canonical source parents', async () => {
1745
+ const libraryPath = resolve(process.cwd(), 'dedupe-vars.jess');
1746
+ const importedVar = vardecl({ name: 'dedupeVar', value: any('red') });
1747
+ const sourceRules = rules([importedVar]);
1748
+ context.sourceTrees.set(libraryPath, sourceRules);
1749
+ const cachedEvaldRules = rules([
1750
+ vardecl({ name: 'dedupeVar', value: any('red') })
1751
+ ]);
1752
+ context.evaldTrees.set(libraryPath, cachedEvaldRules);
1753
+
1754
+ expect(importedVar.parent).toBe(sourceRules);
1755
+
1756
+ const node = rules([
1757
+ style({ path: quoted(any('dedupe-vars.jess')) }, { type: 'import' })
1758
+ ]);
1759
+
1760
+ const evald = await node.eval(context);
1761
+ const dedupedImport = evald.at(0, context) as Rules;
1762
+ const dedupedVar = dedupedImport.at(0, context) as VarDeclaration;
1763
+ const dedupedCtx = asRulesContext(context, dedupedImport);
1764
+
1765
+ expect(getParent(dedupedVar, dedupedCtx)).toBe(dedupedImport);
1766
+ expect(dedupedVar).not.toBe(importedVar);
1767
+ expect(dedupedVar.toTrimmedString({ context })).toBe('$dedupeVar: red');
1768
+ expect(importedVar.parent).toBe(sourceRules);
1769
+ });
1770
+
1771
+ it('shallow top-level child clones keep nested canonical children parented to the source ruleset', () => {
1772
+ const canonicalRuleset = ruleset({
1773
+ selector: sellist([sel([el('.dedupe-shallow')])]),
1774
+ rules: rules([decl({ name: any('color'), value: any('red') })])
1775
+ });
1776
+
1777
+ const canonicalSelector = canonicalRuleset.get('selector');
1778
+ const canonicalBody = canonicalRuleset.get('rules');
1779
+
1780
+ expect(canonicalSelector.parent).toBe(canonicalRuleset);
1781
+ expect(canonicalBody.parent).toBe(canonicalRuleset);
1782
+
1783
+ const shallowClone = canonicalRuleset.clone(false);
1784
+
1785
+ expect(shallowClone).not.toBe(canonicalRuleset);
1786
+ expect(shallowClone.get('selector')).toBe(canonicalSelector);
1787
+ expect(shallowClone.get('rules')).toBe(canonicalBody);
1788
+ expect(canonicalSelector.parent).toBe(canonicalRuleset);
1789
+ expect(canonicalBody.parent).toBe(canonicalRuleset);
1790
+ expect(shallowClone.toTrimmedString({ context })).toContain('color: red');
1791
+ });
1792
+
1793
+ it('returned-tree wrappers do not canonically reparent shared top-level children', () => {
1794
+ const canonicalRuleset = ruleset({
1795
+ selector: sellist([sel([el('.wrapper-blocker')])]),
1796
+ rules: rules([decl({ name: any('color'), value: any('red') })])
1797
+ });
1798
+ const evaluatedRules = rules([canonicalRuleset]);
1799
+
1800
+ expect(canonicalRuleset.parent).toBe(evaluatedRules);
1801
+
1802
+ const shallowWrapper = evaluatedRules.clone(false);
1803
+
1804
+ expect(shallowWrapper).not.toBe(evaluatedRules);
1805
+ expect(shallowWrapper.at(0, context)).toBe(canonicalRuleset);
1806
+ expect(canonicalRuleset.parent).toBe(evaluatedRules);
1807
+ expect(shallowWrapper.toTrimmedString({ context })).toContain('.wrapper-blocker');
1808
+ });
1809
+
1810
+ it.skip('deduped import wrappers keep cached evaluated parent pointers stable with detached wrapper finalization', async () => {
1811
+ const libraryPath = resolve(process.cwd(), 'dedupe-ruleset-identity.jess');
1812
+ const sourceRuleset = ruleset({
1813
+ selector: sellist([sel([el('.dedupe-identity')])]),
1814
+ rules: rules([
1815
+ decl({ name: any('color'), value: ref('libColor', { type: 'variable' }) })
1816
+ ])
1817
+ });
1818
+ context.sourceTrees.set(libraryPath, rules([sourceRuleset]));
1819
+
1820
+ const cachedRuleset = ruleset({
1821
+ selector: sellist([sel([el('.dedupe-identity')])]),
1822
+ rules: rules([
1823
+ decl({ name: any('color'), value: any('red') })
1824
+ ])
1825
+ });
1826
+ cachedRuleset.sourceNode = sourceRuleset;
1827
+ const cachedEvaldRules = rules([cachedRuleset]);
1828
+ cachedEvaldRules.sourceNode = context.sourceTrees.get(libraryPath)!;
1829
+ context.evaldTrees.set(libraryPath, cachedEvaldRules);
1830
+ const originalCachedParent = cachedRuleset.parent;
1831
+
1832
+ const node = rules([
1833
+ style({ path: quoted(any('dedupe-ruleset-identity.jess')) }, { type: 'import' })
1834
+ ]);
1835
+
1836
+ const evald = await node.eval(context);
1837
+ const dedupedImport = evald.at(0, context) as Rules;
1838
+ const dedupedRuleset = dedupedImport.at(0, context) as Ruleset;
1839
+
1840
+ expect(dedupedRuleset).not.toBe(cachedRuleset);
1841
+ expect(dedupedRuleset.parent).toBe(dedupedImport);
1842
+ expect(cachedRuleset.parent).toBe(originalCachedParent);
1843
+ expect(dedupedRuleset.toTrimmedString({ context })).toContain('color: red');
1844
+ });
1845
+
1846
+ it('compose multiple:true renders repeated modules', async () => {
1847
+ context.sourceTrees.set('compose-repeat.jess', rules([
1848
+ ruleset({
1849
+ selector: sellist([sel([el('.compose-repeat')])]),
1850
+ rules: rules([decl({ name: any('color'), value: any('red') })])
1851
+ })
1852
+ ]));
1853
+ const node = rules([
1854
+ style(
1855
+ { path: quoted(any('compose-repeat.jess')) },
1856
+ { type: 'compose', namespace: '*', importOptions: { multiple: true } }
1857
+ ),
1858
+ style(
1859
+ { path: quoted(any('compose-repeat.jess')) },
1860
+ { type: 'compose', namespace: '*', importOptions: { multiple: true } }
1861
+ )
1862
+ ]);
1863
+ const evald = await node.eval(context);
1864
+ expect(countSelector(evald.render(context), '.compose-repeat')).toBe(2);
1865
+ });
1866
+ });
1867
+
1868
+ describe('compose caching', () => {
1869
+ it('caches evaluated compose modules and de-dupes output unless multiple=true', async () => {
1870
+ context.sourceTrees.set('library-dedupe.jess', rules([
1871
+ ruleset({
1872
+ selector: sellist([sel([el('.imported')])]),
1873
+ rules: rules([
1874
+ decl({ name: any('color'), value: any('red') })
1875
+ ])
1876
+ })
1877
+ ]));
1878
+
1879
+ const node = rules([
1880
+ style({ path: quoted(any('library-dedupe.jess')) }, { type: 'compose', namespace: '*' }),
1881
+ style({ path: quoted(any('library-dedupe.jess')) }, { type: 'compose', namespace: '*' })
1882
+ ]);
1883
+
1884
+ const evald = await node.eval(context);
1885
+ const css = evald.render(context);
1886
+ // Should only render `.imported` once (second compose is reference mode by default).
1887
+ expect(css.split('.imported').length - 1).toBe(1);
1888
+ });
1889
+
1890
+ it('still allows per-import visibility differences via shallow clone', async () => {
1891
+ context.sourceTrees.set('library-vis.jess', rules([
1892
+ ruleset({
1893
+ selector: sellist([sel([el('.imported')])]),
1894
+ rules: rules([
1895
+ decl({ name: any('color'), value: any('red') })
1896
+ ])
1897
+ })
1898
+ ]));
1899
+
1900
+ const node = rules([
1901
+ style(
1902
+ { path: quoted(any('library-vis.jess')) },
1903
+ { type: 'compose', namespace: '*', importOptions: { mutable: true } }
1904
+ ),
1905
+ style(
1906
+ { path: quoted(any('library-vis.jess')) },
1907
+ { type: 'compose', namespace: '*', importOptions: { mutable: false, multiple: true } }
1908
+ )
1909
+ ]);
1910
+
1911
+ const evald = await node.eval(context);
1912
+ expect(evald.value.length).toBe(2);
1913
+ const first = evald.at(0, context) as Rules;
1914
+ const second = evald.at(1, context) as Rules;
1915
+ expect(first.options.rulesVisibility.Ruleset).toBe('public');
1916
+ expect(second.options.rulesVisibility.Ruleset).toBe('private');
1917
+ });
1918
+
1919
+ it.skip('compose cache wrappers still share top-level evaluated child identity across per-import visibility wrappers', async () => {
1920
+ const libraryPath = 'library-wrapper-contract.jess';
1921
+ context.sourceTrees.set(libraryPath, rules([
1922
+ ruleset({
1923
+ selector: sellist([sel([el('.imported')])]),
1924
+ rules: rules([
1925
+ decl({ name: any('color'), value: any('red') })
1926
+ ])
1927
+ })
1928
+ ]));
1929
+
1930
+ const node = rules([
1931
+ style(
1932
+ { path: quoted(any(libraryPath)) },
1933
+ { type: 'compose', namespace: '*', importOptions: { mutable: true } }
1934
+ ),
1935
+ style(
1936
+ { path: quoted(any(libraryPath)) },
1937
+ { type: 'compose', namespace: '*', importOptions: { mutable: false, multiple: true } }
1938
+ )
1939
+ ]);
1940
+
1941
+ const evald = await node.eval(context);
1942
+ const first = evald.at(0, context) as Rules;
1943
+ const second = evald.at(1, context) as Rules;
1944
+ const firstRuleset = first.at(0, context) as Ruleset;
1945
+ const secondRuleset = second.at(0, context) as Ruleset;
1946
+ const cachedEvaldRules = context.evaldTrees.get(libraryPath)!;
1947
+ const cachedRuleset = cachedEvaldRules.at(0, context) as Ruleset;
1948
+
1949
+ expect(first.options.rulesVisibility.Ruleset).toBe('public');
1950
+ expect(second.options.rulesVisibility.Ruleset).toBe('private');
1951
+ expect(cachedEvaldRules).not.toBe(first);
1952
+ expect(cachedEvaldRules).not.toBe(second);
1953
+ expect(first.options).not.toBe(second.options);
1954
+ expect(first.options).not.toBe(cachedEvaldRules.options);
1955
+ expect(second.options).not.toBe(cachedEvaldRules.options);
1956
+ expect(first.value).toBe(cachedEvaldRules.value);
1957
+ expect(second.value).toBe(cachedEvaldRules.value);
1958
+ expect(first.value[0]).toBe(cachedRuleset);
1959
+ expect(second.value[0]).toBe(cachedRuleset);
1960
+ expect(firstRuleset).toBe(cachedRuleset);
1961
+ expect(firstRuleset).toBe(secondRuleset);
1962
+ expect(firstRuleset.parent).not.toBe(first);
1963
+ expect(firstRuleset.parent).not.toBe(second);
1964
+ expect(firstRuleset.toTrimmedString({ context })).toContain('.imported');
1965
+ });
1966
+ });
1967
+ });