@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,2272 @@
1
+ import { isNode } from './is-node.js';
2
+ import { Node, F_IMPLICIT_AMPERSAND, F_VISIBLE } from '../node.js';
3
+ import { SelectorList } from '../selector-list.js';
4
+ import { ComplexSelector } from '../selector-complex.js';
5
+ import { Combinator } from '../combinator.js';
6
+ import { PseudoSelector, is as isSelectorPseudo } from '../selector-pseudo.js';
7
+ import { Ampersand } from '../ampersand.js';
8
+ import { Nil } from '../nil.js';
9
+ import { tryExtendSelector, createProcessedSelector, setExtendOrderMap } from './extend.js';
10
+ import { selectorMatchesExtendTarget } from './extend-helpers.js';
11
+ import { processLeadingIs } from './process-leading-is.js';
12
+ import { WARN, toDiagnostic } from '../../jess-error.js';
13
+ import { syncLog } from './__tests__/debug-log.js';
14
+ import { serializeTypes } from './serialize-types.js';
15
+ import { shouldTraceExtend, shouldTraceExtendMd, getExtendTraceRunId } from './extend-trace-debug.js';
16
+ function rulesStructureSummary(r) {
17
+ const len = r.value?.length ?? 0;
18
+ const first = r.value?.[0];
19
+ const firstType = first != null && typeof first.type === 'string' ? first.type : undefined;
20
+ const firstRules = first != null && first.value?.rules;
21
+ const firstValueRulesType = firstRules != null && typeof firstRules.type === 'string' ? firstRules.type : undefined;
22
+ const firstValueRulesLen = firstRules != null && Array.isArray(firstRules.value) ? firstRules.value.length : undefined;
23
+ return { valueLen: len, firstType, firstValueRulesType, firstValueRulesLen };
24
+ }
25
+ /**
26
+ * Recursively ensure all selector nodes have F_VISIBLE so they serialize.
27
+ * Extended selectors can include items from the original target that lacked F_VISIBLE
28
+ * (e.g. nested context), causing components to render as '' and produce wrong output (.c, .rep_ace).
29
+ * Do NOT add F_VISIBLE to implicit ampersands (F_IMPLICIT_AMPERSAND): they must stay invisible
30
+ * so nested output stays short (.a, .c not :is(.b, .a) .a). Do not recurse into them.
31
+ */
32
+ function ensureSelectorVisible(selector) {
33
+ if (!selector || typeof selector.addFlag !== 'function') {
34
+ return;
35
+ }
36
+ const n = selector;
37
+ if (selector instanceof Ampersand && n.hasFlag(F_IMPLICIT_AMPERSAND)) {
38
+ return;
39
+ }
40
+ if (!n.hasFlag(F_VISIBLE)) {
41
+ n.addFlag(F_VISIBLE);
42
+ }
43
+ if (isNode(selector, 'SelectorList')) {
44
+ const items = selector.value;
45
+ if (Array.isArray(items)) {
46
+ for (const item of items) {
47
+ ensureSelectorVisible(item);
48
+ }
49
+ }
50
+ return;
51
+ }
52
+ if (isNode(selector, 'ComplexSelector')) {
53
+ const comps = selector.value;
54
+ if (Array.isArray(comps)) {
55
+ for (const c of comps) {
56
+ if (c && typeof c.addFlag === 'function') {
57
+ ensureSelectorVisible(c);
58
+ }
59
+ }
60
+ }
61
+ return;
62
+ }
63
+ const selWithValue = selector;
64
+ if (Array.isArray(selWithValue.value)) {
65
+ for (const c of selWithValue.value) {
66
+ ensureSelectorVisible(c);
67
+ }
68
+ }
69
+ }
70
+ /** Ensure top-level items in a SelectorList (and their descendants) have F_VISIBLE so they serialize. */
71
+ function ensureSelectorListItemsVisible(selector) {
72
+ if (!isNode(selector, 'SelectorList')) {
73
+ return;
74
+ }
75
+ const list = selector;
76
+ const items = list.value;
77
+ if (!Array.isArray(items)) {
78
+ return;
79
+ }
80
+ for (const item of items) {
81
+ ensureSelectorVisible(item);
82
+ }
83
+ }
84
+ /** Preserve F_IMPLICIT_AMPERSAND on cloned selector(s) so createProcessedSelector keeps implicit ampersand (extend.less .dd,.ee,.ff). */
85
+ function preserveImplicitAmpersandOnClone(extendedSelector, clonedSelector) {
86
+ const preserveOne = (orig, clone) => {
87
+ if (!isNode(orig, 'ComplexSelector') || !isNode(clone, 'ComplexSelector')) {
88
+ return;
89
+ }
90
+ const origFirst = orig.value[0];
91
+ const cloneFirst = clone.value[0];
92
+ if (origFirst instanceof Ampersand && cloneFirst instanceof Ampersand && origFirst.hasFlag(F_IMPLICIT_AMPERSAND)) {
93
+ cloneFirst.addFlag(F_IMPLICIT_AMPERSAND);
94
+ cloneFirst.removeFlag(F_VISIBLE);
95
+ }
96
+ };
97
+ if (isNode(clonedSelector, 'ComplexSelector') && isNode(extendedSelector, 'ComplexSelector')) {
98
+ preserveOne(extendedSelector, clonedSelector);
99
+ }
100
+ else if (isNode(clonedSelector, 'SelectorList') && isNode(extendedSelector, 'SelectorList')) {
101
+ const origList = extendedSelector;
102
+ const cloneList = clonedSelector;
103
+ const origItems = origList.value;
104
+ const cloneItems = cloneList.value;
105
+ if (Array.isArray(origItems) && Array.isArray(cloneItems) && origItems.length === cloneItems.length) {
106
+ for (let i = 0; i < origItems.length; i++) {
107
+ preserveOne(origItems[i], cloneItems[i]);
108
+ }
109
+ }
110
+ }
111
+ }
112
+ /**
113
+ * Before updating a ruleset's selector, ensure any descendant rulesets that share
114
+ * this ruleset's value object get their own value so they keep their current
115
+ * selector when we assign ruleset.value.selector.
116
+ */
117
+ function ensureDescendantRulesetsHaveOwnValue(ruleset, sharedValue) {
118
+ const rules = ruleset.value?.rules;
119
+ if (!rules || !isNode(rules, 'Rules')) {
120
+ return;
121
+ }
122
+ const children = rules.value;
123
+ if (!Array.isArray(children)) {
124
+ return;
125
+ }
126
+ for (const child of children) {
127
+ if (!isNode(child, 'Ruleset')) {
128
+ continue;
129
+ }
130
+ const rs = child;
131
+ if (rs.value === sharedValue) {
132
+ rs.value = {
133
+ selector: rs.value.selector,
134
+ rules: rs.value.rules,
135
+ ...(rs.value.guard !== undefined && { guard: rs.value.guard })
136
+ };
137
+ }
138
+ ensureDescendantRulesetsHaveOwnValue(rs, sharedValue);
139
+ }
140
+ }
141
+ function maybeHoistMixedNestingSelectorList(ruleset, selector, partial) {
142
+ // Only relevant for nested rulesets whose selector becomes a mixed selector list:
143
+ // e.g. inside `.header { .header-nav { ... } }`, after extend we might have:
144
+ // `.header-nav, .footer .footer-nav { ... }`
145
+ //
146
+ // Less/jess expectations for the Less test-data are to hoist to root and materialize the
147
+ // implicit parent on relative selectors, producing:
148
+ // `:is(.header .header-nav, .footer .footer-nav) { ... }`
149
+ const parentRules = ruleset.parent;
150
+ const parentRuleset = parentRules?.parent;
151
+ if (!parentRuleset || !isNode(parentRuleset, 'Ruleset')) {
152
+ return selector;
153
+ }
154
+ const parentSel = parentRuleset.selector;
155
+ if (!parentSel || isNode(parentSel, 'Nil')) {
156
+ return selector;
157
+ }
158
+ // Selector may already be wrapped in :is(...) for partial-extend output.
159
+ let wrapper = null;
160
+ let list = null;
161
+ if (isNode(selector, 'SelectorList')) {
162
+ list = selector;
163
+ }
164
+ else if (isNode(selector, 'PseudoSelector') && selector.value.name === ':is') {
165
+ const arg = selector.value.arg;
166
+ if (arg && isNode(arg, 'SelectorList')) {
167
+ wrapper = selector;
168
+ list = arg;
169
+ }
170
+ }
171
+ if (!list) {
172
+ return selector;
173
+ }
174
+ const materializeImplicitAmpersand = (s) => {
175
+ if (isNode(s, 'ComplexSelector')) {
176
+ const cs = s;
177
+ const first = cs.value[0];
178
+ const second = cs.value[1];
179
+ if (first instanceof Ampersand
180
+ && first.hasFlag(F_IMPLICIT_AMPERSAND)) {
181
+ const resolved = first.getResolvedSelector();
182
+ if (!resolved || resolved instanceof Nil) {
183
+ return s;
184
+ }
185
+ // Same context: ampersand resolves to this ruleset's parent selector. Keep implicit
186
+ // so nested output stays short (.dd, .ee not :is(.aa, .cc) .dd). Do not add F_VISIBLE.
187
+ const resolvedCanonical = canonicalSelectorValueOf(resolved);
188
+ const parentCanonical = canonicalSelectorValueOf(parentSel);
189
+ if (resolvedCanonical !== '' && parentCanonical !== '' && resolvedCanonical === parentCanonical) {
190
+ return s;
191
+ }
192
+ // Replace implicit ampersand with its concrete parent selector so
193
+ // serialization at root doesn't drop it.
194
+ let parentSelConcrete = resolved.copy(true);
195
+ if (parentSelConcrete instanceof Nil) {
196
+ return s;
197
+ }
198
+ // If the parent selector is itself a SelectorList, materialize it as `:is(...)`
199
+ // so it remains a single selector component when hoisted to root.
200
+ if (isNode(parentSelConcrete, 'SelectorList')) {
201
+ parentSelConcrete = isSelectorPseudo(parentSelConcrete);
202
+ }
203
+ const out = cs.copy(true);
204
+ out.value[0] = parentSelConcrete;
205
+ // Ensure the combinator is visible when we materialize the parent.
206
+ const outSecond = out.value[1];
207
+ if (outSecond instanceof Node) {
208
+ outSecond.addFlag(F_VISIBLE);
209
+ }
210
+ return out;
211
+ }
212
+ // For hoisted selector serialization we need leading components to be visible,
213
+ // otherwise `toString()` can drop the parent prefix.
214
+ if (first instanceof Node) {
215
+ first.addFlag(F_VISIBLE);
216
+ }
217
+ if (second instanceof Node) {
218
+ second.addFlag(F_VISIBLE);
219
+ }
220
+ return s;
221
+ }
222
+ // Simple selector in nested context: always prepend parent so "prefixed by parent" is
223
+ // consistent for all selector types (e.g. [data="test"] under .attributes → .attributes [data="test"],
224
+ // same as .c under .replace). Previously we only did this when parent was a SelectorList, so
225
+ // single-selector parents (e.g. .attributes) left [data="test"] unprefixed and wrongly triggered hoist.
226
+ if (parentSel) {
227
+ let parentSelConcrete = parentSel.copy(true);
228
+ if (isNode(parentSelConcrete, 'SelectorList')) {
229
+ parentSelConcrete = isSelectorPseudo(parentSelConcrete);
230
+ }
231
+ const out = ComplexSelector.create([
232
+ parentSelConcrete,
233
+ Combinator.create(' '),
234
+ s.copy(true)
235
+ ]).inherit(s);
236
+ const outFirst = out.value[0];
237
+ const outSecond = out.value[1];
238
+ if (outFirst instanceof Node) {
239
+ outFirst.addFlag(F_VISIBLE);
240
+ }
241
+ if (outSecond instanceof Node) {
242
+ outSecond.addFlag(F_VISIBLE);
243
+ }
244
+ return out;
245
+ }
246
+ return s;
247
+ };
248
+ const items = list.value;
249
+ if (!Array.isArray(items) || items.length < 2) {
250
+ return selector;
251
+ }
252
+ // Special-case: when the parent selector is a selector list, a nested selector list can become
253
+ // "mixed" after extend (some items are relative via implicit `&`, some are absolute like `.rep_ace`).
254
+ // If we serialize that nested, we would incorrectly apply the parent frame to the absolute items.
255
+ // Hoist to root and materialize the implicit parent as `:is(parentSelectors)`.
256
+ if (isNode(parentSel, 'SelectorList')) {
257
+ // Group into :is() when all items have the same invisible ampersand + space (e.g. & .a, & .b, & .c → & :is(.a, .b, .c)).
258
+ const allHaveImplicitAmpersandSpace = () => {
259
+ if (items.length < 2) {
260
+ return false;
261
+ }
262
+ for (const s of items) {
263
+ if (!isNode(s, 'ComplexSelector')) {
264
+ return false;
265
+ }
266
+ const cs = s;
267
+ const first = cs.value[0];
268
+ const second = cs.value[1];
269
+ if (!(first instanceof Ampersand && first.hasFlag(F_IMPLICIT_AMPERSAND))
270
+ || !isNode(second, 'Combinator')
271
+ || second.value !== ' ') {
272
+ return false;
273
+ }
274
+ }
275
+ return true;
276
+ };
277
+ if (allHaveImplicitAmpersandSpace()) {
278
+ const firstCs = items[0];
279
+ const amp = firstCs.value[0].copy(true);
280
+ const spaceComb = firstCs.value[1].copy(true);
281
+ const suffixSelectors = [];
282
+ for (const s of items) {
283
+ const cs = s;
284
+ const rest = cs.value.slice(2);
285
+ if (rest.length === 0) {
286
+ continue;
287
+ }
288
+ const suffixSel = rest.length === 1
289
+ ? rest[0].copy(true)
290
+ : ComplexSelector.create(rest.map(c => c.copy(true))).inherit(cs);
291
+ suffixSelectors.push(suffixSel);
292
+ }
293
+ if (suffixSelectors.length >= 2) {
294
+ const childIs = new PseudoSelector({
295
+ name: ':is',
296
+ arg: SelectorList.create(suffixSelectors).inherit(list)
297
+ }).inherit(list);
298
+ const combined = ComplexSelector.create([amp, spaceComb, childIs]).inherit(list);
299
+ return SelectorList.create([combined]).inherit(list);
300
+ }
301
+ }
302
+ const startsWithImplicitParent = (s) => {
303
+ if (isNode(s, 'ComplexSelector')) {
304
+ const first = s.value[0];
305
+ return first instanceof Ampersand && first.hasFlag(F_IMPLICIT_AMPERSAND);
306
+ }
307
+ // Simple selectors in nested context are relative (need parent materialization)
308
+ return false;
309
+ };
310
+ // Check if we have a mix: some items with implicit parent (relative) and some without (absolute)
311
+ const anyImplicit = items.some(startsWithImplicitParent);
312
+ // An item is "absolute" if it's a ComplexSelector without implicit ampersand, or a simple selector
313
+ // that doesn't match the nested pattern.
314
+ const hasComplexWithoutImplicit = items.some((s) => {
315
+ if (isNode(s, 'ComplexSelector')) {
316
+ const first = s.value[0];
317
+ return !(first instanceof Ampersand && first.hasFlag(F_IMPLICIT_AMPERSAND));
318
+ }
319
+ return false;
320
+ });
321
+ const hasSimpleSelectors = items.some(s => !isNode(s, 'ComplexSelector'));
322
+ // If we ended up with a selector-list where some items are plain selectors (e.g. `.replace`)
323
+ // but others are just the parent prefix materialized as `:is(parentSel) <child>` (e.g. `:is(parentSel) .c`),
324
+ // prefer normalizing back to plain nested selectors rather than hoisting/distributing.
325
+ //
326
+ // Runtime evidence (core-hoist-check):
327
+ // - parentSelV = `:is(.replace,.rep_ace):is(.replace,.rep_ace),.c:is(...)+:is(...)`
328
+ // - items = [`.replace`, `.rep_ace`, `:is(parentSelV) .c`]
329
+ //
330
+ // This is not a true "mixed absolute/relative" list; it's an internal representation artifact.
331
+ if (hasSimpleSelectors) {
332
+ // Special-case: factorize a cartesian-product expansion back into `:is(parentSel) :is(children)`
333
+ // so `extend-exact` matches Less output (avoid full distribution).
334
+ //
335
+ // Example items:
336
+ // - `.replace.replace .replace`
337
+ // - `.c.replace + .replace .replace`
338
+ // - `.replace.replace .c`
339
+ // - `.c.replace + .replace .c`
340
+ // plus an absolute `.rep_ace` selector.
341
+ try {
342
+ const complexItems = items.filter(s => isNode(s, 'ComplexSelector'));
343
+ if (complexItems.length >= 4) {
344
+ const parentAlts = parentSel.value.map(v => v.valueOf());
345
+ const lastBasics = [];
346
+ const complexThatMatch = [];
347
+ for (const cs of complexItems) {
348
+ const last = cs.value[cs.value.length - 1];
349
+ if (!isNode(last, 'BasicSelector')) {
350
+ continue;
351
+ }
352
+ const v = last.valueOf();
353
+ lastBasics.push({ node: last, v });
354
+ // Only consider if it starts with one of the parent alternatives.
355
+ const sV = cs.valueOf();
356
+ if (parentAlts.some(p => sV.startsWith(`${p} `))) {
357
+ complexThatMatch.push(cs);
358
+ }
359
+ }
360
+ const uniqLast = [...new Map(lastBasics.map(b => [b.v, b.node])).entries()].map(([, n]) => n);
361
+ // If we can explain the complex items as parentAlts x uniqLast (cartesian product), factorize.
362
+ if (uniqLast.length >= 2 && complexThatMatch.length >= parentAlts.length * uniqLast.length) {
363
+ const parentIs = new PseudoSelector({ name: ':is', arg: parentSel.copy(true) }).inherit(parentSel);
364
+ const childIs = new PseudoSelector({
365
+ name: ':is',
366
+ arg: SelectorList.create(uniqLast.map(n => n.copy(true))).inherit(parentSel)
367
+ }).inherit(parentSel);
368
+ const combined = ComplexSelector.create([
369
+ parentIs,
370
+ Combinator.create(' ').inherit(parentSel),
371
+ childIs
372
+ ]).inherit(parentSel);
373
+ const kept = [];
374
+ let inserted = false;
375
+ for (const it of items) {
376
+ if (!isNode(it, 'ComplexSelector')) {
377
+ if (!inserted) {
378
+ kept.push(combined);
379
+ inserted = true;
380
+ }
381
+ kept.push(it);
382
+ continue;
383
+ }
384
+ // Drop the distributed combinations.
385
+ const itV = it.valueOf?.() ?? '';
386
+ if (parentAlts.some(p => itV.startsWith(`${p} `))) {
387
+ continue;
388
+ }
389
+ if (!inserted) {
390
+ kept.push(combined);
391
+ inserted = true;
392
+ }
393
+ kept.push(it);
394
+ }
395
+ const listOut = SelectorList.create(kept.map(s => s.clone(true))).inherit(list);
396
+ // We created selectors that already materialize the parent selector list via `:is(parentSel) ...`.
397
+ // If we keep this nested, serialization will incorrectly treat them as relative to the parent frame.
398
+ // Hoist to root.
399
+ listOut.hoistToRoot = true;
400
+ ruleset.hoistToRoot = true;
401
+ if (wrapper) {
402
+ wrapper.value.arg = listOut;
403
+ wrapper.hoistToRoot = true;
404
+ return wrapper;
405
+ }
406
+ return listOut;
407
+ }
408
+ }
409
+ }
410
+ catch { }
411
+ let changed = false;
412
+ const normalized = items.map((s) => {
413
+ if (!isNode(s, 'ComplexSelector')) {
414
+ return s;
415
+ }
416
+ const cs = s;
417
+ const a = cs.value[0];
418
+ const b = cs.value[1];
419
+ const c = cs.value[2];
420
+ if (isNode(a, 'PseudoSelector')
421
+ && a.value?.name === ':is'
422
+ && isNode(a.value?.arg, 'SelectorList')
423
+ && a.value.arg.valueOf() === parentSel.valueOf()
424
+ && isNode(b, 'Combinator')
425
+ && b.value === ' '
426
+ && isNode(c, 'BasicSelector')) {
427
+ changed = true;
428
+ return c.copy(true);
429
+ }
430
+ return s;
431
+ });
432
+ if (changed) {
433
+ const listOut = SelectorList.create(normalized.map(s => s.clone(true)));
434
+ if (wrapper) {
435
+ wrapper.value.arg = listOut;
436
+ return wrapper;
437
+ }
438
+ return listOut;
439
+ }
440
+ }
441
+ // If we have both items with implicit parent (relative) and items without (absolute), hoist and materialize.
442
+ // This covers:
443
+ // - ComplexSelector with implicit ampersand + ComplexSelector without (mixed relative/absolute)
444
+ // - ComplexSelector with implicit ampersand + simple selector (relative + absolute)
445
+ // - Simple selectors (which are relative in nested context) + ComplexSelector without implicit (relative + absolute)
446
+ if (anyImplicit && (hasComplexWithoutImplicit || hasSimpleSelectors)) {
447
+ const listOut = SelectorList.create(items.map(s => materializeImplicitAmpersand(s).clone(true)));
448
+ if (partial) {
449
+ if (!wrapper) {
450
+ listOut.hoistToRoot = true;
451
+ ruleset.hoistToRoot = true;
452
+ return listOut;
453
+ }
454
+ wrapper.value.arg = listOut;
455
+ wrapper.hoistToRoot = true;
456
+ ruleset.hoistToRoot = true;
457
+ return wrapper;
458
+ }
459
+ listOut.hoistToRoot = true;
460
+ ruleset.hoistToRoot = true;
461
+ return listOut;
462
+ }
463
+ // Also hoist if we have simple selectors mixed with ComplexSelector items without implicit ampersand
464
+ // (both could be absolute, but if parent is SelectorList and we're nested, simple selectors are relative)
465
+ if (hasSimpleSelectors && hasComplexWithoutImplicit) {
466
+ const listOut = SelectorList.create(items.map(s => materializeImplicitAmpersand(s).clone(true)));
467
+ if (partial) {
468
+ if (!wrapper) {
469
+ listOut.hoistToRoot = true;
470
+ ruleset.hoistToRoot = true;
471
+ return listOut;
472
+ }
473
+ wrapper.value.arg = listOut;
474
+ wrapper.hoistToRoot = true;
475
+ ruleset.hoistToRoot = true;
476
+ return wrapper;
477
+ }
478
+ listOut.hoistToRoot = true;
479
+ ruleset.hoistToRoot = true;
480
+ return listOut;
481
+ }
482
+ }
483
+ // Use materialized form so relative selectors (e.g. .header-nav with implicit &) count as prefixed.
484
+ // Use normalized comparison so spacing differences don't prevent match (e.g. ".header .header-nav").
485
+ const parentPrefix = `${parentSel.valueOf()} `;
486
+ const parentNorm = normalizedSelectorValueOf(parentSel);
487
+ const parentPrefixNorm = parentNorm ? parentNorm + ' ' : '';
488
+ const itemPrefixedByParent = (s) => {
489
+ if (!parentPrefix && !parentPrefixNorm) {
490
+ return false;
491
+ }
492
+ const mat = materializeImplicitAmpersand(s);
493
+ const v = String(mat.valueOf()).replace(/\s+/g, ' ').trim();
494
+ const vNorm = v.replace(/\s+/g, '');
495
+ if (v.startsWith(parentPrefix)) {
496
+ return true;
497
+ }
498
+ if (parentPrefixNorm && vNorm.startsWith(parentPrefixNorm.replace(/\s+/g, ''))) {
499
+ return true;
500
+ }
501
+ return false;
502
+ };
503
+ const anyPrefixedByParent = items.some(itemPrefixedByParent);
504
+ const anyNotPrefixedByParent = items.some(s => !itemPrefixedByParent(s));
505
+ // Heuristic: if we have a mix of "relative" (no descendant combinator, and not prefixed by parent)
506
+ // and "absolute" (has descendant combinator), hoist and materialize parent. Only count as relative
507
+ // when not prefixed so same-parent simple selectors (e.g. [data="test"] under .attributes) don't trigger hoist.
508
+ const hasDescendantCombinator = (s) => isNode(s, 'ComplexSelector') && s.value.some(c => isNode(c, 'Combinator') && c.value === ' ');
509
+ const anyAbsolute = items.some(hasDescendantCombinator);
510
+ const anyRelative = items.some(s => !hasDescendantCombinator(s) && !itemPrefixedByParent(s));
511
+ // If the selector list mixes selectors that are under the parent prefix and selectors that are not,
512
+ // hoist to root so we don't serialize them inside the parent's frame (which would strip the prefix
513
+ // from the prefixed selectors, producing `.header-nav, .footer .footer-nav`).
514
+ if (anyPrefixedByParent && anyNotPrefixedByParent) {
515
+ const listOut = SelectorList.create(items.map(s => materializeImplicitAmpersand(s).clone(true)));
516
+ if (partial) {
517
+ // If we were going to introduce a wrapper just for partial-mode output,
518
+ // prefer returning a plain selector list. A top-level `:is(...)` wrapper
519
+ // is unnecessary when it is the entire selector.
520
+ if (!wrapper) {
521
+ listOut.hoistToRoot = true;
522
+ ruleset.hoistToRoot = true;
523
+ return listOut;
524
+ }
525
+ // If the selector was already wrapped, preserve that structure.
526
+ wrapper.value.arg = listOut;
527
+ wrapper.hoistToRoot = true;
528
+ ruleset.hoistToRoot = true;
529
+ return wrapper;
530
+ }
531
+ listOut.hoistToRoot = true;
532
+ ruleset.hoistToRoot = true;
533
+ return listOut;
534
+ }
535
+ if (!anyAbsolute || !anyRelative) {
536
+ return selector;
537
+ }
538
+ const rewritten = items.map((s) => {
539
+ if (hasDescendantCombinator(s)) {
540
+ return materializeImplicitAmpersand(s);
541
+ }
542
+ // Prefix the nested parent selector.
543
+ const out = ComplexSelector.create([parentSel.copy(true), Combinator.create(' '), s.copy(true)]).inherit(s);
544
+ return materializeImplicitAmpersand(out);
545
+ });
546
+ const listOut = SelectorList.create(rewritten);
547
+ if (partial) {
548
+ // Same rationale as above: don't introduce a top-level `:is(...)` wrapper
549
+ // if it would be the entire selector.
550
+ if (!wrapper) {
551
+ listOut.hoistToRoot = true;
552
+ ruleset.hoistToRoot = true;
553
+ return listOut;
554
+ }
555
+ wrapper.value.arg = listOut;
556
+ wrapper.hoistToRoot = true;
557
+ ruleset.hoistToRoot = true;
558
+ return wrapper;
559
+ }
560
+ listOut.hoistToRoot = true;
561
+ ruleset.hoistToRoot = true;
562
+ return listOut;
563
+ }
564
+ /** Normalize selector valueOf for comparison (whitespace and comma spacing can differ between clones). */
565
+ function normalizedSelectorValueOf(sel) {
566
+ if (sel == null || typeof sel.valueOf !== 'function') {
567
+ return '';
568
+ }
569
+ const v = sel.valueOf();
570
+ return String(v).replace(/\s+/g, '').trim();
571
+ }
572
+ /**
573
+ * Build the resolved selector for a nested ruleset: parent selector list × own (child) selector list,
574
+ * so that tryExtendSelector can match a complex find (e.g. .replace.replace .replace) and add extendWith.
575
+ * Returns undefined if the ruleset has no parent ruleset or we can't build the product.
576
+ */
577
+ function buildResolvedSelectorForNested(ruleset, ownSel) {
578
+ const parentRuleset = ruleset.parent?.parent;
579
+ if (!parentRuleset || !isNode(parentRuleset, 'Ruleset')) {
580
+ return undefined;
581
+ }
582
+ const parentSelector = parentRuleset.selector;
583
+ if (!parentSelector || parentSelector instanceof Nil) {
584
+ return undefined;
585
+ }
586
+ const parentItems = isNode(parentSelector, 'SelectorList')
587
+ ? parentSelector.value
588
+ : [parentSelector];
589
+ const childItems = isNode(ownSel, 'SelectorList')
590
+ ? ownSel.value
591
+ : [ownSel];
592
+ const product = [];
593
+ for (const p of parentItems) {
594
+ for (const c of childItems) {
595
+ product.push(ComplexSelector.create([p.copy(true), Combinator.create(' '), c.copy(true)]).inherit(ownSel));
596
+ }
597
+ }
598
+ return SelectorList.create(product).inherit(ownSel);
599
+ }
600
+ /** For exact extend: exclude rulesets whose selector is "target " + descendant (e.g. .bb .bb when target is .bb). */
601
+ function selectorIsTargetWithDescendant(ruleset, target) {
602
+ const targetVal = typeof target.valueOf === 'function' ? String(target.valueOf()).trim() : '';
603
+ if (!targetVal) {
604
+ return false;
605
+ }
606
+ const prefix = targetVal + ' ';
607
+ const checkSel = (s) => {
608
+ let str;
609
+ if (isNode(s, 'ComplexSelector')) {
610
+ const first = s.value[0];
611
+ if (first && typeof first.getResolvedSelector === 'function') {
612
+ const resolved = first.getResolvedSelector();
613
+ if (resolved && !isNode(resolved, 'Nil')) {
614
+ const rest = s.value.slice(1);
615
+ str = `${resolved.valueOf?.() ?? ''} ${rest.map((c) => c.valueOf?.() ?? '').join(' ')}`.trim();
616
+ }
617
+ else {
618
+ str = typeof s.valueOf === 'function' ? String(s.valueOf()).trim() : '';
619
+ }
620
+ }
621
+ else {
622
+ str = typeof s.valueOf === 'function' ? String(s.valueOf()).trim() : '';
623
+ }
624
+ }
625
+ else {
626
+ str = typeof s.valueOf === 'function' ? String(s.valueOf()).trim() : '';
627
+ }
628
+ return str.startsWith(prefix);
629
+ };
630
+ const sel = ruleset.selector;
631
+ if (!sel) {
632
+ return false;
633
+ }
634
+ if (isNode(sel, 'SelectorList')) {
635
+ const items = sel.value;
636
+ if (Array.isArray(items)) {
637
+ return items.every(item => checkSel(item));
638
+ }
639
+ }
640
+ return checkSel(sel);
641
+ }
642
+ /** Canonical form for same-context comparison: unwrap :is(...) so :is(.a,.b) and .a,.b compare equal. */
643
+ function canonicalSelectorValueOf(sel) {
644
+ if (sel == null) {
645
+ return '';
646
+ }
647
+ if (isNode(sel, 'PseudoSelector') && sel.value?.name === ':is') {
648
+ const arg = sel.value?.arg;
649
+ if (arg && typeof arg.valueOf === 'function') {
650
+ return normalizedSelectorValueOf(arg);
651
+ }
652
+ }
653
+ return normalizedSelectorValueOf(sel);
654
+ }
655
+ /** Recursively unwrap :is() in list items so :is(.x) .a,:is(.x) .b matches .x .a,.x .b for dematerialize. */
656
+ function fullyCanonicalSelectorValueOf(sel) {
657
+ if (sel == null) {
658
+ return '';
659
+ }
660
+ if (isNode(sel, 'PseudoSelector') && sel.value?.name === ':is') {
661
+ const arg = sel.value?.arg;
662
+ return arg ? fullyCanonicalSelectorValueOf(arg) : normalizedSelectorValueOf(sel);
663
+ }
664
+ if (isNode(sel, 'SelectorList')) {
665
+ const items = sel.value;
666
+ if (!Array.isArray(items)) {
667
+ return normalizedSelectorValueOf(sel);
668
+ }
669
+ return items.map((s) => fullyCanonicalSelectorValueOf(s)).join(',');
670
+ }
671
+ if (isNode(sel, 'ComplexSelector')) {
672
+ const comps = sel.value;
673
+ if (!Array.isArray(comps)) {
674
+ return normalizedSelectorValueOf(sel);
675
+ }
676
+ return comps.map((c) => fullyCanonicalSelectorValueOf(c)).join(' ');
677
+ }
678
+ return normalizedSelectorValueOf(sel);
679
+ }
680
+ /** Get the selector of the ruleset that contains this ruleset (parent context), or undefined if at root.
681
+ * Uses the parent's selectorBeforeExtend when set so "same context" comparison doesn't materialize
682
+ * a nested ruleset's & just because the parent was extended (ampersand that references extended parent
683
+ * should still be treated as same context for serialization). */
684
+ function getRulesetParentSelector(ruleset) {
685
+ const parentRules = ruleset.parent;
686
+ const parentRuleset = parentRules?.parent;
687
+ if (!parentRuleset || !isNode(parentRuleset, 'Ruleset')) {
688
+ return undefined;
689
+ }
690
+ const val = parentRuleset.value;
691
+ const sel = val?.selectorBeforeExtend ?? val?.selector;
692
+ return sel && !isNode(sel, 'Nil') ? sel : undefined;
693
+ }
694
+ /** Leading prefix of a ruleset's selector: canonical value of the first consecutive items that do NOT start with implicit ampersand. Used so nested items (& .dd) under a merged list (.aa, .cc, & .dd, ...) are not materialized when & resolves to that prefix. */
695
+ function getRulesetSelectorLeadingPrefixNormalized(ruleset) {
696
+ const sel = ruleset.value?.selector;
697
+ if (!sel || isNode(sel, 'Nil')) {
698
+ return '';
699
+ }
700
+ if (!isNode(sel, 'SelectorList')) {
701
+ return canonicalSelectorValueOf(sel);
702
+ }
703
+ const items = sel.value;
704
+ if (!Array.isArray(items) || items.length === 0) {
705
+ return '';
706
+ }
707
+ const leading = [];
708
+ for (const s of items) {
709
+ if (isNode(s, 'Nil')) {
710
+ continue;
711
+ }
712
+ if (isNode(s, 'ComplexSelector')) {
713
+ const first = s.value[0];
714
+ if (first instanceof Ampersand && first.hasFlag(F_IMPLICIT_AMPERSAND)) {
715
+ break;
716
+ }
717
+ }
718
+ leading.push(s);
719
+ }
720
+ if (leading.length === 0) {
721
+ return '';
722
+ }
723
+ if (leading.length === 1) {
724
+ return canonicalSelectorValueOf(leading[0]);
725
+ }
726
+ return canonicalSelectorValueOf(SelectorList.create(leading.map(s => s.copy(true))).inherit(sel));
727
+ }
728
+ /**
729
+ * When createProcessedSelector has already resolved an implicit & to :is(ctx) + combinator + rest,
730
+ * convert it back to implicit ampersand when ctx matches the ruleset context so output stays short
731
+ * (.dd not :is(.aa, .cc) .dd).
732
+ */
733
+ function dematerializeSameContextIsPrefix(s, rulesetParentSelector, rulesetLeadingPrefixNormalized) {
734
+ if (!isNode(s, 'ComplexSelector') || s.value.length < 2) {
735
+ return s;
736
+ }
737
+ const first = s.value[0];
738
+ const second = s.value[1];
739
+ if (!isNode(first, 'PseudoSelector')
740
+ || first.value?.name !== ':is'
741
+ || !first.value?.arg
742
+ || !isNode(second, 'Combinator')) {
743
+ return s;
744
+ }
745
+ const isArg = first.value.arg;
746
+ const isArgCanonical = fullyCanonicalSelectorValueOf(isArg);
747
+ const ctxVal = rulesetParentSelector ? fullyCanonicalSelectorValueOf(rulesetParentSelector) : '';
748
+ const matchCtx = isArgCanonical !== '' && ctxVal !== '' && isArgCanonical === ctxVal;
749
+ const matchLeading = isArgCanonical !== '' && rulesetLeadingPrefixNormalized !== '' && isArgCanonical === rulesetLeadingPrefixNormalized;
750
+ if (isArgCanonical === '' || (!matchCtx && !matchLeading)) {
751
+ return s;
752
+ }
753
+ const amp = Ampersand.create({
754
+ selector: isArg.copy(true),
755
+ getResolvedSelector: () => isArg.copy(true)
756
+ }).inherit(first);
757
+ amp.addFlag(F_IMPLICIT_AMPERSAND);
758
+ amp.removeFlag(F_VISIBLE);
759
+ const comb = second.copy(true);
760
+ comb.removeFlag(F_VISIBLE);
761
+ const out = s.copy(true);
762
+ out.value[0] = amp;
763
+ out.value[1] = comb;
764
+ return out;
765
+ }
766
+ const DEBUG_AMPERSAND_EXTEND = process.env.DEBUG_AMPERSAND_EXTEND === '1';
767
+ /**
768
+ * If the selector item starts with an implicit ampersand and that ampersand's context (getResolvedSelector)
769
+ * is different from the ruleset we're extending, materialize the ampersand so it serializes correctly
770
+ * (e.g. extendWith from another ruleset becomes ".issue-2586-somepage .content" not ".content").
771
+ * If same context (nested ruleset's own selector), keep implicit so output stays short (".a, .c").
772
+ * @param rulesetOwnSelectorNormalized - optional normalized valueOf of the ruleset's own selector (so we don't materialize when & resolves to same ruleset)
773
+ * @param rulesetLeadingPrefixNormalized - when ruleset has a mixed selector list (.aa, .cc, & .dd, ...), canonical of leading items (.aa, .cc) so we don't materialize when & resolves to that prefix
774
+ */
775
+ function materializeImplicitAmpersandWhenDifferentContext(s, rulesetParentSelector, rulesetOwnSelectorNormalized = '', rulesetLeadingPrefixNormalized = '') {
776
+ if (!isNode(s, 'ComplexSelector') || s.value.length < 2) {
777
+ return s;
778
+ }
779
+ const first = s.value[0];
780
+ const second = s.value[1];
781
+ if (!(first instanceof Ampersand)
782
+ || !first.hasFlag(F_IMPLICIT_AMPERSAND)
783
+ || !isNode(second, 'Combinator')) {
784
+ return s;
785
+ }
786
+ // Use live getter when present (extendWith from another ruleset); else snapshot (value.selector) after clone.
787
+ const ampResolved = first.getResolvedSelector() ?? first.value?.selector;
788
+ const ampValRaw = ampResolved?.valueOf?.();
789
+ const ctxValRaw = rulesetParentSelector?.valueOf?.();
790
+ const ampVal = ampResolved && !isNode(ampResolved, 'Nil') ? canonicalSelectorValueOf(ampResolved) : '';
791
+ const ctxVal = rulesetParentSelector ? canonicalSelectorValueOf(rulesetParentSelector) : '';
792
+ // Same context when: (1) both undefined, (2) ampersand resolves to ruleset's parent, or
793
+ // (3) ampersand resolves to this ruleset's own selector, or (4) ruleset has mixed list (.aa, .cc, & .dd, ...) and & resolves to leading prefix (.aa,.cc).
794
+ const sameContext = (ampValRaw === undefined && ctxValRaw === undefined)
795
+ || (ampVal !== '' && ctxVal !== '' && ampVal === ctxVal)
796
+ || (ampVal !== '' && rulesetOwnSelectorNormalized !== '' && ampVal === rulesetOwnSelectorNormalized)
797
+ || (ampVal !== '' && rulesetLeadingPrefixNormalized !== '' && ampVal === rulesetLeadingPrefixNormalized);
798
+ if (DEBUG_AMPERSAND_EXTEND) {
799
+ syncLog({
800
+ trace: 'materializeImplicit',
801
+ sVal: String(s.valueOf?.() ?? '').slice(0, 80),
802
+ ampVal: ampVal.slice(0, 80),
803
+ ctxVal: ctxVal.slice(0, 80),
804
+ sameContext,
805
+ willMaterialize: !sameContext && !!ampResolved && !isNode(ampResolved, 'Nil')
806
+ });
807
+ }
808
+ if (sameContext) {
809
+ return s;
810
+ }
811
+ if (!ampResolved || isNode(ampResolved, 'Nil')) {
812
+ return s;
813
+ }
814
+ let parentSelConcrete = ampResolved.copy(true);
815
+ if (isNode(parentSelConcrete, 'SelectorList')) {
816
+ parentSelConcrete = isSelectorPseudo(parentSelConcrete);
817
+ }
818
+ const out = s.copy(true);
819
+ out.value[0] = parentSelConcrete;
820
+ const outSecond = out.value[1];
821
+ if (outSecond instanceof Node) {
822
+ outSecond.addFlag(F_VISIBLE);
823
+ }
824
+ return out;
825
+ }
826
+ /**
827
+ * Apply materializeImplicitAmpersandWhenDifferentContext to each item in the normalized result
828
+ * so extendWith selectors (different context) are materialized and nested-own selectors (same context) stay implicit.
829
+ */
830
+ function materializeNormalizedWhenDifferentContext(normalized, ruleset) {
831
+ const rulesetParentSelector = getRulesetParentSelector(ruleset);
832
+ const rulesetOwnSel = ruleset.value?.selector;
833
+ const rulesetOwnSelectorNormalized = rulesetOwnSel && !isNode(rulesetOwnSel, 'Nil') ? canonicalSelectorValueOf(rulesetOwnSel) : '';
834
+ const rulesetLeadingPrefixNormalized = getRulesetSelectorLeadingPrefixNormalized(ruleset);
835
+ if (DEBUG_AMPERSAND_EXTEND) {
836
+ const ctxStr = rulesetParentSelector ? normalizedSelectorValueOf(rulesetParentSelector).slice(0, 80) : 'undefined';
837
+ const isList = isNode(normalized, 'SelectorList');
838
+ const items = isList ? normalized.value : (Array.isArray(normalized) ? normalized : [normalized]);
839
+ const itemVals = items.slice(0, 5).map((sel) => String(sel.valueOf?.() ?? '').slice(0, 60));
840
+ syncLog({
841
+ trace: 'materializeNormalized',
842
+ ctxVal: ctxStr,
843
+ ownVal: rulesetOwnSelectorNormalized.slice(0, 80),
844
+ isSelectorList: isList,
845
+ itemCount: items.length,
846
+ itemVals
847
+ });
848
+ }
849
+ // When ruleset is hoisted and the selector is our factorized form (:is(parent) :is(children) + simple),
850
+ // we output at root so there is no parent frame; do not dematerialize :is(parent) to implicit &
851
+ // or the prefix would be lost (e.g. extend-exact .rep_ace). Only skip for this shape, not all hoisted.
852
+ const isFactorizedHoistedForm = () => {
853
+ if (!ruleset.hoistToRoot) {
854
+ return false;
855
+ }
856
+ const list = normalized && isNode(normalized, 'SelectorList') ? normalized.value : null;
857
+ if (!list?.length) {
858
+ return false;
859
+ }
860
+ const first = list[0];
861
+ if (!isNode(first, 'ComplexSelector') || first.value.length < 3) {
862
+ return false;
863
+ }
864
+ const [a, b, c] = first.value;
865
+ return (isNode(a, 'PseudoSelector') && a.value?.name === ':is' && a.value?.arg
866
+ && isNode(b, 'Combinator') && b.value === ' '
867
+ && isNode(c, 'PseudoSelector') && c.value?.name === ':is'
868
+ && rulesetParentSelector
869
+ && canonicalSelectorValueOf(a.value.arg) === canonicalSelectorValueOf(rulesetParentSelector));
870
+ };
871
+ const skipDematerialize = isFactorizedHoistedForm();
872
+ const mapOne = (s) => {
873
+ const cloned = s.clone(true);
874
+ const demat = skipDematerialize
875
+ ? cloned
876
+ : dematerializeSameContextIsPrefix(cloned, rulesetParentSelector, rulesetLeadingPrefixNormalized);
877
+ return materializeImplicitAmpersandWhenDifferentContext(demat.clone(true), rulesetParentSelector, rulesetOwnSelectorNormalized, rulesetLeadingPrefixNormalized);
878
+ };
879
+ if (Array.isArray(normalized)) {
880
+ return normalized.map(mapOne);
881
+ }
882
+ // createProcessedSelector can return a SelectorList; materialize each item, not the list itself.
883
+ if (isNode(normalized, 'SelectorList')) {
884
+ const list = normalized;
885
+ const items = list.value;
886
+ if (Array.isArray(items) && items.length > 0) {
887
+ return SelectorList.create(items.map(mapOne)).inherit(list);
888
+ }
889
+ }
890
+ return mapOne(normalized);
891
+ }
892
+ /**
893
+ * Extend Roots Registry
894
+ *
895
+ * Manages extend root relationships and visibility (like ruleset .frames).
896
+ * Uses Rules node object identity (no wrapper class needed).
897
+ *
898
+ * Data architecture (mirrors ruleset frames):
899
+ * - Tree: each extend root has a parent (except document root) and children.
900
+ * parentRoot: Rules -> parent Rules, childrenRoots: Rules -> Set<Rules>.
901
+ * - Accessible roots: for a given extend root, the set of roots where we look up
902
+ * extend targets = the extend root itself + its descendants only (self + descendants).
903
+ * No ancestor targeting. So document root can see itself and all @media/child roots;
904
+ * a @media body root can see only itself and nested at-rules inside it.
905
+ * - Merge: we only merge (add selector) into rulesets whose root is extendRoot or
906
+ * a descendant (isSameOrDescendantRoot).
907
+ */
908
+ export class ExtendRootRegistry {
909
+ // Map Rules -> parent Rules (tree)
910
+ parentRoot = new WeakMap();
911
+ // Map Rules -> Set of child Rules (tree)
912
+ childrenRoots = new WeakMap();
913
+ // Map Rules -> layer name string
914
+ layerName = new WeakMap();
915
+ // Map Rules -> protected flag
916
+ isProtected = new WeakMap();
917
+ // Map Rules -> isCompose flag (compose roots create boundaries and are not accessible as children)
918
+ isCompose = new WeakMap();
919
+ // Map layer name -> Set of Rules with that name
920
+ rootsByLayerName = new Map();
921
+ // Map namespace identifier -> Set of Rules registered under that namespace
922
+ rootsByNamespace = new Map();
923
+ // Map AtRule node -> layer name (temporary storage from preEval to evalNode)
924
+ // We use AtRule as the key since we have access to it in both preEval and evalNode
925
+ layerNames = new WeakMap();
926
+ // Root of the tree
927
+ root;
928
+ // Stack for tracking current extend root (like rulesetFrames)
929
+ extendRootStack = [];
930
+ /**
931
+ * Get current extend root from stack
932
+ */
933
+ getCurrentExtendRoot() {
934
+ return this.extendRootStack[this.extendRootStack.length - 1];
935
+ }
936
+ /**
937
+ * Register a new extend root
938
+ */
939
+ registerRoot(rules, parent, options) {
940
+ // Set as root if this is the first root
941
+ if (!this.root) {
942
+ this.root = rules;
943
+ }
944
+ // Set parent relationship
945
+ if (parent) {
946
+ this.parentRoot.set(rules, parent);
947
+ // Add to parent's children
948
+ let children = this.childrenRoots.get(parent);
949
+ if (!children) {
950
+ children = new Set();
951
+ this.childrenRoots.set(parent, children);
952
+ }
953
+ children.add(rules);
954
+ }
955
+ else {
956
+ }
957
+ // Set layer name if provided
958
+ if (options?.layerName) {
959
+ this.layerName.set(rules, options.layerName);
960
+ // Add to layer name map
961
+ let layerRoots = this.rootsByLayerName.get(options.layerName);
962
+ if (!layerRoots) {
963
+ layerRoots = new Set();
964
+ this.rootsByLayerName.set(options.layerName, layerRoots);
965
+ }
966
+ layerRoots.add(rules);
967
+ }
968
+ // Set namespace if provided
969
+ if (options?.namespace) {
970
+ let nsRoots = this.rootsByNamespace.get(options.namespace);
971
+ if (!nsRoots) {
972
+ nsRoots = new Set();
973
+ this.rootsByNamespace.set(options.namespace, nsRoots);
974
+ }
975
+ nsRoots.add(rules);
976
+ }
977
+ // Set protected flag if provided
978
+ if (options?.isProtected) {
979
+ this.isProtected.set(rules, true);
980
+ }
981
+ // Set compose flag if provided (compose roots create boundaries)
982
+ if (options?.isCompose) {
983
+ this.isCompose.set(rules, true);
984
+ }
985
+ }
986
+ /**
987
+ * Push extend root to stack
988
+ */
989
+ pushExtendRoot(rules) {
990
+ this.extendRootStack.push(rules);
991
+ }
992
+ /**
993
+ * Pop extend root from stack
994
+ */
995
+ popExtendRoot() {
996
+ this.extendRootStack.pop();
997
+ }
998
+ /**
999
+ * Get roots visible to a given extend root (like ruleset .frames).
1000
+ * Alias for getAccessibleRoots; use when you mean "visible to this root".
1001
+ */
1002
+ getVisibleRoots(root) {
1003
+ return this.getAccessibleRoots(root);
1004
+ }
1005
+ /**
1006
+ * Get accessible (visible) roots for a given root.
1007
+ *
1008
+ * Visible roots = where we can look up rulesets for extend targets:
1009
+ * - Self (the current root)
1010
+ * - Self (the root)
1011
+ * - Descendant roots (children, recursively; stop at protected)
1012
+ * - Roots with same layer name (for @layer, if accessible)
1013
+ *
1014
+ * Excludes:
1015
+ * - Ancestor roots (we do not support extend targeting ancestors)
1016
+ * - Roots behind protected boundaries (stop traversal at protected roots)
1017
+ * - Siblings (other children of ancestors, unless same layer)
1018
+ *
1019
+ * Note: @import type uses parent's root, so extends inside @import use that root's
1020
+ * self + descendants. @compose type creates its own root and may be protected.
1021
+ */
1022
+ getAccessibleRoots(root) {
1023
+ const accessible = new Set();
1024
+ const visited = new Set();
1025
+ // Helper to traverse children recursively (downward)
1026
+ const traverseChildren = (currentRoot) => {
1027
+ if (visited.has(currentRoot)) {
1028
+ return;
1029
+ }
1030
+ visited.add(currentRoot);
1031
+ // Add self
1032
+ accessible.add(currentRoot);
1033
+ // Check if this root is protected - if so, stop traversal into children
1034
+ if (this.isProtected.get(currentRoot)) {
1035
+ return;
1036
+ }
1037
+ // Add children (recursively)
1038
+ // Only add non-protected children
1039
+ // - Protected roots block access (including protected compose roots)
1040
+ // - Non-protected compose roots (mutable: true) ARE accessible as children
1041
+ // - Import type roots: protected imports (mutable: false) are NOT accessible, non-protected imports are accessible
1042
+ const children = this.childrenRoots.get(currentRoot);
1043
+ if (children) {
1044
+ for (const child of children) {
1045
+ // Skip protected children - they should not be accessible
1046
+ // This includes protected compose roots (mutable: false or default)
1047
+ if (this.isProtected.get(child)) {
1048
+ continue;
1049
+ }
1050
+ // Non-protected compose roots (mutable: true) ARE accessible
1051
+ // Only protected compose roots create boundaries
1052
+ // So we don't skip compose children here - we only skip if they're protected
1053
+ traverseChildren(child);
1054
+ }
1055
+ }
1056
+ // When collapseNesting wraps at-rule body in Ruleset(&), rulesets live in the inner Rules.
1057
+ // Include inner Rules of every child Ruleset so extends find nested rulesets (e.g. .rep_ace:extend(.replace all)
1058
+ // must find the nested ruleset with selector .replace, .c and extend it to .replace, .rep_ace, .c).
1059
+ // Also when a root has one child that is Rules (post-eval unwrapped), include that child.
1060
+ if (currentRoot.value?.length) {
1061
+ for (const node of currentRoot.value) {
1062
+ if (node && isNode(node, 'Ruleset') && node.value?.rules && isNode(node.value.rules, 'Rules')) {
1063
+ const innerRules = node.value.rules;
1064
+ if (!visited.has(innerRules)) {
1065
+ accessible.add(innerRules);
1066
+ traverseChildren(innerRules);
1067
+ }
1068
+ }
1069
+ else if (node && isNode(node, 'Rules')) {
1070
+ const innerRules = node;
1071
+ if (!visited.has(innerRules)) {
1072
+ accessible.add(innerRules);
1073
+ traverseChildren(innerRules);
1074
+ }
1075
+ }
1076
+ }
1077
+ }
1078
+ // Add roots with same layer name (if this root has a layer name)
1079
+ const layerName = this.layerName.get(currentRoot);
1080
+ if (layerName) {
1081
+ const sameLayerRoots = this.rootsByLayerName.get(layerName);
1082
+ if (sameLayerRoots) {
1083
+ for (const layerRoot of sameLayerRoots) {
1084
+ if (layerRoot !== currentRoot && !visited.has(layerRoot)) {
1085
+ // Check if layer root is accessible (not behind protected boundary)
1086
+ if (!this.isProtected.get(layerRoot)) {
1087
+ accessible.add(layerRoot);
1088
+ // Also traverse its children
1089
+ traverseChildren(layerRoot);
1090
+ }
1091
+ }
1092
+ }
1093
+ }
1094
+ }
1095
+ };
1096
+ // Self + descendants only. No ancestor targeting.
1097
+ traverseChildren(root);
1098
+ return accessible;
1099
+ }
1100
+ /**
1101
+ * True if rulesetRoot is extendRoot or any descendant of extendRoot.
1102
+ * Used to only merge extend into rulesets in the same or a child root (not ancestor).
1103
+ */
1104
+ isSameOrDescendantRoot(rulesetRoot, extendRoot) {
1105
+ if (rulesetRoot === extendRoot) {
1106
+ return true;
1107
+ }
1108
+ // Same-layer roots share extend scope (e.g. two @layer one { } blocks merge).
1109
+ const layerA = this.layerName.get(rulesetRoot);
1110
+ const layerB = this.layerName.get(extendRoot);
1111
+ if (layerA && layerB && layerA === layerB) {
1112
+ return true;
1113
+ }
1114
+ const children = this.childrenRoots.get(extendRoot);
1115
+ if (!children) {
1116
+ return false;
1117
+ }
1118
+ for (const child of children) {
1119
+ if (this.isSameOrDescendantRoot(rulesetRoot, child)) {
1120
+ return true;
1121
+ }
1122
+ }
1123
+ return false;
1124
+ }
1125
+ /**
1126
+ * True if possibleAncestor is an ancestor of root (walking parentRoot up from root).
1127
+ * Used only to reject merging into rulesets in an ancestor root. Extend only alters
1128
+ * selectors (e.g. adding to selector lists); it does not copy declarations between rulesets.
1129
+ */
1130
+ isAncestorRoot(possibleAncestor, root) {
1131
+ let current = this.parentRoot.get(root);
1132
+ while (current) {
1133
+ if (current === possibleAncestor) {
1134
+ return true;
1135
+ }
1136
+ current = this.parentRoot.get(current);
1137
+ }
1138
+ return false;
1139
+ }
1140
+ /**
1141
+ * Get parent extend root (for same-block detection when collapseNesting creates two inner Rules refs).
1142
+ */
1143
+ getParentRoot(root) {
1144
+ return this.parentRoot.get(root);
1145
+ }
1146
+ /**
1147
+ * Get layer name for a Rules root
1148
+ */
1149
+ getRootLayerName(root) {
1150
+ return this.layerName.get(root);
1151
+ }
1152
+ /**
1153
+ * Store pending layer name for an AtRule node (from preEval)
1154
+ * This will be used when the actual Rules is registered in evalNode
1155
+ */
1156
+ setLayerName(atRule, layerName) {
1157
+ this.layerNames.set(atRule, layerName);
1158
+ }
1159
+ /**
1160
+ * Get layer name for an AtRule (stored during preEval, retrieved in evalNode)
1161
+ * Does NOT delete - use takeLayerName to get and delete
1162
+ */
1163
+ getLayerName(atRule) {
1164
+ return this.layerNames.get(atRule);
1165
+ }
1166
+ /**
1167
+ * Get and delete layer name for an AtRule (used when registering the root)
1168
+ */
1169
+ takeLayerName(atRule) {
1170
+ const layerName = this.layerNames.get(atRule);
1171
+ if (layerName) {
1172
+ this.layerNames.delete(atRule);
1173
+ }
1174
+ return layerName;
1175
+ }
1176
+ /**
1177
+ * Get all registered roots (for checking if a target exists anywhere)
1178
+ * This includes all roots regardless of accessibility
1179
+ */
1180
+ getAlts() {
1181
+ const allRoots = new Set();
1182
+ // Start from the main root and traverse all children
1183
+ if (this.root) {
1184
+ const traverse = (currentRoot) => {
1185
+ if (allRoots.has(currentRoot)) {
1186
+ return;
1187
+ }
1188
+ allRoots.add(currentRoot);
1189
+ const children = this.childrenRoots.get(currentRoot);
1190
+ if (children) {
1191
+ for (const child of children) {
1192
+ traverse(child);
1193
+ }
1194
+ }
1195
+ };
1196
+ traverse(this.root);
1197
+ }
1198
+ return allRoots;
1199
+ }
1200
+ /**
1201
+ * Get roots registered for a given namespace identifier.
1202
+ */
1203
+ getByNamespace(namespace) {
1204
+ return this.rootsByNamespace.get(namespace) ?? new Set();
1205
+ }
1206
+ /**
1207
+ * Extract layer name from AtRule prelude
1208
+ * Returns undefined for anonymous layers
1209
+ */
1210
+ extractLayerName(atRule, parentLayerName) {
1211
+ const { prelude } = atRule.value;
1212
+ if (!prelude) {
1213
+ // Anonymous layer - no name
1214
+ return undefined;
1215
+ }
1216
+ // Evaluate prelude if needed (should be static by extend time)
1217
+ // For now, assume it's already evaluated or can be converted to string
1218
+ const preludeStr = prelude.toTrimmedString();
1219
+ // If parent layer name provided, concatenate
1220
+ if (parentLayerName) {
1221
+ return `${parentLayerName}.${preludeStr}`;
1222
+ }
1223
+ return preludeStr;
1224
+ }
1225
+ }
1226
+ /**
1227
+ * Processes all extends registered in the context.
1228
+ * This function handles the complete extend processing pipeline:
1229
+ * 1. Depth-first processing of all original extends
1230
+ * 2. Iterative multi-pass processing of extended rulesets
1231
+ *
1232
+ * All extend processing logic is centralized here, not in rules.ts
1233
+ */
1234
+ export function processExtends(context) {
1235
+ const allExtends = [...context.extends]
1236
+ .sort((a, b) => (a[5] ?? 0) - (b[5] ?? 0)); // Process in depth-first document order
1237
+ if (allExtends.length >= 2) {
1238
+ const orderStr = allExtends.map((e, i) => `${i}:${e[5] ?? 'u'}=${typeof e[1]?.valueOf === 'function' ? String(e[1].valueOf()) : ''}`).join(' ');
1239
+ syncLog({ tag: 'processExtends_afterSort', orderStr });
1240
+ }
1241
+ const trace = shouldTraceExtend(context);
1242
+ const runId = getExtendTraceRunId(context);
1243
+ if (trace) {
1244
+ const opts = context.opts;
1245
+ const collapseNesting = Boolean(opts?.collapseNesting ?? opts?.output?.collapseNesting);
1246
+ const allRootsForLog = context.extendRoots.getAlts();
1247
+ const allRootsArrForLog = Array.isArray(allRootsForLog) ? allRootsForLog : [...allRootsForLog];
1248
+ const rootSummaries = [];
1249
+ for (const r of allRootsArrForLog) {
1250
+ let serializedHead = '';
1251
+ try {
1252
+ serializedHead = serializeTypes(r, { maxStringLength: 80, indentSize: 1 }).slice(0, 500);
1253
+ }
1254
+ catch {
1255
+ serializedHead = '(serialize error)';
1256
+ }
1257
+ const reg = r.getRegistry('ruleset');
1258
+ const indexSize = reg.index?.size ?? 0;
1259
+ const pendingSize = reg.pendingItems?.size ?? 0;
1260
+ const keys = [];
1261
+ if (reg.index) {
1262
+ for (const k of reg.index.keys()) {
1263
+ keys.push(k);
1264
+ if (keys.length >= 20) {
1265
+ break;
1266
+ }
1267
+ }
1268
+ }
1269
+ rootSummaries.push({
1270
+ summary: rulesStructureSummary(r),
1271
+ serializedHead,
1272
+ registryIndexSize: indexSize,
1273
+ registryPendingSize: pendingSize,
1274
+ registryKeys: keys
1275
+ });
1276
+ }
1277
+ const extendsSummary = allExtends.map(([target, sel, partial, extRoot]) => ({
1278
+ target: typeof target?.valueOf === 'function' ? target.valueOf() : '',
1279
+ selectorWithExtend: typeof sel?.valueOf === 'function' ? sel.valueOf() : '',
1280
+ partial,
1281
+ extendRootSummary: rulesStructureSummary(extRoot)
1282
+ }));
1283
+ syncLog({
1284
+ trace: 'processExtends_enter',
1285
+ runId,
1286
+ collapseNesting,
1287
+ allRootsCount: allRootsArrForLog.length,
1288
+ rootSummaries,
1289
+ extendsCount: allExtends.length,
1290
+ extendsSummary
1291
+ });
1292
+ }
1293
+ // NOTE: We must NOT globally de-dupe extends by (target, extendWith, partial).
1294
+ // The same extend relationship must be applied to *any* ruleset whose selector matches
1295
+ // the target, including selectors that become matchable only after previous extends.
1296
+ // De-duping happens per-ruleset via `transformedByExtend`.
1297
+ const processedExtends = new Set(); // Track in-flight recursion only (used as a stack guard)
1298
+ const extendedRulesets = new Set(); // Track rulesets that were extended
1299
+ // Track extend order for source-order preservation when merging into :is()
1300
+ // WeakMap by selector object; clones (e.g. inside :is()) use orderByValueOf fallback.
1301
+ const extendOrderMap = new WeakMap();
1302
+ const extendOrderByValueOf = new Map();
1303
+ for (let i = 0; i < allExtends.length; i++) {
1304
+ const [, selectorWithExtend] = allExtends[i];
1305
+ extendOrderMap.set(selectorWithExtend, i);
1306
+ const v = (typeof selectorWithExtend.valueOf === 'function' ? String(selectorWithExtend.valueOf()) : '').trim();
1307
+ if (v) {
1308
+ extendOrderByValueOf.set(v, i);
1309
+ }
1310
+ }
1311
+ setExtendOrderMap(extendOrderMap, extendOrderByValueOf);
1312
+ // Track which extends have already transformed which rulesets: Map<rulesetId, Set<extendKey>>
1313
+ // Each extend can only transform a particular ruleset's selector once
1314
+ const transformedByExtend = new Map();
1315
+ // Track exact extends that were rejected for a ruleset (e.g. .bb .bb rejected .bb:extend(.ee)).
1316
+ // Phase 2 must not re-apply those to the same ruleset when its selector is later flattened to a list.
1317
+ const rejectedExactExtendByRuleset = new Map();
1318
+ const allRoots = context.extendRoots.getAlts();
1319
+ const allRootsArr = Array.isArray(allRoots) ? allRoots : [...allRoots];
1320
+ /** Walk up from a ruleset to the nearest Rules that is a registered extend root. */
1321
+ const getEffectiveExtendRoot = (ruleset) => {
1322
+ let n = ruleset;
1323
+ while (n) {
1324
+ const p = n.parent;
1325
+ if (p && isNode(p, 'Rules') && allRoots.has(p)) {
1326
+ return p;
1327
+ }
1328
+ n = p;
1329
+ }
1330
+ return undefined;
1331
+ };
1332
+ /**
1333
+ * Helper to re-index a ruleset's registry after selector update
1334
+ * Simply adds the ruleset back to the registry - it will be indexed automatically
1335
+ * when searched. Since the ruleset object is the same, existing keys remain,
1336
+ * and new keys from the updated selector will be added automatically.
1337
+ */
1338
+ const reindexRuleset = (ruleset) => {
1339
+ // Find which extend root this ruleset is registered to and add it back
1340
+ for (const root of allRootsArr) {
1341
+ const registry = root.getRegistry('ruleset');
1342
+ // Check if ruleset is already indexed in this registry
1343
+ for (const rulesetSet of registry.index.values()) {
1344
+ if (rulesetSet.has(ruleset)) {
1345
+ // Add back to pendingItems - will be indexed with new selector's keySet automatically
1346
+ registry.add(ruleset);
1347
+ return;
1348
+ }
1349
+ }
1350
+ }
1351
+ };
1352
+ /** True when node is a strict descendant of ancestor (child, grandchild, etc.). */
1353
+ const isDescendantOf = (node, ancestor) => {
1354
+ for (let p = node.parent; p; p = p.parent) {
1355
+ if (p === ancestor) {
1356
+ return true;
1357
+ }
1358
+ }
1359
+ return false;
1360
+ };
1361
+ /** True when some ancestor ruleset's selector matches the single target (for skip-nested-under-match). */
1362
+ const hasAncestorMatchingTarget = (ruleset, singleTarget, partial) => {
1363
+ for (let p = ruleset.parent; p; p = p.parent) {
1364
+ if (!isNode(p, 'Ruleset')) {
1365
+ continue;
1366
+ }
1367
+ const rs = p;
1368
+ const sel = rs.selector;
1369
+ if (!sel || isNode(sel, 'Nil')) {
1370
+ continue;
1371
+ }
1372
+ if (selectorMatchesExtendTarget(sel, singleTarget, partial))
1373
+ return true;
1374
+ }
1375
+ return false;
1376
+ };
1377
+ /** True when the ruleset's selector is entirely in nested form (every list item starts with implicit &). */
1378
+ const selectorIsNestedWithImplicitAmpersand = (sel) => {
1379
+ if (!sel || isNode(sel, 'Nil')) {
1380
+ return false;
1381
+ }
1382
+ if (!isNode(sel, 'SelectorList')) {
1383
+ return false;
1384
+ }
1385
+ const list = sel;
1386
+ if (!list.value?.length) {
1387
+ return false;
1388
+ }
1389
+ for (const item of list.value) {
1390
+ if (!isNode(item, 'ComplexSelector')) {
1391
+ return false;
1392
+ }
1393
+ const first = item.value[0];
1394
+ if (!(first instanceof Ampersand && first.hasFlag(F_IMPLICIT_AMPERSAND))) {
1395
+ return false;
1396
+ }
1397
+ }
1398
+ return true;
1399
+ };
1400
+ /** True when the extend node is a direct or nested child of the ruleset's rules. */
1401
+ const rulesetContainsExtend = (ruleset, extendNode) => {
1402
+ if (!ruleset.value.rules || !('value' in ruleset.value.rules)) {
1403
+ return false;
1404
+ }
1405
+ const rules = ruleset.value.rules.value;
1406
+ if (!Array.isArray(rules)) {
1407
+ return false;
1408
+ }
1409
+ const findNode = (nodes) => {
1410
+ for (const node of nodes) {
1411
+ if (node === extendNode) {
1412
+ return true;
1413
+ }
1414
+ if ('value' in node && Array.isArray(node.value)) {
1415
+ if (findNode(node.value)) {
1416
+ return true;
1417
+ }
1418
+ }
1419
+ }
1420
+ return false;
1421
+ };
1422
+ return findNode(rules);
1423
+ };
1424
+ /**
1425
+ * Logical exclusion rule: A ruleset should not be extended if the extend is associated with that ruleset
1426
+ * (either as a child or as a prepended sibling). This prevents self-modification.
1427
+ * The extend utility handles selector matching - we just check structural association here.
1428
+ */
1429
+ const shouldSkipRuleset = (ruleset, extendNode) => {
1430
+ // Check 1: Is extend a child of the ruleset?
1431
+ if (rulesetContainsExtend(ruleset, extendNode)) {
1432
+ return true; // Extend is a child - skip this ruleset
1433
+ }
1434
+ // Check 2: Is extend a sibling that precedes this ruleset in a Rules parent?
1435
+ const parent = ruleset.parent;
1436
+ if (parent && isNode(parent, 'Rules')) {
1437
+ const siblings = parent.value;
1438
+ const rulesetIndex = siblings.indexOf(ruleset);
1439
+ if (rulesetIndex > 0) {
1440
+ // Search backwards from the ruleset
1441
+ for (let i = rulesetIndex - 1; i >= 0; i--) {
1442
+ const sibling = siblings[i];
1443
+ if (!sibling) {
1444
+ continue;
1445
+ }
1446
+ // If we encounter an at-rule or another ruleset, the extend is NOT prepended
1447
+ if (isNode(sibling, 'AtRule') || isNode(sibling, 'Ruleset')) {
1448
+ break; // Stop searching - extend can apply
1449
+ }
1450
+ // If we find the extend node, it's prepended
1451
+ if (sibling === extendNode) {
1452
+ return true;
1453
+ }
1454
+ // Also check if sibling is a Rules containing the extend
1455
+ if (isNode(sibling, 'Rules')) {
1456
+ const findInRules = (rules) => {
1457
+ for (const node of rules.value) {
1458
+ if (node === extendNode) {
1459
+ return true;
1460
+ }
1461
+ if (isNode(node, 'Rules')) {
1462
+ if (findInRules(node)) {
1463
+ return true;
1464
+ }
1465
+ }
1466
+ }
1467
+ return false;
1468
+ };
1469
+ if (findInRules(sibling)) {
1470
+ return true; // Extend is prepended - skip this ruleset
1471
+ }
1472
+ }
1473
+ }
1474
+ }
1475
+ }
1476
+ // Selectors match but extend is not associated with this ruleset
1477
+ return false;
1478
+ };
1479
+ /**
1480
+ * Process a single extend recursively (depth-first)
1481
+ */
1482
+ const processExtend = (target, selectorWithExtend, partial, extendRoot, extendNode, depth = 0) => {
1483
+ const maxDepth = 100; // Prevent infinite loops
1484
+ if (depth >= maxDepth) {
1485
+ throw new Error(`Extend chaining exceeded maximum depth (${maxDepth}). Possible circular reference.`);
1486
+ }
1487
+ // Skip self-referencing extends
1488
+ if (target.valueOf() === selectorWithExtend.valueOf()) {
1489
+ return;
1490
+ }
1491
+ // Create a recursion-guard key. This is NOT a global "already applied" key.
1492
+ // It's only meant to prevent infinite recursion for cyclic chaining.
1493
+ const extendKey = `${target.valueOf()}:${selectorWithExtend.valueOf()}:${partial}:${extendRoot === context.root ? 'root' : 'nested'}`;
1494
+ if (processedExtends.has(extendKey)) {
1495
+ return;
1496
+ }
1497
+ processedExtends.add(extendKey);
1498
+ // Determine which roots to search for this extend.
1499
+ // - If extend specifies a namespace:
1500
+ // - '*' searches all file roots
1501
+ // - otherwise search only roots registered for that namespace
1502
+ // - Otherwise, use the accessibility model from the extend's own root.
1503
+ const extendNamespace = extendNode.type === 'Extend'
1504
+ ? extendNode.value.namespace
1505
+ : undefined;
1506
+ let accessibleRoots = extendNamespace
1507
+ ? (extendNamespace === '*' ? context.extendRoots.getAlts() : context.extendRoots.getByNamespace(extendNamespace))
1508
+ : context.extendRoots.getAccessibleRoots(extendRoot);
1509
+ // When collapseNesting wraps at-rule rules in Ruleset(&), rulesets register to the inner Rules.
1510
+ // Ensure we search those inner Rules so same-root extends (e.g. .ma:extend(.md) in @media) find targets.
1511
+ const rootsToSearch = new Set(accessibleRoots);
1512
+ // Explicitly walk extendRoot's tree to add every descendant Rules (ruleset.value.rules) so we
1513
+ // always find nested rulesets (e.g. .rep_ace:extend(.replace all) must extend the inner .replace, .c ruleset).
1514
+ const walkRules = (rules, visited) => {
1515
+ if (visited.has(rules)) {
1516
+ return;
1517
+ }
1518
+ visited.add(rules);
1519
+ rootsToSearch.add(rules);
1520
+ for (const node of rules.value) {
1521
+ if (node && isNode(node, 'Ruleset') && node.value?.rules && isNode(node.value.rules, 'Rules')) {
1522
+ walkRules(node.value.rules, visited);
1523
+ }
1524
+ }
1525
+ };
1526
+ walkRules(extendRoot, new Set());
1527
+ accessibleRoots = rootsToSearch;
1528
+ // If target is a SelectorList (e.g., .aa, .bb), process each selector separately
1529
+ const targetSelectors = isNode(target, 'SelectorList')
1530
+ ? target.value
1531
+ : [target];
1532
+ for (const singleTarget of targetSelectors) {
1533
+ // Skip self-referencing extends for individual selectors too
1534
+ if (singleTarget.valueOf() === selectorWithExtend.valueOf()) {
1535
+ continue;
1536
+ }
1537
+ const singleTargetStr = typeof singleTarget.valueOf === 'function' ? singleTarget.valueOf() : '';
1538
+ const traceMd = shouldTraceExtendMd(context, singleTargetStr);
1539
+ if (traceMd) {
1540
+ let extendRootSerialized;
1541
+ try {
1542
+ extendRootSerialized = serializeTypes(extendRoot, { maxStringLength: 60, indentSize: 1 }).slice(0, 800);
1543
+ }
1544
+ catch {
1545
+ extendRootSerialized = '(serialize error)';
1546
+ }
1547
+ const opts = context.opts;
1548
+ syncLog({
1549
+ trace: 'processExtend_start',
1550
+ runId: getExtendTraceRunId(context),
1551
+ collapseNesting: Boolean(opts?.collapseNesting ?? opts?.output?.collapseNesting),
1552
+ target: singleTargetStr,
1553
+ selectorWithExtend: typeof selectorWithExtend.valueOf === 'function' ? selectorWithExtend.valueOf() : '',
1554
+ extendRootSummary: rulesStructureSummary(extendRoot),
1555
+ extendRootSerialized,
1556
+ rootsToSearchCount: accessibleRoots.size,
1557
+ rootsToSearchSummaries: [...accessibleRoots].map(r => rulesStructureSummary(r))
1558
+ });
1559
+ }
1560
+ // Find rulesets matching this single target in accessible roots
1561
+ let rulesetSet;
1562
+ for (const searchRoot of accessibleRoots) {
1563
+ const searchKeySet = singleTarget.keySet;
1564
+ let found = searchRoot.find('ruleset', searchKeySet);
1565
+ // Fallback: scan searchRoot (and all nested Rules) for matching rulesets so we find
1566
+ // nested rulesets at any depth, even when the registry was built on a different Rules
1567
+ // instance (e.g. after eval clone) or when collapseNesting wraps at-rule body in Ruleset(&).
1568
+ // Always recurse into every Ruleset's rules and every direct Rules child—no special cases for root shape.
1569
+ if (searchRoot.value?.length) {
1570
+ const scanRules = (rules) => {
1571
+ for (const node of rules.value) {
1572
+ if (isNode(node, 'Ruleset')) {
1573
+ const rs = node;
1574
+ const sel = rs.selector;
1575
+ if (sel && !isNode(sel, 'Nil') && selectorMatchesExtendTarget(sel, singleTarget, partial)) {
1576
+ const existing = found ?? [];
1577
+ if (!existing.includes(rs)) {
1578
+ found = existing.length ? [...existing, rs] : [rs];
1579
+ }
1580
+ }
1581
+ if (rs.value?.rules && isNode(rs.value.rules, 'Rules')) {
1582
+ scanRules(rs.value.rules);
1583
+ }
1584
+ }
1585
+ else if (isNode(node, 'Rules')) {
1586
+ scanRules(node);
1587
+ }
1588
+ }
1589
+ };
1590
+ scanRules(searchRoot);
1591
+ }
1592
+ if (traceMd) {
1593
+ syncLog({
1594
+ trace: 'search_root',
1595
+ runId: getExtendTraceRunId(context),
1596
+ searchRootSummary: rulesStructureSummary(searchRoot),
1597
+ foundCount: found ? found.length : 0,
1598
+ foundSelectors: found ? found.map(rs => typeof rs.selector?.valueOf === 'function' ? rs.selector.valueOf() : '') : []
1599
+ });
1600
+ }
1601
+ if (found && !partial) {
1602
+ found = found.filter((rs) => !selectorIsTargetWithDescendant(rs, singleTarget));
1603
+ }
1604
+ if (found) {
1605
+ // Only merge into rulesets in extendRoot or in a descendant root of extendRoot.
1606
+ // Do NOT merge into rulesets in an ancestor root (e.g. .ma in @media extending .a at root
1607
+ // must not add .ma to the root .a ruleset; .tv-lowres in @media must not add to root).
1608
+ // Root .all:extend(.ext1) may add .all to .ext1 rulesets in root and in nested @media (descendants).
1609
+ const filterRejectsTrace = [];
1610
+ const sameOrDescendantRoot = found.filter((rs) => {
1611
+ // When extendRoot is a wrapper (Ruleset(&) with inner Rules), target in that inner Rules
1612
+ // is same-root; allow merge so .ma:extend(.md) in @media finds .md. Check first so we
1613
+ // don't reject due to effectiveRoot walking up to an unregistered clone.
1614
+ if (extendRoot.value?.length === 1
1615
+ && extendRoot.value[0] != null
1616
+ && isNode(extendRoot.value[0], 'Ruleset')) {
1617
+ const innerRules = extendRoot.value[0].value?.rules;
1618
+ if (innerRules != null && rs.parent === innerRules) {
1619
+ return true;
1620
+ }
1621
+ }
1622
+ // When extendRoot is a Rules with one child that is Rules (post-eval unwrapped body),
1623
+ // target in that child is same-root.
1624
+ if (extendRoot.value?.length === 1
1625
+ && extendRoot.value[0] != null
1626
+ && isNode(extendRoot.value[0], 'Rules')
1627
+ && rs.parent === extendRoot.value[0]) {
1628
+ return true;
1629
+ }
1630
+ const effectiveRoot = getEffectiveExtendRoot(rs);
1631
+ if (!effectiveRoot) {
1632
+ return true;
1633
+ }
1634
+ // Extend at document root can target any ruleset in the same root (e.g. .footer-nav:extend(.header .header-nav) finding nested .header-nav).
1635
+ if (extendRoot === context.root && effectiveRoot === context.root) {
1636
+ return true;
1637
+ }
1638
+ if (context.extendRoots.isAncestorRoot(effectiveRoot, extendRoot)
1639
+ && effectiveRoot !== extendRoot) {
1640
+ if (traceMd) {
1641
+ filterRejectsTrace.push({
1642
+ sel: typeof rs.selector?.valueOf === 'function' ? rs.selector.valueOf() : '',
1643
+ reason: 'isAncestorRoot'
1644
+ });
1645
+ }
1646
+ return false;
1647
+ }
1648
+ // Same or descendant: merge into rulesets in extendRoot or its descendants.
1649
+ if (context.extendRoots.isSameOrDescendantRoot(effectiveRoot, extendRoot)) {
1650
+ return true;
1651
+ }
1652
+ // effectiveRoot is extendRoot's inner Rules (same object).
1653
+ if (extendRoot.value?.length === 1
1654
+ && extendRoot.value[0] != null
1655
+ && isNode(extendRoot.value[0], 'Ruleset')) {
1656
+ const innerRules = extendRoot.value[0].value?.rules;
1657
+ if (innerRules === effectiveRoot) {
1658
+ return true;
1659
+ }
1660
+ }
1661
+ // When collapseNesting wraps at-rule body in a wrapper, rulesets can live in a clone that's
1662
+ // not in allRoots, so getEffectiveExtendRoot walks up to the wrapper. Allow merge only when
1663
+ // effectiveRoot is that wrapper (one child Ruleset with inner Rules), not any ancestor.
1664
+ const isAncestor = context.extendRoots.isAncestorRoot(effectiveRoot, extendRoot);
1665
+ const isDocRoot = effectiveRoot === context.root;
1666
+ const effIsWrapper = effectiveRoot.value?.length === 1
1667
+ && effectiveRoot.value[0] != null
1668
+ && isNode(effectiveRoot.value[0], 'Ruleset')
1669
+ && effectiveRoot.value[0].value?.rules != null
1670
+ && isNode(effectiveRoot.value[0].value.rules, 'Rules');
1671
+ if (isAncestor && !isDocRoot && effIsWrapper) {
1672
+ return true;
1673
+ }
1674
+ const effectiveParent = context.extendRoots.getParentRoot(effectiveRoot);
1675
+ const extendParent = context.extendRoots.getParentRoot(extendRoot);
1676
+ if (effectiveParent && extendParent && effectiveParent === extendParent) {
1677
+ return true;
1678
+ }
1679
+ // Same AST parent: two inner Rules (e.g. clone A vs clone B) under the same wrapper.
1680
+ if (effectiveRoot.parent === extendRoot.parent) {
1681
+ return true;
1682
+ }
1683
+ // Same wrapper (grandparent): inner Rules may have different Ruleset parents after eval.
1684
+ const ep = effectiveRoot.parent;
1685
+ const xp = extendRoot.parent;
1686
+ if (ep
1687
+ && xp
1688
+ && ep !== xp
1689
+ && isNode(ep, 'Ruleset')
1690
+ && isNode(xp, 'Ruleset')
1691
+ && ep.parent === xp.parent) {
1692
+ return true;
1693
+ }
1694
+ // extendRoot is detached (preEval clone never attached after eval); target is in inner Rules under wrapper.
1695
+ const effectiveIsInner = ep
1696
+ && isNode(ep, 'Ruleset')
1697
+ && ep.parent
1698
+ && isNode(ep.parent, 'Rules')
1699
+ && ep.parent.value?.length === 1;
1700
+ if (!extendRoot.parent && effectiveIsInner) {
1701
+ return true;
1702
+ }
1703
+ // Target's root has no parent (detached inner Rules); allow. Exclude document root.
1704
+ if (!effectiveRoot.parent && effectiveRoot !== context.root) {
1705
+ return true;
1706
+ }
1707
+ // Target ruleset's direct parent (inner Rules) not in allRoots; getEffectiveExtendRoot walked to doc root.
1708
+ const targetInner = rs.parent;
1709
+ const targetWrapper = targetInner?.parent?.parent
1710
+ && isNode(targetInner.parent, 'Ruleset')
1711
+ && isNode(targetInner.parent.parent, 'Rules')
1712
+ ? targetInner.parent.parent
1713
+ : undefined;
1714
+ const extendWrapper = xp?.parent && isNode(xp.parent, 'Rules') ? xp.parent : undefined;
1715
+ if (targetWrapper
1716
+ && extendWrapper
1717
+ && targetWrapper === extendWrapper
1718
+ && targetInner
1719
+ && isNode(targetInner, 'Rules')
1720
+ && !allRoots.has(targetInner)) {
1721
+ return true;
1722
+ }
1723
+ // effectiveRoot === context.root but target is nested; extendRoot is nested. Only when target's parent Rules is not in allRoots (collapseNesting inner clone).
1724
+ if (effectiveRoot === context.root
1725
+ && rs.parent !== context.root
1726
+ && extendRoot.parent?.parent != null
1727
+ && rs.parent
1728
+ && isNode(rs.parent, 'Rules')
1729
+ && !allRoots.has(rs.parent)) {
1730
+ return true;
1731
+ }
1732
+ if (traceMd) {
1733
+ const effInAll = allRoots.has(effectiveRoot);
1734
+ const extInAll = allRoots.has(extendRoot);
1735
+ filterRejectsTrace.push({
1736
+ sel: typeof rs.selector?.valueOf === 'function' ? rs.selector.valueOf() : '',
1737
+ reason: `fallthrough effInAll=${effInAll} extInAll=${extInAll} effLen=${effectiveRoot.value?.length} extLen=${extendRoot.value?.length} rsParentLen=${rs.parent?.value?.length}`
1738
+ });
1739
+ }
1740
+ return false;
1741
+ });
1742
+ if (traceMd) {
1743
+ syncLog({
1744
+ trace: 'filter_result',
1745
+ runId: getExtendTraceRunId(context),
1746
+ foundCount: found.length,
1747
+ sameOrDescendantRootCount: sameOrDescendantRoot.length,
1748
+ filterRejects: filterRejectsTrace
1749
+ });
1750
+ }
1751
+ if (sameOrDescendantRoot.length > 0) {
1752
+ if (rulesetSet) {
1753
+ rulesetSet.push(...sameOrDescendantRoot);
1754
+ }
1755
+ else {
1756
+ rulesetSet = sameOrDescendantRoot;
1757
+ }
1758
+ }
1759
+ }
1760
+ }
1761
+ if (traceMd) {
1762
+ syncLog({
1763
+ trace: 'after_search',
1764
+ runId: getExtendTraceRunId(context),
1765
+ rulesetSetLength: rulesetSet?.length ?? 0
1766
+ });
1767
+ }
1768
+ // Handle warnings for Less compatibility (only on first processing)
1769
+ if (!rulesetSet || rulesetSet.length === 0) {
1770
+ // Check if target exists anywhere (not just in accessible roots)
1771
+ const allRootsForWarning = context.extendRoots.getAlts();
1772
+ let targetExistsElsewhere = false;
1773
+ let existsCount = 0;
1774
+ for (const searchRoot of allRootsForWarning) {
1775
+ if (!accessibleRoots.has(searchRoot)) {
1776
+ const found = searchRoot.find('ruleset', singleTarget.keySet);
1777
+ if (found && found.length > 0) {
1778
+ targetExistsElsewhere = true;
1779
+ existsCount += found.length;
1780
+ break;
1781
+ }
1782
+ }
1783
+ }
1784
+ // Collect warnings (only on first processing)
1785
+ if (depth === 0) {
1786
+ if (targetExistsElsewhere) {
1787
+ const warning = WARN.extendNotAccessible({
1788
+ ctx: context.treeContext?.file ? { file: context.treeContext.file } : undefined,
1789
+ node: extendNode.location && extendNode.location.length === 6 ? { location: extendNode.location } : undefined,
1790
+ meta: { target: singleTarget.valueOf() }
1791
+ });
1792
+ const warningDiag = toDiagnostic(warning);
1793
+ if (!('errors' in warningDiag)) {
1794
+ context.warnings.push(warningDiag);
1795
+ }
1796
+ }
1797
+ else {
1798
+ const warning = WARN.extendNotFound({
1799
+ ctx: context.treeContext?.file ? { file: context.treeContext.file } : undefined,
1800
+ node: extendNode.location && extendNode.location.length === 6 ? { location: extendNode.location } : undefined,
1801
+ meta: { target: singleTarget.valueOf() }
1802
+ });
1803
+ const warningDiag = toDiagnostic(warning);
1804
+ if (!('errors' in warningDiag)) {
1805
+ context.warnings.push(warningDiag);
1806
+ }
1807
+ }
1808
+ }
1809
+ }
1810
+ // Capture each ruleset's parent selector string before we update any (so we can detect
1811
+ // nested rulesets whose selector equals parent's and use ownSelector for extend).
1812
+ const parentSelectorAtStart = new Map();
1813
+ if (rulesetSet) {
1814
+ for (const rs of rulesetSet) {
1815
+ const pr = rs.parent?.parent;
1816
+ if (pr && isNode(pr, 'Ruleset')) {
1817
+ const sel = pr.selector;
1818
+ parentSelectorAtStart.set(rs, typeof sel?.valueOf === 'function' ? sel.valueOf() : '');
1819
+ }
1820
+ }
1821
+ }
1822
+ // Find the ruleset that contains this extend (the "extend owner"). We will skip updating
1823
+ // any matching ruleset that is a strict descendant of the extend owner, so nested blocks
1824
+ // (e.g. .a, .c inside .b, .a inside .c,.a,.effected) keep their implicit ampersand and
1825
+ // are not replaced with materialized :is() form. We still update the extend owner and any
1826
+ // matching ruleset that is not nested under it (e.g. .aa, .cc elsewhere).
1827
+ const extendOwnerFromNode = extendNode.parent && isNode(extendNode.parent, 'Rules')
1828
+ ? extendNode.parent.parent
1829
+ : undefined;
1830
+ const extendOwner = rulesetSet?.find(rs => rulesetContainsExtend(rs, extendNode))
1831
+ ?? (extendOwnerFromNode && isNode(extendOwnerFromNode, 'Ruleset') ? extendOwnerFromNode : undefined);
1832
+ if (rulesetSet) {
1833
+ rulesetSet.forEach((ruleset) => {
1834
+ const targetIsSelectorList = isNode(target, 'SelectorList') && target.value?.length > 1;
1835
+ const rawSelector = ruleset.selector;
1836
+ const ownSel = ruleset.options?.ownSelector;
1837
+ const rawStr = typeof rawSelector?.valueOf === 'function' ? rawSelector.valueOf() : '';
1838
+ const ownStr = ownSel && typeof ownSel.valueOf === 'function' ? ownSel.valueOf() : '';
1839
+ // Do not replace a ruleset that is a strict descendant of another ruleset in this match set when target is a list.
1840
+ // Use ancestor selector valueOf to compare (clone vs original can differ by identity but match by selector).
1841
+ const hasAncestorInSet = () => {
1842
+ for (let p = ruleset.parent; p; p = p.parent) {
1843
+ if (!isNode(p, 'Ruleset')) {
1844
+ continue;
1845
+ }
1846
+ const anc = p;
1847
+ if (rulesetSet.some(rs => rs !== ruleset && (rs === anc || String(rs.selector?.valueOf?.() ?? '').trim() === String(anc.selector?.valueOf?.() ?? '').trim()))) {
1848
+ return true;
1849
+ }
1850
+ }
1851
+ return false;
1852
+ };
1853
+ const isNestedUnderAnotherMatch = targetIsSelectorList && hasAncestorInSet();
1854
+ if (isNestedUnderAnotherMatch) {
1855
+ if (traceMd) {
1856
+ syncLog({
1857
+ trace: 'apply_skip',
1858
+ runId: getExtendTraceRunId(context),
1859
+ reason: 'nestedUnderAnotherMatch',
1860
+ rulesetSel: typeof ruleset.selector?.valueOf === 'function' ? ruleset.selector.valueOf() : ''
1861
+ });
1862
+ }
1863
+ return; // Do not replace nested ruleset's selector with materialized form
1864
+ }
1865
+ // Skip prepended-sibling case only; do NOT skip the extend owner (we need to update it).
1866
+ if (ruleset !== extendOwner && shouldSkipRuleset(ruleset, extendNode)) {
1867
+ if (traceMd) {
1868
+ syncLog({
1869
+ trace: 'apply_skip',
1870
+ runId: getExtendTraceRunId(context),
1871
+ reason: 'shouldSkipRuleset',
1872
+ rulesetSel: typeof ruleset.selector?.valueOf === 'function' ? ruleset.selector.valueOf() : ''
1873
+ });
1874
+ }
1875
+ return;
1876
+ }
1877
+ // When this ruleset's selector equals its parent's (at start), use own selector so we
1878
+ // extend .replace,.c not the resolved form (rep_ace bug).
1879
+ const parentSelAtStart = parentSelectorAtStart.get(ruleset) ?? '';
1880
+ const sameAsParentAtStart = rawStr === parentSelAtStart && parentSelAtStart.length > 0;
1881
+ // Nested ruleset's selector can be "resolved" (longer than own); use own so we extend
1882
+ // .replace,.c not the resolved form (rep_ace bug). Only when we have a parent (in map).
1883
+ const isNestedWithResolvedSelector = parentSelAtStart.length > 0 && ownStr.length > 0 && rawStr.length > ownStr.length;
1884
+ let useOwn = (sameAsParentAtStart || isNestedWithResolvedSelector) && !!ownSel;
1885
+ // Exact extend: match against the ruleset's actual selector value in context. A nested
1886
+ // selector .b has implicit & and space, so its value is .a .b — not an exact match for find .b.
1887
+ if (!partial) {
1888
+ useOwn = false;
1889
+ }
1890
+ // If extend target is the full resolved selector (e.g. .header .header-nav), we must pass
1891
+ // the resolved selector to tryExtendSelector so the match succeeds; passing own (.header-nav) would not match.
1892
+ const targetNorm = normalizedSelectorValueOf(singleTarget);
1893
+ const rawNorm = normalizedSelectorValueOf(rawSelector);
1894
+ if (useOwn && targetNorm && rawNorm === targetNorm) {
1895
+ useOwn = false;
1896
+ }
1897
+ // If this ruleset was already transformed by a previous extend (e.g. .clearfix:after -> :is(.clearfix,.foo):after),
1898
+ // we must extend the current selector so the next extender adds to the :is(); using ownSelector would pass
1899
+ // the original &:after and overwrite with :is(.clearfix,.bar):after, dropping .foo.
1900
+ const alreadyTransformed = transformedByExtend.get(ruleset)?.size > 0;
1901
+ if (useOwn && alreadyTransformed) {
1902
+ useOwn = false;
1903
+ }
1904
+ let originalSelector = (useOwn ? ownSel : rawSelector);
1905
+ // When find is complex (e.g. .replace.replace .replace), we need the ruleset's selector in
1906
+ // explicit form (parent × child) so tryExtendSelector can match; rawSelector may be & .replace, & .c.
1907
+ const findIsComplex = typeof singleTarget.valueOf === 'function' && String(singleTarget.valueOf()).trim().includes(' ');
1908
+ if (findIsComplex && ownSel && parentSelAtStart.length > 0) {
1909
+ const resolved = buildResolvedSelectorForNested(ruleset, ownSel);
1910
+ if (resolved) {
1911
+ originalSelector = resolved;
1912
+ }
1913
+ }
1914
+ const origStr = typeof originalSelector?.valueOf === 'function' ? originalSelector.valueOf() : '';
1915
+ // Check if this extend has already transformed this ruleset's selector
1916
+ const extendKey = `${singleTarget.valueOf()}:${selectorWithExtend.valueOf()}:${partial}`;
1917
+ if (!transformedByExtend.has(ruleset)) {
1918
+ transformedByExtend.set(ruleset, new Set());
1919
+ }
1920
+ const transformsForRuleset = transformedByExtend.get(ruleset);
1921
+ // Skip if this extend has already transformed this ruleset
1922
+ if (transformsForRuleset.has(extendKey)) {
1923
+ if (traceMd) {
1924
+ syncLog({
1925
+ trace: 'apply_skip',
1926
+ runId: getExtendTraceRunId(context),
1927
+ reason: 'alreadyTransformed',
1928
+ rulesetSel: typeof ruleset.selector?.valueOf === 'function' ? ruleset.selector.valueOf() : ''
1929
+ });
1930
+ }
1931
+ return; // This extend already transformed this ruleset - skip
1932
+ }
1933
+ // Skip if this exact extend was previously rejected for this ruleset (e.g. .bb .bb for .bb:extend(.ee)).
1934
+ // Phase 2 would otherwise re-apply it when the selector is flattened to [.bb, .ff] and wrongly add .ee.
1935
+ if (!partial && rejectedExactExtendByRuleset.get(ruleset)?.has(extendKey)) {
1936
+ return;
1937
+ }
1938
+ // Track object identity and structure to detect transformations
1939
+ let result = tryExtendSelector(originalSelector, singleTarget, selectorWithExtend, partial);
1940
+ const changed = result && !result.error && result.value.valueOf() !== originalSelector.valueOf();
1941
+ // Record exact extends we rejected only when the selector was "find find" (e.g. .bb .bb).
1942
+ // Phase 2 must not re-apply those when the selector is later flattened to [.bb, .ff].
1943
+ // Do not record for other complex selectors (e.g. .aa .dd) so .dd:extend(.ff) still applies there.
1944
+ const findVal = singleTarget.valueOf();
1945
+ const wasSameNestedSelector = typeof findVal === 'string'
1946
+ && origStr.includes(' ')
1947
+ && isNode(originalSelector, 'ComplexSelector')
1948
+ && origStr === `${findVal} ${findVal}`;
1949
+ if (!partial && result && !result.error && !changed && wasSameNestedSelector) {
1950
+ if (!rejectedExactExtendByRuleset.has(ruleset)) {
1951
+ rejectedExactExtendByRuleset.set(ruleset, new Set());
1952
+ }
1953
+ rejectedExactExtendByRuleset.get(ruleset).add(extendKey);
1954
+ }
1955
+ if (traceMd) {
1956
+ syncLog({
1957
+ trace: 'tryExtend',
1958
+ runId: getExtendTraceRunId(context),
1959
+ rulesetSel: typeof ruleset.selector?.valueOf === 'function' ? ruleset.selector.valueOf() : '',
1960
+ hasResult: !!result,
1961
+ error: result?.error ?? null,
1962
+ changed: result && !result.error ? result.value.valueOf() !== originalSelector.valueOf() : null
1963
+ });
1964
+ }
1965
+ if (result && !result.error) {
1966
+ const extendedSelector = result.value;
1967
+ const origStr = typeof originalSelector?.valueOf === 'function' ? String(originalSelector.valueOf()) : '';
1968
+ const extStr = typeof extendedSelector?.valueOf === 'function' ? String(extendedSelector.valueOf()) : '';
1969
+ const traceDd = origStr.includes('dd') && (origStr.includes('aa') || origStr.includes('.dd'));
1970
+ if (traceDd) {
1971
+ const extItemCount = isNode(extendedSelector, 'SelectorList') ? extendedSelector.value?.length : 1;
1972
+ syncLog({
1973
+ trace: 'phase1_extend_dd',
1974
+ target: String(singleTarget.valueOf?.() ?? '').slice(0, 40),
1975
+ origLen: origStr.length,
1976
+ extLen: extStr.length,
1977
+ extItemCount,
1978
+ changed: extendedSelector.valueOf() !== originalSelector.valueOf()
1979
+ });
1980
+ }
1981
+ // Only update if selector actually changed
1982
+ if (extendedSelector.valueOf() !== originalSelector.valueOf()) {
1983
+ // Mark that this extend has transformed this ruleset
1984
+ transformsForRuleset.add(extendKey);
1985
+ const shouldHoist = !!extendedSelector.hoistToRoot;
1986
+ // CRITICAL: Clone the selector to avoid object reference issues
1987
+ const clonedSelector = extendedSelector.clone(true);
1988
+ preserveImplicitAmpersandOnClone(extendedSelector, clonedSelector);
1989
+ if (shouldHoist) {
1990
+ // NOTE: Node.clone()/inherit() does not currently copy hoistToRoot.
1991
+ clonedSelector.hoistToRoot = true;
1992
+ }
1993
+ // Update the ruleset's selector directly
1994
+ const origStrForHoist = typeof originalSelector?.valueOf === 'function' ? String(originalSelector.valueOf()) : '';
1995
+ const clonedStr = typeof clonedSelector?.valueOf === 'function' ? String(clonedSelector.valueOf()) : '';
1996
+ const hoisted = maybeHoistMixedNestingSelectorList(ruleset, clonedSelector, partial);
1997
+ const hoistStr = typeof hoisted?.valueOf === 'function' ? String(hoisted.valueOf()) : '';
1998
+ const parentRules = ruleset.parent;
1999
+ const parentLen = parentRules && isNode(parentRules, 'Rules') ? parentRules.value?.length : 0;
2000
+ const traceDdPipeline = hoistStr.includes('dd') && (hoistStr.includes('aa') || hoistStr.includes('.dd'));
2001
+ if (traceDdPipeline) {
2002
+ const hoistItemCount = isNode(hoisted, 'SelectorList') ? hoisted.value?.length : 1;
2003
+ syncLog({
2004
+ trace: 'phase1_after_hoist_dd',
2005
+ hoistItemCount,
2006
+ hoistSlice: hoistStr.slice(0, 120)
2007
+ });
2008
+ }
2009
+ // Normalize selectors after extend so generated :is() wrappers can be unwrapped/merged
2010
+ // when they are the only simple selector in a selector-list item (Less expectations).
2011
+ const normalized = createProcessedSelector(hoisted, true);
2012
+ if (traceDdPipeline) {
2013
+ const normStr = typeof normalized?.valueOf === 'function' ? String(normalized.valueOf()) : '';
2014
+ const normItemCount = isNode(normalized, 'SelectorList') ? normalized.value?.length : Array.isArray(normalized) ? normalized.length : 1;
2015
+ syncLog({
2016
+ trace: 'phase1_after_createProcessed_dd',
2017
+ normItemCount,
2018
+ normSlice: normStr.slice(0, 120)
2019
+ });
2020
+ }
2021
+ // Materialize implicit ampersand in items from a different context (e.g. extendWith)
2022
+ // so they serialize correctly; keep implicit when same context (nested .a, .c).
2023
+ const materialized = materializeNormalizedWhenDifferentContext(normalized, ruleset);
2024
+ if (traceDdPipeline) {
2025
+ const matItemCount = Array.isArray(materialized) ? materialized.length : (isNode(materialized, 'SelectorList') ? materialized.value?.length : 1);
2026
+ const matStr = Array.isArray(materialized) ? materialized.map((s) => String(s.valueOf?.() ?? '')).join(' | ') : String(materialized.valueOf?.() ?? '');
2027
+ syncLog({
2028
+ trace: 'phase1_after_materialize_dd',
2029
+ matItemCount,
2030
+ matSlice: matStr.slice(0, 160)
2031
+ });
2032
+ }
2033
+ let normalizedSelector;
2034
+ if (Array.isArray(materialized)) {
2035
+ normalizedSelector = SelectorList.create(materialized.map(s => s.clone(true))).inherit(hoisted);
2036
+ }
2037
+ else {
2038
+ normalizedSelector = materialized;
2039
+ }
2040
+ // NOTE: Node.clone()/inherit() does not currently copy hoistToRoot.
2041
+ if (hoisted.hoistToRoot) {
2042
+ normalizedSelector.hoistToRoot = true;
2043
+ }
2044
+ const leadingIsResult = processLeadingIs(normalizedSelector);
2045
+ normalizedSelector = Array.isArray(leadingIsResult)
2046
+ ? SelectorList.create(leadingIsResult.map(s => s.copy(true))).inherit(normalizedSelector)
2047
+ : leadingIsResult;
2048
+ const parentRuleset = ruleset.parent?.parent && isNode(ruleset.parent.parent, 'Ruleset')
2049
+ ? ruleset.parent.parent
2050
+ : null;
2051
+ let valueSharedWithAncestor = false;
2052
+ for (let p = ruleset.parent; p; p = p.parent) {
2053
+ if (isNode(p, 'Ruleset') && p.value === ruleset.value) {
2054
+ valueSharedWithAncestor = true;
2055
+ break;
2056
+ }
2057
+ }
2058
+ const valueSharedWithParent = parentRuleset !== null && ruleset.value === parentRuleset.value;
2059
+ // If this ruleset shares its value object with an ancestor ruleset, assigning
2060
+ // value.selector would overwrite the ancestor's selector too. Give this ruleset
2061
+ // its own value object so we only update this ruleset's selector.
2062
+ if (valueSharedWithAncestor) {
2063
+ ruleset.value = {
2064
+ selector: ruleset.value.selector,
2065
+ rules: ruleset.value.rules,
2066
+ ...(ruleset.value.guard !== undefined && { guard: ruleset.value.guard })
2067
+ };
2068
+ }
2069
+ // Before we overwrite this ruleset's selector, give any descendant rulesets that
2070
+ // share this value their own value so they keep their current selector.
2071
+ ensureDescendantRulesetsHaveOwnValue(ruleset, ruleset.value);
2072
+ ensureSelectorListItemsVisible(normalizedSelector);
2073
+ if (!ruleset.value.selectorBeforeExtend) {
2074
+ ruleset.value.selectorBeforeExtend = ruleset.value.selector;
2075
+ }
2076
+ ruleset.value.selector = normalizedSelector;
2077
+ ruleset.invalidateSelectorValueCache();
2078
+ if (normalizedSelector.hoistToRoot) {
2079
+ ruleset.hoistToRoot = true;
2080
+ }
2081
+ extendedRulesets.add(ruleset); // Track that this ruleset was extended
2082
+ reindexRuleset(ruleset);
2083
+ // NOTE: Do not apply chained extends depth-first.
2084
+ //
2085
+ // Chaining must not reorder independent extends that share a target and must not
2086
+ // cause later extends to be applied early. Phase 2 is responsible for reaching
2087
+ // a fixed point by extending already-extended selectors (including cycles).
2088
+ }
2089
+ else {
2090
+ }
2091
+ }
2092
+ else {
2093
+ }
2094
+ });
2095
+ }
2096
+ }
2097
+ processedExtends.delete(extendKey);
2098
+ };
2099
+ // Phase 1: Process all original extends depth-first
2100
+ for (const [target, selectorWithExtend, partial, extendRoot, extendNode] of allExtends) {
2101
+ processExtend(target, selectorWithExtend, partial, extendRoot, extendNode);
2102
+ }
2103
+ // Phase 2: Iterative multi-pass on extended rulesets
2104
+ let rulesetsToCheck = new Set(extendedRulesets);
2105
+ const seenSelectorStates = new Map(); // Track selector states per ruleset to detect loops
2106
+ const maxIterations = 100; // Prevent infinite loops
2107
+ let iteration = 0;
2108
+ while (rulesetsToCheck.size > 0 && iteration < maxIterations) {
2109
+ iteration++;
2110
+ const nextIteration = new Set();
2111
+ // Initialize seen states for new rulesets
2112
+ for (const ruleset of rulesetsToCheck) {
2113
+ if (!seenSelectorStates.has(ruleset)) {
2114
+ seenSelectorStates.set(ruleset, new Set());
2115
+ }
2116
+ }
2117
+ for (const ruleset of rulesetsToCheck) {
2118
+ const currentSelector = ruleset.selector;
2119
+ const currentSelectorValue = currentSelector.valueOf();
2120
+ const seenStates = seenSelectorStates.get(ruleset);
2121
+ let phase2ConsideredTargets = 0;
2122
+ let phase2SkipKeySet = 0;
2123
+ let phase2SkipInaccessible = 0;
2124
+ let phase2SkipAlreadyTransformed = 0;
2125
+ let phase2TryExtendSelector = 0;
2126
+ let phase2SelectorChanged = 0;
2127
+ // Check if we've seen this selector state before (infinite loop detection)
2128
+ if (seenStates.has(currentSelectorValue)) {
2129
+ continue; // Infinite loop detected - skip this ruleset
2130
+ }
2131
+ seenStates.add(currentSelectorValue);
2132
+ // Check if this ruleset's selector matches any extend targets
2133
+ const currentSelectors = isNode(currentSelector, 'SelectorList')
2134
+ ? currentSelector.value
2135
+ : [currentSelector];
2136
+ // Check each selector in the current ruleset against all extend targets.
2137
+ // NOTE: This loop is used ONLY for fast keySet rejection. We must not run
2138
+ // tryExtendSelector multiple times for the same (ruleset, extendKey).
2139
+ // The first iteration does the work; subsequent iterations are redundant because
2140
+ // we always call tryExtendSelector on `currentSelector` (not on `currentSel`).
2141
+ // We'll keep the loop but ensure we only attempt each extend once.
2142
+ const attemptedPhase2ExtendKeys = new Set();
2143
+ // KeySet rejection must consider *any* selector-list item, but we must not "attempt"
2144
+ // an extendKey based on a non-matching representative item (that would skip real matches).
2145
+ for (const [target, selectorWithExtend, partial, extendRoot, extendNode] of allExtends) {
2146
+ if (shouldSkipRuleset(ruleset, extendNode)) {
2147
+ continue; // Skip this extend for this ruleset
2148
+ }
2149
+ const targetSelectors = isNode(target, 'SelectorList')
2150
+ ? target.value
2151
+ : [target];
2152
+ for (const singleTarget of targetSelectors) {
2153
+ const phase2ExtendKey = `${singleTarget.valueOf()}:${selectorWithExtend.valueOf()}:${partial}`;
2154
+ if (attemptedPhase2ExtendKeys.has(phase2ExtendKey)) {
2155
+ continue;
2156
+ }
2157
+ // Fast rejection: check overlap against any selector-list item.
2158
+ const targetKeySet = singleTarget.keySet;
2159
+ const keySetOverlaps = currentSelectors.some((currentSel) => {
2160
+ const currentSelKeySet = currentSel.keySet;
2161
+ return partial
2162
+ ? targetKeySet.isSubsetOf(currentSelKeySet)
2163
+ : targetKeySet.size === currentSelKeySet.size && targetKeySet.isSubsetOf(currentSelKeySet);
2164
+ });
2165
+ if (!keySetOverlaps) {
2166
+ phase2SkipKeySet++;
2167
+ continue; // Fast rejection - keys don't overlap
2168
+ }
2169
+ // Mark as attempted only once we know it's plausible to match.
2170
+ attemptedPhase2ExtendKeys.add(phase2ExtendKey);
2171
+ phase2ConsideredTargets++;
2172
+ // Check if ruleset is accessible for this extend and in same/child root (not ancestor).
2173
+ // Prefer finding via registry; if the ruleset was extended in Phase 1 its keySet may have changed
2174
+ // so find(singleTarget.keySet) may not return it (e.g. .ma,.mb ruleset not found when searching for .mb).
2175
+ const accessibleRoots = context.extendRoots.getAccessibleRoots(extendRoot);
2176
+ let foundRuleset = false;
2177
+ for (const searchRoot of accessibleRoots) {
2178
+ const found = searchRoot.find('ruleset', singleTarget.keySet);
2179
+ if (found && found.includes(ruleset)) {
2180
+ const effectiveRoot = getEffectiveExtendRoot(ruleset);
2181
+ if (effectiveRoot && context.extendRoots.isSameOrDescendantRoot(effectiveRoot, extendRoot)) {
2182
+ foundRuleset = true;
2183
+ }
2184
+ break;
2185
+ }
2186
+ }
2187
+ // Phase 2: we already have the ruleset; if keySetOverlaps and it's in an accessible root, allow apply.
2188
+ if (!foundRuleset) {
2189
+ const effectiveRoot = getEffectiveExtendRoot(ruleset);
2190
+ if (effectiveRoot && context.extendRoots.isSameOrDescendantRoot(effectiveRoot, extendRoot)) {
2191
+ foundRuleset = true;
2192
+ }
2193
+ }
2194
+ if (!foundRuleset) {
2195
+ phase2SkipInaccessible++;
2196
+ continue; // Ruleset not accessible for this extend
2197
+ }
2198
+ // Check if this extend has already transformed this ruleset's selector
2199
+ const extendKey = `${singleTarget.valueOf()}:${selectorWithExtend.valueOf()}:${partial}`;
2200
+ if (!transformedByExtend.has(ruleset)) {
2201
+ transformedByExtend.set(ruleset, new Set());
2202
+ }
2203
+ const transformsForRuleset = transformedByExtend.get(ruleset);
2204
+ // Skip if this extend has already transformed this ruleset
2205
+ if (transformsForRuleset.has(extendKey)) {
2206
+ phase2SkipAlreadyTransformed++;
2207
+ continue; // This extend already transformed this ruleset - skip
2208
+ }
2209
+ // Skip if this exact extend was rejected for this ruleset in Phase 1 (e.g. .bb .bb for .bb:extend(.ee)).
2210
+ // The selector may now be flattened to [.bb, .ff]; re-applying would wrongly add .ee.
2211
+ if (!partial && rejectedExactExtendByRuleset.get(ruleset)?.has(extendKey)) {
2212
+ continue;
2213
+ }
2214
+ // Try to extend - tryExtendSelector will check for actual matches (including combinators)
2215
+ // and return an error if there's no match
2216
+ phase2TryExtendSelector++;
2217
+ const result = tryExtendSelector(currentSelector, singleTarget, selectorWithExtend, partial);
2218
+ if (result && !result.error) {
2219
+ const extendedSelector = result.value;
2220
+ // Only update if selector actually changed
2221
+ if (extendedSelector.valueOf() !== currentSelectorValue) {
2222
+ phase2SelectorChanged++;
2223
+ // Mark that this extend has transformed this ruleset
2224
+ transformsForRuleset.add(extendKey);
2225
+ const shouldHoist = !!extendedSelector.hoistToRoot;
2226
+ // CRITICAL: Clone the selector to avoid object reference issues
2227
+ const clonedSelector = extendedSelector.clone(true);
2228
+ preserveImplicitAmpersandOnClone(extendedSelector, clonedSelector);
2229
+ if (shouldHoist) {
2230
+ // NOTE: Node.clone()/inherit() does not currently copy hoistToRoot.
2231
+ clonedSelector.hoistToRoot = true;
2232
+ }
2233
+ // Normalize selectors after extend so generated :is() wrappers can be unwrapped/merged
2234
+ // when they are the only simple selector in a selector-list item (Less expectations).
2235
+ const normalized = createProcessedSelector(clonedSelector, true);
2236
+ const materialized = materializeNormalizedWhenDifferentContext(normalized, ruleset);
2237
+ let normalizedSelector;
2238
+ if (Array.isArray(materialized)) {
2239
+ normalizedSelector = SelectorList.create(materialized.map(s => s.clone(true))).inherit(clonedSelector);
2240
+ }
2241
+ else {
2242
+ normalizedSelector = materialized;
2243
+ }
2244
+ ensureSelectorListItemsVisible(normalizedSelector);
2245
+ if (!ruleset.value.selectorBeforeExtend) {
2246
+ ruleset.value.selectorBeforeExtend = ruleset.value.selector;
2247
+ }
2248
+ ruleset.value.selector = normalizedSelector;
2249
+ ruleset.invalidateSelectorValueCache();
2250
+ if (normalizedSelector.hoistToRoot) {
2251
+ ruleset.hoistToRoot = true;
2252
+ }
2253
+ reindexRuleset(ruleset);
2254
+ nextIteration.add(ruleset); // Keep in next iteration
2255
+ break; // Found a match, no need to check other targets
2256
+ }
2257
+ }
2258
+ }
2259
+ }
2260
+ // If we added to nextIteration, break out of outer loop
2261
+ if (nextIteration.has(ruleset)) {
2262
+ continue;
2263
+ }
2264
+ }
2265
+ rulesetsToCheck = nextIteration;
2266
+ }
2267
+ if (iteration >= maxIterations) {
2268
+ throw new Error(`Extend chaining exceeded maximum iterations (${maxIterations}). Possible infinite loop.`);
2269
+ }
2270
+ setExtendOrderMap(null);
2271
+ }
2272
+ //# sourceMappingURL=extend-roots.old.js.map