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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (637) hide show
  1. package/lib/index.cjs +20159 -0
  2. package/lib/index.d.cts +5993 -0
  3. package/lib/index.d.cts.map +1 -0
  4. package/lib/index.d.ts +5992 -21
  5. package/lib/index.d.ts.map +1 -1
  6. package/lib/index.js +19926 -22
  7. package/lib/index.js.map +1 -1
  8. package/package.json +15 -14
  9. package/src/__tests__/define-function-record.test.ts +58 -0
  10. package/src/__tests__/define-function-simple.test.ts +55 -0
  11. package/src/__tests__/define-function-split-sequence.test.ts +547 -0
  12. package/src/__tests__/define-function-type-parity.test.ts +9 -0
  13. package/src/__tests__/define-function.test.ts +763 -0
  14. package/src/__tests__/num-operations.test.ts +91 -0
  15. package/src/__tests__/safe-parse.test.ts +374 -0
  16. package/src/context.ts +896 -0
  17. package/src/conversions.ts +282 -0
  18. package/src/debug-log.ts +29 -0
  19. package/src/define-function.ts +1006 -0
  20. package/src/deprecation.ts +67 -0
  21. package/src/globals.d.ts +26 -0
  22. package/src/index.ts +31 -0
  23. package/src/jess-error.ts +773 -0
  24. package/src/logger/deprecation-processing.ts +109 -0
  25. package/src/logger.ts +31 -0
  26. package/src/plugin.ts +292 -0
  27. package/src/tree/LOOKUP_CHAINS.md +35 -0
  28. package/src/tree/README.md +18 -0
  29. package/src/tree/__tests__/__snapshots__/extend-eval-integration.test.ts.snap +1455 -0
  30. package/src/tree/__tests__/ampersand.test.ts +382 -0
  31. package/src/tree/__tests__/at-rule.test.ts +2047 -0
  32. package/src/tree/__tests__/basic-render.test.ts +212 -0
  33. package/src/tree/__tests__/block.test.ts +40 -0
  34. package/src/tree/__tests__/call.test.ts +346 -0
  35. package/src/tree/__tests__/color.test.ts +537 -0
  36. package/src/tree/__tests__/condition.test.ts +186 -0
  37. package/src/tree/__tests__/control.test.ts +564 -0
  38. package/src/tree/__tests__/declaration.test.ts +253 -0
  39. package/src/tree/__tests__/dependency-graph.test.ts +177 -0
  40. package/src/tree/__tests__/detached-rulesets.test.ts +213 -0
  41. package/src/tree/__tests__/dimension.test.ts +236 -0
  42. package/src/tree/__tests__/expression.test.ts +73 -0
  43. package/src/tree/__tests__/ext-node.test.ts +31 -0
  44. package/src/tree/__tests__/extend-eval-integration.test.ts +1033 -0
  45. package/src/tree/__tests__/extend-import-style.test.ts +929 -0
  46. package/src/tree/__tests__/extend-less-fixtures.test.ts +851 -0
  47. package/src/tree/__tests__/extend-list.test.ts +31 -0
  48. package/src/tree/__tests__/extend-roots.test.ts +1045 -0
  49. package/src/tree/__tests__/extend-rules.test.ts +740 -0
  50. package/src/tree/__tests__/func.test.ts +171 -0
  51. package/src/tree/__tests__/import-js.test.ts +33 -0
  52. package/src/tree/__tests__/import-style-test-helpers.ts +56 -0
  53. package/src/tree/__tests__/import-style.test.ts +1967 -0
  54. package/src/tree/__tests__/interpolated-reference.test.ts +44 -0
  55. package/src/tree/__tests__/interpolated.test.ts +41 -0
  56. package/src/tree/__tests__/list.test.ts +177 -0
  57. package/src/tree/__tests__/log.test.ts +83 -0
  58. package/src/tree/__tests__/mixin-recursion.test.ts +639 -0
  59. package/src/tree/__tests__/mixin.test.ts +2171 -0
  60. package/src/tree/__tests__/negative.test.ts +45 -0
  61. package/src/tree/__tests__/nesting-collapse.test.ts +519 -0
  62. package/src/tree/__tests__/node-flags-perf.test.ts +195 -0
  63. package/src/tree/__tests__/node-flags.test.ts +410 -0
  64. package/src/tree/__tests__/node-graph.test.ts +598 -0
  65. package/src/tree/__tests__/node-mutation.test.ts +182 -0
  66. package/src/tree/__tests__/operation.test.ts +18 -0
  67. package/src/tree/__tests__/paren.test.ts +90 -0
  68. package/src/tree/__tests__/preserve-mode-output.test.ts +50 -0
  69. package/src/tree/__tests__/quoted.test.ts +72 -0
  70. package/src/tree/__tests__/range.test.ts +59 -0
  71. package/src/tree/__tests__/reference.test.ts +743 -0
  72. package/src/tree/__tests__/rest.test.ts +29 -0
  73. package/src/tree/__tests__/rules-raw.test.ts +14 -0
  74. package/src/tree/__tests__/rules.test.ts +1271 -0
  75. package/src/tree/__tests__/ruleset.test.ts +597 -0
  76. package/src/tree/__tests__/selector-attr.test.ts +50 -0
  77. package/src/tree/__tests__/selector-basic.test.ts +44 -0
  78. package/src/tree/__tests__/selector-capture.test.ts +22 -0
  79. package/src/tree/__tests__/selector-complex.test.ts +120 -0
  80. package/src/tree/__tests__/selector-compound.test.ts +74 -0
  81. package/src/tree/__tests__/selector-interpolated.test.ts +50 -0
  82. package/src/tree/__tests__/selector-list.test.ts +59 -0
  83. package/src/tree/__tests__/selector-pseudo.test.ts +23 -0
  84. package/src/tree/__tests__/selector.test.ts +182 -0
  85. package/src/tree/__tests__/sequence.test.ts +226 -0
  86. package/src/tree/__tests__/serialize-types.test.ts +529 -0
  87. package/src/tree/__tests__/spaced.test.ts +8 -0
  88. package/src/tree/__tests__/url.test.ts +72 -0
  89. package/src/tree/__tests__/var-declaration.test.ts +90 -0
  90. package/src/tree/ampersand.ts +538 -0
  91. package/src/tree/any.ts +169 -0
  92. package/src/tree/at-rule.ts +760 -0
  93. package/src/tree/block.ts +72 -0
  94. package/src/tree/bool.ts +46 -0
  95. package/src/tree/call.ts +593 -0
  96. package/src/tree/collection.ts +52 -0
  97. package/src/tree/color.ts +629 -0
  98. package/src/tree/combinator.ts +30 -0
  99. package/src/tree/comment.ts +36 -0
  100. package/src/tree/condition.ts +194 -0
  101. package/src/tree/control.ts +452 -0
  102. package/src/tree/declaration-custom.ts +56 -0
  103. package/src/tree/declaration-var.ts +87 -0
  104. package/src/tree/declaration.ts +742 -0
  105. package/src/tree/default-guard.ts +35 -0
  106. package/src/tree/dimension.ts +392 -0
  107. package/src/tree/expression.ts +97 -0
  108. package/src/tree/extend-list.ts +51 -0
  109. package/src/tree/extend.ts +391 -0
  110. package/src/tree/function.ts +254 -0
  111. package/src/tree/import-js.ts +130 -0
  112. package/src/tree/import-style.ts +875 -0
  113. package/{lib/tree/index.js → src/tree/index.ts} +49 -22
  114. package/src/tree/interpolated.ts +346 -0
  115. package/src/tree/js-array.ts +21 -0
  116. package/src/tree/js-expr.ts +50 -0
  117. package/src/tree/js-function.ts +31 -0
  118. package/src/tree/js-object.ts +22 -0
  119. package/src/tree/list.ts +415 -0
  120. package/src/tree/log.ts +89 -0
  121. package/src/tree/mixin.ts +331 -0
  122. package/src/tree/negative.ts +58 -0
  123. package/src/tree/nil.ts +57 -0
  124. package/src/tree/node-base.ts +1716 -0
  125. package/src/tree/node-type.ts +122 -0
  126. package/src/tree/node.ts +118 -0
  127. package/src/tree/number.ts +54 -0
  128. package/src/tree/operation.ts +187 -0
  129. package/src/tree/paren.ts +132 -0
  130. package/src/tree/query-condition.ts +47 -0
  131. package/src/tree/quoted.ts +119 -0
  132. package/src/tree/range.ts +101 -0
  133. package/src/tree/reference.ts +1099 -0
  134. package/src/tree/rest.ts +55 -0
  135. package/src/tree/rules-raw.ts +52 -0
  136. package/src/tree/rules.ts +2896 -0
  137. package/src/tree/ruleset.ts +1217 -0
  138. package/src/tree/selector-attr.ts +172 -0
  139. package/src/tree/selector-basic.ts +75 -0
  140. package/src/tree/selector-capture.ts +85 -0
  141. package/src/tree/selector-complex.ts +189 -0
  142. package/src/tree/selector-compound.ts +205 -0
  143. package/src/tree/selector-interpolated.ts +95 -0
  144. package/src/tree/selector-list.ts +245 -0
  145. package/src/tree/selector-pseudo.ts +173 -0
  146. package/src/tree/selector-simple.ts +10 -0
  147. package/src/tree/selector.ts +152 -0
  148. package/src/tree/sequence.ts +463 -0
  149. package/src/tree/tree.ts +130 -0
  150. package/src/tree/url.ts +95 -0
  151. package/src/tree/util/EXTEND_ARCHITECTURE_ANALYSIS.md +215 -0
  152. package/src/tree/util/EXTEND_AUDIT.md +233 -0
  153. package/src/tree/util/EXTEND_BASELINE.md +64 -0
  154. package/src/tree/util/EXTEND_CALL_GRAPH_ANALYSIS.md +244 -0
  155. package/src/tree/util/EXTEND_DOCS.md +24 -0
  156. package/src/tree/util/EXTEND_FINAL_SUMMARY.md +95 -0
  157. package/src/tree/util/EXTEND_FUNCTION_AUDIT.md +1433 -0
  158. package/src/tree/util/EXTEND_OPTIMIZATION_PLAN.md +114 -0
  159. package/src/tree/util/EXTEND_REFACTORING_SUMMARY.md +152 -0
  160. package/src/tree/util/EXTEND_RULES.md +74 -0
  161. package/src/tree/util/EXTEND_UNUSED_FUNCTIONS.md +127 -0
  162. package/src/tree/util/EXTEND_UNUSED_FUNCTIONS_ANALYSIS.md +227 -0
  163. package/src/tree/util/NODE_COPY_REDUCTION_PLAN.md +12 -0
  164. package/src/tree/util/__tests__/EXTEND_TEST_INDEX.md +59 -0
  165. package/src/tree/util/__tests__/OPTIMIZATION-ANALYSIS.md +130 -0
  166. package/src/tree/util/__tests__/WALK_AND_CONSUME_DESIGN.md +138 -0
  167. package/src/tree/util/__tests__/_archive/2026-02-09__OPTIMIZATION-ANALYSIS.md +9 -0
  168. package/src/tree/util/__tests__/_archive/README.md +4 -0
  169. package/src/tree/util/__tests__/bitset.test.ts +142 -0
  170. package/src/tree/util/__tests__/debug-log.ts +50 -0
  171. package/src/tree/util/__tests__/extend-comment-handling.test.ts +187 -0
  172. package/src/tree/util/__tests__/extend-core-unit.test.ts +941 -0
  173. package/src/tree/util/__tests__/extend-pipeline-bench.test.ts +154 -0
  174. package/src/tree/util/__tests__/extend-pipeline-bench.ts +190 -0
  175. package/src/tree/util/__tests__/fast-reject.test.ts +377 -0
  176. package/src/tree/util/__tests__/is-node.test.ts +63 -0
  177. package/src/tree/util/__tests__/list-like.test.ts +63 -0
  178. package/src/tree/util/__tests__/outputwriter.test.ts +523 -0
  179. package/src/tree/util/__tests__/print.test.ts +183 -0
  180. package/src/tree/util/__tests__/process-extends.test.ts +226 -0
  181. package/src/tree/util/__tests__/process-leading-is.test.ts +205 -0
  182. package/src/tree/util/__tests__/recursion-helper.test.ts +184 -0
  183. package/src/tree/util/__tests__/selector-match-unit.test.ts +1427 -0
  184. package/src/tree/util/__tests__/sourcemap.test.ts +117 -0
  185. package/src/tree/util/ampersand-template.ts +9 -0
  186. package/src/tree/util/bitset.ts +194 -0
  187. package/src/tree/util/calculate.ts +11 -0
  188. package/src/tree/util/cast.ts +89 -0
  189. package/src/tree/util/cloning.ts +8 -0
  190. package/src/tree/util/collections.ts +299 -0
  191. package/src/tree/util/compare.ts +90 -0
  192. package/src/tree/util/cursor.ts +171 -0
  193. package/src/tree/util/extend-core.ts +2139 -0
  194. package/src/tree/util/extend-roots.ts +1108 -0
  195. package/src/tree/util/field-helpers.ts +354 -0
  196. package/src/tree/util/is-node.ts +43 -0
  197. package/src/tree/util/list-like.ts +93 -0
  198. package/src/tree/util/mixin-instance-primitives.ts +2020 -0
  199. package/src/tree/util/print.ts +303 -0
  200. package/src/tree/util/process-leading-is.ts +421 -0
  201. package/src/tree/util/recursion-helper.ts +54 -0
  202. package/src/tree/util/regex.ts +2 -0
  203. package/src/tree/util/registry-utils.ts +1953 -0
  204. package/src/tree/util/ruleset-trace.ts +17 -0
  205. package/src/tree/util/scoped-body-eval.ts +320 -0
  206. package/src/tree/util/selector-match-core.ts +2005 -0
  207. package/src/tree/util/selector-utils.ts +757 -0
  208. package/src/tree/util/serialize-helper.ts +535 -0
  209. package/src/tree/util/serialize-types.ts +318 -0
  210. package/src/tree/util/should-operate.ts +78 -0
  211. package/src/tree/util/sourcemap.ts +37 -0
  212. package/src/types/config.ts +247 -0
  213. package/src/types/index.ts +12 -0
  214. package/{lib/types/modes.d.ts → src/types/modes.ts} +2 -1
  215. package/src/types.d.ts +9 -0
  216. package/src/types.ts +68 -0
  217. package/src/use-webpack-resolver.ts +56 -0
  218. package/src/visitor/__tests__/visitor.test.ts +136 -0
  219. package/src/visitor/index.ts +263 -0
  220. package/{lib/visitor/less-visitor.js → src/visitor/less-visitor.ts} +3 -2
  221. package/lib/context.d.ts +0 -352
  222. package/lib/context.d.ts.map +0 -1
  223. package/lib/context.js +0 -636
  224. package/lib/context.js.map +0 -1
  225. package/lib/conversions.d.ts +0 -73
  226. package/lib/conversions.d.ts.map +0 -1
  227. package/lib/conversions.js +0 -253
  228. package/lib/conversions.js.map +0 -1
  229. package/lib/debug-log.d.ts +0 -2
  230. package/lib/debug-log.d.ts.map +0 -1
  231. package/lib/debug-log.js +0 -27
  232. package/lib/debug-log.js.map +0 -1
  233. package/lib/define-function.d.ts +0 -587
  234. package/lib/define-function.d.ts.map +0 -1
  235. package/lib/define-function.js +0 -726
  236. package/lib/define-function.js.map +0 -1
  237. package/lib/deprecation.d.ts +0 -34
  238. package/lib/deprecation.d.ts.map +0 -1
  239. package/lib/deprecation.js +0 -57
  240. package/lib/deprecation.js.map +0 -1
  241. package/lib/jess-error.d.ts +0 -343
  242. package/lib/jess-error.d.ts.map +0 -1
  243. package/lib/jess-error.js +0 -508
  244. package/lib/jess-error.js.map +0 -1
  245. package/lib/logger/deprecation-processing.d.ts +0 -41
  246. package/lib/logger/deprecation-processing.d.ts.map +0 -1
  247. package/lib/logger/deprecation-processing.js +0 -81
  248. package/lib/logger/deprecation-processing.js.map +0 -1
  249. package/lib/logger.d.ts +0 -10
  250. package/lib/logger.d.ts.map +0 -1
  251. package/lib/logger.js +0 -20
  252. package/lib/logger.js.map +0 -1
  253. package/lib/plugin.d.ts +0 -94
  254. package/lib/plugin.d.ts.map +0 -1
  255. package/lib/plugin.js +0 -174
  256. package/lib/plugin.js.map +0 -1
  257. package/lib/tree/ampersand.d.ts +0 -98
  258. package/lib/tree/ampersand.d.ts.map +0 -1
  259. package/lib/tree/ampersand.js +0 -319
  260. package/lib/tree/ampersand.js.map +0 -1
  261. package/lib/tree/any.d.ts +0 -58
  262. package/lib/tree/any.d.ts.map +0 -1
  263. package/lib/tree/any.js +0 -104
  264. package/lib/tree/any.js.map +0 -1
  265. package/lib/tree/at-rule.d.ts +0 -53
  266. package/lib/tree/at-rule.d.ts.map +0 -1
  267. package/lib/tree/at-rule.js +0 -503
  268. package/lib/tree/at-rule.js.map +0 -1
  269. package/lib/tree/block.d.ts +0 -22
  270. package/lib/tree/block.d.ts.map +0 -1
  271. package/lib/tree/block.js +0 -24
  272. package/lib/tree/block.js.map +0 -1
  273. package/lib/tree/bool.d.ts +0 -18
  274. package/lib/tree/bool.d.ts.map +0 -1
  275. package/lib/tree/bool.js +0 -28
  276. package/lib/tree/bool.js.map +0 -1
  277. package/lib/tree/call.d.ts +0 -66
  278. package/lib/tree/call.d.ts.map +0 -1
  279. package/lib/tree/call.js +0 -306
  280. package/lib/tree/call.js.map +0 -1
  281. package/lib/tree/collection.d.ts +0 -30
  282. package/lib/tree/collection.d.ts.map +0 -1
  283. package/lib/tree/collection.js +0 -37
  284. package/lib/tree/collection.js.map +0 -1
  285. package/lib/tree/color.d.ts +0 -101
  286. package/lib/tree/color.d.ts.map +0 -1
  287. package/lib/tree/color.js +0 -513
  288. package/lib/tree/color.js.map +0 -1
  289. package/lib/tree/combinator.d.ts +0 -13
  290. package/lib/tree/combinator.d.ts.map +0 -1
  291. package/lib/tree/combinator.js +0 -12
  292. package/lib/tree/combinator.js.map +0 -1
  293. package/lib/tree/comment.d.ts +0 -20
  294. package/lib/tree/comment.d.ts.map +0 -1
  295. package/lib/tree/comment.js +0 -19
  296. package/lib/tree/comment.js.map +0 -1
  297. package/lib/tree/condition.d.ts +0 -31
  298. package/lib/tree/condition.d.ts.map +0 -1
  299. package/lib/tree/condition.js +0 -103
  300. package/lib/tree/condition.js.map +0 -1
  301. package/lib/tree/control.d.ts +0 -104
  302. package/lib/tree/control.d.ts.map +0 -1
  303. package/lib/tree/control.js +0 -430
  304. package/lib/tree/control.js.map +0 -1
  305. package/lib/tree/declaration-custom.d.ts +0 -18
  306. package/lib/tree/declaration-custom.d.ts.map +0 -1
  307. package/lib/tree/declaration-custom.js +0 -24
  308. package/lib/tree/declaration-custom.js.map +0 -1
  309. package/lib/tree/declaration-var.d.ts +0 -35
  310. package/lib/tree/declaration-var.d.ts.map +0 -1
  311. package/lib/tree/declaration-var.js +0 -63
  312. package/lib/tree/declaration-var.js.map +0 -1
  313. package/lib/tree/declaration.d.ts +0 -78
  314. package/lib/tree/declaration.d.ts.map +0 -1
  315. package/lib/tree/declaration.js +0 -286
  316. package/lib/tree/declaration.js.map +0 -1
  317. package/lib/tree/default-guard.d.ts +0 -15
  318. package/lib/tree/default-guard.d.ts.map +0 -1
  319. package/lib/tree/default-guard.js +0 -19
  320. package/lib/tree/default-guard.js.map +0 -1
  321. package/lib/tree/dimension.d.ts +0 -34
  322. package/lib/tree/dimension.d.ts.map +0 -1
  323. package/lib/tree/dimension.js +0 -294
  324. package/lib/tree/dimension.js.map +0 -1
  325. package/lib/tree/expression.d.ts +0 -25
  326. package/lib/tree/expression.d.ts.map +0 -1
  327. package/lib/tree/expression.js +0 -32
  328. package/lib/tree/expression.js.map +0 -1
  329. package/lib/tree/extend-list.d.ts +0 -23
  330. package/lib/tree/extend-list.d.ts.map +0 -1
  331. package/lib/tree/extend-list.js +0 -23
  332. package/lib/tree/extend-list.js.map +0 -1
  333. package/lib/tree/extend.d.ts +0 -47
  334. package/lib/tree/extend.d.ts.map +0 -1
  335. package/lib/tree/extend.js +0 -296
  336. package/lib/tree/extend.js.map +0 -1
  337. package/lib/tree/function.d.ts +0 -48
  338. package/lib/tree/function.d.ts.map +0 -1
  339. package/lib/tree/function.js +0 -74
  340. package/lib/tree/function.js.map +0 -1
  341. package/lib/tree/import-js.d.ts +0 -35
  342. package/lib/tree/import-js.d.ts.map +0 -1
  343. package/lib/tree/import-js.js +0 -45
  344. package/lib/tree/import-js.js.map +0 -1
  345. package/lib/tree/import-style.d.ts +0 -156
  346. package/lib/tree/import-style.d.ts.map +0 -1
  347. package/lib/tree/import-style.js +0 -566
  348. package/lib/tree/import-style.js.map +0 -1
  349. package/lib/tree/index.d.ts +0 -71
  350. package/lib/tree/index.d.ts.map +0 -1
  351. package/lib/tree/index.js.map +0 -1
  352. package/lib/tree/interpolated-reference.d.ts +0 -24
  353. package/lib/tree/interpolated-reference.d.ts.map +0 -1
  354. package/lib/tree/interpolated-reference.js +0 -37
  355. package/lib/tree/interpolated-reference.js.map +0 -1
  356. package/lib/tree/interpolated.d.ts +0 -62
  357. package/lib/tree/interpolated.d.ts.map +0 -1
  358. package/lib/tree/interpolated.js +0 -204
  359. package/lib/tree/interpolated.js.map +0 -1
  360. package/lib/tree/js-array.d.ts +0 -10
  361. package/lib/tree/js-array.d.ts.map +0 -1
  362. package/lib/tree/js-array.js +0 -10
  363. package/lib/tree/js-array.js.map +0 -1
  364. package/lib/tree/js-expr.d.ts +0 -23
  365. package/lib/tree/js-expr.d.ts.map +0 -1
  366. package/lib/tree/js-expr.js +0 -28
  367. package/lib/tree/js-expr.js.map +0 -1
  368. package/lib/tree/js-function.d.ts +0 -20
  369. package/lib/tree/js-function.d.ts.map +0 -1
  370. package/lib/tree/js-function.js +0 -16
  371. package/lib/tree/js-function.js.map +0 -1
  372. package/lib/tree/js-object.d.ts +0 -10
  373. package/lib/tree/js-object.d.ts.map +0 -1
  374. package/lib/tree/js-object.js +0 -10
  375. package/lib/tree/js-object.js.map +0 -1
  376. package/lib/tree/list.d.ts +0 -38
  377. package/lib/tree/list.d.ts.map +0 -1
  378. package/lib/tree/list.js +0 -83
  379. package/lib/tree/list.js.map +0 -1
  380. package/lib/tree/log.d.ts +0 -29
  381. package/lib/tree/log.d.ts.map +0 -1
  382. package/lib/tree/log.js +0 -56
  383. package/lib/tree/log.js.map +0 -1
  384. package/lib/tree/mixin.d.ts +0 -87
  385. package/lib/tree/mixin.d.ts.map +0 -1
  386. package/lib/tree/mixin.js +0 -112
  387. package/lib/tree/mixin.js.map +0 -1
  388. package/lib/tree/negative.d.ts +0 -17
  389. package/lib/tree/negative.d.ts.map +0 -1
  390. package/lib/tree/negative.js +0 -22
  391. package/lib/tree/negative.js.map +0 -1
  392. package/lib/tree/nil.d.ts +0 -30
  393. package/lib/tree/nil.d.ts.map +0 -1
  394. package/lib/tree/nil.js +0 -35
  395. package/lib/tree/nil.js.map +0 -1
  396. package/lib/tree/node-base.d.ts +0 -361
  397. package/lib/tree/node-base.d.ts.map +0 -1
  398. package/lib/tree/node-base.js +0 -930
  399. package/lib/tree/node-base.js.map +0 -1
  400. package/lib/tree/node.d.ts +0 -10
  401. package/lib/tree/node.d.ts.map +0 -1
  402. package/lib/tree/node.js +0 -45
  403. package/lib/tree/node.js.map +0 -1
  404. package/lib/tree/number.d.ts +0 -21
  405. package/lib/tree/number.d.ts.map +0 -1
  406. package/lib/tree/number.js +0 -27
  407. package/lib/tree/number.js.map +0 -1
  408. package/lib/tree/operation.d.ts +0 -26
  409. package/lib/tree/operation.d.ts.map +0 -1
  410. package/lib/tree/operation.js +0 -103
  411. package/lib/tree/operation.js.map +0 -1
  412. package/lib/tree/paren.d.ts +0 -19
  413. package/lib/tree/paren.d.ts.map +0 -1
  414. package/lib/tree/paren.js +0 -92
  415. package/lib/tree/paren.js.map +0 -1
  416. package/lib/tree/query-condition.d.ts +0 -17
  417. package/lib/tree/query-condition.d.ts.map +0 -1
  418. package/lib/tree/query-condition.js +0 -39
  419. package/lib/tree/query-condition.js.map +0 -1
  420. package/lib/tree/quoted.d.ts +0 -28
  421. package/lib/tree/quoted.d.ts.map +0 -1
  422. package/lib/tree/quoted.js +0 -75
  423. package/lib/tree/quoted.js.map +0 -1
  424. package/lib/tree/range.d.ts +0 -33
  425. package/lib/tree/range.d.ts.map +0 -1
  426. package/lib/tree/range.js +0 -47
  427. package/lib/tree/range.js.map +0 -1
  428. package/lib/tree/reference.d.ts +0 -76
  429. package/lib/tree/reference.d.ts.map +0 -1
  430. package/lib/tree/reference.js +0 -521
  431. package/lib/tree/reference.js.map +0 -1
  432. package/lib/tree/rest.d.ts +0 -15
  433. package/lib/tree/rest.d.ts.map +0 -1
  434. package/lib/tree/rest.js +0 -32
  435. package/lib/tree/rest.js.map +0 -1
  436. package/lib/tree/rules-raw.d.ts +0 -17
  437. package/lib/tree/rules-raw.d.ts.map +0 -1
  438. package/lib/tree/rules-raw.js +0 -37
  439. package/lib/tree/rules-raw.js.map +0 -1
  440. package/lib/tree/rules.d.ts +0 -262
  441. package/lib/tree/rules.d.ts.map +0 -1
  442. package/lib/tree/rules.js +0 -2359
  443. package/lib/tree/rules.js.map +0 -1
  444. package/lib/tree/ruleset.d.ts +0 -92
  445. package/lib/tree/ruleset.d.ts.map +0 -1
  446. package/lib/tree/ruleset.js +0 -528
  447. package/lib/tree/ruleset.js.map +0 -1
  448. package/lib/tree/selector-attr.d.ts +0 -31
  449. package/lib/tree/selector-attr.d.ts.map +0 -1
  450. package/lib/tree/selector-attr.js +0 -99
  451. package/lib/tree/selector-attr.js.map +0 -1
  452. package/lib/tree/selector-basic.d.ts +0 -24
  453. package/lib/tree/selector-basic.d.ts.map +0 -1
  454. package/lib/tree/selector-basic.js +0 -38
  455. package/lib/tree/selector-basic.js.map +0 -1
  456. package/lib/tree/selector-capture.d.ts +0 -23
  457. package/lib/tree/selector-capture.d.ts.map +0 -1
  458. package/lib/tree/selector-capture.js +0 -34
  459. package/lib/tree/selector-capture.js.map +0 -1
  460. package/lib/tree/selector-complex.d.ts +0 -40
  461. package/lib/tree/selector-complex.d.ts.map +0 -1
  462. package/lib/tree/selector-complex.js +0 -143
  463. package/lib/tree/selector-complex.js.map +0 -1
  464. package/lib/tree/selector-compound.d.ts +0 -16
  465. package/lib/tree/selector-compound.d.ts.map +0 -1
  466. package/lib/tree/selector-compound.js +0 -114
  467. package/lib/tree/selector-compound.js.map +0 -1
  468. package/lib/tree/selector-interpolated.d.ts +0 -23
  469. package/lib/tree/selector-interpolated.d.ts.map +0 -1
  470. package/lib/tree/selector-interpolated.js +0 -27
  471. package/lib/tree/selector-interpolated.js.map +0 -1
  472. package/lib/tree/selector-list.d.ts +0 -17
  473. package/lib/tree/selector-list.d.ts.map +0 -1
  474. package/lib/tree/selector-list.js +0 -174
  475. package/lib/tree/selector-list.js.map +0 -1
  476. package/lib/tree/selector-pseudo.d.ts +0 -42
  477. package/lib/tree/selector-pseudo.d.ts.map +0 -1
  478. package/lib/tree/selector-pseudo.js +0 -204
  479. package/lib/tree/selector-pseudo.js.map +0 -1
  480. package/lib/tree/selector-simple.d.ts +0 -5
  481. package/lib/tree/selector-simple.d.ts.map +0 -1
  482. package/lib/tree/selector-simple.js +0 -6
  483. package/lib/tree/selector-simple.js.map +0 -1
  484. package/lib/tree/selector.d.ts +0 -43
  485. package/lib/tree/selector.d.ts.map +0 -1
  486. package/lib/tree/selector.js +0 -56
  487. package/lib/tree/selector.js.map +0 -1
  488. package/lib/tree/sequence.d.ts +0 -43
  489. package/lib/tree/sequence.d.ts.map +0 -1
  490. package/lib/tree/sequence.js +0 -151
  491. package/lib/tree/sequence.js.map +0 -1
  492. package/lib/tree/tree.d.ts +0 -87
  493. package/lib/tree/tree.d.ts.map +0 -1
  494. package/lib/tree/tree.js +0 -2
  495. package/lib/tree/tree.js.map +0 -1
  496. package/lib/tree/url.d.ts +0 -18
  497. package/lib/tree/url.d.ts.map +0 -1
  498. package/lib/tree/url.js +0 -35
  499. package/lib/tree/url.js.map +0 -1
  500. package/lib/tree/util/__tests__/debug-log.d.ts +0 -1
  501. package/lib/tree/util/__tests__/debug-log.d.ts.map +0 -1
  502. package/lib/tree/util/__tests__/debug-log.js +0 -36
  503. package/lib/tree/util/__tests__/debug-log.js.map +0 -1
  504. package/lib/tree/util/calculate.d.ts +0 -3
  505. package/lib/tree/util/calculate.d.ts.map +0 -1
  506. package/lib/tree/util/calculate.js +0 -10
  507. package/lib/tree/util/calculate.js.map +0 -1
  508. package/lib/tree/util/cast.d.ts +0 -10
  509. package/lib/tree/util/cast.d.ts.map +0 -1
  510. package/lib/tree/util/cast.js +0 -87
  511. package/lib/tree/util/cast.js.map +0 -1
  512. package/lib/tree/util/cloning.d.ts +0 -4
  513. package/lib/tree/util/cloning.d.ts.map +0 -1
  514. package/lib/tree/util/cloning.js +0 -8
  515. package/lib/tree/util/cloning.js.map +0 -1
  516. package/lib/tree/util/collections.d.ts +0 -57
  517. package/lib/tree/util/collections.d.ts.map +0 -1
  518. package/lib/tree/util/collections.js +0 -136
  519. package/lib/tree/util/collections.js.map +0 -1
  520. package/lib/tree/util/compare.d.ts +0 -11
  521. package/lib/tree/util/compare.d.ts.map +0 -1
  522. package/lib/tree/util/compare.js +0 -89
  523. package/lib/tree/util/compare.js.map +0 -1
  524. package/lib/tree/util/extend-helpers.d.ts +0 -2
  525. package/lib/tree/util/extend-helpers.d.ts.map +0 -1
  526. package/lib/tree/util/extend-helpers.js +0 -2
  527. package/lib/tree/util/extend-helpers.js.map +0 -1
  528. package/lib/tree/util/extend-roots.d.ts +0 -37
  529. package/lib/tree/util/extend-roots.d.ts.map +0 -1
  530. package/lib/tree/util/extend-roots.js +0 -700
  531. package/lib/tree/util/extend-roots.js.map +0 -1
  532. package/lib/tree/util/extend-roots.old.d.ts +0 -132
  533. package/lib/tree/util/extend-roots.old.d.ts.map +0 -1
  534. package/lib/tree/util/extend-roots.old.js +0 -2272
  535. package/lib/tree/util/extend-roots.old.js.map +0 -1
  536. package/lib/tree/util/extend-trace-debug.d.ts +0 -13
  537. package/lib/tree/util/extend-trace-debug.d.ts.map +0 -1
  538. package/lib/tree/util/extend-trace-debug.js +0 -34
  539. package/lib/tree/util/extend-trace-debug.js.map +0 -1
  540. package/lib/tree/util/extend-walk.d.ts +0 -53
  541. package/lib/tree/util/extend-walk.d.ts.map +0 -1
  542. package/lib/tree/util/extend-walk.js +0 -881
  543. package/lib/tree/util/extend-walk.js.map +0 -1
  544. package/lib/tree/util/extend.d.ts +0 -218
  545. package/lib/tree/util/extend.d.ts.map +0 -1
  546. package/lib/tree/util/extend.js +0 -3182
  547. package/lib/tree/util/extend.js.map +0 -1
  548. package/lib/tree/util/find-extendable-locations.d.ts +0 -2
  549. package/lib/tree/util/find-extendable-locations.d.ts.map +0 -1
  550. package/lib/tree/util/find-extendable-locations.js +0 -2
  551. package/lib/tree/util/find-extendable-locations.js.map +0 -1
  552. package/lib/tree/util/format.d.ts +0 -20
  553. package/lib/tree/util/format.d.ts.map +0 -1
  554. package/lib/tree/util/format.js +0 -67
  555. package/lib/tree/util/format.js.map +0 -1
  556. package/lib/tree/util/is-node.d.ts +0 -13
  557. package/lib/tree/util/is-node.d.ts.map +0 -1
  558. package/lib/tree/util/is-node.js +0 -43
  559. package/lib/tree/util/is-node.js.map +0 -1
  560. package/lib/tree/util/print.d.ts +0 -80
  561. package/lib/tree/util/print.d.ts.map +0 -1
  562. package/lib/tree/util/print.js +0 -205
  563. package/lib/tree/util/print.js.map +0 -1
  564. package/lib/tree/util/process-leading-is.d.ts +0 -25
  565. package/lib/tree/util/process-leading-is.d.ts.map +0 -1
  566. package/lib/tree/util/process-leading-is.js +0 -364
  567. package/lib/tree/util/process-leading-is.js.map +0 -1
  568. package/lib/tree/util/recursion-helper.d.ts +0 -15
  569. package/lib/tree/util/recursion-helper.d.ts.map +0 -1
  570. package/lib/tree/util/recursion-helper.js +0 -43
  571. package/lib/tree/util/recursion-helper.js.map +0 -1
  572. package/lib/tree/util/regex.d.ts +0 -4
  573. package/lib/tree/util/regex.d.ts.map +0 -1
  574. package/lib/tree/util/regex.js +0 -4
  575. package/lib/tree/util/regex.js.map +0 -1
  576. package/lib/tree/util/registry-utils.d.ts +0 -192
  577. package/lib/tree/util/registry-utils.d.ts.map +0 -1
  578. package/lib/tree/util/registry-utils.js +0 -1214
  579. package/lib/tree/util/registry-utils.js.map +0 -1
  580. package/lib/tree/util/ruleset-trace.d.ts +0 -4
  581. package/lib/tree/util/ruleset-trace.d.ts.map +0 -1
  582. package/lib/tree/util/ruleset-trace.js +0 -14
  583. package/lib/tree/util/ruleset-trace.js.map +0 -1
  584. package/lib/tree/util/selector-compare.d.ts +0 -2
  585. package/lib/tree/util/selector-compare.d.ts.map +0 -1
  586. package/lib/tree/util/selector-compare.js +0 -2
  587. package/lib/tree/util/selector-compare.js.map +0 -1
  588. package/lib/tree/util/selector-match-core.d.ts +0 -184
  589. package/lib/tree/util/selector-match-core.d.ts.map +0 -1
  590. package/lib/tree/util/selector-match-core.js +0 -1603
  591. package/lib/tree/util/selector-match-core.js.map +0 -1
  592. package/lib/tree/util/selector-utils.d.ts +0 -30
  593. package/lib/tree/util/selector-utils.d.ts.map +0 -1
  594. package/lib/tree/util/selector-utils.js +0 -100
  595. package/lib/tree/util/selector-utils.js.map +0 -1
  596. package/lib/tree/util/serialize-helper.d.ts +0 -13
  597. package/lib/tree/util/serialize-helper.d.ts.map +0 -1
  598. package/lib/tree/util/serialize-helper.js +0 -387
  599. package/lib/tree/util/serialize-helper.js.map +0 -1
  600. package/lib/tree/util/serialize-types.d.ts +0 -9
  601. package/lib/tree/util/serialize-types.d.ts.map +0 -1
  602. package/lib/tree/util/serialize-types.js +0 -216
  603. package/lib/tree/util/serialize-types.js.map +0 -1
  604. package/lib/tree/util/should-operate.d.ts +0 -23
  605. package/lib/tree/util/should-operate.d.ts.map +0 -1
  606. package/lib/tree/util/should-operate.js +0 -46
  607. package/lib/tree/util/should-operate.js.map +0 -1
  608. package/lib/tree/util/sourcemap.d.ts +0 -7
  609. package/lib/tree/util/sourcemap.d.ts.map +0 -1
  610. package/lib/tree/util/sourcemap.js +0 -25
  611. package/lib/tree/util/sourcemap.js.map +0 -1
  612. package/lib/types/config.d.ts +0 -205
  613. package/lib/types/config.d.ts.map +0 -1
  614. package/lib/types/config.js +0 -2
  615. package/lib/types/config.js.map +0 -1
  616. package/lib/types/index.d.ts +0 -15
  617. package/lib/types/index.d.ts.map +0 -1
  618. package/lib/types/index.js +0 -3
  619. package/lib/types/index.js.map +0 -1
  620. package/lib/types/modes.d.ts.map +0 -1
  621. package/lib/types/modes.js +0 -2
  622. package/lib/types/modes.js.map +0 -1
  623. package/lib/types.d.ts +0 -61
  624. package/lib/types.d.ts.map +0 -1
  625. package/lib/types.js +0 -2
  626. package/lib/types.js.map +0 -1
  627. package/lib/use-webpack-resolver.d.ts +0 -9
  628. package/lib/use-webpack-resolver.d.ts.map +0 -1
  629. package/lib/use-webpack-resolver.js +0 -41
  630. package/lib/use-webpack-resolver.js.map +0 -1
  631. package/lib/visitor/index.d.ts +0 -136
  632. package/lib/visitor/index.d.ts.map +0 -1
  633. package/lib/visitor/index.js +0 -135
  634. package/lib/visitor/index.js.map +0 -1
  635. package/lib/visitor/less-visitor.d.ts +0 -7
  636. package/lib/visitor/less-visitor.d.ts.map +0 -1
  637. package/lib/visitor/less-visitor.js.map +0 -1
@@ -0,0 +1,1953 @@
1
+ import type { Ruleset } from '../ruleset.js';
2
+ import type { Selector } from '../selector.js';
3
+ import type { Rules } from '../rules.js';
4
+ import { isNode } from './is-node.js';
5
+ import { N } from '../node-type.js';
6
+ import type { Mixin } from '../mixin.js';
7
+ import { Nil } from '../nil.js';
8
+ import { CALLER, CANONICAL, EVAL, Node } from '../node.js';
9
+ import { JsFunction } from '../js-function.js';
10
+ import type { Func } from '../function.js';
11
+ import type { Declaration } from '../declaration.js';
12
+ import type { Context } from '../../context.js';
13
+ import { atIndex } from './collections.js';
14
+ import { comparePosition } from './compare.js';
15
+ import { type BitSet, type BitSetLibrary, isSubsetOf } from './bitset.js';
16
+ import { getParent, setParent } from './field-helpers.js';
17
+ import { getCurrentParentNode } from './selector-utils.js';
18
+
19
+ const { isArray } = Array;
20
+
21
+ type SelectorKeySet = Set<string> | BitSet<string>;
22
+ type SelectorKeySource = SelectorKeySet | string[];
23
+
24
+ const NON_INDEXABLE_SELECTOR_KEYS = new Set(['', ' ', '>', '+', '~', '||']);
25
+
26
+ function getNodeValueArray(node: Node): unknown[] | undefined {
27
+ const value = Reflect.get(node, 'value');
28
+ return Array.isArray(value) ? value : undefined;
29
+ }
30
+
31
+ function getSelectorKeySetLibrary(node: unknown): BitSetLibrary<string> | undefined {
32
+ return typeof node === 'object' && node !== null
33
+ ? Reflect.get(node, 'keySetLibrary')
34
+ : undefined;
35
+ }
36
+
37
+ function setSelectorKeySetLibrary(node: unknown, library: BitSetLibrary<string>): void {
38
+ if (typeof node === 'object' && node !== null) {
39
+ Reflect.set(node, 'keySetLibrary', library);
40
+ }
41
+ }
42
+
43
+ function isSelectorLikeNode(value: unknown): value is Selector {
44
+ return value instanceof Node
45
+ && typeof value === 'object'
46
+ && 'isSelector' in value
47
+ && value.isSelector === true;
48
+ }
49
+
50
+ function isSelectorLikeOrNil(value: unknown): value is Selector | Nil {
51
+ return isSelectorLikeNode(value) || isNode(value, N.Nil);
52
+ }
53
+
54
+ function getRulesetSelector(ruleset: Ruleset, context?: Context): Selector | Nil | undefined {
55
+ const selector = ruleset.get('selector', context);
56
+ return isSelectorLikeOrNil(selector) ? selector : undefined;
57
+ }
58
+
59
+ function getMixinOwnSelector(mixin: Ruleset): Selector | Nil | undefined {
60
+ const ownSelector = mixin.options.ownSelector;
61
+ return isSelectorLikeOrNil(ownSelector) ? ownSelector : undefined;
62
+ }
63
+
64
+ function isIndexableSelectorKey(key: string): boolean {
65
+ return !key.startsWith(':') && !NON_INDEXABLE_SELECTOR_KEYS.has(key);
66
+ }
67
+
68
+ function tryGetSelectorKeySet(
69
+ selector: Selector | Nil | undefined,
70
+ visible: boolean = true
71
+ ): SelectorKeySet | undefined {
72
+ if (!selector || selector instanceof Nil) {
73
+ return undefined;
74
+ }
75
+ try {
76
+ return visible ? selector.visibleKeySet : selector.keySet;
77
+ } catch {
78
+ return undefined;
79
+ }
80
+ }
81
+
82
+ function getSelectorKeyValues(keySet: SelectorKeySource | undefined): string[] {
83
+ if (!keySet) {
84
+ return [];
85
+ }
86
+ if (isArray(keySet)) {
87
+ return keySet;
88
+ }
89
+ if (keySet instanceof Set) {
90
+ return [...keySet];
91
+ }
92
+ return keySet._library?.valuesOf(keySet) ?? [];
93
+ }
94
+
95
+ function getFallbackSelectorIndexKeys(selector: Node | undefined): string[] {
96
+ if (!selector) {
97
+ return [];
98
+ }
99
+ const value = Reflect.get(selector, 'value');
100
+ if (isArray(value)) {
101
+ const keys: string[] = [];
102
+ for (const child of value) {
103
+ if (!(child instanceof Node)) {
104
+ continue;
105
+ }
106
+ if (isNode(child, N.Combinator)) {
107
+ continue;
108
+ }
109
+ keys.push(...getFallbackSelectorIndexKeys(child));
110
+ }
111
+ return keys;
112
+ }
113
+ const key = String(selector.valueOf?.() ?? '');
114
+ return key && isIndexableSelectorKey(key) ? [key] : [];
115
+ }
116
+
117
+ function hasSelectorKey(keySet: SelectorKeySet | undefined, key: string): boolean {
118
+ if (!keySet) {
119
+ return false;
120
+ }
121
+ if (keySet instanceof Set) {
122
+ return keySet.has(key);
123
+ }
124
+ return keySet._library?.hasBit(keySet, key) ?? false;
125
+ }
126
+
127
+ function getIndexableSelectorKeys(keySet: SelectorKeySource | undefined): string[] {
128
+ return getSelectorKeyValues(keySet).filter(
129
+ key => typeof key === 'string' && !key.startsWith('*') && isIndexableSelectorKey(key)
130
+ );
131
+ }
132
+
133
+ function isNonImportStyleBoundary(rules: Rules | undefined): boolean {
134
+ return Boolean(
135
+ rules
136
+ && rules.sourceNode?.type === 'StyleImport'
137
+ && rules.sourceNode.options.type !== 'import'
138
+ );
139
+ }
140
+
141
+ export type DeclarationFindOptions = {
142
+ filter?: (n: Node) => boolean;
143
+ candidates?: Set<Node>;
144
+ optionalCandidates?: Set<Node>;
145
+ findAll?: boolean;
146
+ /** This gets set if any parent is set to readonly */
147
+ readonly?: boolean;
148
+ searchParents?: boolean;
149
+ start?: number;
150
+ local?: boolean;
151
+ };
152
+
153
+ export type FindOptions = DeclarationFindOptions & {
154
+ childFilterType?: 'Mixin' | 'Ruleset' | undefined;
155
+ context?: Context;
156
+ searchedRules?: Set<Rules>;
157
+ candidateContexts?: WeakMap<Node, Context>;
158
+ /**
159
+ * Whether this lookup has an explicit target (e.g., #ns[@foo]).
160
+ * When true, Rules with isMixinOutput=true will be searchable.
161
+ * When false or undefined, mixin output Rules will be excluded.
162
+ */
163
+ hasTarget?: boolean;
164
+ };
165
+
166
+ export type MixinRegistryEntry = {
167
+ value: Mixin | Ruleset;
168
+ match: string[];
169
+ };
170
+
171
+ // [DELETED: RegistryData, globalRegistryCache, peekRegistryData, ensureRegistryData,
172
+ // getRegistryDelta, ensureSessionRegistryIndex, getRegistryIndex, isRegistryIndexing,
173
+ // syncRegistryCache, registerCanonicalNode, registerSessionNode — see registry-state-plan.md]
174
+
175
+ function addRulesetToIndex(
176
+ index: Map<string, Set<Ruleset>>,
177
+ rules: Rules,
178
+ ruleset: Ruleset,
179
+ context?: Context
180
+ ): void {
181
+ if (!isSelectorLikeNode(ruleset.get('selector', context))) {
182
+ return;
183
+ }
184
+
185
+ const selector = ruleset.get('selector', context);
186
+ if (!isSelectorLikeNode(selector)) {
187
+ return;
188
+ }
189
+ const selectorBits = (rules.treeContext as { selectorBits?: BitSetLibrary<string>; opts?: { selectorBits?: BitSetLibrary<string> } } | undefined)?.selectorBits
190
+ ?? rules.treeContext?.opts?.selectorBits;
191
+ if (selectorBits && !getSelectorKeySetLibrary(selector)) {
192
+ setSelectorKeySetLibrary(selector, selectorBits);
193
+ const selectorValue = getNodeValueArray(selector);
194
+ if (selectorValue) {
195
+ for (const child of selectorValue) {
196
+ if (isSelectorLikeNode(child) && !getSelectorKeySetLibrary(child)) {
197
+ setSelectorKeySetLibrary(child, selectorBits);
198
+ }
199
+ }
200
+ }
201
+ }
202
+
203
+ let keySet: SelectorKeySource;
204
+ try {
205
+ keySet = selector.keySet;
206
+ } catch {
207
+ return;
208
+ }
209
+
210
+ for (const key of getSelectorKeyValues(keySet)) {
211
+ const existing = index.get(key);
212
+ if (existing) {
213
+ existing.add(ruleset);
214
+ } else {
215
+ index.set(key, new Set([ruleset]));
216
+ }
217
+ }
218
+ }
219
+
220
+ function addMixinToIndex(
221
+ index: Map<string, MixinRegistryEntry[]>,
222
+ rules: Rules,
223
+ mixin: Mixin | Ruleset,
224
+ context?: Context
225
+ ): void {
226
+ if (isNode(mixin, N.Ruleset)) {
227
+ const ruleset = mixin;
228
+ let selector = getRulesetSelector(ruleset, context);
229
+ if (isNode(selector, N.Nil)) {
230
+ return;
231
+ }
232
+ const ownSelector = getMixinOwnSelector(ruleset);
233
+ const callableSelector = ownSelector && !isNode(ownSelector, N.Nil) ? ownSelector : selector;
234
+ if (isNode(callableSelector, N.Ampersand)) {
235
+ return;
236
+ }
237
+ const sourceSelector = selector?.sourceNode;
238
+ const selectorVisibleKeySet = tryGetSelectorKeySet(selector);
239
+ const sourceVisibleKeySet = tryGetSelectorKeySet(sourceSelector);
240
+ const selectorToIndex = getIndexableSelectorKeys(selectorVisibleKeySet).length
241
+ ? selector
242
+ : (getIndexableSelectorKeys(sourceVisibleKeySet).length ? sourceSelector : selector);
243
+ let keySetToUse: SelectorKeySet | string[] | undefined;
244
+ if (isNode(selectorToIndex, N.SelectorList)) {
245
+ for (const sel of selectorToIndex.get('value')) {
246
+ const selKeySet = tryGetSelectorKeySet(sel);
247
+ if (selKeySet) {
248
+ indexMixinSelectorStart(index, mixin, selKeySet);
249
+ }
250
+ }
251
+ keySetToUse = undefined;
252
+ } else {
253
+ keySetToUse = tryGetSelectorKeySet(selectorToIndex);
254
+ }
255
+ if (
256
+ keySetToUse
257
+ && getIndexableSelectorKeys(keySetToUse).length > 0
258
+ && ownSelector
259
+ && !isNode(ownSelector, N.Nil)
260
+ ) {
261
+ const resolvedKeys = getIndexableSelectorKeys(keySetToUse);
262
+ const ownSelectorText = String(ownSelector?.valueOf?.() ?? '');
263
+ if (ownSelector && !getSelectorKeySetLibrary(ownSelector) && getSelectorKeySetLibrary(selectorToIndex)) {
264
+ setSelectorKeySetLibrary(ownSelector, getSelectorKeySetLibrary(selectorToIndex)!);
265
+ }
266
+ const ownKeys = getIndexableSelectorKeys(tryGetSelectorKeySet(ownSelector));
267
+ const parentRules = context ? getParent(mixin, context) : mixin.parent;
268
+ const parentRuleset = parentRules
269
+ ? (context ? getParent(parentRules, context) : parentRules.parent)
270
+ : undefined;
271
+ const parentSelector = isNode(parentRuleset, N.Ruleset)
272
+ ? parentRuleset.get('selector', context)
273
+ : undefined;
274
+ const parentKeys = (
275
+ parentSelector && !isNode(parentSelector, N.Nil)
276
+ ? getIndexableSelectorKeys(tryGetSelectorKeySet(parentSelector))
277
+ : []
278
+ );
279
+ if (
280
+ parentKeys.length > 0
281
+ && resolvedKeys.length > parentKeys.length
282
+ ) {
283
+ const parentKeySet = new Set(parentKeys);
284
+ const localKeys = resolvedKeys.filter(k => !parentKeySet.has(k));
285
+ if (localKeys.length > 0 && localKeys.length < resolvedKeys.length) {
286
+ keySetToUse = localKeys;
287
+ }
288
+ } else if (ownKeys.length > 1 && ownSelectorText.trimStart().startsWith('&')) {
289
+ keySetToUse = ownKeys.slice(1);
290
+ } else if (
291
+ parentKeys.length === 0
292
+ && ownKeys.length > 0
293
+ && resolvedKeys.length > ownKeys.length
294
+ ) {
295
+ keySetToUse = ownKeys;
296
+ }
297
+ }
298
+ if (
299
+ keySetToUse !== undefined
300
+ && getIndexableSelectorKeys(keySetToUse).length === 0
301
+ && ownSelector
302
+ && !isNode(ownSelector, N.Nil)
303
+ ) {
304
+ const ownKeySet = tryGetSelectorKeySet(ownSelector);
305
+ if (ownKeySet && getIndexableSelectorKeys(ownKeySet).length) {
306
+ const ownKeys = getIndexableSelectorKeys(ownKeySet);
307
+ const selectorText = String(selectorToIndex.valueOf?.() ?? '');
308
+ keySetToUse = selectorText.startsWith('&') && ownKeys.length > 1
309
+ ? new Set(ownKeys.slice(1))
310
+ : ownKeySet;
311
+ }
312
+ }
313
+ if (keySetToUse !== undefined) {
314
+ indexMixinSelectorStart(index, mixin, keySetToUse);
315
+ } else {
316
+ // Fallback: test-created compound selectors may not have a keySet yet.
317
+ // Derive indexable selector segments directly from the selector tree so
318
+ // namespace/compound lookups can still use startKey + remainder matching.
319
+ const fallbackKeys = getFallbackSelectorIndexKeys(callableSelector);
320
+ if (fallbackKeys.length > 0) {
321
+ indexMixinSelectorStart(index, mixin, fallbackKeys);
322
+ } else {
323
+ const selectorStr = String(callableSelector.valueOf?.() ?? '');
324
+ if (selectorStr) {
325
+ indexMixinSelectorStart(index, mixin, [selectorStr]);
326
+ }
327
+ }
328
+ }
329
+ return;
330
+ }
331
+
332
+ const keys = isNode(mixin, N.Mixin) && context ? mixin.getKeySet(context) : mixin.keySet;
333
+ indexMixinSelectorStart(index, mixin, keys);
334
+ }
335
+
336
+ function addDeclarationToIndex(
337
+ index: Map<string, Set<Declaration>>,
338
+ item: Declaration
339
+ ): void {
340
+ const key = item.get('name').valueOf();
341
+ const set = index.get(key);
342
+ if (set) {
343
+ set.add(item);
344
+ } else {
345
+ index.set(key, new Set([item]));
346
+ }
347
+ }
348
+
349
+ function indexMixinSelectorStart(
350
+ index: Map<string, MixinRegistryEntry[]>,
351
+ mixin: Mixin | Ruleset,
352
+ keys: SelectorKeySource
353
+ ): void {
354
+ let candidateKeys = getIndexableSelectorKeys(keys);
355
+ for (let i = 0; i < candidateKeys.length; i++) {
356
+ const startKey = candidateKeys[i]!;
357
+ const rest = candidateKeys.filter(k => k !== startKey);
358
+ const existing = index.get(startKey);
359
+ if (existing) {
360
+ existing.push({ value: mixin, match: rest });
361
+ } else {
362
+ index.set(startKey, [{ value: mixin, match: rest }]);
363
+ }
364
+ }
365
+ }
366
+
367
+ export abstract class Registry<
368
+ Type extends Node,
369
+ IndexType extends Type | Set<Type> | Array<{
370
+ value: Type;
371
+ [key: string]: any;
372
+ }> = Set<Type>
373
+ > {
374
+ abstract index: Map<string, IndexType>;
375
+ protected pendingItems = new Set<Type>();
376
+
377
+ constructor(
378
+ public rules: Rules,
379
+ protected context?: Context
380
+ ) {}
381
+
382
+ add(item: Type): void {
383
+ this.pendingItems.add(item);
384
+ }
385
+
386
+ indexPendingItems() {
387
+ if (this.pendingItems.size === 0) {
388
+ return;
389
+ }
390
+ for (const item of this.pendingItems) {
391
+ let key = String(item.valueOf());
392
+ let set = this.index.get(key);
393
+ if (set && set instanceof Set) {
394
+ set.add(item);
395
+ } else {
396
+ this.index.set(key, new Set([item]));
397
+ }
398
+ }
399
+ this.pendingItems.clear();
400
+ }
401
+
402
+ _searchRulesChildren(
403
+ key: string,
404
+ filterType: 'VarDeclaration' | 'Declaration' | 'Mixin',
405
+ options: FindOptions = {}
406
+ ) {
407
+ let rules = this.rules;
408
+ // CRITICAL FIX: Initialize searchedRules if not provided, and add current Rules BEFORE any recursive calls
409
+ // The flaw in the original algorithm: when _searchRulesChildren calls childRules.find(), that creates
410
+ // a new search context via DeclarationRegistry.find(), which may not preserve searchedRules tracking.
411
+ // By initializing it here and adding the current Rules immediately, we ensure tracking persists.
412
+ const searchedRules = options?.searchedRules ?? new Set<Rules>();
413
+ if (!searchedRules.has(rules)) {
414
+ searchedRules.add(rules);
415
+ }
416
+ let findType = filterType === 'Mixin' ? 'mixin' as const : 'declaration' as const;
417
+ let findAll = Boolean(options.findAll);
418
+ let {
419
+ candidates = new Set(),
420
+ start,
421
+ readonly,
422
+ local,
423
+ childFilterType,
424
+ context
425
+ } = options;
426
+ // childFilterType is the filterType to use when calling child Rules.find
427
+ // If not provided, use filterType (for backward compatibility with DeclarationRegistry)
428
+ // Note: childFilterType can be undefined to mean "don't filter" (accept both Mixin and Ruleset)
429
+ const actualChildFilterType = 'childFilterType' in options ? childFilterType : filterType;
430
+ let firstValue = candidates.values().next().value;
431
+ rules.ensureCurrentRenderRulesRegistered(context);
432
+ if (rules._rulesSet) {
433
+ const { rulesSet } = rules;
434
+ const length = rulesSet.length;
435
+ if (length) {
436
+ // Create one shared child options object, reused across loop iterations
437
+ const childOpts: FindOptions = options
438
+ ? {
439
+ ...options,
440
+ searchParents: false,
441
+ start: undefined,
442
+ searchedRules,
443
+ context
444
+ }
445
+ : { searchParents: false, start: undefined, readonly, searchedRules, context };
446
+ const optionalCandidates = options?.optionalCandidates;
447
+ const isComparisonContext = firstValue && candidates.size > 0;
448
+ const hasTarget = options?.hasTarget === true;
449
+
450
+ // searchedRules is already initialized above and includes the current Rules
451
+ // Inline the filter logic into the loop to avoid creating an intermediate array
452
+ for (let i = length - 1; i >= 0; i--) {
453
+ let r = rulesSet.at(i)!;
454
+ // --- inline filter logic ---
455
+ const entryVisibility = r.rulesVisibility?.[filterType];
456
+ const nodeVisibility = r.node.options.rulesVisibility?.[filterType];
457
+ const visibility = entryVisibility ?? nodeVisibility;
458
+ const isMixinOutput = r.node.options?.isMixinOutput === true;
459
+ if (isMixinOutput && !hasTarget) {
460
+ continue;
461
+ }
462
+ if (!isMixinOutput) {
463
+ const isVisible = visibility === 'public' || visibility === 'optional';
464
+ if (!isVisible) {
465
+ continue;
466
+ }
467
+ if (r.node.options?.forward && context?.rulesContext === rules) {
468
+ continue;
469
+ }
470
+ if (local && r.node.options?.local) {
471
+ continue;
472
+ }
473
+ if (!(findAll || !firstValue || isComparisonContext)) {
474
+ continue;
475
+ }
476
+ if (start !== undefined && r.node.index >= start) {
477
+ continue;
478
+ }
479
+ }
480
+ // --- end inline filter logic ---
481
+ // Skip if we've already searched this Rules node to prevent infinite recursion
482
+ if (searchedRules && searchedRules.has(r.node)) {
483
+ continue;
484
+ }
485
+ if (r.node === rules) {
486
+ throw new Error(`Rules node contains itself in rulesSet`);
487
+ }
488
+ // Update per-iteration fields on the shared object
489
+ childOpts.readonly = readonly || r.readonly;
490
+ childOpts.local = local || Boolean(r.node.options?.local);
491
+ // Use actualChildFilterType which may be undefined for mixin-ruleset lookups
492
+ // filterType parameter is used to SELECT registry, actualChildFilterType is used to FILTER results
493
+ let result = r.node.find(findType, key, actualChildFilterType, childOpts);
494
+ if (result) {
495
+ // Check if this Rules has optional visibility (from RulesEntry or the actual Rules node)
496
+ const entryVisibility = r.rulesVisibility?.[filterType];
497
+ const nodeVisibility = r.node.options.rulesVisibility?.[filterType];
498
+ const isOptional = entryVisibility === 'optional' || nodeVisibility === 'optional';
499
+
500
+ const isPublic = entryVisibility === 'public' || nodeVisibility === 'public';
501
+ if (!findAll && isPublic) {
502
+ if (options && childOpts.readonly) {
503
+ options.readonly = true;
504
+ }
505
+ if (isArray(result)) {
506
+ for (const node of result) {
507
+ candidates.add(node);
508
+ }
509
+ } else {
510
+ candidates.add(result);
511
+ }
512
+ break;
513
+ }
514
+ if (options) {
515
+ options.readonly ||= childOpts.readonly;
516
+ }
517
+ if (isArray(result)) {
518
+ for (const node of result) {
519
+ if (isOptional && optionalCandidates) {
520
+ optionalCandidates.add(node);
521
+ } else {
522
+ candidates.add(node);
523
+ }
524
+ }
525
+ } else {
526
+ if (isOptional && optionalCandidates) {
527
+ optionalCandidates.add(result);
528
+ } else {
529
+ candidates.add(result);
530
+ }
531
+ }
532
+ }
533
+ }
534
+ }
535
+ }
536
+ // REMOVED: Manual iteration through rules.value for child Rules nodes
537
+ // If a Rules node is in rules.value and should be searchable, it should be registered
538
+ // via registerNode() which adds it to rulesSet. We already search rulesSet above.
539
+ // Manually iterating through rules.value creates infinite loops when a Rules node
540
+ // appears in its own children, and is unnecessary since registered Rules are in rulesSet.
541
+ }
542
+
543
+ /**
544
+ * Find the closest declaration from start, in reverse order,
545
+ * using a binary search
546
+ */
547
+ _findClosestByStart(list: Type[], start?: number) {
548
+ if (start === undefined) {
549
+ return atIndex(list, -1);
550
+ }
551
+ /**
552
+ * We do this so we start looking above the given position and don't
553
+ * return the current node.
554
+ */
555
+ start -= 1;
556
+ let bestMatch: number | undefined;
557
+
558
+ /** Binary search the queue to find a starting position */
559
+ let left = 0;
560
+ let right = list.length - 1;
561
+
562
+ while (left <= right) {
563
+ let mid = Math.floor((left + right) / 2);
564
+ let midVal = list.at(mid)!.index;
565
+ if (midVal === start) {
566
+ bestMatch = mid;
567
+ break;
568
+ }
569
+ if (midVal < start) {
570
+ bestMatch = mid;
571
+ left = mid + 1;
572
+ } else {
573
+ right = mid - 1;
574
+ }
575
+ }
576
+
577
+ return bestMatch !== undefined ? list.at(bestMatch) : undefined;
578
+ }
579
+
580
+ private _findByKey(candidates: Set<Type> | Type | undefined, key: string): Set<Type> | Type | undefined {
581
+ let set = this.index.get(key);
582
+ if (set) {
583
+ let newSet: Set<Type> | undefined;
584
+ if (set instanceof Set) {
585
+ newSet = set;
586
+ } else if (isArray(set)) {
587
+ newSet = new Set(set.map(({ value }) => value));
588
+ } else {
589
+ return set;
590
+ }
591
+ if (candidates) {
592
+ if (candidates instanceof Set) {
593
+ // Avoid Set.prototype.union (not available in our TS lib target)
594
+ for (const v of newSet) {
595
+ candidates.add(v);
596
+ }
597
+ } else {
598
+ candidates = new Set([candidates, ...newSet]);
599
+ }
600
+ } else {
601
+ candidates = newSet;
602
+ }
603
+ }
604
+ return candidates;
605
+ }
606
+
607
+ find(keys: string | string[] | Set<string>, _filterType?: string, _options?: FindOptions): Type[] | Type | Array<{ value: Type; [key: string]: any }> | undefined {
608
+ this.indexPendingItems();
609
+ let candidates: Set<Type> | Type | undefined;
610
+ if (isArray(keys) || keys instanceof Set) {
611
+ for (const key of keys) {
612
+ candidates = this._findByKey(candidates, key);
613
+ }
614
+ } else {
615
+ candidates = this._findByKey(candidates, keys);
616
+ }
617
+ if (candidates instanceof Set) {
618
+ return candidates.size ? [...candidates] : undefined;
619
+ }
620
+ return candidates;
621
+ }
622
+ }
623
+
624
+ /**
625
+ * Registry for fast selector-based ruleset lookups
626
+ */
627
+ export class RulesetRegistry extends Registry<Ruleset> {
628
+ private _index = new Map<string, Set<Ruleset>>();
629
+ get index(): Map<string, Set<Ruleset>> {
630
+ return this._index;
631
+ }
632
+
633
+ /**
634
+ * Add a ruleset to be indexed later
635
+ */
636
+ override add(ruleset: Ruleset) {
637
+ addRulesetToIndex(this.index, this.rules, ruleset, this.context);
638
+ }
639
+
640
+ /**
641
+ * Index any pending rulesets
642
+ * Override the base class method to use keySet-based indexing
643
+ */
644
+ override indexPendingItems() {
645
+ return;
646
+ }
647
+
648
+ /**
649
+ * Find candidate rulesets that might match the target selector.
650
+ * Searches only the local index - all rulesets should be registered
651
+ * to the extend root's registry during evaluation.
652
+ */
653
+ override find(keys: string[] | Set<string>): Ruleset[] | undefined {
654
+ // Index any pending rulesets first
655
+ this.indexPendingItems();
656
+
657
+ let candidates: Set<Ruleset> | undefined;
658
+ let rulesets: Ruleset[] | undefined;
659
+
660
+ /** Just get based on first key */
661
+ const indices = [this.index];
662
+ for (const key of keys) {
663
+ for (const index of indices) {
664
+ const set = index.get(key);
665
+ if (set) {
666
+ candidates ??= new Set<Ruleset>();
667
+ for (const candidate of set) {
668
+ candidates.add(candidate);
669
+ }
670
+ }
671
+ }
672
+ break;
673
+ }
674
+ if (!candidates) {
675
+ return undefined;
676
+ }
677
+
678
+ /** Now find selectors that have all keys */
679
+ const searchKeys = keys instanceof Set ? [...keys] : keys;
680
+ let searchKeySet = keys instanceof Set ? keys : new Set(keys);
681
+ let searchBitSet: BitSet<string> | undefined;
682
+ for (const c of candidates) {
683
+ let sel = c.get('selector');
684
+ if (!sel || isNode(sel, N.Nil)) {
685
+ continue;
686
+ }
687
+ let isSubset: boolean;
688
+ const selectorKeySet = sel.keySet;
689
+ if (!(selectorKeySet instanceof Set) && selectorKeySet._library) {
690
+ searchBitSet ??= selectorKeySet._library.getBitset(searchKeySet);
691
+ isSubset = isSubsetOf(searchBitSet, selectorKeySet);
692
+ } else {
693
+ isSubset = true;
694
+ for (const k of searchKeys) {
695
+ if (!hasSelectorKey(selectorKeySet, k)) {
696
+ isSubset = false;
697
+ break;
698
+ }
699
+ }
700
+ }
701
+ if (isSubset) {
702
+ (rulesets ??= []).push(c);
703
+ }
704
+ }
705
+
706
+ return rulesets;
707
+ }
708
+ }
709
+
710
+ /**
711
+ * The mixin registry works a little differently than the selector registry
712
+ * in these ways:
713
+ *
714
+ * 1. The mixin registry can only be indexed by basic element, class, and
715
+ * id selectors.
716
+ * 2. The index is the start key, not any key found in the selector.
717
+ * 3. '>' and ' ' combinators are ignored.
718
+ * 4. Initial ampersands (implicit or explicit) are ignored.
719
+ * 5. The mixin registry is local to the rules, whereas the selector registry
720
+ * is global to the file tree.
721
+ * 6. Rulesets and mixins without params will have their children searched
722
+ * if the first part matches.
723
+ */
724
+ export class MixinRegistry extends Registry<
725
+ Mixin | Ruleset,
726
+ MixinRegistryEntry[]
727
+ > {
728
+ private _index = new Map<string, MixinRegistryEntry[]>();
729
+ get index(): Map<string, MixinRegistryEntry[]> {
730
+ return this._index;
731
+ }
732
+
733
+ // private getSimpleKeyList(selector: Selector | Nil | undefined): string[] | undefined {
734
+ // let keyList: string[] | undefined;
735
+ // if (selector && 'keySet' in selector) {
736
+ // let passed = true;
737
+ // let foundBasic = false;
738
+ // for (const sel of selector.nodes()) {
739
+ // /** Ampersand is okay at start, but not after a basic selector */
740
+ // if (!foundBasic && isNode(sel, 'Ampersand')) {
741
+ // continue;
742
+ // }
743
+
744
+ // if (isNode(sel, 'Combinator')) {
745
+ // if (sel.value !== '>' && sel.value !== ' ') {
746
+ // passed = false;
747
+ // break;
748
+ // }
749
+ // continue;
750
+ // }
751
+
752
+ // /** Anything other than a universal selector is fine */
753
+ // if (isNode(sel, 'BasicSelector') && /^[^*]/.test(sel.value)) {
754
+ // (keyList ??= []).push(sel.valueOf() as string);
755
+ // foundBasic = true;
756
+ // continue;
757
+ // }
758
+ // if (isNode(sel, 'CompoundSelector') || isNode(sel, 'ComplexSelector')) {
759
+ // /** Might still be fine */
760
+ // continue;
761
+ // }
762
+ // /** Nothing else is valid, so fail */
763
+ // passed = false;
764
+ // break;
765
+ // }
766
+ // if (!passed) {
767
+ // return;
768
+ // }
769
+ // }
770
+ // return keyList;
771
+ // }
772
+
773
+ private _indexSelectorStart(mixin: Ruleset | Mixin, keySet: SelectorKeySet | string[]) {
774
+ indexMixinSelectorStart(this.index, mixin, keySet);
775
+ }
776
+
777
+ /**
778
+ * For un-preEvaluated mixin rules, register child Rulesets/Mixins
779
+ * so namespace lookup can descend into them. Also propagate
780
+ * keySetLibrary so selectors can compute their keySets.
781
+ */
782
+ private _ensureChildrenRegistered(rules: Rules, selectorBits?: BitSetLibrary<string>) {
783
+ for (const child of rules.getRegistryChildren(this.context)) {
784
+ if (isNode(child, N.Ruleset)) {
785
+ const sel = child.get('selector', this.context);
786
+ if (isSelectorLikeNode(sel) && selectorBits && !sel.keySetLibrary) {
787
+ sel.keySetLibrary = selectorBits;
788
+ const selValue = Reflect.get(sel, 'value');
789
+ if (isArray(selValue)) {
790
+ for (const sub of selValue) {
791
+ if (isSelectorLikeNode(sub) && !sub.keySetLibrary) {
792
+ sub.keySetLibrary = selectorBits;
793
+ }
794
+ }
795
+ }
796
+ }
797
+ rules.registerNode(child, undefined, this.context);
798
+ } else if (isNode(child, N.Mixin)) {
799
+ rules.registerNode(child, undefined, this.context);
800
+ }
801
+ }
802
+ }
803
+
804
+ override add(mixin: Mixin | Ruleset) {
805
+ addMixinToIndex(this.index, this.rules, mixin, this.context);
806
+ }
807
+
808
+ override indexPendingItems() {
809
+ return;
810
+ }
811
+
812
+ /**
813
+ * Check if an entry matches the search criteria.
814
+ * Handles exact matches, partial matches (compound selector completion), and recursive searches.
815
+ * This consolidates the matching logic to avoid duplication.
816
+ */
817
+ /**
818
+ * Check if a Ruleset/Mixin matches a given array of keys using the same logic as the registry
819
+ * This uses the indexed match arrays (same as _checkEntryMatch) rather than direct selector comparison
820
+ * @param value The Ruleset or Mixin to check
821
+ * @param keys The array of keys to match against (e.g., [".jo", ".ki"])
822
+ * @returns true if the Ruleset/Mixin matches the keys using registry matching logic
823
+ */
824
+ checkRulesetMatchesKeys(value: Mixin | Ruleset, keys: string[]): boolean {
825
+ if (!keys || keys.length === 0) {
826
+ return false;
827
+ }
828
+
829
+ // Get the selector's keySet and extract indexable keys (same as _indexSelectorStart)
830
+ let indexableKeys: string[] = [];
831
+ if (isNode(value, N.Ruleset)) {
832
+ const selector = value.get('selector', this.context);
833
+ if (isNode(selector, N.Nil)) {
834
+ return false;
835
+ }
836
+ if (isNode(selector, N.SelectorList)) {
837
+ // For selector lists, check if any selector matches
838
+ return selector.get('value').some((sel) => {
839
+ const selKeys = getIndexableSelectorKeys(tryGetSelectorKeySet(sel, false));
840
+ if (selKeys.length === 0) {
841
+ return false;
842
+ }
843
+ // Check if keys appear in sequence in this selector's keys
844
+ return this._checkKeysSubsequence(selKeys, keys);
845
+ });
846
+ }
847
+ const keySet = tryGetSelectorKeySet(selector, false);
848
+ if (!keySet || getSelectorKeyValues(keySet).length === 0) {
849
+ return false;
850
+ }
851
+ indexableKeys = getIndexableSelectorKeys(keySet);
852
+ } else {
853
+ const keySet = value.keySet;
854
+ if (!keySet || getSelectorKeyValues(keySet).length === 0) {
855
+ return false;
856
+ }
857
+ indexableKeys = getIndexableSelectorKeys(keySet);
858
+ }
859
+
860
+ if (indexableKeys.length === 0) {
861
+ return false;
862
+ }
863
+
864
+ // Check if the provided keys appear in sequence in the selector's indexable keys
865
+ // The keySet should only contain keys from the Ruleset's own selector, not parent context
866
+ return this._checkKeysSubsequence(indexableKeys, keys);
867
+ }
868
+
869
+ /**
870
+ * Internal helper that checks if the provided keys appear in sequence within the selector's keys
871
+ *
872
+ * For compound selectors like `#header .milk .chips .jo.ki`, when we search for `.jo`, we get:
873
+ * - The full selector's indexable keys: `["#header", ".milk", ".chips", ".jo", ".ki"]`
874
+ * - When checking if accumulated keys `[".jo", ".ki"]` match, we check if they appear in sequence
875
+ */
876
+ private _checkKeysSubsequence(selectorKeys: string[], searchKeys: string[]): boolean {
877
+ if (searchKeys.length === 0) {
878
+ return false;
879
+ }
880
+
881
+ // Check if searchKeys is a subsequence of selectorKeys (searchKeys appear in order)
882
+ let searchIndex = 0;
883
+ for (const selectorKey of selectorKeys) {
884
+ if (searchIndex < searchKeys.length && selectorKey === searchKeys[searchIndex]) {
885
+ searchIndex++;
886
+ }
887
+ }
888
+
889
+ const matches = searchIndex === searchKeys.length;
890
+ return matches;
891
+ }
892
+
893
+ /**
894
+ * Find candidate mixins (or rulesets, or both) that might match the target selector
895
+ *
896
+ * ...also...
897
+ *
898
+ * @todo - Not sure how recursion works here with the match overflow and returning
899
+ * proper arrays.
900
+ */
901
+ override find(
902
+ keys: string | string[],
903
+ filterType: 'Mixin' | 'Ruleset' | undefined = undefined,
904
+ options: FindOptions = {}
905
+ ): (Mixin | Ruleset)[] | undefined {
906
+ let keyList: string[] | undefined;
907
+
908
+ if (isArray(keys)) {
909
+ keyList = keys;
910
+ } else {
911
+ keyList = [keys];
912
+ }
913
+
914
+ if (!keyList?.length) {
915
+ return;
916
+ }
917
+
918
+ let rules: Rules | undefined = this.rules;
919
+ let {
920
+ searchParents = true,
921
+ local = false,
922
+ candidates = new Set<Node>(),
923
+ context,
924
+ hasTarget = false
925
+ } = options ?? {};
926
+ const candidateContexts = options?.candidateContexts ?? new WeakMap<Node, Context>();
927
+ const getCandidateIdentity = (node: Node): Node => node.sourceNode ?? node;
928
+ const getContextRenderPriority = (ctx?: Context): number => {
929
+ const renderKey = ctx?.renderKey;
930
+ if (renderKey === undefined || renderKey === CANONICAL) {
931
+ return 0;
932
+ }
933
+ if (renderKey === EVAL) {
934
+ return 1;
935
+ }
936
+ return 2;
937
+ };
938
+ const getCandidateScore = (node: Node, ctx?: Context): [number, number, number] => {
939
+ const nonCanonicalParentEdgeKeys = node.parentEdges
940
+ ? [...node.parentEdges.keys()].filter(key => key !== CANONICAL && key !== CALLER)
941
+ : [];
942
+ const ctxRenderKey = ctx?.renderKey;
943
+ const matchesContextKey = ctxRenderKey !== undefined && nonCanonicalParentEdgeKeys.includes(ctxRenderKey) ? 1 : 0;
944
+ const contextPriority = getContextRenderPriority(ctx);
945
+ const fewerEdgesScore = -nonCanonicalParentEdgeKeys.length;
946
+ return [matchesContextKey, contextPriority, fewerEdgesScore];
947
+ };
948
+ const rememberCandidateContext = (node: Node): void => {
949
+ if (!context || !rules) {
950
+ return;
951
+ }
952
+ const identity = getCandidateIdentity(node);
953
+ const nextContext: Context = {
954
+ ...context,
955
+ rulesContext: rules
956
+ };
957
+ const existingContext = candidateContexts.get(identity);
958
+ if (
959
+ !existingContext
960
+ || (
961
+ existingContext.renderKey === CANONICAL
962
+ && nextContext.renderKey !== CANONICAL
963
+ )
964
+ || (
965
+ existingContext.renderKey === undefined
966
+ && nextContext.renderKey !== undefined
967
+ )
968
+ ) {
969
+ candidateContexts.set(identity, nextContext);
970
+ }
971
+ };
972
+ const addCandidate = (node: Node): void => {
973
+ const sourceNode = node.sourceNode ?? node;
974
+ for (const existing of candidates) {
975
+ const existingNode = existing;
976
+ if ((existingNode.sourceNode ?? existingNode) !== sourceNode) {
977
+ continue;
978
+ }
979
+ const existingIdentity = getCandidateIdentity(existingNode);
980
+ const existingContext = candidateContexts.get(existingIdentity);
981
+ const nextContext = context && rules
982
+ ? {
983
+ ...context,
984
+ rulesContext: rules
985
+ }
986
+ : context;
987
+ const existingScore = getCandidateScore(existingNode, existingContext);
988
+ const nextScore = getCandidateScore(node, nextContext);
989
+ rememberCandidateContext(existingNode);
990
+ if (
991
+ existingScore[0] > nextScore[0]
992
+ || (existingScore[0] === nextScore[0] && existingScore[1] > nextScore[1])
993
+ || (existingScore[0] === nextScore[0] && existingScore[1] === nextScore[1] && existingScore[2] >= nextScore[2])
994
+ ) {
995
+ return;
996
+ }
997
+ candidates.delete(existingNode);
998
+ break;
999
+ }
1000
+ if (context && rules) {
1001
+ setParent(node, rules, context);
1002
+ rememberCandidateContext(node);
1003
+ }
1004
+ (candidates ??= new Set()).add(node);
1005
+ };
1006
+ const mixinHasNoRequiredParams = (mixinNode: Mixin): boolean => {
1007
+ const params = mixinNode.get('params');
1008
+ if (!params || params.length === 0) {
1009
+ return true;
1010
+ }
1011
+ for (const param of params.get('value')) {
1012
+ if (param.type === 'Rest') {
1013
+ continue;
1014
+ }
1015
+ if (isNode(param, N.VarDeclaration)) {
1016
+ if (param.get('value') instanceof Nil) {
1017
+ return false;
1018
+ }
1019
+ continue;
1020
+ }
1021
+ if (isNode(param, N.Any) && param.role === 'property') {
1022
+ return false;
1023
+ }
1024
+ return false;
1025
+ }
1026
+ return true;
1027
+ };
1028
+ const getDescendContext = (node: Node, baseContext?: Context): Context | undefined => {
1029
+ if (!baseContext) {
1030
+ return undefined;
1031
+ }
1032
+ let nodeRenderKey = node.renderKey;
1033
+ if (nodeRenderKey === undefined || nodeRenderKey === CANONICAL || nodeRenderKey === baseContext.renderKey) {
1034
+ const nonCanonicalParentEdgeKeys = node.parentEdges
1035
+ ? [...node.parentEdges.keys()].filter(key => key !== CANONICAL && key !== CALLER)
1036
+ : [];
1037
+ if (nonCanonicalParentEdgeKeys.length === 1) {
1038
+ nodeRenderKey = nonCanonicalParentEdgeKeys[0]!;
1039
+ }
1040
+ }
1041
+ if (
1042
+ nodeRenderKey === undefined
1043
+ || nodeRenderKey === CANONICAL
1044
+ || nodeRenderKey === baseContext.renderKey
1045
+ ) {
1046
+ return baseContext;
1047
+ }
1048
+ return {
1049
+ ...baseContext,
1050
+ renderKey: nodeRenderKey
1051
+ };
1052
+ };
1053
+
1054
+ // Track which Rules nodes we've already searched to prevent infinite recursion
1055
+ // Use the searchedRules from options if it exists, otherwise create a new Set
1056
+ let mixinChildSearchOpts: FindOptions | undefined;
1057
+ const searchedRules = options?.searchedRules || new Set<Rules>();
1058
+ if (options) {
1059
+ options.searchedRules = searchedRules;
1060
+ options.candidateContexts = candidateContexts;
1061
+ }
1062
+ while (rules) {
1063
+ // Don't add to searchedRules yet - we'll add it after we finish searching (including children)
1064
+ let [startKey, ...search] = keyList;
1065
+ const registry = rules.getRegistry('mixin', context);
1066
+ if (registry) {
1067
+ registry.indexPendingItems();
1068
+ }
1069
+ const mixinIndices = registry ? [registry.index] : [];
1070
+ const existing: MixinRegistryEntry[] = [];
1071
+ for (const index of mixinIndices) {
1072
+ const entries = index.get(startKey!);
1073
+ if (entries) {
1074
+ existing.push(...entries);
1075
+ }
1076
+ }
1077
+ // Resolve interpolated selector starts (e.g. "@{a2}") against current context
1078
+ // so unresolved-index keys can still match resolved call keys (e.g. ".foo").
1079
+ let resolvedInterpolatedStartEntries: Array<{ value: Mixin | Ruleset; match: string[] }> = [];
1080
+ if (context && typeof startKey === 'string' && existing.length === 0) {
1081
+ for (const index of mixinIndices) {
1082
+ for (const [indexedKey, indexedEntries] of index) {
1083
+ const matchInterpolated = /^@\{(.+)\}$/.exec(indexedKey);
1084
+ if (!matchInterpolated) {
1085
+ continue;
1086
+ }
1087
+ const varName = matchInterpolated[1]!;
1088
+ const maybeVar = rules.find('declaration', varName, 'VarDeclaration', {
1089
+ context,
1090
+ hasTarget,
1091
+ filter: options?.filter
1092
+ });
1093
+ if (isNode(maybeVar, N.VarDeclaration)) {
1094
+ const resolvedValue = String(maybeVar.get('value').valueOf?.() ?? maybeVar.get('value') ?? '');
1095
+ if (resolvedValue === startKey) {
1096
+ resolvedInterpolatedStartEntries.push(...indexedEntries);
1097
+ }
1098
+ }
1099
+ }
1100
+ }
1101
+ }
1102
+
1103
+ // With the new indexing (by local visible keys), nested rulesets are indexed under their own keys
1104
+ // So we only need to check entries under the startKey - no need to scan all entries
1105
+ let allEntriesToCheck: Array<{ value: Mixin | Ruleset; match: string[] }> = [];
1106
+ if (existing) {
1107
+ allEntriesToCheck.push(...existing);
1108
+ }
1109
+ if (resolvedInterpolatedStartEntries.length > 0) {
1110
+ allEntriesToCheck.push(...resolvedInterpolatedStartEntries);
1111
+ }
1112
+
1113
+ if (allEntriesToCheck.length > 0) {
1114
+ const targetMatch = search.length === 0 ? [startKey!] : search;
1115
+ const deferredExactMatches: Array<Mixin | Ruleset> = [];
1116
+ const candidateSizeBeforeEntries = candidates.size;
1117
+ for (const { value, match } of allEntriesToCheck) {
1118
+ if (filterType && value.type !== filterType) {
1119
+ continue;
1120
+ }
1121
+
1122
+ // If match equals targetMatch (search or [startKey] when search is empty), this IS the ruleset we're looking for
1123
+ // Also, if search is empty and match is empty, this ruleset IS the startKey we're looking for
1124
+ // BUT: For compound search paths (keyList.length > 1), we should NOT add the startKey mixin itself
1125
+ // as a candidate when search.length === 0 && match.length === 0, because that means we found the startKey
1126
+ // but haven't fully matched the compound path. The startKey should only be added as a candidate if we're
1127
+ // doing a simple lookup (keyList.length === 1), where the startKey IS the full match.
1128
+ if (arraysEqualAsSet(match, targetMatch)) {
1129
+ if (keyList.length > 1) {
1130
+ deferredExactMatches.push(value);
1131
+ } else {
1132
+ addCandidate(value);
1133
+ }
1134
+ continue;
1135
+ }
1136
+ // Only add startKey mixin as candidate if we're doing a simple lookup (not a compound path)
1137
+ if (search.length === 0 && match.length === 0 && keyList.length === 1) {
1138
+ addCandidate(value);
1139
+ continue;
1140
+ }
1141
+ // For compound paths, we don't add startKey as a candidate, but we still need to search inside it
1142
+ // The recursive search below will handle finding nested mixins
1143
+
1144
+ // If match equals [startKey] OR match is empty (meaning this ruleset IS the startKey),
1145
+ // we need to search inside it for the remaining search keys
1146
+ // NOTE: We should search inside #theme even if we're not adding it as a candidate (for compound paths)
1147
+ if (search.length > 0 && (arraysEqual(match, [startKey!]) || match.length === 0)) {
1148
+ const isRuleset = isNode(value, N.Ruleset);
1149
+ const isMixin = isNode(value, N.Mixin);
1150
+ const hasNoParams = isMixin && mixinHasNoRequiredParams(value);
1151
+ if (isRuleset || hasNoParams) {
1152
+ const subRules = isRuleset
1153
+ ? value.enterRules(context)
1154
+ : value.get('rules', context).withRenderOwner(
1155
+ value,
1156
+ context?.renderKey,
1157
+ context
1158
+ );
1159
+ // Mixin rules aren't preEvaluated during registration — register
1160
+ // child rulesets/mixins now so namespace lookup can descend.
1161
+ // Always ensure children are registered for namespace descent —
1162
+ // preEvaluated children still need keySetLibrary on their selectors
1163
+ // for the mixin registry to index them.
1164
+ this._ensureChildrenRegistered(subRules, context?.selectorBits);
1165
+ const subMixinRegistry = subRules.getRegistry('mixin', context);
1166
+ subMixinRegistry?.indexPendingItems();
1167
+ subMixinRegistry?.find(search, filterType, {
1168
+ searchParents: false,
1169
+ local,
1170
+ candidates,
1171
+ context,
1172
+ candidateContexts,
1173
+ filter: options?.filter,
1174
+ hasTarget,
1175
+ searchedRules: undefined // Not needed when searchParents is false
1176
+ });
1177
+ }
1178
+ continue;
1179
+ }
1180
+
1181
+ // If there are more search keys than match keys, recursively search inside this ruleset
1182
+ // This handles cases where match is a prefix of search (e.g., match=[".foo"], search=[".foo", ".bar"])
1183
+ // Or when match is empty (ruleset IS the startKey) and we need to search inside for the full search
1184
+ const shouldRecurse = search.length > 0 && (search.length > match.length || match.length === 0);
1185
+ const matchKeysInSearch = match.length > 0 && arrayContainsAll(search, match);
1186
+ if (shouldRecurse) {
1187
+ let searchKeys: string[];
1188
+ if (match.length === 0) {
1189
+ // Match is empty, meaning this ruleset IS the startKey, search inside for the full search
1190
+ searchKeys = search;
1191
+ } else if (matchKeysInSearch) {
1192
+ // Match keys are all contained in search — remove them (set-based) to get the remainder
1193
+ const matchSet = new Set(match);
1194
+ searchKeys = search.filter(k => !matchSet.has(k));
1195
+ } else {
1196
+ // Match is not a prefix of search - skip this ruleset, it doesn't match
1197
+ continue;
1198
+ }
1199
+ const isRuleset = isNode(value, N.Ruleset);
1200
+ const isMixin = isNode(value, N.Mixin);
1201
+ const hasNoParams = isMixin && mixinHasNoRequiredParams(value);
1202
+ if (isRuleset || hasNoParams) {
1203
+ const subRules = isRuleset
1204
+ ? value.enterRules(context)
1205
+ : value.get('rules', context).withRenderOwner(
1206
+ value,
1207
+ context?.renderKey,
1208
+ context
1209
+ );
1210
+ this._ensureChildrenRegistered(subRules, context?.selectorBits);
1211
+ const subMixinRegistry = subRules.getRegistry('mixin', context);
1212
+ subMixinRegistry?.indexPendingItems();
1213
+ subMixinRegistry?.find(searchKeys, filterType, {
1214
+ searchParents: false,
1215
+ local,
1216
+ candidates,
1217
+ context,
1218
+ candidateContexts,
1219
+ filter: options?.filter,
1220
+ hasTarget,
1221
+ searchedRules: searchedRules
1222
+ });
1223
+ }
1224
+ }
1225
+ }
1226
+
1227
+ if (keyList.length > 1 && candidates.size === candidateSizeBeforeEntries) {
1228
+ for (const candidate of deferredExactMatches) {
1229
+ addCandidate(candidate);
1230
+ }
1231
+ }
1232
+ }
1233
+
1234
+ // Track which candidates existed before searching children (by snapshot of the set)
1235
+ const candidateSizeBefore = candidates ? candidates.size : 0;
1236
+ const candidatesBefore = candidateSizeBefore > 0 ? new Set(candidates) : undefined;
1237
+ // Reuse a single child search options object
1238
+ // For compound paths (keyList.length > 1), the first segment acts as a namespace
1239
+ // target — allow searching inside mixin output rulesets so that e.g.
1240
+ // `.Person("Male")` output containing `.person { .sayGender() {} }` is reachable.
1241
+ const childHasTarget = hasTarget || keyList.length > 1;
1242
+ if (!mixinChildSearchOpts) {
1243
+ mixinChildSearchOpts = {
1244
+ searchParents: false,
1245
+ local,
1246
+ candidates,
1247
+ findAll: true,
1248
+ childFilterType: filterType,
1249
+ context,
1250
+ filter: options?.filter,
1251
+ hasTarget: childHasTarget,
1252
+ searchedRules
1253
+ };
1254
+ } else {
1255
+ mixinChildSearchOpts.searchedRules = searchedRules;
1256
+ }
1257
+ registry?._searchRulesChildren(startKey!, 'Mixin', mixinChildSearchOpts);
1258
+
1259
+ // After _searchRulesChildren, check if any new candidates are mixins/rulesets we should search inside
1260
+ // This handles the case where #theme mixin is found in imported Rules and we need to search inside it
1261
+ // Also, for compound paths, remove #theme from candidates if it was added by _searchRulesChildren
1262
+ // because we only want to search inside it, not include it as a final candidate
1263
+ if (candidates && candidates.size > candidateSizeBefore) {
1264
+ const candidatesToRemove: (Mixin | Ruleset)[] = [];
1265
+ for (const candidate of candidates) {
1266
+ const candidateNode = candidate;
1267
+ // Only check candidates that were added by _searchRulesChildren (not in original set)
1268
+ if (candidatesBefore && candidatesBefore.has(candidateNode)) {
1269
+ continue;
1270
+ }
1271
+ {
1272
+ const isMixin = isNode(candidateNode, N.Mixin);
1273
+ const isRuleset = isNode(candidateNode, N.Ruleset);
1274
+ const hasNoParams = isMixin && mixinHasNoRequiredParams(candidateNode);
1275
+ // Check if this candidate matches the startKey.
1276
+ // For rulesets discovered via child-search, key-set membership is the reliable signal.
1277
+ const candidateKey = isMixin
1278
+ ? candidateNode.get('name')?.valueOf?.()
1279
+ : (isRuleset ? candidateNode.get('selector').valueOf?.() : '');
1280
+ const candidateSelector = isRuleset
1281
+ ? candidateNode.get('selector')
1282
+ : undefined;
1283
+ const candidateVisibleKeySet = candidateSelector && !isNode(candidateSelector, N.Nil)
1284
+ ? tryGetSelectorKeySet(candidateSelector, true)
1285
+ : undefined;
1286
+ const candidateKeySet = candidateSelector && !isNode(candidateSelector, N.Nil)
1287
+ ? tryGetSelectorKeySet(candidateSelector, false)
1288
+ : undefined;
1289
+ const matchesStartKey = isRuleset
1290
+ ? (
1291
+ hasSelectorKey(candidateVisibleKeySet, startKey!)
1292
+ || hasSelectorKey(candidateKeySet, startKey!)
1293
+ || candidateKey === startKey
1294
+ )
1295
+ : candidateKey === startKey;
1296
+
1297
+ // For compound paths (keyList.length > 1), remove startKey from candidates if it was added by _searchRulesChildren
1298
+ // because we only want to search inside it, not include it as a final candidate
1299
+ if (matchesStartKey && keyList.length > 1) {
1300
+ candidatesToRemove.push(candidateNode);
1301
+ }
1302
+
1303
+ // Search inside the candidate if it matches startKey and we have remaining search keys
1304
+ if (matchesStartKey && search.length > 0 && (isRuleset || hasNoParams)) {
1305
+ const foundContext = candidateContexts.get(getCandidateIdentity(candidateNode));
1306
+ const descendContext = getDescendContext(candidateNode, foundContext ?? context);
1307
+ let subRules = isRuleset
1308
+ ? candidateNode.enterRules(descendContext)
1309
+ : candidateNode.get('rules', descendContext).withRenderOwner(
1310
+ candidateNode,
1311
+ descendContext?.renderKey,
1312
+ descendContext
1313
+ );
1314
+ const searchContext = descendContext
1315
+ ? { ...descendContext, rulesContext: subRules }
1316
+ : undefined;
1317
+ const subMixinRegistry = subRules.getRegistry('mixin', searchContext);
1318
+ subMixinRegistry?.indexPendingItems();
1319
+ subMixinRegistry?.find(search, filterType, {
1320
+ searchParents: false,
1321
+ local,
1322
+ candidates,
1323
+ context: searchContext,
1324
+ candidateContexts,
1325
+ filter: options?.filter,
1326
+ hasTarget,
1327
+ searchedRules: undefined // Not needed when searchParents is false
1328
+ });
1329
+ }
1330
+ }
1331
+ }
1332
+ // Remove candidates that shouldn't be in the final result (for compound paths)
1333
+ for (const candidateToRemove of candidatesToRemove) {
1334
+ candidates.delete(candidateToRemove);
1335
+ }
1336
+ }
1337
+
1338
+ // Mark this Rules node as searched after we've finished searching it (including children)
1339
+ searchedRules.add(rules);
1340
+
1341
+ if (isNonImportStyleBoundary(rules)) {
1342
+ searchParents = false;
1343
+ }
1344
+
1345
+ if (!searchParents) {
1346
+ break;
1347
+ }
1348
+ do {
1349
+ rules = rules?.getRegistryParent(context);
1350
+ } while (rules && rules.type !== 'Rules');
1351
+ }
1352
+
1353
+ // With compound keys parsed as arrays (e.g., ['#theme', '.dark', '.navbar', '.colors']),
1354
+ // we can find all matches in one pass. The find() method handles compound keys by
1355
+ // recursively searching inside nested rulesets for the remaining keys.
1356
+
1357
+ return candidates.size ? [...candidates] : undefined;
1358
+ }
1359
+ }
1360
+
1361
+ /**
1362
+ * For either Sass, Jess, or JS functions.
1363
+ *
1364
+ * Less and Sass can register global functions that can be called from the language
1365
+ * without a `@-use` directive.
1366
+ *
1367
+ * @todo Should the presence of `@-use` directives anywhere in the
1368
+ * stylesheet tree cause these global functions to be disabled?
1369
+ */
1370
+ export class FunctionRegistry extends Registry<JsFunction | Func, JsFunction | Func> {
1371
+ index = new Map<string, JsFunction | Func>();
1372
+
1373
+ cloneForRules(rules: Rules): FunctionRegistry {
1374
+ const next = new FunctionRegistry(rules);
1375
+ // Preserve any functions injected directly into the registry (Less plugin style).
1376
+ next.index = new Map(this.index);
1377
+ next.pendingItems = new Set(this.pendingItems);
1378
+ return next;
1379
+ }
1380
+
1381
+ override indexPendingItems() {
1382
+ if (this.pendingItems.size === 0) {
1383
+ return;
1384
+ }
1385
+ for (const item of this.pendingItems) {
1386
+ if (item instanceof JsFunction) {
1387
+ this.index.set(item.name!, item);
1388
+ continue;
1389
+ }
1390
+ // Stylesheet-defined function node
1391
+ const nameKey = (item as Func).nameKey;
1392
+ if (nameKey) {
1393
+ this.index.set(nameKey, item);
1394
+ }
1395
+ }
1396
+ this.pendingItems.clear();
1397
+ }
1398
+
1399
+ override find(name: string, filterType?: string, options?: FindOptions): JsFunction | Func | undefined {
1400
+ let fn: JsFunction | Func | undefined;
1401
+ let rules: Rules | undefined = this.rules;
1402
+ let { searchParents = true } = options ?? {};
1403
+ let findRoot = false;
1404
+ while (rules) {
1405
+ if (isNonImportStyleBoundary(rules)) {
1406
+ searchParents = false;
1407
+ }
1408
+ let registry = rules.functionRegistry;
1409
+ if (registry) {
1410
+ registry.indexPendingItems();
1411
+ fn = registry.index.get(name);
1412
+
1413
+ if (fn || !searchParents) {
1414
+ break;
1415
+ }
1416
+ }
1417
+
1418
+ do {
1419
+ rules = rules?.getRegistryParent(this.context);
1420
+ if (
1421
+ findRoot
1422
+ && rules?.type === 'Rules'
1423
+ && rules.getRegistryParent(this.context) === undefined
1424
+ ) {
1425
+ /** We're at the root */
1426
+ break;
1427
+ }
1428
+ } while (!findRoot && rules && rules.type !== 'Rules');
1429
+ }
1430
+
1431
+ return fn;
1432
+ }
1433
+
1434
+ /**
1435
+ * Override add() to support both Jess API (add(item)) and Less.js API (add(name, func))
1436
+ */
1437
+ override add(item: JsFunction | Func): void;
1438
+ override add(name: string, func: JsFunction | ((...args: any[]) => any)): void;
1439
+ override add(
1440
+ nameOrItem: string | JsFunction | Func,
1441
+ func?: JsFunction | ((...args: any[]) => any)
1442
+ ): void {
1443
+ // If first argument is a JsFunction or Func, use base class behavior
1444
+ if (nameOrItem instanceof JsFunction || (typeof nameOrItem === 'object' && nameOrItem !== null && Reflect.get(nameOrItem, 'type') === 'Func')) {
1445
+ super.add(nameOrItem as JsFunction | Func);
1446
+ return;
1447
+ }
1448
+
1449
+ // Otherwise, it's Less.js-compatible API: add(name, func)
1450
+ if (typeof nameOrItem !== 'string' || func === undefined) {
1451
+ throw new Error('FunctionRegistry.add() requires either a JsFunction or (name: string, func: JsFunction | Function)');
1452
+ }
1453
+
1454
+ // Convert name to lowercase for Less.js compatibility
1455
+ const lowerName = nameOrItem.toLowerCase();
1456
+
1457
+ // If func is already a JsFunction, use it directly
1458
+ // Otherwise, create a new JsFunction from the raw function
1459
+ const jsFunc = func instanceof JsFunction
1460
+ ? func
1461
+ : new JsFunction({ name: lowerName, fn: func });
1462
+
1463
+ // Ensure the name is set
1464
+ if (!jsFunc.name) {
1465
+ jsFunc.name = lowerName;
1466
+ }
1467
+
1468
+ // Add to pendingItems directly
1469
+ this.pendingItems.add(jsFunc);
1470
+ }
1471
+
1472
+ /**
1473
+ * Less.js-compatible API: Add multiple functions at once
1474
+ * @param functions Object mapping function names to functions
1475
+ */
1476
+ addMultiple(functions: Record<string, JsFunction | ((...args: any[]) => any)>): void {
1477
+ for (const [name, func] of Object.entries(functions)) {
1478
+ this.add(name, func);
1479
+ }
1480
+ }
1481
+
1482
+ /**
1483
+ * Less.js-compatible API: Get a function by name
1484
+ * Uses case-insensitive lookup and searches parent chain
1485
+ * @param name Function name (case-insensitive)
1486
+ * @returns The function if found, undefined otherwise
1487
+ */
1488
+ get(name: string): JsFunction | Func | undefined {
1489
+ // Convert to lowercase for case-insensitive lookup
1490
+ const lowerName = name.toLowerCase();
1491
+
1492
+ // First check local registry
1493
+ this.indexPendingItems();
1494
+ let fn = this.index.get(lowerName);
1495
+
1496
+ if (fn) {
1497
+ return fn;
1498
+ }
1499
+
1500
+ // If not found locally, use find() to search parent chain
1501
+ // find() already handles parent traversal
1502
+ return this.find(lowerName);
1503
+ }
1504
+
1505
+ /**
1506
+ * Less.js-compatible API: Get all local functions (without parent chain)
1507
+ * @returns Object mapping function names to functions
1508
+ */
1509
+ getLocalFunctions(): Record<string, JsFunction | Func> {
1510
+ this.indexPendingItems();
1511
+ const result: Record<string, JsFunction | Func> = {};
1512
+ for (const [name, func] of this.index.entries()) {
1513
+ result[name] = func;
1514
+ }
1515
+ return result;
1516
+ }
1517
+
1518
+ /**
1519
+ * Less.js-compatible API: Create a child registry that inherits from this one
1520
+ * In Less.js, this creates a new registry with prototype inheritance.
1521
+ * In Jess, we create a new registry that searches this one as a parent.
1522
+ *
1523
+ * @returns A new FunctionRegistry that will search this registry when functions aren't found locally
1524
+ */
1525
+ inherit(): FunctionRegistry {
1526
+ // Create a new registry for the same Rules
1527
+ // The new registry will use find() which searches parent chain
1528
+ // We need to create a registry that references this one as parent
1529
+ // Since FunctionRegistry.find() already searches parent Rules chain,
1530
+ // we can create a new registry on the same Rules and it will naturally
1531
+ // find functions in parent Rules. However, for true "inherit" behavior
1532
+ // where we want to search THIS registry specifically, we need a different approach.
1533
+
1534
+ // For now, create a new registry on the same Rules
1535
+ // The find() method will search up the Rules parent chain, which includes
1536
+ // this registry's Rules, so it should work correctly.
1537
+ const childRegistry = new FunctionRegistry(this.rules);
1538
+
1539
+ // Store reference to parent registry for direct lookup
1540
+ // This allows the child to search the parent registry even if it's on the same Rules
1541
+ Reflect.set(childRegistry, '_parentRegistry', this);
1542
+
1543
+ // Override get() to check parent registry first
1544
+ const originalGet = childRegistry.get.bind(childRegistry);
1545
+ childRegistry.get = function(this: FunctionRegistry, name: string): JsFunction | Func | undefined {
1546
+ // First check local registry
1547
+ this.indexPendingItems();
1548
+ const localFn = this.index.get(name.toLowerCase());
1549
+ if (localFn) {
1550
+ return localFn;
1551
+ }
1552
+
1553
+ // Then check parent registry
1554
+ const parentRegistry = Reflect.get(this, '_parentRegistry');
1555
+ if (parentRegistry instanceof FunctionRegistry) {
1556
+ const parentFn = parentRegistry.get(name);
1557
+ if (parentFn) {
1558
+ return parentFn;
1559
+ }
1560
+ }
1561
+
1562
+ // Finally, use find() to search Rules parent chain
1563
+ return originalGet(name);
1564
+ }.bind(childRegistry);
1565
+
1566
+ return childRegistry;
1567
+ }
1568
+ }
1569
+
1570
+ /**
1571
+ *
1572
+ * @note - Keys of different types may overlap, but then are filtered when searching.
1573
+ * As in, a variable named `$foo` and a property named `foo` will be in the
1574
+ * same map.
1575
+ */
1576
+ export class DeclarationRegistry extends Registry<Declaration> {
1577
+ private _index = new Map<string, Set<Declaration>>();
1578
+ get index(): Map<string, Set<Declaration>> {
1579
+ return this._index;
1580
+ }
1581
+
1582
+ override add(item: Declaration): void {
1583
+ addDeclarationToIndex(this.index, item);
1584
+ }
1585
+
1586
+ override indexPendingItems() {
1587
+ return;
1588
+ }
1589
+
1590
+ /**
1591
+ * Get declarations from map and nested rulesets.
1592
+ * This will return a list of all matching nodes.
1593
+ *
1594
+ * @todo - The pattern for mixins will be similar, no? Can this be
1595
+ * re-used / abstracted?
1596
+ *
1597
+ * @todo - Register declarations and index them only when searching.
1598
+ * This would be similar to how we index rulesets for extending.
1599
+ */
1600
+ override find(
1601
+ key: string,
1602
+ filterType: 'VarDeclaration' | 'Declaration' = 'VarDeclaration',
1603
+ options?: FindOptions
1604
+ ): Declaration | undefined {
1605
+ const candidateContexts = options?.candidateContexts ?? new WeakMap<Node, Context>();
1606
+ const getCandidateIdentity = (node: Declaration): Node => node.sourceNode ?? node;
1607
+ const getDeclarationCandidateScore = (node: Declaration, activeRules: Rules): [number, number, number, number] => {
1608
+ const activeRenderKey = context?.renderKey ?? activeRules.renderKey;
1609
+ const nonCanonicalParentEdgeKeys = node.parentEdges
1610
+ ? [...node.parentEdges.keys()].filter(key => key !== CANONICAL && key !== CALLER)
1611
+ : [];
1612
+ const matchesActiveKey = activeRenderKey !== undefined && nonCanonicalParentEdgeKeys.includes(activeRenderKey) ? 1 : 0;
1613
+ const isDerived = node !== getCandidateIdentity(node) ? 1 : 0;
1614
+ const isPreEvaluated = node.preEvaluated ? 1 : 0;
1615
+ const isEvaluated = node.evaluated ? 1 : 0;
1616
+ return [matchesActiveKey, isDerived, isPreEvaluated, isEvaluated];
1617
+ };
1618
+ const shouldReplaceCandidate = (existing: Declaration, next: Declaration, activeRules: Rules): boolean => {
1619
+ const existingScore = getDeclarationCandidateScore(existing, activeRules);
1620
+ const nextScore = getDeclarationCandidateScore(next, activeRules);
1621
+ for (let i = 0; i < existingScore.length; i++) {
1622
+ if (nextScore[i]! > existingScore[i]!) {
1623
+ return true;
1624
+ }
1625
+ if (nextScore[i]! < existingScore[i]!) {
1626
+ return false;
1627
+ }
1628
+ }
1629
+ return false;
1630
+ };
1631
+ const rememberCandidateContext = (node: Declaration, activeRules: Rules): void => {
1632
+ if (!context) {
1633
+ return;
1634
+ }
1635
+ setParent(node, activeRules, context);
1636
+ candidateContexts.set(node, {
1637
+ ...context,
1638
+ rulesContext: activeRules,
1639
+ renderKey: context.renderKey ?? activeRules.renderKey
1640
+ });
1641
+ };
1642
+ const getCandidateContext = (node: Declaration, activeRules: Rules): Context | undefined => {
1643
+ return candidateContexts.get(node) ?? (
1644
+ context
1645
+ ? {
1646
+ ...context,
1647
+ rulesContext: activeRules
1648
+ }
1649
+ : context
1650
+ );
1651
+ };
1652
+ const getDeclarationOrderPath = (node: Declaration, boundaryRules: Rules): number[] | undefined => {
1653
+ const path: number[] = [];
1654
+ let current: Node | undefined = node;
1655
+ const nodeContext = getCandidateContext(node, boundaryRules);
1656
+ while (current) {
1657
+ const parent = nodeContext ? getParent(current, nodeContext) : current.parent;
1658
+ if (!parent) {
1659
+ return undefined;
1660
+ }
1661
+ if (current.index === undefined) {
1662
+ return undefined;
1663
+ }
1664
+ path.unshift(current.index);
1665
+ if (parent === boundaryRules) {
1666
+ return path;
1667
+ }
1668
+ if (!isNode(parent, N.Rules)) {
1669
+ return undefined;
1670
+ }
1671
+ current = parent;
1672
+ }
1673
+ return undefined;
1674
+ };
1675
+ const compareDeclarationsForLookup = (a: Declaration, b: Declaration): number => {
1676
+ const aPath = getDeclarationOrderPath(a, rules);
1677
+ const bPath = getDeclarationOrderPath(b, rules);
1678
+ if (aPath && bPath) {
1679
+ const length = Math.min(aPath.length, bPath.length);
1680
+ for (let i = 0; i < length; i++) {
1681
+ const diff = aPath[i]! - bPath[i]!;
1682
+ if (diff !== 0) {
1683
+ return diff;
1684
+ }
1685
+ }
1686
+ if (aPath.length !== bPath.length) {
1687
+ return aPath.length - bPath.length;
1688
+ }
1689
+ }
1690
+ const pos = comparePosition(a, b);
1691
+ return pos ?? 0;
1692
+ };
1693
+ let declCandidate = new Set<Declaration>();
1694
+ let optionalCandidates = options?.optionalCandidates ?? new Set<Declaration>();
1695
+ let rules: Rules | undefined = this.rules;
1696
+ let isPublic = false;
1697
+ let {
1698
+ searchParents = true,
1699
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1700
+ local = false,
1701
+ start,
1702
+ context
1703
+ } = options ?? {};
1704
+ if (options) {
1705
+ options.candidateContexts = candidateContexts;
1706
+ }
1707
+
1708
+ let newReadonly: boolean | undefined = false;
1709
+ let searchChildrenOptions: FindOptions | undefined;
1710
+ // Track visited Rules nodes in the parent chain to detect circular parent chains
1711
+ const visitedRules = new Set<Rules>();
1712
+ while (rules) {
1713
+ // CRITICAL: Check for circular parent chain
1714
+ if (visitedRules.has(rules)) {
1715
+ throw new Error(`Circular parent chain detected in DeclarationRegistry.find`);
1716
+ }
1717
+ visitedRules.add(rules);
1718
+ let currentReadonly = options?.readonly || rules.options.readonly;
1719
+ newReadonly = currentReadonly;
1720
+ const invocationBinding = filterType === 'VarDeclaration'
1721
+ ? rules.getInvocationBinding(key, context)
1722
+ : undefined;
1723
+ if (invocationBinding && (!options?.filter || options.filter(invocationBinding))) {
1724
+ rememberCandidateContext(invocationBinding, rules);
1725
+ newReadonly ||= invocationBinding.options.readonly;
1726
+ if (options && newReadonly) {
1727
+ options.readonly = true;
1728
+ }
1729
+ return invocationBinding;
1730
+ }
1731
+ const registry = rules.getRegistry('declaration', context);
1732
+ registry?.indexPendingItems();
1733
+ let list: Declaration[] | undefined;
1734
+ const filter = options?.filter;
1735
+ const indexSet = registry?.index.get(key);
1736
+ if (indexSet) {
1737
+ const deduped = new Map<Node, Declaration>();
1738
+ for (const n of indexSet) {
1739
+ if (n.type === filterType && (!filter || filter(n))) {
1740
+ const identity = getCandidateIdentity(n);
1741
+ const existing = deduped.get(identity);
1742
+ if (!existing || shouldReplaceCandidate(existing, n, rules)) {
1743
+ deduped.set(identity, n);
1744
+ }
1745
+ }
1746
+ }
1747
+ for (const n of deduped.values()) {
1748
+ rememberCandidateContext(n, rules);
1749
+ (list ??= []).push(n);
1750
+ }
1751
+ }
1752
+ if (list) {
1753
+ if (list.length > 1) {
1754
+ list.sort(compareDeclarationsForLookup);
1755
+ }
1756
+ let result = registry._findClosestByStart(list, start);
1757
+ if (result) {
1758
+ rememberCandidateContext(result, rules);
1759
+ newReadonly ||= result.options.readonly;
1760
+ // Visibility determines how declarations are found:
1761
+ // - 'private': only visible from INSIDE (children looking up) or same scope,
1762
+ // NOT from outside looking in (child Rules searches).
1763
+ // - 'optional': fallback only — returned if no public match is found.
1764
+ // - 'public': immediate candidate.
1765
+ //
1766
+ // IMPORTANT: Walking UP the parent chain is always an "inside" lookup — the
1767
+ // search originates from a descendant of this scope, so private does NOT block.
1768
+ // Private only blocks _searchRulesChildren (outside looking in).
1769
+ const currentRulesVisibility = rules.options.rulesVisibility?.[filterType] ?? '';
1770
+ const currentRulesOwner = getCurrentParentNode(rules, context);
1771
+ const shouldPreferLexicalVar = (
1772
+ filterType === 'VarDeclaration'
1773
+ && (
1774
+ currentRulesVisibility !== 'optional'
1775
+ || currentRulesOwner?.type === 'Ruleset'
1776
+ )
1777
+ );
1778
+ if (shouldPreferLexicalVar) {
1779
+ if (options) {
1780
+ options.readonly ||= newReadonly;
1781
+ }
1782
+ return result;
1783
+ }
1784
+ if (currentRulesVisibility === 'optional') {
1785
+ optionalCandidates.add(result);
1786
+ } else {
1787
+ declCandidate.add(result);
1788
+ isPublic = true;
1789
+ }
1790
+ }
1791
+ }
1792
+ // Initialize searchedRules to prevent infinite recursion when searching child Rules
1793
+ // This is critical: if a Rules node appears in its own children, we need to track it
1794
+ const searchedRules = options?.searchedRules ?? new Set<Rules>();
1795
+ if (!searchedRules.has(rules)) {
1796
+ searchedRules.add(rules);
1797
+ }
1798
+ // CRITICAL: When searching children, we MUST set searchParents: false to prevent
1799
+ // Reuse a single child options object — update mutable fields per iteration
1800
+ // instead of spreading a new object every loop
1801
+ if (!searchChildrenOptions) {
1802
+ searchChildrenOptions = options
1803
+ ? {
1804
+ ...options,
1805
+ searchParents: false,
1806
+ readonly: newReadonly,
1807
+ candidates: declCandidate,
1808
+ searchedRules: searchedRules,
1809
+ optionalCandidates
1810
+ }
1811
+ : {
1812
+ searchParents: false,
1813
+ readonly: newReadonly,
1814
+ candidates: declCandidate,
1815
+ searchedRules: searchedRules,
1816
+ optionalCandidates
1817
+ };
1818
+ } else {
1819
+ searchChildrenOptions.readonly = newReadonly;
1820
+ }
1821
+ rules.getRegistry('declaration', context)._searchRulesChildren(key, filterType, searchChildrenOptions);
1822
+
1823
+ // After searching the CURRENT scope (index + children), if we found public declarations,
1824
+ // sort them, find the best one (closest to start or at bottom), and return immediately.
1825
+ // Otherwise, continue up the parent scope.
1826
+ if (declCandidate.size > 0) {
1827
+ let bestResult: Declaration | undefined;
1828
+ // Use comparePosition to find the last declaration by source order
1829
+ const candidateArray = Array.from(declCandidate);
1830
+ if (candidateArray.length === 1) {
1831
+ bestResult = candidateArray[0];
1832
+ } else {
1833
+ candidateArray.sort((a, b) => {
1834
+ const order = compareDeclarationsForLookup(a, b);
1835
+ if (order !== 0) {
1836
+ return order;
1837
+ }
1838
+ const aContext = getCandidateContext(a, rules);
1839
+ const bContext = getCandidateContext(b, rules);
1840
+ const aDirect = (aContext ? getParent(a, aContext) : a.parent) === rules;
1841
+ const bDirect = (bContext ? getParent(b, bContext) : b.parent) === rules;
1842
+ if (aDirect !== bDirect) {
1843
+ return aDirect ? 1 : -1;
1844
+ }
1845
+ return 0;
1846
+ });
1847
+ bestResult = candidateArray[candidateArray.length - 1];
1848
+ }
1849
+ if (options && searchChildrenOptions?.readonly) {
1850
+ options.readonly = true;
1851
+ }
1852
+ return bestResult;
1853
+ }
1854
+
1855
+ // If we haven't found public candidates in the current scope, continue normal parent search
1856
+ // (optional candidates are tracked but we keep searching up the parent chain)
1857
+ if (isNonImportStyleBoundary(rules)) {
1858
+ searchParents = false;
1859
+ }
1860
+ if (isPublic || !searchParents) {
1861
+ if (options && searchChildrenOptions?.readonly) {
1862
+ options.readonly = true;
1863
+ }
1864
+ const result = declCandidate.values().next().value;
1865
+ return result;
1866
+ }
1867
+
1868
+ do {
1869
+ rules = rules?.getRegistryParent(context);
1870
+ } while (rules && rules.type !== 'Rules');
1871
+ // The start constraint only applies within the originating scope.
1872
+ // When walking up to a parent scope, drop it so declarations at any
1873
+ // position in the parent are eligible.
1874
+ start = undefined;
1875
+ }
1876
+ if (options && newReadonly) {
1877
+ options.readonly = true;
1878
+ }
1879
+ // After searching all parents, if we only have optional candidates, return the best one
1880
+ if (declCandidate.size === 0 && optionalCandidates.size > 0) {
1881
+ const optionalArray = Array.from(optionalCandidates);
1882
+ if (optionalArray.length === 1) {
1883
+ return optionalArray[0];
1884
+ }
1885
+ optionalArray.sort(compareDeclarationsForLookup);
1886
+ const optionalResult = optionalArray[optionalArray.length - 1];
1887
+ return optionalResult;
1888
+ }
1889
+ return declCandidate.values().next().value;
1890
+ }
1891
+ }
1892
+
1893
+ export function getDirectDeclarationsByKey(
1894
+ rules: Rules,
1895
+ key: string | undefined,
1896
+ context?: Context
1897
+ ): Declaration[] {
1898
+ const children = rules.getRegistryChildren(context);
1899
+ const matches: Declaration[] = [];
1900
+ for (const child of children) {
1901
+ if (!isNode(child, N.Declaration | N.VarDeclaration)) {
1902
+ continue;
1903
+ }
1904
+ const name = child.get('name', context);
1905
+ if (key === undefined || name?.valueOf?.() === key) {
1906
+ matches.push(child);
1907
+ }
1908
+ }
1909
+ return matches;
1910
+ }
1911
+
1912
+ function arraysEqual(a: string[], b: string[]) {
1913
+ if (a.length !== b.length) {
1914
+ return false;
1915
+ }
1916
+ for (let i = 0; i < a.length; i++) {
1917
+ if (a[i] !== b[i]) {
1918
+ return false;
1919
+ }
1920
+ }
1921
+ return true;
1922
+ }
1923
+
1924
+ /**
1925
+ * Does `a` contain all elements of `b`? (order-independent)
1926
+ *
1927
+ * Uses linear scan instead of `Set.prototype.isSubsetOf` because
1928
+ * selector key arrays are typically 1–3 elements, where the overhead
1929
+ * of allocating a Set dominates.
1930
+ */
1931
+ function arrayContainsAll(a: string[], b: string[]): boolean {
1932
+ if (b.length > a.length) {
1933
+ return false;
1934
+ }
1935
+ if (b.length === 0) {
1936
+ return true;
1937
+ }
1938
+ // For small arrays, just use includes
1939
+ for (const item of b) {
1940
+ if (!a.includes(item)) {
1941
+ return false;
1942
+ }
1943
+ }
1944
+ return true;
1945
+ }
1946
+
1947
+ /** Are `a` and `b` equal as unordered sets? See {@link arrayContainsAll}. */
1948
+ function arraysEqualAsSet(a: string[], b: string[]): boolean {
1949
+ if (a.length !== b.length) {
1950
+ return false;
1951
+ }
1952
+ return arrayContainsAll(a, b);
1953
+ }