@sun-asterisk/sungen 1.0.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 (451) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +490 -0
  3. package/bin/sungen.js +12 -0
  4. package/dist/cli/commands/auto-tag-command.d.ts +8 -0
  5. package/dist/cli/commands/auto-tag-command.d.ts.map +1 -0
  6. package/dist/cli/commands/auto-tag-command.js +104 -0
  7. package/dist/cli/commands/auto-tag-command.js.map +1 -0
  8. package/dist/cli/index.d.ts +7 -0
  9. package/dist/cli/index.d.ts.map +1 -0
  10. package/dist/cli/index.js +196 -0
  11. package/dist/cli/index.js.map +1 -0
  12. package/dist/config/ai-providers.yaml +56 -0
  13. package/dist/config/config-loader.d.ts +51 -0
  14. package/dist/config/config-loader.d.ts.map +1 -0
  15. package/dist/config/config-loader.js +216 -0
  16. package/dist/config/config-loader.js.map +1 -0
  17. package/dist/config/config-schema.d.ts +121 -0
  18. package/dist/config/config-schema.d.ts.map +1 -0
  19. package/dist/config/config-schema.js +7 -0
  20. package/dist/config/config-schema.js.map +1 -0
  21. package/dist/config/default.config.yaml +101 -0
  22. package/dist/config/framework.config.yaml +52 -0
  23. package/dist/config/routes.yaml +31 -0
  24. package/dist/core/selector-base/annotation-handler.d.ts +45 -0
  25. package/dist/core/selector-base/annotation-handler.d.ts.map +1 -0
  26. package/dist/core/selector-base/annotation-handler.js +102 -0
  27. package/dist/core/selector-base/annotation-handler.js.map +1 -0
  28. package/dist/core/selector-base/base-generator.d.ts +49 -0
  29. package/dist/core/selector-base/base-generator.d.ts.map +1 -0
  30. package/dist/core/selector-base/base-generator.js +214 -0
  31. package/dist/core/selector-base/base-generator.js.map +1 -0
  32. package/dist/core/selector-base/gherkin-parser.d.ts +24 -0
  33. package/dist/core/selector-base/gherkin-parser.d.ts.map +1 -0
  34. package/dist/core/selector-base/gherkin-parser.js +42 -0
  35. package/dist/core/selector-base/gherkin-parser.js.map +1 -0
  36. package/dist/core/selector-mapper/priority-mapper.d.ts +74 -0
  37. package/dist/core/selector-mapper/priority-mapper.d.ts.map +1 -0
  38. package/dist/core/selector-mapper/priority-mapper.js +477 -0
  39. package/dist/core/selector-mapper/priority-mapper.js.map +1 -0
  40. package/dist/core/ui-scanner/heuristics/base-heuristic.d.ts +91 -0
  41. package/dist/core/ui-scanner/heuristics/base-heuristic.d.ts.map +1 -0
  42. package/dist/core/ui-scanner/heuristics/base-heuristic.js +175 -0
  43. package/dist/core/ui-scanner/heuristics/base-heuristic.js.map +1 -0
  44. package/dist/core/ui-scanner/react-scanner.d.ts +32 -0
  45. package/dist/core/ui-scanner/react-scanner.d.ts.map +1 -0
  46. package/dist/core/ui-scanner/react-scanner.js +163 -0
  47. package/dist/core/ui-scanner/react-scanner.js.map +1 -0
  48. package/dist/core/ui-scanner/scanner-interface.d.ts +94 -0
  49. package/dist/core/ui-scanner/scanner-interface.d.ts.map +1 -0
  50. package/dist/core/ui-scanner/scanner-interface.js +33 -0
  51. package/dist/core/ui-scanner/scanner-interface.js.map +1 -0
  52. package/dist/core/ui-scanner/strict-scanner.d.ts +81 -0
  53. package/dist/core/ui-scanner/strict-scanner.d.ts.map +1 -0
  54. package/dist/core/ui-scanner/strict-scanner.js +511 -0
  55. package/dist/core/ui-scanner/strict-scanner.js.map +1 -0
  56. package/dist/executor/playwright/playwright-generator.d.ts +33 -0
  57. package/dist/executor/playwright/playwright-generator.d.ts.map +1 -0
  58. package/dist/executor/playwright/playwright-generator.js +136 -0
  59. package/dist/executor/playwright/playwright-generator.js.map +1 -0
  60. package/dist/executor/test-generator.d.ts +63 -0
  61. package/dist/executor/test-generator.d.ts.map +1 -0
  62. package/dist/executor/test-generator.js +30 -0
  63. package/dist/executor/test-generator.js.map +1 -0
  64. package/dist/external/ai-provider.d.ts +60 -0
  65. package/dist/external/ai-provider.d.ts.map +1 -0
  66. package/dist/external/ai-provider.js +30 -0
  67. package/dist/external/ai-provider.js.map +1 -0
  68. package/dist/external/anthropic-provider.d.ts +29 -0
  69. package/dist/external/anthropic-provider.d.ts.map +1 -0
  70. package/dist/external/anthropic-provider.js +85 -0
  71. package/dist/external/anthropic-provider.js.map +1 -0
  72. package/dist/generators/cache/cache-manager.d.ts +66 -0
  73. package/dist/generators/cache/cache-manager.d.ts.map +1 -0
  74. package/dist/generators/cache/cache-manager.js +286 -0
  75. package/dist/generators/cache/cache-manager.js.map +1 -0
  76. package/dist/generators/cli.d.ts +7 -0
  77. package/dist/generators/cli.d.ts.map +1 -0
  78. package/dist/generators/cli.js +570 -0
  79. package/dist/generators/cli.js.map +1 -0
  80. package/dist/generators/dsl-writer/index.d.ts +33 -0
  81. package/dist/generators/dsl-writer/index.d.ts.map +1 -0
  82. package/dist/generators/dsl-writer/index.js +226 -0
  83. package/dist/generators/dsl-writer/index.js.map +1 -0
  84. package/dist/generators/gherkin-parser/index.d.ts +47 -0
  85. package/dist/generators/gherkin-parser/index.d.ts.map +1 -0
  86. package/dist/generators/gherkin-parser/index.js +149 -0
  87. package/dist/generators/gherkin-parser/index.js.map +1 -0
  88. package/dist/generators/gherkin-parser/selector-extractor.d.ts +37 -0
  89. package/dist/generators/gherkin-parser/selector-extractor.d.ts.map +1 -0
  90. package/dist/generators/gherkin-parser/selector-extractor.js +108 -0
  91. package/dist/generators/gherkin-parser/selector-extractor.js.map +1 -0
  92. package/dist/generators/scaffold-generator/index.d.ts +111 -0
  93. package/dist/generators/scaffold-generator/index.d.ts.map +1 -0
  94. package/dist/generators/scaffold-generator/index.js +408 -0
  95. package/dist/generators/scaffold-generator/index.js.map +1 -0
  96. package/dist/generators/selector-mapper/ai-mapper.d.ts +56 -0
  97. package/dist/generators/selector-mapper/ai-mapper.d.ts.map +1 -0
  98. package/dist/generators/selector-mapper/ai-mapper.js +457 -0
  99. package/dist/generators/selector-mapper/ai-mapper.js.map +1 -0
  100. package/dist/generators/selector-mapper/hybrid-mapper.d.ts +67 -0
  101. package/dist/generators/selector-mapper/hybrid-mapper.d.ts.map +1 -0
  102. package/dist/generators/selector-mapper/hybrid-mapper.js +349 -0
  103. package/dist/generators/selector-mapper/hybrid-mapper.js.map +1 -0
  104. package/dist/generators/selector-mapper/index.d.ts +8 -0
  105. package/dist/generators/selector-mapper/index.d.ts.map +1 -0
  106. package/dist/generators/selector-mapper/index.js +12 -0
  107. package/dist/generators/selector-mapper/index.js.map +1 -0
  108. package/dist/generators/selector-mapper/intelligent-mapper.d.ts +125 -0
  109. package/dist/generators/selector-mapper/intelligent-mapper.d.ts.map +1 -0
  110. package/dist/generators/selector-mapper/intelligent-mapper.js +391 -0
  111. package/dist/generators/selector-mapper/intelligent-mapper.js.map +1 -0
  112. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +49 -0
  113. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -0
  114. package/dist/generators/test-generator/adapters/adapter-interface.js +7 -0
  115. package/dist/generators/test-generator/adapters/adapter-interface.js.map +1 -0
  116. package/dist/generators/test-generator/adapters/adapter-registry.d.ts +29 -0
  117. package/dist/generators/test-generator/adapters/adapter-registry.d.ts.map +1 -0
  118. package/dist/generators/test-generator/adapters/adapter-registry.js +50 -0
  119. package/dist/generators/test-generator/adapters/adapter-registry.js.map +1 -0
  120. package/dist/generators/test-generator/adapters/index.d.ts +4 -0
  121. package/dist/generators/test-generator/adapters/index.d.ts.map +1 -0
  122. package/dist/generators/test-generator/adapters/index.js +13 -0
  123. package/dist/generators/test-generator/adapters/index.js.map +1 -0
  124. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +23 -0
  125. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -0
  126. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js +38 -0
  127. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -0
  128. package/dist/generators/test-generator/adapters/playwright/templates/before-each.hbs +8 -0
  129. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +5 -0
  130. package/dist/generators/test-generator/adapters/playwright/templates/scenario.hbs +8 -0
  131. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +1 -0
  132. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/clear-action.hbs +1 -0
  133. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-action.hbs +1 -0
  134. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/double-click-action.hbs +1 -0
  135. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-action.hbs +1 -0
  136. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/hover-action.hbs +1 -0
  137. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/press-action.hbs +1 -0
  138. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +1 -0
  139. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +1 -0
  140. package/dist/generators/test-generator/adapters/playwright/templates/steps/active-state-assertion.hbs +2 -0
  141. package/dist/generators/test-generator/adapters/playwright/templates/steps/ai-response-assertion-selector.hbs +5 -0
  142. package/dist/generators/test-generator/adapters/playwright/templates/steps/ai-response-assertion-simple.hbs +1 -0
  143. package/dist/generators/test-generator/adapters/playwright/templates/steps/application-running.hbs +1 -0
  144. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +1 -0
  145. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -0
  146. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/count-assertion.hbs +1 -0
  147. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-assertion.hbs +2 -0
  148. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/empty-assertion.hbs +2 -0
  149. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/enabled-assertion.hbs +2 -0
  150. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/focused-assertion.hbs +1 -0
  151. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -0
  152. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +1 -0
  153. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/not-visible-assertion.hbs +1 -0
  154. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-assertion.hbs +1 -0
  155. package/dist/generators/test-generator/adapters/playwright/templates/steps/check-action.hbs +1 -0
  156. package/dist/generators/test-generator/adapters/playwright/templates/steps/checkbox.hbs +2 -0
  157. package/dist/generators/test-generator/adapters/playwright/templates/steps/checked-assertion.hbs +1 -0
  158. package/dist/generators/test-generator/adapters/playwright/templates/steps/clear-action.hbs +1 -0
  159. package/dist/generators/test-generator/adapters/playwright/templates/steps/clear-auth.hbs +6 -0
  160. package/dist/generators/test-generator/adapters/playwright/templates/steps/clear-browser-state.hbs +6 -0
  161. package/dist/generators/test-generator/adapters/playwright/templates/steps/clear-database.hbs +4 -0
  162. package/dist/generators/test-generator/adapters/playwright/templates/steps/clear.hbs +2 -0
  163. package/dist/generators/test-generator/adapters/playwright/templates/steps/click-action.hbs +1 -0
  164. package/dist/generators/test-generator/adapters/playwright/templates/steps/click.hbs +2 -0
  165. package/dist/generators/test-generator/adapters/playwright/templates/steps/contain-text-assertion.hbs +1 -0
  166. package/dist/generators/test-generator/adapters/playwright/templates/steps/contains-text-assertion.hbs +2 -0
  167. package/dist/generators/test-generator/adapters/playwright/templates/steps/count-assertion.hbs +1 -0
  168. package/dist/generators/test-generator/adapters/playwright/templates/steps/count-greater-than.hbs +3 -0
  169. package/dist/generators/test-generator/adapters/playwright/templates/steps/count-less-than.hbs +3 -0
  170. package/dist/generators/test-generator/adapters/playwright/templates/steps/disabled-assertion.hbs +2 -0
  171. package/dist/generators/test-generator/adapters/playwright/templates/steps/displayed-containing-text.hbs +3 -0
  172. package/dist/generators/test-generator/adapters/playwright/templates/steps/displayed-with-text.hbs +3 -0
  173. package/dist/generators/test-generator/adapters/playwright/templates/steps/double-click-action.hbs +1 -0
  174. package/dist/generators/test-generator/adapters/playwright/templates/steps/empty-assertion-advanced.hbs +3 -0
  175. package/dist/generators/test-generator/adapters/playwright/templates/steps/empty-assertion.hbs +2 -0
  176. package/dist/generators/test-generator/adapters/playwright/templates/steps/enabled-assertion.hbs +2 -0
  177. package/dist/generators/test-generator/adapters/playwright/templates/steps/error-message-assertion.hbs +3 -0
  178. package/dist/generators/test-generator/adapters/playwright/templates/steps/fill-action.hbs +1 -0
  179. package/dist/generators/test-generator/adapters/playwright/templates/steps/fill.hbs +2 -0
  180. package/dist/generators/test-generator/adapters/playwright/templates/steps/focused-assertion.hbs +1 -0
  181. package/dist/generators/test-generator/adapters/playwright/templates/steps/generic-message-assertion.hbs +3 -0
  182. package/dist/generators/test-generator/adapters/playwright/templates/steps/has-attribute.hbs +2 -0
  183. package/dist/generators/test-generator/adapters/playwright/templates/steps/has-class.hbs +2 -0
  184. package/dist/generators/test-generator/adapters/playwright/templates/steps/has-count.hbs +2 -0
  185. package/dist/generators/test-generator/adapters/playwright/templates/steps/has-image-src.hbs +3 -0
  186. package/dist/generators/test-generator/adapters/playwright/templates/steps/has-link.hbs +3 -0
  187. package/dist/generators/test-generator/adapters/playwright/templates/steps/has-placeholder.hbs +3 -0
  188. package/dist/generators/test-generator/adapters/playwright/templates/steps/has-value.hbs +2 -0
  189. package/dist/generators/test-generator/adapters/playwright/templates/steps/have-text-assertion.hbs +1 -0
  190. package/dist/generators/test-generator/adapters/playwright/templates/steps/hover-action.hbs +1 -0
  191. package/dist/generators/test-generator/adapters/playwright/templates/steps/html5-validation-check.hbs +4 -0
  192. package/dist/generators/test-generator/adapters/playwright/templates/steps/is-checked.hbs +2 -0
  193. package/dist/generators/test-generator/adapters/playwright/templates/steps/is-editable.hbs +2 -0
  194. package/dist/generators/test-generator/adapters/playwright/templates/steps/is-focused.hbs +2 -0
  195. package/dist/generators/test-generator/adapters/playwright/templates/steps/is-hidden.hbs +2 -0
  196. package/dist/generators/test-generator/adapters/playwright/templates/steps/is-unchecked.hbs +2 -0
  197. package/dist/generators/test-generator/adapters/playwright/templates/steps/locator.hbs +1 -0
  198. package/dist/generators/test-generator/adapters/playwright/templates/steps/login.hbs +8 -0
  199. package/dist/generators/test-generator/adapters/playwright/templates/steps/message-assertion-body.hbs +1 -0
  200. package/dist/generators/test-generator/adapters/playwright/templates/steps/message-assertion-selector.hbs +2 -0
  201. package/dist/generators/test-generator/adapters/playwright/templates/steps/message-count-assertion.hbs +3 -0
  202. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +1 -0
  203. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +2 -0
  204. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-for-element.hbs +1 -0
  205. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-timeout.hbs +1 -0
  206. package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation.hbs +1 -0
  207. package/dist/generators/test-generator/adapters/playwright/templates/steps/not-checked-assertion.hbs +1 -0
  208. package/dist/generators/test-generator/adapters/playwright/templates/steps/not-visible-assertion.hbs +1 -0
  209. package/dist/generators/test-generator/adapters/playwright/templates/steps/not-visible.hbs +2 -0
  210. package/dist/generators/test-generator/adapters/playwright/templates/steps/notification-assertion.hbs +4 -0
  211. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +1 -0
  212. package/dist/generators/test-generator/adapters/playwright/templates/steps/press-action.hbs +1 -0
  213. package/dist/generators/test-generator/adapters/playwright/templates/steps/press-enter.hbs +2 -0
  214. package/dist/generators/test-generator/adapters/playwright/templates/steps/redirect-assertion.hbs +3 -0
  215. package/dist/generators/test-generator/adapters/playwright/templates/steps/route-assertion.hbs +2 -0
  216. package/dist/generators/test-generator/adapters/playwright/templates/steps/screen-navigation.hbs +3 -0
  217. package/dist/generators/test-generator/adapters/playwright/templates/steps/scroll-bottom-assertion.hbs +5 -0
  218. package/dist/generators/test-generator/adapters/playwright/templates/steps/select-action.hbs +1 -0
  219. package/dist/generators/test-generator/adapters/playwright/templates/steps/select.hbs +2 -0
  220. package/dist/generators/test-generator/adapters/playwright/templates/steps/setup/application-running.hbs +1 -0
  221. package/dist/generators/test-generator/adapters/playwright/templates/steps/setup/clear-auth.hbs +6 -0
  222. package/dist/generators/test-generator/adapters/playwright/templates/steps/setup/clear-browser-state.hbs +6 -0
  223. package/dist/generators/test-generator/adapters/playwright/templates/steps/setup/clear-database.hbs +4 -0
  224. package/dist/generators/test-generator/adapters/playwright/templates/steps/setup/user-login-todo.hbs +6 -0
  225. package/dist/generators/test-generator/adapters/playwright/templates/steps/text-matches-pattern.hbs +3 -0
  226. package/dist/generators/test-generator/adapters/playwright/templates/steps/uncheck-action.hbs +1 -0
  227. package/dist/generators/test-generator/adapters/playwright/templates/steps/user-login-todo.hbs +6 -0
  228. package/dist/generators/test-generator/adapters/playwright/templates/steps/visibility-assertion.hbs +2 -0
  229. package/dist/generators/test-generator/adapters/playwright/templates/steps/visible-assertion.hbs +1 -0
  230. package/dist/generators/test-generator/adapters/playwright/templates/steps/wait-for-element.hbs +1 -0
  231. package/dist/generators/test-generator/adapters/playwright/templates/steps/wait-timeout.hbs +1 -0
  232. package/dist/generators/test-generator/adapters/playwright/templates/steps/wait.hbs +1 -0
  233. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +19 -0
  234. package/dist/generators/test-generator/ai-step-mapper.d.ts +27 -0
  235. package/dist/generators/test-generator/ai-step-mapper.d.ts.map +1 -0
  236. package/dist/generators/test-generator/ai-step-mapper.js +204 -0
  237. package/dist/generators/test-generator/ai-step-mapper.js.map +1 -0
  238. package/dist/generators/test-generator/code-generator.d.ts +52 -0
  239. package/dist/generators/test-generator/code-generator.d.ts.map +1 -0
  240. package/dist/generators/test-generator/code-generator.js +191 -0
  241. package/dist/generators/test-generator/code-generator.js.map +1 -0
  242. package/dist/generators/test-generator/patterns/assertion-patterns.d.ts +7 -0
  243. package/dist/generators/test-generator/patterns/assertion-patterns.d.ts.map +1 -0
  244. package/dist/generators/test-generator/patterns/assertion-patterns.js +173 -0
  245. package/dist/generators/test-generator/patterns/assertion-patterns.js.map +1 -0
  246. package/dist/generators/test-generator/patterns/form-patterns.d.ts +8 -0
  247. package/dist/generators/test-generator/patterns/form-patterns.d.ts.map +1 -0
  248. package/dist/generators/test-generator/patterns/form-patterns.js +110 -0
  249. package/dist/generators/test-generator/patterns/form-patterns.js.map +1 -0
  250. package/dist/generators/test-generator/patterns/index.d.ts +45 -0
  251. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -0
  252. package/dist/generators/test-generator/patterns/index.js +106 -0
  253. package/dist/generators/test-generator/patterns/index.js.map +1 -0
  254. package/dist/generators/test-generator/patterns/interaction-patterns.d.ts +7 -0
  255. package/dist/generators/test-generator/patterns/interaction-patterns.d.ts.map +1 -0
  256. package/dist/generators/test-generator/patterns/interaction-patterns.js +100 -0
  257. package/dist/generators/test-generator/patterns/interaction-patterns.js.map +1 -0
  258. package/dist/generators/test-generator/patterns/navigation-patterns.d.ts +8 -0
  259. package/dist/generators/test-generator/patterns/navigation-patterns.d.ts.map +1 -0
  260. package/dist/generators/test-generator/patterns/navigation-patterns.js +92 -0
  261. package/dist/generators/test-generator/patterns/navigation-patterns.js.map +1 -0
  262. package/dist/generators/test-generator/patterns/setup-patterns.d.ts +7 -0
  263. package/dist/generators/test-generator/patterns/setup-patterns.d.ts.map +1 -0
  264. package/dist/generators/test-generator/patterns/setup-patterns.js +84 -0
  265. package/dist/generators/test-generator/patterns/setup-patterns.js.map +1 -0
  266. package/dist/generators/test-generator/patterns/types.d.ts +38 -0
  267. package/dist/generators/test-generator/patterns/types.d.ts.map +1 -0
  268. package/dist/generators/test-generator/patterns/types.js +3 -0
  269. package/dist/generators/test-generator/patterns/types.js.map +1 -0
  270. package/dist/generators/test-generator/step-mapper-old.d.ts +180 -0
  271. package/dist/generators/test-generator/step-mapper-old.d.ts.map +1 -0
  272. package/dist/generators/test-generator/step-mapper-old.js +752 -0
  273. package/dist/generators/test-generator/step-mapper-old.js.map +1 -0
  274. package/dist/generators/test-generator/step-mapper-refactored.d.ts +47 -0
  275. package/dist/generators/test-generator/step-mapper-refactored.d.ts.map +1 -0
  276. package/dist/generators/test-generator/step-mapper-refactored.js +182 -0
  277. package/dist/generators/test-generator/step-mapper-refactored.js.map +1 -0
  278. package/dist/generators/test-generator/step-mapper.d.ts +66 -0
  279. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -0
  280. package/dist/generators/test-generator/step-mapper.js +248 -0
  281. package/dist/generators/test-generator/step-mapper.js.map +1 -0
  282. package/dist/generators/test-generator/template-engine.d.ts +33 -0
  283. package/dist/generators/test-generator/template-engine.d.ts.map +1 -0
  284. package/dist/generators/test-generator/template-engine.js +129 -0
  285. package/dist/generators/test-generator/template-engine.js.map +1 -0
  286. package/dist/generators/test-generator/utils/data-resolver.d.ts +39 -0
  287. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -0
  288. package/dist/generators/test-generator/utils/data-resolver.js +162 -0
  289. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -0
  290. package/dist/generators/test-generator/utils/path-inference.d.ts +49 -0
  291. package/dist/generators/test-generator/utils/path-inference.d.ts.map +1 -0
  292. package/dist/generators/test-generator/utils/path-inference.js +286 -0
  293. package/dist/generators/test-generator/utils/path-inference.js.map +1 -0
  294. package/dist/generators/test-generator/utils/selector-resolver.d.ts +93 -0
  295. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -0
  296. package/dist/generators/test-generator/utils/selector-resolver.js +408 -0
  297. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -0
  298. package/dist/generators/types.d.ts +118 -0
  299. package/dist/generators/types.d.ts.map +1 -0
  300. package/dist/generators/types.js +48 -0
  301. package/dist/generators/types.js.map +1 -0
  302. package/dist/generators/ui-model-builder/deep-scanner.d.ts +121 -0
  303. package/dist/generators/ui-model-builder/deep-scanner.d.ts.map +1 -0
  304. package/dist/generators/ui-model-builder/deep-scanner.js +1113 -0
  305. package/dist/generators/ui-model-builder/deep-scanner.js.map +1 -0
  306. package/dist/generators/ui-model-builder/enhanced-deep-scanner.d.ts +110 -0
  307. package/dist/generators/ui-model-builder/enhanced-deep-scanner.d.ts.map +1 -0
  308. package/dist/generators/ui-model-builder/enhanced-deep-scanner.js +608 -0
  309. package/dist/generators/ui-model-builder/enhanced-deep-scanner.js.map +1 -0
  310. package/dist/generators/ui-model-builder/react-scanner.d.ts +107 -0
  311. package/dist/generators/ui-model-builder/react-scanner.d.ts.map +1 -0
  312. package/dist/generators/ui-model-builder/react-scanner.js +797 -0
  313. package/dist/generators/ui-model-builder/react-scanner.js.map +1 -0
  314. package/dist/input/cli-adapter.d.ts +63 -0
  315. package/dist/input/cli-adapter.d.ts.map +1 -0
  316. package/dist/input/cli-adapter.js +173 -0
  317. package/dist/input/cli-adapter.js.map +1 -0
  318. package/dist/input/config-adapter.d.ts +25 -0
  319. package/dist/input/config-adapter.d.ts.map +1 -0
  320. package/dist/input/config-adapter.js +70 -0
  321. package/dist/input/config-adapter.js.map +1 -0
  322. package/dist/input/input-adapter.d.ts +28 -0
  323. package/dist/input/input-adapter.d.ts.map +1 -0
  324. package/dist/input/input-adapter.js +17 -0
  325. package/dist/input/input-adapter.js.map +1 -0
  326. package/dist/input/vscode-adapter.d.ts +62 -0
  327. package/dist/input/vscode-adapter.d.ts.map +1 -0
  328. package/dist/input/vscode-adapter.js +64 -0
  329. package/dist/input/vscode-adapter.js.map +1 -0
  330. package/dist/orchestrator/cache-manager.d.ts +37 -0
  331. package/dist/orchestrator/cache-manager.d.ts.map +1 -0
  332. package/dist/orchestrator/cache-manager.js +148 -0
  333. package/dist/orchestrator/cache-manager.js.map +1 -0
  334. package/dist/orchestrator/pipeline.d.ts +73 -0
  335. package/dist/orchestrator/pipeline.d.ts.map +1 -0
  336. package/dist/orchestrator/pipeline.js +607 -0
  337. package/dist/orchestrator/pipeline.js.map +1 -0
  338. package/dist/orchestrator/project-initializer.d.ts +51 -0
  339. package/dist/orchestrator/project-initializer.d.ts.map +1 -0
  340. package/dist/orchestrator/project-initializer.js +326 -0
  341. package/dist/orchestrator/project-initializer.js.map +1 -0
  342. package/dist/orchestrator/reporter.d.ts +15 -0
  343. package/dist/orchestrator/reporter.d.ts.map +1 -0
  344. package/dist/orchestrator/reporter.js +30 -0
  345. package/dist/orchestrator/reporter.js.map +1 -0
  346. package/dist/orchestrator/screen-manager.d.ts +47 -0
  347. package/dist/orchestrator/screen-manager.d.ts.map +1 -0
  348. package/dist/orchestrator/screen-manager.js +271 -0
  349. package/dist/orchestrator/screen-manager.js.map +1 -0
  350. package/dist/tools/auto-tagger.d.ts +107 -0
  351. package/dist/tools/auto-tagger.d.ts.map +1 -0
  352. package/dist/tools/auto-tagger.js +502 -0
  353. package/dist/tools/auto-tagger.js.map +1 -0
  354. package/package.json +73 -0
  355. package/src/cli/commands/auto-tag-command.ts +80 -0
  356. package/src/cli/index.ts +205 -0
  357. package/src/config/ai-providers.yaml +56 -0
  358. package/src/config/config-loader.ts +248 -0
  359. package/src/config/config-schema.ts +148 -0
  360. package/src/config/default.config.yaml +101 -0
  361. package/src/config/framework.config.yaml +52 -0
  362. package/src/config/routes.yaml +31 -0
  363. package/src/core/selector-base/annotation-handler.ts +127 -0
  364. package/src/core/selector-base/base-generator.ts +234 -0
  365. package/src/core/selector-base/gherkin-parser.ts +57 -0
  366. package/src/core/selector-mapper/priority-mapper.ts +607 -0
  367. package/src/core/ui-scanner/heuristics/base-heuristic.ts +216 -0
  368. package/src/core/ui-scanner/react-scanner.ts +156 -0
  369. package/src/core/ui-scanner/scanner-interface.ts +133 -0
  370. package/src/core/ui-scanner/strict-scanner.ts +629 -0
  371. package/src/executor/playwright/playwright-generator.ts +125 -0
  372. package/src/executor/test-generator.ts +90 -0
  373. package/src/external/ai-provider.ts +90 -0
  374. package/src/external/anthropic-provider.ts +114 -0
  375. package/src/generators/README.md +410 -0
  376. package/src/generators/cache/cache-manager.ts +322 -0
  377. package/src/generators/cli.ts +640 -0
  378. package/src/generators/dsl-writer/index.ts +253 -0
  379. package/src/generators/gherkin-parser/index.ts +155 -0
  380. package/src/generators/gherkin-parser/selector-extractor.ts +142 -0
  381. package/src/generators/scaffold-generator/index.ts +524 -0
  382. package/src/generators/selector-mapper/ai-mapper.ts +528 -0
  383. package/src/generators/selector-mapper/hybrid-mapper.ts +427 -0
  384. package/src/generators/selector-mapper/index.ts +10 -0
  385. package/src/generators/selector-mapper/intelligent-mapper.ts +530 -0
  386. package/src/generators/test-generator/adapters/adapter-interface.ts +49 -0
  387. package/src/generators/test-generator/adapters/adapter-registry.ts +56 -0
  388. package/src/generators/test-generator/adapters/index.ts +9 -0
  389. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +40 -0
  390. package/src/generators/test-generator/adapters/playwright/templates/before-each.hbs +8 -0
  391. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +5 -0
  392. package/src/generators/test-generator/adapters/playwright/templates/scenario.hbs +8 -0
  393. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +1 -0
  394. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/clear-action.hbs +1 -0
  395. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-action.hbs +1 -0
  396. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/double-click-action.hbs +1 -0
  397. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-action.hbs +1 -0
  398. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/hover-action.hbs +1 -0
  399. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/press-action.hbs +1 -0
  400. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +1 -0
  401. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +1 -0
  402. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +1 -0
  403. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -0
  404. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/count-assertion.hbs +1 -0
  405. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-assertion.hbs +2 -0
  406. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/empty-assertion.hbs +2 -0
  407. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/enabled-assertion.hbs +2 -0
  408. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/focused-assertion.hbs +1 -0
  409. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -0
  410. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +1 -0
  411. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/not-visible-assertion.hbs +1 -0
  412. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-assertion.hbs +1 -0
  413. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/navigation.hbs +1 -0
  414. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/route-assertion.hbs +2 -0
  415. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-for-element.hbs +1 -0
  416. package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-timeout.hbs +1 -0
  417. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +1 -0
  418. package/src/generators/test-generator/adapters/playwright/templates/steps/setup/application-running.hbs +1 -0
  419. package/src/generators/test-generator/adapters/playwright/templates/steps/setup/clear-auth.hbs +6 -0
  420. package/src/generators/test-generator/adapters/playwright/templates/steps/setup/clear-browser-state.hbs +6 -0
  421. package/src/generators/test-generator/adapters/playwright/templates/steps/setup/clear-database.hbs +4 -0
  422. package/src/generators/test-generator/adapters/playwright/templates/steps/setup/user-login-todo.hbs +6 -0
  423. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +19 -0
  424. package/src/generators/test-generator/ai-step-mapper.ts +224 -0
  425. package/src/generators/test-generator/code-generator.ts +235 -0
  426. package/src/generators/test-generator/patterns/assertion-patterns.ts +183 -0
  427. package/src/generators/test-generator/patterns/form-patterns.ts +124 -0
  428. package/src/generators/test-generator/patterns/index.ts +97 -0
  429. package/src/generators/test-generator/patterns/interaction-patterns.ts +119 -0
  430. package/src/generators/test-generator/patterns/navigation-patterns.ts +110 -0
  431. package/src/generators/test-generator/patterns/setup-patterns.ts +94 -0
  432. package/src/generators/test-generator/patterns/types.ts +41 -0
  433. package/src/generators/test-generator/step-mapper.ts +254 -0
  434. package/src/generators/test-generator/template-engine.ts +160 -0
  435. package/src/generators/test-generator/utils/data-resolver.ts +147 -0
  436. package/src/generators/test-generator/utils/path-inference.ts +344 -0
  437. package/src/generators/test-generator/utils/selector-resolver.ts +480 -0
  438. package/src/generators/types.ts +226 -0
  439. package/src/generators/ui-model-builder/deep-scanner.ts +1244 -0
  440. package/src/generators/ui-model-builder/enhanced-deep-scanner.ts +731 -0
  441. package/src/generators/ui-model-builder/react-scanner.ts +959 -0
  442. package/src/input/cli-adapter.ts +185 -0
  443. package/src/input/config-adapter.ts +71 -0
  444. package/src/input/input-adapter.ts +32 -0
  445. package/src/input/vscode-adapter.ts +90 -0
  446. package/src/orchestrator/cache-manager.ts +138 -0
  447. package/src/orchestrator/pipeline.ts +713 -0
  448. package/src/orchestrator/project-initializer.ts +315 -0
  449. package/src/orchestrator/reporter.ts +36 -0
  450. package/src/orchestrator/screen-manager.ts +268 -0
  451. package/src/tools/auto-tagger.ts +572 -0
@@ -0,0 +1,1244 @@
1
+ /**
2
+ * PRODUCTION-READY Deep Scanner
3
+ * Implements all 4 iterations:
4
+ * 1. Deep Scanning (4+ levels)
5
+ * 2. UI Library Handling
6
+ * 3. Props Merging
7
+ * 4. Smart Filtering
8
+ */
9
+
10
+ import * as parser from '@babel/parser';
11
+ import traverse from '@babel/traverse';
12
+ import * as t from '@babel/types';
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+ import { UIElement } from '../types';
16
+
17
+ // ============================================================================
18
+ // Configuration
19
+ // ============================================================================
20
+
21
+ interface DeepScanConfig {
22
+ sourceRoot: string;
23
+ projectRoot: string;
24
+ maxDepth: number;
25
+ verbose?: boolean;
26
+ }
27
+
28
+ // ============================================================================
29
+ // Framework Components Registry
30
+ // ============================================================================
31
+
32
+ interface FrameworkComponentDef {
33
+ convertTo: string; // Target DOM element (e.g., 'a', 'img')
34
+ role?: string; // ARIA role
35
+ propsMap: Record<string, string | ((attrs: any) => any)>; // Props mapping
36
+ defaultProps?: Record<string, any>;
37
+ }
38
+
39
+ const FRAMEWORK_COMPONENTS: Record<string, Record<string, FrameworkComponentDef>> = {
40
+ // Next.js Framework Components
41
+ 'next/link': {
42
+ Link: {
43
+ convertTo: 'a',
44
+ role: 'link',
45
+ propsMap: {
46
+ href: 'href',
47
+ // Next.js Link wraps children in <a>, so we preserve all props
48
+ }
49
+ }
50
+ },
51
+ 'next/image': {
52
+ Image: {
53
+ convertTo: 'img',
54
+ role: 'img',
55
+ propsMap: {
56
+ src: 'src',
57
+ alt: 'alt',
58
+ width: 'width',
59
+ height: 'height',
60
+ loading: 'loading',
61
+ priority: (attrs: any) => attrs.priority ? 'eager' : 'lazy'
62
+ }
63
+ }
64
+ },
65
+ 'next/script': {
66
+ Script: {
67
+ convertTo: 'script',
68
+ propsMap: {
69
+ src: 'src',
70
+ strategy: 'strategy',
71
+ onLoad: 'onload'
72
+ }
73
+ }
74
+ },
75
+
76
+ // React Router Components (for future support)
77
+ 'react-router-dom': {
78
+ Link: {
79
+ convertTo: 'a',
80
+ role: 'link',
81
+ propsMap: {
82
+ to: 'href' // React Router uses 'to' instead of 'href'
83
+ }
84
+ },
85
+ NavLink: {
86
+ convertTo: 'a',
87
+ role: 'link',
88
+ propsMap: {
89
+ to: 'href'
90
+ }
91
+ }
92
+ },
93
+
94
+ // Remix Framework Components (for future support)
95
+ 'remix': {
96
+ Link: {
97
+ convertTo: 'a',
98
+ role: 'link',
99
+ propsMap: {
100
+ to: 'href'
101
+ }
102
+ },
103
+ Form: {
104
+ convertTo: 'form',
105
+ role: 'form',
106
+ propsMap: {
107
+ action: 'action',
108
+ method: 'method'
109
+ }
110
+ }
111
+ }
112
+ };
113
+
114
+ // UI libraries to scan (don't skip these)
115
+ const UI_LIBRARIES = [
116
+ 'flowbite-react',
117
+ '@mui/material',
118
+ '@mui/icons-material',
119
+ 'antd',
120
+ 'react-bootstrap',
121
+ '@headlessui/react',
122
+ '@radix-ui',
123
+ 'chakra-ui',
124
+ '@chakra-ui/react',
125
+ 'semantic-ui-react',
126
+ '@fluentui/react'
127
+ ];
128
+
129
+ // Skip these patterns (REFINED - don't skip framework UI components)
130
+ const SKIP_PATTERNS = [
131
+ /^react$/,
132
+ /^react-dom$/,
133
+ /^next$/, // Skip 'next' core package
134
+ /^next\/navigation$/, // Skip navigation hooks (not UI)
135
+ /^next\/headers$/, // Skip headers (not UI)
136
+ /^next\/server$/, // Skip server utilities (not UI)
137
+ /^next\/font/, // Skip font utilities (not UI)
138
+ // NOTE: We DON'T skip 'next/link', 'next/image', 'next/script' anymore!
139
+ /Icon$/,
140
+ /^Logo$/,
141
+ /^Copyright$/,
142
+ /^RenderIf$/,
143
+ /^Fragment$/
144
+ ];
145
+
146
+ // Prioritize scanning these patterns
147
+ const PRIORITY_PATTERNS = [
148
+ /Input$/,
149
+ /Field$/,
150
+ /Select$/,
151
+ /Checkbox$/,
152
+ /Radio$/,
153
+ /Button$/,
154
+ /TextArea$/,
155
+ /Form$/,
156
+ /TextField$/,
157
+ /Switch$/,
158
+ /Slider$/
159
+ ];
160
+
161
+ // ============================================================================
162
+ // Deep Scanner Class
163
+ // ============================================================================
164
+
165
+ export class DeepScanner {
166
+ private config: DeepScanConfig;
167
+ private elements: UIElement[] = [];
168
+ private elementCounter = 0;
169
+ private scannedFiles = new Set<string>(); // Track file+props combinations
170
+ private componentCache = new Map<string, any>(); // Cache parsed components
171
+
172
+ constructor(config: DeepScanConfig) {
173
+ this.config = config;
174
+ }
175
+
176
+ /**
177
+ * Main entry point - scan files deeply
178
+ */
179
+ async scanFiles(filePaths: string[]): Promise<UIElement[]> {
180
+ this.elements = [];
181
+ this.elementCounter = 0;
182
+ this.scannedFiles.clear();
183
+
184
+ for (const filePath of filePaths) {
185
+ await this.scanFile(filePath, 0, {});
186
+ }
187
+
188
+ return this.elements;
189
+ }
190
+
191
+ /**
192
+ * Check if component should be scanned
193
+ */
194
+ private shouldScanComponent(componentName: string, importPath: string): boolean {
195
+ // Skip patterns
196
+ if (SKIP_PATTERNS.some(pattern => pattern.test(componentName))) {
197
+ return false;
198
+ }
199
+
200
+ if (SKIP_PATTERNS.some(pattern => pattern.test(importPath))) {
201
+ return false;
202
+ }
203
+
204
+ // Always scan priority components
205
+ if (PRIORITY_PATTERNS.some(pattern => pattern.test(componentName))) {
206
+ return true;
207
+ }
208
+
209
+ // Scan UI library components
210
+ if (UI_LIBRARIES.some(lib => importPath.includes(lib))) {
211
+ return true;
212
+ }
213
+
214
+ // Scan custom components (starts with uppercase)
215
+ if (/^[A-Z]/.test(componentName)) {
216
+ return true;
217
+ }
218
+
219
+ return false;
220
+ }
221
+
222
+ /**
223
+ * Check if this is an actual DOM element OR a UI library component
224
+ * We treat UI library components as terminal elements (don't scan deeper)
225
+ */
226
+ private isActualDOMElement(tagName: string): boolean {
227
+ const domElements = [
228
+ 'input', 'button', 'textarea', 'select', 'form',
229
+ 'a', 'img', 'video', 'audio', 'canvas',
230
+ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
231
+ 'p', 'span', 'div', 'section', 'article',
232
+ 'table', 'tr', 'td', 'th', 'thead', 'tbody',
233
+ 'ul', 'ol', 'li', 'dl', 'dt', 'dd',
234
+ 'label', 'fieldset', 'legend'
235
+ ];
236
+
237
+ return domElements.includes(tagName.toLowerCase());
238
+ }
239
+
240
+ /**
241
+ * Check if this is a UI library component (flowbite, MUI, etc.)
242
+ * These are treated as "actual elements" for our purposes
243
+ */
244
+ private isUILibraryComponent(tagName: string, importPath: string): boolean {
245
+ // Check if from UI library
246
+ const isFromUILibrary = UI_LIBRARIES.some(lib => importPath.includes(lib));
247
+
248
+ if (!isFromUILibrary) return false;
249
+
250
+ // For UI libraries, check if it's a form/interactive component
251
+ // (not layout/utility components like Flowbite, ThemeProvider, etc.)
252
+ const uiComponentPatterns = [
253
+ /Input/i, // TextInput, InputText, Input, EmailInput
254
+ /TextField/i,
255
+ /Button/i,
256
+ /Select/i,
257
+ /Checkbox/i,
258
+ /Radio/i,
259
+ /Switch/i,
260
+ /Slider/i,
261
+ /TextArea/i,
262
+ /Dropdown/i,
263
+ /Modal/i,
264
+ /Dialog/i,
265
+ /Picker/i, // DatePicker, TimePicker
266
+ /Toggle/i
267
+ ];
268
+
269
+ const matchesPattern = uiComponentPatterns.some(pattern => pattern.test(tagName));
270
+
271
+ // Skip utility components
272
+ const utilityComponents = ['Flowbite', 'ThemeProvider', 'Label', 'HelperText'];
273
+ const isUtility = utilityComponents.some(util => tagName.includes(util));
274
+
275
+ return matchesPattern && !isUtility;
276
+ }
277
+
278
+ /**
279
+ * Check if component is a custom wrapper of UI library component
280
+ * E.g., Button wraps ButtonFlowbite, TextInput wraps InputText
281
+ * These should be treated as terminal elements (buttons/inputs)
282
+ */
283
+ private isCustomUIWrapper(componentName: string, importPath: string): boolean {
284
+ // Not from UI library (it's custom)
285
+ const isFromUILib = UI_LIBRARIES.some(lib => importPath.includes(lib));
286
+ if (isFromUILib) return false;
287
+
288
+ // Must be from project's components
289
+ if (!importPath.includes('@/components')) return false;
290
+
291
+ // Match patterns that suggest it's a UI wrapper
292
+ const wrapperPatterns = [
293
+ /Button$/i, // Button, SubmitButton, etc.
294
+ /Input$/i, // TextInput, EmailInput, etc.
295
+ /Select$/i,
296
+ /Checkbox$/i,
297
+ /Radio$/i,
298
+ /TextArea$/i,
299
+ /Switch$/i,
300
+ /Slider$/i,
301
+ /Link$/i // Custom Link components
302
+ ];
303
+
304
+ return wrapperPatterns.some(pattern => pattern.test(componentName));
305
+ }
306
+
307
+ /**
308
+ * Check if component is a framework component (Next.js, React Router, etc.)
309
+ * Returns component definition if found, null otherwise
310
+ */
311
+ private getFrameworkComponent(componentName: string, importPath: string): FrameworkComponentDef | null {
312
+ // Check if import path is in FRAMEWORK_COMPONENTS registry
313
+ if (!FRAMEWORK_COMPONENTS[importPath]) {
314
+ return null;
315
+ }
316
+
317
+ // Check if component name exists for this import path
318
+ const componentDef = FRAMEWORK_COMPONENTS[importPath][componentName];
319
+ return componentDef || null;
320
+ }
321
+
322
+ /**
323
+ * Map framework component props to target DOM element props
324
+ */
325
+ private mapFrameworkProps(
326
+ attrs: Record<string, any>,
327
+ propsMap: Record<string, string | ((attrs: any) => any)>
328
+ ): Record<string, any> {
329
+ const mapped: Record<string, any> = {};
330
+
331
+ // Map each prop according to propsMap
332
+ for (const [sourceKey, targetKey] of Object.entries(propsMap)) {
333
+ if (attrs[sourceKey] !== undefined) {
334
+ if (typeof targetKey === 'function') {
335
+ // Custom mapping function
336
+ mapped[sourceKey] = targetKey(attrs);
337
+ } else {
338
+ // Direct prop name mapping
339
+ mapped[targetKey] = attrs[sourceKey];
340
+ }
341
+ }
342
+ }
343
+
344
+ // Preserve unmapped props that might be useful
345
+ const mappedKeys = new Set(Object.keys(propsMap));
346
+ for (const [key, value] of Object.entries(attrs)) {
347
+ if (!mappedKeys.has(key) && !mapped[key]) {
348
+ // Keep useful attributes like className, id, data-testid, etc.
349
+ if (['className', 'id', 'data-testid', 'aria-label', 'aria-labelledby', 'role'].includes(key)) {
350
+ mapped[key] = value;
351
+ }
352
+ }
353
+ }
354
+
355
+ return mapped;
356
+ }
357
+
358
+ /**
359
+ * Convert framework component to synthetic DOM element
360
+ */
361
+ private convertFrameworkComponent(
362
+ componentName: string,
363
+ componentDef: FrameworkComponentDef,
364
+ attrs: Record<string, any>,
365
+ text: string | null,
366
+ sourceFileName: string
367
+ ): UIElement {
368
+ // Map props according to component definition
369
+ const mappedProps = this.mapFrameworkProps(attrs, componentDef.propsMap);
370
+
371
+ // Apply default props if any
372
+ const finalProps = {
373
+ ...componentDef.defaultProps,
374
+ ...mappedProps
375
+ };
376
+
377
+ // Create synthetic element
378
+ const element: UIElement = {
379
+ key: `e${++this.elementCounter}`,
380
+ tag: componentDef.convertTo,
381
+ role: componentDef.role || this.inferRole(componentDef.convertTo),
382
+ id: finalProps.id,
383
+ name: finalProps.name,
384
+ placeholder: finalProps.placeholder,
385
+ ariaLabel: finalProps['aria-label'] || finalProps.ariaLabel,
386
+ testId: finalProps['data-testid'],
387
+ text: text,
388
+ source: sourceFileName,
389
+ props: finalProps
390
+ };
391
+
392
+ // Add metadata to track this was converted from framework component
393
+ (element as any).metadata = {
394
+ frameworkComponent: componentName,
395
+ originalImport: 'framework' // Will be set by caller
396
+ };
397
+
398
+ return element;
399
+ }
400
+
401
+ /**
402
+ * Try to resolve a path with different file extensions
403
+ */
404
+ private tryResolveWithExtensions(basePath: string): string | null {
405
+ const extensions = ['.tsx', '.ts', '.jsx', '.js'];
406
+
407
+ // Try direct file with extensions
408
+ for (const ext of extensions) {
409
+ const fullPath = basePath + ext;
410
+ if (fs.existsSync(fullPath)) {
411
+ return fullPath;
412
+ }
413
+ }
414
+
415
+ // Try index files
416
+ for (const ext of extensions) {
417
+ const indexPath = path.join(basePath, `index${ext}`);
418
+ if (fs.existsSync(indexPath)) {
419
+ return indexPath;
420
+ }
421
+ }
422
+
423
+ return null;
424
+ }
425
+
426
+ /**
427
+ * Resolve import path to actual file path
428
+ * Enhanced to try multiple locations for @/ alias
429
+ */
430
+ private resolveComponentPath(importPath: string, sourceFile: string): string | null {
431
+ try {
432
+ // Handle @ alias - Try multiple locations (Next.js, CRA, custom)
433
+ if (importPath.startsWith('@/')) {
434
+ const relativePath = importPath.replace('@/', '');
435
+
436
+ // Try multiple candidate directories in priority order
437
+ const candidates = [
438
+ path.join(this.config.projectRoot, 'src', relativePath), // CRA, Vite: src/
439
+ path.join(this.config.projectRoot, 'app', relativePath), // Next.js 13+: app/
440
+ path.join(this.config.projectRoot, relativePath), // Root level
441
+ path.join(this.config.sourceRoot, '..', relativePath), // Relative to sourceRoot
442
+ ];
443
+
444
+ for (const candidate of candidates) {
445
+ const resolved = this.tryResolveWithExtensions(candidate);
446
+ if (resolved) {
447
+ if (this.config.verbose) {
448
+ console.log(` ✓ Resolved @/${relativePath} → ${path.relative(this.config.projectRoot, resolved)}`);
449
+ }
450
+ return resolved;
451
+ }
452
+ }
453
+
454
+ // If not found, log for debugging
455
+ if (this.config.verbose) {
456
+ console.log(` ⚠️ Could not resolve @/${relativePath} (tried: src/, app/, root/)`);
457
+ }
458
+ return null;
459
+ }
460
+
461
+ // Handle relative imports
462
+ else if (importPath.startsWith('./') || importPath.startsWith('../')) {
463
+ const sourceDir = path.dirname(sourceFile);
464
+ const resolvedPath = path.resolve(sourceDir, importPath);
465
+ return this.tryResolveWithExtensions(resolvedPath);
466
+ }
467
+
468
+ // Handle UI library imports (node_modules)
469
+ else if (UI_LIBRARIES.some(lib => importPath.includes(lib))) {
470
+ const nodeModulesPath = path.join(this.config.projectRoot, 'node_modules', importPath);
471
+
472
+ // Try with extensions first
473
+ const resolved = this.tryResolveWithExtensions(nodeModulesPath);
474
+ if (resolved) return resolved;
475
+
476
+ // Try package.json main/module field
477
+ const pkgPath = path.join(nodeModulesPath, 'package.json');
478
+ if (fs.existsSync(pkgPath)) {
479
+ try {
480
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
481
+ const mainFile = pkg.module || pkg.main || 'index.js';
482
+ const mainPath = path.join(nodeModulesPath, mainFile);
483
+ if (fs.existsSync(mainPath)) {
484
+ return mainPath;
485
+ }
486
+ } catch (e) {
487
+ // Ignore package.json parse errors
488
+ }
489
+ }
490
+
491
+ return null;
492
+ }
493
+
494
+ // Skip other node_modules
495
+ else {
496
+ return null;
497
+ }
498
+ } catch (error) {
499
+ return null;
500
+ }
501
+ }
502
+
503
+ /**
504
+ * Extract JSX element from conditional expression (ignoring the condition)
505
+ * This allows us to discover elements in modals, conditional blocks, etc.
506
+ */
507
+ private extractConditionalJSXElement(
508
+ jsxElement: any,
509
+ sourceFileName: string,
510
+ imports: Map<string, string>,
511
+ componentProps: Record<string, any>,
512
+ depth: number,
513
+ detectedComponents: Array<{ name: string; importPath: string; props: Record<string, any>; isProjectComponent?: boolean }>
514
+ ): void {
515
+ const openingElement = jsxElement.openingElement;
516
+ let tagName = '';
517
+
518
+ if (openingElement.name.type === 'JSXIdentifier') {
519
+ tagName = openingElement.name.name;
520
+ }
521
+
522
+ if (this.config.verbose && depth === 2) {
523
+ console.log(` ${' '.repeat(depth)} 🔄 Conditional JSX: <${tagName}>`);
524
+ }
525
+
526
+ // Extract attributes and merge with component props
527
+ const attrs = this.extractAttributes(openingElement.attributes, componentProps);
528
+ const text = this.extractTextContent(jsxElement);
529
+ const mergedAttrs = this.mergeProps(componentProps, attrs);
530
+
531
+ // Check if custom component (Modal, Button, Input, etc.)
532
+ if (/^[A-Z]/.test(tagName) && imports.has(tagName)) {
533
+ const importPath = imports.get(tagName)!;
534
+
535
+ // Check if it's a project component that should be deep scanned
536
+ if (importPath.startsWith('@/') || importPath.startsWith('./') || importPath.startsWith('../')) {
537
+ if (this.config.verbose) {
538
+ console.log(` ${' '.repeat(depth)} 🔄 Conditional component: <${tagName}> - adding to scan queue`);
539
+ }
540
+
541
+ // Add to detectedComponents queue for deep scanning
542
+ // This ensures <Input>, <Button>, etc. get fully scanned
543
+ detectedComponents.push({
544
+ name: tagName,
545
+ importPath,
546
+ props: mergedAttrs, // Use merged props for prop resolution
547
+ isProjectComponent: true
548
+ });
549
+
550
+ // Also recursively scan immediate JSX children (for containers like <Modal>)
551
+ // This will discover HTML elements and additional custom components
552
+ if (jsxElement.children && jsxElement.children.length > 0) {
553
+ for (const child of jsxElement.children) {
554
+ if (child.type === 'JSXElement') {
555
+ this.extractConditionalJSXElement(child, sourceFileName, imports, componentProps, depth, detectedComponents);
556
+ } else if (child.type === 'JSXExpressionContainer') {
557
+ const expr = child.expression;
558
+ if (expr.type === 'LogicalExpression' && expr.operator === '&&' && expr.right.type === 'JSXElement') {
559
+ this.extractConditionalJSXElement(expr.right, sourceFileName, imports, componentProps, depth, detectedComponents);
560
+ } else if (expr.type === 'ConditionalExpression') {
561
+ if (expr.consequent.type === 'JSXElement') {
562
+ this.extractConditionalJSXElement(expr.consequent, sourceFileName, imports, componentProps, depth, detectedComponents);
563
+ }
564
+ if (expr.alternate && expr.alternate.type === 'JSXElement') {
565
+ this.extractConditionalJSXElement(expr.alternate, sourceFileName, imports, componentProps, depth, detectedComponents);
566
+ }
567
+ }
568
+ }
569
+ }
570
+ }
571
+ }
572
+
573
+ return; // Don't add component itself as element
574
+ }
575
+
576
+ // HTML element - add directly
577
+ if (this.shouldIncludeElement(tagName, mergedAttrs, text)) {
578
+ this.elements.push({
579
+ key: `e${++this.elementCounter}`,
580
+ tag: tagName,
581
+ role: this.inferRole(tagName),
582
+ id: mergedAttrs.id,
583
+ name: mergedAttrs.name,
584
+ placeholder: mergedAttrs.placeholder,
585
+ ariaLabel: mergedAttrs['aria-label'] || mergedAttrs.ariaLabel,
586
+ testId: mergedAttrs['data-testid'],
587
+ text: text,
588
+ source: sourceFileName,
589
+ props: mergedAttrs
590
+ });
591
+
592
+ if (this.config.verbose) {
593
+ console.log(` ${' '.repeat(depth)} ✅ Found conditional element: <${tagName}> ${mergedAttrs['aria-label'] || mergedAttrs.ariaLabel ? `aria-label="${mergedAttrs['aria-label'] || mergedAttrs.ariaLabel}"` : ''}${text ? `text="${text.substring(0, 30)}"` : ''}`);
594
+ }
595
+ }
596
+
597
+ // Recursively process children
598
+ if (jsxElement.children && jsxElement.children.length > 0) {
599
+ for (const child of jsxElement.children) {
600
+ if (child.type === 'JSXElement') {
601
+ this.extractConditionalJSXElement(child, sourceFileName, imports, componentProps, depth, detectedComponents);
602
+ } else if (child.type === 'JSXExpressionContainer') {
603
+ // Handle nested conditionals
604
+ const expr = child.expression;
605
+
606
+ if (expr.type === 'LogicalExpression' && expr.operator === '&&' && expr.right.type === 'JSXElement') {
607
+ this.extractConditionalJSXElement(expr.right, sourceFileName, imports, componentProps, depth, detectedComponents);
608
+ } else if (expr.type === 'ConditionalExpression') {
609
+ if (expr.consequent.type === 'JSXElement') {
610
+ this.extractConditionalJSXElement(expr.consequent, sourceFileName, imports, componentProps, depth, detectedComponents);
611
+ }
612
+ if (expr.alternate && expr.alternate.type === 'JSXElement') {
613
+ this.extractConditionalJSXElement(expr.alternate, sourceFileName, imports, componentProps, depth, detectedComponents);
614
+ }
615
+ }
616
+ }
617
+ }
618
+ }
619
+ }
620
+
621
+ /**
622
+ * Extract attributes from JSX element
623
+ * Now supports resolving expressions from componentProps!
624
+ */
625
+ private extractAttributes(attributes: any[], componentProps: Record<string, any> = {}): Record<string, any> {
626
+ const attrs: Record<string, any> = {};
627
+
628
+ attributes.forEach(attr => {
629
+ if (attr.type !== 'JSXAttribute' || attr.name.type !== 'JSXIdentifier') {
630
+ return;
631
+ }
632
+
633
+ const attrName = attr.name.name;
634
+ let value: any = null;
635
+
636
+ if (!attr.value) {
637
+ value = true; // Boolean attribute like <input required />
638
+ } else if (attr.value.type === 'StringLiteral') {
639
+ value = attr.value.value;
640
+ } else if (attr.value.type === 'JSXExpressionContainer') {
641
+ const expr = attr.value.expression;
642
+
643
+ if (expr.type === 'BooleanLiteral') {
644
+ value = expr.value;
645
+ } else if (expr.type === 'StringLiteral') {
646
+ value = expr.value;
647
+ } else if (expr.type === 'NumericLiteral') {
648
+ value = expr.value;
649
+ } else if (expr.type === 'MemberExpression') {
650
+ // props.name → try to resolve from componentProps
651
+ if (expr.object.type === 'Identifier' && expr.object.name === 'props' &&
652
+ expr.property.type === 'Identifier') {
653
+ const propName = expr.property.name;
654
+
655
+ // Try to resolve from componentProps
656
+ if (componentProps[propName] !== undefined) {
657
+ value = componentProps[propName]; // ✅ RESOLVED!
658
+ } else {
659
+ // Keep as expression if can't resolve
660
+ value = `{props.${propName}}`;
661
+ }
662
+ }
663
+ } else if (expr.type === 'Identifier') {
664
+ // Handle: className={inputClassName}
665
+ // Try to resolve from componentProps
666
+ const identifierName = expr.name;
667
+ if (componentProps[identifierName] !== undefined) {
668
+ value = componentProps[identifierName]; // ✅ RESOLVED!
669
+ } else {
670
+ // Keep as expression
671
+ value = `{${identifierName}}`;
672
+ }
673
+ } else if (expr.type === 'CallExpression') {
674
+ // Handle: register('email', ...) → extract first string argument
675
+ if (expr.arguments && expr.arguments.length > 0) {
676
+ const firstArg = expr.arguments[0];
677
+ if (firstArg.type === 'StringLiteral') {
678
+ // register('email') → use 'email' as value
679
+ value = firstArg.value; // ✅ EXTRACTED!
680
+ }
681
+ }
682
+ }
683
+ }
684
+
685
+ if (value !== null) {
686
+ attrs[attrName] = value;
687
+ }
688
+ });
689
+
690
+ return attrs;
691
+ }
692
+
693
+ /**
694
+ * Extract text content from JSX element
695
+ */
696
+ private extractTextContent(node: any): string | null {
697
+ let text = '';
698
+
699
+ if (!node.children) return null;
700
+
701
+ node.children.forEach((child: any) => {
702
+ if (child.type === 'JSXText') {
703
+ const trimmed = child.value.trim();
704
+ if (trimmed) {
705
+ text += trimmed + ' ';
706
+ }
707
+ } else if (child.type === 'JSXExpressionContainer') {
708
+ if (child.expression.type === 'StringLiteral') {
709
+ text += child.expression.value + ' ';
710
+ }
711
+ }
712
+ });
713
+
714
+ return text.trim() || null;
715
+ }
716
+
717
+ /**
718
+ * Check if element should be included in UI model
719
+ */
720
+ private shouldIncludeElement(tagName: string, attrs: Record<string, any>, text: string | null): boolean {
721
+ const lowercaseTag = tagName.toLowerCase();
722
+
723
+ // Priority 1: UI library components (ALWAYS include)
724
+ if (lowercaseTag.includes('input') || lowercaseTag.includes('button') || lowercaseTag.includes('select')) {
725
+ return true;
726
+ }
727
+
728
+ // Priority 2: Form elements (ALWAYS include)
729
+ const formElements = ['input', 'button', 'textarea', 'select', 'form'];
730
+ if (formElements.includes(lowercaseTag)) return true;
731
+
732
+ // Priority 2: Links
733
+ if (lowercaseTag === 'a' || tagName === 'Link') return true;
734
+
735
+ // Priority 3: Elements with identifiers
736
+ if (attrs.id || attrs.name || attrs['data-testid'] || attrs['aria-label']) {
737
+ return true;
738
+ }
739
+
740
+ // Priority 4: Headings with text
741
+ if (/^h[1-6]$/.test(lowercaseTag) && text) return true;
742
+
743
+ // Priority 5: Images with alt
744
+ if (lowercaseTag === 'img' && attrs.alt) return true;
745
+
746
+ // Priority 6: Interactive elements
747
+ if (attrs.onClick || attrs.onSubmit || attrs.href) return true;
748
+
749
+ // Skip generic containers unless meaningful
750
+ if (['div', 'span', 'p'].includes(lowercaseTag)) {
751
+ return !!(text && text.length > 5); // Only if meaningful text
752
+ }
753
+
754
+ // Include semantic elements
755
+ if (['section', 'article', 'nav', 'header', 'footer', 'main'].includes(lowercaseTag)) {
756
+ return true;
757
+ }
758
+
759
+ return false;
760
+ }
761
+
762
+ /**
763
+ * Merge props from component chain
764
+ */
765
+ private mergeProps(componentProps: Record<string, any>, elementAttrs: Record<string, any>): Record<string, any> {
766
+ const merged = { ...componentProps };
767
+
768
+ // Element attributes override component props
769
+ Object.entries(elementAttrs).forEach(([key, value]) => {
770
+ // Handle prop expressions like {props.name}
771
+ if (typeof value === 'string' && value.startsWith('{props.')) {
772
+ const propName = value.match(/\{props\.(\w+)\}/)?.[1];
773
+ if (propName && componentProps[propName]) {
774
+ merged[key] = componentProps[propName];
775
+ } else {
776
+ merged[key] = value; // Keep expression as-is
777
+ }
778
+ } else {
779
+ merged[key] = value;
780
+ }
781
+ });
782
+
783
+ return merged;
784
+ }
785
+
786
+ /**
787
+ * Generate cache key for file+props combination
788
+ * This allows same component to be scanned multiple times with different props
789
+ */
790
+ private getCacheKey(filePath: string, componentProps: Record<string, any>): string {
791
+ // Create a stable key from significant props (id, name, ariaLabel, etc.)
792
+ const significantProps = ['id', 'name', 'ariaLabel', 'aria-label', 'placeholder', 'type', 'testId', 'data-testid'];
793
+ const propsSignature = significantProps
794
+ .map(key => componentProps[key] || componentProps[key.replace(/-/g, '')])
795
+ .filter(Boolean)
796
+ .join('|');
797
+
798
+ return `${filePath}::${propsSignature}`;
799
+ }
800
+
801
+ /**
802
+ * Recursively scan a file for UI elements
803
+ */
804
+ private async scanFile(
805
+ filePath: string,
806
+ depth: number,
807
+ componentProps: Record<string, any>
808
+ ): Promise<void> {
809
+ // Safety checks
810
+ if (depth >= this.config.maxDepth) {
811
+ if (this.config.verbose) {
812
+ console.log(` ${' '.repeat(depth)}⚠️ Max depth ${this.config.maxDepth} reached`);
813
+ }
814
+ return;
815
+ }
816
+
817
+ // Use cache key based on file+props to allow same component with different props
818
+ const cacheKey = this.getCacheKey(filePath, componentProps);
819
+ if (this.scannedFiles.has(cacheKey)) {
820
+ if (this.config.verbose) {
821
+ console.log(` ${' '.repeat(depth)}⚠️ Already scanned: ${path.basename(filePath)} with these props`);
822
+ }
823
+ return;
824
+ }
825
+
826
+ if (!fs.existsSync(filePath)) {
827
+ if (this.config.verbose) {
828
+ console.log(` ${' '.repeat(depth)}⚠️ File not found: ${filePath}`);
829
+ }
830
+ return;
831
+ }
832
+
833
+ this.scannedFiles.add(cacheKey);
834
+
835
+ if (this.config.verbose) {
836
+ console.log(` ${' '.repeat(depth)}📄 Scanning (L${depth}): ${path.basename(filePath)}`);
837
+ }
838
+
839
+ // Read and parse file
840
+ const code = fs.readFileSync(filePath, 'utf-8');
841
+ let ast;
842
+
843
+ try {
844
+ ast = parser.parse(code, {
845
+ sourceType: 'module',
846
+ plugins: ['typescript', 'jsx']
847
+ });
848
+ } catch (error) {
849
+ if (this.config.verbose) {
850
+ console.log(` ${' '.repeat(depth)}⚠️ Parse error: ${(error as Error).message}`);
851
+ }
852
+ return;
853
+ }
854
+
855
+ // Extract imports and JSX elements
856
+ const self = this; // Capture 'this' and avoid naming conflict
857
+ const sourceFileName = path.basename(filePath);
858
+ const imports = new Map<string, string>();
859
+ const detectedComponents: Array<{ name: string; importPath: string; props: Record<string, any>; isProjectComponent?: boolean }> = [];
860
+ let jsxElementCount = 0;
861
+
862
+ traverse(ast, {
863
+ ImportDeclaration: (path: any) => {
864
+ const importPath = path.node.source.value;
865
+
866
+ // Default import
867
+ const defaultSpec = path.node.specifiers.find((s: any) => s.type === 'ImportDefaultSpecifier');
868
+ if (defaultSpec) {
869
+ imports.set(defaultSpec.local.name, importPath);
870
+ }
871
+
872
+ // Named imports
873
+ path.node.specifiers
874
+ .filter((s: any) => s.type === 'ImportSpecifier')
875
+ .forEach((spec: any) => {
876
+ if (spec.imported.type === 'Identifier') {
877
+ // Use LOCAL name (after 'as'), not imported name
878
+ imports.set(spec.local.name, importPath);
879
+
880
+ if (self.config.verbose && depth === 2) {
881
+ console.log(` ${' '.repeat(depth)} Import: ${spec.imported.name} as ${spec.local.name} from ${importPath}`);
882
+ }
883
+ }
884
+ });
885
+ }
886
+ });
887
+
888
+ // Extract JSX elements
889
+
890
+ traverse(ast, {
891
+ JSXElement: (nodePath: any) => {
892
+ jsxElementCount++;
893
+ const openingElement = nodePath.node.openingElement;
894
+ let tagName = '';
895
+
896
+ if (openingElement.name.type === 'JSXIdentifier') {
897
+ tagName = openingElement.name.name;
898
+ }
899
+
900
+ if (self.config.verbose && depth === 2) {
901
+ console.log(` ${' '.repeat(depth)} JSX #${jsxElementCount}: <${tagName}>`);
902
+ }
903
+
904
+ // Extract attributes WITH componentProps for expression resolution
905
+ const attrs = self.extractAttributes(openingElement.attributes, componentProps);
906
+ const text = self.extractTextContent(nodePath.node);
907
+
908
+ // Merge with component props (for additional props not in attrs)
909
+ const mergedAttrs = self.mergeProps(componentProps, attrs);
910
+
911
+ // Check if custom component
912
+ if (/^[A-Z]/.test(tagName) && imports.has(tagName)) {
913
+ const importPath = imports.get(tagName)!;
914
+
915
+ if (self.config.verbose) {
916
+ console.log(` ${' '.repeat(depth)} 🔎 Component: <${tagName}> from ${importPath}`);
917
+ }
918
+
919
+ // NEW: Check if framework component (Next.js Link, Image, etc.)
920
+ const frameworkComp = self.getFrameworkComponent(tagName, importPath);
921
+ if (frameworkComp) {
922
+ // Convert to synthetic DOM element
923
+ const syntheticElement = self.convertFrameworkComponent(
924
+ tagName,
925
+ frameworkComp,
926
+ mergedAttrs,
927
+ text,
928
+ sourceFileName
929
+ );
930
+
931
+ // Update metadata with actual import path
932
+ (syntheticElement as any).metadata.originalImport = importPath;
933
+
934
+ // Add to elements
935
+ self.elements.push(syntheticElement);
936
+
937
+ if (self.config.verbose) {
938
+ console.log(` ${' '.repeat(depth)} ✅ Found Framework Component: <${tagName}> → <${frameworkComp.convertTo}> ${mergedAttrs.href ? `href="${mergedAttrs.href}"` : ''}${text ? `text="${text.substring(0, 30)}"` : ''}`);
939
+ }
940
+
941
+ // Don't scan deeper - framework components are terminal
942
+ return;
943
+ }
944
+
945
+ // Check if UI library component - treat as terminal element
946
+ const isUILib = self.isUILibraryComponent(tagName, importPath);
947
+
948
+ if (self.config.verbose && depth === 2) {
949
+ console.log(` ${' '.repeat(depth)} isUILibraryComponent(${tagName}, ${importPath}) = ${isUILib}`);
950
+ }
951
+
952
+ if (isUILib) {
953
+ // Treat as actual element (don't scan deeper)
954
+ if (self.shouldIncludeElement(tagName, mergedAttrs, text)) {
955
+ self.elements.push({
956
+ key: `e${++self.elementCounter}`,
957
+ tag: tagName.toLowerCase(), // e.g. "inputtext"
958
+ role: self.inferRole('input'), // Infer as input
959
+ id: mergedAttrs.id,
960
+ name: mergedAttrs.name,
961
+ placeholder: mergedAttrs.placeholder,
962
+ ariaLabel: mergedAttrs['aria-label'] || mergedAttrs.ariaLabel,
963
+ testId: mergedAttrs['data-testid'],
964
+ text: text,
965
+ source: sourceFileName,
966
+ props: mergedAttrs
967
+ });
968
+
969
+ if (self.config.verbose) {
970
+ console.log(` ${' '.repeat(depth)} ✅ Found UI Library: <${tagName}> ${mergedAttrs.id ? `id="${mergedAttrs.id}"` : ''}${mergedAttrs['data-testid'] ? `data-testid="${mergedAttrs['data-testid']}"` : ''}`);
971
+ }
972
+ }
973
+ }
974
+ // NEW: Project components - ALWAYS try to scan deeper
975
+ else if (importPath.startsWith('@/') || importPath.startsWith('./') || importPath.startsWith('../')) {
976
+ // This is a project component - add to scan queue
977
+ // We'll scan it after traverse completes (cannot await in traverse callback)
978
+ if (self.config.verbose) {
979
+ console.log(` ${' '.repeat(depth)} → Will scan project component: <${tagName}>`);
980
+ }
981
+
982
+ detectedComponents.push({
983
+ name: tagName,
984
+ importPath,
985
+ props: mergedAttrs, // Use merged props (includes component props)
986
+ isProjectComponent: true // Mark as project component
987
+ });
988
+ }
989
+ // For other components (3rd party libs not in UI_LIBRARIES), decide whether to scan
990
+ else if (self.shouldScanComponent(tagName, importPath)) {
991
+ detectedComponents.push({
992
+ name: tagName,
993
+ importPath,
994
+ props: attrs,
995
+ isProjectComponent: false
996
+ });
997
+ }
998
+ }
999
+ // Check if actual DOM element
1000
+ else if (self.isActualDOMElement(tagName)) {
1001
+ // Found actual element - include it!
1002
+ if (self.shouldIncludeElement(tagName, mergedAttrs, text)) {
1003
+ self.elements.push({
1004
+ key: `e${++self.elementCounter}`,
1005
+ tag: tagName.toLowerCase(),
1006
+ role: self.inferRole(tagName),
1007
+ id: mergedAttrs.id,
1008
+ name: mergedAttrs.name,
1009
+ placeholder: mergedAttrs.placeholder,
1010
+ ariaLabel: mergedAttrs['aria-label'] || mergedAttrs.ariaLabel,
1011
+ testId: mergedAttrs['data-testid'],
1012
+ text: text,
1013
+ source: sourceFileName,
1014
+ props: mergedAttrs
1015
+ });
1016
+
1017
+ if (self.config.verbose) {
1018
+ console.log(` ${' '.repeat(depth)} ✅ Found: <${tagName}> ${mergedAttrs.id ? `id="${mergedAttrs.id}"` : ''}${mergedAttrs['data-testid'] ? `data-testid="${mergedAttrs['data-testid']}"` : ''}`);
1019
+ }
1020
+ }
1021
+ }
1022
+ },
1023
+
1024
+ // NEW: Handle conditional rendering - ignore conditions, scan all JSX
1025
+ JSXExpressionContainer: (nodePath: any) => {
1026
+ const expr = nodePath.node.expression;
1027
+
1028
+ // Pattern 1: Logical AND (condition && <Element />)
1029
+ if (expr.type === 'LogicalExpression' && expr.operator === '&&') {
1030
+ const rightSide = expr.right;
1031
+
1032
+ if (rightSide.type === 'JSXElement') {
1033
+ // Extract element from condition, ignoring the condition itself
1034
+ self.extractConditionalJSXElement(rightSide, sourceFileName, imports, componentProps, depth, detectedComponents);
1035
+ } else if (rightSide.type === 'JSXFragment') {
1036
+ // Handle {condition && (<><Element1 /><Element2 /></>)}
1037
+ for (const child of rightSide.children) {
1038
+ if (child.type === 'JSXElement') {
1039
+ self.extractConditionalJSXElement(child, sourceFileName, imports, componentProps, depth, detectedComponents);
1040
+ }
1041
+ }
1042
+ }
1043
+ }
1044
+
1045
+ // Pattern 2: Ternary operator (condition ? <A /> : <B />)
1046
+ if (expr.type === 'ConditionalExpression') {
1047
+ const consequent = expr.consequent;
1048
+ const alternate = expr.alternate;
1049
+
1050
+ // Extract BOTH branches (we want all possible UI)
1051
+ if (consequent.type === 'JSXElement') {
1052
+ self.extractConditionalJSXElement(consequent, sourceFileName, imports, componentProps, depth, detectedComponents);
1053
+ }
1054
+
1055
+ if (alternate && alternate.type === 'JSXElement') {
1056
+ self.extractConditionalJSXElement(alternate, sourceFileName, imports, componentProps, depth, detectedComponents);
1057
+ }
1058
+ }
1059
+
1060
+ // Pattern 3: Array.map() rendering
1061
+ if (expr.type === 'CallExpression') {
1062
+ const callee = expr.callee;
1063
+
1064
+ if (callee.type === 'MemberExpression' &&
1065
+ callee.property.type === 'Identifier' &&
1066
+ callee.property.name === 'map') {
1067
+ const mapCallback = expr.arguments[0];
1068
+
1069
+ if (mapCallback &&
1070
+ (mapCallback.type === 'ArrowFunctionExpression' ||
1071
+ mapCallback.type === 'FunctionExpression')) {
1072
+ const body = mapCallback.body;
1073
+
1074
+ // Direct return: arr.map(item => <Element />)
1075
+ if (body.type === 'JSXElement') {
1076
+ self.extractConditionalJSXElement(body, sourceFileName, imports, componentProps, depth, detectedComponents);
1077
+ }
1078
+
1079
+ // Block with return: arr.map(item => { return <Element /> })
1080
+ if (body.type === 'BlockStatement') {
1081
+ for (const statement of body.body) {
1082
+ if (statement.type === 'ReturnStatement' &&
1083
+ statement.argument &&
1084
+ statement.argument.type === 'JSXElement') {
1085
+ self.extractConditionalJSXElement(statement.argument, sourceFileName, imports, componentProps, depth, detectedComponents);
1086
+ }
1087
+ }
1088
+ }
1089
+ }
1090
+ }
1091
+ }
1092
+ }
1093
+ });
1094
+
1095
+ if (this.config.verbose && detectedComponents.length > 0) {
1096
+ console.log(` ${' '.repeat(depth)} → Found ${detectedComponents.length} component(s) to scan deeper`);
1097
+ }
1098
+
1099
+ // Recursively scan detected components
1100
+ for (const comp of detectedComponents) {
1101
+ const componentPath = this.resolveComponentPath(comp.importPath, filePath);
1102
+
1103
+ if (componentPath) {
1104
+ const elementsBefore = this.elements.length;
1105
+
1106
+ if (this.config.verbose) {
1107
+ console.log(` ${' '.repeat(depth)} → Diving into: ${comp.name}`);
1108
+ }
1109
+
1110
+ await this.scanFile(componentPath, depth + 1, comp.props);
1111
+
1112
+ const elementsAfter = this.elements.length;
1113
+ const elementsFound = elementsAfter - elementsBefore;
1114
+
1115
+ // Project components: Check if found elements, if not, try fallback
1116
+ if ((comp as any).isProjectComponent && elementsFound === 0) {
1117
+ // No elements found inside project component - try fallback as wrapper
1118
+ if (this.config.verbose) {
1119
+ console.log(` ${' '.repeat(depth)} ⚠️ <${comp.name}> scanned but empty, trying wrapper fallback`);
1120
+ }
1121
+
1122
+ if (this.isCustomUIWrapper(comp.name, comp.importPath)) {
1123
+ // Extract as synthetic element
1124
+ const { tag: elementType, role } = this.inferWrapperType(comp.name);
1125
+
1126
+ this.elements.push({
1127
+ key: `e${++this.elementCounter}`,
1128
+ tag: elementType,
1129
+ role: role,
1130
+ id: comp.props.id,
1131
+ name: comp.props.name,
1132
+ placeholder: comp.props.placeholder,
1133
+ ariaLabel: comp.props['aria-label'] || comp.props.ariaLabel,
1134
+ testId: comp.props['data-testid'],
1135
+ text: null,
1136
+ source: sourceFileName,
1137
+ props: comp.props
1138
+ });
1139
+
1140
+ if (this.config.verbose) {
1141
+ console.log(` ${' '.repeat(depth)} ✅ Fallback: Extracted <${comp.name}> as ${elementType}`);
1142
+ }
1143
+ }
1144
+ } else if (this.config.verbose && elementsFound > 0) {
1145
+ console.log(` ${' '.repeat(depth)} ✅ Scanned <${comp.name}> → found ${elementsFound} element(s)`);
1146
+ }
1147
+ } else {
1148
+ // Cannot resolve path
1149
+ if (this.config.verbose) {
1150
+ console.log(` ${' '.repeat(depth)} ⚠️ Could not resolve: ${comp.name} from ${comp.importPath}`);
1151
+ }
1152
+
1153
+ // For project components that cannot be resolved, try wrapper fallback
1154
+ if ((comp as any).isProjectComponent && this.isCustomUIWrapper(comp.name, comp.importPath)) {
1155
+ const { tag: elementType, role } = this.inferWrapperType(comp.name);
1156
+
1157
+ this.elements.push({
1158
+ key: `e${++this.elementCounter}`,
1159
+ tag: elementType,
1160
+ role: role,
1161
+ id: comp.props.id,
1162
+ name: comp.props.name,
1163
+ placeholder: comp.props.placeholder,
1164
+ ariaLabel: comp.props['aria-label'] || comp.props.ariaLabel,
1165
+ testId: comp.props['data-testid'],
1166
+ text: null,
1167
+ source: sourceFileName,
1168
+ props: comp.props
1169
+ });
1170
+
1171
+ if (this.config.verbose) {
1172
+ console.log(` ${' '.repeat(depth)} ✅ Fallback (unresolved): Extracted <${comp.name}> as ${elementType}`);
1173
+ }
1174
+ }
1175
+ }
1176
+ }
1177
+ }
1178
+
1179
+ /**
1180
+ * Infer wrapper type from component name
1181
+ * Used for fallback when component is empty
1182
+ */
1183
+ private inferWrapperType(componentName: string): { tag: string; role: string } {
1184
+ if (/Input$/i.test(componentName)) {
1185
+ return { tag: 'input', role: 'textbox' };
1186
+ } else if (/TextArea$/i.test(componentName)) {
1187
+ return { tag: 'textarea', role: 'textbox' };
1188
+ } else if (/Select$/i.test(componentName)) {
1189
+ return { tag: 'select', role: 'combobox' };
1190
+ } else if (/Link$/i.test(componentName)) {
1191
+ return { tag: 'a', role: 'link' };
1192
+ } else if (/Button$/i.test(componentName)) {
1193
+ return { tag: 'button', role: 'button' };
1194
+ } else {
1195
+ // Default to div
1196
+ return { tag: 'div', role: undefined as any };
1197
+ }
1198
+ }
1199
+
1200
+ /**
1201
+ * Infer semantic role from tag name
1202
+ */
1203
+ private inferRole(tagName: string): string | undefined {
1204
+ const roleMap: Record<string, string> = {
1205
+ 'button': 'button',
1206
+ 'a': 'link',
1207
+ 'input': 'textbox',
1208
+ 'textarea': 'textbox',
1209
+ 'select': 'combobox',
1210
+ 'img': 'img',
1211
+ 'nav': 'navigation',
1212
+ 'main': 'main',
1213
+ 'header': 'banner',
1214
+ 'footer': 'contentinfo'
1215
+ };
1216
+
1217
+ return roleMap[tagName.toLowerCase()];
1218
+ }
1219
+
1220
+ /**
1221
+ * Get scan results with statistics
1222
+ */
1223
+ getResults() {
1224
+ const byTag: Record<string, number> = {};
1225
+ const formElements = [];
1226
+
1227
+ this.elements.forEach(el => {
1228
+ byTag[el.tag] = (byTag[el.tag] || 0) + 1;
1229
+
1230
+ const formTags = ['input', 'button', 'textarea', 'select', 'form'];
1231
+ if (formTags.includes(el.tag)) {
1232
+ formElements.push(el);
1233
+ }
1234
+ });
1235
+
1236
+ return {
1237
+ totalElements: this.elements.length,
1238
+ filesScanned: this.scannedFiles.size,
1239
+ byTag,
1240
+ formElements,
1241
+ elements: this.elements
1242
+ };
1243
+ }
1244
+ }