@jesscss/core 2.0.0-alpha.1

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 (423) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -0
  3. package/lib/context.d.ts +352 -0
  4. package/lib/context.d.ts.map +1 -0
  5. package/lib/context.js +636 -0
  6. package/lib/context.js.map +1 -0
  7. package/lib/conversions.d.ts +73 -0
  8. package/lib/conversions.d.ts.map +1 -0
  9. package/lib/conversions.js +253 -0
  10. package/lib/conversions.js.map +1 -0
  11. package/lib/debug-log.d.ts +2 -0
  12. package/lib/debug-log.d.ts.map +1 -0
  13. package/lib/debug-log.js +27 -0
  14. package/lib/debug-log.js.map +1 -0
  15. package/lib/define-function.d.ts +587 -0
  16. package/lib/define-function.d.ts.map +1 -0
  17. package/lib/define-function.js +726 -0
  18. package/lib/define-function.js.map +1 -0
  19. package/lib/deprecation.d.ts +34 -0
  20. package/lib/deprecation.d.ts.map +1 -0
  21. package/lib/deprecation.js +57 -0
  22. package/lib/deprecation.js.map +1 -0
  23. package/lib/index.d.ts +22 -0
  24. package/lib/index.d.ts.map +1 -0
  25. package/lib/index.js +23 -0
  26. package/lib/index.js.map +1 -0
  27. package/lib/jess-error.d.ts +343 -0
  28. package/lib/jess-error.d.ts.map +1 -0
  29. package/lib/jess-error.js +508 -0
  30. package/lib/jess-error.js.map +1 -0
  31. package/lib/logger/deprecation-processing.d.ts +41 -0
  32. package/lib/logger/deprecation-processing.d.ts.map +1 -0
  33. package/lib/logger/deprecation-processing.js +81 -0
  34. package/lib/logger/deprecation-processing.js.map +1 -0
  35. package/lib/logger.d.ts +10 -0
  36. package/lib/logger.d.ts.map +1 -0
  37. package/lib/logger.js +20 -0
  38. package/lib/logger.js.map +1 -0
  39. package/lib/plugin.d.ts +94 -0
  40. package/lib/plugin.d.ts.map +1 -0
  41. package/lib/plugin.js +174 -0
  42. package/lib/plugin.js.map +1 -0
  43. package/lib/tree/ampersand.d.ts +94 -0
  44. package/lib/tree/ampersand.d.ts.map +1 -0
  45. package/lib/tree/ampersand.js +269 -0
  46. package/lib/tree/ampersand.js.map +1 -0
  47. package/lib/tree/any.d.ts +58 -0
  48. package/lib/tree/any.d.ts.map +1 -0
  49. package/lib/tree/any.js +101 -0
  50. package/lib/tree/any.js.map +1 -0
  51. package/lib/tree/at-rule.d.ts +53 -0
  52. package/lib/tree/at-rule.d.ts.map +1 -0
  53. package/lib/tree/at-rule.js +503 -0
  54. package/lib/tree/at-rule.js.map +1 -0
  55. package/lib/tree/block.d.ts +22 -0
  56. package/lib/tree/block.d.ts.map +1 -0
  57. package/lib/tree/block.js +24 -0
  58. package/lib/tree/block.js.map +1 -0
  59. package/lib/tree/bool.d.ts +17 -0
  60. package/lib/tree/bool.d.ts.map +1 -0
  61. package/lib/tree/bool.js +24 -0
  62. package/lib/tree/bool.js.map +1 -0
  63. package/lib/tree/call.d.ts +66 -0
  64. package/lib/tree/call.d.ts.map +1 -0
  65. package/lib/tree/call.js +306 -0
  66. package/lib/tree/call.js.map +1 -0
  67. package/lib/tree/collection.d.ts +30 -0
  68. package/lib/tree/collection.d.ts.map +1 -0
  69. package/lib/tree/collection.js +37 -0
  70. package/lib/tree/collection.js.map +1 -0
  71. package/lib/tree/color.d.ts +101 -0
  72. package/lib/tree/color.d.ts.map +1 -0
  73. package/lib/tree/color.js +513 -0
  74. package/lib/tree/color.js.map +1 -0
  75. package/lib/tree/combinator.d.ts +12 -0
  76. package/lib/tree/combinator.d.ts.map +1 -0
  77. package/lib/tree/combinator.js +8 -0
  78. package/lib/tree/combinator.js.map +1 -0
  79. package/lib/tree/comment.d.ts +20 -0
  80. package/lib/tree/comment.d.ts.map +1 -0
  81. package/lib/tree/comment.js +18 -0
  82. package/lib/tree/comment.js.map +1 -0
  83. package/lib/tree/condition.d.ts +31 -0
  84. package/lib/tree/condition.d.ts.map +1 -0
  85. package/lib/tree/condition.js +103 -0
  86. package/lib/tree/condition.js.map +1 -0
  87. package/lib/tree/control.d.ts +104 -0
  88. package/lib/tree/control.d.ts.map +1 -0
  89. package/lib/tree/control.js +430 -0
  90. package/lib/tree/control.js.map +1 -0
  91. package/lib/tree/declaration-custom.d.ts +18 -0
  92. package/lib/tree/declaration-custom.d.ts.map +1 -0
  93. package/lib/tree/declaration-custom.js +24 -0
  94. package/lib/tree/declaration-custom.js.map +1 -0
  95. package/lib/tree/declaration-var.d.ts +36 -0
  96. package/lib/tree/declaration-var.d.ts.map +1 -0
  97. package/lib/tree/declaration-var.js +63 -0
  98. package/lib/tree/declaration-var.js.map +1 -0
  99. package/lib/tree/declaration.d.ts +78 -0
  100. package/lib/tree/declaration.d.ts.map +1 -0
  101. package/lib/tree/declaration.js +289 -0
  102. package/lib/tree/declaration.js.map +1 -0
  103. package/lib/tree/default-guard.d.ts +15 -0
  104. package/lib/tree/default-guard.d.ts.map +1 -0
  105. package/lib/tree/default-guard.js +19 -0
  106. package/lib/tree/default-guard.js.map +1 -0
  107. package/lib/tree/dimension.d.ts +33 -0
  108. package/lib/tree/dimension.d.ts.map +1 -0
  109. package/lib/tree/dimension.js +291 -0
  110. package/lib/tree/dimension.js.map +1 -0
  111. package/lib/tree/expression.d.ts +24 -0
  112. package/lib/tree/expression.d.ts.map +1 -0
  113. package/lib/tree/expression.js +28 -0
  114. package/lib/tree/expression.js.map +1 -0
  115. package/lib/tree/extend-list.d.ts +23 -0
  116. package/lib/tree/extend-list.d.ts.map +1 -0
  117. package/lib/tree/extend-list.js +20 -0
  118. package/lib/tree/extend-list.js.map +1 -0
  119. package/lib/tree/extend.d.ts +47 -0
  120. package/lib/tree/extend.d.ts.map +1 -0
  121. package/lib/tree/extend.js +292 -0
  122. package/lib/tree/extend.js.map +1 -0
  123. package/lib/tree/function.d.ts +48 -0
  124. package/lib/tree/function.d.ts.map +1 -0
  125. package/lib/tree/function.js +74 -0
  126. package/lib/tree/function.js.map +1 -0
  127. package/lib/tree/import-js.d.ts +35 -0
  128. package/lib/tree/import-js.d.ts.map +1 -0
  129. package/lib/tree/import-js.js +45 -0
  130. package/lib/tree/import-js.js.map +1 -0
  131. package/lib/tree/import-style.d.ts +156 -0
  132. package/lib/tree/import-style.d.ts.map +1 -0
  133. package/lib/tree/import-style.js +556 -0
  134. package/lib/tree/import-style.js.map +1 -0
  135. package/lib/tree/index.d.ts +71 -0
  136. package/lib/tree/index.d.ts.map +1 -0
  137. package/lib/tree/index.js +95 -0
  138. package/lib/tree/index.js.map +1 -0
  139. package/lib/tree/interpolated-reference.d.ts +24 -0
  140. package/lib/tree/interpolated-reference.d.ts.map +1 -0
  141. package/lib/tree/interpolated-reference.js +37 -0
  142. package/lib/tree/interpolated-reference.js.map +1 -0
  143. package/lib/tree/interpolated.d.ts +62 -0
  144. package/lib/tree/interpolated.d.ts.map +1 -0
  145. package/lib/tree/interpolated.js +204 -0
  146. package/lib/tree/interpolated.js.map +1 -0
  147. package/lib/tree/js-array.d.ts +10 -0
  148. package/lib/tree/js-array.d.ts.map +1 -0
  149. package/lib/tree/js-array.js +10 -0
  150. package/lib/tree/js-array.js.map +1 -0
  151. package/lib/tree/js-expr.d.ts +23 -0
  152. package/lib/tree/js-expr.d.ts.map +1 -0
  153. package/lib/tree/js-expr.js +28 -0
  154. package/lib/tree/js-expr.js.map +1 -0
  155. package/lib/tree/js-function.d.ts +20 -0
  156. package/lib/tree/js-function.d.ts.map +1 -0
  157. package/lib/tree/js-function.js +16 -0
  158. package/lib/tree/js-function.js.map +1 -0
  159. package/lib/tree/js-object.d.ts +10 -0
  160. package/lib/tree/js-object.d.ts.map +1 -0
  161. package/lib/tree/js-object.js +10 -0
  162. package/lib/tree/js-object.js.map +1 -0
  163. package/lib/tree/list.d.ts +38 -0
  164. package/lib/tree/list.d.ts.map +1 -0
  165. package/lib/tree/list.js +83 -0
  166. package/lib/tree/list.js.map +1 -0
  167. package/lib/tree/log.d.ts +29 -0
  168. package/lib/tree/log.d.ts.map +1 -0
  169. package/lib/tree/log.js +56 -0
  170. package/lib/tree/log.js.map +1 -0
  171. package/lib/tree/mixin.d.ts +87 -0
  172. package/lib/tree/mixin.d.ts.map +1 -0
  173. package/lib/tree/mixin.js +112 -0
  174. package/lib/tree/mixin.js.map +1 -0
  175. package/lib/tree/negative.d.ts +17 -0
  176. package/lib/tree/negative.d.ts.map +1 -0
  177. package/lib/tree/negative.js +22 -0
  178. package/lib/tree/negative.js.map +1 -0
  179. package/lib/tree/nil.d.ts +31 -0
  180. package/lib/tree/nil.d.ts.map +1 -0
  181. package/lib/tree/nil.js +36 -0
  182. package/lib/tree/nil.js.map +1 -0
  183. package/lib/tree/node-base.d.ts +359 -0
  184. package/lib/tree/node-base.d.ts.map +1 -0
  185. package/lib/tree/node-base.js +884 -0
  186. package/lib/tree/node-base.js.map +1 -0
  187. package/lib/tree/node.d.ts +10 -0
  188. package/lib/tree/node.d.ts.map +1 -0
  189. package/lib/tree/node.js +45 -0
  190. package/lib/tree/node.js.map +1 -0
  191. package/lib/tree/number.d.ts +21 -0
  192. package/lib/tree/number.d.ts.map +1 -0
  193. package/lib/tree/number.js +27 -0
  194. package/lib/tree/number.js.map +1 -0
  195. package/lib/tree/operation.d.ts +26 -0
  196. package/lib/tree/operation.d.ts.map +1 -0
  197. package/lib/tree/operation.js +103 -0
  198. package/lib/tree/operation.js.map +1 -0
  199. package/lib/tree/paren.d.ts +18 -0
  200. package/lib/tree/paren.d.ts.map +1 -0
  201. package/lib/tree/paren.js +86 -0
  202. package/lib/tree/paren.js.map +1 -0
  203. package/lib/tree/query-condition.d.ts +17 -0
  204. package/lib/tree/query-condition.d.ts.map +1 -0
  205. package/lib/tree/query-condition.js +39 -0
  206. package/lib/tree/query-condition.js.map +1 -0
  207. package/lib/tree/quoted.d.ts +27 -0
  208. package/lib/tree/quoted.d.ts.map +1 -0
  209. package/lib/tree/quoted.js +66 -0
  210. package/lib/tree/quoted.js.map +1 -0
  211. package/lib/tree/range.d.ts +33 -0
  212. package/lib/tree/range.d.ts.map +1 -0
  213. package/lib/tree/range.js +47 -0
  214. package/lib/tree/range.js.map +1 -0
  215. package/lib/tree/reference.d.ts +76 -0
  216. package/lib/tree/reference.d.ts.map +1 -0
  217. package/lib/tree/reference.js +521 -0
  218. package/lib/tree/reference.js.map +1 -0
  219. package/lib/tree/rest.d.ts +15 -0
  220. package/lib/tree/rest.d.ts.map +1 -0
  221. package/lib/tree/rest.js +32 -0
  222. package/lib/tree/rest.js.map +1 -0
  223. package/lib/tree/rules-raw.d.ts +17 -0
  224. package/lib/tree/rules-raw.d.ts.map +1 -0
  225. package/lib/tree/rules-raw.js +37 -0
  226. package/lib/tree/rules-raw.js.map +1 -0
  227. package/lib/tree/rules.d.ts +255 -0
  228. package/lib/tree/rules.d.ts.map +1 -0
  229. package/lib/tree/rules.js +2293 -0
  230. package/lib/tree/rules.js.map +1 -0
  231. package/lib/tree/ruleset.d.ts +91 -0
  232. package/lib/tree/ruleset.d.ts.map +1 -0
  233. package/lib/tree/ruleset.js +506 -0
  234. package/lib/tree/ruleset.js.map +1 -0
  235. package/lib/tree/selector-attr.d.ts +31 -0
  236. package/lib/tree/selector-attr.d.ts.map +1 -0
  237. package/lib/tree/selector-attr.js +99 -0
  238. package/lib/tree/selector-attr.js.map +1 -0
  239. package/lib/tree/selector-basic.d.ts +23 -0
  240. package/lib/tree/selector-basic.d.ts.map +1 -0
  241. package/lib/tree/selector-basic.js +34 -0
  242. package/lib/tree/selector-basic.js.map +1 -0
  243. package/lib/tree/selector-capture.d.ts +23 -0
  244. package/lib/tree/selector-capture.d.ts.map +1 -0
  245. package/lib/tree/selector-capture.js +34 -0
  246. package/lib/tree/selector-capture.js.map +1 -0
  247. package/lib/tree/selector-complex.d.ts +40 -0
  248. package/lib/tree/selector-complex.d.ts.map +1 -0
  249. package/lib/tree/selector-complex.js +143 -0
  250. package/lib/tree/selector-complex.js.map +1 -0
  251. package/lib/tree/selector-compound.d.ts +16 -0
  252. package/lib/tree/selector-compound.d.ts.map +1 -0
  253. package/lib/tree/selector-compound.js +114 -0
  254. package/lib/tree/selector-compound.js.map +1 -0
  255. package/lib/tree/selector-interpolated.d.ts +23 -0
  256. package/lib/tree/selector-interpolated.d.ts.map +1 -0
  257. package/lib/tree/selector-interpolated.js +27 -0
  258. package/lib/tree/selector-interpolated.js.map +1 -0
  259. package/lib/tree/selector-list.d.ts +17 -0
  260. package/lib/tree/selector-list.d.ts.map +1 -0
  261. package/lib/tree/selector-list.js +184 -0
  262. package/lib/tree/selector-list.js.map +1 -0
  263. package/lib/tree/selector-pseudo.d.ts +42 -0
  264. package/lib/tree/selector-pseudo.d.ts.map +1 -0
  265. package/lib/tree/selector-pseudo.js +191 -0
  266. package/lib/tree/selector-pseudo.js.map +1 -0
  267. package/lib/tree/selector-simple.d.ts +5 -0
  268. package/lib/tree/selector-simple.d.ts.map +1 -0
  269. package/lib/tree/selector-simple.js +6 -0
  270. package/lib/tree/selector-simple.js.map +1 -0
  271. package/lib/tree/selector.d.ts +43 -0
  272. package/lib/tree/selector.d.ts.map +1 -0
  273. package/lib/tree/selector.js +56 -0
  274. package/lib/tree/selector.js.map +1 -0
  275. package/lib/tree/sequence.d.ts +43 -0
  276. package/lib/tree/sequence.d.ts.map +1 -0
  277. package/lib/tree/sequence.js +148 -0
  278. package/lib/tree/sequence.js.map +1 -0
  279. package/lib/tree/tree.d.ts +87 -0
  280. package/lib/tree/tree.d.ts.map +1 -0
  281. package/lib/tree/tree.js +2 -0
  282. package/lib/tree/tree.js.map +1 -0
  283. package/lib/tree/url.d.ts +18 -0
  284. package/lib/tree/url.d.ts.map +1 -0
  285. package/lib/tree/url.js +35 -0
  286. package/lib/tree/url.js.map +1 -0
  287. package/lib/tree/util/__tests__/debug-log.d.ts +1 -0
  288. package/lib/tree/util/__tests__/debug-log.d.ts.map +1 -0
  289. package/lib/tree/util/__tests__/debug-log.js +36 -0
  290. package/lib/tree/util/__tests__/debug-log.js.map +1 -0
  291. package/lib/tree/util/calculate.d.ts +3 -0
  292. package/lib/tree/util/calculate.d.ts.map +1 -0
  293. package/lib/tree/util/calculate.js +10 -0
  294. package/lib/tree/util/calculate.js.map +1 -0
  295. package/lib/tree/util/cast.d.ts +10 -0
  296. package/lib/tree/util/cast.d.ts.map +1 -0
  297. package/lib/tree/util/cast.js +87 -0
  298. package/lib/tree/util/cast.js.map +1 -0
  299. package/lib/tree/util/cloning.d.ts +4 -0
  300. package/lib/tree/util/cloning.d.ts.map +1 -0
  301. package/lib/tree/util/cloning.js +8 -0
  302. package/lib/tree/util/cloning.js.map +1 -0
  303. package/lib/tree/util/collections.d.ts +57 -0
  304. package/lib/tree/util/collections.d.ts.map +1 -0
  305. package/lib/tree/util/collections.js +136 -0
  306. package/lib/tree/util/collections.js.map +1 -0
  307. package/lib/tree/util/compare.d.ts +11 -0
  308. package/lib/tree/util/compare.d.ts.map +1 -0
  309. package/lib/tree/util/compare.js +89 -0
  310. package/lib/tree/util/compare.js.map +1 -0
  311. package/lib/tree/util/extend-helpers.d.ts +2 -0
  312. package/lib/tree/util/extend-helpers.d.ts.map +1 -0
  313. package/lib/tree/util/extend-helpers.js +2 -0
  314. package/lib/tree/util/extend-helpers.js.map +1 -0
  315. package/lib/tree/util/extend-roots.d.ts +37 -0
  316. package/lib/tree/util/extend-roots.d.ts.map +1 -0
  317. package/lib/tree/util/extend-roots.js +682 -0
  318. package/lib/tree/util/extend-roots.js.map +1 -0
  319. package/lib/tree/util/extend-roots.old.d.ts +132 -0
  320. package/lib/tree/util/extend-roots.old.d.ts.map +1 -0
  321. package/lib/tree/util/extend-roots.old.js +2272 -0
  322. package/lib/tree/util/extend-roots.old.js.map +1 -0
  323. package/lib/tree/util/extend-trace-debug.d.ts +13 -0
  324. package/lib/tree/util/extend-trace-debug.d.ts.map +1 -0
  325. package/lib/tree/util/extend-trace-debug.js +34 -0
  326. package/lib/tree/util/extend-trace-debug.js.map +1 -0
  327. package/lib/tree/util/extend.d.ts +218 -0
  328. package/lib/tree/util/extend.d.ts.map +1 -0
  329. package/lib/tree/util/extend.js +3033 -0
  330. package/lib/tree/util/extend.js.map +1 -0
  331. package/lib/tree/util/find-extendable-locations.d.ts +2 -0
  332. package/lib/tree/util/find-extendable-locations.d.ts.map +1 -0
  333. package/lib/tree/util/find-extendable-locations.js +2 -0
  334. package/lib/tree/util/find-extendable-locations.js.map +1 -0
  335. package/lib/tree/util/format.d.ts +20 -0
  336. package/lib/tree/util/format.d.ts.map +1 -0
  337. package/lib/tree/util/format.js +67 -0
  338. package/lib/tree/util/format.js.map +1 -0
  339. package/lib/tree/util/is-node.d.ts +13 -0
  340. package/lib/tree/util/is-node.d.ts.map +1 -0
  341. package/lib/tree/util/is-node.js +43 -0
  342. package/lib/tree/util/is-node.js.map +1 -0
  343. package/lib/tree/util/print.d.ts +80 -0
  344. package/lib/tree/util/print.d.ts.map +1 -0
  345. package/lib/tree/util/print.js +205 -0
  346. package/lib/tree/util/print.js.map +1 -0
  347. package/lib/tree/util/process-leading-is.d.ts +25 -0
  348. package/lib/tree/util/process-leading-is.d.ts.map +1 -0
  349. package/lib/tree/util/process-leading-is.js +364 -0
  350. package/lib/tree/util/process-leading-is.js.map +1 -0
  351. package/lib/tree/util/recursion-helper.d.ts +15 -0
  352. package/lib/tree/util/recursion-helper.d.ts.map +1 -0
  353. package/lib/tree/util/recursion-helper.js +43 -0
  354. package/lib/tree/util/recursion-helper.js.map +1 -0
  355. package/lib/tree/util/regex.d.ts +4 -0
  356. package/lib/tree/util/regex.d.ts.map +1 -0
  357. package/lib/tree/util/regex.js +4 -0
  358. package/lib/tree/util/regex.js.map +1 -0
  359. package/lib/tree/util/registry-utils.d.ts +192 -0
  360. package/lib/tree/util/registry-utils.d.ts.map +1 -0
  361. package/lib/tree/util/registry-utils.js +1242 -0
  362. package/lib/tree/util/registry-utils.js.map +1 -0
  363. package/lib/tree/util/ruleset-trace.d.ts +4 -0
  364. package/lib/tree/util/ruleset-trace.d.ts.map +1 -0
  365. package/lib/tree/util/ruleset-trace.js +14 -0
  366. package/lib/tree/util/ruleset-trace.js.map +1 -0
  367. package/lib/tree/util/selector-compare.d.ts +2 -0
  368. package/lib/tree/util/selector-compare.d.ts.map +1 -0
  369. package/lib/tree/util/selector-compare.js +2 -0
  370. package/lib/tree/util/selector-compare.js.map +1 -0
  371. package/lib/tree/util/selector-match-core.d.ts +171 -0
  372. package/lib/tree/util/selector-match-core.d.ts.map +1 -0
  373. package/lib/tree/util/selector-match-core.js +1578 -0
  374. package/lib/tree/util/selector-match-core.js.map +1 -0
  375. package/lib/tree/util/selector-utils.d.ts +30 -0
  376. package/lib/tree/util/selector-utils.d.ts.map +1 -0
  377. package/lib/tree/util/selector-utils.js +100 -0
  378. package/lib/tree/util/selector-utils.js.map +1 -0
  379. package/lib/tree/util/serialize-helper.d.ts +13 -0
  380. package/lib/tree/util/serialize-helper.d.ts.map +1 -0
  381. package/lib/tree/util/serialize-helper.js +387 -0
  382. package/lib/tree/util/serialize-helper.js.map +1 -0
  383. package/lib/tree/util/serialize-types.d.ts +9 -0
  384. package/lib/tree/util/serialize-types.d.ts.map +1 -0
  385. package/lib/tree/util/serialize-types.js +216 -0
  386. package/lib/tree/util/serialize-types.js.map +1 -0
  387. package/lib/tree/util/should-operate.d.ts +23 -0
  388. package/lib/tree/util/should-operate.d.ts.map +1 -0
  389. package/lib/tree/util/should-operate.js +46 -0
  390. package/lib/tree/util/should-operate.js.map +1 -0
  391. package/lib/tree/util/sourcemap.d.ts +7 -0
  392. package/lib/tree/util/sourcemap.d.ts.map +1 -0
  393. package/lib/tree/util/sourcemap.js +25 -0
  394. package/lib/tree/util/sourcemap.js.map +1 -0
  395. package/lib/types/config.d.ts +205 -0
  396. package/lib/types/config.d.ts.map +1 -0
  397. package/lib/types/config.js +2 -0
  398. package/lib/types/config.js.map +1 -0
  399. package/lib/types/index.d.ts +15 -0
  400. package/lib/types/index.d.ts.map +1 -0
  401. package/lib/types/index.js +3 -0
  402. package/lib/types/index.js.map +1 -0
  403. package/lib/types/modes.d.ts +24 -0
  404. package/lib/types/modes.d.ts.map +1 -0
  405. package/lib/types/modes.js +2 -0
  406. package/lib/types/modes.js.map +1 -0
  407. package/lib/types.d.ts +61 -0
  408. package/lib/types.d.ts.map +1 -0
  409. package/lib/types.js +2 -0
  410. package/lib/types.js.map +1 -0
  411. package/lib/use-webpack-resolver.d.ts +9 -0
  412. package/lib/use-webpack-resolver.d.ts.map +1 -0
  413. package/lib/use-webpack-resolver.js +41 -0
  414. package/lib/use-webpack-resolver.js.map +1 -0
  415. package/lib/visitor/index.d.ts +136 -0
  416. package/lib/visitor/index.d.ts.map +1 -0
  417. package/lib/visitor/index.js +135 -0
  418. package/lib/visitor/index.js.map +1 -0
  419. package/lib/visitor/less-visitor.d.ts +7 -0
  420. package/lib/visitor/less-visitor.d.ts.map +1 -0
  421. package/lib/visitor/less-visitor.js +7 -0
  422. package/lib/visitor/less-visitor.js.map +1 -0
  423. package/package.json +66 -0
@@ -0,0 +1,1242 @@
1
+ import { isNode } from './is-node.js';
2
+ import { Nil } from '../nil.js';
3
+ import { JsFunction } from '../js-function.js';
4
+ import { atIndex } from './collections.js';
5
+ import { comparePosition } from './compare.js';
6
+ const { isArray } = Array;
7
+ export class Registry {
8
+ rules;
9
+ pendingItems = new Set();
10
+ constructor(rules) {
11
+ this.rules = rules;
12
+ }
13
+ add(item) {
14
+ this.pendingItems.add(item);
15
+ }
16
+ indexPendingItems() {
17
+ for (const item of this.pendingItems) {
18
+ let key = String(item.value);
19
+ let set = this.index.get(key);
20
+ if (set && set instanceof Set) {
21
+ set.add(item);
22
+ }
23
+ else {
24
+ this.index.set(key, new Set([item]));
25
+ }
26
+ }
27
+ this.pendingItems.clear();
28
+ }
29
+ _searchRulesChildren(key, filterType, options = {}) {
30
+ let rules = this.rules;
31
+ // CRITICAL FIX: Initialize searchedRules if not provided, and add current Rules BEFORE any recursive calls
32
+ // The flaw in the original algorithm: when _searchRulesChildren calls childRules.find(), that creates
33
+ // a new search context via DeclarationRegistry.find(), which may not preserve searchedRules tracking.
34
+ // By initializing it here and adding the current Rules immediately, we ensure tracking persists.
35
+ const searchedRules = options?.searchedRules ?? new Set();
36
+ if (!searchedRules.has(rules)) {
37
+ searchedRules.add(rules);
38
+ }
39
+ let findType = filterType === 'Mixin' ? 'mixin' : 'declaration';
40
+ let findAll = Boolean(options.findAll);
41
+ let { candidates = new Set(), start, readonly, local, childFilterType, context } = options;
42
+ // childFilterType is the filterType to use when calling child Rules.find
43
+ // If not provided, use filterType (for backward compatibility with DeclarationRegistry)
44
+ // Note: childFilterType can be undefined to mean "don't filter" (accept both Mixin and Ruleset)
45
+ const actualChildFilterType = 'childFilterType' in options ? childFilterType : filterType;
46
+ let firstValue = candidates.values().next().value;
47
+ if (rules._rulesSet) {
48
+ let { rulesSet } = rules;
49
+ /**
50
+ * Only consider rules after the last found declaration (if relevant)
51
+ * and before the start position (if relevant)
52
+ */
53
+ rulesSet = rulesSet.filter((n) => {
54
+ // Check RulesEntry visibility first, then fall back to the actual Rules node's visibility
55
+ // Rules constructor sets defaults, so visibility should always be defined
56
+ const entryVisibility = n.rulesVisibility?.[filterType];
57
+ const nodeVisibility = n.node.options.rulesVisibility?.[filterType];
58
+ const visibility = entryVisibility ?? nodeVisibility;
59
+ const isMixinOutput = n.node.options?.isMixinOutput === true;
60
+ // Mixin output Rules should never participate in untargeted lookups.
61
+ // They are only searchable when the lookup has an explicit target.
62
+ // (This prevents mixin-call frame variables—including parameter bindings—from leaking.)
63
+ if (isMixinOutput) {
64
+ return options?.hasTarget === true;
65
+ }
66
+ // Otherwise, follow normal visibility rules
67
+ // Only 'public' and 'optional' are visible (not 'private' or undefined)
68
+ const isVisible = visibility === 'public' || visibility === 'optional';
69
+ /**
70
+ * Sass `@forward`:
71
+ * Forwarded Rules should not be visible to lookups within the current stylesheet scope
72
+ * (context.rulesContext === rules), but should remain visible to downstream consumers.
73
+ */
74
+ const isForwardNode = Boolean(n.node.options?.forward);
75
+ const skipForwardNode = isForwardNode && context?.rulesContext === rules;
76
+ // Local nodes can only be searched once - if we've already passed through
77
+ // a local boundary (local === true), we cannot search another local node
78
+ // This prevents re-exporting local variables to parent's parent
79
+ const isLocalNode = Boolean(n.node.options?.local);
80
+ const skipLocalNode = local && isLocalNode; // Skip if already in local context
81
+ // If we already have a candidate (firstValue exists), we should still search imported Rules
82
+ // to compare them and determine which was declared later.
83
+ // The original condition (findAll || !firstValue) was preventing comparison.
84
+ // We need to search imported Rules when:
85
+ // 1. findAll is true (search all)
86
+ // 2. firstValue doesn't exist (initial lookup - search imported Rules to find the variable)
87
+ // 3. firstValue exists AND candidates was explicitly passed with items (comparison context)
88
+ // This happens when DeclarationRegistry.find calls _searchRulesChildren after finding a local variable
89
+ // The key difference: if candidates was passed from DeclarationRegistry.find, it will have the local variable
90
+ // If candidates is empty or wasn't passed, we're in initial lookup mode
91
+ const isComparisonContext = firstValue && candidates.size > 0;
92
+ return (findAll || !firstValue || isComparisonContext)
93
+ && (start === undefined || n.node.index < start)
94
+ && !skipForwardNode
95
+ && !skipLocalNode
96
+ && isVisible;
97
+ });
98
+ let length = rulesSet.length;
99
+ if (length) {
100
+ // searchedRules is already initialized above and includes the current Rules
101
+ for (let i = length - 1; i >= 0; i--) {
102
+ let r = rulesSet.at(i);
103
+ // Skip if we've already searched this Rules node to prevent infinite recursion
104
+ if (searchedRules && searchedRules.has(r.node)) {
105
+ continue;
106
+ }
107
+ if (r.node === rules) {
108
+ throw new Error(`Rules node contains itself in rulesSet`);
109
+ }
110
+ /** Locals can be searched once but not twice */
111
+ let newLocal = local || Boolean(r.node.options?.local);
112
+ let newOpts = options ? { ...options, readonly: readonly || r.readonly } : { readonly: readonly || r.readonly };
113
+ newOpts.local = newLocal;
114
+ newOpts.start = undefined;
115
+ // _searchRulesChildren should never search parents - only search within imported Rules
116
+ newOpts.searchParents = false;
117
+ // Pass through searchedRules to prevent circular references
118
+ // searchedRules is always defined (initialized above)
119
+ newOpts.searchedRules = searchedRules;
120
+ if (context) {
121
+ newOpts.context = context;
122
+ }
123
+ // Use actualChildFilterType which may be undefined for mixin-ruleset lookups
124
+ // filterType parameter is used to SELECT registry, actualChildFilterType is used to FILTER results
125
+ let result = r.node.find(findType, key, actualChildFilterType, newOpts);
126
+ if (result) {
127
+ // Check if this Rules has optional visibility (from RulesEntry or the actual Rules node)
128
+ const entryVisibility = r.rulesVisibility?.[filterType];
129
+ const nodeVisibility = r.node.options.rulesVisibility?.[filterType];
130
+ const isOptional = entryVisibility === 'optional' || nodeVisibility === 'optional';
131
+ const optionalCandidates = options?.optionalCandidates;
132
+ /**
133
+ * If it's a public declaration, and it's the lower-most declaration,
134
+ * it wins.
135
+ * Rules constructor sets defaults, so visibility should always be defined.
136
+ */
137
+ const isPublic = entryVisibility === 'public' || nodeVisibility === 'public';
138
+ if (!findAll && isPublic) {
139
+ if (options && newOpts.readonly) {
140
+ options.readonly = true;
141
+ }
142
+ // Add to candidates and stop searching this rule
143
+ if (isArray(result)) {
144
+ for (const node of result) {
145
+ candidates.add(node);
146
+ }
147
+ }
148
+ else {
149
+ candidates.add(result);
150
+ }
151
+ break; // Stop searching this rule
152
+ }
153
+ /**
154
+ * If we're looking for a declaration and its optional OR
155
+ * we're looking for a mixin, then we need to keep searching.
156
+ */
157
+ options.readonly ||= newOpts.readonly;
158
+ if (isArray(result)) {
159
+ for (const node of result) {
160
+ if (isOptional && optionalCandidates) {
161
+ optionalCandidates.add(node);
162
+ }
163
+ else {
164
+ candidates.add(node);
165
+ }
166
+ }
167
+ }
168
+ else {
169
+ if (isOptional && optionalCandidates) {
170
+ optionalCandidates.add(result);
171
+ }
172
+ else {
173
+ candidates.add(result);
174
+ }
175
+ }
176
+ }
177
+ }
178
+ }
179
+ }
180
+ // REMOVED: Manual iteration through rules.value for child Rules nodes
181
+ // If a Rules node is in rules.value and should be searchable, it should be registered
182
+ // via registerNode() which adds it to rulesSet. We already search rulesSet above.
183
+ // Manually iterating through rules.value creates infinite loops when a Rules node
184
+ // appears in its own children, and is unnecessary since registered Rules are in rulesSet.
185
+ }
186
+ /**
187
+ * Find the closest declaration from start, in reverse order,
188
+ * using a binary search
189
+ */
190
+ _findClosestByStart(list, start) {
191
+ if (start === undefined) {
192
+ return atIndex(list, -1);
193
+ }
194
+ /**
195
+ * We do this so we start looking above the given position and don't
196
+ * return the current node.
197
+ */
198
+ start -= 1;
199
+ let bestMatch;
200
+ /** Binary search the queue to find a starting position */
201
+ let left = 0;
202
+ let right = list.length - 1;
203
+ while (left <= right) {
204
+ let mid = Math.floor((left + right) / 2);
205
+ let midVal = list.at(mid).index;
206
+ if (midVal === start) {
207
+ bestMatch = mid;
208
+ break;
209
+ }
210
+ if (midVal < start) {
211
+ bestMatch = mid;
212
+ left = mid + 1;
213
+ }
214
+ else {
215
+ right = mid - 1;
216
+ }
217
+ }
218
+ return bestMatch !== undefined ? list.at(bestMatch) : undefined;
219
+ }
220
+ _findByKey(candidates, key) {
221
+ let set = this.index.get(key);
222
+ if (set) {
223
+ let newSet;
224
+ if (set instanceof Set) {
225
+ newSet = set;
226
+ }
227
+ else if (isArray(set)) {
228
+ newSet = new Set(set.map(({ value }) => value));
229
+ }
230
+ else {
231
+ return set;
232
+ }
233
+ if (candidates) {
234
+ if (candidates instanceof Set) {
235
+ // Avoid Set.prototype.union (not available in our TS lib target)
236
+ for (const v of newSet) {
237
+ candidates.add(v);
238
+ }
239
+ }
240
+ else {
241
+ candidates = new Set([candidates, ...newSet]);
242
+ }
243
+ }
244
+ else {
245
+ candidates = newSet;
246
+ }
247
+ }
248
+ return candidates;
249
+ }
250
+ find(keys, _filterType, _options) {
251
+ this.indexPendingItems();
252
+ let candidates;
253
+ if (isArray(keys) || keys instanceof Set) {
254
+ for (const key of keys) {
255
+ candidates = this._findByKey(candidates, key);
256
+ }
257
+ }
258
+ else {
259
+ candidates = this._findByKey(candidates, keys);
260
+ }
261
+ if (candidates instanceof Set) {
262
+ return candidates.size ? [...candidates] : undefined;
263
+ }
264
+ return candidates;
265
+ }
266
+ }
267
+ /**
268
+ * Registry for fast selector-based ruleset lookups
269
+ */
270
+ export class RulesetRegistry extends Registry {
271
+ index = new Map();
272
+ /**
273
+ * Add a ruleset to be indexed later
274
+ */
275
+ add(ruleset) {
276
+ if (isNode(ruleset.value.selector, 'Selector')) {
277
+ this.pendingItems.add(ruleset);
278
+ }
279
+ }
280
+ /**
281
+ * Index any pending rulesets
282
+ * Override the base class method to use keySet-based indexing
283
+ */
284
+ indexPendingItems() {
285
+ const index = this.index;
286
+ for (const ruleset of this.pendingItems) {
287
+ /** Index using the ruleset's actual selector keySet - no need for getImplicitSelector here
288
+ * since we're indexing the selector as-is, not transforming it for parent context */
289
+ const selector = ruleset.selector;
290
+ if (selector instanceof Nil) {
291
+ continue;
292
+ }
293
+ if (!('keySet' in selector)) {
294
+ continue;
295
+ }
296
+ const keySet = selector.keySet;
297
+ for (const key of keySet) {
298
+ const existing = index.get(key);
299
+ if (existing) {
300
+ existing.add(ruleset);
301
+ }
302
+ else {
303
+ index.set(key, new Set([ruleset]));
304
+ }
305
+ }
306
+ }
307
+ this.pendingItems.clear();
308
+ }
309
+ /**
310
+ * Find candidate rulesets that might match the target selector.
311
+ * Searches only the local index - all rulesets should be registered
312
+ * to the extend root's registry during evaluation.
313
+ */
314
+ find(keys) {
315
+ // Index any pending rulesets first
316
+ this.indexPendingItems();
317
+ let candidates;
318
+ let rulesets;
319
+ /** Just get based on first key */
320
+ for (const key of keys) {
321
+ candidates = this.index.get(key);
322
+ break;
323
+ }
324
+ if (!candidates) {
325
+ return undefined;
326
+ }
327
+ /** Now find selectors that have all keys */
328
+ let keySet = keys instanceof Set ? keys : new Set(keys);
329
+ for (const c of candidates) {
330
+ let sel = c.selector;
331
+ if (!sel || isNode(sel, 'Nil')) {
332
+ continue;
333
+ }
334
+ // Avoid Set.prototype.isSubsetOf (not available in our TS lib target)
335
+ let isSubset = true;
336
+ for (const k of keySet) {
337
+ if (!sel.keySet.has(k)) {
338
+ isSubset = false;
339
+ break;
340
+ }
341
+ }
342
+ if (isSubset) {
343
+ (rulesets ??= []).push(c);
344
+ }
345
+ }
346
+ return rulesets;
347
+ }
348
+ }
349
+ /**
350
+ * The mixin registry works a little differently than the selector registry
351
+ * in these ways:
352
+ *
353
+ * 1. The mixin registry can only be indexed by basic element, class, and
354
+ * id selectors.
355
+ * 2. The index is the start key, not any key found in the selector.
356
+ * 3. '>' and ' ' combinators are ignored.
357
+ * 4. Initial ampersands (implicit or explicit) are ignored.
358
+ * 5. The mixin registry is local to the rules, whereas the selector registry
359
+ * is global to the file tree.
360
+ * 6. Rulesets and mixins without params will have their children searched
361
+ * if the first part matches.
362
+ */
363
+ export class MixinRegistry extends Registry {
364
+ index = new Map();
365
+ // private getSimpleKeyList(selector: Selector | Nil | undefined): string[] | undefined {
366
+ // let keyList: string[] | undefined;
367
+ // if (selector && 'keySet' in selector) {
368
+ // let passed = true;
369
+ // let foundBasic = false;
370
+ // for (const sel of selector.nodes()) {
371
+ // /** Ampersand is okay at start, but not after a basic selector */
372
+ // if (!foundBasic && isNode(sel, 'Ampersand')) {
373
+ // continue;
374
+ // }
375
+ // if (isNode(sel, 'Combinator')) {
376
+ // if (sel.value !== '>' && sel.value !== ' ') {
377
+ // passed = false;
378
+ // break;
379
+ // }
380
+ // continue;
381
+ // }
382
+ // /** Anything other than a universal selector is fine */
383
+ // if (isNode(sel, 'BasicSelector') && /^[^*]/.test(sel.value)) {
384
+ // (keyList ??= []).push(sel.valueOf() as string);
385
+ // foundBasic = true;
386
+ // continue;
387
+ // }
388
+ // if (isNode(sel, 'CompoundSelector') || isNode(sel, 'ComplexSelector')) {
389
+ // /** Might still be fine */
390
+ // continue;
391
+ // }
392
+ // /** Nothing else is valid, so fail */
393
+ // passed = false;
394
+ // break;
395
+ // }
396
+ // if (!passed) {
397
+ // return;
398
+ // }
399
+ // }
400
+ // return keyList;
401
+ // }
402
+ _indexSelectorStart(mixin, keySet) {
403
+ const index = this.index;
404
+ if (keySet?.size) {
405
+ // Keep `*` in the trailing match path so selectors like `.mixin > *` do not collide
406
+ // with plain `.mixin` mixin calls. Only pseudo keys are excluded from indexing.
407
+ const candidateKeys = Array.from(keySet).filter(key => typeof key === 'string' && !key.startsWith(':'));
408
+ const startIndex = candidateKeys.findIndex(key => !key.startsWith('*'));
409
+ if (startIndex === -1) {
410
+ return; // Only skip when there is no indexable key (e.g. :hover-only selector)
411
+ }
412
+ const startKey = candidateKeys[startIndex];
413
+ const rest = candidateKeys.slice(startIndex + 1);
414
+ const existing = index.get(startKey);
415
+ if (existing) {
416
+ existing.push({ value: mixin, match: rest });
417
+ }
418
+ else {
419
+ index.set(startKey, [{ value: mixin, match: rest }]);
420
+ }
421
+ }
422
+ }
423
+ indexPendingItems() {
424
+ for (const mixin of this.pendingItems) {
425
+ if (isNode(mixin, 'Ruleset')) {
426
+ // Use the ruleset's own selector, not the implicit selector with parent context
427
+ // This ensures nested rulesets are indexed by their local keys, not parent keys
428
+ // If the selector has been evaluated/flattened, use sourceNode which has the original
429
+ let selector = mixin.value.selector;
430
+ if (isNode(selector, 'Nil')) {
431
+ continue;
432
+ }
433
+ // `&` rulesets are structural nesting selectors, not callable mixins.
434
+ // Determine callability from ownSelector (before implicit selector resolution) when available.
435
+ const ownSelector = mixin.options?.ownSelector;
436
+ const callableSelector = ownSelector && !isNode(ownSelector, 'Nil') ? ownSelector : selector;
437
+ if (isNode(callableSelector, 'Ampersand')) {
438
+ continue;
439
+ }
440
+ // Prefer evaluated selector keys; they resolve interpolations (e.g. .@{a0} -> .\123).
441
+ // Fall back to source selector only when evaluated keys are empty.
442
+ const sourceSelector = selector.sourceNode;
443
+ const selectorToIndex = (selector.visibleKeySet?.size
444
+ ? selector
445
+ : (sourceSelector?.visibleKeySet?.size ? sourceSelector : selector));
446
+ let keySetToUse;
447
+ if (isNode(selectorToIndex, 'SelectorList')) {
448
+ /** Selector list's selectors are individually registered */
449
+ for (const sel of selectorToIndex.value) {
450
+ this._indexSelectorStart(mixin, sel.visibleKeySet);
451
+ }
452
+ keySetToUse = undefined; // already indexed above
453
+ }
454
+ else {
455
+ keySetToUse = selectorToIndex.visibleKeySet;
456
+ }
457
+ // Normalize nested `&...` selectors to local keys when possible.
458
+ // Evaluated key sets can include inherited parent keys (e.g. [".b",".bb",".foo-xxx",...]),
459
+ // but recursive lookup descends with local remainder keys (e.g. [".foo-xxx", ...]).
460
+ if (keySetToUse
461
+ && keySetToUse.size > 0
462
+ && ownSelector
463
+ && !isNode(ownSelector, 'Nil')) {
464
+ const ownSelectorText = String(ownSelector.valueOf?.() ?? '');
465
+ const ownKeys = Array.from(ownSelector.visibleKeySet ?? []);
466
+ const parentSelector = isNode(mixin.parent?.parent, 'Ruleset')
467
+ ? mixin.parent.parent.value.selector
468
+ : undefined;
469
+ const parentKeys = (parentSelector && !isNode(parentSelector, 'Nil')
470
+ ? Array.from(parentSelector.visibleKeySet ?? [])
471
+ : []);
472
+ if (parentKeys.length > 0
473
+ && ownKeys.length > parentKeys.length
474
+ && parentKeys.every((k, i) => ownKeys[i] === k)) {
475
+ keySetToUse = new Set(ownKeys.slice(parentKeys.length));
476
+ }
477
+ else if (ownKeys.length > 1 && ownSelectorText.trimStart().startsWith('&')) {
478
+ keySetToUse = new Set(ownKeys.slice(1));
479
+ }
480
+ }
481
+ // When the resolved selector is an Ampersand (implicit &), visibleKeySet is empty so we
482
+ // would not index. Use the ruleset's ownSelector (set in preEval before getImplicitSelector)
483
+ // to index by the callable selector that was explicitly authored.
484
+ if (keySetToUse !== undefined) {
485
+ if (keySetToUse.size === 0
486
+ && ownSelector
487
+ && !isNode(ownSelector, 'Nil')) {
488
+ const ownKeySet = ownSelector.visibleKeySet;
489
+ if (ownKeySet?.size) {
490
+ const ownKeys = Array.from(ownKeySet);
491
+ const selectorText = String(selectorToIndex.valueOf?.() ?? '');
492
+ // In nested `&...` rulesets, ownKeySet may include inherited parent key first.
493
+ // For local lookup chains we want the nested segment as the start key.
494
+ if (selectorText.startsWith('&') && ownKeys.length > 1) {
495
+ keySetToUse = new Set(ownKeys.slice(1));
496
+ }
497
+ else {
498
+ keySetToUse = ownKeySet;
499
+ }
500
+ }
501
+ }
502
+ this._indexSelectorStart(mixin, keySetToUse);
503
+ }
504
+ }
505
+ else {
506
+ this._indexSelectorStart(mixin, mixin.keySet);
507
+ }
508
+ }
509
+ this.pendingItems.clear();
510
+ }
511
+ /**
512
+ * Check if an entry matches the search criteria.
513
+ * Handles exact matches, partial matches (compound selector completion), and recursive searches.
514
+ * This consolidates the matching logic to avoid duplication.
515
+ */
516
+ /**
517
+ * Check if a Ruleset/Mixin matches a given array of keys using the same logic as the registry
518
+ * This uses the indexed match arrays (same as _checkEntryMatch) rather than direct selector comparison
519
+ * @param value The Ruleset or Mixin to check
520
+ * @param keys The array of keys to match against (e.g., [".jo", ".ki"])
521
+ * @returns true if the Ruleset/Mixin matches the keys using registry matching logic
522
+ */
523
+ checkRulesetMatchesKeys(value, keys) {
524
+ if (!keys || keys.length === 0) {
525
+ return false;
526
+ }
527
+ // Get the selector's keySet and extract indexable keys (same as _indexSelectorStart)
528
+ let indexableKeys = [];
529
+ if (isNode(value, 'Ruleset')) {
530
+ const selector = value.value.selector;
531
+ if (isNode(selector, 'Nil')) {
532
+ return false;
533
+ }
534
+ if (isNode(selector, 'SelectorList')) {
535
+ // For selector lists, check if any selector matches
536
+ return selector.value.some((sel) => {
537
+ const selKeys = Array.from(sel.keySet).filter(key => typeof key === 'string' && !key.startsWith('*') && !key.startsWith(':'));
538
+ if (selKeys.length === 0) {
539
+ return false;
540
+ }
541
+ // Check if keys appear in sequence in this selector's keys
542
+ return this._checkKeysSubsequence(selKeys, keys);
543
+ });
544
+ }
545
+ const keySet = selector.keySet;
546
+ if (!keySet || keySet.size === 0) {
547
+ return false;
548
+ }
549
+ indexableKeys = Array.from(keySet).filter((key) => {
550
+ return typeof key === 'string' && !key.startsWith('*') && !key.startsWith(':');
551
+ });
552
+ }
553
+ else {
554
+ const keySet = value.keySet;
555
+ if (!keySet || keySet.size === 0) {
556
+ return false;
557
+ }
558
+ indexableKeys = Array.from(keySet).filter((key) => {
559
+ return typeof key === 'string' && !key.startsWith('*') && !key.startsWith(':');
560
+ });
561
+ }
562
+ if (indexableKeys.length === 0) {
563
+ return false;
564
+ }
565
+ // Check if the provided keys appear in sequence in the selector's indexable keys
566
+ // The keySet should only contain keys from the Ruleset's own selector, not parent context
567
+ return this._checkKeysSubsequence(indexableKeys, keys);
568
+ }
569
+ /**
570
+ * Internal helper that checks if the provided keys appear in sequence within the selector's keys
571
+ *
572
+ * For compound selectors like `#header .milk .chips .jo.ki`, when we search for `.jo`, we get:
573
+ * - The full selector's indexable keys: `["#header", ".milk", ".chips", ".jo", ".ki"]`
574
+ * - When checking if accumulated keys `[".jo", ".ki"]` match, we check if they appear in sequence
575
+ */
576
+ _checkKeysSubsequence(selectorKeys, searchKeys) {
577
+ if (searchKeys.length === 0) {
578
+ return false;
579
+ }
580
+ // Check if searchKeys is a subsequence of selectorKeys (searchKeys appear in order)
581
+ let searchIndex = 0;
582
+ for (const selectorKey of selectorKeys) {
583
+ if (searchIndex < searchKeys.length && selectorKey === searchKeys[searchIndex]) {
584
+ searchIndex++;
585
+ }
586
+ }
587
+ const matches = searchIndex === searchKeys.length;
588
+ return matches;
589
+ }
590
+ /**
591
+ * Find candidate mixins (or rulesets, or both) that might match the target selector
592
+ *
593
+ * ...also...
594
+ *
595
+ * @todo - Not sure how recursion works here with the match overflow and returning
596
+ * proper arrays.
597
+ */
598
+ find(keys, filterType = undefined, options = {}) {
599
+ let keyList;
600
+ if (isArray(keys)) {
601
+ keyList = keys;
602
+ }
603
+ else {
604
+ keyList = [keys];
605
+ }
606
+ if (!keyList?.length) {
607
+ return;
608
+ }
609
+ let rules = this.rules;
610
+ let { searchParents = true, local = false, candidates = new Set(), context, hasTarget = false } = options ?? {};
611
+ const mixinHasNoRequiredParams = (mixinNode) => {
612
+ const params = mixinNode.value.params;
613
+ if (!params || params.length === 0) {
614
+ return true;
615
+ }
616
+ for (const param of params.value) {
617
+ if (isNode(param, 'Rest')) {
618
+ continue;
619
+ }
620
+ if (isNode(param, 'VarDeclaration')) {
621
+ if (param.value.value instanceof Nil) {
622
+ return false;
623
+ }
624
+ continue;
625
+ }
626
+ if (isNode(param, 'Any') && param.options.role === 'property') {
627
+ return false;
628
+ }
629
+ return false;
630
+ }
631
+ return true;
632
+ };
633
+ // Track which Rules nodes we've already searched to prevent infinite recursion
634
+ // Use the searchedRules from options if it exists, otherwise create a new Set
635
+ const searchedRules = options?.searchedRules || new Set();
636
+ if (options) {
637
+ options.searchedRules = searchedRules;
638
+ }
639
+ while (rules) {
640
+ // Don't add to searchedRules yet - we'll add it after we finish searching (including children)
641
+ let [startKey, ...search] = keyList;
642
+ let registry = rules.getRegistry('mixin');
643
+ registry.indexPendingItems();
644
+ const existing = registry.index.get(startKey);
645
+ // Resolve interpolated selector starts (e.g. "@{a2}") against current context
646
+ // so unresolved-index keys can still match resolved call keys (e.g. ".foo").
647
+ let resolvedInterpolatedStartEntries = [];
648
+ if (context && typeof startKey === 'string' && !existing?.length) {
649
+ for (const [indexedKey, indexedEntries] of registry.index) {
650
+ const matchInterpolated = /^@\{(.+)\}$/.exec(indexedKey);
651
+ if (!matchInterpolated) {
652
+ continue;
653
+ }
654
+ const varName = matchInterpolated[1];
655
+ const maybeVar = rules.find('declaration', varName, 'VarDeclaration', {
656
+ context,
657
+ hasTarget,
658
+ filter: options?.filter
659
+ });
660
+ if (isNode(maybeVar, 'VarDeclaration')) {
661
+ const resolvedValue = String(maybeVar.value.value.valueOf?.() ?? maybeVar.value.value ?? '');
662
+ if (resolvedValue === startKey) {
663
+ resolvedInterpolatedStartEntries.push(...indexedEntries);
664
+ }
665
+ }
666
+ }
667
+ }
668
+ // With the new indexing (by local visible keys), nested rulesets are indexed under their own keys
669
+ // So we only need to check entries under the startKey - no need to scan all entries
670
+ let allEntriesToCheck = [];
671
+ if (existing) {
672
+ allEntriesToCheck.push(...existing);
673
+ }
674
+ if (resolvedInterpolatedStartEntries.length > 0) {
675
+ allEntriesToCheck.push(...resolvedInterpolatedStartEntries);
676
+ }
677
+ // Also check if any entries match the full search path (for compound selectors like .foo.bar)
678
+ const targetMatch = search.length === 0 ? [startKey] : search;
679
+ for (const entries of registry.index.values()) {
680
+ for (const entry of entries) {
681
+ if (arraysEqual(entry.match, targetMatch)) {
682
+ allEntriesToCheck.push(entry);
683
+ }
684
+ }
685
+ }
686
+ if (allEntriesToCheck.length > 0) {
687
+ const targetMatch = search.length === 0 ? [startKey] : search;
688
+ for (const { value, match } of allEntriesToCheck) {
689
+ if (filterType && value.type !== filterType) {
690
+ continue;
691
+ }
692
+ // If match equals targetMatch (search or [startKey] when search is empty), this IS the ruleset we're looking for
693
+ // Also, if search is empty and match is empty, this ruleset IS the startKey we're looking for
694
+ // BUT: For compound search paths (keyList.length > 1), we should NOT add the startKey mixin itself
695
+ // as a candidate when search.length === 0 && match.length === 0, because that means we found the startKey
696
+ // but haven't fully matched the compound path. The startKey should only be added as a candidate if we're
697
+ // doing a simple lookup (keyList.length === 1), where the startKey IS the full match.
698
+ if (arraysEqual(match, targetMatch)) {
699
+ (candidates ??= new Set()).add(value);
700
+ continue;
701
+ }
702
+ // Only add startKey mixin as candidate if we're doing a simple lookup (not a compound path)
703
+ if (search.length === 0 && match.length === 0 && keyList.length === 1) {
704
+ (candidates ??= new Set()).add(value);
705
+ continue;
706
+ }
707
+ // For compound paths, we don't add startKey as a candidate, but we still need to search inside it
708
+ // The recursive search below will handle finding nested mixins
709
+ // If match equals [startKey] OR match is empty (meaning this ruleset IS the startKey),
710
+ // we need to search inside it for the remaining search keys
711
+ // NOTE: We should search inside #theme even if we're not adding it as a candidate (for compound paths)
712
+ if (search.length > 0 && (arraysEqual(match, [startKey]) || match.length === 0)) {
713
+ if ((isNode(value, 'Ruleset'))
714
+ || (isNode(value, 'Mixin') && mixinHasNoRequiredParams(value))) {
715
+ let subRules = value.value.rules;
716
+ const subMixinRegistry = subRules.getRegistry('mixin');
717
+ subMixinRegistry.indexPendingItems();
718
+ // With the new indexing, nested rulesets are indexed by their local visible keys
719
+ // So we can just do a normal recursive search - no need to check for matches ending with search
720
+ // When searching inside a nested ruleset with searchParents: false, we don't need searchedRules
721
+ // because we're not traversing the parent chain
722
+ subMixinRegistry.find(search, filterType, {
723
+ searchParents: false,
724
+ local,
725
+ candidates: candidates,
726
+ context,
727
+ filter: options?.filter,
728
+ hasTarget,
729
+ searchedRules: undefined // Not needed when searchParents is false
730
+ });
731
+ }
732
+ continue;
733
+ }
734
+ // If there are more search keys than match keys, recursively search inside this ruleset
735
+ // This handles cases where match is a prefix of search (e.g., match=[".foo"], search=[".foo", ".bar"])
736
+ // Or when match is empty (ruleset IS the startKey) and we need to search inside for the full search
737
+ const shouldRecurse = search.length > 0 && (search.length > match.length || match.length === 0);
738
+ const isPrefix = match.length > 0 && arraysEqual(match, search.slice(0, match.length));
739
+ if (shouldRecurse) {
740
+ let searchKeys;
741
+ if (match.length === 0) {
742
+ // Match is empty, meaning this ruleset IS the startKey, search inside for the full search
743
+ searchKeys = search;
744
+ }
745
+ else if (isPrefix) {
746
+ // Match is a prefix of search, search for the remainder after the match
747
+ searchKeys = search.slice(match.length);
748
+ }
749
+ else {
750
+ // Match is not a prefix of search - skip this ruleset, it doesn't match
751
+ continue;
752
+ }
753
+ if ((isNode(value, 'Ruleset'))
754
+ || (isNode(value, 'Mixin') && mixinHasNoRequiredParams(value))) {
755
+ let subRules = value.value.rules;
756
+ const subMixinRegistry = subRules.getRegistry('mixin');
757
+ subMixinRegistry.indexPendingItems();
758
+ subMixinRegistry.find(searchKeys, filterType, {
759
+ searchParents: false,
760
+ local,
761
+ candidates: candidates,
762
+ context,
763
+ filter: options?.filter,
764
+ hasTarget,
765
+ searchedRules: searchedRules
766
+ });
767
+ }
768
+ }
769
+ }
770
+ }
771
+ // Always search children (old behavior)
772
+ const candidatesBeforeChildren = candidates ? new Set(candidates) : new Set();
773
+ registry._searchRulesChildren(startKey, 'Mixin', {
774
+ searchParents: false,
775
+ local,
776
+ candidates: candidates,
777
+ findAll: true,
778
+ childFilterType: filterType,
779
+ context,
780
+ filter: options?.filter,
781
+ hasTarget,
782
+ searchedRules: searchedRules
783
+ });
784
+ // After _searchRulesChildren, check if any new candidates are mixins/rulesets we should search inside
785
+ // This handles the case where #theme mixin is found in imported Rules and we need to search inside it
786
+ // Also, for compound paths, remove #theme from candidates if it was added by _searchRulesChildren
787
+ // because we only want to search inside it, not include it as a final candidate
788
+ if (candidates) {
789
+ const candidatesToRemove = [];
790
+ for (const candidate of candidates) {
791
+ const candidateNode = candidate;
792
+ // Only check candidates that were added by _searchRulesChildren (not in allEntriesToCheck)
793
+ if (!candidatesBeforeChildren.has(candidateNode)) {
794
+ const isMixin = isNode(candidateNode, 'Mixin');
795
+ const isRuleset = isNode(candidateNode, 'Ruleset');
796
+ const hasNoParams = isMixin && mixinHasNoRequiredParams(candidateNode);
797
+ // Check if this candidate matches the startKey.
798
+ // For rulesets discovered via child-search, key-set membership is the reliable signal.
799
+ const candidateKey = isMixin
800
+ ? candidateNode.value.name?.valueOf?.()
801
+ : (isRuleset ? candidateNode.value.selector.valueOf?.() : '');
802
+ const matchesStartKey = isRuleset
803
+ ? ((!isNode(candidateNode.value.selector, 'Nil') && candidateNode.value.selector.visibleKeySet.has(startKey))
804
+ || (!isNode(candidateNode.value.selector, 'Nil') && candidateNode.value.selector.keySet.has(startKey)))
805
+ : candidateKey === startKey;
806
+ // For compound paths (keyList.length > 1), remove startKey from candidates if it was added by _searchRulesChildren
807
+ // because we only want to search inside it, not include it as a final candidate
808
+ if (matchesStartKey && keyList.length > 1) {
809
+ candidatesToRemove.push(candidateNode);
810
+ }
811
+ // Search inside the candidate if it matches startKey and we have remaining search keys
812
+ if (matchesStartKey && search.length > 0 && (isRuleset || hasNoParams)) {
813
+ let subRules = candidateNode.value.rules;
814
+ const subMixinRegistry = subRules.getRegistry('mixin');
815
+ subMixinRegistry.indexPendingItems();
816
+ subMixinRegistry.find(search, filterType, {
817
+ searchParents: false,
818
+ local,
819
+ candidates: candidates,
820
+ context,
821
+ filter: options?.filter,
822
+ hasTarget,
823
+ searchedRules: undefined // Not needed when searchParents is false
824
+ });
825
+ }
826
+ }
827
+ }
828
+ // Remove candidates that shouldn't be in the final result (for compound paths)
829
+ for (const candidateToRemove of candidatesToRemove) {
830
+ candidates.delete(candidateToRemove);
831
+ }
832
+ }
833
+ // Mark this Rules node as searched after we've finished searching it (including children)
834
+ searchedRules.add(rules);
835
+ if (!searchParents) {
836
+ break;
837
+ }
838
+ do {
839
+ rules = rules?.parent;
840
+ /**
841
+ * If we reach an import boundary, stop unless it's an `@import`
842
+ * which means these rules can reach into the parent file that imports
843
+ * this one.
844
+ */
845
+ if (rules && isNode(rules.sourceNode, 'StyleImport') && rules.sourceNode.options.type !== 'import') {
846
+ rules = undefined;
847
+ break;
848
+ }
849
+ } while (rules && rules.type !== 'Rules');
850
+ }
851
+ // With compound keys parsed as arrays (e.g., ['#theme', '.dark', '.navbar', '.colors']),
852
+ // we can find all matches in one pass. The find() method handles compound keys by
853
+ // recursively searching inside nested rulesets for the remaining keys.
854
+ return candidates.size ? [...candidates] : undefined;
855
+ }
856
+ }
857
+ /**
858
+ * For either Sass, Jess, or JS functions.
859
+ *
860
+ * Less and Sass can register global functions that can be called from the language
861
+ * without a `@-use` directive.
862
+ *
863
+ * @todo Should the presence of `@-use` directives anywhere in the
864
+ * stylesheet tree cause these global functions to be disabled?
865
+ */
866
+ export class FunctionRegistry extends Registry {
867
+ index = new Map();
868
+ cloneForRules(rules) {
869
+ const next = new FunctionRegistry(rules);
870
+ // Preserve any functions injected directly into the registry (Less plugin style).
871
+ next.index = new Map(this.index);
872
+ next.pendingItems = new Set(this.pendingItems);
873
+ return next;
874
+ }
875
+ indexPendingItems() {
876
+ for (const item of this.pendingItems) {
877
+ if (item instanceof JsFunction) {
878
+ this.index.set(item.name, item);
879
+ continue;
880
+ }
881
+ // Stylesheet-defined function node
882
+ const nameKey = item.nameKey;
883
+ if (nameKey) {
884
+ this.index.set(nameKey, item);
885
+ }
886
+ }
887
+ this.pendingItems.clear();
888
+ }
889
+ find(name, filterType, options) {
890
+ let fn;
891
+ let rules = this.rules;
892
+ let { searchParents = true } = options ?? {};
893
+ let findRoot = false;
894
+ while (rules) {
895
+ let registry = rules.functionRegistry;
896
+ if (registry) {
897
+ registry.indexPendingItems();
898
+ fn = registry.index.get(name);
899
+ if (fn || !searchParents) {
900
+ break;
901
+ }
902
+ }
903
+ do {
904
+ rules = rules?.parent;
905
+ if (findRoot && rules.type === 'Rules' && rules?.parent === undefined) {
906
+ /** We're at the root */
907
+ break;
908
+ }
909
+ /**
910
+ * If we reach an import boundary, skip the scope until we get to the top level.
911
+ */
912
+ if (rules && isNode(rules.sourceNode, 'StyleImport') && rules.sourceNode.options.type !== 'import') {
913
+ findRoot = true;
914
+ }
915
+ } while (!findRoot && rules && rules.type !== 'Rules');
916
+ }
917
+ return fn;
918
+ }
919
+ add(nameOrItem, func) {
920
+ // If first argument is a JsFunction or Func, use base class behavior
921
+ if (nameOrItem instanceof JsFunction || nameOrItem?.type === 'Func') {
922
+ super.add(nameOrItem);
923
+ return;
924
+ }
925
+ // Otherwise, it's Less.js-compatible API: add(name, func)
926
+ if (typeof nameOrItem !== 'string' || func === undefined) {
927
+ throw new Error('FunctionRegistry.add() requires either a JsFunction or (name: string, func: JsFunction | Function)');
928
+ }
929
+ // Convert name to lowercase for Less.js compatibility
930
+ const lowerName = nameOrItem.toLowerCase();
931
+ // If func is already a JsFunction, use it directly
932
+ // Otherwise, create a new JsFunction from the raw function
933
+ const jsFunc = func instanceof JsFunction
934
+ ? func
935
+ : new JsFunction({ name: lowerName, fn: func });
936
+ // Ensure the name is set
937
+ if (!jsFunc.name) {
938
+ jsFunc.name = lowerName;
939
+ }
940
+ // Add to pendingItems directly
941
+ this.pendingItems.add(jsFunc);
942
+ }
943
+ /**
944
+ * Less.js-compatible API: Add multiple functions at once
945
+ * @param functions Object mapping function names to functions
946
+ */
947
+ addMultiple(functions) {
948
+ for (const [name, func] of Object.entries(functions)) {
949
+ this.add(name, func);
950
+ }
951
+ }
952
+ /**
953
+ * Less.js-compatible API: Get a function by name
954
+ * Uses case-insensitive lookup and searches parent chain
955
+ * @param name Function name (case-insensitive)
956
+ * @returns The function if found, undefined otherwise
957
+ */
958
+ get(name) {
959
+ // Convert to lowercase for case-insensitive lookup
960
+ const lowerName = name.toLowerCase();
961
+ // First check local registry
962
+ this.indexPendingItems();
963
+ let fn = this.index.get(lowerName);
964
+ if (fn) {
965
+ return fn;
966
+ }
967
+ // If not found locally, use find() to search parent chain
968
+ // find() already handles parent traversal
969
+ return this.find(lowerName);
970
+ }
971
+ /**
972
+ * Less.js-compatible API: Get all local functions (without parent chain)
973
+ * @returns Object mapping function names to functions
974
+ */
975
+ getLocalFunctions() {
976
+ this.indexPendingItems();
977
+ const result = {};
978
+ for (const [name, func] of this.index.entries()) {
979
+ result[name] = func;
980
+ }
981
+ return result;
982
+ }
983
+ /**
984
+ * Less.js-compatible API: Create a child registry that inherits from this one
985
+ * In Less.js, this creates a new registry with prototype inheritance.
986
+ * In Jess, we create a new registry that searches this one as a parent.
987
+ *
988
+ * @returns A new FunctionRegistry that will search this registry when functions aren't found locally
989
+ */
990
+ inherit() {
991
+ // Create a new registry for the same Rules
992
+ // The new registry will use find() which searches parent chain
993
+ // We need to create a registry that references this one as parent
994
+ // Since FunctionRegistry.find() already searches parent Rules chain,
995
+ // we can create a new registry on the same Rules and it will naturally
996
+ // find functions in parent Rules. However, for true "inherit" behavior
997
+ // where we want to search THIS registry specifically, we need a different approach.
998
+ // For now, create a new registry on the same Rules
999
+ // The find() method will search up the Rules parent chain, which includes
1000
+ // this registry's Rules, so it should work correctly.
1001
+ const childRegistry = new FunctionRegistry(this.rules);
1002
+ // Store reference to parent registry for direct lookup
1003
+ // This allows the child to search the parent registry even if it's on the same Rules
1004
+ childRegistry._parentRegistry = this;
1005
+ // Override get() to check parent registry first
1006
+ const originalGet = childRegistry.get.bind(childRegistry);
1007
+ childRegistry.get = function (name) {
1008
+ // First check local registry
1009
+ this.indexPendingItems();
1010
+ const localFn = this.index.get(name.toLowerCase());
1011
+ if (localFn) {
1012
+ return localFn;
1013
+ }
1014
+ // Then check parent registry
1015
+ const parentRegistry = this._parentRegistry;
1016
+ if (parentRegistry) {
1017
+ const parentFn = parentRegistry.get(name);
1018
+ if (parentFn) {
1019
+ return parentFn;
1020
+ }
1021
+ }
1022
+ // Finally, use find() to search Rules parent chain
1023
+ return originalGet(name);
1024
+ }.bind(childRegistry);
1025
+ return childRegistry;
1026
+ }
1027
+ }
1028
+ /**
1029
+ *
1030
+ * @note - Keys of different types may overlap, but then are filtered when searching.
1031
+ * As in, a variable named `$foo` and a property named `foo` will be in the
1032
+ * same map.
1033
+ */
1034
+ export class DeclarationRegistry extends Registry {
1035
+ index = new Map();
1036
+ indexPendingItems() {
1037
+ for (const item of this.pendingItems) {
1038
+ let key = item.value.name.valueOf();
1039
+ let set = this.index.get(key);
1040
+ if (set && set instanceof Set) {
1041
+ set.add(item);
1042
+ }
1043
+ else {
1044
+ this.index.set(key, new Set([item]));
1045
+ }
1046
+ }
1047
+ this.pendingItems.clear();
1048
+ }
1049
+ /**
1050
+ * Get declarations from map and nested rulesets.
1051
+ * This will return a list of all matching nodes.
1052
+ *
1053
+ * @todo - The pattern for mixins will be similar, no? Can this be
1054
+ * re-used / abstracted?
1055
+ *
1056
+ * @todo - Register declarations and index them only when searching.
1057
+ * This would be similar to how we index rulesets for extending.
1058
+ */
1059
+ find(key, filterType = 'VarDeclaration', options) {
1060
+ let declCandidate = new Set();
1061
+ let optionalCandidates = options?.optionalCandidates ?? new Set();
1062
+ let rules = this.rules;
1063
+ let isPublic = false;
1064
+ let { searchParents = true, local = false, start } = options ?? {};
1065
+ let newReadonly = false;
1066
+ // Track visited Rules nodes in the parent chain to detect circular parent chains
1067
+ const visitedRules = new Set();
1068
+ while (rules) {
1069
+ // CRITICAL: Check for circular parent chain
1070
+ if (visitedRules.has(rules)) {
1071
+ throw new Error(`Circular parent chain detected in DeclarationRegistry.find`);
1072
+ }
1073
+ visitedRules.add(rules);
1074
+ let currentReadonly = options?.readonly || rules.options.readonly;
1075
+ newReadonly = currentReadonly;
1076
+ let registry = rules.getRegistry('declaration');
1077
+ registry.indexPendingItems();
1078
+ let set = registry.index.get(key);
1079
+ let list = set ? [...set] : undefined;
1080
+ if (list) {
1081
+ list = list.filter(n => n.type === filterType
1082
+ && (!options?.filter
1083
+ || options.filter(n)));
1084
+ // Sort using comparePosition for proper source order comparison
1085
+ if (list.length > 1) {
1086
+ list.sort((a, b) => {
1087
+ const pos = comparePosition(a, b);
1088
+ return pos ?? 0;
1089
+ });
1090
+ }
1091
+ }
1092
+ if (list?.length) {
1093
+ let result = rules.getRegistry('declaration')._findClosestByStart(list, start);
1094
+ if (result) {
1095
+ newReadonly ||= result.options.readonly;
1096
+ // Respect visibility on the currently searched Rules scope.
1097
+ // - private: never directly visible to lookup
1098
+ // - optional: only returned if no public match is found
1099
+ // - public: immediate candidate
1100
+ const currentRulesVisibility = rules.options.rulesVisibility?.[filterType] ?? '';
1101
+ if (currentRulesVisibility === 'private') {
1102
+ // Local lookups are allowed to read private declarations in their own scope.
1103
+ // Additionally, targeted reference resolution may set context.rulesContext
1104
+ // to this same Rules scope; allow private reads in that exact in-scope case.
1105
+ const inContextScope = options?.context?.rulesContext === rules;
1106
+ const contextRules = options?.context?.rulesContext;
1107
+ let inContextLineage = false;
1108
+ let contextCursor = contextRules;
1109
+ while (contextCursor) {
1110
+ if (contextCursor === rules) {
1111
+ inContextLineage = true;
1112
+ break;
1113
+ }
1114
+ contextCursor = contextCursor.parent;
1115
+ }
1116
+ const isParamVar = isNode(result, 'VarDeclaration') && Boolean(result.options?.paramVar);
1117
+ if ((local || inContextScope) && rules === this.rules) {
1118
+ declCandidate.add(result);
1119
+ isPublic = true;
1120
+ }
1121
+ else if (isParamVar && inContextLineage) {
1122
+ // Mixin parameters are lexical bindings and remain visible to descendant
1123
+ // scopes in the same invocation chain, even when declaration visibility is private.
1124
+ declCandidate.add(result);
1125
+ isPublic = true;
1126
+ }
1127
+ else if (options?.hasTarget === true) {
1128
+ // Targeted namespace lookups (e.g. @set[@key]) should still be able to
1129
+ // read private declaration members from the targeted rules.
1130
+ declCandidate.add(result);
1131
+ isPublic = true;
1132
+ }
1133
+ }
1134
+ else if (currentRulesVisibility === 'optional') {
1135
+ // Optional declarations are fallback-only: keep searching for public declarations
1136
+ // through the lookup chain and only return optional candidates if none are found.
1137
+ optionalCandidates.add(result);
1138
+ }
1139
+ else {
1140
+ declCandidate.add(result);
1141
+ isPublic = true;
1142
+ }
1143
+ }
1144
+ }
1145
+ // Initialize searchedRules to prevent infinite recursion when searching child Rules
1146
+ // This is critical: if a Rules node appears in its own children, we need to track it
1147
+ const searchedRules = options?.searchedRules ?? new Set();
1148
+ if (!searchedRules.has(rules)) {
1149
+ searchedRules.add(rules);
1150
+ }
1151
+ // CRITICAL: When searching children, we MUST set searchParents: false to prevent
1152
+ // infinite loops. Children searches should never traverse up the parent chain.
1153
+ // If options.searchParents is true, we're in a parent search context, and searching
1154
+ // children should not trigger another parent search.
1155
+ let searchChildrenOptions = {
1156
+ ...options,
1157
+ searchParents: false, // Always false when searching children
1158
+ readonly: newReadonly,
1159
+ candidates: declCandidate,
1160
+ searchedRules: searchedRules
1161
+ };
1162
+ const searchRules = rules;
1163
+ searchChildrenOptions.optionalCandidates = optionalCandidates;
1164
+ searchRules.getRegistry('declaration')._searchRulesChildren(key, filterType, searchChildrenOptions);
1165
+ // After searching the CURRENT scope (index + children), if we found public declarations,
1166
+ // sort them, find the best one (closest to start or at bottom), and return immediately.
1167
+ // Otherwise, continue up the parent scope.
1168
+ if (declCandidate.size > 0) {
1169
+ let bestResult;
1170
+ // Use comparePosition to find the last declaration by source order
1171
+ const candidateArray = Array.from(declCandidate);
1172
+ if (candidateArray.length === 1) {
1173
+ bestResult = candidateArray[0];
1174
+ }
1175
+ else {
1176
+ // Sort by comparePosition and take the last one
1177
+ candidateArray.sort((a, b) => {
1178
+ const pos = comparePosition(a, b);
1179
+ return pos ?? 0;
1180
+ });
1181
+ bestResult = candidateArray[candidateArray.length - 1];
1182
+ }
1183
+ if (options && searchChildrenOptions.readonly) {
1184
+ options.readonly = true;
1185
+ }
1186
+ return bestResult;
1187
+ }
1188
+ // If we haven't found public candidates in the current scope, continue normal parent search
1189
+ // (optional candidates are tracked but we keep searching up the parent chain)
1190
+ if (isPublic || !searchParents) {
1191
+ if (options && searchChildrenOptions.readonly) {
1192
+ options.readonly = true;
1193
+ }
1194
+ const result = declCandidate.values().next().value;
1195
+ return result;
1196
+ }
1197
+ do {
1198
+ rules = rules?.parent;
1199
+ /** If we're searching linearly, update the start position to the parent node index */
1200
+ /**
1201
+ * If we reach an import boundary, stop unless it's an `@import`
1202
+ * which means these rules can reach into the parent file that imports
1203
+ * this one.
1204
+ */
1205
+ if (rules && isNode(rules.sourceNode, 'StyleImport') && rules.sourceNode.options.type !== 'import') {
1206
+ rules = undefined;
1207
+ break;
1208
+ }
1209
+ } while (rules && rules.type !== 'Rules');
1210
+ }
1211
+ if (options && newReadonly) {
1212
+ options.readonly = true;
1213
+ }
1214
+ // After searching all parents, if we only have optional candidates, return the best one
1215
+ if (declCandidate.size === 0 && optionalCandidates.size > 0) {
1216
+ const optionalArray = Array.from(optionalCandidates);
1217
+ if (optionalArray.length === 1) {
1218
+ return optionalArray[0];
1219
+ }
1220
+ // Sort by comparePosition and take the last one
1221
+ optionalArray.sort((a, b) => {
1222
+ const pos = comparePosition(a, b);
1223
+ return pos ?? 0;
1224
+ });
1225
+ const optionalResult = optionalArray[optionalArray.length - 1];
1226
+ return optionalResult;
1227
+ }
1228
+ return declCandidate.values().next().value;
1229
+ }
1230
+ }
1231
+ function arraysEqual(a, b) {
1232
+ if (a.length !== b.length) {
1233
+ return false;
1234
+ }
1235
+ for (let i = 0; i < a.length; i++) {
1236
+ if (a[i] !== b[i]) {
1237
+ return false;
1238
+ }
1239
+ }
1240
+ return true;
1241
+ }
1242
+ //# sourceMappingURL=registry-utils.js.map