@aceshooting/lyra-ui 23.0.0 → 24.1.0

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 (280) hide show
  1. package/CHANGELOG.md +16 -39
  2. package/README.md +72 -35
  3. package/custom-elements.json +1 -1
  4. package/design-tokens.json +1 -1
  5. package/dist/cli/lyra-rename-ledger.mjs +1 -1
  6. package/dist/cli/migration-contract.json +1 -1
  7. package/dist/cli/migration-contract.mjs +1 -1
  8. package/dist/cli/migration-renames.mjs +2 -2
  9. package/dist/cli/migration-transforms.mjs +2 -2
  10. package/dist/components/agent-tools/approval-queue/approval-queue.class.d.ts +1 -4
  11. package/dist/components/agent-tools/approval-queue/approval-queue.class.js +1 -3
  12. package/dist/components/agent-tools/compare-panel/compare-panel.class.d.ts +1 -4
  13. package/dist/components/agent-tools/compare-panel/compare-panel.class.js +1 -1
  14. package/dist/components/agent-tools/confirm-bar/confirm-bar.class.d.ts +1 -7
  15. package/dist/components/agent-tools/confirm-bar/confirm-bar.class.js +2 -2
  16. package/dist/components/agent-tools/context-inspector/context-inspector.class.d.ts +1 -1
  17. package/dist/components/agent-tools/evaluation-run/evaluation-run.class.d.ts +2 -5
  18. package/dist/components/agent-tools/evaluation-run/evaluation-run.class.js +1 -3
  19. package/dist/components/agent-tools/terminal/terminal.class.d.ts +1 -4
  20. package/dist/components/agent-tools/terminal/terminal.class.js +2 -2
  21. package/dist/components/agent-tools/tool-approval-dialog/tool-approval-dialog.class.d.ts +1 -7
  22. package/dist/components/agent-tools/tool-approval-dialog/tool-approval-dialog.class.js +1 -1
  23. package/dist/components/agent-tools/tool-timeline/tool-timeline.class.d.ts +1 -4
  24. package/dist/components/agent-tools/tool-timeline/tool-timeline.class.js +1 -3
  25. package/dist/components/charts/chart/lite-chart-axis-layout.d.ts +13 -0
  26. package/dist/components/charts/chart/lite-chart-axis-layout.js +1 -0
  27. package/dist/components/charts/chart/lite-chart.class.d.ts +1 -11
  28. package/dist/components/charts/chart/lite-chart.class.js +4 -4
  29. package/dist/components/conversation/agent-workspace/agent-workspace.class.d.ts +13 -3
  30. package/dist/components/conversation/agent-workspace/agent-workspace.class.js +1 -2
  31. package/dist/components/conversation/chat-message/chat-message.class.d.ts +3 -9
  32. package/dist/components/conversation/chat-message/chat-message.class.js +1 -1
  33. package/dist/components/conversation/code-block/code-block-base.class.js +1 -1
  34. package/dist/components/conversation/code-block/code-block-core.class.d.ts +6 -8
  35. package/dist/components/conversation/code-block/code-block-shared.d.ts +1 -4
  36. package/dist/components/conversation/code-block/code-block.class.d.ts +4 -6
  37. package/dist/components/conversation/markdown/markdown-base.class.d.ts +1 -3
  38. package/dist/components/conversation/markdown/markdown-base.class.js +1 -1
  39. package/dist/components/conversation/markdown/markdown-core.class.d.ts +1 -4
  40. package/dist/components/conversation/markdown/markdown.class.d.ts +1 -4
  41. package/dist/components/conversation/message-actions/message-actions.class.d.ts +1 -4
  42. package/dist/components/conversation/message-actions/message-actions.class.js +1 -1
  43. package/dist/components/conversation/message-feedback/message-feedback.class.d.ts +1 -4
  44. package/dist/components/conversation/message-feedback/message-feedback.class.js +2 -2
  45. package/dist/components/conversation/message-parts/message-parts.class.d.ts +2 -5
  46. package/dist/components/conversation/message-parts/message-parts.class.js +3 -3
  47. package/dist/components/conversation/streaming-text/streaming-text-base.class.d.ts +1 -1
  48. package/dist/components/conversation/streaming-text/streaming-text-core.class.d.ts +2 -0
  49. package/dist/components/conversation/streaming-text/streaming-text-core.class.js +0 -1
  50. package/dist/components/conversation/streaming-text/streaming-text.class.d.ts +2 -0
  51. package/dist/components/conversation/streaming-text/streaming-text.class.js +0 -1
  52. package/dist/components/conversation/thread-list/thread-list.class.d.ts +6 -17
  53. package/dist/components/conversation/thread-list/thread-list.class.js +6 -6
  54. package/dist/components/data/context-meter/context-meter.class.d.ts +1 -6
  55. package/dist/components/data/context-meter/context-meter.class.js +1 -1
  56. package/dist/components/data/data-grid/data-grid.class.d.ts +0 -1
  57. package/dist/components/data/data-grid/data-grid.class.js +2 -2
  58. package/dist/components/data/document-library/document-library.class.d.ts +6 -13
  59. package/dist/components/data/document-library/document-library.class.js +1 -2
  60. package/dist/components/data/heatmap/heatmap-grid-navigation.d.ts +7 -0
  61. package/dist/components/data/heatmap/heatmap-grid-navigation.js +1 -0
  62. package/dist/components/data/heatmap/heatmap.class.d.ts +3 -23
  63. package/dist/components/data/heatmap/heatmap.class.js +2 -2
  64. package/dist/components/data/sequence-strip/sequence-strip.class.d.ts +3 -9
  65. package/dist/components/data/sequence-strip/sequence-strip.class.js +3 -3
  66. package/dist/components/data/table/table.class.d.ts +5 -14
  67. package/dist/components/data/table/table.class.js +6 -6
  68. package/dist/components/forms/button/button.class.d.ts +2 -2
  69. package/dist/components/forms/button/button.class.js +9 -9
  70. package/dist/components/forms/combobox/combobox.class.d.ts +1 -4
  71. package/dist/components/forms/combobox/combobox.class.js +2 -2
  72. package/dist/components/forms/input/time-input.class.d.ts +1 -1
  73. package/dist/components/forms/input/time-input.class.js +1 -1
  74. package/dist/components/forms/select/select.class.d.ts +1 -2
  75. package/dist/components/forms/swatch-picker/swatch-picker.class.d.ts +3 -1
  76. package/dist/components/forms/swatch-picker/swatch-picker.class.js +1 -1
  77. package/dist/components/layout/app-rail/app-rail-item.class.d.ts +3 -6
  78. package/dist/components/layout/app-rail/app-rail-item.class.js +1 -1
  79. package/dist/components/layout/app-rail/app-rail.class.d.ts +2 -4
  80. package/dist/components/layout/app-rail/app-rail.class.js +1 -1
  81. package/dist/components/layout/app-rail-group/app-rail-group.class.d.ts +2 -6
  82. package/dist/components/layout/app-rail-group/app-rail-group.class.js +1 -1
  83. package/dist/components/layout/carousel/carousel.class.d.ts +1 -4
  84. package/dist/components/layout/carousel/carousel.class.js +2 -2
  85. package/dist/components/layout/command-palette/command-palette.class.js +1 -1
  86. package/dist/components/layout/details/details.class.d.ts +2 -4
  87. package/dist/components/layout/details/details.class.js +1 -1
  88. package/dist/components/layout/dock-panel/dock-panel.class.d.ts +3 -7
  89. package/dist/components/layout/dock-panel/dock-panel.class.js +1 -1
  90. package/dist/components/layout/menu/menu.class.js +1 -1
  91. package/dist/components/layout/multi-split/multi-split.class.d.ts +2 -4
  92. package/dist/components/layout/multi-split/multi-split.class.js +1 -1
  93. package/dist/components/layout/navigation-menu-item/navigation-menu-item.class.d.ts +2 -4
  94. package/dist/components/layout/navigation-menu-item/navigation-menu-item.class.js +1 -1
  95. package/dist/components/layout/reorder-list/reorder-item.class.d.ts +1 -4
  96. package/dist/components/layout/reorder-list/reorder-item.class.js +2 -2
  97. package/dist/components/media/attachment-trigger/attachment-trigger.class.d.ts +5 -9
  98. package/dist/components/media/attachment-trigger/attachment-trigger.class.js +5 -5
  99. package/dist/components/media/av-player/av-player.class.js +3 -3
  100. package/dist/components/media/file-input/file-input.class.d.ts +3 -6
  101. package/dist/components/media/file-input/file-input.class.js +3 -3
  102. package/dist/components/media/lightbox/lightbox.class.d.ts +11 -20
  103. package/dist/components/media/lightbox/lightbox.class.js +1 -1
  104. package/dist/components/media/map/map-data-layers.d.ts +20 -0
  105. package/dist/components/media/map/map-data-layers.js +1 -0
  106. package/dist/components/media/map/map.class.js +3 -3
  107. package/dist/components/media/media-time.d.ts +2 -0
  108. package/dist/components/media/media-time.js +1 -0
  109. package/dist/components/media/video/video.class.js +5 -5
  110. package/dist/components/media/video-playlist/video-playlist.class.js +2 -2
  111. package/dist/components/overlays/callout/callout.class.d.ts +2 -5
  112. package/dist/components/overlays/callout/callout.class.js +2 -2
  113. package/dist/components/overlays/dialog/dialog.class.d.ts +4 -7
  114. package/dist/components/overlays/dialog/dialog.class.js +2 -2
  115. package/dist/components/overlays/overlay/dropdown.class.d.ts +5 -10
  116. package/dist/components/overlays/overlay/dropdown.class.js +2 -2
  117. package/dist/components/overlays/overlay/popover.class.d.ts +2 -3
  118. package/dist/components/overlays/popup/popup.class.js +1 -1
  119. package/dist/components/overlays/progress/progress-bar.class.d.ts +1 -4
  120. package/dist/components/overlays/progress/progress-bar.class.js +2 -2
  121. package/dist/components/overlays/progress/progress-ring.class.d.ts +1 -4
  122. package/dist/components/overlays/progress/progress-ring.class.js +2 -2
  123. package/dist/components/overlays/progress/progress-shared.d.ts +3 -3
  124. package/dist/components/overlays/progress/progress-shared.js +1 -1
  125. package/dist/components/overlays/toast/toast-item.class.js +1 -1
  126. package/dist/components/retrieval/graph/graph.class.d.ts +5 -10
  127. package/dist/components/retrieval/graph/graph.class.js +1 -1
  128. package/dist/components/retrieval/knowledge-base/knowledge-base.class.d.ts +7 -14
  129. package/dist/components/retrieval/knowledge-base/knowledge-base.class.js +2 -3
  130. package/dist/components/retrieval/knowledge-graph-explorer/knowledge-graph-explorer.class.d.ts +2 -8
  131. package/dist/components/retrieval/memory-panel/memory-panel.class.js +0 -4
  132. package/dist/components/retrieval/rag-answer/rag-answer.class.js +1 -1
  133. package/dist/components/utility/export-button/export-button.class.d.ts +6 -10
  134. package/dist/components/utility/export-button/export-button.class.js +1 -1
  135. package/dist/components/utility/mention-popover/mention-popover.class.js +1 -1
  136. package/dist/components/viewers/geojson-view/geojson-view.class.d.ts +1 -1
  137. package/dist/components/viewers/highlight-layer/highlight-layer-timing.js +1 -1
  138. package/dist/components/viewers/pdf-viewer/pdf-viewer.class.js +4 -4
  139. package/dist/events.d.ts +17 -78
  140. package/dist/framework-types.d.ts +1 -1
  141. package/dist/internal/catalog-picker.js +1 -1
  142. package/dist/internal/css-motion-time.d.ts +7 -0
  143. package/dist/internal/css-motion-time.js +1 -0
  144. package/dist/internal/data-state-renderer.d.ts +1 -3
  145. package/dist/internal/data-state-renderer.js +1 -1
  146. package/dist/internal/localization-runtime.d.ts +2 -0
  147. package/dist/internal/localization-runtime.js +1 -1
  148. package/dist/internal/lyra-element.js +2 -2
  149. package/dist/internal/package-metadata.d.ts +1 -1
  150. package/dist/internal/package-metadata.js +1 -1
  151. package/dist/looks/data.css +1 -1
  152. package/dist/looks/terminal.css +1 -1
  153. package/dist/lyra.d.ts +1 -592
  154. package/dist/lyra.js +1 -1
  155. package/dist/testing/event-factory.d.ts +2 -2
  156. package/dist/testing/interaction-drivers.d.ts +5 -3
  157. package/dist/testing/lyra-tag-event-map.js +1 -1
  158. package/dist/theme/looks/data.d.ts +1 -1
  159. package/dist/theme/looks/data.js +1 -1
  160. package/dist/theme/looks/terminal.d.ts +1 -1
  161. package/dist/theme/looks/terminal.js +1 -1
  162. package/dist/theme/theme.d.ts +2 -80
  163. package/dist/theme/theme.js +1 -1
  164. package/llms/components/lr-agent-workspace.md +24 -2
  165. package/llms/components/lr-alert.md +1 -1
  166. package/llms/components/lr-animation.md +1 -1
  167. package/llms/components/lr-app-rail.md +8 -10
  168. package/llms/components/lr-approval-queue.md +2 -4
  169. package/llms/components/lr-attachment-trigger.md +3 -4
  170. package/llms/components/lr-badge.md +2 -2
  171. package/llms/components/lr-button.md +45 -99
  172. package/llms/components/lr-callout.md +3 -4
  173. package/llms/components/lr-carousel.md +3 -5
  174. package/llms/components/lr-chat-message.md +1 -2
  175. package/llms/components/lr-claim-evidence.md +1 -1
  176. package/llms/components/lr-code-block-core.md +2 -2
  177. package/llms/components/lr-code-block.md +5 -6
  178. package/llms/components/lr-color-picker.md +1 -1
  179. package/llms/components/lr-combobox.md +5 -6
  180. package/llms/components/lr-compare-panel.md +2 -4
  181. package/llms/components/lr-confirm-bar.md +3 -7
  182. package/llms/components/lr-context-inspector.md +3 -2
  183. package/llms/components/lr-context-menu.md +2 -2
  184. package/llms/components/lr-context-meter.md +2 -4
  185. package/llms/components/lr-copy-button.md +1 -1
  186. package/llms/components/lr-data-grid.md +4 -7
  187. package/llms/components/lr-details.md +3 -3
  188. package/llms/components/lr-dialog.md +4 -6
  189. package/llms/components/lr-dock-panel.md +5 -6
  190. package/llms/components/lr-document-library.md +5 -7
  191. package/llms/components/lr-drawer.md +3 -3
  192. package/llms/components/lr-dropdown.md +4 -5
  193. package/llms/components/lr-eval-run.md +3 -5
  194. package/llms/components/lr-export-button.md +4 -7
  195. package/llms/components/lr-file-input.md +0 -5
  196. package/llms/components/lr-funnel.md +1 -1
  197. package/llms/components/lr-graph.md +3 -7
  198. package/llms/components/lr-icon.md +1 -1
  199. package/llms/components/lr-include.md +1 -1
  200. package/llms/components/lr-input.md +31 -79
  201. package/llms/components/lr-json-viewer.md +1 -1
  202. package/llms/components/lr-knowledge-base.md +4 -6
  203. package/llms/components/lr-knowledge-graph-explorer.md +2 -5
  204. package/llms/components/lr-known-date.md +1 -1
  205. package/llms/components/lr-lightbox.md +9 -7
  206. package/llms/components/lr-lite-chart.md +1 -5
  207. package/llms/components/lr-markdown-core.md +3 -4
  208. package/llms/components/lr-markdown.md +3 -5
  209. package/llms/components/lr-mcp-app.md +1 -1
  210. package/llms/components/lr-message-actions.md +2 -3
  211. package/llms/components/lr-message-feedback.md +1 -3
  212. package/llms/components/lr-message-parts.md +4 -5
  213. package/llms/components/lr-multi-split.md +8 -7
  214. package/llms/components/lr-native-time-input.md +1 -1
  215. package/llms/components/lr-navigation-menu.md +5 -5
  216. package/llms/components/lr-number-input.md +3 -1
  217. package/llms/components/lr-otp-input.md +2 -0
  218. package/llms/components/lr-page.md +1 -1
  219. package/llms/components/lr-pan-zoom.md +1 -1
  220. package/llms/components/lr-phone-input.md +3 -1
  221. package/llms/components/lr-popover.md +2 -2
  222. package/llms/components/lr-progress-bar.md +3 -5
  223. package/llms/components/lr-progress-ring.md +5 -7
  224. package/llms/components/lr-prompt-input.md +1 -1
  225. package/llms/components/lr-prompt-queue.md +1 -1
  226. package/llms/components/lr-prompt-studio.md +1 -1
  227. package/llms/components/lr-rag-eval-dashboard.md +1 -1
  228. package/llms/components/lr-rating.md +2 -2
  229. package/llms/components/lr-realtime-session.md +1 -1
  230. package/llms/components/lr-reorder-item.md +1 -1
  231. package/llms/components/lr-reorder-list.md +2 -3
  232. package/llms/components/lr-retrieval-compare.md +1 -1
  233. package/llms/components/lr-select.md +3 -1
  234. package/llms/components/lr-selection-toolbar.md +1 -1
  235. package/llms/components/lr-sequence-strip.md +3 -8
  236. package/llms/components/lr-span-waterfall.md +1 -1
  237. package/llms/components/lr-split-panel.md +1 -1
  238. package/llms/components/lr-streaming-text-core.md +2 -1
  239. package/llms/components/lr-streaming-text.md +3 -2
  240. package/llms/components/lr-subagent-panel.md +1 -1
  241. package/llms/components/lr-swatch-picker.md +7 -6
  242. package/llms/components/lr-switch.md +3 -1
  243. package/llms/components/lr-table.md +33 -85
  244. package/llms/components/lr-terminal.md +2 -5
  245. package/llms/components/lr-textarea.md +1 -1
  246. package/llms/components/lr-thread-list.md +8 -14
  247. package/llms/components/lr-time-input.md +1 -1
  248. package/llms/components/lr-toggle-group.md +2 -2
  249. package/llms/components/lr-toggle.md +1 -1
  250. package/llms/components/lr-tool-approval-dialog.md +4 -7
  251. package/llms/components/lr-tool-call-block.md +1 -1
  252. package/llms/components/lr-tool-timeline.md +2 -5
  253. package/llms/components/lr-video-playlist.md +1 -1
  254. package/llms/components/lr-video.md +1 -1
  255. package/llms/components/lr-widget-renderer.md +7 -6
  256. package/llms/components/lr-zoomable-frame.md +1 -1
  257. package/llms/index.md +3 -618
  258. package/llms/migration.md +614 -614
  259. package/llms/shared/events-and-types.md +32 -31
  260. package/llms/shared/frameworks-and-ssr.md +7 -3
  261. package/llms/shared/imports-and-registration.md +22 -22
  262. package/llms/shared/localization-and-rtl.md +2 -2
  263. package/llms/shared/native-styles-and-utilities.md +263 -0
  264. package/llms/shared/styles-and-tokens.md +85 -676
  265. package/llms/shared/testing-and-utilities.md +12 -11
  266. package/llms/shared/v23-to-v24-migration.md +167 -0
  267. package/llms/shared.md +149 -494
  268. package/llms/tokens.md +3 -3
  269. package/llms.txt +9 -4
  270. package/package.json +7 -311
  271. package/registrations.json +1 -1
  272. package/vscode-html-data.json +1 -1
  273. package/web-types.json +1 -1
  274. package/dist/ssr-loader.d.ts +0 -2
  275. package/dist/ssr-loader.js +0 -1
  276. package/dist/theme/presets/shadcn.d.ts +0 -22
  277. package/dist/theme/presets/shadcn.js +0 -1
  278. package/dist/theme/presets.d.ts +0 -49
  279. package/dist/theme/presets.js +0 -1
  280. package/dist/themes/shadcn.css +0 -1
@@ -1,2 +1,2 @@
1
- import fs from"node:fs";import path from"node:path";import{fileURLToPath}from"node:url";import{LOCAL_MIGRATION_ORIGINS,compareMappedSurfaces,validateAccessibilityContract,validateLocalMigrations,validateMappingNormalizations,validateMethodEdgeParity}from"./component-inventory.mjs";import{LYRA_RENAME_ORIGINS,createRenameProfiles,emptyRenameProjection,projectRenameLedger,validateRenameLedgerShape}from"./lyra-rename-ledger.mjs";const scriptDir=path.dirname(fileURLToPath(import.meta.url)),packageDir=path.resolve(scriptDir,".."),packagedInventoryPath=path.join(scriptDir,"migration-contract.json"),packagedRuntime=fs.existsSync(packagedInventoryPath),inventoryPath=packagedRuntime?packagedInventoryPath:path.join(packageDir,"scripts","fixtures","component-inventory.json"),renameLedgerPath=path.join(packageDir,"scripts","fixtures","lyra-renames.json"),MIGRATION_RUNTIME_SCHEMA_VERSION=3,MIGRATION_ORIGINS=Object.freeze([...LOCAL_MIGRATION_ORIGINS,...LYRA_RENAME_ORIGINS]),AUTO_CLASSIFICATIONS=new Set(["exact","rewritten"]),CLASSIFICATIONS=new Set(["exact","rewritten","warning-required","conceptual-only","unsupported"]),MEMBER_RULE_SECTIONS=["attributes","properties","events","slots","parts","cssProperties","methods"],REWRITE_RULE_SECTIONS=[...MEMBER_RULE_SECTIONS,"defaults"],ECOSYSTEMS=["webawesome","shoelace"],PACKAGE_TIERS=new Set(["free","pro"]),STATIC_API_STATUSES=new Set(["reviewed","tag-only","unreviewed"]),LIGHT_DOM_STATUSES=new Set(["not-applicable","surface-only","warning-required","unreviewed"]),REGISTRATION_STATUSES=new Set(["all","granular","unavailable"]),CONDITIONAL_BEHAVIOR_REVIEW_TAGS=new Set(["wa-random-content"]);function invariant(condition,message){if(!condition)throw new Error(`Invalid component inventory migration contract: ${message}`)}function surfaceNames(component,section){return new Set((component?.surface?.[section]??[]).map(entry=>entry.name))}function isScalar(value){return value===null||typeof value=="string"||typeof value=="boolean"||typeof value=="number"&&Number.isFinite(value)}function validateMemberRules(mapping,source,target,section){const rules=mapping.rewrites[section];invariant(Array.isArray(rules),`${mapping.upstreamTag}: rewrites.${section} must be an array`);const sourceNames=surfaceNames(source,section),targetNames=surfaceNames(target,section),seen=new Set;for(const rule of rules){invariant(rule&&typeof rule.from=="string"&&rule.from&&typeof rule.to=="string"&&rule.to,`${mapping.upstreamTag}: every rewrites.${section} rule needs non-empty from/to`);const unknownKeys=Object.keys(rule).filter(key=>key!=="from"&&key!=="to");invariant(unknownKeys.length===0,`${mapping.upstreamTag}: rewrites.${section} rule has unknown key(s) ${unknownKeys.join(", ")}`),invariant(rule.from!==rule.to,`${mapping.upstreamTag}: rewrites.${section} cannot map a member to itself`),invariant(!seen.has(rule.from),`${mapping.upstreamTag}: duplicate rewrites.${section} source ${rule.from}`),seen.add(rule.from),invariant(sourceNames.has(rule.from),`${mapping.upstreamTag}: unknown ${section} source ${rule.from}`),invariant(targetNames.has(rule.to),`${mapping.upstreamTag}: unknown target ${section} member ${rule.to}`)}}function validateDefaultRules(mapping,source,target){const rules=mapping.rewrites.defaults;invariant(Array.isArray(rules),`${mapping.upstreamTag}: rewrites.defaults must be an array`);const seen=new Set;for(const rule of rules){invariant(rule?.memberKind==="attribute"||rule?.memberKind==="property",`${mapping.upstreamTag}: default memberKind must be attribute or property`),invariant(typeof rule.member=="string"&&rule.member,`${mapping.upstreamTag}: default rule needs member`),invariant(rule.action==="insert-if-absent"||rule.action==="replace-value",`${mapping.upstreamTag}: unsupported default action ${String(rule.action)}`);const allowedKeys=rule.action==="insert-if-absent"?new Set(["memberKind","member","action","value"]):new Set(["memberKind","member","action","from","to"]),unknownKeys=Object.keys(rule).filter(key2=>!allowedKeys.has(key2));invariant(unknownKeys.length===0,`${mapping.upstreamTag}: default rule has unknown key(s) ${unknownKeys.join(", ")}`);const key=`${rule.memberKind}:${rule.member}:${rule.action}`;invariant(!seen.has(key),`${mapping.upstreamTag}: duplicate default rule ${key}`),seen.add(key);const section=rule.memberKind==="attribute"?"attributes":"properties";invariant(surfaceNames(target,section).has(rule.member),`${mapping.upstreamTag}: unknown target ${key}`),rule.action==="insert-if-absent"?(invariant(rule.memberKind==="attribute",`${mapping.upstreamTag}: insert-if-absent is only deterministic for attributes`),invariant(Object.hasOwn(rule,"value"),`${mapping.upstreamTag}: ${key} needs value`),invariant(isScalar(rule.value),`${mapping.upstreamTag}: ${key} value must be a scalar`),invariant(!Object.hasOwn(rule,"from")&&!Object.hasOwn(rule,"to"),`${mapping.upstreamTag}: ${key} cannot use from/to`)):(invariant(surfaceNames(source,section).has(rule.member),`${mapping.upstreamTag}: unknown source ${key}`),invariant(Object.hasOwn(rule,"from")&&Object.hasOwn(rule,"to"),`${mapping.upstreamTag}: ${key} needs from/to`),invariant(isScalar(rule.from)&&isScalar(rule.to),`${mapping.upstreamTag}: ${key} from/to must be scalars`),invariant(rule.from!==rule.to,`${mapping.upstreamTag}: ${key} cannot replace a value with itself`),invariant(!Object.hasOwn(rule,"value"),`${mapping.upstreamTag}: ${key} cannot use value`))}}function driftCovered(mapping,finding){const codeToSection={"missing-attribute":"attributes","polarity-mismatch":"attributes","missing-property":"properties","missing-event":"events","missing-slot":"slots","missing-part":"parts","missing-css-property":"cssProperties","missing-method":"methods"};if(finding.code==="default-mismatch")return mapping.rewrites.defaults.some(rule=>rule.member===finding.member);const section=codeToSection[finding.code];return!!(section&&mapping.rewrites[section].some(rule=>rule.from===finding.member))}function runtimeMemberSurfaces(mapping,section){const rules=Array.isArray(mapping.rewrites?.[section])?mapping.rewrites[section]:[];return{source:{surface:{[section]:rules.map(rule=>({name:rule?.from}))}},target:{surface:{[section]:rules.map(rule=>({name:rule?.to}))}}}}function runtimeDefaultSurfaces(mapping){const source={surface:{attributes:[],properties:[]}},target={surface:{attributes:[],properties:[]}},rules=Array.isArray(mapping.rewrites?.defaults)?mapping.rewrites.defaults:[];for(const rule of rules){const section=rule?.memberKind==="attribute"?"attributes":"properties";target.surface[section].push({name:rule?.member}),rule?.action==="replace-value"&&source.surface[section].push({name:rule?.member})}return{source,target}}function buildMigrationContract(inventory,{renameLedger=null,exportDeprecations=[],lyraVersion=null,compatibilityContext=null}={}){invariant(inventory?.schemaVersion===1,"schemaVersion must be 1");const runtimeInventory=Object.hasOwn(inventory,"migrationRuntimeSchemaVersion");if(runtimeInventory){invariant(inventory.migrationRuntimeSchemaVersion===MIGRATION_RUNTIME_SCHEMA_VERSION,`migrationRuntimeSchemaVersion must be ${MIGRATION_RUNTIME_SCHEMA_VERSION}`),invariant(renameLedger===null,"a packaged runtime inventory carries its own rename projection");const renameFindings=validateRenameLedgerShape(inventory.lyraRenames,{projected:!0});invariant(renameFindings.length===0,renameFindings.join("; "))}const renameProfiles=createRenameProfiles(runtimeInventory?inventory.lyraRenames:renameLedger?projectRenameLedger(renameLedger,inventory,{exportDeprecations,compatibilityContext}):emptyRenameProjection(),{lyraVersion});invariant(Array.isArray(inventory.components),"components must be an array"),invariant(Array.isArray(inventory.mappings),"mappings must be an array"),invariant(inventory.upstreams&&typeof inventory.upstreams=="object","upstreams must be an object");const accessibilityFindings=validateAccessibilityContract(inventory.accessibilityProfiles,inventory.mappings);invariant(accessibilityFindings.length===0,accessibilityFindings.join("; "));const components=new Map;for(const component of inventory.components)invariant(typeof component.tag=="string"&&component.tag,"every Lyra component needs a tag"),invariant(!components.has(component.tag),`duplicate Lyra component ${component.tag}`),invariant(typeof component.registrationModule=="string"&&component.registrationModule.endsWith(".ts"),`${component.tag}: registrationModule must end in .ts`),Object.hasOwn(component,"rootIncluded")&&invariant(typeof component.rootIncluded=="boolean",`${component.tag}: rootIncluded must be boolean`),Object.hasOwn(component,"optionalPeers")&&(invariant(Array.isArray(component.optionalPeers),`${component.tag}: optionalPeers must be an array`),invariant(component.optionalPeers.every(peer=>typeof peer=="string"&&peer),`${component.tag}: optionalPeers entries must be non-empty package names`)),components.set(component.tag,component);const localMigrationFindings=validateLocalMigrations(inventory);invariant(localMigrationFindings.length===0,localMigrationFindings.join("; "));const localMigrations=new Map(LOCAL_MIGRATION_ORIGINS.map(origin=>[origin,new Map]));for(const profile of inventory.localMigrations)localMigrations.get(profile.origin).set(profile.tag,profile);const upstreamComponents=new Map,packageIdentities=new Map,packagesByEcosystem=new Map(ECOSYSTEMS.map(ecosystem=>[ecosystem,[]]));let extendedPackageSchema=!1;for(const ecosystem of ECOSYSTEMS){const upstream=inventory.upstreams[ecosystem],entries=upstream?.components;invariant(Array.isArray(entries),`${ecosystem}: upstream components must be an array`);const identities=Array.isArray(upstream?.packages)?upstream.packages:typeof upstream?.package=="string"&&upstream.package?[{name:upstream.package,tiers:["free","pro"]}]:[];Array.isArray(upstream?.packages)&&(extendedPackageSchema=!0),invariant(identities.length>0,`${ecosystem}: packages must contain at least one identity`);for(const identity of identities){invariant(identity&&typeof identity.name=="string"&&identity.name,`${ecosystem}: package identity needs a non-empty name`),invariant(!packageIdentities.has(identity.name),`duplicate package identity ${identity.name}`),invariant(Array.isArray(identity.tiers)&&identity.tiers.length>0,`${identity.name}: tiers must be a non-empty array`),invariant(identity.tiers.every(tier=>PACKAGE_TIERS.has(tier)),`${identity.name}: tiers contain an unsupported value`),invariant(new Set(identity.tiers).size===identity.tiers.length,`${identity.name}: tiers contain duplicates`);const normalized={ecosystem,tiers:new Set(identity.tiers)};packageIdentities.set(identity.name,normalized),packagesByEcosystem.get(ecosystem).push({name:identity.name,tiers:normalized.tiers})}for(const component of entries)invariant(!upstreamComponents.has(component.tag),`duplicate upstream component ${component.tag}`),Object.hasOwn(component,"tier")&&(invariant(PACKAGE_TIERS.has(component.tier),`${component.tag}: unsupported package tier`),invariant(packagesByEcosystem.get(ecosystem).some(identity=>identity.tiers.has(component.tier)),`${component.tag}: no ${ecosystem} package identity provides tier ${component.tier}`)),upstreamComponents.set(component.tag,{ecosystem,component})}const mappings=new Map;for(const mapping of inventory.mappings){invariant(typeof mapping.upstreamTag=="string"&&mapping.upstreamTag,"every mapping needs upstreamTag"),invariant(!mappings.has(mapping.upstreamTag),`duplicate mapping ${mapping.upstreamTag}`),invariant(CLASSIFICATIONS.has(mapping.classification),`${mapping.upstreamTag}: invalid classification`),invariant(mapping.upstream==="webawesome"||mapping.upstream==="shoelace",`${mapping.upstreamTag}: invalid upstream`);const upstreamEntry=upstreamComponents.get(mapping.upstreamTag);invariant(upstreamEntry?.ecosystem===mapping.upstream,`${mapping.upstreamTag}: missing upstream surface`);const target=components.get(mapping.targetTag);AUTO_CLASSIFICATIONS.has(mapping.classification)&&invariant(target,`${mapping.upstreamTag}: automatic mapping needs a registered target`),invariant(mapping.rewrites&&typeof mapping.rewrites=="object",`${mapping.upstreamTag}: rewrites missing`);const unknownRewriteKeys=Object.keys(mapping.rewrites).filter(key=>!REWRITE_RULE_SECTIONS.includes(key));invariant(unknownRewriteKeys.length===0,`${mapping.upstreamTag}: unknown rewrite section(s) ${unknownRewriteKeys.join(", ")}`);for(const section of MEMBER_RULE_SECTIONS)if(runtimeInventory){const surfaces=runtimeMemberSurfaces(mapping,section);validateMemberRules(mapping,surfaces.source,surfaces.target,section)}else validateMemberRules(mapping,upstreamEntry.component,target,section);if(runtimeInventory){const surfaces=runtimeDefaultSurfaces(mapping);validateDefaultRules(mapping,surfaces.source,surfaces.target),invariant(!Object.hasOwn(mapping,"normalizations")&&!Object.hasOwn(mapping,"drift"),`${mapping.upstreamTag}: packaged runtime mappings cannot carry analyzer-only data`)}else{validateDefaultRules(mapping,upstreamEntry.component,target);const normalizationFindings=validateMappingNormalizations(mapping,{upstream:upstreamEntry.component.surface,target:target?.surface});invariant(normalizationFindings.length===0,normalizationFindings.join("; ")),invariant(Array.isArray(mapping.drift),`${mapping.upstreamTag}: drift must be an array`)}if(extendedPackageSchema){const parity=mapping.parity;invariant(parity&&typeof parity=="object"&&!Array.isArray(parity),`${mapping.upstreamTag}: parity missing`);const parityKeys=Object.keys(parity).filter(key=>!["staticApi","lightDom","runtime","behaviorReviewFlags","accessibility","methodEdges"].includes(key));invariant(parityKeys.length===0,`${mapping.upstreamTag}: parity has unknown key(s) ${parityKeys.join(", ")}`),invariant(STATIC_API_STATUSES.has(parity.staticApi),`${mapping.upstreamTag}: invalid parity.staticApi`),invariant(LIGHT_DOM_STATUSES.has(parity.lightDom),`${mapping.upstreamTag}: invalid parity.lightDom`),invariant(Array.isArray(parity.behaviorReviewFlags)&&parity.behaviorReviewFlags.every(flag=>typeof flag=="string"&&flag)&&new Set(parity.behaviorReviewFlags).size===parity.behaviorReviewFlags.length,`${mapping.upstreamTag}: parity.behaviorReviewFlags must contain unique non-empty strings`),invariant(parity.runtime&&typeof parity.runtime=="object"&&!Array.isArray(parity.runtime),`${mapping.upstreamTag}: parity.runtime missing`),invariant(Object.keys(parity.runtime).every(key=>key==="registration"||key==="optionalPeers"),`${mapping.upstreamTag}: parity.runtime has unknown keys`),invariant(REGISTRATION_STATUSES.has(parity.runtime.registration),`${mapping.upstreamTag}: invalid parity.runtime.registration`),invariant(Array.isArray(parity.runtime.optionalPeers),`${mapping.upstreamTag}: parity.runtime.optionalPeers must be an array`);const expectedRegistration=target?target.rootIncluded===!1?"granular":"all":"unavailable";invariant(parity.runtime.registration===expectedRegistration,`${mapping.upstreamTag}: parity runtime registration is stale`);const expectedPeers=[...target?.optionalPeers??[]].sort();invariant(JSON.stringify([...parity.runtime.optionalPeers].sort())===JSON.stringify(expectedPeers),`${mapping.upstreamTag}: parity runtime optional peers are stale`);const methodEdgeFindings=validateMethodEdgeParity(mapping,runtimeInventory?{}:{upstream:upstreamEntry.component.surface,target:target?.surface});invariant(methodEdgeFindings.length===0,methodEdgeFindings.join("; ")),AUTO_CLASSIFICATIONS.has(mapping.classification)&&(parity.behaviorReviewFlags.length>0?invariant(mapping.classification==="rewritten"&&CONDITIONAL_BEHAVIOR_REVIEW_TAGS.has(mapping.upstreamTag)&&parity.lightDom==="warning-required",`${mapping.upstreamTag}: automatic behavior review lacks a registered conditional scanner`):invariant(parity.lightDom!=="warning-required"&&parity.lightDom!=="unreviewed",`${mapping.upstreamTag}: automatic mapping cannot require unscoped light-DOM review`))}if(!runtimeInventory&&target&&upstreamEntry.component.review?.status==="complete"){const expectedDrift=compareMappedSurfaces(upstreamEntry.component.surface,target.surface,{upstreamPrefix:mapping.upstream==="webawesome"?"wa-":"sl-",rewrites:mapping.rewrites,normalizations:mapping.normalizations});invariant(JSON.stringify(mapping.drift)===JSON.stringify(expectedDrift),`${mapping.upstreamTag}: stored surface drift is stale`)}mapping.classification==="exact"&&(runtimeInventory||invariant(mapping.drift.length===0,`${mapping.upstreamTag}: exact mapping has surface drift`),invariant(REWRITE_RULE_SECTIONS.every(section=>mapping.rewrites[section].length===0),`${mapping.upstreamTag}: exact mapping cannot declare rewrite rules`)),mapping.classification==="rewritten"&&(invariant(REWRITE_RULE_SECTIONS.some(section=>mapping.rewrites[section].length>0),`${mapping.upstreamTag}: rewritten mapping needs at least one deterministic rule`),runtimeInventory||invariant(mapping.drift.every(finding=>driftCovered(mapping,finding)),`${mapping.upstreamTag}: rewritten mapping contains drift without a deterministic rule`)),mappings.set(mapping.upstreamTag,{...mapping,drift:mapping.drift??[],source:upstreamEntry.component,target})}invariant(mappings.size===upstreamComponents.size,"every upstream tag must have exactly one mapping");for(const tag of upstreamComponents.keys())invariant(mappings.has(tag),`${tag}: missing mapping`);return{inventory,components,mappings,upstreamComponents,localMigrations,renameProfiles,packageIdentities,packagesByEcosystem}}function createMigrationRuntimeInventory(inventory,{renameLedger,exportDeprecations=[],compatibilityContext=null}={}){invariant(!Object.hasOwn(inventory??{},"migrationRuntimeSchemaVersion"),"cannot project an already-packaged migration runtime inventory"),invariant(renameLedger&&typeof renameLedger=="object","createMigrationRuntimeInventory needs the rename ledger"),buildMigrationContract(inventory,{renameLedger,exportDeprecations,compatibilityContext});const targetTags=new Set(inventory.mappings.map(mapping=>mapping.targetTag).filter(Boolean)),localAttributes=new Map;for(const profile of inventory.localMigrations){targetTags.add(profile.tag);const names=localAttributes.get(profile.tag)??new Set;for(const rule of profile.defaults)names.add(rule.member);localAttributes.set(profile.tag,names)}return{schemaVersion:1,migrationRuntimeSchemaVersion:MIGRATION_RUNTIME_SCHEMA_VERSION,accessibilityProfiles:structuredClone(inventory.accessibilityProfiles),components:inventory.components.filter(component=>targetTags.has(component.tag)).map(component=>({tag:component.tag,registrationModule:component.registrationModule,rootIncluded:component.rootIncluded,optionalPeers:structuredClone(component.optionalPeers??[]),surface:{attributes:(component.surface?.attributes??[]).filter(attribute=>localAttributes.get(component.tag)?.has(attribute.name)).map(attribute=>({name:attribute.name,type:attribute.type}))}})),localMigrations:structuredClone(inventory.localMigrations),upstreams:Object.fromEntries(ECOSYSTEMS.map(ecosystem=>{const upstream=inventory.upstreams[ecosystem];return[ecosystem,{packages:structuredClone(upstream.packages),components:upstream.components.map(component=>({tag:component.tag,tier:component.tier}))}]})),mappings:inventory.mappings.map(mapping=>({upstream:mapping.upstream,upstreamTag:mapping.upstreamTag,targetTag:mapping.targetTag,classification:mapping.classification,rationale:mapping.rationale,parity:structuredClone(mapping.parity),rewrites:structuredClone(mapping.rewrites)})),lyraRenames:projectRenameLedger(renameLedger,inventory,{exportDeprecations,compatibilityContext})}}function readRenameLedger(file=renameLedgerPath){return JSON.parse(fs.readFileSync(file,"utf8"))}function readExportDeprecations(file=path.join(packageDir,"scripts","fixtures","component-metadata.json")){const records=JSON.parse(fs.readFileSync(file,"utf8")).exportDeprecations;return invariant(Array.isArray(records),"component metadata must contain exportDeprecations"),records}function buildMirrorMap(readmeText){const map=new Map,conflicts=[];let mode=null;const setMapping=(from,to)=>{map.has(from)&&map.get(from)!==to?conflicts.push(`${from}: already mapped to ${map.get(from)}, also saw ${to}`):map.set(from,to)};for(const rawLine of readmeText.split(`
1
+ import fs from"node:fs";import path from"node:path";import{fileURLToPath}from"node:url";import{LOCAL_MIGRATION_ORIGINS,compareMappedSurfaces,validateAccessibilityContract,validateLocalMigrations,validateMappingNormalizations,validateMethodEdgeParity}from"./component-inventory.mjs";import{LYRA_RENAME_ORIGINS,createRenameProfiles,emptyRenameProjection,projectRenameLedger,validateRenameLedgerShape}from"./lyra-rename-ledger.mjs";const scriptDir=path.dirname(fileURLToPath(import.meta.url)),packageDir=path.resolve(scriptDir,".."),packagedInventoryPath=path.join(scriptDir,"migration-contract.json"),packagedRuntime=fs.existsSync(packagedInventoryPath),inventoryPath=packagedRuntime?packagedInventoryPath:path.join(packageDir,"scripts","fixtures","component-inventory.json"),renameLedgerPath=path.join(packageDir,"scripts","fixtures","lyra-renames.json"),MIGRATION_RUNTIME_SCHEMA_VERSION=3,MIGRATION_ORIGINS=Object.freeze([...LOCAL_MIGRATION_ORIGINS,...LYRA_RENAME_ORIGINS]),AUTO_CLASSIFICATIONS=new Set(["exact","rewritten"]),CLASSIFICATIONS=new Set(["exact","rewritten","warning-required","conceptual-only","unsupported"]),MEMBER_RULE_SECTIONS=["attributes","properties","events","slots","parts","cssProperties","methods"],REWRITE_RULE_SECTIONS=[...MEMBER_RULE_SECTIONS,"defaults"],ECOSYSTEMS=["webawesome","shoelace"],PACKAGE_TIERS=new Set(["free","pro"]),STATIC_API_STATUSES=new Set(["reviewed","tag-only","unreviewed"]),LIGHT_DOM_STATUSES=new Set(["not-applicable","surface-only","warning-required","unreviewed"]),REGISTRATION_STATUSES=new Set(["all","granular","unavailable"]),CONDITIONAL_BEHAVIOR_REVIEW_TAGS=new Set(["wa-random-content"]);function invariant(condition,message){if(!condition)throw new Error(`Invalid component inventory migration contract: ${message}`)}function surfaceNames(component,section){return new Set((component?.surface?.[section]??[]).map(entry=>entry.name))}function isScalar(value){return value===null||typeof value=="string"||typeof value=="boolean"||typeof value=="number"&&Number.isFinite(value)}function validateMemberRules(mapping,source,target,section){const rules=mapping.rewrites[section];invariant(Array.isArray(rules),`${mapping.upstreamTag}: rewrites.${section} must be an array`);const sourceNames=surfaceNames(source,section),targetNames=surfaceNames(target,section),seen=new Set;for(const rule of rules){invariant(rule&&typeof rule.from=="string"&&rule.from&&typeof rule.to=="string"&&rule.to,`${mapping.upstreamTag}: every rewrites.${section} rule needs non-empty from/to`);const unknownKeys=Object.keys(rule).filter(key=>key!=="from"&&key!=="to");invariant(unknownKeys.length===0,`${mapping.upstreamTag}: rewrites.${section} rule has unknown key(s) ${unknownKeys.join(", ")}`),invariant(rule.from!==rule.to,`${mapping.upstreamTag}: rewrites.${section} cannot map a member to itself`),invariant(!seen.has(rule.from),`${mapping.upstreamTag}: duplicate rewrites.${section} source ${rule.from}`),seen.add(rule.from),invariant(sourceNames.has(rule.from),`${mapping.upstreamTag}: unknown ${section} source ${rule.from}`),invariant(targetNames.has(rule.to),`${mapping.upstreamTag}: unknown target ${section} member ${rule.to}`)}}function validateDefaultRules(mapping,source,target){const rules=mapping.rewrites.defaults;invariant(Array.isArray(rules),`${mapping.upstreamTag}: rewrites.defaults must be an array`);const seen=new Set;for(const rule of rules){invariant(rule?.memberKind==="attribute"||rule?.memberKind==="property",`${mapping.upstreamTag}: default memberKind must be attribute or property`),invariant(typeof rule.member=="string"&&rule.member,`${mapping.upstreamTag}: default rule needs member`),invariant(rule.action==="insert-if-absent"||rule.action==="replace-value",`${mapping.upstreamTag}: unsupported default action ${String(rule.action)}`);const allowedKeys=rule.action==="insert-if-absent"?new Set(["memberKind","member","action","value"]):new Set(["memberKind","member","action","from","to"]),unknownKeys=Object.keys(rule).filter(key2=>!allowedKeys.has(key2));invariant(unknownKeys.length===0,`${mapping.upstreamTag}: default rule has unknown key(s) ${unknownKeys.join(", ")}`);const key=`${rule.memberKind}:${rule.member}:${rule.action}`;invariant(!seen.has(key),`${mapping.upstreamTag}: duplicate default rule ${key}`),seen.add(key);const section=rule.memberKind==="attribute"?"attributes":"properties";invariant(surfaceNames(target,section).has(rule.member),`${mapping.upstreamTag}: unknown target ${key}`),rule.action==="insert-if-absent"?(invariant(rule.memberKind==="attribute",`${mapping.upstreamTag}: insert-if-absent is only deterministic for attributes`),invariant(Object.hasOwn(rule,"value"),`${mapping.upstreamTag}: ${key} needs value`),invariant(isScalar(rule.value),`${mapping.upstreamTag}: ${key} value must be a scalar`),invariant(!Object.hasOwn(rule,"from")&&!Object.hasOwn(rule,"to"),`${mapping.upstreamTag}: ${key} cannot use from/to`)):(invariant(surfaceNames(source,section).has(rule.member),`${mapping.upstreamTag}: unknown source ${key}`),invariant(Object.hasOwn(rule,"from")&&Object.hasOwn(rule,"to"),`${mapping.upstreamTag}: ${key} needs from/to`),invariant(isScalar(rule.from)&&isScalar(rule.to),`${mapping.upstreamTag}: ${key} from/to must be scalars`),invariant(rule.from!==rule.to,`${mapping.upstreamTag}: ${key} cannot replace a value with itself`),invariant(!Object.hasOwn(rule,"value"),`${mapping.upstreamTag}: ${key} cannot use value`))}}function driftCovered(mapping,finding){const codeToSection={"missing-attribute":"attributes","polarity-mismatch":"attributes","missing-property":"properties","missing-event":"events","missing-slot":"slots","missing-part":"parts","missing-css-property":"cssProperties","missing-method":"methods"};if(finding.code==="default-mismatch")return mapping.rewrites.defaults.some(rule=>rule.member===finding.member);const section=codeToSection[finding.code];return!!(section&&mapping.rewrites[section].some(rule=>rule.from===finding.member))}function runtimeMemberSurfaces(mapping,section){const rules=Array.isArray(mapping.rewrites?.[section])?mapping.rewrites[section]:[];return{source:{surface:{[section]:rules.map(rule=>({name:rule?.from}))}},target:{surface:{[section]:rules.map(rule=>({name:rule?.to}))}}}}function runtimeDefaultSurfaces(mapping){const source={surface:{attributes:[],properties:[]}},target={surface:{attributes:[],properties:[]}},rules=Array.isArray(mapping.rewrites?.defaults)?mapping.rewrites.defaults:[];for(const rule of rules){const section=rule?.memberKind==="attribute"?"attributes":"properties";target.surface[section].push({name:rule?.member}),rule?.action==="replace-value"&&source.surface[section].push({name:rule?.member})}return{source,target}}function buildMigrationContract(inventory,{renameLedger=null,exportDeprecations=[],lyraVersion=null,compatibilityContext=null}={}){invariant(inventory?.schemaVersion===1,"schemaVersion must be 1");const runtimeInventory=Object.hasOwn(inventory,"migrationRuntimeSchemaVersion");if(runtimeInventory){invariant(inventory.migrationRuntimeSchemaVersion===MIGRATION_RUNTIME_SCHEMA_VERSION,`migrationRuntimeSchemaVersion must be ${MIGRATION_RUNTIME_SCHEMA_VERSION}`),invariant(renameLedger===null,"a packaged runtime inventory carries its own rename projection");const renameFindings=validateRenameLedgerShape(inventory.lyraRenames,{projected:!0});invariant(renameFindings.length===0,renameFindings.join("; "))}const renameProfiles=createRenameProfiles(runtimeInventory?inventory.lyraRenames:renameLedger?projectRenameLedger(renameLedger,inventory,{exportDeprecations,compatibilityContext}):emptyRenameProjection(),{lyraVersion});invariant(Array.isArray(inventory.components),"components must be an array"),invariant(Array.isArray(inventory.mappings),"mappings must be an array"),invariant(inventory.upstreams&&typeof inventory.upstreams=="object","upstreams must be an object");const accessibilityFindings=validateAccessibilityContract(inventory.accessibilityProfiles,inventory.mappings);invariant(accessibilityFindings.length===0,accessibilityFindings.join("; "));const components=new Map;for(const component of inventory.components){invariant(typeof component.tag=="string"&&component.tag,"every Lyra component needs a tag"),invariant(!components.has(component.tag),`duplicate Lyra component ${component.tag}`),invariant(typeof component.registrationModule=="string"&&component.registrationModule.endsWith(".ts"),`${component.tag}: registrationModule must end in .ts`),Object.hasOwn(component,"rootIncluded")&&invariant(typeof component.rootIncluded=="boolean",`${component.tag}: rootIncluded must be boolean`),Object.hasOwn(component,"optionalPeers")&&(invariant(Array.isArray(component.optionalPeers),`${component.tag}: optionalPeers must be an array`),invariant(component.optionalPeers.every(peer=>typeof peer=="string"&&peer),`${component.tag}: optionalPeers entries must be non-empty package names`));const registrationRoute=component.registrationRoute??compatibilityContext?.componentRegistrationRoutes?.[component.tag];registrationRoute!==void 0&&invariant(registrationRoute===`./components/${component.tag}.js`,`${component.tag}: registrationRoute must be its stable tag-shaped package entry`),components.set(component.tag,registrationRoute===void 0?component:{...component,registrationRoute})}const localMigrationFindings=validateLocalMigrations(inventory);invariant(localMigrationFindings.length===0,localMigrationFindings.join("; "));const localMigrations=new Map(LOCAL_MIGRATION_ORIGINS.map(origin=>[origin,new Map]));for(const profile of inventory.localMigrations)localMigrations.get(profile.origin).set(profile.tag,profile);const upstreamComponents=new Map,packageIdentities=new Map,packagesByEcosystem=new Map(ECOSYSTEMS.map(ecosystem=>[ecosystem,[]]));let extendedPackageSchema=!1;for(const ecosystem of ECOSYSTEMS){const upstream=inventory.upstreams[ecosystem],entries=upstream?.components;invariant(Array.isArray(entries),`${ecosystem}: upstream components must be an array`);const identities=Array.isArray(upstream?.packages)?upstream.packages:typeof upstream?.package=="string"&&upstream.package?[{name:upstream.package,tiers:["free","pro"]}]:[];Array.isArray(upstream?.packages)&&(extendedPackageSchema=!0),invariant(identities.length>0,`${ecosystem}: packages must contain at least one identity`);for(const identity of identities){invariant(identity&&typeof identity.name=="string"&&identity.name,`${ecosystem}: package identity needs a non-empty name`),invariant(!packageIdentities.has(identity.name),`duplicate package identity ${identity.name}`),invariant(Array.isArray(identity.tiers)&&identity.tiers.length>0,`${identity.name}: tiers must be a non-empty array`),invariant(identity.tiers.every(tier=>PACKAGE_TIERS.has(tier)),`${identity.name}: tiers contain an unsupported value`),invariant(new Set(identity.tiers).size===identity.tiers.length,`${identity.name}: tiers contain duplicates`);const normalized={ecosystem,tiers:new Set(identity.tiers)};packageIdentities.set(identity.name,normalized),packagesByEcosystem.get(ecosystem).push({name:identity.name,tiers:normalized.tiers})}for(const component of entries)invariant(!upstreamComponents.has(component.tag),`duplicate upstream component ${component.tag}`),Object.hasOwn(component,"tier")&&(invariant(PACKAGE_TIERS.has(component.tier),`${component.tag}: unsupported package tier`),invariant(packagesByEcosystem.get(ecosystem).some(identity=>identity.tiers.has(component.tier)),`${component.tag}: no ${ecosystem} package identity provides tier ${component.tier}`)),upstreamComponents.set(component.tag,{ecosystem,component})}const mappings=new Map;for(const mapping of inventory.mappings){invariant(typeof mapping.upstreamTag=="string"&&mapping.upstreamTag,"every mapping needs upstreamTag"),invariant(!mappings.has(mapping.upstreamTag),`duplicate mapping ${mapping.upstreamTag}`),invariant(CLASSIFICATIONS.has(mapping.classification),`${mapping.upstreamTag}: invalid classification`),invariant(mapping.upstream==="webawesome"||mapping.upstream==="shoelace",`${mapping.upstreamTag}: invalid upstream`);const upstreamEntry=upstreamComponents.get(mapping.upstreamTag);invariant(upstreamEntry?.ecosystem===mapping.upstream,`${mapping.upstreamTag}: missing upstream surface`);const target=components.get(mapping.targetTag);AUTO_CLASSIFICATIONS.has(mapping.classification)&&invariant(target,`${mapping.upstreamTag}: automatic mapping needs a registered target`),invariant(mapping.rewrites&&typeof mapping.rewrites=="object",`${mapping.upstreamTag}: rewrites missing`);const unknownRewriteKeys=Object.keys(mapping.rewrites).filter(key=>!REWRITE_RULE_SECTIONS.includes(key));invariant(unknownRewriteKeys.length===0,`${mapping.upstreamTag}: unknown rewrite section(s) ${unknownRewriteKeys.join(", ")}`);for(const section of MEMBER_RULE_SECTIONS)if(runtimeInventory){const surfaces=runtimeMemberSurfaces(mapping,section);validateMemberRules(mapping,surfaces.source,surfaces.target,section)}else validateMemberRules(mapping,upstreamEntry.component,target,section);if(runtimeInventory){const surfaces=runtimeDefaultSurfaces(mapping);validateDefaultRules(mapping,surfaces.source,surfaces.target),invariant(!Object.hasOwn(mapping,"normalizations")&&!Object.hasOwn(mapping,"drift"),`${mapping.upstreamTag}: packaged runtime mappings cannot carry analyzer-only data`)}else{validateDefaultRules(mapping,upstreamEntry.component,target);const normalizationFindings=validateMappingNormalizations(mapping,{upstream:upstreamEntry.component.surface,target:target?.surface});invariant(normalizationFindings.length===0,normalizationFindings.join("; ")),invariant(Array.isArray(mapping.drift),`${mapping.upstreamTag}: drift must be an array`)}if(extendedPackageSchema){const parity=mapping.parity;invariant(parity&&typeof parity=="object"&&!Array.isArray(parity),`${mapping.upstreamTag}: parity missing`);const parityKeys=Object.keys(parity).filter(key=>!["staticApi","lightDom","runtime","behaviorReviewFlags","accessibility","methodEdges"].includes(key));invariant(parityKeys.length===0,`${mapping.upstreamTag}: parity has unknown key(s) ${parityKeys.join(", ")}`),invariant(STATIC_API_STATUSES.has(parity.staticApi),`${mapping.upstreamTag}: invalid parity.staticApi`),invariant(LIGHT_DOM_STATUSES.has(parity.lightDom),`${mapping.upstreamTag}: invalid parity.lightDom`),invariant(Array.isArray(parity.behaviorReviewFlags)&&parity.behaviorReviewFlags.every(flag=>typeof flag=="string"&&flag)&&new Set(parity.behaviorReviewFlags).size===parity.behaviorReviewFlags.length,`${mapping.upstreamTag}: parity.behaviorReviewFlags must contain unique non-empty strings`),invariant(parity.runtime&&typeof parity.runtime=="object"&&!Array.isArray(parity.runtime),`${mapping.upstreamTag}: parity.runtime missing`),invariant(Object.keys(parity.runtime).every(key=>key==="registration"||key==="optionalPeers"),`${mapping.upstreamTag}: parity.runtime has unknown keys`),invariant(REGISTRATION_STATUSES.has(parity.runtime.registration),`${mapping.upstreamTag}: invalid parity.runtime.registration`),invariant(Array.isArray(parity.runtime.optionalPeers),`${mapping.upstreamTag}: parity.runtime.optionalPeers must be an array`);const expectedRegistration=target?target.rootIncluded===!1?"granular":"all":"unavailable";invariant(parity.runtime.registration===expectedRegistration,`${mapping.upstreamTag}: parity runtime registration is stale`);const expectedPeers=[...target?.optionalPeers??[]].sort();invariant(JSON.stringify([...parity.runtime.optionalPeers].sort())===JSON.stringify(expectedPeers),`${mapping.upstreamTag}: parity runtime optional peers are stale`);const methodEdgeFindings=validateMethodEdgeParity(mapping,runtimeInventory?{}:{upstream:upstreamEntry.component.surface,target:target?.surface});invariant(methodEdgeFindings.length===0,methodEdgeFindings.join("; ")),AUTO_CLASSIFICATIONS.has(mapping.classification)&&(parity.behaviorReviewFlags.length>0?invariant(mapping.classification==="rewritten"&&CONDITIONAL_BEHAVIOR_REVIEW_TAGS.has(mapping.upstreamTag)&&parity.lightDom==="warning-required",`${mapping.upstreamTag}: automatic behavior review lacks a registered conditional scanner`):invariant(parity.lightDom!=="warning-required"&&parity.lightDom!=="unreviewed",`${mapping.upstreamTag}: automatic mapping cannot require unscoped light-DOM review`))}if(!runtimeInventory&&target&&upstreamEntry.component.review?.status==="complete"){const expectedDrift=compareMappedSurfaces(upstreamEntry.component.surface,target.surface,{upstreamPrefix:mapping.upstream==="webawesome"?"wa-":"sl-",rewrites:mapping.rewrites,normalizations:mapping.normalizations});invariant(JSON.stringify(mapping.drift)===JSON.stringify(expectedDrift),`${mapping.upstreamTag}: stored surface drift is stale`)}mapping.classification==="exact"&&(runtimeInventory||invariant(mapping.drift.length===0,`${mapping.upstreamTag}: exact mapping has surface drift`),invariant(REWRITE_RULE_SECTIONS.every(section=>mapping.rewrites[section].length===0),`${mapping.upstreamTag}: exact mapping cannot declare rewrite rules`)),mapping.classification==="rewritten"&&(invariant(REWRITE_RULE_SECTIONS.some(section=>mapping.rewrites[section].length>0),`${mapping.upstreamTag}: rewritten mapping needs at least one deterministic rule`),runtimeInventory||invariant(mapping.drift.every(finding=>driftCovered(mapping,finding)),`${mapping.upstreamTag}: rewritten mapping contains drift without a deterministic rule`)),mappings.set(mapping.upstreamTag,{...mapping,drift:mapping.drift??[],source:upstreamEntry.component,target})}invariant(mappings.size===upstreamComponents.size,"every upstream tag must have exactly one mapping");for(const tag of upstreamComponents.keys())invariant(mappings.has(tag),`${tag}: missing mapping`);return{inventory,components,mappings,upstreamComponents,localMigrations,renameProfiles,packageIdentities,packagesByEcosystem}}function createMigrationRuntimeInventory(inventory,{renameLedger,exportDeprecations=[],compatibilityContext=null}={}){invariant(!Object.hasOwn(inventory??{},"migrationRuntimeSchemaVersion"),"cannot project an already-packaged migration runtime inventory"),invariant(renameLedger&&typeof renameLedger=="object","createMigrationRuntimeInventory needs the rename ledger"),buildMigrationContract(inventory,{renameLedger,exportDeprecations,compatibilityContext});const targetTags=new Set(inventory.mappings.map(mapping=>mapping.targetTag).filter(Boolean)),localAttributes=new Map;for(const profile of inventory.localMigrations){targetTags.add(profile.tag);const names=localAttributes.get(profile.tag)??new Set;for(const rule of profile.defaults)names.add(rule.member);localAttributes.set(profile.tag,names)}return{schemaVersion:1,migrationRuntimeSchemaVersion:MIGRATION_RUNTIME_SCHEMA_VERSION,accessibilityProfiles:structuredClone(inventory.accessibilityProfiles),components:inventory.components.filter(component=>targetTags.has(component.tag)).map(component=>({tag:component.tag,registrationModule:component.registrationModule,...compatibilityContext?.componentRegistrationRoutes?.[component.tag]?{registrationRoute:compatibilityContext.componentRegistrationRoutes[component.tag]}:{},rootIncluded:component.rootIncluded,optionalPeers:structuredClone(component.optionalPeers??[]),surface:{attributes:(component.surface?.attributes??[]).filter(attribute=>localAttributes.get(component.tag)?.has(attribute.name)).map(attribute=>({name:attribute.name,type:attribute.type}))}})),localMigrations:structuredClone(inventory.localMigrations),upstreams:Object.fromEntries(ECOSYSTEMS.map(ecosystem=>{const upstream=inventory.upstreams[ecosystem];return[ecosystem,{packages:structuredClone(upstream.packages),components:upstream.components.map(component=>({tag:component.tag,tier:component.tier}))}]})),mappings:inventory.mappings.map(mapping=>({upstream:mapping.upstream,upstreamTag:mapping.upstreamTag,targetTag:mapping.targetTag,classification:mapping.classification,rationale:mapping.rationale,parity:structuredClone(mapping.parity),rewrites:structuredClone(mapping.rewrites)})),lyraRenames:projectRenameLedger(renameLedger,inventory,{exportDeprecations,compatibilityContext})}}function readRenameLedger(file=renameLedgerPath){return JSON.parse(fs.readFileSync(file,"utf8"))}function readExportDeprecations(file=path.join(packageDir,"scripts","fixtures","component-metadata.json")){const records=JSON.parse(fs.readFileSync(file,"utf8")).exportDeprecations;return invariant(Array.isArray(records),"component metadata must contain exportDeprecations"),records}function buildMirrorMap(readmeText){const map=new Map,conflicts=[];let mode=null;const setMapping=(from,to)=>{map.has(from)&&map.get(from)!==to?conflicts.push(`${from}: already mapped to ${map.get(from)}, also saw ${to}`):map.set(from,to)};for(const rawLine of readmeText.split(`
2
2
  `)){const line=rawLine.trim();if(/^\|\s*Component\s*\|\s*Mirrors\s*\|\s*Notes\s*\|$/.test(line)){mode="wa";continue}if(/^\|\s*Shoelace\s*\|\s*Lyra\s*\|\s*Migration note\s*\|$/.test(line)){mode="sl";continue}if(!line.startsWith("|")){mode=null;continue}if(!mode)continue;const cells=line.split("|").slice(1,-1).map(cell=>cell.trim());if(cells.length<2)continue;if(mode==="sl"){const source=[...cells[0].matchAll(/<sl-([a-z0-9-]+)>/g)].map(match=>match[1]),targets2=new Set([...cells[1].matchAll(/<lr-([a-z0-9-]+)>/g)].map(match=>match[1]));for(const suffix of source)targets2.has(suffix)&&setMapping(`sl-${suffix}`,`lr-${suffix}`);continue}const targets=[...cells[0].matchAll(/<lr-([a-z0-9-]+)>/g)].map(match=>match[1]),sources=[...cells[1].matchAll(/`((?:wa|sl)-[a-z0-9-]+\*?)`/g)].map(match=>match[1]);if(!targets.length||!sources.length)continue;const consumed=new Set;for(const suffix of targets)for(const prefix of["wa","sl"]){const expected=`${prefix}-${suffix}`;if(sources.includes(expected))setMapping(expected,`lr-${suffix}`),consumed.add(expected);else{const wildcard=sources.find(source=>source.endsWith("*")&&expected.startsWith(source.slice(0,-1)));wildcard&&(setMapping(expected,`lr-${suffix}`),consumed.add(wildcard))}}if(targets.length===1)for(const source of sources)!consumed.has(source)&&!source.endsWith("*")&&setMapping(source,`lr-${targets[0]}`)}return{map,conflicts}}export{AUTO_CLASSIFICATIONS,MIGRATION_ORIGINS,MIGRATION_RUNTIME_SCHEMA_VERSION,buildMigrationContract,buildMirrorMap,createMigrationRuntimeInventory,invariant,inventoryPath,packagedRuntime,readExportDeprecations,readRenameLedger};
@@ -1,4 +1,4 @@
1
- import{VOID_HTML_TAGS,camelCase,commentRanges,finalizeEdits,findTagEnd,kebabCase,lineStarts,localMigrationKey,locationAt,memberStyle,moduleSpecifierContext,parseNamedModuleBindings,parseTagAttributes,regexEscape,reportEntry,scanAllOpeningTags,scanLocalMigrationHazards,serializeLocalDefault,skipBalanced}from"./migration-analysis.mjs";import{invariant}from"./migration-contract.mjs";import{htmlCommentEnd}from"./html-comments.mjs";const RENAME_REVIEW="RENAME_REVIEW",RENAME_TARGET_SHARED_REVIEW="RENAME_TARGET_SHARED_REVIEW",NAME_GAINED_OWNER_REVIEW="NAME_GAINED_OWNER_REVIEW",POLARITY_REVIEW="POLARITY_REVIEW",DETAIL_SHAPE_REVIEW="DETAIL_SHAPE_REVIEW",DEPRECATED_MEMBER_REVIEW="DEPRECATED_MEMBER_REVIEW",DEPRECATED_CONTENT_REVIEW="DEPRECATED_CONTENT_REVIEW",RENAME_CONFLICT_REVIEW="RENAME_CONFLICT_REVIEW",UNUSED_ACKNOWLEDGEMENT="UNUSED_ACKNOWLEDGEMENT",REVIEW_ACKNOWLEDGEMENT=/lyra-migrate-reviewed:\s*([^\n]*)/g,ACKNOWLEDGEMENT_TOKEN=/^([A-Z][A-Z0-9_]*):(\S+)$/,DEFAULT_SLOT_LABEL="#default",LISTENER_CALL_BEFORE=/(?:\b(?:add|remove)EventListener|\bHostListener)\s*\(\s*$/,LISTENER_RECEIVER_BEFORE=/([A-Za-z_$][\w$]*(?:\s*(?:\?\.|\.)\s*#?[A-Za-z_$][\w$]*|\s*\([^()\n]*\)|\s*!(?!=))*)\s*(?:\?\.|\.)\s*(?:add|remove)EventListener\s*\(\s*$/,LISTENER_HANDLER_AFTER=/^\s*,\s*([A-Za-z_$][\w$]*(?:\s*(?:\?\.|\.)\s*#?[A-Za-z_$][\w$]*)*)\s*[,)]/,EVENT_CONSTRUCTION=/\bnew\s+(?:CustomEvent|Event)\s*(?:<[^>\n]+>)?\(\s*(['"`])(lr-[a-z0-9]+(?:-[a-z0-9]+)*)\1/g,TAG_API_CALL_BEFORE=/(?:\b(?:createElement|querySelector(?:All)?|closest|matches|unsafeStatic|literal)|\bcustomElements\s*\.\s*(?:get|whenDefined|define))\s*(?:<[^>\n]+>)?\(\s*$/,ANCHORED_MEMBER=/\b(?:querySelector|closest|createElement)\s*(?:<[^>\n]+>)?\(\s*(['"`])(lr-[a-z0-9]+(?:-[a-z0-9]+)*)\1\s*\)\s*!?\s*(?:\?\.|\.)\s*([A-Za-z_$][\w$]*)/g,ATTRIBUTE_METHODS=new Set(["setAttribute","getAttribute","hasAttribute","removeAttribute","toggleAttribute"]),LISTENER_METHODS=new Set(["addEventListener","removeEventListener"]),STYLESHEET_FILE=/\.(?:css|scss|sass|less|pcss|styl)$/i,MARKUP_FILE=/\.(?:html?|xhtml|md|markdown|vue|svelte)$/i,ATTRIBUTE_SEMANTICS_FILE=/\.(?:html?|xhtml|md|markdown)$/i,LIT_TEMPLATE_FILE=/\.(?:[cm]?[jt]s)$/i,SELECTOR_LOOKBEHIND=256,ackName=member=>member===""?DEFAULT_SLOT_LABEL:String(member);function mergeRanges(ranges){const merged=[];for(const[start,end]of[...ranges].sort((left,right)=>left[0]-right[0])){const last=merged.at(-1);last&&start<=last[1]?last[1]=Math.max(last[1],end):merged.push([start,end])}return merged}function rangeTester(merged){return offset=>{let low=0,high=merged.length-1;for(;low<=high;){const middle=low+high>>1;if(merged[middle][1]<=offset)low=middle+1;else if(merged[middle][0]>offset)high=middle-1;else return!0}return!1}}function templateLiteralEnd(text,open){let index=open+1;for(;index<text.length;){const character=text[index];if(character==="\\")index+=2;else{if(character==="`")return index+1;character==="$"&&text[index+1]==="{"?index=templateExpressionEnd(text,index+2):index+=1}}return text.length}function templateExpressionEnd(text,index){let depth=1;for(;index<text.length;){const character=text[index];if(character==="`"){index=templateLiteralEnd(text,index);continue}if(character==='"'||character==="'"){for(index+=1;index<text.length&&text[index]!==character&&text[index]!==`
1
+ import{VOID_HTML_TAGS,camelCase,commentRanges,finalizeEdits,findTagEnd,kebabCase,lineStarts,localMigrationKey,locationAt,memberStyle,moduleSpecifierContext,parseNamedModuleBindings,parseTagAttributes,regexEscape,reportEntry,scanAllOpeningTags,scanLocalMigrationHazards,serializeLocalDefault,skipBalanced}from"./migration-analysis.mjs";import{invariant}from"./migration-contract.mjs";import{htmlCommentEnd}from"./html-comments.mjs";const RENAME_REVIEW="RENAME_REVIEW",RENAME_TARGET_SHARED_REVIEW="RENAME_TARGET_SHARED_REVIEW",NAME_GAINED_OWNER_REVIEW="NAME_GAINED_OWNER_REVIEW",POLARITY_REVIEW="POLARITY_REVIEW",DETAIL_SHAPE_REVIEW="DETAIL_SHAPE_REVIEW",DETAIL_FIELD_REVIEW="DETAIL_FIELD_REVIEW",DEPRECATED_MEMBER_REVIEW="DEPRECATED_MEMBER_REVIEW",DEPRECATED_CONTENT_REVIEW="DEPRECATED_CONTENT_REVIEW",RENAME_CONFLICT_REVIEW="RENAME_CONFLICT_REVIEW",UNUSED_ACKNOWLEDGEMENT="UNUSED_ACKNOWLEDGEMENT",REVIEW_ACKNOWLEDGEMENT=/lyra-migrate-reviewed:\s*([^\n]*)/g,ACKNOWLEDGEMENT_TOKEN=/^([A-Z][A-Z0-9_]*):(\S+)$/,DEFAULT_SLOT_LABEL="#default",LISTENER_CALL_BEFORE=/(?:\b(?:add|remove)EventListener|\bHostListener)\s*\(\s*$/,LISTENER_RECEIVER_BEFORE=/([A-Za-z_$][\w$]*(?:\s*(?:\?\.|\.)\s*#?[A-Za-z_$][\w$]*|\s*\([^()\n]*\)|\s*!(?!=))*)\s*(?:\?\.|\.)\s*(?:add|remove)EventListener\s*\(\s*$/,LISTENER_HANDLER_AFTER=/^\s*,\s*([A-Za-z_$][\w$]*(?:\s*(?:\?\.|\.)\s*#?[A-Za-z_$][\w$]*)*)\s*[,)]/,EVENT_CONSTRUCTION=/\bnew\s+(?:CustomEvent|Event)\s*(?:<[^>\n]+>)?\(\s*(['"`])(lr-[a-z0-9]+(?:-[a-z0-9]+)*)\1/g,TAG_API_CALL_BEFORE=/(?:\b(?:createElement|querySelector(?:All)?|closest|matches|unsafeStatic|literal)|\bcustomElements\s*\.\s*(?:get|whenDefined|define))\s*(?:<[^>\n]+>)?\(\s*$/,ANCHORED_MEMBER=/\b(?:querySelector|closest|createElement)\s*(?:<[^>\n]+>)?\(\s*(['"`])(lr-[a-z0-9]+(?:-[a-z0-9]+)*)\1\s*\)\s*!?\s*(?:\?\.|\.)\s*([A-Za-z_$][\w$]*)/g,ATTRIBUTE_METHODS=new Set(["setAttribute","getAttribute","hasAttribute","removeAttribute","toggleAttribute"]),LISTENER_METHODS=new Set(["addEventListener","removeEventListener"]),STYLESHEET_FILE=/\.(?:css|scss|sass|less|pcss|styl)$/i,MARKUP_FILE=/\.(?:html?|xhtml|md|markdown|vue|svelte)$/i,ATTRIBUTE_SEMANTICS_FILE=/\.(?:html?|xhtml|md|markdown)$/i,LIT_TEMPLATE_FILE=/\.(?:[cm]?[jt]s)$/i,SELECTOR_LOOKBEHIND=256,ackName=member=>member===""?DEFAULT_SLOT_LABEL:String(member);function mergeRanges(ranges){const merged=[];for(const[start,end]of[...ranges].sort((left,right)=>left[0]-right[0])){const last=merged.at(-1);last&&start<=last[1]?last[1]=Math.max(last[1],end):merged.push([start,end])}return merged}function rangeTester(merged){return offset=>{let low=0,high=merged.length-1;for(;low<=high;){const middle=low+high>>1;if(merged[middle][1]<=offset)low=middle+1;else if(merged[middle][0]>offset)high=middle-1;else return!0}return!1}}function templateLiteralEnd(text,open){let index=open+1;for(;index<text.length;){const character=text[index];if(character==="\\")index+=2;else{if(character==="`")return index+1;character==="$"&&text[index+1]==="{"?index=templateExpressionEnd(text,index+2):index+=1}}return text.length}function templateExpressionEnd(text,index){let depth=1;for(;index<text.length;){const character=text[index];if(character==="`"){index=templateLiteralEnd(text,index);continue}if(character==='"'||character==="'"){for(index+=1;index<text.length&&text[index]!==character&&text[index]!==`
2
2
  `;)index+=text[index]==="\\"?2:1;index+=1;continue}if(character==="{")depth+=1;else if(character==="}"&&--depth===0)return index+1;index+=1}return text.length}function templateLiteralRanges(text,inComment){const ranges=[];let index=0;for(;index<text.length;){const character=text[index];if(inComment(index))index+=1;else if(character==="`"){const end=templateLiteralEnd(text,index);ranges.push([index,end]),index=end}else if(character==='"'||character==="'"){for(index+=1;index<text.length&&text[index]!==character&&text[index]!==`
3
3
  `;)index+=text[index]==="\\"?2:1;index+=1}else index+=1}return ranges}function taggedTemplateRanges(text,tags,inComment){const ranges=[];for(const match of text.matchAll(new RegExp(`\\b(?:${tags.join("|")})\\s*\``,"g"))){if(inComment(match.index))continue;const open=match.index+match[0].length-1;ranges.push([open,templateLiteralEnd(text,open)])}return mergeRanges(ranges)}function renameIgnoredRanges(text,file){const base=mergeRanges(commentRanges(text)),inBase=rangeTester(base),extra=[],addHtmlComments=(start,end)=>{let index=text.indexOf("<!--",start);for(;index>=0&&index<end;){const finish=htmlCommentEnd(text,index,end);inBase(index)||extra.push([index,finish]),index=text.indexOf("<!--",finish)}};if(MARKUP_FILE.test(file))addHtmlComments(0,text.length);else if(!STYLESHEET_FILE.test(file))for(const[start,end]of templateLiteralRanges(text,inBase))addHtmlComments(start,end);return mergeRanges([...base,...extra])}function scanReviewAcknowledgements(text,ignoredRanges,starts,openingTokens){const tokenAt=new Map(openingTokens.map(token=>[token.start,token])),records=[],byLine=new Map,cover=(line,record)=>{const list=byLine.get(line)??[];list.includes(record)||list.push(record),byLine.set(line,list)};for(const[start,end]of ignoredRanges){const body=text.slice(start,end);if(!body.includes("lyra-migrate-reviewed:"))continue;const firstLine=locationAt(starts,start).line,lastLine=locationAt(starts,Math.max(start,end-1)).line;let next=end;for(;next<text.length&&/[\s}]/.test(text[next]);)next+=1;const followingTag=tokenAt.get(next),standalone=/^\s*\{?\s*$/.test(text.slice(starts[firstLine-1],start));for(const match of body.matchAll(REVIEW_ACKNOWLEDGEMENT)){const tokens=match[1].replace(/(?:\*\/|--!?>)[\s\S]*$/,"").split(/[\s,]+/).filter(Boolean),record={offset:start+match.index,entries:tokens.map(token=>{const parsed=ACKNOWLEDGEMENT_TOKEN.exec(token);return{token,code:parsed?.[1]??null,name:parsed?.[2]??null,used:!1}})};records.push(record);for(let line=firstLine;line<=lastLine;line+=1)cover(line,record);if(standalone&&cover(lastLine+1,record),followingTag){const tagLast=locationAt(starts,followingTag.end).line;for(let line=locationAt(starts,followingTag.start).line;line<=tagLast;line+=1)cover(line,record)}}}return{consume(line,code,member){const name=ackName(member);for(const record of byLine.get(line)??[]){const entry=record.entries.find(candidate=>candidate.code===code&&candidate.name===name);if(entry)return entry.used=!0,!0}return!1},unused:()=>records.flatMap(record=>record.entries.filter(entry=>!entry.used).map(entry=>({offset:record.offset,entry})))}}function quotedStringRanges(text,inComment){const ranges=[];let index=0;for(;index<text.length;){const quote=text[index];if(inComment(index)||quote!=='"'&&quote!=="'"&&quote!=="`"){index+=1;continue}const start=index;for(index+=1;index<text.length&&text[index]!==quote&&(quote==="`"||text[index]!==`
4
- `);)text[index]==="\\"&&(index+=1),index+=1;index+=1,quote!=="`"&&ranges.push([start,index])}return ranges}function stylesheetRanges(text,file,inComment){if(STYLESHEET_FILE.test(file))return[[0,text.length]];const ranges=[];for(const match of text.matchAll(/<style(?:\s[^>]*)?>[\s\S]*?<\/style\s*>/gi))ranges.push([match.index,match.index+match[0].length]);return mergeRanges([...ranges,...taggedTemplateRanges(text,["css"],inComment)])}function compoundTypeBefore(text,index){let start=index,brackets=0,parens=0;const limit=Math.max(0,index-SELECTOR_LOOKBEHIND);for(;start>limit;){const character=text[start-1];if(character==="]")brackets+=1;else if(character==="["){if(brackets===0)break;brackets-=1}else if(character===")")parens+=1;else if(character==="("){if(parens===0)break;parens-=1}else if(brackets===0&&parens===0&&/[\s,>+~{};'"`]/.test(character))break;start-=1}if(start===limit&&limit>0)return null;const compound=text.slice(start,index);return(/^:global\(\s*([A-Za-z][\w-]*)\s*\)$/.exec(compound)??/^([A-Za-z][\w-]*)/.exec(compound))?.[1].toLowerCase()??null}function mentionedTags(text,tags,inComment){const found=new Set;for(const tag of tags)for(const match of text.matchAll(new RegExp(`(?<![\\w-])${tag}(?![\\w-])`,"g")))if(!inComment(match.index)){found.add(tag);break}return found}function elementPairs(text,tag,inComment){const pairs=[],stack=[];for(const match of text.matchAll(new RegExp(`<(/)?${tag}(?=[\\s/>])`,"g"))){if(inComment(match.index))continue;const end=findTagEnd(text,match.index+match[0].length);if(!(end<0))if(match[1]){const opening=stack.pop();opening&&pairs.push({opening,closingStart:match.index})}else/\/\s*>$/.test(text.slice(match.index,end+1))||stack.push({start:match.index,end})}return pairs}function directChildTokens(text,start,end,inComment){const children=[],stack=[],pattern=/<(\/)?([A-Za-z][\w.:-]*)(?=[\s/>])/g;pattern.lastIndex=start;for(let match=pattern.exec(text);match&&match.index<end;match=pattern.exec(text)){if(inComment(match.index))continue;const tokenEnd=findTagEnd(text,match.index+match[0].length);if(tokenEnd<0||tokenEnd>=end)break;const name=match[2].toLowerCase();if(match[1]){const index=stack.lastIndexOf(name);index>=0&&(stack.length=index)}else{const nameStart=match.index+1;stack.length===0&&children.push({tag:name,start:match.index,nameStart,nameEnd:nameStart+match[2].length,end:tokenEnd}),!/\/\s*>$/.test(text.slice(match.index,tokenEnd+1))&&!VOID_HTML_TAGS.has(name)&&stack.push(name)}pattern.lastIndex=tokenEnd+1}return children}function openingTagBraceGroups(text,token){const groups=[];let index=token.nameEnd,quote=null;for(;index<token.end;){const character=text[index];if(quote){character==="\\"?index+=1:character===quote&&(quote=null),index+=1;continue}if(character==='"'||character==="'")quote=character;else if(character==="{"){const end=skipBalanced(text,index,"{","}");!/=\s*$/.test(text.slice(token.nameEnd,index))&&text[index-1]!=="$"&&groups.push({start:index,end,body:text.slice(index+1,end-1).trim()}),index=end;continue}index+=1}return groups}function markupEventName(rawName){if(/^\(lr-[a-z0-9-]+\)$/.test(rawName))return{name:rawName.slice(1,-1),offset:1};if(/^onlr-/.test(rawName))return{name:rawName.slice(2).replace(/Capture$/,""),offset:2};for(const[prefix,separator]of[["@","."],["v-on:","."],["on:","|"]])if(rawName.startsWith(prefix))return{name:rawName.slice(prefix.length).split(separator)[0],offset:prefix.length};return null}function markupMemberBinding(attribute){const raw=attribute.rawName;let match;if(raw.startsWith("?"))return{form:"attribute",offset:1,name:raw.slice(1),dynamic:!0};if(match=/^\[attr\.([^\]]+)\]$/.exec(raw))return{form:"attribute",offset:6,name:match[1],dynamic:!0};if(match=/^\[([A-Za-z_$][\w$]*)\]$/.exec(raw))return{form:"property",offset:1,name:match[1],dynamic:!0};if(raw.startsWith("."))return{form:"property",offset:1,name:raw.slice(1),dynamic:!0};if(raw.startsWith("bind:"))return{form:"property",offset:5,name:raw.slice(5).split("|")[0],dynamic:!0};for(const prefix of["v-bind:",":"])if(raw.startsWith(prefix))return{form:"member",offset:prefix.length,name:raw.slice(prefix.length).split(".")[0],dynamic:!0};if(!/^[A-Za-z][\w-]*$/.test(raw)||/^onlr-/.test(raw))return null;const dynamic=attribute.valueKind==="expression"||/\$\{|\{\{/.test(attribute.value??"");return{form:/[A-Z]/.test(raw)?"property":"attribute",offset:0,name:raw,dynamic}}function attributeEnd(text,attribute){if(attribute.valueEnd===null)return attribute.nameEnd;const quote=text[attribute.valueStart-1];return(quote==='"'||quote==="'")&&text[attribute.valueEnd]===quote?attribute.valueEnd+1:attribute.valueEnd}function boundMemberNames(attributes){const names=new Set;for(const attribute of attributes){const binding=markupMemberBinding(attribute);binding&&names.add(kebabCase(binding.name))}return names}function sortReportEntries(entries,codeKey){return entries.sort((left,right)=>left.line-right.line||left.column-right.column||String(left[codeKey]).localeCompare(String(right[codeKey])))}function reportModuleReviews(text,file,reviews,openingTokens,inComment,warn){if(!reviews.length)return;const packageName="@aceshooting/lyra-ui",specifierOf=subpath=>`${packageName}${subpath.slice(1)}`,named=reviews.filter(entry=>["function","type","constant","class"].includes(entry.kind)),report=(offset,review,context="")=>warn(offset,{member:review.name,code:"DEPRECATED_MODULE_REVIEW",target:review.replacement,message:`${context}${review.kind} ${review.module?`${review.module}#`:""}${review.name} ${review.removedIn?`was removed in ${review.removedIn}`:`is deprecated and scheduled for removal in ${review.removalNotBefore}`}; review ${review.replacement}.`}),namedFor=specifier=>named.filter(entry=>specifier===packageName||specifier===specifierOf(entry.module)),strings=quotedStringRanges(text,inComment),templates=templateLiteralRanges(text,inComment),stringStarts=new Set([...strings,...templates.filter(([start,end])=>!text.slice(start,end).includes("${"))].map(([start])=>start)),inString=rangeTester(strings),inLiteral=rangeTester(mergeRanges([...strings,...templates])),inCss=rangeTester(stylesheetRanges(text,file,inComment)),inMarkup=offset=>openingTokens.some(token=>offset>=token.nameEnd&&offset<token.end),opaque=(offset,specifier,entries)=>{entries.length&&warn(offset,{member:specifier,code:"MODULE_NAMESPACE_REVIEW",target:[...new Set(entries.map(entry=>entry.replacement))].join("; "),message:`Review the namespace or dynamic import of ${specifier} for deprecated exports ${entries.map(entry=>entry.name).join(", ")}. They cannot be safely renamed through aliases; the earliest removal is ${entries.map(entry=>entry.removalNotBefore).sort()[0]}.`})},bindings=/\b(?:import|export)\s+(?:type\s+)?(?:[$A-Z_a-z][$\w]*\s*,\s*)?\{(?<bindings>[^{};]*)\}\s*from\s*(?<quote>['"])(?<source>@aceshooting\/lyra-ui(?:\/[^'"\s]+)?)\k<quote>/g,handledSpecifiers=new Set;for(const match of text.matchAll(bindings)){if(inComment(match.index)||inLiteral(match.index))continue;const available=namedFor(match.groups.source),bodyStart=match.index+match[0].indexOf("{")+1;for(const binding of parseNamedModuleBindings(match.groups.bindings)){const review=available.find(entry=>entry.name===binding.imported);if(!review)continue;const member=[...match.groups.bindings.matchAll(new RegExp(`(?<![$\\w])${regexEscape(binding.imported)}(?![$\\w])`,"g"))].find(candidate=>!inComment(bodyStart+candidate.index));member&&report(bodyStart+member.index,review)}handledSpecifiers.add(match.index+match[0].lastIndexOf(match.groups.source)-1)}for(const match of text.matchAll(/(['"])(@aceshooting\/lyra-ui(?:\/[^'"\s]+)?)\1/g)){if(inComment(match.index))continue;const markup=inMarkup(match.index),css=inCss(match.index);if(!stringStarts.has(match.index)&&!markup&&!css)continue;const before=text.slice(Math.max(0,match.index-500),match.index),context=moduleSpecifierContext(text,match.index),cssImport=css&&/@import\s+(?:url\(\s*)?$/.test(before),resource=markup&&/\b(?:href|src)\s*=\s*$/.test(before);if(!context&&!cssImport&&!resource)continue;const specifier=match[2];for(const review of reviews)(review.kind==="entry-point"||review.kind==="stylesheet")&&specifier===specifierOf(review.name)&&report(match.index+1,review);if(handledSpecifiers.has(match.index)||!context||context.sideEffect)continue;const available=namedFor(specifier);if(context.kind==="binding"&&/\bexport\s+(?:type\s+)?\*\s*(?:as\s+[$A-Z_a-z][$\w]*\s*)?from\s*$/.test(before))for(const review of available)report(match.index+1,review,"This re-export includes ");else opaque(match.index+1,specifier,available)}for(const match of text.matchAll(/@import\s+url\(\s*(@aceshooting\/lyra-ui\/[^\s)'";]+)\s*\)/g))if(!(inComment(match.index)||!inCss(match.index)))for(const review of reviews)review.kind==="stylesheet"&&match[1]===specifierOf(review.name)&&report(match.index+match[0].indexOf(match[1]),review);for(const review of reviews)if(review.kind==="window-event")for(const match of text.matchAll(new RegExp(`(['"\\x60])(${regexEscape(review.name)})\\1`,"g"))){if(inComment(match.index))continue;const before=text.slice(Math.max(0,match.index-200),match.index);(LISTENER_CALL_BEFORE.test(before)||/\bnew\s+(?:CustomEvent|Event)\s*(?:<[^>\n]+>)?\(\s*$/.test(before))&&stringStarts.has(match.index)&&report(match.index+1,review)}else if(review.kind==="root-attribute"){const attributeOffsets=new Set;for(const token of openingTokens)for(const attribute of parseTagAttributes(text,token))attribute.rawName.replace(/^\[attr\.(.+)\]$/,"$1").replace(/^(?:v-bind:|bind:|[?:])/,"")===review.name&&!inString(attribute.nameStart)&&attributeOffsets.add(attribute.nameStart+attribute.rawName.indexOf(review.name));for(const match of text.matchAll(new RegExp(`(?<![\\w-])${regexEscape(review.name)}(?![\\w-])`,"g"))){if(inComment(match.index))continue;const offset=match.index,before=text.slice(Math.max(0,offset-180),offset),after=text.slice(offset+review.name.length),literalApi=/\b(?:getAttribute|setAttribute|hasAttribute|removeAttribute|toggleAttribute)\s*\(\s*['"`]$/.test(before),selector=/\[\s*$/.test(before)&&/^\s*(?:[~|^$*]?=|\])/.test(after),markup=attributeOffsets.has(offset);(literalApi&&stringStarts.has(offset-1)||selector&&(inCss(offset)||inString(offset))||markup)&&report(offset,review)}const datasetName=review.name.slice(5).replace(/-([a-z])/g,(_match,letter)=>letter.toUpperCase()),dataset=new RegExp(`\\bdataset\\s*(?:\\.\\s*${regexEscape(datasetName)}\\b|\\[\\s*(['"])${regexEscape(datasetName)}\\1\\s*\\])`,"g");for(const match of text.matchAll(dataset))!inComment(match.index)&&!inLiteral(match.index)&&report(match.index,review)}}function migrateRenameText(original,contract,options){const file=options.file??"<memory>",origin=options.origin,profile=contract.renameProfiles.get(origin);invariant(profile,`unknown migration origin ${String(origin)}`);const result={content:original,changes:[],warnings:[],acknowledged:0,usage:{webawesome:{automatic:0,manual:0},shoelace:{automatic:0,manual:0}},blockedMappings:new Set,blockedEcosystems:new Set,bareImportEcosystems:new Set,blockedLocalMigrations:new Set,constructedEvents:new Set};if(profile.isEmpty)return result;const starts=lineStarts(original),ignoredRanges=renameIgnoredRanges(original,file),inComment=rangeTester(ignoredRanges),openingTokens=scanAllOpeningTags(original,ignoredRanges,inComment),acknowledgements=scanReviewAcknowledgements(original,ignoredRanges,starts,openingTokens),litTemplates=LIT_TEMPLATE_FILE.test(file)?rangeTester(taggedTemplateRanges(original,["html","svg"],inComment)):()=>!1,attributeSemantics=offset=>ATTRIBUTE_SEMANTICS_FILE.test(file)||litTemplates(offset),release=`Lyra ${profile.toMajor}`,removal=`${profile.aliasRemovalMajor}.0.0`;for(const match of original.matchAll(EVENT_CONSTRUCTION))!inComment(match.index)&&profile.renamesNamed("event",match[2]).length&&result.constructedEvents.add(match[2]);const constructedEvents=new Set([...options.constructedEvents??[],...result.constructedEvents]),listenerIdentity=(quote,name)=>{const receiver=LISTENER_RECEIVER_BEFORE.exec(original.slice(Math.max(0,quote-200),quote))?.[1],handler=LISTENER_HANDLER_AFTER.exec(original.slice(quote+name.length+2,quote+name.length+202))?.[1];return receiver&&handler?`${receiver.replace(/\s+/g,"")}\0${handler.replace(/\s+/g,"")}`:null},listenedNames=new Map;if(profile.data.renames.some(entry=>entry.kind==="event"))for(const match of original.matchAll(/(['"`])(lr-[a-z0-9]+(?:-[a-z0-9]+)*)\1/g)){if(inComment(match.index)||!LISTENER_CALL_BEFORE.test(original.slice(Math.max(0,match.index-120),match.index)))continue;const identity=listenerIdentity(match.index,match[2]);identity&&listenedNames.set(identity,new Set([...listenedNames.get(identity)??[],match[2]]))}const sameHandlerListens=(quote,name)=>{if(!listenedNames.size)return!1;const names=listenedNames.get(listenerIdentity(quote,name));return!!names&&profile.renamesNamed("event",name).some(entry=>names.has(entry.to))},edits=[],seenWarnings=new Set,handledStrings=new Set,handledMembers=new Set,unique=values=>[...new Set(values)],list=values=>unique(values).join(", "),soleTag=entries=>unique(entries.map(entry=>entry.tag)).length===1?entries[0].tag:null,hint=(code,member)=>`After review, mark the site with a "lyra-migrate-reviewed: ${code}:${ackName(member)}" comment.`,rewrite=(start,end,replacement,{tag,member,action,target,message})=>{edits.push({start,end,replacement}),result.changes.push(reportEntry({textStarts:starts,file,offset:start,origin,upstreamTag:tag,upstreamMember:member,action,target,message}))},warn=(offset,{tag=null,member,code,target=null,message})=>{const key=`${offset}:${code}:${member}`;if(!seenWarnings.has(key)){if(seenWarnings.add(key),acknowledgements.consume(locationAt(starts,offset).line,code,member)){result.acknowledged+=1;return}result.warnings.push(reportEntry({textStarts:starts,file,offset,origin,upstreamTag:tag,upstreamMember:member,action:"manual-review",target,warningCode:code,message:`${message} ${hint(code,member)}`}))}},reviewMessage=review=>`${review.kind==="component"?`<${review.tag}>`:review.kind==="slot"&&review.name===""?`Content in the default slot of ${review.tag}`:`The ${review.tag} ${review.kind} ${review.name}`} ${review.removedIn?`was removed in ${review.removedIn}`:`is deprecated and scheduled for removal in ${review.removalNotBefore}`}; migrate to ${review.replacement} by hand.`,reportReview=(offset,review)=>warn(offset,{tag:review.tag,member:review.name,code:DEPRECATED_MEMBER_REVIEW,target:review.replacement,message:reviewMessage(review)}),reportPropertyChange=(offset,entry)=>warn(offset,{tag:entry.tag,member:entry.property,code:"PROPERTY_CHANGE_REVIEW",target:entry.property,message:`Review ${entry.tag}.${entry.property} for ${release}: ${entry.summary}`});reportModuleReviews(original,file,profile.data.moduleReviews,openingTokens,inComment,warn);const unownedReviews=(kind,name,offset,owner)=>{const own=owner?profile.reviewFor(owner,kind,name):null;if(own)reportReview(offset,own);else if(!profile.exposes(kind,name,owner))for(const review of profile.reviewsNamed(kind,name))reportReview(offset,review)},label=kind=>kind==="css-property"?"custom property":kind,detailNote=name=>{const details=profile.detailsNamed(name);return details.length?` Its ${release} detail also changed: ${details.map(entry=>`${entry.tag}: ${entry.summary}`).join(" ")}`:""},unownedMove=({kind,name,start,context,rewritable,action})=>{const renames=profile.renamesNamed(kind,name),targets=unique(renames.map(entry=>entry.to)),constructed=kind==="event"&&constructedEvents.has(name);if(rewritable&&!constructed&&profile.isGlobal(kind,name))return rewrite(start,start+name.length,targets[0],{tag:soleTag(renames),member:name,action,target:targets[0],message:`Rename ${name} to ${targets[0]}; every component exposing it renamed it, and no other component uses ${targets[0]}.`}),targets[0];const sourceKeepers=unique(targets.flatMap(to=>profile.sourceKeepers(kind,name,to))).filter(tag=>!renames.some(entry=>entry.tag===tag)),targetKeepers=unique(targets.flatMap(to=>profile.targetKeepers(kind,name,to))),shared=targets.length===1&&!sourceKeepers.length&&targetKeepers.length>0&&!renames.some(entry=>entry.polarity);return warn(start,{tag:soleTag(renames),member:name,code:shared?RENAME_TARGET_SHARED_REVIEW:RENAME_REVIEW,target:targets.join(" | "),message:(kind==="css-property"?"Custom properties inherit into nested components. ":context==="listener"?"This listener does not prove which element it listens to. ":"This string may name an event of any component. ")+`${list(renames.map(entry=>entry.tag))} renamed the ${label(kind)} ${name} to ${targets.join(" or ")}`+(sourceKeepers.length?`, while ${list(sourceKeepers)} still ${sourceKeepers.length===1?"uses":"use"} ${name}`:"")+(targetKeepers.length?`; ${list(targetKeepers)} already ${targetKeepers.length===1?"uses":"use"} ${targets.join(" and ")}, which this site would start reaching`:"")+(constructed?`; the scanned code also dispatches ${name} itself, so its listeners and dispatches must move together`:"")+`. Update it by hand for the components it is meant for; ${name} keeps working until ${removal}.`+(kind==="event"?detailNote(targets[0]):"")}),null},eventMove=(name,owner,rewritable=!0)=>{if(constructedEvents.has(name))return null;const own=owner?profile.renameFor(owner,"event",name):null;return own?profile.targetKeepers("event",name,own.to).length?null:own.to:!rewritable||!profile.renamesNamed("event",name).length||profile.exposes("event",name,owner)?null:profile.isGlobal("event",name)?profile.renamesNamed("event",name)[0].to:null},conflictMessage=(conflict,subject,to)=>conflict==="same-handler"?`${subject} already listens to ${to} with this same handler; renamed, the two identical registrations would collapse into one, so the handler would run once per activation instead of twice. Rename or remove this call, and its add or remove counterpart, together by hand.`:`${subject} already listens to ${to}; merge the two handlers by hand.`,eventSite=({name,start,owner,context,rewritable=!0,conflict=!1})=>{const retired=owner?profile.retiredEventFor(owner,name):null,retiredCandidates=retired?[retired]:profile.exposes("event",name,owner)?[]:profile.retiredEventsNamed(name);retiredCandidates.length&&warn(start,{tag:retired?owner:soleTag(retiredCandidates),member:name,code:"RETIRED_EVENT_REVIEW",target:unique(retiredCandidates.map(entry=>entry.replacement)).join(", "),message:`${name} is no longer dispatched by ${list(retiredCandidates.map(entry=>entry.tag))} in ${release}. `+retiredCandidates.map(entry=>`${entry.tag}: use ${entry.replacement}. ${entry.summary}`).join(" ")+" Review this listener and its matching removal together; keep unrelated events with the same spelling, check nested event targets, and merge any existing canonical listener without dropping or duplicating handler calls."});const own=owner?profile.renameFor(owner,"event",name):null;let current=name;if(own){const keepers=profile.targetKeepers("event",name,own.to);if(constructedEvents.has(name))warn(start,{tag:owner,member:name,code:RENAME_REVIEW,target:own.to,message:`Not renamed: the scanned code dispatches ${name} itself, so this listener would stop hearing it. Rename the dispatch and its listeners to ${own.to} together; ${name} keeps working until ${removal}.${detailNote(own.to)}`});else if(keepers.length)warn(start,{tag:owner,member:name,code:RENAME_TARGET_SHARED_REVIEW,target:own.to,message:`Not renamed: ${list(keepers)} also ${keepers.length===1?"dispatches":"dispatch"} ${own.to}, so this listener would start receiving their events when they are nested inside this ${owner}. Rename it by hand and ignore events whose target is not this ${owner}; ${name} keeps working until ${removal}.${detailNote(own.to)}`});else if(conflict)warn(start,{tag:owner,member:name,code:RENAME_CONFLICT_REVIEW,target:own.to,message:`${conflictMessage(conflict,`This ${owner}`,own.to)} ${name} keeps working until ${removal}.`});else{const narrowed=profile.sourceKeepers("event",name,own.to).filter(tag=>tag!==owner);rewrite(start,start+name.length,own.to,{tag:owner,member:name,action:"rewrite-event",target:own.to,message:`Rename the ${owner} event ${name} to ${own.to}.`+(narrowed.length?` ${list(narrowed)} still ${narrowed.length===1?"dispatches":"dispatch"} ${name}; this listener stops hearing theirs from nested elements.`:"")}),current=own.to}}else profile.renamesNamed("event",name).length&&!profile.exposes("event",name,owner)&&(conflict?warn(start,{tag:soleTag(profile.renamesNamed("event",name)),member:name,code:RENAME_CONFLICT_REVIEW,target:profile.renamesNamed("event",name)[0].to,message:conflictMessage(conflict,conflict==="same-handler"?"This receiver":"This element",profile.renamesNamed("event",name)[0].to)}):current=unownedMove({kind:"event",name,start,context,rewritable,action:"rewrite-event"})??name);const details=profile.detailsNamed(current),ownDetail=owner?profile.detailFor(owner,current):null;if(ownDetail||details.length&&!profile.exposes("event",current,owner)){const affected=ownDetail?[ownDetail]:details;warn(start,{tag:ownDetail?owner:soleTag(affected),member:current,code:DETAIL_SHAPE_REVIEW,target:current,message:`The ${current} event detail changed in ${release} and cannot be aliased: `+affected.map(entry=>`${entry.tag}: ${entry.summary}`).join(" ")+` Update this handler if it reads the detail of ${ownDetail?`the ${owner} event`:"one of these components"}.`})}const gained=current===name?profile.gainedOwners("event",name):[];gained.length&&!profile.exposes("event",name,owner)&&warn(start,{tag:soleTag(gained.map(tag=>({tag}))),member:name,code:NAME_GAINED_OWNER_REVIEW,target:name,message:`In ${release}, ${list(gained)} also ${gained.length===1?"dispatches":"dispatch"} ${name}. This ${context} does not prove which element it listens to, so it may start receiving those events; ignore them by event target if it should not.`}),unownedReviews("event",name,start,owner)},memberSite=({owner,kind,name,start,forms,dynamic,removal:removalRange})=>{const propertyChange=forms.filter(([candidateKind])=>candidateKind==="property").map(([,candidate])=>profile.propertyChangeFor(owner,candidate)).find(Boolean);propertyChange&&reportPropertyChange(start,propertyChange);const review=forms.map(([candidateKind,candidate])=>profile.reviewFor(owner,candidateKind,candidate)).find(Boolean);review&&reportReview(start,review);const rule=forms.map(([candidateKind,candidate])=>profile.renameFor(owner,candidateKind,candidate)).find(Boolean);if(!rule)return null;if(rule.polarity==="inverted"&&(dynamic||!removalRange)){const target=kind==="binding"?memberStyle(name,rule.to):rule.to;return warn(start,{tag:owner,member:rule.from,code:POLARITY_REVIEW,target:rule.to,message:`${target} is the inverse of ${name} on ${owner}, and this ${kind==="binding"?"binding":kind==="property"?"property access":"attribute binding"} is not a static attribute. Bind the negated value to ${target} by hand; ${name} keeps working until ${removal}.`}),null}return rule},renamedMembers=owner=>unique([...profile.data.renames.filter(entry=>entry.tag===owner&&["attribute","property","event"].includes(entry.kind)).map(entry=>entry.from),...profile.data.reviews.filter(entry=>entry.tag===owner&&["attribute","property","event"].includes(entry.kind)).map(entry=>entry.name),...profile.data.retiredEvents.filter(entry=>entry.tag===owner).map(entry=>entry.event),...profile.data.propertyChanges.filter(entry=>entry.tag===owner).map(entry=>entry.property)]),defaultTokens=[];for(const token of openingTokens){const owner=token.tag.toLowerCase(),attributes=parseTagAttributes(original,token),lyraOwner=profile.tags.has(owner),bound=lyraOwner?boundMemberNames(attributes):null,finalNames=new Map,planned=attributes.map(attribute=>{const event=markupEventName(attribute.rawName);let finalRaw=attribute.rawName;if(event&&/^lr-/.test(event.name)){const to=eventMove(event.name,owner);to&&(finalRaw=attribute.rawName.slice(0,event.offset)+to+attribute.rawName.slice(event.offset+event.name.length))}else if(lyraOwner){const binding=markupMemberBinding(attribute),rule=binding&&!binding.form.startsWith("member")&&profile.renameFor(owner,binding.form,binding.name);rule&&!rule.polarity&&(finalRaw=attribute.rawName.slice(0,binding.offset)+rule.to+attribute.rawName.slice(binding.offset+binding.name.length))}const key=finalRaw.toLowerCase();return finalNames.set(key,(finalNames.get(key)??0)+1),{attribute,event,key,renamed:finalRaw!==attribute.rawName}});if(lyraOwner&&renamedMembers(owner).length)for(const group of openingTagBraceGroups(original,token)){const shorthand=/^[A-Za-z_$][\w$]*$/.exec(group.body)?.[0];if(!shorthand){warn(group.start,{tag:owner,member:owner,code:"DYNAMIC_VALUE_REVIEW",target:null,message:`This spread on ${owner} may carry renamed or deprecated members (${list(renamedMembers(owner))}); rename them where the object is built.`});continue}const forms=[["property",camelCase(shorthand)],["attribute",kebabCase(shorthand)]],review=forms.map(([kind,name])=>profile.reviewFor(owner,kind,name)).find(Boolean);review&&reportReview(group.start+1,review);const propertyChange=profile.propertyChangeFor(owner,camelCase(shorthand));propertyChange&&reportPropertyChange(group.start+1,propertyChange);const rule=forms.map(([kind,name])=>profile.renameFor(owner,kind,name)).find(Boolean);rule&&warn(group.start+1,{tag:owner,member:rule.from,code:rule.polarity?POLARITY_REVIEW:RENAME_REVIEW,target:rule.to,message:`The shorthand {${shorthand}} binds the deprecated ${owner} ${rule.kind} ${rule.from}; write ${memberStyle(shorthand,rule.to)}={${rule.polarity?`!${shorthand}`:shorthand}} by hand.`})}for(const{attribute,event,key,renamed}of planned){if(event){/^lr-/.test(event.name)&&eventSite({name:event.name,start:attribute.nameStart+event.offset,owner,context:"listener",conflict:renamed&&finalNames.get(key)>1});continue}if(!lyraOwner)continue;if(/^v-(?:bind|on)$/.test(attribute.rawName)||attribute.rawName.startsWith("${")&&!/^\$\{\s*ref\s*\(/.test(attribute.rawName)){renamedMembers(owner).length&&warn(attribute.nameStart,{tag:owner,member:owner,code:"DYNAMIC_VALUE_REVIEW",target:null,message:`This object binding on ${owner} may carry renamed or deprecated members (${list(renamedMembers(owner))}); rename them where the object is built.`});continue}if(attribute.rawName==="exportparts"){if(attribute.valueKind!=="literal"||/\$\{|\{\{/.test(attribute.value??""))continue;for(const match of(attribute.value??"").matchAll(/([^,\s:]+)(\s*:\s*[^,\s]+)?/g)){const inner=match[1],start2=attribute.valueStart+match.index,review=profile.reviewFor(owner,"part",inner);review&&reportReview(start2,review);const rule2=profile.renameFor(owner,"part",inner);rule2&&rewrite(start2,start2+inner.length,match[2]?rule2.to:`${rule2.to}:${inner}`,{tag:owner,member:inner,action:"rewrite-part",target:rule2.to,message:match[2]?`Forward the renamed ${owner} part ${rule2.to} under the same exported name.`:`Forward the renamed ${owner} part ${rule2.to} under its previous exported name ${inner}.`})}continue}const binding=markupMemberBinding(attribute);if(!binding)continue;const forms=binding.form==="member"?[["property",camelCase(binding.name)],["attribute",kebabCase(binding.name)]]:[[binding.form,binding.name]],start=attribute.nameStart+binding.offset,staticAttribute=binding.form==="attribute"&&binding.offset===0&&!binding.dynamic,rule=memberSite({owner,kind:binding.form==="member"?"binding":binding.form,name:binding.name,start,forms,dynamic:binding.dynamic,removal:staticAttribute});if(!rule)continue;if(bound.has(kebabCase(rule.to))||renamed&&finalNames.get(key)>1){warn(start,{tag:owner,member:rule.from,code:RENAME_CONFLICT_REVIEW,target:rule.to,message:`This ${owner} already binds ${rule.to}; remove the deprecated ${rule.from} binding by hand.`});continue}if(rule.polarity==="inverted"){if(!attributeSemantics(token.start)){warn(start,{tag:owner,member:rule.from,code:POLARITY_REVIEW,target:rule.to,message:`${rule.to} is the inverse of ${rule.from}. In this template syntax a static value may be assigned to the ${camelCase(rule.from)} property as a string, where "false" is truthy, so set ${rule.to} by hand.`});continue}if(profile.preservedDefaultsFor(owner).some(entry=>entry.attribute===rule.to&&entry.value===!0)){warn(start,{tag:owner,member:rule.from,code:POLARITY_REVIEW,target:rule.to,message:`${rule.from} has a preserved absent default, so removing an explicit attribute would let a later run insert ${rule.to}. Review this value using the old converter (a presence boolean treats even "false" as true), then migrate it by hand after the default migration is complete.`});continue}const value=attribute.value??"",end=attributeEnd(original,attribute);if(value===""||value==="true"||value===rule.from){let removeFrom=attribute.nameStart;for(;removeFrom>0&&/\s/.test(original[removeFrom-1]);)removeFrom-=1;rewrite(removeFrom,end,"",{tag:owner,member:rule.from,action:"remove-attribute",target:rule.to,message:`Remove ${rule.from}: it restated the ${owner} default, and ${rule.to} is off by default.`})}else value==="false"?rewrite(attribute.nameStart,end,rule.to,{tag:owner,member:rule.from,action:"rewrite-attribute",target:rule.to,message:`Replace ${rule.from}="false" with the presence of ${rule.to}.`}):warn(start,{tag:owner,member:rule.from,code:POLARITY_REVIEW,target:rule.to,message:`${rule.from}="${attribute.value}" is not a boolean literal; set ${rule.to} to its inverse by hand.`});continue}const target=binding.form==="member"?memberStyle(binding.name,rule.to):rule.to;rewrite(start,start+binding.name.length,target,{tag:owner,member:rule.from,action:`rewrite-${rule.kind}`,target:rule.to,message:`Rename the ${owner} ${rule.kind} ${rule.from} to ${rule.to}.`})}lyraOwner&&profile.defaultsFor(owner).length&&defaultTokens.push({token,owner,bound})}const slotTags=[...profile.tags].filter(tag=>profile.slotContentFor(tag).length||[...profile.data.renames,...profile.data.reviews].some(entry=>entry.tag===tag&&entry.kind==="slot"));for(const tag of slotTags)for(const pair of elementPairs(original,tag,inComment))for(const child of directChildTokens(original,pair.opening.end+1,pair.closingStart,inComment)){let slot="",start=child.nameStart;for(const attribute of parseTagAttributes(original,child))if(attribute.rawName==="slot")slot=attribute.valueKind==="literal"?attribute.value??"":null,start=attribute.valueStart??attribute.nameStart;else if(attribute.rawName==="v-slot")slot="";else{const prefix=["v-slot:","#"].find(candidate=>attribute.rawName.startsWith(candidate));prefix&&(slot=attribute.rawName.slice(prefix.length),start=attribute.nameStart+prefix.length,slot==="default"&&(slot=""))}if(slot===null)continue;const review=profile.reviewFor(tag,"slot",slot);review&&reportReview(start,review);const rule=slot===""?null:profile.renameFor(tag,"slot",slot);if(rule&&rewrite(start,start+slot.length,rule.to,{tag,member:slot,action:"rewrite-slot",target:rule.to,message:`Rename the ${tag} slot ${slot} to ${rule.to}.`}),child.tag!=="template")for(const content of profile.slotContentFor(tag)){if(content.slot!==slot)continue;const listed=(content.report??content.allow).includes(child.tag);(content.report?!listed:listed)||warn(child.nameStart,{tag,member:child.tag,code:DEPRECATED_CONTENT_REVIEW,target:null,message:`<${child.tag}> in ${slot===""?"the default slot":`the ${slot} slot`} of ${tag}: ${content.summary}`})}}for(const match of original.matchAll(/::part\(([^)]*)\)/g)){if(inComment(match.index))continue;const owner=compoundTypeBefore(original,match.index),listStart=match.index+7;for(const nameMatch of match[1].matchAll(/\S+/g)){const name=nameMatch[0],start=listStart+nameMatch.index,own=owner?profile.renameFor(owner,"part",name):null;if(own)rewrite(start,start+name.length,own.to,{tag:owner,member:name,action:"rewrite-part",target:own.to,message:`Rename the ${owner} part ${name} to ${own.to}.`});else if(profile.renamesNamed("part",name).length&&!profile.exposes("part",name,owner)){const renames=profile.renamesNamed("part",name),keepers=profile.exposers("part",name).filter(tag=>!renames.some(entry=>entry.tag===tag));warn(start,{tag:soleTag(renames),member:name,code:RENAME_REVIEW,target:list(renames.map(entry=>entry.to)),message:`This ::part() selector does not name the component that owns the part. ${list(renames.map(entry=>entry.tag))} renamed the part ${name} to ${list(renames.map(entry=>entry.to))}`+(keepers.length?`, while ${list(keepers)} still ${keepers.length===1?"exposes":"expose"} ${name}`:"")+". Rename it where the selector reaches a renamed component directly; a part forwarded through exportparts keeps its exported name."})}else if(profile.gainedOwners("part",name).length&&!owner?.startsWith("lr-")){const gained=profile.gainedOwners("part",name);warn(start,{tag:soleTag(gained.map(tag=>({tag}))),member:name,code:NAME_GAINED_OWNER_REVIEW,target:name,message:`In ${release}, ${list(gained)} also ${gained.length===1?"exposes":"expose"} the part ${name}, so this selector may start matching it.`})}unownedReviews("part",name,start,owner)}}const cssNames=new Set([...profile.data.renames.filter(entry=>entry.kind==="css-property").flatMap(entry=>[entry.from,entry.to]),...profile.data.reviews.filter(entry=>entry.kind==="css-property").map(entry=>entry.name)]);if(cssNames.size)for(const match of original.matchAll(/(?<![\w-])--[A-Za-z0-9_-]+/g)){const name=match[0];if(!cssNames.has(name)||inComment(match.index))continue;profile.renamesNamed("css-property",name).length&&unownedMove({kind:"css-property",name,start:match.index,context:"custom property",rewritable:!0,action:"rewrite-css-property"});const declaration=/^\s*['"]?\s*:/.test(original.slice(match.index+name.length,match.index+name.length+16))||/setProperty\s*\(\s*['"`]$/.test(original.slice(Math.max(0,match.index-24),match.index)),gained=profile.gainedOwners("css-property",name);declaration&&gained.length&&warn(match.index,{tag:soleTag(gained.map(tag=>({tag}))),member:name,code:NAME_GAINED_OWNER_REVIEW,target:name,message:`In ${release}, ${list(gained)} also ${gained.length===1?"reads":"read"} ${name}, so this declaration now reaches them when they are nested below it.`});for(const review of profile.reviewsNamed("css-property",name))reportReview(match.index,review)}for(const match of original.matchAll(/\[\s*([A-Za-z_][\w-]*)(?=\s*(?:[~|^$*]?=|\]))/g)){if(inComment(match.index))continue;const owner=compoundTypeBefore(original,match.index);if(!owner||!profile.tags.has(owner))continue;const name=match[1],start=match.index+match[0].length-name.length,review=profile.reviewFor(owner,"attribute",name);review&&reportReview(start,review);const rule=profile.renameFor(owner,"attribute",name);if(rule){if(rule.polarity==="inverted"){warn(start,{tag:owner,member:name,code:POLARITY_REVIEW,target:rule.to,message:`${rule.to} is the inverse of ${name}; rewrite this ${owner} selector to test the opposite state (for example :not([${rule.to}])).`});continue}if(!rule.reflects){warn(start,{tag:owner,member:name,code:RENAME_REVIEW,target:rule.to,message:`${owner} does not reflect ${rule.to}, so [${rule.to}] matches only where markup sets it. Rename this selector once every place that sets ${name} (markup, setAttribute) sets ${rule.to} instead.`});continue}rewrite(start,start+name.length,rule.to,{tag:owner,member:name,action:"rewrite-attribute",target:rule.to,message:`Rename the ${owner} attribute selector [${name}] to [${rule.to}]; ${owner} reflects ${rule.to}.`})}}for(const match of original.matchAll(/:state\(\s*([a-z][a-z0-9-]*)\s*\)/g)){if(inComment(match.index))continue;const owner=compoundTypeBefore(original,match.index),review=owner?profile.reviewFor(owner,"css-state",match[1]):null;review&&reportReview(match.index+match[0].indexOf(match[1]),review)}for(const review of profile.data.reviews.filter(entry=>entry.kind==="component"))for(const match of original.matchAll(new RegExp(`(?<![\\w-])${review.tag}(?![\\w-])`,"g")))inComment(match.index)||original.slice(match.index-2,match.index)==="</"||reportReview(match.index,review);for(const match of original.matchAll(ANCHORED_MEMBER)){if(inComment(match.index))continue;const owner=match[2],member=match[3],start=match.index+match[0].length-member.length,after=start+member.length;if(handledMembers.add(start),LISTENER_METHODS.has(member)||ATTRIBUTE_METHODS.has(member)){const call=/^\s*\(\s*(['"`])([^'"`\n]+)\1/.exec(original.slice(after,after+200));if(!call)continue;const valueStart=after+call[0].length-1-call[2].length;if(handledStrings.add(valueStart-1),LISTENER_METHODS.has(member)){eventSite({name:call[2],start:valueStart,owner,context:"listener",conflict:sameHandlerListens(valueStart-1,call[2])&&"same-handler"});continue}const rule2=memberSite({owner,kind:"attribute",name:call[2],start:valueStart,forms:[["attribute",call[2]]],dynamic:!0});rule2&&rewrite(valueStart,valueStart+call[2].length,rule2.to,{tag:owner,member:rule2.from,action:"rewrite-attribute",target:rule2.to,message:`Rename the ${owner} attribute ${rule2.from} to ${rule2.to}.`});continue}if(!profile.tags.has(owner))continue;if(/^\s*\(/.test(original.slice(after,after+64))){const review=profile.reviewFor(owner,"method",member);review&&reportReview(start,review);continue}const rule=memberSite({owner,kind:"property",name:member,start,forms:[["property",member]],dynamic:!0});rule&&rewrite(start,after,rule.to,{tag:owner,member:rule.from,action:"rewrite-property",target:rule.to,message:`Rename the ${owner} property ${rule.from} to ${rule.to}.`})}const eventNameMatters=name=>profile.renamesNamed("event",name).length||profile.detailsNamed(name).length||profile.retiredEventsNamed(name).length||profile.reviewsNamed("event",name).length||profile.gainedOwners("event",name).length;for(const match of original.matchAll(/(['"`])(?:(window|document|body):)?(lr-[a-z0-9]+(?:-[a-z0-9]+)*)\1/g)){const name=match[3];if(handledStrings.has(match.index)||inComment(match.index)||!eventNameMatters(name))continue;const before=original.slice(Math.max(0,match.index-120),match.index);if(!match[2]&&TAG_API_CALL_BEFORE.test(before))continue;const listener=LISTENER_CALL_BEFORE.test(before);eventSite({name,start:match.index+1+(match[2]?match[2].length+1:0),owner:null,context:listener?"listener":"event name string",rewritable:listener,conflict:listener&&!match[2]&&sameHandlerListens(match.index,name)&&"same-handler"})}const mentioned=mentionedTags(original,profile.tags,inComment),accessRules=new Map,addAccess=(name,candidate)=>accessRules.set(name,[...accessRules.get(name)??[],candidate]);for(const entry of profile.data.renames)entry.kind==="property"&&mentioned.has(entry.tag)&&addAccess(entry.from,{tag:entry.tag,rule:entry,call:!1});for(const entry of profile.data.reviews)(entry.kind==="property"||entry.kind==="method")&&mentioned.has(entry.tag)&&addAccess(entry.name,{tag:entry.tag,review:entry,call:entry.kind==="method"});for(const entry of profile.data.propertyChanges)mentioned.has(entry.tag)&&addAccess(entry.property,{tag:entry.tag,propertyChange:entry,call:!1});if(accessRules.size){const inString=rangeTester(mergeRanges(quotedStringRanges(original,inComment))),inStylesheet=rangeTester(stylesheetRanges(original,file,inComment)),inToken=rangeTester(mergeRanges(openingTokens.map(token=>[token.start+1,token.end])));for(const match of original.matchAll(/(?<=[\w$)\]])\s*(?:\?\.|!?\.)\s*([A-Za-z_$][\w$]*)/g)){const name=match[1],candidates=accessRules.get(name);if(!candidates)continue;const start=match.index+match[0].length-name.length;if(handledMembers.has(start)||inComment(start)||inString(start)||inStylesheet(start)||inToken(start))continue;const call=/^\s*\(/.test(original.slice(start+name.length,start+name.length+64));for(const candidate of candidates){if(candidate.call!==call)continue;if(candidate.propertyChange){reportPropertyChange(start,candidate.propertyChange);continue}if(candidate.review){reportReview(start,candidate.review);continue}const{rule}=candidate;warn(start,{tag:rule.tag,member:name,code:rule.polarity?POLARITY_REVIEW:RENAME_REVIEW,target:rule.to,message:`If this reads or writes an ${rule.tag}, ${rule.polarity?`use the inverse property .${rule.to}`:`rename .${name} to .${rule.to}`}; .${name} keeps working until ${removal}.`})}}}for(const match of original.matchAll(/\.\s*(?:set|get|has|remove|toggle)Attribute\s*\(\s*(['"`])([a-z][a-z0-9-]*)\1/g)){const name=match[2],start=match.index+match[0].length-1-name.length;if(!(handledStrings.has(start-1)||inComment(match.index)))for(const tag of mentioned){const review=profile.reviewFor(tag,"attribute",name);review&&reportReview(start,review);const rule=profile.renameFor(tag,"attribute",name);rule&&warn(start,{tag,member:name,code:rule.polarity?POLARITY_REVIEW:RENAME_REVIEW,target:rule.to,message:`If this element is an ${tag}, ${rule.polarity?`use the inverse attribute ${rule.to}`:`rename ${name} to ${rule.to}`}; ${name} keeps working until ${removal}.`})}}if(profile.data.defaults.length){const defaultProfiles=new Map([...new Set(profile.data.defaults.map(entry=>entry.tag))].map(tag=>[tag,{origin,tag,defaults:profile.defaultsFor(tag).map(entry=>({member:entry.attribute,value:entry.value}))}])),hazards=scanLocalMigrationHazards(original,defaultProfiles,ignoredRanges,defaultTokens.map(({token})=>({...token,tag:token.tag.toLowerCase()}))),blocked=new Set([...options.blockedLocalMigrations??[],...hazards.blocked]);result.blockedLocalMigrations=hazards.blocked;for(const use of hazards.uses)warn(use.offset,{tag:use.tag,member:use.tag,code:use.warningCode,target:use.tag,message:use.message});for(const{token,owner,bound}of defaultTokens){const missing=profile.defaultsFor(owner).filter(entry=>!profile.defaultAliasesFor(owner,entry.attribute).some(name=>bound.has(name)));if(!missing.length)continue;if(blocked.has(localMigrationKey(origin,owner))){warn(token.nameStart,{tag:owner,member:owner,code:"MAPPING_REVIEW_BLOCKED",target:owner,message:`${owner} did not receive its Lyra ${profile.fromMajor} defaults (${missing.map(entry=>`${entry.attribute}=${String(entry.value)}`).join(", ")}) because the scanned target set accesses it through a DOM alias or an opaque attribute spread. Add them by hand where the previous default matters.`});continue}const insertions=[];for(const entry of missing)insertions.push(serializeLocalDefault({member:entry.attribute,value:entry.value})),result.changes.push(reportEntry({textStarts:starts,file,offset:token.end,origin,upstreamTag:owner,upstreamMember:entry.attribute,action:"insert-default",target:`${entry.attribute}=${String(entry.value)}`,message:`Insert ${entry.attribute} to preserve the Lyra ${profile.fromMajor} default.`}));if(insertions.length){const insertionOffset=original[token.end-1]==="/"?token.end-1:token.end,replacement=/\s/.test(original[insertionOffset-1])?`${insertions.join(" ")} `:` ${insertions.join(" ")}`;edits.push({start:insertionOffset,end:insertionOffset,replacement})}}}if(!profile.skipped.length)for(const{offset,entry}of acknowledgements.unused()){const key=`${offset}:${UNUSED_ACKNOWLEDGEMENT}:${entry.token}`;seenWarnings.has(key)||(seenWarnings.add(key),result.warnings.push(reportEntry({textStarts:starts,file,offset,origin,upstreamTag:null,upstreamMember:entry.token,action:"manual-review",target:null,warningCode:UNUSED_ACKNOWLEDGEMENT,message:entry.code?`The acknowledgement ${entry.token} matches no report on the lines it covers; remove it.`:`The acknowledgement ${entry.token} is not of the form CODE:name, for example DETAIL_SHAPE_REVIEW:lr-close.`})))}return result.content=finalizeEdits(original,edits),sortReportEntries(result.changes,"action"),sortReportEntries(result.warnings,"warningCode"),result}export{migrateRenameText};
4
+ `);)text[index]==="\\"&&(index+=1),index+=1;index+=1,quote!=="`"&&ranges.push([start,index])}return ranges}function stylesheetRanges(text,file,inComment){if(STYLESHEET_FILE.test(file))return[[0,text.length]];const ranges=[];for(const match of text.matchAll(/<style(?:\s[^>]*)?>[\s\S]*?<\/style\s*>/gi))ranges.push([match.index,match.index+match[0].length]);return mergeRanges([...ranges,...taggedTemplateRanges(text,["css"],inComment)])}function compoundTypeBefore(text,index){let start=index,brackets=0,parens=0;const limit=Math.max(0,index-SELECTOR_LOOKBEHIND);for(;start>limit;){const character=text[start-1];if(character==="]")brackets+=1;else if(character==="["){if(brackets===0)break;brackets-=1}else if(character===")")parens+=1;else if(character==="("){if(parens===0)break;parens-=1}else if(brackets===0&&parens===0&&/[\s,>+~{};'"`]/.test(character))break;start-=1}if(start===limit&&limit>0)return null;const compound=text.slice(start,index);return(/^:global\(\s*([A-Za-z][\w-]*)\s*\)$/.exec(compound)??/^([A-Za-z][\w-]*)/.exec(compound))?.[1].toLowerCase()??null}function mentionedTags(text,tags,inComment){const found=new Set;for(const tag of tags)for(const match of text.matchAll(new RegExp(`(?<![\\w-])${tag}(?![\\w-])`,"g")))if(!inComment(match.index)){found.add(tag);break}return found}function elementPairs(text,tag,inComment){const pairs=[],stack=[];for(const match of text.matchAll(new RegExp(`<(/)?${tag}(?=[\\s/>])`,"g"))){if(inComment(match.index))continue;const end=findTagEnd(text,match.index+match[0].length);if(!(end<0))if(match[1]){const opening=stack.pop();opening&&pairs.push({opening,closingStart:match.index})}else/\/\s*>$/.test(text.slice(match.index,end+1))||stack.push({start:match.index,end})}return pairs}function directChildTokens(text,start,end,inComment){const children=[],stack=[],pattern=/<(\/)?([A-Za-z][\w.:-]*)(?=[\s/>])/g;pattern.lastIndex=start;for(let match=pattern.exec(text);match&&match.index<end;match=pattern.exec(text)){if(inComment(match.index))continue;const tokenEnd=findTagEnd(text,match.index+match[0].length);if(tokenEnd<0||tokenEnd>=end)break;const name=match[2].toLowerCase();if(match[1]){const index=stack.lastIndexOf(name);index>=0&&(stack.length=index)}else{const nameStart=match.index+1;stack.length===0&&children.push({tag:name,start:match.index,nameStart,nameEnd:nameStart+match[2].length,end:tokenEnd}),!/\/\s*>$/.test(text.slice(match.index,tokenEnd+1))&&!VOID_HTML_TAGS.has(name)&&stack.push(name)}pattern.lastIndex=tokenEnd+1}return children}function openingTagBraceGroups(text,token){const groups=[];let index=token.nameEnd,quote=null;for(;index<token.end;){const character=text[index];if(quote){character==="\\"?index+=1:character===quote&&(quote=null),index+=1;continue}if(character==='"'||character==="'")quote=character;else if(character==="{"){const end=skipBalanced(text,index,"{","}");!/=\s*$/.test(text.slice(token.nameEnd,index))&&text[index-1]!=="$"&&groups.push({start:index,end,body:text.slice(index+1,end-1).trim()}),index=end;continue}index+=1}return groups}function markupEventName(rawName){if(/^\(lr-[a-z0-9-]+\)$/.test(rawName))return{name:rawName.slice(1,-1),offset:1};if(/^onlr-/.test(rawName))return{name:rawName.slice(2).replace(/Capture$/,""),offset:2};for(const[prefix,separator]of[["@","."],["v-on:","."],["on:","|"]])if(rawName.startsWith(prefix))return{name:rawName.slice(prefix.length).split(separator)[0],offset:prefix.length};return null}function markupMemberBinding(attribute){const raw=attribute.rawName;let match;if(raw.startsWith("?"))return{form:"attribute",offset:1,name:raw.slice(1),dynamic:!0};if(match=/^\[attr\.([^\]]+)\]$/.exec(raw))return{form:"attribute",offset:6,name:match[1],dynamic:!0};if(match=/^\[([A-Za-z_$][\w$]*)\]$/.exec(raw))return{form:"property",offset:1,name:match[1],dynamic:!0};if(raw.startsWith("."))return{form:"property",offset:1,name:raw.slice(1),dynamic:!0};if(raw.startsWith("bind:"))return{form:"property",offset:5,name:raw.slice(5).split("|")[0],dynamic:!0};for(const prefix of["v-bind:",":"])if(raw.startsWith(prefix))return{form:"member",offset:prefix.length,name:raw.slice(prefix.length).split(".")[0],dynamic:!0};if(!/^[A-Za-z][\w-]*$/.test(raw)||/^onlr-/.test(raw))return null;const dynamic=attribute.valueKind==="expression"||/\$\{|\{\{/.test(attribute.value??"");return{form:/[A-Z]/.test(raw)?"property":"attribute",offset:0,name:raw,dynamic}}function attributeEnd(text,attribute){if(attribute.valueEnd===null)return attribute.nameEnd;const quote=text[attribute.valueStart-1];return(quote==='"'||quote==="'")&&text[attribute.valueEnd]===quote?attribute.valueEnd+1:attribute.valueEnd}function boundMemberNames(attributes){const names=new Set;for(const attribute of attributes){const binding=markupMemberBinding(attribute);binding&&names.add(kebabCase(binding.name))}return names}function sortReportEntries(entries,codeKey){return entries.sort((left,right)=>left.line-right.line||left.column-right.column||String(left[codeKey]).localeCompare(String(right[codeKey])))}function reportModuleReviews(text,file,reviews,openingTokens,inComment,warn){if(!reviews.length)return;const packageName="@aceshooting/lyra-ui",specifierOf=subpath=>`${packageName}${subpath.slice(1)}`,named=reviews.filter(entry=>["function","type","constant","class"].includes(entry.kind)),report=(offset,review,context="")=>warn(offset,{member:review.name,code:"DEPRECATED_MODULE_REVIEW",target:review.replacement,message:`${context}${review.kind} ${review.module?`${review.module}#`:""}${review.name} ${review.removedIn?`was removed in ${review.removedIn}`:`is deprecated and scheduled for removal in ${review.removalNotBefore}`}; review ${review.replacement}.`}),namedFor=specifier=>named.filter(entry=>specifier===packageName||specifier===specifierOf(entry.module)),strings=quotedStringRanges(text,inComment),templates=templateLiteralRanges(text,inComment),stringStarts=new Set([...strings,...templates.filter(([start,end])=>!text.slice(start,end).includes("${"))].map(([start])=>start)),inString=rangeTester(strings),inLiteral=rangeTester(mergeRanges([...strings,...templates])),inCss=rangeTester(stylesheetRanges(text,file,inComment)),inMarkup=offset=>openingTokens.some(token=>offset>=token.nameEnd&&offset<token.end),opaque=(offset,specifier,entries)=>{entries.length&&warn(offset,{member:specifier,code:"MODULE_NAMESPACE_REVIEW",target:[...new Set(entries.map(entry=>entry.replacement))].join("; "),message:`Review the namespace or dynamic import of ${specifier} for deprecated exports ${entries.map(entry=>entry.name).join(", ")}. They cannot be safely renamed through aliases; the earliest removal is ${entries.map(entry=>entry.removalNotBefore).sort()[0]}.`})},bindings=/\b(?:import|export)\s+(?:type\s+)?(?:[$A-Z_a-z][$\w]*\s*,\s*)?\{(?<bindings>[^{};]*)\}\s*from\s*(?<quote>['"])(?<source>@aceshooting\/lyra-ui(?:\/[^'"\s]+)?)\k<quote>/g,handledSpecifiers=new Set;for(const match of text.matchAll(bindings)){if(inComment(match.index)||inLiteral(match.index))continue;const available=namedFor(match.groups.source),bodyStart=match.index+match[0].indexOf("{")+1;for(const binding of parseNamedModuleBindings(match.groups.bindings)){const review=available.find(entry=>entry.name===binding.imported);if(!review)continue;const member=[...match.groups.bindings.matchAll(new RegExp(`(?<![$\\w])${regexEscape(binding.imported)}(?![$\\w])`,"g"))].find(candidate=>!inComment(bodyStart+candidate.index));member&&report(bodyStart+member.index,review)}handledSpecifiers.add(match.index+match[0].lastIndexOf(match.groups.source)-1)}for(const match of text.matchAll(/(['"])(@aceshooting\/lyra-ui(?:\/[^'"\s]+)?)\1/g)){if(inComment(match.index))continue;const markup=inMarkup(match.index),css=inCss(match.index);if(!stringStarts.has(match.index)&&!markup&&!css)continue;const before=text.slice(Math.max(0,match.index-500),match.index),context=moduleSpecifierContext(text,match.index),cssImport=css&&/@import\s+(?:url\(\s*)?$/.test(before),resource=markup&&/\b(?:href|src)\s*=\s*$/.test(before);if(!context&&!cssImport&&!resource)continue;const specifier=match[2];for(const review of reviews)(review.kind==="entry-point"||review.kind==="stylesheet")&&specifier===specifierOf(review.name)&&report(match.index+1,review);if(handledSpecifiers.has(match.index)||!context||context.sideEffect)continue;const available=namedFor(specifier);if(context.kind==="binding"&&/\bexport\s+(?:type\s+)?\*\s*(?:as\s+[$A-Z_a-z][$\w]*\s*)?from\s*$/.test(before))for(const review of available)report(match.index+1,review,"This re-export includes ");else opaque(match.index+1,specifier,available)}for(const match of text.matchAll(/@import\s+url\(\s*(@aceshooting\/lyra-ui\/[^\s)'";]+)\s*\)/g))if(!(inComment(match.index)||!inCss(match.index)))for(const review of reviews)review.kind==="stylesheet"&&match[1]===specifierOf(review.name)&&report(match.index+match[0].indexOf(match[1]),review);for(const review of reviews)if(review.kind==="window-event")for(const match of text.matchAll(new RegExp(`(['"\\x60])(${regexEscape(review.name)})\\1`,"g"))){if(inComment(match.index))continue;const before=text.slice(Math.max(0,match.index-200),match.index);(LISTENER_CALL_BEFORE.test(before)||/\bnew\s+(?:CustomEvent|Event)\s*(?:<[^>\n]+>)?\(\s*$/.test(before))&&stringStarts.has(match.index)&&report(match.index+1,review)}else if(review.kind==="root-attribute"){const attributeOffsets=new Set;for(const token of openingTokens)for(const attribute of parseTagAttributes(text,token))attribute.rawName.replace(/^\[attr\.(.+)\]$/,"$1").replace(/^(?:v-bind:|bind:|[?:])/,"")===review.name&&!inString(attribute.nameStart)&&attributeOffsets.add(attribute.nameStart+attribute.rawName.indexOf(review.name));for(const match of text.matchAll(new RegExp(`(?<![\\w-])${regexEscape(review.name)}(?![\\w-])`,"g"))){if(inComment(match.index))continue;const offset=match.index,before=text.slice(Math.max(0,offset-180),offset),after=text.slice(offset+review.name.length),literalApi=/\b(?:getAttribute|setAttribute|hasAttribute|removeAttribute|toggleAttribute)\s*\(\s*['"`]$/.test(before),selector=/\[\s*$/.test(before)&&/^\s*(?:[~|^$*]?=|\])/.test(after),markup=attributeOffsets.has(offset);(literalApi&&stringStarts.has(offset-1)||selector&&(inCss(offset)||inString(offset))||markup)&&report(offset,review)}const datasetName=review.name.slice(5).replace(/-([a-z])/g,(_match,letter)=>letter.toUpperCase()),dataset=new RegExp(`\\bdataset\\s*(?:\\.\\s*${regexEscape(datasetName)}\\b|\\[\\s*(['"])${regexEscape(datasetName)}\\1\\s*\\])`,"g");for(const match of text.matchAll(dataset))!inComment(match.index)&&!inLiteral(match.index)&&report(match.index,review)}}function migrateRenameText(original,contract,options){const file=options.file??"<memory>",origin=options.origin,profile=contract.renameProfiles.get(origin);invariant(profile,`unknown migration origin ${String(origin)}`);const result={content:original,changes:[],warnings:[],acknowledged:0,usage:{webawesome:{automatic:0,manual:0},shoelace:{automatic:0,manual:0}},blockedMappings:new Set,blockedEcosystems:new Set,bareImportEcosystems:new Set,blockedLocalMigrations:new Set,constructedEvents:new Set};if(profile.isEmpty)return result;const starts=lineStarts(original),ignoredRanges=renameIgnoredRanges(original,file),inComment=rangeTester(ignoredRanges),openingTokens=scanAllOpeningTags(original,ignoredRanges,inComment),acknowledgements=scanReviewAcknowledgements(original,ignoredRanges,starts,openingTokens),litTemplates=LIT_TEMPLATE_FILE.test(file)?rangeTester(taggedTemplateRanges(original,["html","svg"],inComment)):()=>!1,inLiteral=rangeTester(mergeRanges([...quotedStringRanges(original,inComment),...templateLiteralRanges(original,inComment)])),inCss=rangeTester(stylesheetRanges(original,file,inComment)),attributeSemantics=offset=>ATTRIBUTE_SEMANTICS_FILE.test(file)||litTemplates(offset),release=`Lyra ${profile.toMajor}`,removal=`${profile.aliasRemovalMajor}.0.0`;for(const match of original.matchAll(EVENT_CONSTRUCTION))!inComment(match.index)&&profile.renamesNamed("event",match[2]).length&&result.constructedEvents.add(match[2]);const constructedEvents=new Set([...options.constructedEvents??[],...result.constructedEvents]),listenerIdentity=(quote,name)=>{const receiver=LISTENER_RECEIVER_BEFORE.exec(original.slice(Math.max(0,quote-200),quote))?.[1],handler=LISTENER_HANDLER_AFTER.exec(original.slice(quote+name.length+2,quote+name.length+202))?.[1];return receiver&&handler?`${receiver.replace(/\s+/g,"")}\0${handler.replace(/\s+/g,"")}`:null},listenedNames=new Map;if(profile.data.renames.some(entry=>entry.kind==="event"))for(const match of original.matchAll(/(['"`])(lr-[a-z0-9]+(?:-[a-z0-9]+)*)\1/g)){if(inComment(match.index)||!LISTENER_CALL_BEFORE.test(original.slice(Math.max(0,match.index-120),match.index)))continue;const identity=listenerIdentity(match.index,match[2]);identity&&listenedNames.set(identity,new Set([...listenedNames.get(identity)??[],match[2]]))}const sameHandlerListens=(quote,name)=>{if(!listenedNames.size)return!1;const names=listenedNames.get(listenerIdentity(quote,name));return!!names&&profile.renamesNamed("event",name).some(entry=>names.has(entry.to))},edits=[],seenWarnings=new Set,handledStrings=new Set,handledMembers=new Set,unique=values=>[...new Set(values)],list=values=>unique(values).join(", "),soleTag=entries=>unique(entries.map(entry=>entry.tag)).length===1?entries[0].tag:null,hint=(code,member)=>`After review, mark the site with a "lyra-migrate-reviewed: ${code}:${ackName(member)}" comment.`,rewrite=(start,end,replacement,{tag,member,action,target,message})=>{edits.push({start,end,replacement}),result.changes.push(reportEntry({textStarts:starts,file,offset:start,origin,upstreamTag:tag,upstreamMember:member,action,target,message}))},warn=(offset,{tag=null,member,code,target=null,message})=>{const key=`${offset}:${code}:${member}`;if(!seenWarnings.has(key)){if(seenWarnings.add(key),acknowledgements.consume(locationAt(starts,offset).line,code,member)){result.acknowledged+=1;return}result.warnings.push(reportEntry({textStarts:starts,file,offset,origin,upstreamTag:tag,upstreamMember:member,action:"manual-review",target,warningCode:code,message:`${message} ${hint(code,member)}`}))}},reviewMessage=review=>`${review.kind==="component"?`<${review.tag}>`:review.kind==="slot"&&review.name===""?`Content in the default slot of ${review.tag}`:`The ${review.tag} ${review.kind} ${review.name}`} ${review.removedIn?`was removed in ${review.removedIn}`:`is deprecated and scheduled for removal in ${review.removalNotBefore}`}; migrate to ${review.replacement} by hand.`,reportReview=(offset,review)=>warn(offset,{tag:review.tag,member:review.name,code:DEPRECATED_MEMBER_REVIEW,target:review.replacement,message:reviewMessage(review)}),reportPropertyChange=(offset,entry)=>warn(offset,{tag:entry.tag,member:entry.property,code:"PROPERTY_CHANGE_REVIEW",target:entry.property,message:`Review ${entry.tag}.${entry.property} for ${release}: ${entry.summary}`});reportModuleReviews(original,file,profile.data.moduleReviews,openingTokens,inComment,warn);const unownedReviews=(kind,name,offset,owner)=>{const own=owner?profile.reviewFor(owner,kind,name):null;if(own)reportReview(offset,own);else if(!profile.exposes(kind,name,owner))for(const review of profile.reviewsNamed(kind,name))reportReview(offset,review)},label=kind=>kind==="css-property"?"custom property":kind,detailNote=name=>{const details=profile.detailsNamed(name);return details.length?` Its ${release} detail also changed: ${details.map(entry=>`${entry.tag}: ${entry.summary}`).join(" ")}`:""},unownedMove=({kind,name,start,context,rewritable,action})=>{const renames=profile.renamesNamed(kind,name),targets=unique(renames.map(entry=>entry.to)),constructed=kind==="event"&&constructedEvents.has(name);if(rewritable&&!constructed&&profile.isGlobal(kind,name))return rewrite(start,start+name.length,targets[0],{tag:soleTag(renames),member:name,action,target:targets[0],message:`Rename ${name} to ${targets[0]}; every component exposing it renamed it, and no other component uses ${targets[0]}.`}),targets[0];const sourceKeepers=unique(targets.flatMap(to=>profile.sourceKeepers(kind,name,to))).filter(tag=>!renames.some(entry=>entry.tag===tag)),targetKeepers=unique(targets.flatMap(to=>profile.targetKeepers(kind,name,to))),shared=targets.length===1&&!sourceKeepers.length&&targetKeepers.length>0&&!renames.some(entry=>entry.polarity);return warn(start,{tag:soleTag(renames),member:name,code:shared?RENAME_TARGET_SHARED_REVIEW:RENAME_REVIEW,target:targets.join(" | "),message:(kind==="css-property"?"Custom properties inherit into nested components. ":context==="listener"?"This listener does not prove which element it listens to. ":"This string may name an event of any component. ")+`${list(renames.map(entry=>entry.tag))} renamed the ${label(kind)} ${name} to ${targets.join(" or ")}`+(sourceKeepers.length?`, while ${list(sourceKeepers)} still ${sourceKeepers.length===1?"uses":"use"} ${name}`:"")+(targetKeepers.length?`; ${list(targetKeepers)} already ${targetKeepers.length===1?"uses":"use"} ${targets.join(" and ")}, which this site would start reaching`:"")+(constructed?`; the scanned code also dispatches ${name} itself, so its listeners and dispatches must move together`:"")+`. Update it by hand for the components it is meant for; ${name} keeps working until ${removal}.`+(kind==="event"?detailNote(targets[0]):"")}),null},eventMove=(name,owner,rewritable=!0)=>{if(constructedEvents.has(name))return null;const own=owner?profile.renameFor(owner,"event",name):null;return own?profile.targetKeepers("event",name,own.to).length?null:own.to:!rewritable||!profile.renamesNamed("event",name).length||profile.exposes("event",name,owner)?null:profile.isGlobal("event",name)?profile.renamesNamed("event",name)[0].to:null},conflictMessage=(conflict,subject,to)=>conflict==="same-handler"?`${subject} already listens to ${to} with this same handler; renamed, the two identical registrations would collapse into one, so the handler would run once per activation instead of twice. Rename or remove this call, and its add or remove counterpart, together by hand.`:`${subject} already listens to ${to}; merge the two handlers by hand.`,eventSite=({name,start,owner,context,rewritable=!0,conflict=!1})=>{const retired=owner?profile.retiredEventFor(owner,name):null,retiredCandidates=retired?[retired]:profile.exposes("event",name,owner)?[]:profile.retiredEventsNamed(name);retiredCandidates.length&&warn(start,{tag:retired?owner:soleTag(retiredCandidates),member:name,code:"RETIRED_EVENT_REVIEW",target:unique(retiredCandidates.map(entry=>entry.replacement)).join(", "),message:`${name} is no longer dispatched by ${list(retiredCandidates.map(entry=>entry.tag))} in ${release}. `+retiredCandidates.map(entry=>`${entry.tag}: use ${entry.replacement}. ${entry.summary}`).join(" ")+" Review this listener and its matching removal together; keep unrelated events with the same spelling, check nested event targets, and merge any existing canonical listener without dropping or duplicating handler calls."});const own=owner?profile.renameFor(owner,"event",name):null;let current=name;if(own){const keepers=profile.targetKeepers("event",name,own.to);if(constructedEvents.has(name))warn(start,{tag:owner,member:name,code:RENAME_REVIEW,target:own.to,message:`Not renamed: the scanned code dispatches ${name} itself, so this listener would stop hearing it. Rename the dispatch and its listeners to ${own.to} together; ${name} keeps working until ${removal}.${detailNote(own.to)}`});else if(keepers.length)warn(start,{tag:owner,member:name,code:RENAME_TARGET_SHARED_REVIEW,target:own.to,message:`Not renamed: ${list(keepers)} also ${keepers.length===1?"dispatches":"dispatch"} ${own.to}, so this listener would start receiving their events when they are nested inside this ${owner}. Rename it by hand and ignore events whose target is not this ${owner}; ${name} keeps working until ${removal}.${detailNote(own.to)}`});else if(conflict)warn(start,{tag:owner,member:name,code:RENAME_CONFLICT_REVIEW,target:own.to,message:`${conflictMessage(conflict,`This ${owner}`,own.to)} ${name} keeps working until ${removal}.`});else{const narrowed=profile.sourceKeepers("event",name,own.to).filter(tag=>tag!==owner);rewrite(start,start+name.length,own.to,{tag:owner,member:name,action:"rewrite-event",target:own.to,message:`Rename the ${owner} event ${name} to ${own.to}.`+(narrowed.length?` ${list(narrowed)} still ${narrowed.length===1?"dispatches":"dispatch"} ${name}; this listener stops hearing theirs from nested elements.`:"")}),current=own.to}}else profile.renamesNamed("event",name).length&&!profile.exposes("event",name,owner)&&(conflict?warn(start,{tag:soleTag(profile.renamesNamed("event",name)),member:name,code:RENAME_CONFLICT_REVIEW,target:profile.renamesNamed("event",name)[0].to,message:conflictMessage(conflict,conflict==="same-handler"?"This receiver":"This element",profile.renamesNamed("event",name)[0].to)}):current=unownedMove({kind:"event",name,start,context,rewritable,action:"rewrite-event"})??name);const details=profile.detailsNamed(current),ownDetail=owner?profile.detailFor(owner,current):null;if(ownDetail||details.length&&!profile.exposes("event",current,owner)){const affected=ownDetail?[ownDetail]:details;warn(start,{tag:ownDetail?owner:soleTag(affected),member:current,code:DETAIL_SHAPE_REVIEW,target:current,message:`The ${current} event detail changed in ${release} and cannot be aliased: `+affected.map(entry=>`${entry.tag}: ${entry.summary}`).join(" ")+` Update this handler if it reads the detail of ${ownDetail?`the ${owner} event`:"one of these components"}.`})}const gained=current===name?profile.gainedOwners("event",name):[];gained.length&&!profile.exposes("event",name,owner)&&warn(start,{tag:soleTag(gained.map(tag=>({tag}))),member:name,code:NAME_GAINED_OWNER_REVIEW,target:name,message:`In ${release}, ${list(gained)} also ${gained.length===1?"dispatches":"dispatch"} ${name}. This ${context} does not prove which element it listens to, so it may start receiving those events; ignore them by event target if it should not.`}),unownedReviews("event",name,start,owner)},reportedDetailFieldOffsets=new Set,detailFieldAccess=/\b([A-Za-z_$][\w$]*)\s*(?:\?\.|\.)\s*detail\s*(?:\?\.|\.)\s*(open|collapsed)\b/g,reportDetailField=(field,start,owner=null,event=null)=>{if(reportedDetailFieldOffsets.has(start))return;const exact=owner&&event?profile.detailFieldFor(owner,event):null;if(exact&&exact.field===field){const target=exact.relation==="equal"?"detail.expanded":"!detail.expanded",relation=exact.relation==="equal"?"carries the same value as":"is the inverse of",removal2=exact.removedIn?` It was removed in ${exact.removedIn}.`:" This notice does not assert that the field was removed.";warn(start,{tag:owner,member:field,code:DETAIL_FIELD_REVIEW,target,message:`The ${owner} ${event} detail field ${field} is deprecated: it ${relation} detail.expanded (${exact.notice}). Read ${target} in this handler by hand; no field rewrite was applied.${removal2}`}),reportedDetailFieldOffsets.add(start);return}const candidates=event?profile.detailFieldsNamed(event).filter(entry=>entry.field===field):[],possible=candidates.length?candidates:(profile.data.detailFields??[]).filter(entry=>entry.field===field);if(!possible.length)return;const possibilities=[...new Set(possible.map(entry=>`${entry.tag}/${entry.event} (${entry.relation})`))];warn(start,{tag:null,member:field,code:DETAIL_FIELD_REVIEW,target:null,message:`This detail.${field} read has no proven Lyra tag and event. Matching published field contracts are ${possibilities.join(", ")}; identify the runtime detail type and migrate it by hand. No field rewrite was applied.`}),reportedDetailFieldOffsets.add(start)},inspectMarkupDetailFields=(owner,event,attribute)=>{if(attribute.valueStart===null||attribute.valueEnd===null)return;const value=original.slice(attribute.valueStart,attribute.valueEnd);for(const match of value.matchAll(detailFieldAccess)){const start=attribute.valueStart+match.index+match[0].lastIndexOf(match[2]);reportDetailField(match[2],start,owner,event)}};for(const token of openingTokens){const owner=token.tag.toLowerCase();for(const attribute of parseTagAttributes(original,token)){const event=markupEventName(attribute.rawName);event&&/^lr-/u.test(event.name)&&inspectMarkupDetailFields(owner,event.name,attribute)}}const memberSite=({owner,kind,name,start,forms,dynamic,removal:removalRange})=>{const propertyChange=forms.filter(([candidateKind])=>candidateKind==="property").map(([,candidate])=>profile.propertyChangeFor(owner,candidate)).find(Boolean);propertyChange&&reportPropertyChange(start,propertyChange);const review=forms.map(([candidateKind,candidate])=>profile.reviewFor(owner,candidateKind,candidate)).find(Boolean);review&&reportReview(start,review);const rule=forms.map(([candidateKind,candidate])=>profile.renameFor(owner,candidateKind,candidate)).find(Boolean);if(!rule)return null;if(rule.polarity==="inverted"&&(dynamic||!removalRange)){const target=kind==="binding"?memberStyle(name,rule.to):rule.to;return warn(start,{tag:owner,member:rule.from,code:POLARITY_REVIEW,target:rule.to,message:`${target} is the inverse of ${name} on ${owner}, and this ${kind==="binding"?"binding":kind==="property"?"property access":"attribute binding"} is not a static attribute. Bind the negated value to ${target} by hand; ${name} keeps working until ${removal}.`}),null}return rule},renamedMembers=owner=>unique([...profile.data.renames.filter(entry=>entry.tag===owner&&["attribute","property","event"].includes(entry.kind)).map(entry=>entry.from),...profile.data.reviews.filter(entry=>entry.tag===owner&&["attribute","property","event"].includes(entry.kind)).map(entry=>entry.name),...profile.data.retiredEvents.filter(entry=>entry.tag===owner).map(entry=>entry.event),...profile.data.propertyChanges.filter(entry=>entry.tag===owner).map(entry=>entry.property)]),defaultTokens=[];for(const token of openingTokens){const owner=token.tag.toLowerCase(),attributes=parseTagAttributes(original,token),lyraOwner=profile.tags.has(owner),bound=lyraOwner?boundMemberNames(attributes):null,finalNames=new Map,planned=attributes.map(attribute=>{const event=markupEventName(attribute.rawName);let finalRaw=attribute.rawName;if(event&&/^lr-/.test(event.name)){const to=eventMove(event.name,owner);to&&(finalRaw=attribute.rawName.slice(0,event.offset)+to+attribute.rawName.slice(event.offset+event.name.length))}else if(lyraOwner){const binding=markupMemberBinding(attribute),rule=binding&&!binding.form.startsWith("member")&&profile.renameFor(owner,binding.form,binding.name);rule&&!rule.polarity&&(finalRaw=attribute.rawName.slice(0,binding.offset)+rule.to+attribute.rawName.slice(binding.offset+binding.name.length))}const key=finalRaw.toLowerCase();return finalNames.set(key,(finalNames.get(key)??0)+1),{attribute,event,key,renamed:finalRaw!==attribute.rawName}});if(lyraOwner&&renamedMembers(owner).length)for(const group of openingTagBraceGroups(original,token)){const shorthand=/^[A-Za-z_$][\w$]*$/.exec(group.body)?.[0];if(!shorthand){warn(group.start,{tag:owner,member:owner,code:"DYNAMIC_VALUE_REVIEW",target:null,message:`This spread on ${owner} may carry renamed or deprecated members (${list(renamedMembers(owner))}); rename them where the object is built.`});continue}const forms=[["property",camelCase(shorthand)],["attribute",kebabCase(shorthand)]],review=forms.map(([kind,name])=>profile.reviewFor(owner,kind,name)).find(Boolean);review&&reportReview(group.start+1,review);const propertyChange=profile.propertyChangeFor(owner,camelCase(shorthand));propertyChange&&reportPropertyChange(group.start+1,propertyChange);const rule=forms.map(([kind,name])=>profile.renameFor(owner,kind,name)).find(Boolean);rule&&warn(group.start+1,{tag:owner,member:rule.from,code:rule.polarity?POLARITY_REVIEW:RENAME_REVIEW,target:rule.to,message:`The shorthand {${shorthand}} binds the deprecated ${owner} ${rule.kind} ${rule.from}; write ${memberStyle(shorthand,rule.to)}={${rule.polarity?`!${shorthand}`:shorthand}} by hand.`})}for(const{attribute,event,key,renamed}of planned){if(event){/^lr-/.test(event.name)&&(inspectMarkupDetailFields(owner,event.name,attribute),eventSite({name:event.name,start:attribute.nameStart+event.offset,owner,context:"listener",conflict:renamed&&finalNames.get(key)>1}));continue}if(!lyraOwner)continue;if(/^v-(?:bind|on)$/.test(attribute.rawName)||attribute.rawName.startsWith("${")&&!/^\$\{\s*ref\s*\(/.test(attribute.rawName)){renamedMembers(owner).length&&warn(attribute.nameStart,{tag:owner,member:owner,code:"DYNAMIC_VALUE_REVIEW",target:null,message:`This object binding on ${owner} may carry renamed or deprecated members (${list(renamedMembers(owner))}); rename them where the object is built.`});continue}if(attribute.rawName==="exportparts"){if(attribute.valueKind!=="literal"||/\$\{|\{\{/.test(attribute.value??""))continue;for(const match of(attribute.value??"").matchAll(/([^,\s:]+)(\s*:\s*[^,\s]+)?/g)){const inner=match[1],start2=attribute.valueStart+match.index,review=profile.reviewFor(owner,"part",inner);review&&reportReview(start2,review);const rule2=profile.renameFor(owner,"part",inner);rule2&&rewrite(start2,start2+inner.length,match[2]?rule2.to:`${rule2.to}:${inner}`,{tag:owner,member:inner,action:"rewrite-part",target:rule2.to,message:match[2]?`Forward the renamed ${owner} part ${rule2.to} under the same exported name.`:`Forward the renamed ${owner} part ${rule2.to} under its previous exported name ${inner}.`})}continue}const binding=markupMemberBinding(attribute);if(!binding)continue;const forms=binding.form==="member"?[["property",camelCase(binding.name)],["attribute",kebabCase(binding.name)]]:[[binding.form,binding.name]],start=attribute.nameStart+binding.offset,staticAttribute=binding.form==="attribute"&&binding.offset===0&&!binding.dynamic,rule=memberSite({owner,kind:binding.form==="member"?"binding":binding.form,name:binding.name,start,forms,dynamic:binding.dynamic,removal:staticAttribute});if(!rule)continue;if(bound.has(kebabCase(rule.to))||renamed&&finalNames.get(key)>1){warn(start,{tag:owner,member:rule.from,code:RENAME_CONFLICT_REVIEW,target:rule.to,message:`This ${owner} already binds ${rule.to}; remove the deprecated ${rule.from} binding by hand.`});continue}if(rule.polarity==="inverted"){if(!attributeSemantics(token.start)){warn(start,{tag:owner,member:rule.from,code:POLARITY_REVIEW,target:rule.to,message:`${rule.to} is the inverse of ${rule.from}. In this template syntax a static value may be assigned to the ${camelCase(rule.from)} property as a string, where "false" is truthy, so set ${rule.to} by hand.`});continue}if(profile.preservedDefaultsFor(owner).some(entry=>entry.attribute===rule.to&&entry.value===!0)){warn(start,{tag:owner,member:rule.from,code:POLARITY_REVIEW,target:rule.to,message:`${rule.from} has a preserved absent default, so removing an explicit attribute would let a later run insert ${rule.to}. Review this value using the old converter (a presence boolean treats even "false" as true), then migrate it by hand after the default migration is complete.`});continue}const value=attribute.value??"",end=attributeEnd(original,attribute);if(value===""||value==="true"||value===rule.from){let removeFrom=attribute.nameStart;for(;removeFrom>0&&/\s/.test(original[removeFrom-1]);)removeFrom-=1;rewrite(removeFrom,end,"",{tag:owner,member:rule.from,action:"remove-attribute",target:rule.to,message:`Remove ${rule.from}: it restated the ${owner} default, and ${rule.to} is off by default.`})}else value==="false"?rewrite(attribute.nameStart,end,rule.to,{tag:owner,member:rule.from,action:"rewrite-attribute",target:rule.to,message:`Replace ${rule.from}="false" with the presence of ${rule.to}.`}):warn(start,{tag:owner,member:rule.from,code:POLARITY_REVIEW,target:rule.to,message:`${rule.from}="${attribute.value}" is not a boolean literal; set ${rule.to} to its inverse by hand.`});continue}const target=binding.form==="member"?memberStyle(binding.name,rule.to):rule.to;rewrite(start,start+binding.name.length,target,{tag:owner,member:rule.from,action:`rewrite-${rule.kind}`,target:rule.to,message:`Rename the ${owner} ${rule.kind} ${rule.from} to ${rule.to}.`})}lyraOwner&&profile.defaultsFor(owner).length&&defaultTokens.push({token,owner,bound})}const slotTags=[...profile.tags].filter(tag=>profile.slotContentFor(tag).length||[...profile.data.renames,...profile.data.reviews].some(entry=>entry.tag===tag&&entry.kind==="slot"));for(const tag of slotTags)for(const pair of elementPairs(original,tag,inComment))for(const child of directChildTokens(original,pair.opening.end+1,pair.closingStart,inComment)){let slot="",start=child.nameStart;for(const attribute of parseTagAttributes(original,child))if(attribute.rawName==="slot")slot=attribute.valueKind==="literal"?attribute.value??"":null,start=attribute.valueStart??attribute.nameStart;else if(attribute.rawName==="v-slot")slot="";else{const prefix=["v-slot:","#"].find(candidate=>attribute.rawName.startsWith(candidate));prefix&&(slot=attribute.rawName.slice(prefix.length),start=attribute.nameStart+prefix.length,slot==="default"&&(slot=""))}if(slot===null)continue;const review=profile.reviewFor(tag,"slot",slot);review&&reportReview(start,review);const rule=slot===""?null:profile.renameFor(tag,"slot",slot);if(rule&&rewrite(start,start+slot.length,rule.to,{tag,member:slot,action:"rewrite-slot",target:rule.to,message:`Rename the ${tag} slot ${slot} to ${rule.to}.`}),child.tag!=="template")for(const content of profile.slotContentFor(tag)){if(content.slot!==slot)continue;const listed=(content.report??content.allow).includes(child.tag);(content.report?!listed:listed)||warn(child.nameStart,{tag,member:child.tag,code:DEPRECATED_CONTENT_REVIEW,target:null,message:`<${child.tag}> in ${slot===""?"the default slot":`the ${slot} slot`} of ${tag}: ${content.summary}`})}}for(const match of original.matchAll(/::part\(([^)]*)\)/g)){if(inComment(match.index))continue;const owner=compoundTypeBefore(original,match.index),listStart=match.index+7;for(const nameMatch of match[1].matchAll(/\S+/g)){const name=nameMatch[0],start=listStart+nameMatch.index,own=owner?profile.renameFor(owner,"part",name):null;if(own)rewrite(start,start+name.length,own.to,{tag:owner,member:name,action:"rewrite-part",target:own.to,message:`Rename the ${owner} part ${name} to ${own.to}.`});else if(profile.renamesNamed("part",name).length&&!profile.exposes("part",name,owner)){const renames=profile.renamesNamed("part",name),keepers=profile.exposers("part",name).filter(tag=>!renames.some(entry=>entry.tag===tag));warn(start,{tag:soleTag(renames),member:name,code:RENAME_REVIEW,target:list(renames.map(entry=>entry.to)),message:`This ::part() selector does not name the component that owns the part. ${list(renames.map(entry=>entry.tag))} renamed the part ${name} to ${list(renames.map(entry=>entry.to))}`+(keepers.length?`, while ${list(keepers)} still ${keepers.length===1?"exposes":"expose"} ${name}`:"")+". Rename it where the selector reaches a renamed component directly; a part forwarded through exportparts keeps its exported name."})}else if(profile.gainedOwners("part",name).length&&!owner?.startsWith("lr-")){const gained=profile.gainedOwners("part",name);warn(start,{tag:soleTag(gained.map(tag=>({tag}))),member:name,code:NAME_GAINED_OWNER_REVIEW,target:name,message:`In ${release}, ${list(gained)} also ${gained.length===1?"exposes":"expose"} the part ${name}, so this selector may start matching it.`})}unownedReviews("part",name,start,owner)}}const cssNames=new Set([...profile.data.renames.filter(entry=>entry.kind==="css-property").flatMap(entry=>[entry.from,entry.to]),...profile.data.reviews.filter(entry=>entry.kind==="css-property").map(entry=>entry.name)]);if(cssNames.size)for(const match of original.matchAll(/(?<![\w-])--[A-Za-z0-9_-]+/g)){const name=match[0];if(!cssNames.has(name)||inComment(match.index))continue;profile.renamesNamed("css-property",name).length&&unownedMove({kind:"css-property",name,start:match.index,context:"custom property",rewritable:!0,action:"rewrite-css-property"});const declaration=/^\s*['"]?\s*:/.test(original.slice(match.index+name.length,match.index+name.length+16))||/setProperty\s*\(\s*['"`]$/.test(original.slice(Math.max(0,match.index-24),match.index)),gained=profile.gainedOwners("css-property",name);declaration&&gained.length&&warn(match.index,{tag:soleTag(gained.map(tag=>({tag}))),member:name,code:NAME_GAINED_OWNER_REVIEW,target:name,message:`In ${release}, ${list(gained)} also ${gained.length===1?"reads":"read"} ${name}, so this declaration now reaches them when they are nested below it.`});for(const review of profile.reviewsNamed("css-property",name))reportReview(match.index,review)}for(const match of original.matchAll(/\[\s*([A-Za-z_][\w-]*)(?=\s*(?:[~|^$*]?=|\]))/g)){if(inComment(match.index))continue;const owner=compoundTypeBefore(original,match.index);if(!owner||!profile.tags.has(owner))continue;const name=match[1],start=match.index+match[0].length-name.length,review=profile.reviewFor(owner,"attribute",name);review&&reportReview(start,review);const rule=profile.renameFor(owner,"attribute",name);if(rule){if(rule.polarity==="inverted"){warn(start,{tag:owner,member:name,code:POLARITY_REVIEW,target:rule.to,message:`${rule.to} is the inverse of ${name}; rewrite this ${owner} selector to test the opposite state (for example :not([${rule.to}])).`});continue}if(!rule.reflects){warn(start,{tag:owner,member:name,code:RENAME_REVIEW,target:rule.to,message:`${owner} does not reflect ${rule.to}, so [${rule.to}] matches only where markup sets it. Rename this selector once every place that sets ${name} (markup, setAttribute) sets ${rule.to} instead.`});continue}rewrite(start,start+name.length,rule.to,{tag:owner,member:name,action:"rewrite-attribute",target:rule.to,message:`Rename the ${owner} attribute selector [${name}] to [${rule.to}]; ${owner} reflects ${rule.to}.`})}}for(const match of original.matchAll(/:state\(\s*([a-z][a-z0-9-]*)\s*\)/g)){if(inComment(match.index))continue;const owner=compoundTypeBefore(original,match.index),review=owner?profile.reviewFor(owner,"css-state",match[1]):null;review&&reportReview(match.index+match[0].indexOf(match[1]),review)}for(const review of profile.data.reviews.filter(entry=>entry.kind==="component"))for(const match of original.matchAll(new RegExp(`(?<![\\w-])${review.tag}(?![\\w-])`,"g")))inComment(match.index)||original.slice(match.index-2,match.index)==="</"||reportReview(match.index,review);const anchoredDetailListener=/(?:document\.)?querySelector\(\s*(['"])(lr-[a-z0-9-]+)\1\s*\)\s*!?\s*\.\s*addEventListener\(\s*(['"])(lr-[a-z0-9-]+)\3\s*,/g,unownedDetailListener=/[A-Za-z_$][\w$]*\s*\.\s*addEventListener\(\s*(['"])(lr-[a-z0-9-]+)\1\s*,/g,scriptListenerAt=offset=>{const start=Math.max(0,offset-4096),prefix=original.slice(start,offset);let result2=null;for(const pattern of[anchoredDetailListener,unownedDetailListener]){pattern.lastIndex=0;for(const match of prefix.matchAll(pattern)){const event=pattern===anchoredDetailListener?match[4]:match[2],callbackStart=match.index+match[0].length,callback=prefix.slice(callbackStart);!/=>|\bfunction\b/u.test(callback)||/\)\s*;/u.test(callback)||(!result2||match.index>=result2.index)&&(result2={owner:pattern===anchoredDetailListener?match[2]:null,event,index:match.index})}}return result2};for(const match of original.matchAll(detailFieldAccess)){const offset=match.index+match[0].lastIndexOf(match[2]);if(reportedDetailFieldOffsets.has(offset)||inComment(offset)||inLiteral(offset)||inCss(offset))continue;const listener=scriptListenerAt(offset);reportDetailField(match[2],offset,listener?.owner??null,listener?.event??null)}for(const match of original.matchAll(ANCHORED_MEMBER)){if(inComment(match.index))continue;const owner=match[2],member=match[3],start=match.index+match[0].length-member.length,after=start+member.length;if(handledMembers.add(start),LISTENER_METHODS.has(member)||ATTRIBUTE_METHODS.has(member)){const call=/^\s*\(\s*(['"`])([^'"`\n]+)\1/.exec(original.slice(after,after+200));if(!call)continue;const valueStart=after+call[0].length-1-call[2].length;if(handledStrings.add(valueStart-1),LISTENER_METHODS.has(member)){eventSite({name:call[2],start:valueStart,owner,context:"listener",conflict:sameHandlerListens(valueStart-1,call[2])&&"same-handler"});continue}const rule2=memberSite({owner,kind:"attribute",name:call[2],start:valueStart,forms:[["attribute",call[2]]],dynamic:!0});rule2&&rewrite(valueStart,valueStart+call[2].length,rule2.to,{tag:owner,member:rule2.from,action:"rewrite-attribute",target:rule2.to,message:`Rename the ${owner} attribute ${rule2.from} to ${rule2.to}.`});continue}if(!profile.tags.has(owner))continue;if(/^\s*\(/.test(original.slice(after,after+64))){const review=profile.reviewFor(owner,"method",member);review&&reportReview(start,review);continue}const rule=memberSite({owner,kind:"property",name:member,start,forms:[["property",member]],dynamic:!0});rule&&rewrite(start,after,rule.to,{tag:owner,member:rule.from,action:"rewrite-property",target:rule.to,message:`Rename the ${owner} property ${rule.from} to ${rule.to}.`})}const eventNameMatters=name=>profile.renamesNamed("event",name).length||profile.detailsNamed(name).length||profile.retiredEventsNamed(name).length||profile.reviewsNamed("event",name).length||profile.gainedOwners("event",name).length;for(const match of original.matchAll(/(['"`])(?:(window|document|body):)?(lr-[a-z0-9]+(?:-[a-z0-9]+)*)\1/g)){const name=match[3];if(handledStrings.has(match.index)||inComment(match.index)||!eventNameMatters(name))continue;const before=original.slice(Math.max(0,match.index-120),match.index);if(!match[2]&&TAG_API_CALL_BEFORE.test(before))continue;const listener=LISTENER_CALL_BEFORE.test(before);eventSite({name,start:match.index+1+(match[2]?match[2].length+1:0),owner:null,context:listener?"listener":"event name string",rewritable:listener,conflict:listener&&!match[2]&&sameHandlerListens(match.index,name)&&"same-handler"})}const mentioned=mentionedTags(original,profile.tags,inComment),accessRules=new Map,addAccess=(name,candidate)=>accessRules.set(name,[...accessRules.get(name)??[],candidate]);for(const entry of profile.data.renames)entry.kind==="property"&&mentioned.has(entry.tag)&&addAccess(entry.from,{tag:entry.tag,rule:entry,call:!1});for(const entry of profile.data.reviews)(entry.kind==="property"||entry.kind==="method")&&mentioned.has(entry.tag)&&addAccess(entry.name,{tag:entry.tag,review:entry,call:entry.kind==="method"});for(const entry of profile.data.propertyChanges)mentioned.has(entry.tag)&&addAccess(entry.property,{tag:entry.tag,propertyChange:entry,call:!1});if(accessRules.size){const inString=rangeTester(mergeRanges(quotedStringRanges(original,inComment))),inStylesheet=rangeTester(stylesheetRanges(original,file,inComment)),inToken=rangeTester(mergeRanges(openingTokens.map(token=>[token.start+1,token.end])));for(const match of original.matchAll(/(?<=[\w$)\]])\s*(?:\?\.|!?\.)\s*([A-Za-z_$][\w$]*)/g)){const name=match[1],candidates=accessRules.get(name);if(!candidates)continue;const start=match.index+match[0].length-name.length;if(handledMembers.has(start)||reportedDetailFieldOffsets.has(start)||inComment(start)||inString(start)||inStylesheet(start)||inToken(start))continue;const call=/^\s*\(/.test(original.slice(start+name.length,start+name.length+64));for(const candidate of candidates){if(candidate.call!==call)continue;if(candidate.propertyChange){reportPropertyChange(start,candidate.propertyChange);continue}if(candidate.review){reportReview(start,candidate.review);continue}const{rule}=candidate;warn(start,{tag:rule.tag,member:name,code:rule.polarity?POLARITY_REVIEW:RENAME_REVIEW,target:rule.to,message:`If this reads or writes an ${rule.tag}, ${rule.polarity?`use the inverse property .${rule.to}`:`rename .${name} to .${rule.to}`}; .${name} keeps working until ${removal}.`})}}}for(const match of original.matchAll(/\.\s*(?:set|get|has|remove|toggle)Attribute\s*\(\s*(['"`])([a-z][a-z0-9-]*)\1/g)){const name=match[2],start=match.index+match[0].length-1-name.length;if(!(handledStrings.has(start-1)||inComment(match.index)))for(const tag of mentioned){const review=profile.reviewFor(tag,"attribute",name);review&&reportReview(start,review);const rule=profile.renameFor(tag,"attribute",name);rule&&warn(start,{tag,member:name,code:rule.polarity?POLARITY_REVIEW:RENAME_REVIEW,target:rule.to,message:`If this element is an ${tag}, ${rule.polarity?`use the inverse attribute ${rule.to}`:`rename ${name} to ${rule.to}`}; ${name} keeps working until ${removal}.`})}}if(profile.data.defaults.length){const defaultProfiles=new Map([...new Set(profile.data.defaults.map(entry=>entry.tag))].map(tag=>[tag,{origin,tag,defaults:profile.defaultsFor(tag).map(entry=>({member:entry.attribute,value:entry.value}))}])),hazards=scanLocalMigrationHazards(original,defaultProfiles,ignoredRanges,defaultTokens.map(({token})=>({...token,tag:token.tag.toLowerCase()}))),blocked=new Set([...options.blockedLocalMigrations??[],...hazards.blocked]);result.blockedLocalMigrations=hazards.blocked;for(const use of hazards.uses)warn(use.offset,{tag:use.tag,member:use.tag,code:use.warningCode,target:use.tag,message:use.message});for(const{token,owner,bound}of defaultTokens){const missing=profile.defaultsFor(owner).filter(entry=>!profile.defaultAliasesFor(owner,entry.attribute).some(name=>bound.has(name)));if(!missing.length)continue;if(blocked.has(localMigrationKey(origin,owner))){warn(token.nameStart,{tag:owner,member:owner,code:"MAPPING_REVIEW_BLOCKED",target:owner,message:`${owner} did not receive its Lyra ${profile.fromMajor} defaults (${missing.map(entry=>`${entry.attribute}=${String(entry.value)}`).join(", ")}) because the scanned target set accesses it through a DOM alias or an opaque attribute spread. Add them by hand where the previous default matters.`});continue}const insertions=[];for(const entry of missing)insertions.push(serializeLocalDefault({member:entry.attribute,value:entry.value})),result.changes.push(reportEntry({textStarts:starts,file,offset:token.end,origin,upstreamTag:owner,upstreamMember:entry.attribute,action:"insert-default",target:`${entry.attribute}=${String(entry.value)}`,message:`Insert ${entry.attribute} to preserve the Lyra ${profile.fromMajor} default.`}));if(insertions.length){const insertionOffset=original[token.end-1]==="/"?token.end-1:token.end,replacement=/\s/.test(original[insertionOffset-1])?`${insertions.join(" ")} `:` ${insertions.join(" ")}`;edits.push({start:insertionOffset,end:insertionOffset,replacement})}}}if(!profile.skipped.length)for(const{offset,entry}of acknowledgements.unused()){const key=`${offset}:${UNUSED_ACKNOWLEDGEMENT}:${entry.token}`;seenWarnings.has(key)||(seenWarnings.add(key),result.warnings.push(reportEntry({textStarts:starts,file,offset,origin,upstreamTag:null,upstreamMember:entry.token,action:"manual-review",target:null,warningCode:UNUSED_ACKNOWLEDGEMENT,message:entry.code?`The acknowledgement ${entry.token} matches no report on the lines it covers; remove it.`:`The acknowledgement ${entry.token} is not of the form CODE:name, for example DETAIL_SHAPE_REVIEW:lr-close.`})))}return result.content=finalizeEdits(original,edits),sortReportEntries(result.changes,"action"),sortReportEntries(result.warnings,"warningCode"),result}export{migrateRenameText};
@@ -1,4 +1,4 @@
1
- import{AUTO_CLASSIFICATIONS,invariant}from"./migration-contract.mjs";import{assessImperativeDefaults,camelCase,commentRanges,ecosystemForTag,finalizeEdits,htmlTemplateStyleRanges,insideRanges,insideTemplateExpression,isDynamicAttribute,kebabCase,lineStarts,localMigrationKey,namedRule,normalizedAttributeName,originLabel,pairedMarkupTokens,parseTagAttributes,quotedRangeContaining,reportEntry,scanAllOpeningTags,scanApiTagReferences,scanDirectElementChildren,scanImports,scanLocalMigrationHazards,scanMarkupTags,serializeLocalDefault}from"./migration-analysis.mjs";import{migrateRenameText}from"./migration-renames.mjs";const REDUCED_MOTION_REVIEW_TAGS=new Set(["wa-animation","sl-animation","wa-animated-image","sl-animated-image"]),ICON_VOCABULARY_REVIEW_TAGS=new Set(["wa-icon","sl-icon","sl-icon-button"]);function reducedMotionReviewMessage(upstreamTag){return`${upstreamTag} does not freeze or snap its animation under prefers-reduced-motion: reduce. The migrated Lyra target respects prefers-reduced-motion by default and does exactly that automatically. Review whether the migrated element should keep animating for users who asked for less motion, or set ignore-reduced-motion to preserve upstream playback under that preference.`}function iconVocabularyReviewMessage(upstreamTag){return`${upstreamTag}'s icon-name vocabulary is not bundled with Lyra. The default library includes only add, check, close, search, menu, chevron-left, chevron-right, chevron-down, calendar, command, and trash; any other migrated name renders no glyph. Register the required vocabulary with registerIconLibrary('default', { resolver }) or replace each name explicitly.`}function warningCode(mapping){return mapping?mapping.classification==="warning-required"?"WARNING_REQUIRED":mapping.classification==="conceptual-only"?"CONCEPTUAL_MAPPING":"UNSUPPORTED_MAPPING":"UNKNOWN_UPSTREAM_TAG"}function scanAliasedRewriteReviews(text,contract,ignoredRanges){const reviews=new Map,addUse=(mapping,tagStart,alias,offset,upstreamMember,target,message)=>{const record=reviews.get(mapping.upstreamTag)??{mapping,aliases:[],uses:[]};reviews.has(mapping.upstreamTag)||reviews.set(mapping.upstreamTag,record),record.aliases.some(entry=>entry.tagStart===tagStart)||record.aliases.push({tagStart,alias});const key=`${offset}:${upstreamMember}:${target}`;record.uses.some(entry=>entry.key===key)||record.uses.push({key,offset,alias,upstreamMember,target,message})};for(const mapping of contract.mappings.values()){if(!AUTO_CLASSIFICATIONS.has(mapping.classification))continue;const escapedTag=mapping.upstreamTag.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),declaration=new RegExp(`\\b(?:const|let|var)\\s+(?<alias>[$A-Z_a-z][$\\w]*)\\s*=\\s*(?:document\\.)?querySelector(?:<[^>\\n]+>)?\\(\\s*(['"])${escapedTag}\\2\\s*\\)`,"g");for(const match of text.matchAll(declaration)){if(insideRanges(match.index,ignoredRanges))continue;const alias=match.groups.alias,tagStart=match.index+match[0].lastIndexOf(mapping.upstreamTag),afterDeclaration=match.index+match[0].length,owner=`(?<![$\\w])${alias.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\s*(?:\\?\\.|!\\s*\\.|\\.)\\s*`;for(const[section,suffix]of[["methods","(?=\\s*\\()"],["properties","(?=\\s*(?:=|\\.|\\?|;|$))"]])for(const rule of mapping.rewrites[section]){const member=rule.from.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),regex=new RegExp(`${owner}(?<member>${member})${suffix}`,"g");regex.lastIndex=afterDeclaration;for(let use=regex.exec(text);use;use=regex.exec(text)){const offset=use.index+use[0].lastIndexOf(use.groups.member);insideRanges(offset,ignoredRanges)||addUse(mapping,tagStart,alias,offset,rule.from,rule.to,`Aliased ${section} member ${alias}.${rule.from} must be migrated with its selector.`)}}for(const rule of mapping.rewrites.events){const member=rule.from.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),regex=new RegExp(`${owner}(?:add|remove)EventListener\\(\\s*(['"])(?<member>${member})\\1`,"g");regex.lastIndex=afterDeclaration;for(let use=regex.exec(text);use;use=regex.exec(text)){const offset=use.index+use[0].lastIndexOf(use.groups.member);insideRanges(offset,ignoredRanges)||addUse(mapping,tagStart,alias,offset,rule.from,rule.to,`Aliased event ${alias}.${rule.from} must be migrated with its selector.`)}}for(const rule of mapping.rewrites.attributes){const member=rule.from.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),regex=new RegExp(`${owner}(?:get|set|has|toggle|remove)Attribute\\(\\s*(['"])(?<member>${member})\\1`,"g");regex.lastIndex=afterDeclaration;for(let use=regex.exec(text);use;use=regex.exec(text)){const offset=use.index+use[0].lastIndexOf(use.groups.member);insideRanges(offset,ignoredRanges)||addUse(mapping,tagStart,alias,offset,rule.from,rule.to,`Aliased attribute ${alias}.${rule.from} must be migrated with its selector.`)}}}}return reviews}function scanDynamicDefaultReviews(text,markupTokens,contract){const reviews=new Map;for(const token of markupTokens){if(token.closing)continue;const mapping=contract.mappings.get(token.tag);if(!AUTO_CLASSIFICATIONS.has(mapping?.classification))continue;const attributes=parseTagAttributes(text,token);for(const rule of mapping.rewrites.defaults){if(rule.action!=="replace-value")continue;const variants=new Set([rule.member,kebabCase(rule.member),camelCase(rule.member)]),attribute=attributes.find(entry=>variants.has(entry.rawName.replace(/^[.?:]/,"")));if(!attribute||attribute.valueKind==="boolean"||!isDynamicAttribute(attribute))continue;const record=reviews.get(mapping.upstreamTag)??{mapping,uses:[]};reviews.has(mapping.upstreamTag)||reviews.set(mapping.upstreamTag,record),record.uses.push({offset:attribute.valueStart,upstreamMember:rule.member,target:String(rule.to),message:`The ${rule.member} value is dynamic; migrate ${String(rule.from)} to ${String(rule.to)} at its source before changing this component mapping.`})}}return reviews}const RANDOM_CONTENT_BEHAVIOR_MESSAGES=Object.freeze({"host-layout":"Web Awesome uses display: contents while Lyra uses a block host with a shadow base; review inline placement and host-authored layout CSS.","multi-item-layout":"Lyra owns the simultaneous-item flex/wrap/gap context inside its shadow base instead of leaving it on the host.","unique-retry-bound":"Web Awesome promises non-repeating unique selection while Lyra uses a bounded retry and can eventually repeat.","forwarded-slot-candidates":"Lyra flattens a direct forwarding slot into projected candidates while Web Awesome treats only its direct element child as the candidate.","autoplay-semantics":"Lyra stops autoplay for reduced motion, keeps timer ticks silent, does not pause on hover, and exposes a localized paused control/state/event absent upstream."});function scanConditionalBehaviorReviews(text,markupTokens,ignoredRanges,contract){const reviews=new Map,pairs=pairedMarkupTokens(markupTokens);for(const opening of markupTokens){if(opening.closing||opening.tag!=="wa-random-content")continue;const mapping=contract.mappings.get(opening.tag);if(!AUTO_CLASSIFICATIONS.has(mapping?.classification)||!mapping.parity.behaviorReviewFlags.length)continue;const attributes=parseTagAttributes(text,opening),attribute=name=>attributes.find(candidate=>normalizedAttributeName(candidate)===name),closing=pairs.get(opening.start),children=closing?scanDirectElementChildren(text,opening.end+1,closing.start,ignoredRanges):{directTags:[],dynamicDirectContent:!opening.selfClosing},flags=[];(children.directTags.length>0||children.dynamicDirectContent)&&flags.push("host-layout");const items=attribute("items"),dynamicItems=items?isDynamicAttribute(items):!1,itemCount=!items||items.value===null?1:Number(items.value);(dynamicItems||Number.isFinite(itemCount)&&itemCount>1)&&flags.push("multi-item-layout");const mode=attribute("mode"),dynamicMode=mode?isDynamicAttribute(mode):!1,effectiveItems=Number.isFinite(itemCount)?Math.max(1,Math.trunc(itemCount)):1;(!mode||dynamicMode||mode.value==="unique")&&(dynamicMode||children.dynamicDirectContent||children.directTags.length>effectiveItems)&&flags.push("unique-retry-bound"),(children.directTags.includes("slot")||children.dynamicDirectContent)&&flags.push("forwarded-slot-candidates"),attribute("autoplay")&&flags.push("autoplay-semantics");const uniqueFlags=flags.filter((flag,index)=>mapping.parity.behaviorReviewFlags.includes(flag)&&flags.indexOf(flag)===index);uniqueFlags.length&&reviews.set(opening.start,{flags:uniqueFlags,message:`Review ${mapping.upstreamTag} before relying on the migrated behavior: `+uniqueFlags.map(flag=>`${flag}: ${RANDOM_CONTENT_BEHAVIOR_MESSAGES[flag]}`).join(" ")})}return reviews}function memberAction(section){return`rewrite-${{attributes:"attribute",properties:"property",events:"event",slots:"slot",parts:"part",cssProperties:"css-property",methods:"method"}[section]}`}function targetImport(component){return`@aceshooting/lyra-ui/${component.registrationModule.replace(/^src\//,"").replace(/\.ts$/,".js")}`}function registrationClosure(contract,upstreamTags){const targets=new Map;for(const upstreamTag of upstreamTags??[]){const mapping=contract.mappings.get(upstreamTag);!mapping||!AUTO_CLASSIFICATIONS.has(mapping.classification)||!mapping.target||targets.set(mapping.targetTag,mapping.target)}const rootIncluded=[...targets.values()].some(component=>component.rootIncluded!==!1),granular=[...targets.values()].filter(component=>component.rootIncluded===!1).map(targetImport).sort();return[...rootIncluded?["@aceshooting/lyra-ui/all.js"]:[],...granular]}function deepImportTag(specifier,ecosystem){const match=specifier.match(/\/components\/([a-z0-9-]+)\/(?:\1(?:\.component)?|index)\.(?:js|mjs)$/);return match?`${ecosystem==="webawesome"?"wa":"sl"}-${match[1]}`:null}function packageProvidesMapping(imported,mapping){return!!(mapping&&(!mapping.source?.tier||imported.tiers.has(mapping.source.tier)))}function mappingMessage(mapping){return mapping?mapping.rationale||`The ${mapping.classification} mapping requires manual review.`:"No pinned inventory mapping exists for this upstream tag."}function localAttributeName(rawName){return rawName.replace(/^(?:v-bind:|bind:|[.?:])/,"").toLowerCase()}function migrateLocalText(original,contract,options){const file=options.file??"<memory>",origin=options.origin,profiles=contract.localMigrations.get(origin);invariant(profiles,`unknown local migration origin ${String(origin)}`);const starts=lineStarts(original),ignoredRanges=commentRanges(original),openingTokens=scanAllOpeningTags(original,ignoredRanges).filter(token=>profiles.has(token.tag)),hazards=scanLocalMigrationHazards(original,profiles,ignoredRanges,openingTokens),blocked=new Set([...options.blockedLocalMigrations??[],...hazards.blocked]),edits=[],changes=[],warnings=[],entry=(offset,tag,action,target,message,warningCode2=null,upstreamMember=null)=>reportEntry({textStarts:starts,file,offset,origin,upstreamTag:tag,upstreamMember,action,target,warningCode:warningCode2,message});for(const use of hazards.uses)warnings.push(entry(use.offset,use.tag,"manual-review",use.tag,use.message,use.warningCode));for(const token of openingTokens){const profile=profiles.get(token.tag),key=localMigrationKey(origin,token.tag);if(blocked.has(key)){warnings.push(entry(token.nameStart,token.tag,"manual-review",token.tag,`${token.tag} remains unchanged because the scanned target set contains an aliased or dynamic value requiring review.`,"MAPPING_REVIEW_BLOCKED"));continue}const attributes=parseTagAttributes(original,token),present=new Set(attributes.map(attribute=>localAttributeName(attribute.rawName))),insertions=[];for(const rule of profile.defaults)present.has(rule.member.toLowerCase())||(insertions.push(serializeLocalDefault(rule)),changes.push(entry(token.end,token.tag,"insert-default",`${rule.member}=${String(rule.value)}`,`Insert ${rule.member} to preserve the Lyra ${originLabel(origin)} default.`,null,rule.member)));if(insertions.length>0){const insertionOffset=original[token.end-1]==="/"?token.end-1:token.end;edits.push({start:insertionOffset,end:insertionOffset,replacement:` ${insertions.join(" ")}`})}}return changes.sort((left,right)=>left.line-right.line||left.column-right.column||left.action.localeCompare(right.action)),warnings.sort((left,right)=>left.line-right.line||left.column-right.column||left.warningCode.localeCompare(right.warningCode)),{content:finalizeEdits(original,edits),changes,warnings,usage:{webawesome:{automatic:0,manual:0},shoelace:{automatic:0,manual:0}},blockedMappings:new Set,blockedEcosystems:new Set,bareImportEcosystems:new Set,blockedLocalMigrations:blocked}}function scanUnrewrittenUpstreamReferences(text){const found=[],seen=new Set,push=(offset,upstreamTag,member,code,target,message)=>{const key=`${offset}:${code}`;seen.has(key)||(seen.add(key),found.push({offset,upstreamTag,member,code,target,message}))},TAG=/(?<![\w-])(?<tag>(?:wa|sl)-[a-z][a-z0-9-]*)(?![\w-])/g;for(const block of text.matchAll(/\bcss`/g)){const bodyStart=block.index+block[0].length,bodyEnd=text.indexOf("`",bodyStart);if(bodyEnd===-1)continue;const body=text.slice(bodyStart,bodyEnd);for(const match of body.matchAll(TAG)){const tag=match.groups.tag;push(bodyStart+match.index,tag,"css-template","CSS_IN_JS_SELECTOR_REVIEW",tag,`\`${tag}\` appears in a css\`\` template and was NOT rewritten. A rule keyed on a tag that no longer exists matches nothing, silently. Rename the selector by hand.`)}}for(const match of text.matchAll(/::slotted\(\s*([^)]*)\)/g))for(const inner of match[1].matchAll(TAG)){const tag=inner.groups.tag;push(match.index+match[0].indexOf(inner.groups.tag),tag,"::slotted","SLOTTED_SELECTOR_REVIEW",tag,`\`::slotted(${tag})\` was NOT rewritten and will match nothing after the migration. Rename it by hand.`)}const CALL=/\bthis(?:\s*\??\.\s*(?:shadowRoot|renderRoot))?\s*\??\.\s*(?:querySelector|querySelectorAll|closest|matches)\s*(?:<[^>\n]+>)?\(\s*(['"`])(?<selector>[^'"`]*)\1/g;for(const match of text.matchAll(CALL)){const selectorOffset=match.index+match[0].lastIndexOf(match.groups.selector);for(const inner of match.groups.selector.matchAll(TAG)){const tag=inner.groups.tag;push(selectorOffset+inner.index,tag,"selector-string","SELECTOR_STRING_REVIEW",tag,`\`${tag}\` appears in a DOM selector string that was NOT rewritten. The query will return null after the migration rather than throwing. Rename it by hand.`)}}const declaredHere=new Set([...text.matchAll(/(--(?:wa|sl)-[a-z0-9-]+)\s*:/g)].map(match=>match[1]));for(const match of text.matchAll(/--(?<ecosystem>wa|sl)-[a-z0-9-]+/g)){const token=match[0];declaredHere.has(token)||push(match.index,`${match.groups.ecosystem}-tokens`,"custom-property","UPSTREAM_TOKEN_REVIEW",token,`\`${token}\` is an upstream design token with no automatic Lyra equivalent, and was left as-is. A var() naming a token that no longer exists falls back to its second argument, or to nothing -- silently. Map it by hand against llms/tokens.md; note the spacing scales are offset by one step, so renaming by name alone tightens every gap.`)}return found}function migrateText(original,contract,options={}){if(options.origin&&contract.renameProfiles?.has(options.origin))return migrateRenameText(original,contract,options);if(options.origin)return migrateLocalText(original,contract,options);const file=options.file??"<memory>",rewriteBarePackages=options.rewriteBarePackages??new Set,rootRegistrationMappings=options.rootRegistrationMappings??new Map,starts=lineStarts(original),ignoredRanges=commentRanges(original),htmlStyleRanges=htmlTemplateStyleRanges(original),markupTokens=scanMarkupTags(original,ignoredRanges),allOpeningTokens=scanAllOpeningTags(original,ignoredRanges),apiReferences=scanApiTagReferences(original,ignoredRanges,options.domFactoryBindings),aliasReviews=scanAliasedRewriteReviews(original,contract,ignoredRanges),dynamicDefaultReviews=scanDynamicDefaultReviews(original,markupTokens,contract),conditionalBehaviorReviews=scanConditionalBehaviorReviews(original,markupTokens,ignoredRanges,contract),imports=scanImports(original,ignoredRanges,contract),bareImportPackages=new Set(imports.filter(imported=>imported.sideEffect&&!imported.subpath).map(imported=>imported.packageName)),blockedMappings=new Set([...options.blockedMappings??[],...aliasReviews.keys(),...dynamicDefaultReviews.keys()]),imperativeDefaultReviews=apiReferences.flatMap(reference=>{if(reference.kind!=="construction")return[];const mapping=contract.mappings.get(reference.tag),defaults=mapping?.rewrites.defaults.filter(rule=>rule.action==="insert-if-absent")??[];if(!mapping||!AUTO_CLASSIFICATIONS.has(mapping.classification)||defaults.length===0)return[];const assessment=assessImperativeDefaults(original,reference,defaults);return assessment.safe?[]:(blockedMappings.add(mapping.upstreamTag),[{reference,mapping,defaults,missing:assessment.missing}])}),registrationBlockedMappings=new Set(options.registrationBlockedMappings??[]),blockedEcosystems=new Set(options.blockedEcosystems??[]);for(const imported of imports){if(imported.sideEffect)continue;const upstreamTag=imported.subpath?deepImportTag(imported.specifier,imported.ecosystem):null,mapping=upstreamTag?contract.mappings.get(upstreamTag):null;mapping&&AUTO_CLASSIFICATIONS.has(mapping.classification)?blockedMappings.add(mapping.upstreamTag):imported.subpath||blockedEcosystems.add(imported.ecosystem)}const edits=[],changes=[],warnings=[],usage={webawesome:{automatic:0,manual:0},shoelace:{automatic:0,manual:0}},automaticMappings=new Set,deepRegistrationMappings=new Set,reportedOptionalPeers=new Set,reportOrigin=(upstreamTag,origin)=>origin??(upstreamTag?.startsWith("wa-")?"webawesome":upstreamTag?.startsWith("sl-")?"shoelace":null),change=(offset,upstreamTag,upstreamMember,action,target,message,origin=null)=>{changes.push(reportEntry({textStarts:starts,file,offset,origin:reportOrigin(upstreamTag,origin),upstreamTag,upstreamMember,action,target,message}))},warn=(offset,upstreamTag,upstreamMember,code,target,message,origin=null,behaviorReviewFlags=null)=>{warnings.push(reportEntry({textStarts:starts,file,offset,origin:reportOrigin(upstreamTag,origin),upstreamTag,upstreamMember,action:"manual-review",target,warningCode:code,behaviorReviewFlags,message}))},noteRuntimeRequirements=(mapping,offset)=>{for(const peer of mapping.target?.optionalPeers??[]){const key=`${mapping.targetTag}:${peer}`;reportedOptionalPeers.has(key)||(reportedOptionalPeers.add(key),warn(offset,mapping.upstreamTag,"runtime","OPTIONAL_PEER_REQUIRED",peer,`${mapping.targetTag} requires the optional peer package ${peer}; install a compatible version before relying on the migrated component.`))}},noteAutomatic=(mapping,offset)=>{automaticMappings.add(mapping.upstreamTag),noteRuntimeRequirements(mapping,offset)},addEdit=(start,end,replacement,details)=>{edits.push({start,end,replacement}),change(start,details.upstreamTag,details.upstreamMember,details.action,details.target,details.message,details.origin)},isBlocked=mapping=>!!mapping&&(blockedMappings.has(mapping.upstreamTag)||registrationBlockedMappings.has(mapping.upstreamTag)||blockedEcosystems.has(mapping.upstream)),isBlockedAutomatic=mapping=>AUTO_CLASSIFICATIONS.has(mapping?.classification)&&isBlocked(mapping),isAutomatic=mapping=>AUTO_CLASSIFICATIONS.has(mapping?.classification)&&!isBlocked(mapping),blockedMessage=mapping=>registrationBlockedMappings.has(mapping.upstreamTag)?`${mapping.upstreamTag} remains unchanged because the scanned target set does not prove registration for ${mapping.targetTag}. Include ${targetImport(mapping.target)} in the scan or migrate the file that owns its supported root registration import.`:`${mapping.upstreamTag} remains unchanged because the scanned target set contains a use that requires manual member, default, or import review.`,blockedWarningCode=mapping=>registrationBlockedMappings.has(mapping.upstreamTag)?"REGISTRATION_CLOSURE_REQUIRED":"MAPPING_REVIEW_BLOCKED";for(const{mapping,uses}of aliasReviews.values())for(const use of uses)warn(use.offset,mapping.upstreamTag,use.upstreamMember,"ALIASED_MEMBER_REVIEW",use.target,use.message);for(const{mapping,uses}of dynamicDefaultReviews.values())for(const use of uses)warn(use.offset,mapping.upstreamTag,use.upstreamMember,"DYNAMIC_VALUE_REVIEW",use.target,use.message);for(const{reference,mapping,defaults,missing}of imperativeDefaultReviews)warn(reference.start,mapping.upstreamTag,null,"IMPERATIVE_DEFAULT_REVIEW",mapping.targetTag,`${mapping.upstreamTag} is constructed imperatively and requires explicit migrated defaults (${defaults.map(rule=>`${rule.member}=${String(rule.value)}`).join(", ")}); review the created element before renaming this mapping. No unconditional immediate assignment was proven for ${missing.map(rule=>rule.member).join(", ")}.`);const openingTokens=[],manualOpeningTags=new Set;for(const token of markupTokens){const mapping=contract.mappings.get(token.tag),ecosystem=ecosystemForTag(token.tag);if(!isAutomatic(mapping)){if(mapping&&noteRuntimeRequirements(mapping,token.nameStart),token.closing&&manualOpeningTags.has(token.tag))continue;token.closing||manualOpeningTags.add(token.tag),usage[ecosystem].manual+=1,warn(token.nameStart,token.tag,null,isBlockedAutomatic(mapping)?blockedWarningCode(mapping):warningCode(mapping),mapping?.targetTag??null,isBlockedAutomatic(mapping)?blockedMessage(mapping):mappingMessage(mapping),null,mapping?.parity?.behaviorReviewFlags);continue}usage[ecosystem].automatic+=1,noteAutomatic(mapping,token.nameStart),addEdit(token.nameStart,token.nameEnd,mapping.targetTag,{upstreamTag:token.tag,upstreamMember:null,action:"rewrite-tag",target:mapping.targetTag,message:`Rename ${token.tag} to ${mapping.targetTag}.`});const behaviorReview=conditionalBehaviorReviews.get(token.start);behaviorReview&&warn(token.nameStart,mapping.upstreamTag,null,"BEHAVIOR_REVIEW_REQUIRED",mapping.targetTag,behaviorReview.message,null,behaviorReview.flags),!token.closing&&REDUCED_MOTION_REVIEW_TAGS.has(token.tag)&&warn(token.nameStart,mapping.upstreamTag,null,"BEHAVIOR_REVIEW_REQUIRED",mapping.targetTag,reducedMotionReviewMessage(mapping.upstreamTag),null,["reduced-motion-default"]),!token.closing&&ICON_VOCABULARY_REVIEW_TAGS.has(token.tag)&&warn(token.nameStart,mapping.upstreamTag,null,"BEHAVIOR_REVIEW_REQUIRED",mapping.targetTag,iconVocabularyReviewMessage(mapping.upstreamTag),null,["icon-name-vocabulary"]),token.closing||openingTokens.push({token,mapping,attributes:parseTagAttributes(original,token)})}for(const reference of apiReferences){const mapping=contract.mappings.get(reference.tag),ecosystem=ecosystemForTag(reference.tag);if(!isAutomatic(mapping)){mapping&&noteRuntimeRequirements(mapping,reference.start),usage[ecosystem].manual+=1,warn(reference.start,reference.tag,null,isBlockedAutomatic(mapping)?blockedWarningCode(mapping):warningCode(mapping),mapping?.targetTag??null,isBlockedAutomatic(mapping)?blockedMessage(mapping):mappingMessage(mapping),null,mapping?.parity?.behaviorReviewFlags);continue}usage[ecosystem].automatic+=1,noteAutomatic(mapping,reference.start),addEdit(reference.start,reference.end,mapping.targetTag,{upstreamTag:reference.tag,upstreamMember:null,action:"rewrite-tag",target:mapping.targetTag,message:`Rename ${reference.tag} to ${mapping.targetTag}.`}),REDUCED_MOTION_REVIEW_TAGS.has(reference.tag)&&warn(reference.start,mapping.upstreamTag,null,"BEHAVIOR_REVIEW_REQUIRED",mapping.targetTag,reducedMotionReviewMessage(mapping.upstreamTag),null,["reduced-motion-default"]),ICON_VOCABULARY_REVIEW_TAGS.has(reference.tag)&&warn(reference.start,mapping.upstreamTag,null,"BEHAVIOR_REVIEW_REQUIRED",mapping.targetTag,iconVocabularyReviewMessage(mapping.upstreamTag),null,["icon-name-vocabulary"])}for(const{token,mapping,attributes}of openingTokens){for(const attribute of attributes){const rule=namedRule(attribute.rawName,mapping);rule&&addEdit(attribute.nameStart,attribute.nameEnd,rule.replacement,{upstreamTag:mapping.upstreamTag,upstreamMember:rule.from,action:memberAction(rule.section),target:rule.to,message:`Rewrite ${rule.section} member ${rule.from} to ${rule.to}.`})}const insertions=[];for(const rule of mapping.rewrites.defaults){const variants=new Set([rule.member,kebabCase(rule.member),camelCase(rule.member)]),attribute=attributes.find(entry=>variants.has(entry.rawName.replace(/^[.?:]/,"")));if(rule.action==="insert-if-absent"){if(attribute)continue;insertions.push(`${kebabCase(rule.member)}="${String(rule.value).replaceAll("&","&amp;").replaceAll('"',"&quot;")}"`),change(token.end,mapping.upstreamTag,rule.member,"insert-default",`${rule.member}=${String(rule.value)}`,`Insert ${rule.member} to preserve the upstream default.`);continue}!attribute||attribute.valueKind==="boolean"||(isDynamicAttribute(attribute)?(usage[mapping.upstream].manual+=1,warn(attribute.valueStart,mapping.upstreamTag,rule.member,"DYNAMIC_VALUE_REVIEW",String(rule.to),`The ${rule.member} value is dynamic; replace ${String(rule.from)} with ${String(rule.to)} at its source.`)):attribute.value===String(rule.from)&&addEdit(attribute.valueStart,attribute.valueEnd,String(rule.to),{upstreamTag:mapping.upstreamTag,upstreamMember:rule.member,action:"replace-default",target:String(rule.to),message:`Replace ${rule.member} value ${String(rule.from)} with ${String(rule.to)}.`}))}if(insertions.length){const insertionOffset=original[token.end-1]==="/"?token.end-1:token.end;edits.push({start:insertionOffset,end:insertionOffset,replacement:` ${insertions.join(" ")}`})}}const stack=[],pairs=[];for(const token of markupTokens)if(!token.closing&&!token.selfClosing)stack.push(token);else if(token.closing)for(let index=stack.length-1;index>=0;index-=1){if(stack[index].tag!==token.tag)continue;const[opening]=stack.splice(index,1);pairs.push({opening,closing:token});break}for(const{opening,closing}of pairs){const mapping=contract.mappings.get(opening.tag);if(!(!isAutomatic(mapping)||!mapping.rewrites.slots.length)){for(const descendant of allOpeningTokens)if(!(descendant.start<=opening.end||descendant.end>=closing.start))for(const attribute of parseTagAttributes(original,descendant))if(attribute.rawName==="slot"&&attribute.valueKind==="literal"){const rule=mapping.rewrites.slots.find(entry=>entry.from===attribute.value);rule&&addEdit(attribute.valueStart,attribute.valueEnd,rule.to,{upstreamTag:mapping.upstreamTag,upstreamMember:rule.from,action:"rewrite-slot",target:rule.to,message:`Rewrite slot ${rule.from} to ${rule.to}.`})}else{const prefix=attribute.rawName.startsWith("v-slot:")?"v-slot:":attribute.rawName.startsWith("#")?"#":null;if(!prefix)continue;const name=attribute.rawName.slice(prefix.length),rule=mapping.rewrites.slots.find(entry=>entry.from===name);rule&&addEdit(attribute.nameStart,attribute.nameEnd,`${prefix}${rule.to}`,{upstreamTag:mapping.upstreamTag,upstreamMember:rule.from,action:"rewrite-slot",target:rule.to,message:`Rewrite slot ${rule.from} to ${rule.to}.`})}}}const cssRule=/(?<selector>[^{}]+)\{(?<body>[^{}]*)\}/g;for(const match of original.matchAll(cssRule)){if(insideRanges(match.index,ignoredRanges))continue;const selectorStart=match.index,bodyStart=match.index+match[0].indexOf(match.groups.body);for(const mapping of contract.mappings.values()){const tagPattern=new RegExp(`(?<![a-z0-9-])${mapping.upstreamTag}(?![a-z0-9-])`,"g"),selectorMatches=[...match.groups.selector.matchAll(tagPattern)].filter(tagMatch=>{const offset=selectorStart+tagMatch.index;if(insideRanges(offset,ignoredRanges))return!1;const quoted=quotedRangeContaining(original,offset);return quoted?insideRanges(offset,htmlStyleRanges)&&!insideTemplateExpression(original,quoted[0],offset):!0});if(selectorMatches.length){if(!isAutomatic(mapping)){usage[mapping.upstream].manual+=selectorMatches.length;for(const tagMatch of selectorMatches){const offset=selectorStart+tagMatch.index;noteRuntimeRequirements(mapping,offset),warn(offset,mapping.upstreamTag,null,isBlockedAutomatic(mapping)?blockedWarningCode(mapping):warningCode(mapping),mapping.targetTag,isBlockedAutomatic(mapping)?blockedMessage(mapping):mappingMessage(mapping),null,mapping?.parity?.behaviorReviewFlags)}continue}for(const tagMatch of selectorMatches){const offset=selectorStart+tagMatch.index;if(noteAutomatic(mapping,offset),mapping.upstreamTag==="wa-random-content"){const flags=["host-layout","multi-item-layout"];warn(offset,mapping.upstreamTag,"selector","BEHAVIOR_REVIEW_REQUIRED",mapping.targetTag,"Review this selector after migration: Web Awesome lays candidates out through the host, while Lyra uses a block host and owns simultaneous-item flex layout inside its shadow base.",null,flags)}addEdit(offset,offset+mapping.upstreamTag.length,mapping.targetTag,{upstreamTag:mapping.upstreamTag,upstreamMember:null,action:"rewrite-tag",target:mapping.targetTag,message:`Rename ${mapping.upstreamTag} selector to ${mapping.targetTag}.`})}for(const rule of mapping.rewrites.parts){const pattern=new RegExp(`(?<=::part\\()${rule.from.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}(?=\\))`,"g");for(const partMatch of match.groups.selector.matchAll(pattern)){const offset=selectorStart+partMatch.index;insideRanges(offset,ignoredRanges)||addEdit(offset,offset+rule.from.length,rule.to,{upstreamTag:mapping.upstreamTag,upstreamMember:rule.from,action:"rewrite-part",target:rule.to,message:`Rewrite CSS part ${rule.from} to ${rule.to}.`})}}for(const rule of mapping.rewrites.cssProperties){const pattern=new RegExp(`${rule.from.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}(?![a-z0-9-])`,"g");for(const propertyMatch of match.groups.body.matchAll(pattern)){const offset=bodyStart+propertyMatch.index;insideRanges(offset,ignoredRanges)||addEdit(offset,offset+rule.from.length,rule.to,{upstreamTag:mapping.upstreamTag,upstreamMember:rule.from,action:"rewrite-css-property",target:rule.to,message:`Rewrite CSS custom property ${rule.from} to ${rule.to}.`})}}}}}for(const mapping of contract.mappings.values()){if(!isAutomatic(mapping))continue;const owner=`(?:document\\.)?querySelector(?:<[^>\\n]+>)?\\(\\s*(['"])${mapping.upstreamTag.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\1\\s*\\)`;for(const[section,suffix]of[["methods","(?=\\s*\\()"],["properties","(?=\\s*(?:=|\\.|\\?|;|$))"]])for(const rule of mapping.rewrites[section]){const escaped=rule.from.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),regex=new RegExp(`${owner}\\s*(?:\\?\\.|!\\s*\\.|\\.)\\s*(?<member>${escaped})${suffix}`,"g");for(const memberMatch of original.matchAll(regex)){if(insideRanges(memberMatch.index,ignoredRanges))continue;const relative=memberMatch[0].lastIndexOf(memberMatch.groups.member),offset=memberMatch.index+relative;addEdit(offset,offset+rule.from.length,rule.to,{upstreamTag:mapping.upstreamTag,upstreamMember:rule.from,action:memberAction(section),target:rule.to,message:`Rewrite ${section} member ${rule.from} to ${rule.to}.`})}}for(const rule of mapping.rewrites.events){const escaped=rule.from.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),regex=new RegExp(`${owner}\\s*(?:\\?\\.|!\\s*\\.|\\.)\\s*(?:add|remove)EventListener\\(\\s*(['"])(?<member>${escaped})\\2`,"g");for(const eventMatch of original.matchAll(regex)){if(insideRanges(eventMatch.index,ignoredRanges))continue;const relative=eventMatch[0].lastIndexOf(eventMatch.groups.member),offset=eventMatch.index+relative;addEdit(offset,offset+rule.from.length,rule.to,{upstreamTag:mapping.upstreamTag,upstreamMember:rule.from,action:"rewrite-event",target:rule.to,message:`Rewrite event ${rule.from} to ${rule.to}.`})}}}for(const imported of imports){const{ecosystem}=imported;if(!imported.sideEffect){usage[ecosystem].manual+=1,warn(imported.start,imported.subpath?deepImportTag(imported.specifier,ecosystem):null,"module","IMPORT_BINDING_REVIEW_REQUIRED",null,`The ${imported.kind} import has runtime/type bindings whose exported names cannot be inferred safely.`,ecosystem);continue}if(!imported.subpath){const canRewrite=rewriteBarePackages.has(ecosystem)||rewriteBarePackages.has(imported.packageName),registeredMappings=rootRegistrationMappings.get(imported.packageName)??rootRegistrationMappings.get(ecosystem)??new Set,closure=registrationClosure(contract,registeredMappings);if(canRewrite&&closure.length>0){addEdit(imported.start,imported.end,closure[0],{upstreamTag:null,origin:ecosystem,upstreamMember:"module",action:"rewrite-import",target:closure[0],message:`Rewrite ${imported.packageName} root registration import to its proven Lyra registration closure.`});const lineEnding=original.includes(`\r
1
+ import{AUTO_CLASSIFICATIONS,invariant}from"./migration-contract.mjs";import{assessImperativeDefaults,camelCase,commentRanges,ecosystemForTag,finalizeEdits,htmlTemplateStyleRanges,insideRanges,insideTemplateExpression,isDynamicAttribute,kebabCase,lineStarts,localMigrationKey,namedRule,normalizedAttributeName,originLabel,pairedMarkupTokens,parseTagAttributes,quotedRangeContaining,reportEntry,scanAllOpeningTags,scanApiTagReferences,scanDirectElementChildren,scanImports,scanLocalMigrationHazards,scanMarkupTags,serializeLocalDefault}from"./migration-analysis.mjs";import{migrateRenameText}from"./migration-renames.mjs";const REDUCED_MOTION_REVIEW_TAGS=new Set(["wa-animation","sl-animation","wa-animated-image","sl-animated-image"]),ICON_VOCABULARY_REVIEW_TAGS=new Set(["wa-icon","sl-icon","sl-icon-button"]);function reducedMotionReviewMessage(upstreamTag){return`${upstreamTag} does not freeze or snap its animation under prefers-reduced-motion: reduce. The migrated Lyra target respects prefers-reduced-motion by default and does exactly that automatically. Review whether the migrated element should keep animating for users who asked for less motion, or set ignore-reduced-motion to preserve upstream playback under that preference.`}function iconVocabularyReviewMessage(upstreamTag){return`${upstreamTag}'s icon-name vocabulary is not bundled with Lyra. The default library includes only add, check, close, search, menu, chevron-left, chevron-right, chevron-down, calendar, command, and trash; any other migrated name renders no glyph. Register the required vocabulary with registerIconLibrary('default', { resolver }) or replace each name explicitly.`}function warningCode(mapping){return mapping?mapping.classification==="warning-required"?"WARNING_REQUIRED":mapping.classification==="conceptual-only"?"CONCEPTUAL_MAPPING":"UNSUPPORTED_MAPPING":"UNKNOWN_UPSTREAM_TAG"}function scanAliasedRewriteReviews(text,contract,ignoredRanges){const reviews=new Map,addUse=(mapping,tagStart,alias,offset,upstreamMember,target,message)=>{const record=reviews.get(mapping.upstreamTag)??{mapping,aliases:[],uses:[]};reviews.has(mapping.upstreamTag)||reviews.set(mapping.upstreamTag,record),record.aliases.some(entry=>entry.tagStart===tagStart)||record.aliases.push({tagStart,alias});const key=`${offset}:${upstreamMember}:${target}`;record.uses.some(entry=>entry.key===key)||record.uses.push({key,offset,alias,upstreamMember,target,message})};for(const mapping of contract.mappings.values()){if(!AUTO_CLASSIFICATIONS.has(mapping.classification))continue;const escapedTag=mapping.upstreamTag.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),declaration=new RegExp(`\\b(?:const|let|var)\\s+(?<alias>[$A-Z_a-z][$\\w]*)\\s*=\\s*(?:document\\.)?querySelector(?:<[^>\\n]+>)?\\(\\s*(['"])${escapedTag}\\2\\s*\\)`,"g");for(const match of text.matchAll(declaration)){if(insideRanges(match.index,ignoredRanges))continue;const alias=match.groups.alias,tagStart=match.index+match[0].lastIndexOf(mapping.upstreamTag),afterDeclaration=match.index+match[0].length,owner=`(?<![$\\w])${alias.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\s*(?:\\?\\.|!\\s*\\.|\\.)\\s*`;for(const[section,suffix]of[["methods","(?=\\s*\\()"],["properties","(?=\\s*(?:=|\\.|\\?|;|$))"]])for(const rule of mapping.rewrites[section]){const member=rule.from.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),regex=new RegExp(`${owner}(?<member>${member})${suffix}`,"g");regex.lastIndex=afterDeclaration;for(let use=regex.exec(text);use;use=regex.exec(text)){const offset=use.index+use[0].lastIndexOf(use.groups.member);insideRanges(offset,ignoredRanges)||addUse(mapping,tagStart,alias,offset,rule.from,rule.to,`Aliased ${section} member ${alias}.${rule.from} must be migrated with its selector.`)}}for(const rule of mapping.rewrites.events){const member=rule.from.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),regex=new RegExp(`${owner}(?:add|remove)EventListener\\(\\s*(['"])(?<member>${member})\\1`,"g");regex.lastIndex=afterDeclaration;for(let use=regex.exec(text);use;use=regex.exec(text)){const offset=use.index+use[0].lastIndexOf(use.groups.member);insideRanges(offset,ignoredRanges)||addUse(mapping,tagStart,alias,offset,rule.from,rule.to,`Aliased event ${alias}.${rule.from} must be migrated with its selector.`)}}for(const rule of mapping.rewrites.attributes){const member=rule.from.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),regex=new RegExp(`${owner}(?:get|set|has|toggle|remove)Attribute\\(\\s*(['"])(?<member>${member})\\1`,"g");regex.lastIndex=afterDeclaration;for(let use=regex.exec(text);use;use=regex.exec(text)){const offset=use.index+use[0].lastIndexOf(use.groups.member);insideRanges(offset,ignoredRanges)||addUse(mapping,tagStart,alias,offset,rule.from,rule.to,`Aliased attribute ${alias}.${rule.from} must be migrated with its selector.`)}}}}return reviews}function scanDynamicDefaultReviews(text,markupTokens,contract){const reviews=new Map;for(const token of markupTokens){if(token.closing)continue;const mapping=contract.mappings.get(token.tag);if(!AUTO_CLASSIFICATIONS.has(mapping?.classification))continue;const attributes=parseTagAttributes(text,token);for(const rule of mapping.rewrites.defaults){if(rule.action!=="replace-value")continue;const variants=new Set([rule.member,kebabCase(rule.member),camelCase(rule.member)]),attribute=attributes.find(entry=>variants.has(entry.rawName.replace(/^[.?:]/,"")));if(!attribute||attribute.valueKind==="boolean"||!isDynamicAttribute(attribute))continue;const record=reviews.get(mapping.upstreamTag)??{mapping,uses:[]};reviews.has(mapping.upstreamTag)||reviews.set(mapping.upstreamTag,record),record.uses.push({offset:attribute.valueStart,upstreamMember:rule.member,target:String(rule.to),message:`The ${rule.member} value is dynamic; migrate ${String(rule.from)} to ${String(rule.to)} at its source before changing this component mapping.`})}}return reviews}const RANDOM_CONTENT_BEHAVIOR_MESSAGES=Object.freeze({"host-layout":"Web Awesome uses display: contents while Lyra uses a block host with a shadow base; review inline placement and host-authored layout CSS.","multi-item-layout":"Lyra owns the simultaneous-item flex/wrap/gap context inside its shadow base instead of leaving it on the host.","unique-retry-bound":"Web Awesome promises non-repeating unique selection while Lyra uses a bounded retry and can eventually repeat.","forwarded-slot-candidates":"Lyra flattens a direct forwarding slot into projected candidates while Web Awesome treats only its direct element child as the candidate.","autoplay-semantics":"Lyra stops autoplay for reduced motion, keeps timer ticks silent, does not pause on hover, and exposes a localized paused control/state/event absent upstream."});function scanConditionalBehaviorReviews(text,markupTokens,ignoredRanges,contract){const reviews=new Map,pairs=pairedMarkupTokens(markupTokens);for(const opening of markupTokens){if(opening.closing||opening.tag!=="wa-random-content")continue;const mapping=contract.mappings.get(opening.tag);if(!AUTO_CLASSIFICATIONS.has(mapping?.classification)||!mapping.parity.behaviorReviewFlags.length)continue;const attributes=parseTagAttributes(text,opening),attribute=name=>attributes.find(candidate=>normalizedAttributeName(candidate)===name),closing=pairs.get(opening.start),children=closing?scanDirectElementChildren(text,opening.end+1,closing.start,ignoredRanges):{directTags:[],dynamicDirectContent:!opening.selfClosing},flags=[];(children.directTags.length>0||children.dynamicDirectContent)&&flags.push("host-layout");const items=attribute("items"),dynamicItems=items?isDynamicAttribute(items):!1,itemCount=!items||items.value===null?1:Number(items.value);(dynamicItems||Number.isFinite(itemCount)&&itemCount>1)&&flags.push("multi-item-layout");const mode=attribute("mode"),dynamicMode=mode?isDynamicAttribute(mode):!1,effectiveItems=Number.isFinite(itemCount)?Math.max(1,Math.trunc(itemCount)):1;(!mode||dynamicMode||mode.value==="unique")&&(dynamicMode||children.dynamicDirectContent||children.directTags.length>effectiveItems)&&flags.push("unique-retry-bound"),(children.directTags.includes("slot")||children.dynamicDirectContent)&&flags.push("forwarded-slot-candidates"),attribute("autoplay")&&flags.push("autoplay-semantics");const uniqueFlags=flags.filter((flag,index)=>mapping.parity.behaviorReviewFlags.includes(flag)&&flags.indexOf(flag)===index);uniqueFlags.length&&reviews.set(opening.start,{flags:uniqueFlags,message:`Review ${mapping.upstreamTag} before relying on the migrated behavior: `+uniqueFlags.map(flag=>`${flag}: ${RANDOM_CONTENT_BEHAVIOR_MESSAGES[flag]}`).join(" ")})}return reviews}function memberAction(section){return`rewrite-${{attributes:"attribute",properties:"property",events:"event",slots:"slot",parts:"part",cssProperties:"css-property",methods:"method"}[section]}`}function targetImport(component){return typeof component.registrationRoute=="string"?`@aceshooting/lyra-ui/${component.registrationRoute.slice(2)}`:`@aceshooting/lyra-ui/${component.registrationModule.replace(/^src\//,"").replace(/\.ts$/,".js")}`}function registrationClosure(contract,upstreamTags){const targets=new Map;for(const upstreamTag of upstreamTags??[]){const mapping=contract.mappings.get(upstreamTag);!mapping||!AUTO_CLASSIFICATIONS.has(mapping.classification)||!mapping.target||targets.set(mapping.targetTag,mapping.target)}const rootIncluded=[...targets.values()].some(component=>component.rootIncluded!==!1),granular=[...targets.values()].filter(component=>component.rootIncluded===!1).map(targetImport).sort();return[...rootIncluded?["@aceshooting/lyra-ui/all.js"]:[],...granular]}function deepImportTag(specifier,ecosystem){const match=specifier.match(/\/components\/([a-z0-9-]+)\/(?:\1(?:\.component)?|index)\.(?:js|mjs)$/);return match?`${ecosystem==="webawesome"?"wa":"sl"}-${match[1]}`:null}function packageProvidesMapping(imported,mapping){return!!(mapping&&(!mapping.source?.tier||imported.tiers.has(mapping.source.tier)))}function mappingMessage(mapping){return mapping?mapping.rationale||`The ${mapping.classification} mapping requires manual review.`:"No pinned inventory mapping exists for this upstream tag."}function localAttributeName(rawName){return rawName.replace(/^(?:v-bind:|bind:|[.?:])/,"").toLowerCase()}function migrateLocalText(original,contract,options){const file=options.file??"<memory>",origin=options.origin,profiles=contract.localMigrations.get(origin);invariant(profiles,`unknown local migration origin ${String(origin)}`);const starts=lineStarts(original),ignoredRanges=commentRanges(original),openingTokens=scanAllOpeningTags(original,ignoredRanges).filter(token=>profiles.has(token.tag)),hazards=scanLocalMigrationHazards(original,profiles,ignoredRanges,openingTokens),blocked=new Set([...options.blockedLocalMigrations??[],...hazards.blocked]),edits=[],changes=[],warnings=[],entry=(offset,tag,action,target,message,warningCode2=null,upstreamMember=null)=>reportEntry({textStarts:starts,file,offset,origin,upstreamTag:tag,upstreamMember,action,target,warningCode:warningCode2,message});for(const use of hazards.uses)warnings.push(entry(use.offset,use.tag,"manual-review",use.tag,use.message,use.warningCode));for(const token of openingTokens){const profile=profiles.get(token.tag),key=localMigrationKey(origin,token.tag);if(blocked.has(key)){warnings.push(entry(token.nameStart,token.tag,"manual-review",token.tag,`${token.tag} remains unchanged because the scanned target set contains an aliased or dynamic value requiring review.`,"MAPPING_REVIEW_BLOCKED"));continue}const attributes=parseTagAttributes(original,token),present=new Set(attributes.map(attribute=>localAttributeName(attribute.rawName))),insertions=[];for(const rule of profile.defaults)present.has(rule.member.toLowerCase())||(insertions.push(serializeLocalDefault(rule)),changes.push(entry(token.end,token.tag,"insert-default",`${rule.member}=${String(rule.value)}`,`Insert ${rule.member} to preserve the Lyra ${originLabel(origin)} default.`,null,rule.member)));if(insertions.length>0){const insertionOffset=original[token.end-1]==="/"?token.end-1:token.end;edits.push({start:insertionOffset,end:insertionOffset,replacement:` ${insertions.join(" ")}`})}}return changes.sort((left,right)=>left.line-right.line||left.column-right.column||left.action.localeCompare(right.action)),warnings.sort((left,right)=>left.line-right.line||left.column-right.column||left.warningCode.localeCompare(right.warningCode)),{content:finalizeEdits(original,edits),changes,warnings,usage:{webawesome:{automatic:0,manual:0},shoelace:{automatic:0,manual:0}},blockedMappings:new Set,blockedEcosystems:new Set,bareImportEcosystems:new Set,blockedLocalMigrations:blocked}}function scanUnrewrittenUpstreamReferences(text){const found=[],seen=new Set,push=(offset,upstreamTag,member,code,target,message)=>{const key=`${offset}:${code}`;seen.has(key)||(seen.add(key),found.push({offset,upstreamTag,member,code,target,message}))},TAG=/(?<![\w-])(?<tag>(?:wa|sl)-[a-z][a-z0-9-]*)(?![\w-])/g;for(const block of text.matchAll(/\bcss`/g)){const bodyStart=block.index+block[0].length,bodyEnd=text.indexOf("`",bodyStart);if(bodyEnd===-1)continue;const body=text.slice(bodyStart,bodyEnd);for(const match of body.matchAll(TAG)){const tag=match.groups.tag;push(bodyStart+match.index,tag,"css-template","CSS_IN_JS_SELECTOR_REVIEW",tag,`\`${tag}\` appears in a css\`\` template and was NOT rewritten. A rule keyed on a tag that no longer exists matches nothing, silently. Rename the selector by hand.`)}}for(const match of text.matchAll(/::slotted\(\s*([^)]*)\)/g))for(const inner of match[1].matchAll(TAG)){const tag=inner.groups.tag;push(match.index+match[0].indexOf(inner.groups.tag),tag,"::slotted","SLOTTED_SELECTOR_REVIEW",tag,`\`::slotted(${tag})\` was NOT rewritten and will match nothing after the migration. Rename it by hand.`)}const CALL=/\bthis(?:\s*\??\.\s*(?:shadowRoot|renderRoot))?\s*\??\.\s*(?:querySelector|querySelectorAll|closest|matches)\s*(?:<[^>\n]+>)?\(\s*(['"`])(?<selector>[^'"`]*)\1/g;for(const match of text.matchAll(CALL)){const selectorOffset=match.index+match[0].lastIndexOf(match.groups.selector);for(const inner of match.groups.selector.matchAll(TAG)){const tag=inner.groups.tag;push(selectorOffset+inner.index,tag,"selector-string","SELECTOR_STRING_REVIEW",tag,`\`${tag}\` appears in a DOM selector string that was NOT rewritten. The query will return null after the migration rather than throwing. Rename it by hand.`)}}const declaredHere=new Set([...text.matchAll(/(--(?:wa|sl)-[a-z0-9-]+)\s*:/g)].map(match=>match[1]));for(const match of text.matchAll(/--(?<ecosystem>wa|sl)-[a-z0-9-]+/g)){const token=match[0];declaredHere.has(token)||push(match.index,`${match.groups.ecosystem}-tokens`,"custom-property","UPSTREAM_TOKEN_REVIEW",token,`\`${token}\` is an upstream design token with no automatic Lyra equivalent, and was left as-is. A var() naming a token that no longer exists falls back to its second argument, or to nothing -- silently. Map it by hand against llms/tokens.md; note the spacing scales are offset by one step, so renaming by name alone tightens every gap.`)}return found}function migrateText(original,contract,options={}){if(options.origin&&contract.renameProfiles?.has(options.origin))return migrateRenameText(original,contract,options);if(options.origin)return migrateLocalText(original,contract,options);const file=options.file??"<memory>",rewriteBarePackages=options.rewriteBarePackages??new Set,rootRegistrationMappings=options.rootRegistrationMappings??new Map,starts=lineStarts(original),ignoredRanges=commentRanges(original),htmlStyleRanges=htmlTemplateStyleRanges(original),markupTokens=scanMarkupTags(original,ignoredRanges),allOpeningTokens=scanAllOpeningTags(original,ignoredRanges),apiReferences=scanApiTagReferences(original,ignoredRanges,options.domFactoryBindings),aliasReviews=scanAliasedRewriteReviews(original,contract,ignoredRanges),dynamicDefaultReviews=scanDynamicDefaultReviews(original,markupTokens,contract),conditionalBehaviorReviews=scanConditionalBehaviorReviews(original,markupTokens,ignoredRanges,contract),imports=scanImports(original,ignoredRanges,contract),bareImportPackages=new Set(imports.filter(imported=>imported.sideEffect&&!imported.subpath).map(imported=>imported.packageName)),blockedMappings=new Set([...options.blockedMappings??[],...aliasReviews.keys(),...dynamicDefaultReviews.keys()]),imperativeDefaultReviews=apiReferences.flatMap(reference=>{if(reference.kind!=="construction")return[];const mapping=contract.mappings.get(reference.tag),defaults=mapping?.rewrites.defaults.filter(rule=>rule.action==="insert-if-absent")??[];if(!mapping||!AUTO_CLASSIFICATIONS.has(mapping.classification)||defaults.length===0)return[];const assessment=assessImperativeDefaults(original,reference,defaults);return assessment.safe?[]:(blockedMappings.add(mapping.upstreamTag),[{reference,mapping,defaults,missing:assessment.missing}])}),registrationBlockedMappings=new Set(options.registrationBlockedMappings??[]),blockedEcosystems=new Set(options.blockedEcosystems??[]);for(const imported of imports){if(imported.sideEffect)continue;const upstreamTag=imported.subpath?deepImportTag(imported.specifier,imported.ecosystem):null,mapping=upstreamTag?contract.mappings.get(upstreamTag):null;mapping&&AUTO_CLASSIFICATIONS.has(mapping.classification)?blockedMappings.add(mapping.upstreamTag):imported.subpath||blockedEcosystems.add(imported.ecosystem)}const edits=[],changes=[],warnings=[],usage={webawesome:{automatic:0,manual:0},shoelace:{automatic:0,manual:0}},automaticMappings=new Set,deepRegistrationMappings=new Set,reportedOptionalPeers=new Set,reportOrigin=(upstreamTag,origin)=>origin??(upstreamTag?.startsWith("wa-")?"webawesome":upstreamTag?.startsWith("sl-")?"shoelace":null),change=(offset,upstreamTag,upstreamMember,action,target,message,origin=null)=>{changes.push(reportEntry({textStarts:starts,file,offset,origin:reportOrigin(upstreamTag,origin),upstreamTag,upstreamMember,action,target,message}))},warn=(offset,upstreamTag,upstreamMember,code,target,message,origin=null,behaviorReviewFlags=null)=>{warnings.push(reportEntry({textStarts:starts,file,offset,origin:reportOrigin(upstreamTag,origin),upstreamTag,upstreamMember,action:"manual-review",target,warningCode:code,behaviorReviewFlags,message}))},noteRuntimeRequirements=(mapping,offset)=>{for(const peer of mapping.target?.optionalPeers??[]){const key=`${mapping.targetTag}:${peer}`;reportedOptionalPeers.has(key)||(reportedOptionalPeers.add(key),warn(offset,mapping.upstreamTag,"runtime","OPTIONAL_PEER_REQUIRED",peer,`${mapping.targetTag} requires the optional peer package ${peer}; install a compatible version before relying on the migrated component.`))}},noteAutomatic=(mapping,offset)=>{automaticMappings.add(mapping.upstreamTag),noteRuntimeRequirements(mapping,offset)},addEdit=(start,end,replacement,details)=>{edits.push({start,end,replacement}),change(start,details.upstreamTag,details.upstreamMember,details.action,details.target,details.message,details.origin)},isBlocked=mapping=>!!mapping&&(blockedMappings.has(mapping.upstreamTag)||registrationBlockedMappings.has(mapping.upstreamTag)||blockedEcosystems.has(mapping.upstream)),isBlockedAutomatic=mapping=>AUTO_CLASSIFICATIONS.has(mapping?.classification)&&isBlocked(mapping),isAutomatic=mapping=>AUTO_CLASSIFICATIONS.has(mapping?.classification)&&!isBlocked(mapping),blockedMessage=mapping=>registrationBlockedMappings.has(mapping.upstreamTag)?`${mapping.upstreamTag} remains unchanged because the scanned target set does not prove registration for ${mapping.targetTag}. Include ${targetImport(mapping.target)} in the scan or migrate the file that owns its supported root registration import.`:`${mapping.upstreamTag} remains unchanged because the scanned target set contains a use that requires manual member, default, or import review.`,blockedWarningCode=mapping=>registrationBlockedMappings.has(mapping.upstreamTag)?"REGISTRATION_CLOSURE_REQUIRED":"MAPPING_REVIEW_BLOCKED";for(const{mapping,uses}of aliasReviews.values())for(const use of uses)warn(use.offset,mapping.upstreamTag,use.upstreamMember,"ALIASED_MEMBER_REVIEW",use.target,use.message);for(const{mapping,uses}of dynamicDefaultReviews.values())for(const use of uses)warn(use.offset,mapping.upstreamTag,use.upstreamMember,"DYNAMIC_VALUE_REVIEW",use.target,use.message);for(const{reference,mapping,defaults,missing}of imperativeDefaultReviews)warn(reference.start,mapping.upstreamTag,null,"IMPERATIVE_DEFAULT_REVIEW",mapping.targetTag,`${mapping.upstreamTag} is constructed imperatively and requires explicit migrated defaults (${defaults.map(rule=>`${rule.member}=${String(rule.value)}`).join(", ")}); review the created element before renaming this mapping. No unconditional immediate assignment was proven for ${missing.map(rule=>rule.member).join(", ")}.`);const openingTokens=[],manualOpeningTags=new Set;for(const token of markupTokens){const mapping=contract.mappings.get(token.tag),ecosystem=ecosystemForTag(token.tag);if(!isAutomatic(mapping)){if(mapping&&noteRuntimeRequirements(mapping,token.nameStart),token.closing&&manualOpeningTags.has(token.tag))continue;token.closing||manualOpeningTags.add(token.tag),usage[ecosystem].manual+=1,warn(token.nameStart,token.tag,null,isBlockedAutomatic(mapping)?blockedWarningCode(mapping):warningCode(mapping),mapping?.targetTag??null,isBlockedAutomatic(mapping)?blockedMessage(mapping):mappingMessage(mapping),null,mapping?.parity?.behaviorReviewFlags);continue}usage[ecosystem].automatic+=1,noteAutomatic(mapping,token.nameStart),addEdit(token.nameStart,token.nameEnd,mapping.targetTag,{upstreamTag:token.tag,upstreamMember:null,action:"rewrite-tag",target:mapping.targetTag,message:`Rename ${token.tag} to ${mapping.targetTag}.`});const behaviorReview=conditionalBehaviorReviews.get(token.start);behaviorReview&&warn(token.nameStart,mapping.upstreamTag,null,"BEHAVIOR_REVIEW_REQUIRED",mapping.targetTag,behaviorReview.message,null,behaviorReview.flags),!token.closing&&REDUCED_MOTION_REVIEW_TAGS.has(token.tag)&&warn(token.nameStart,mapping.upstreamTag,null,"BEHAVIOR_REVIEW_REQUIRED",mapping.targetTag,reducedMotionReviewMessage(mapping.upstreamTag),null,["reduced-motion-default"]),!token.closing&&ICON_VOCABULARY_REVIEW_TAGS.has(token.tag)&&warn(token.nameStart,mapping.upstreamTag,null,"BEHAVIOR_REVIEW_REQUIRED",mapping.targetTag,iconVocabularyReviewMessage(mapping.upstreamTag),null,["icon-name-vocabulary"]),token.closing||openingTokens.push({token,mapping,attributes:parseTagAttributes(original,token)})}for(const reference of apiReferences){const mapping=contract.mappings.get(reference.tag),ecosystem=ecosystemForTag(reference.tag);if(!isAutomatic(mapping)){mapping&&noteRuntimeRequirements(mapping,reference.start),usage[ecosystem].manual+=1,warn(reference.start,reference.tag,null,isBlockedAutomatic(mapping)?blockedWarningCode(mapping):warningCode(mapping),mapping?.targetTag??null,isBlockedAutomatic(mapping)?blockedMessage(mapping):mappingMessage(mapping),null,mapping?.parity?.behaviorReviewFlags);continue}usage[ecosystem].automatic+=1,noteAutomatic(mapping,reference.start),addEdit(reference.start,reference.end,mapping.targetTag,{upstreamTag:reference.tag,upstreamMember:null,action:"rewrite-tag",target:mapping.targetTag,message:`Rename ${reference.tag} to ${mapping.targetTag}.`}),REDUCED_MOTION_REVIEW_TAGS.has(reference.tag)&&warn(reference.start,mapping.upstreamTag,null,"BEHAVIOR_REVIEW_REQUIRED",mapping.targetTag,reducedMotionReviewMessage(mapping.upstreamTag),null,["reduced-motion-default"]),ICON_VOCABULARY_REVIEW_TAGS.has(reference.tag)&&warn(reference.start,mapping.upstreamTag,null,"BEHAVIOR_REVIEW_REQUIRED",mapping.targetTag,iconVocabularyReviewMessage(mapping.upstreamTag),null,["icon-name-vocabulary"])}for(const{token,mapping,attributes}of openingTokens){for(const attribute of attributes){const rule=namedRule(attribute.rawName,mapping);rule&&addEdit(attribute.nameStart,attribute.nameEnd,rule.replacement,{upstreamTag:mapping.upstreamTag,upstreamMember:rule.from,action:memberAction(rule.section),target:rule.to,message:`Rewrite ${rule.section} member ${rule.from} to ${rule.to}.`})}const insertions=[];for(const rule of mapping.rewrites.defaults){const variants=new Set([rule.member,kebabCase(rule.member),camelCase(rule.member)]),attribute=attributes.find(entry=>variants.has(entry.rawName.replace(/^[.?:]/,"")));if(rule.action==="insert-if-absent"){if(attribute)continue;insertions.push(`${kebabCase(rule.member)}="${String(rule.value).replaceAll("&","&amp;").replaceAll('"',"&quot;")}"`),change(token.end,mapping.upstreamTag,rule.member,"insert-default",`${rule.member}=${String(rule.value)}`,`Insert ${rule.member} to preserve the upstream default.`);continue}!attribute||attribute.valueKind==="boolean"||(isDynamicAttribute(attribute)?(usage[mapping.upstream].manual+=1,warn(attribute.valueStart,mapping.upstreamTag,rule.member,"DYNAMIC_VALUE_REVIEW",String(rule.to),`The ${rule.member} value is dynamic; replace ${String(rule.from)} with ${String(rule.to)} at its source.`)):attribute.value===String(rule.from)&&addEdit(attribute.valueStart,attribute.valueEnd,String(rule.to),{upstreamTag:mapping.upstreamTag,upstreamMember:rule.member,action:"replace-default",target:String(rule.to),message:`Replace ${rule.member} value ${String(rule.from)} with ${String(rule.to)}.`}))}if(insertions.length){const insertionOffset=original[token.end-1]==="/"?token.end-1:token.end;edits.push({start:insertionOffset,end:insertionOffset,replacement:` ${insertions.join(" ")}`})}}const stack=[],pairs=[];for(const token of markupTokens)if(!token.closing&&!token.selfClosing)stack.push(token);else if(token.closing)for(let index=stack.length-1;index>=0;index-=1){if(stack[index].tag!==token.tag)continue;const[opening]=stack.splice(index,1);pairs.push({opening,closing:token});break}for(const{opening,closing}of pairs){const mapping=contract.mappings.get(opening.tag);if(!(!isAutomatic(mapping)||!mapping.rewrites.slots.length)){for(const descendant of allOpeningTokens)if(!(descendant.start<=opening.end||descendant.end>=closing.start))for(const attribute of parseTagAttributes(original,descendant))if(attribute.rawName==="slot"&&attribute.valueKind==="literal"){const rule=mapping.rewrites.slots.find(entry=>entry.from===attribute.value);rule&&addEdit(attribute.valueStart,attribute.valueEnd,rule.to,{upstreamTag:mapping.upstreamTag,upstreamMember:rule.from,action:"rewrite-slot",target:rule.to,message:`Rewrite slot ${rule.from} to ${rule.to}.`})}else{const prefix=attribute.rawName.startsWith("v-slot:")?"v-slot:":attribute.rawName.startsWith("#")?"#":null;if(!prefix)continue;const name=attribute.rawName.slice(prefix.length),rule=mapping.rewrites.slots.find(entry=>entry.from===name);rule&&addEdit(attribute.nameStart,attribute.nameEnd,`${prefix}${rule.to}`,{upstreamTag:mapping.upstreamTag,upstreamMember:rule.from,action:"rewrite-slot",target:rule.to,message:`Rewrite slot ${rule.from} to ${rule.to}.`})}}}const cssRule=/(?<selector>[^{}]+)\{(?<body>[^{}]*)\}/g;for(const match of original.matchAll(cssRule)){if(insideRanges(match.index,ignoredRanges))continue;const selectorStart=match.index,bodyStart=match.index+match[0].indexOf(match.groups.body);for(const mapping of contract.mappings.values()){const tagPattern=new RegExp(`(?<![a-z0-9-])${mapping.upstreamTag}(?![a-z0-9-])`,"g"),selectorMatches=[...match.groups.selector.matchAll(tagPattern)].filter(tagMatch=>{const offset=selectorStart+tagMatch.index;if(insideRanges(offset,ignoredRanges))return!1;const quoted=quotedRangeContaining(original,offset);return quoted?insideRanges(offset,htmlStyleRanges)&&!insideTemplateExpression(original,quoted[0],offset):!0});if(selectorMatches.length){if(!isAutomatic(mapping)){usage[mapping.upstream].manual+=selectorMatches.length;for(const tagMatch of selectorMatches){const offset=selectorStart+tagMatch.index;noteRuntimeRequirements(mapping,offset),warn(offset,mapping.upstreamTag,null,isBlockedAutomatic(mapping)?blockedWarningCode(mapping):warningCode(mapping),mapping.targetTag,isBlockedAutomatic(mapping)?blockedMessage(mapping):mappingMessage(mapping),null,mapping?.parity?.behaviorReviewFlags)}continue}for(const tagMatch of selectorMatches){const offset=selectorStart+tagMatch.index;if(noteAutomatic(mapping,offset),mapping.upstreamTag==="wa-random-content"){const flags=["host-layout","multi-item-layout"];warn(offset,mapping.upstreamTag,"selector","BEHAVIOR_REVIEW_REQUIRED",mapping.targetTag,"Review this selector after migration: Web Awesome lays candidates out through the host, while Lyra uses a block host and owns simultaneous-item flex layout inside its shadow base.",null,flags)}addEdit(offset,offset+mapping.upstreamTag.length,mapping.targetTag,{upstreamTag:mapping.upstreamTag,upstreamMember:null,action:"rewrite-tag",target:mapping.targetTag,message:`Rename ${mapping.upstreamTag} selector to ${mapping.targetTag}.`})}for(const rule of mapping.rewrites.parts){const pattern=new RegExp(`(?<=::part\\()${rule.from.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}(?=\\))`,"g");for(const partMatch of match.groups.selector.matchAll(pattern)){const offset=selectorStart+partMatch.index;insideRanges(offset,ignoredRanges)||addEdit(offset,offset+rule.from.length,rule.to,{upstreamTag:mapping.upstreamTag,upstreamMember:rule.from,action:"rewrite-part",target:rule.to,message:`Rewrite CSS part ${rule.from} to ${rule.to}.`})}}for(const rule of mapping.rewrites.cssProperties){const pattern=new RegExp(`${rule.from.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}(?![a-z0-9-])`,"g");for(const propertyMatch of match.groups.body.matchAll(pattern)){const offset=bodyStart+propertyMatch.index;insideRanges(offset,ignoredRanges)||addEdit(offset,offset+rule.from.length,rule.to,{upstreamTag:mapping.upstreamTag,upstreamMember:rule.from,action:"rewrite-css-property",target:rule.to,message:`Rewrite CSS custom property ${rule.from} to ${rule.to}.`})}}}}}for(const mapping of contract.mappings.values()){if(!isAutomatic(mapping))continue;const owner=`(?:document\\.)?querySelector(?:<[^>\\n]+>)?\\(\\s*(['"])${mapping.upstreamTag.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\1\\s*\\)`;for(const[section,suffix]of[["methods","(?=\\s*\\()"],["properties","(?=\\s*(?:=|\\.|\\?|;|$))"]])for(const rule of mapping.rewrites[section]){const escaped=rule.from.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),regex=new RegExp(`${owner}\\s*(?:\\?\\.|!\\s*\\.|\\.)\\s*(?<member>${escaped})${suffix}`,"g");for(const memberMatch of original.matchAll(regex)){if(insideRanges(memberMatch.index,ignoredRanges))continue;const relative=memberMatch[0].lastIndexOf(memberMatch.groups.member),offset=memberMatch.index+relative;addEdit(offset,offset+rule.from.length,rule.to,{upstreamTag:mapping.upstreamTag,upstreamMember:rule.from,action:memberAction(section),target:rule.to,message:`Rewrite ${section} member ${rule.from} to ${rule.to}.`})}}for(const rule of mapping.rewrites.events){const escaped=rule.from.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),regex=new RegExp(`${owner}\\s*(?:\\?\\.|!\\s*\\.|\\.)\\s*(?:add|remove)EventListener\\(\\s*(['"])(?<member>${escaped})\\2`,"g");for(const eventMatch of original.matchAll(regex)){if(insideRanges(eventMatch.index,ignoredRanges))continue;const relative=eventMatch[0].lastIndexOf(eventMatch.groups.member),offset=eventMatch.index+relative;addEdit(offset,offset+rule.from.length,rule.to,{upstreamTag:mapping.upstreamTag,upstreamMember:rule.from,action:"rewrite-event",target:rule.to,message:`Rewrite event ${rule.from} to ${rule.to}.`})}}}for(const imported of imports){const{ecosystem}=imported;if(!imported.sideEffect){usage[ecosystem].manual+=1,warn(imported.start,imported.subpath?deepImportTag(imported.specifier,ecosystem):null,"module","IMPORT_BINDING_REVIEW_REQUIRED",null,`The ${imported.kind} import has runtime/type bindings whose exported names cannot be inferred safely.`,ecosystem);continue}if(!imported.subpath){const canRewrite=rewriteBarePackages.has(ecosystem)||rewriteBarePackages.has(imported.packageName),registeredMappings=rootRegistrationMappings.get(imported.packageName)??rootRegistrationMappings.get(ecosystem)??new Set,closure=registrationClosure(contract,registeredMappings);if(canRewrite&&closure.length>0){addEdit(imported.start,imported.end,closure[0],{upstreamTag:null,origin:ecosystem,upstreamMember:"module",action:"rewrite-import",target:closure[0],message:`Rewrite ${imported.packageName} root registration import to its proven Lyra registration closure.`});const lineEnding=original.includes(`\r
2
2
  `)?`\r
3
3
  `:`
4
- `;for(const specifier of closure.slice(1))edits.push({start:imported.statementEnd,end:imported.statementEnd,replacement:`${lineEnding}import ${imported.quote}${specifier}${imported.quote};`}),change(imported.start,null,"module","insert-registration",specifier,"Insert granular registration for a root-excluded Lyra target.",ecosystem)}else warn(imported.start,null,"module","PACKAGE_IMPORT_BLOCKED","@aceshooting/lyra-ui/all.js",`The ${ecosystem} package import remains because this scan contains manual uses or no proven registration closure.`,ecosystem);continue}const upstreamTag=deepImportTag(imported.specifier,ecosystem),mapping=upstreamTag?contract.mappings.get(upstreamTag):null;if(mapping&&!packageProvidesMapping(imported,mapping)){usage[ecosystem].manual+=1,noteRuntimeRequirements(mapping,imported.start),warn(imported.start,upstreamTag,"module","PACKAGE_TIER_MISMATCH",mapping.targetTag,`${imported.packageName} does not provide the ${mapping.source.tier} ${upstreamTag} registration entry; use a package identity that provides that tier before migrating it.`,ecosystem);continue}if(!upstreamTag||!isAutomatic(mapping)){usage[ecosystem].manual+=1,mapping&&noteRuntimeRequirements(mapping,imported.start),warn(imported.start,upstreamTag,"module",isBlockedAutomatic(mapping)?blockedWarningCode(mapping):mapping?warningCode(mapping):"UNRESOLVED_DEEP_IMPORT",mapping?.targetTag??null,isBlockedAutomatic(mapping)?blockedMessage(mapping):upstreamTag?mappingMessage(mapping):`No component registration entry can be derived from ${imported.specifier}.`,ecosystem,mapping?.parity?.behaviorReviewFlags);continue}usage[ecosystem].automatic+=1,noteAutomatic(mapping,imported.start),deepRegistrationMappings.add(mapping.upstreamTag);const target=targetImport(mapping.target);addEdit(imported.start,imported.end,target,{upstreamTag,upstreamMember:"module",action:"rewrite-import",target,message:`Rewrite the ${upstreamTag} registration entry to its inventory module.`})}const rewrittenRanges=edits.map(edit=>[edit.start,edit.end]),wasRewritten=offset=>rewrittenRanges.some(([start,end])=>offset>=start&&offset<end);for(const scan of scanUnrewrittenUpstreamReferences(original))wasRewritten(scan.offset)||warn(scan.offset,scan.upstreamTag,scan.member,scan.code,scan.target,scan.message);const content=finalizeEdits(original,edits);return changes.sort((left,right)=>left.line-right.line||left.column-right.column||left.action.localeCompare(right.action)),warnings.sort((left,right)=>left.line-right.line||left.column-right.column||left.warningCode.localeCompare(right.warningCode)),{content,changes,warnings,usage,blockedMappings,blockedEcosystems,bareImportPackages,automaticMappings,deepRegistrationMappings}}export{migrateText,scanUnrewrittenUpstreamReferences};
4
+ `;for(const specifier of closure.slice(1))edits.push({start:imported.statementEnd,end:imported.statementEnd,replacement:`${lineEnding}import ${imported.quote}${specifier}${imported.quote};`}),change(imported.start,null,"module","insert-registration",specifier,"Insert granular registration for a root-excluded Lyra target.",ecosystem)}else warn(imported.start,null,"module","PACKAGE_IMPORT_BLOCKED","@aceshooting/lyra-ui/all.js",`The ${ecosystem} package import remains because this scan contains manual uses or no proven registration closure.`,ecosystem);continue}const upstreamTag=deepImportTag(imported.specifier,ecosystem),mapping=upstreamTag?contract.mappings.get(upstreamTag):null;if(mapping&&!packageProvidesMapping(imported,mapping)){usage[ecosystem].manual+=1,noteRuntimeRequirements(mapping,imported.start),warn(imported.start,upstreamTag,"module","PACKAGE_TIER_MISMATCH",mapping.targetTag,`${imported.packageName} does not provide the ${mapping.source.tier} ${upstreamTag} registration entry; use a package identity that provides that tier before migrating it.`,ecosystem);continue}if(!upstreamTag||!isAutomatic(mapping)){usage[ecosystem].manual+=1,mapping&&noteRuntimeRequirements(mapping,imported.start),warn(imported.start,upstreamTag,"module",isBlockedAutomatic(mapping)?blockedWarningCode(mapping):mapping?warningCode(mapping):"UNRESOLVED_DEEP_IMPORT",mapping?.targetTag??null,isBlockedAutomatic(mapping)?blockedMessage(mapping):upstreamTag?mappingMessage(mapping):`No component registration entry can be derived from ${imported.specifier}.`,ecosystem,mapping?.parity?.behaviorReviewFlags);continue}usage[ecosystem].automatic+=1,noteAutomatic(mapping,imported.start),deepRegistrationMappings.add(mapping.upstreamTag);const target=targetImport(mapping.target);addEdit(imported.start,imported.end,target,{upstreamTag,upstreamMember:"module",action:"rewrite-import",target,message:`Rewrite the ${upstreamTag} registration entry to its checked Lyra package route.`})}const rewrittenRanges=edits.map(edit=>[edit.start,edit.end]),wasRewritten=offset=>rewrittenRanges.some(([start,end])=>offset>=start&&offset<end);for(const scan of scanUnrewrittenUpstreamReferences(original))wasRewritten(scan.offset)||warn(scan.offset,scan.upstreamTag,scan.member,scan.code,scan.target,scan.message);const content=finalizeEdits(original,edits);return changes.sort((left,right)=>left.line-right.line||left.column-right.column||left.action.localeCompare(right.action)),warnings.sort((left,right)=>left.line-right.line||left.column-right.column||left.warningCode.localeCompare(right.warningCode)),{content,changes,warnings,usage,blockedMappings,blockedEcosystems,bareImportPackages,automaticMappings,deepRegistrationMappings}}export{migrateText,scanUnrewrittenUpstreamReferences};
@@ -1,8 +1,6 @@
1
1
  import{type PropertyValues,type TemplateResult}from'lit';import{LyraElement}from'../../../internal/lyra-element.js';import type{ToolApprovalEventDetail}from'../../../ai/types.js';import type{ToolApprovalDialogCloseReason}from'../tool-approval-dialog/tool-approval-dialog.class.js';import type{ApprovalDecision}from'../approval-state.js';export type ApprovalRequestStatus='pending'|ApprovalDecision;
2
2
  /** A host-owned tool call waiting for or carrying a human approval decision. */
3
- export interface ToolApprovalRequest{id:string;toolName:string;args:unknown;status?:ApprovalRequestStatus;}export interface LyraApprovalQueueEventMap{
4
- /** @deprecated Use `lr-approval-decision-request`. */
5
- 'lr-approval-decision':LyraApprovalQueueEventMap['lr-approval-decision-request'];'lr-approval-select':CustomEvent<{invocationId:string;}>;'lr-approval-decision-request':CustomEvent<ToolApprovalEventDetail&{args?:unknown;}>;'lr-approval-close':CustomEvent<{invocationId:string;reason:ToolApprovalDialogCloseReason;}>;}
3
+ export interface ToolApprovalRequest{id:string;toolName:string;args:unknown;status?:ApprovalRequestStatus;}export interface LyraApprovalQueueEventMap{'lr-approval-select':CustomEvent<{invocationId:string;}>;'lr-approval-decision-request':CustomEvent<ToolApprovalEventDetail&{args?:unknown;}>;'lr-approval-close':CustomEvent<{invocationId:string;reason:ToolApprovalDialogCloseReason;}>;}
6
4
  /**
7
5
  * `<lr-approval-queue>` — a controlled queue of tool calls that need human approval, with a
8
6
  * keyboard-accessible request list and a single reused `<lr-tool-approval-dialog>`. It never
@@ -15,7 +13,6 @@ export interface ToolApprovalRequest{id:string;toolName:string;args:unknown;stat
15
13
  *
16
14
  * @customElement lr-approval-queue
17
15
  * @event lr-approval-select - A request was selected. `detail: { invocationId }`.
18
- * @event lr-approval-decision - Deprecated cancelable compatibility alias of `lr-approval-decision-request`.
19
16
  * @event lr-approval-decision-request - A request was approved or denied. `detail: { invocationId,
20
17
  * approved, args? }`. Cancelable; preventing it keeps the nested dialog pending.
21
18
  * @event lr-approval-close - The nested decision dialog closed, or controlled requests invalidated