driftdetect-detectors 0.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 (509) hide show
  1. package/dist/accessibility/alt-text.d.ts +63 -0
  2. package/dist/accessibility/alt-text.d.ts.map +1 -0
  3. package/dist/accessibility/alt-text.js +100 -0
  4. package/dist/accessibility/alt-text.js.map +1 -0
  5. package/dist/accessibility/aria-roles.d.ts +65 -0
  6. package/dist/accessibility/aria-roles.d.ts.map +1 -0
  7. package/dist/accessibility/aria-roles.js +87 -0
  8. package/dist/accessibility/aria-roles.js.map +1 -0
  9. package/dist/accessibility/focus-management.d.ts +62 -0
  10. package/dist/accessibility/focus-management.d.ts.map +1 -0
  11. package/dist/accessibility/focus-management.js +88 -0
  12. package/dist/accessibility/focus-management.js.map +1 -0
  13. package/dist/accessibility/heading-hierarchy.d.ts +66 -0
  14. package/dist/accessibility/heading-hierarchy.d.ts.map +1 -0
  15. package/dist/accessibility/heading-hierarchy.js +94 -0
  16. package/dist/accessibility/heading-hierarchy.js.map +1 -0
  17. package/dist/accessibility/index.d.ts +25 -0
  18. package/dist/accessibility/index.d.ts.map +1 -0
  19. package/dist/accessibility/index.js +21 -0
  20. package/dist/accessibility/index.js.map +1 -0
  21. package/dist/accessibility/keyboard-nav.d.ts +63 -0
  22. package/dist/accessibility/keyboard-nav.d.ts.map +1 -0
  23. package/dist/accessibility/keyboard-nav.js +86 -0
  24. package/dist/accessibility/keyboard-nav.js.map +1 -0
  25. package/dist/accessibility/semantic-html.d.ts +76 -0
  26. package/dist/accessibility/semantic-html.d.ts.map +1 -0
  27. package/dist/accessibility/semantic-html.js +204 -0
  28. package/dist/accessibility/semantic-html.js.map +1 -0
  29. package/dist/api/client-patterns.d.ts +121 -0
  30. package/dist/api/client-patterns.d.ts.map +1 -0
  31. package/dist/api/client-patterns.js +478 -0
  32. package/dist/api/client-patterns.js.map +1 -0
  33. package/dist/api/error-format.d.ts +140 -0
  34. package/dist/api/error-format.d.ts.map +1 -0
  35. package/dist/api/error-format.js +614 -0
  36. package/dist/api/error-format.js.map +1 -0
  37. package/dist/api/http-methods.d.ts +255 -0
  38. package/dist/api/http-methods.d.ts.map +1 -0
  39. package/dist/api/http-methods.js +890 -0
  40. package/dist/api/http-methods.js.map +1 -0
  41. package/dist/api/index.d.ts +16 -0
  42. package/dist/api/index.d.ts.map +1 -0
  43. package/dist/api/index.js +37 -0
  44. package/dist/api/index.js.map +1 -0
  45. package/dist/api/pagination.d.ts +133 -0
  46. package/dist/api/pagination.d.ts.map +1 -0
  47. package/dist/api/pagination.js +521 -0
  48. package/dist/api/pagination.js.map +1 -0
  49. package/dist/api/response-envelope.d.ts +261 -0
  50. package/dist/api/response-envelope.d.ts.map +1 -0
  51. package/dist/api/response-envelope.js +1050 -0
  52. package/dist/api/response-envelope.js.map +1 -0
  53. package/dist/api/retry-patterns.d.ts +117 -0
  54. package/dist/api/retry-patterns.d.ts.map +1 -0
  55. package/dist/api/retry-patterns.js +480 -0
  56. package/dist/api/retry-patterns.js.map +1 -0
  57. package/dist/api/route-structure.d.ts +128 -0
  58. package/dist/api/route-structure.d.ts.map +1 -0
  59. package/dist/api/route-structure.js +738 -0
  60. package/dist/api/route-structure.js.map +1 -0
  61. package/dist/auth/audit-logging.d.ts +80 -0
  62. package/dist/auth/audit-logging.d.ts.map +1 -0
  63. package/dist/auth/audit-logging.js +370 -0
  64. package/dist/auth/audit-logging.js.map +1 -0
  65. package/dist/auth/index.d.ts +33 -0
  66. package/dist/auth/index.d.ts.map +1 -0
  67. package/dist/auth/index.js +49 -0
  68. package/dist/auth/index.js.map +1 -0
  69. package/dist/auth/middleware-usage.d.ts +65 -0
  70. package/dist/auth/middleware-usage.d.ts.map +1 -0
  71. package/dist/auth/middleware-usage.js +192 -0
  72. package/dist/auth/middleware-usage.js.map +1 -0
  73. package/dist/auth/permission-checks.d.ts +60 -0
  74. package/dist/auth/permission-checks.d.ts.map +1 -0
  75. package/dist/auth/permission-checks.js +159 -0
  76. package/dist/auth/permission-checks.js.map +1 -0
  77. package/dist/auth/rbac-patterns.d.ts +68 -0
  78. package/dist/auth/rbac-patterns.d.ts.map +1 -0
  79. package/dist/auth/rbac-patterns.js +143 -0
  80. package/dist/auth/rbac-patterns.js.map +1 -0
  81. package/dist/auth/resource-ownership.d.ts +77 -0
  82. package/dist/auth/resource-ownership.d.ts.map +1 -0
  83. package/dist/auth/resource-ownership.js +324 -0
  84. package/dist/auth/resource-ownership.js.map +1 -0
  85. package/dist/auth/token-handling.d.ts +64 -0
  86. package/dist/auth/token-handling.d.ts.map +1 -0
  87. package/dist/auth/token-handling.js +151 -0
  88. package/dist/auth/token-handling.js.map +1 -0
  89. package/dist/base/ast-detector.d.ts +421 -0
  90. package/dist/base/ast-detector.d.ts.map +1 -0
  91. package/dist/base/ast-detector.js +699 -0
  92. package/dist/base/ast-detector.js.map +1 -0
  93. package/dist/base/base-detector.d.ts +366 -0
  94. package/dist/base/base-detector.d.ts.map +1 -0
  95. package/dist/base/base-detector.js +170 -0
  96. package/dist/base/base-detector.js.map +1 -0
  97. package/dist/base/index.d.ts +12 -0
  98. package/dist/base/index.d.ts.map +1 -0
  99. package/dist/base/index.js +17 -0
  100. package/dist/base/index.js.map +1 -0
  101. package/dist/base/regex-detector.d.ts +421 -0
  102. package/dist/base/regex-detector.d.ts.map +1 -0
  103. package/dist/base/regex-detector.js +537 -0
  104. package/dist/base/regex-detector.js.map +1 -0
  105. package/dist/base/structural-detector.d.ts +424 -0
  106. package/dist/base/structural-detector.d.ts.map +1 -0
  107. package/dist/base/structural-detector.js +731 -0
  108. package/dist/base/structural-detector.js.map +1 -0
  109. package/dist/base/types.d.ts +53 -0
  110. package/dist/base/types.d.ts.map +1 -0
  111. package/dist/base/types.js +5 -0
  112. package/dist/base/types.js.map +1 -0
  113. package/dist/components/component-structure.d.ts +163 -0
  114. package/dist/components/component-structure.d.ts.map +1 -0
  115. package/dist/components/component-structure.js +500 -0
  116. package/dist/components/component-structure.js.map +1 -0
  117. package/dist/components/composition.d.ts +287 -0
  118. package/dist/components/composition.d.ts.map +1 -0
  119. package/dist/components/composition.js +1123 -0
  120. package/dist/components/composition.js.map +1 -0
  121. package/dist/components/duplicate-detection.d.ts +251 -0
  122. package/dist/components/duplicate-detection.d.ts.map +1 -0
  123. package/dist/components/duplicate-detection.js +804 -0
  124. package/dist/components/duplicate-detection.js.map +1 -0
  125. package/dist/components/index.d.ts +16 -0
  126. package/dist/components/index.d.ts.map +1 -0
  127. package/dist/components/index.js +51 -0
  128. package/dist/components/index.js.map +1 -0
  129. package/dist/components/near-duplicate.d.ts +402 -0
  130. package/dist/components/near-duplicate.d.ts.map +1 -0
  131. package/dist/components/near-duplicate.js +1090 -0
  132. package/dist/components/near-duplicate.js.map +1 -0
  133. package/dist/components/props-patterns.d.ts +194 -0
  134. package/dist/components/props-patterns.d.ts.map +1 -0
  135. package/dist/components/props-patterns.js +795 -0
  136. package/dist/components/props-patterns.js.map +1 -0
  137. package/dist/components/ref-forwarding.d.ts +250 -0
  138. package/dist/components/ref-forwarding.d.ts.map +1 -0
  139. package/dist/components/ref-forwarding.js +832 -0
  140. package/dist/components/ref-forwarding.js.map +1 -0
  141. package/dist/components/state-patterns.d.ts +291 -0
  142. package/dist/components/state-patterns.d.ts.map +1 -0
  143. package/dist/components/state-patterns.js +970 -0
  144. package/dist/components/state-patterns.js.map +1 -0
  145. package/dist/config/config-validation.d.ts +74 -0
  146. package/dist/config/config-validation.d.ts.map +1 -0
  147. package/dist/config/config-validation.js +446 -0
  148. package/dist/config/config-validation.js.map +1 -0
  149. package/dist/config/default-values.d.ts +72 -0
  150. package/dist/config/default-values.d.ts.map +1 -0
  151. package/dist/config/default-values.js +386 -0
  152. package/dist/config/default-values.js.map +1 -0
  153. package/dist/config/env-naming.d.ts +73 -0
  154. package/dist/config/env-naming.d.ts.map +1 -0
  155. package/dist/config/env-naming.js +429 -0
  156. package/dist/config/env-naming.js.map +1 -0
  157. package/dist/config/environment-detection.d.ts +72 -0
  158. package/dist/config/environment-detection.d.ts.map +1 -0
  159. package/dist/config/environment-detection.js +400 -0
  160. package/dist/config/environment-detection.js.map +1 -0
  161. package/dist/config/feature-flags.d.ts +72 -0
  162. package/dist/config/feature-flags.d.ts.map +1 -0
  163. package/dist/config/feature-flags.js +384 -0
  164. package/dist/config/feature-flags.js.map +1 -0
  165. package/dist/config/index.d.ts +27 -0
  166. package/dist/config/index.d.ts.map +1 -0
  167. package/dist/config/index.js +43 -0
  168. package/dist/config/index.js.map +1 -0
  169. package/dist/config/required-optional.d.ts +71 -0
  170. package/dist/config/required-optional.d.ts.map +1 -0
  171. package/dist/config/required-optional.js +344 -0
  172. package/dist/config/required-optional.js.map +1 -0
  173. package/dist/data-access/connection-pooling.d.ts +63 -0
  174. package/dist/data-access/connection-pooling.d.ts.map +1 -0
  175. package/dist/data-access/connection-pooling.js +297 -0
  176. package/dist/data-access/connection-pooling.js.map +1 -0
  177. package/dist/data-access/dto-patterns.d.ts +64 -0
  178. package/dist/data-access/dto-patterns.d.ts.map +1 -0
  179. package/dist/data-access/dto-patterns.js +291 -0
  180. package/dist/data-access/dto-patterns.js.map +1 -0
  181. package/dist/data-access/index.d.ts +31 -0
  182. package/dist/data-access/index.d.ts.map +1 -0
  183. package/dist/data-access/index.js +49 -0
  184. package/dist/data-access/index.js.map +1 -0
  185. package/dist/data-access/n-plus-one.d.ts +60 -0
  186. package/dist/data-access/n-plus-one.d.ts.map +1 -0
  187. package/dist/data-access/n-plus-one.js +264 -0
  188. package/dist/data-access/n-plus-one.js.map +1 -0
  189. package/dist/data-access/query-patterns.d.ts +64 -0
  190. package/dist/data-access/query-patterns.d.ts.map +1 -0
  191. package/dist/data-access/query-patterns.js +314 -0
  192. package/dist/data-access/query-patterns.js.map +1 -0
  193. package/dist/data-access/repository-pattern.d.ts +62 -0
  194. package/dist/data-access/repository-pattern.d.ts.map +1 -0
  195. package/dist/data-access/repository-pattern.js +257 -0
  196. package/dist/data-access/repository-pattern.js.map +1 -0
  197. package/dist/data-access/transaction-patterns.d.ts +61 -0
  198. package/dist/data-access/transaction-patterns.d.ts.map +1 -0
  199. package/dist/data-access/transaction-patterns.js +277 -0
  200. package/dist/data-access/transaction-patterns.js.map +1 -0
  201. package/dist/data-access/validation-patterns.d.ts +62 -0
  202. package/dist/data-access/validation-patterns.d.ts.map +1 -0
  203. package/dist/data-access/validation-patterns.js +301 -0
  204. package/dist/data-access/validation-patterns.js.map +1 -0
  205. package/dist/documentation/deprecation.d.ts +62 -0
  206. package/dist/documentation/deprecation.d.ts.map +1 -0
  207. package/dist/documentation/deprecation.js +83 -0
  208. package/dist/documentation/deprecation.js.map +1 -0
  209. package/dist/documentation/example-code.d.ts +64 -0
  210. package/dist/documentation/example-code.d.ts.map +1 -0
  211. package/dist/documentation/example-code.js +79 -0
  212. package/dist/documentation/example-code.js.map +1 -0
  213. package/dist/documentation/index.d.ts +22 -0
  214. package/dist/documentation/index.d.ts.map +1 -0
  215. package/dist/documentation/index.js +19 -0
  216. package/dist/documentation/index.js.map +1 -0
  217. package/dist/documentation/jsdoc-patterns.d.ts +72 -0
  218. package/dist/documentation/jsdoc-patterns.d.ts.map +1 -0
  219. package/dist/documentation/jsdoc-patterns.js +92 -0
  220. package/dist/documentation/jsdoc-patterns.js.map +1 -0
  221. package/dist/documentation/readme-structure.d.ts +67 -0
  222. package/dist/documentation/readme-structure.d.ts.map +1 -0
  223. package/dist/documentation/readme-structure.js +76 -0
  224. package/dist/documentation/readme-structure.js.map +1 -0
  225. package/dist/documentation/todo-patterns.d.ts +67 -0
  226. package/dist/documentation/todo-patterns.d.ts.map +1 -0
  227. package/dist/documentation/todo-patterns.js +73 -0
  228. package/dist/documentation/todo-patterns.js.map +1 -0
  229. package/dist/errors/async-errors.d.ts +72 -0
  230. package/dist/errors/async-errors.d.ts.map +1 -0
  231. package/dist/errors/async-errors.js +214 -0
  232. package/dist/errors/async-errors.js.map +1 -0
  233. package/dist/errors/circuit-breaker.d.ts +53 -0
  234. package/dist/errors/circuit-breaker.d.ts.map +1 -0
  235. package/dist/errors/circuit-breaker.js +241 -0
  236. package/dist/errors/circuit-breaker.js.map +1 -0
  237. package/dist/errors/error-codes.d.ts +73 -0
  238. package/dist/errors/error-codes.d.ts.map +1 -0
  239. package/dist/errors/error-codes.js +211 -0
  240. package/dist/errors/error-codes.js.map +1 -0
  241. package/dist/errors/error-logging.d.ts +73 -0
  242. package/dist/errors/error-logging.d.ts.map +1 -0
  243. package/dist/errors/error-logging.js +256 -0
  244. package/dist/errors/error-logging.js.map +1 -0
  245. package/dist/errors/error-propagation.d.ts +73 -0
  246. package/dist/errors/error-propagation.d.ts.map +1 -0
  247. package/dist/errors/error-propagation.js +244 -0
  248. package/dist/errors/error-propagation.js.map +1 -0
  249. package/dist/errors/exception-hierarchy.d.ts +75 -0
  250. package/dist/errors/exception-hierarchy.d.ts.map +1 -0
  251. package/dist/errors/exception-hierarchy.js +259 -0
  252. package/dist/errors/exception-hierarchy.js.map +1 -0
  253. package/dist/errors/index.d.ts +31 -0
  254. package/dist/errors/index.d.ts.map +1 -0
  255. package/dist/errors/index.js +49 -0
  256. package/dist/errors/index.js.map +1 -0
  257. package/dist/errors/try-catch-placement.d.ts +73 -0
  258. package/dist/errors/try-catch-placement.d.ts.map +1 -0
  259. package/dist/errors/try-catch-placement.js +214 -0
  260. package/dist/errors/try-catch-placement.js.map +1 -0
  261. package/dist/index.d.ts +221 -0
  262. package/dist/index.d.ts.map +1 -0
  263. package/dist/index.js +245 -0
  264. package/dist/index.js.map +1 -0
  265. package/dist/logging/context-fields.d.ts +48 -0
  266. package/dist/logging/context-fields.d.ts.map +1 -0
  267. package/dist/logging/context-fields.js +160 -0
  268. package/dist/logging/context-fields.js.map +1 -0
  269. package/dist/logging/correlation-ids.d.ts +44 -0
  270. package/dist/logging/correlation-ids.d.ts.map +1 -0
  271. package/dist/logging/correlation-ids.js +144 -0
  272. package/dist/logging/correlation-ids.js.map +1 -0
  273. package/dist/logging/health-checks.d.ts +45 -0
  274. package/dist/logging/health-checks.d.ts.map +1 -0
  275. package/dist/logging/health-checks.js +165 -0
  276. package/dist/logging/health-checks.js.map +1 -0
  277. package/dist/logging/index.d.ts +31 -0
  278. package/dist/logging/index.d.ts.map +1 -0
  279. package/dist/logging/index.js +49 -0
  280. package/dist/logging/index.js.map +1 -0
  281. package/dist/logging/log-levels.d.ts +46 -0
  282. package/dist/logging/log-levels.d.ts.map +1 -0
  283. package/dist/logging/log-levels.js +178 -0
  284. package/dist/logging/log-levels.js.map +1 -0
  285. package/dist/logging/metric-naming.d.ts +46 -0
  286. package/dist/logging/metric-naming.d.ts.map +1 -0
  287. package/dist/logging/metric-naming.js +157 -0
  288. package/dist/logging/metric-naming.js.map +1 -0
  289. package/dist/logging/pii-redaction.d.ts +44 -0
  290. package/dist/logging/pii-redaction.d.ts.map +1 -0
  291. package/dist/logging/pii-redaction.js +166 -0
  292. package/dist/logging/pii-redaction.js.map +1 -0
  293. package/dist/logging/structured-format.d.ts +53 -0
  294. package/dist/logging/structured-format.d.ts.map +1 -0
  295. package/dist/logging/structured-format.js +235 -0
  296. package/dist/logging/structured-format.js.map +1 -0
  297. package/dist/performance/bundle-size.d.ts +79 -0
  298. package/dist/performance/bundle-size.d.ts.map +1 -0
  299. package/dist/performance/bundle-size.js +276 -0
  300. package/dist/performance/bundle-size.js.map +1 -0
  301. package/dist/performance/caching-patterns.d.ts +78 -0
  302. package/dist/performance/caching-patterns.d.ts.map +1 -0
  303. package/dist/performance/caching-patterns.js +257 -0
  304. package/dist/performance/caching-patterns.js.map +1 -0
  305. package/dist/performance/code-splitting.d.ts +86 -0
  306. package/dist/performance/code-splitting.d.ts.map +1 -0
  307. package/dist/performance/code-splitting.js +447 -0
  308. package/dist/performance/code-splitting.js.map +1 -0
  309. package/dist/performance/debounce-throttle.d.ts +75 -0
  310. package/dist/performance/debounce-throttle.d.ts.map +1 -0
  311. package/dist/performance/debounce-throttle.js +232 -0
  312. package/dist/performance/debounce-throttle.js.map +1 -0
  313. package/dist/performance/index.d.ts +28 -0
  314. package/dist/performance/index.d.ts.map +1 -0
  315. package/dist/performance/index.js +39 -0
  316. package/dist/performance/index.js.map +1 -0
  317. package/dist/performance/lazy-loading.d.ts +75 -0
  318. package/dist/performance/lazy-loading.d.ts.map +1 -0
  319. package/dist/performance/lazy-loading.js +233 -0
  320. package/dist/performance/lazy-loading.js.map +1 -0
  321. package/dist/performance/memoization.d.ts +75 -0
  322. package/dist/performance/memoization.d.ts.map +1 -0
  323. package/dist/performance/memoization.js +251 -0
  324. package/dist/performance/memoization.js.map +1 -0
  325. package/dist/registry/detector-registry.d.ts +266 -0
  326. package/dist/registry/detector-registry.d.ts.map +1 -0
  327. package/dist/registry/detector-registry.js +526 -0
  328. package/dist/registry/detector-registry.js.map +1 -0
  329. package/dist/registry/index.d.ts +10 -0
  330. package/dist/registry/index.d.ts.map +1 -0
  331. package/dist/registry/index.js +10 -0
  332. package/dist/registry/index.js.map +1 -0
  333. package/dist/registry/loader.d.ts +232 -0
  334. package/dist/registry/loader.d.ts.map +1 -0
  335. package/dist/registry/loader.js +419 -0
  336. package/dist/registry/loader.js.map +1 -0
  337. package/dist/registry/types.d.ts +111 -0
  338. package/dist/registry/types.d.ts.map +1 -0
  339. package/dist/registry/types.js +19 -0
  340. package/dist/registry/types.js.map +1 -0
  341. package/dist/security/csp-headers.d.ts +78 -0
  342. package/dist/security/csp-headers.d.ts.map +1 -0
  343. package/dist/security/csp-headers.js +401 -0
  344. package/dist/security/csp-headers.js.map +1 -0
  345. package/dist/security/csrf-protection.d.ts +72 -0
  346. package/dist/security/csrf-protection.d.ts.map +1 -0
  347. package/dist/security/csrf-protection.js +344 -0
  348. package/dist/security/csrf-protection.js.map +1 -0
  349. package/dist/security/index.d.ts +30 -0
  350. package/dist/security/index.d.ts.map +1 -0
  351. package/dist/security/index.js +48 -0
  352. package/dist/security/index.js.map +1 -0
  353. package/dist/security/input-sanitization.d.ts +74 -0
  354. package/dist/security/input-sanitization.d.ts.map +1 -0
  355. package/dist/security/input-sanitization.js +373 -0
  356. package/dist/security/input-sanitization.js.map +1 -0
  357. package/dist/security/rate-limiting.d.ts +81 -0
  358. package/dist/security/rate-limiting.d.ts.map +1 -0
  359. package/dist/security/rate-limiting.js +535 -0
  360. package/dist/security/rate-limiting.js.map +1 -0
  361. package/dist/security/secret-management.d.ts +83 -0
  362. package/dist/security/secret-management.d.ts.map +1 -0
  363. package/dist/security/secret-management.js +547 -0
  364. package/dist/security/secret-management.js.map +1 -0
  365. package/dist/security/sql-injection.d.ts +76 -0
  366. package/dist/security/sql-injection.d.ts.map +1 -0
  367. package/dist/security/sql-injection.js +383 -0
  368. package/dist/security/sql-injection.js.map +1 -0
  369. package/dist/security/xss-prevention.d.ts +80 -0
  370. package/dist/security/xss-prevention.d.ts.map +1 -0
  371. package/dist/security/xss-prevention.js +416 -0
  372. package/dist/security/xss-prevention.js.map +1 -0
  373. package/dist/structural/barrel-exports.d.ts +178 -0
  374. package/dist/structural/barrel-exports.d.ts.map +1 -0
  375. package/dist/structural/barrel-exports.js +553 -0
  376. package/dist/structural/barrel-exports.js.map +1 -0
  377. package/dist/structural/circular-deps.d.ts +140 -0
  378. package/dist/structural/circular-deps.d.ts.map +1 -0
  379. package/dist/structural/circular-deps.js +422 -0
  380. package/dist/structural/circular-deps.js.map +1 -0
  381. package/dist/structural/co-location.d.ts +202 -0
  382. package/dist/structural/co-location.d.ts.map +1 -0
  383. package/dist/structural/co-location.js +640 -0
  384. package/dist/structural/co-location.js.map +1 -0
  385. package/dist/structural/directory-structure.d.ts +151 -0
  386. package/dist/structural/directory-structure.d.ts.map +1 -0
  387. package/dist/structural/directory-structure.js +457 -0
  388. package/dist/structural/directory-structure.js.map +1 -0
  389. package/dist/structural/file-naming.d.ts +61 -0
  390. package/dist/structural/file-naming.d.ts.map +1 -0
  391. package/dist/structural/file-naming.js +231 -0
  392. package/dist/structural/file-naming.js.map +1 -0
  393. package/dist/structural/import-ordering.d.ts +212 -0
  394. package/dist/structural/import-ordering.d.ts.map +1 -0
  395. package/dist/structural/import-ordering.js +821 -0
  396. package/dist/structural/import-ordering.js.map +1 -0
  397. package/dist/structural/index.d.ts +23 -0
  398. package/dist/structural/index.d.ts.map +1 -0
  399. package/dist/structural/index.js +26 -0
  400. package/dist/structural/index.js.map +1 -0
  401. package/dist/structural/module-boundaries.d.ts +164 -0
  402. package/dist/structural/module-boundaries.d.ts.map +1 -0
  403. package/dist/structural/module-boundaries.js +616 -0
  404. package/dist/structural/module-boundaries.js.map +1 -0
  405. package/dist/structural/package-boundaries.d.ts +182 -0
  406. package/dist/structural/package-boundaries.d.ts.map +1 -0
  407. package/dist/structural/package-boundaries.js +602 -0
  408. package/dist/structural/package-boundaries.js.map +1 -0
  409. package/dist/styling/class-naming.d.ts +263 -0
  410. package/dist/styling/class-naming.d.ts.map +1 -0
  411. package/dist/styling/class-naming.js +892 -0
  412. package/dist/styling/class-naming.js.map +1 -0
  413. package/dist/styling/color-usage.d.ts +213 -0
  414. package/dist/styling/color-usage.d.ts.map +1 -0
  415. package/dist/styling/color-usage.js +732 -0
  416. package/dist/styling/color-usage.js.map +1 -0
  417. package/dist/styling/design-tokens.d.ts +212 -0
  418. package/dist/styling/design-tokens.d.ts.map +1 -0
  419. package/dist/styling/design-tokens.js +748 -0
  420. package/dist/styling/design-tokens.js.map +1 -0
  421. package/dist/styling/index.d.ts +16 -0
  422. package/dist/styling/index.d.ts.map +1 -0
  423. package/dist/styling/index.js +56 -0
  424. package/dist/styling/index.js.map +1 -0
  425. package/dist/styling/responsive.d.ts +304 -0
  426. package/dist/styling/responsive.d.ts.map +1 -0
  427. package/dist/styling/responsive.js +888 -0
  428. package/dist/styling/responsive.js.map +1 -0
  429. package/dist/styling/spacing-scale.d.ts +248 -0
  430. package/dist/styling/spacing-scale.d.ts.map +1 -0
  431. package/dist/styling/spacing-scale.js +865 -0
  432. package/dist/styling/spacing-scale.js.map +1 -0
  433. package/dist/styling/tailwind-patterns.d.ts +305 -0
  434. package/dist/styling/tailwind-patterns.d.ts.map +1 -0
  435. package/dist/styling/tailwind-patterns.js +1181 -0
  436. package/dist/styling/tailwind-patterns.js.map +1 -0
  437. package/dist/styling/typography.d.ts +281 -0
  438. package/dist/styling/typography.d.ts.map +1 -0
  439. package/dist/styling/typography.js +1004 -0
  440. package/dist/styling/typography.js.map +1 -0
  441. package/dist/styling/z-index-scale.d.ts +270 -0
  442. package/dist/styling/z-index-scale.d.ts.map +1 -0
  443. package/dist/styling/z-index-scale.js +714 -0
  444. package/dist/styling/z-index-scale.js.map +1 -0
  445. package/dist/testing/co-location.d.ts +42 -0
  446. package/dist/testing/co-location.d.ts.map +1 -0
  447. package/dist/testing/co-location.js +134 -0
  448. package/dist/testing/co-location.js.map +1 -0
  449. package/dist/testing/describe-naming.d.ts +47 -0
  450. package/dist/testing/describe-naming.d.ts.map +1 -0
  451. package/dist/testing/describe-naming.js +150 -0
  452. package/dist/testing/describe-naming.js.map +1 -0
  453. package/dist/testing/file-naming.d.ts +44 -0
  454. package/dist/testing/file-naming.d.ts.map +1 -0
  455. package/dist/testing/file-naming.js +131 -0
  456. package/dist/testing/file-naming.js.map +1 -0
  457. package/dist/testing/fixture-patterns.d.ts +52 -0
  458. package/dist/testing/fixture-patterns.d.ts.map +1 -0
  459. package/dist/testing/fixture-patterns.js +228 -0
  460. package/dist/testing/fixture-patterns.js.map +1 -0
  461. package/dist/testing/index.d.ts +31 -0
  462. package/dist/testing/index.d.ts.map +1 -0
  463. package/dist/testing/index.js +49 -0
  464. package/dist/testing/index.js.map +1 -0
  465. package/dist/testing/mock-patterns.d.ts +53 -0
  466. package/dist/testing/mock-patterns.d.ts.map +1 -0
  467. package/dist/testing/mock-patterns.js +264 -0
  468. package/dist/testing/mock-patterns.js.map +1 -0
  469. package/dist/testing/setup-teardown.d.ts +55 -0
  470. package/dist/testing/setup-teardown.d.ts.map +1 -0
  471. package/dist/testing/setup-teardown.js +262 -0
  472. package/dist/testing/setup-teardown.js.map +1 -0
  473. package/dist/testing/test-structure.d.ts +51 -0
  474. package/dist/testing/test-structure.d.ts.map +1 -0
  475. package/dist/testing/test-structure.js +225 -0
  476. package/dist/testing/test-structure.js.map +1 -0
  477. package/dist/types/any-usage.d.ts +99 -0
  478. package/dist/types/any-usage.d.ts.map +1 -0
  479. package/dist/types/any-usage.js +641 -0
  480. package/dist/types/any-usage.js.map +1 -0
  481. package/dist/types/file-location.d.ts +76 -0
  482. package/dist/types/file-location.d.ts.map +1 -0
  483. package/dist/types/file-location.js +395 -0
  484. package/dist/types/file-location.js.map +1 -0
  485. package/dist/types/generic-patterns.d.ts +97 -0
  486. package/dist/types/generic-patterns.d.ts.map +1 -0
  487. package/dist/types/generic-patterns.js +615 -0
  488. package/dist/types/generic-patterns.js.map +1 -0
  489. package/dist/types/index.d.ts +31 -0
  490. package/dist/types/index.d.ts.map +1 -0
  491. package/dist/types/index.js +43 -0
  492. package/dist/types/index.js.map +1 -0
  493. package/dist/types/interface-vs-type.d.ts +81 -0
  494. package/dist/types/interface-vs-type.d.ts.map +1 -0
  495. package/dist/types/interface-vs-type.js +440 -0
  496. package/dist/types/interface-vs-type.js.map +1 -0
  497. package/dist/types/naming-conventions.d.ts +84 -0
  498. package/dist/types/naming-conventions.d.ts.map +1 -0
  499. package/dist/types/naming-conventions.js +455 -0
  500. package/dist/types/naming-conventions.js.map +1 -0
  501. package/dist/types/type-assertions.d.ts +98 -0
  502. package/dist/types/type-assertions.d.ts.map +1 -0
  503. package/dist/types/type-assertions.js +639 -0
  504. package/dist/types/type-assertions.js.map +1 -0
  505. package/dist/types/utility-types.d.ts +110 -0
  506. package/dist/types/utility-types.d.ts.map +1 -0
  507. package/dist/types/utility-types.js +547 -0
  508. package/dist/types/utility-types.js.map +1 -0
  509. package/package.json +44 -0
@@ -0,0 +1,1090 @@
1
+ /**
2
+ * Near Duplicate Detector - Semantic similarity detection for abstraction candidates
3
+ *
4
+ * Detects semantically similar components that could be refactored into shared
5
+ * abstractions. Unlike duplicate-detection.ts which focuses on AST structure,
6
+ * this detector analyzes functional similarity including:
7
+ * - Similar prop patterns
8
+ * - Similar render patterns
9
+ * - Similar state management
10
+ * - Opportunities for shared hooks, HOCs, or render props
11
+ *
12
+ * @requirements 8.4 - THE Component_Detector SHALL detect near-duplicate components that should be abstracted
13
+ */
14
+ import { ASTDetector } from '../base/index.js';
15
+ // ============================================================================
16
+ // Constants
17
+ // ============================================================================
18
+ /**
19
+ * Default configuration for near-duplicate detection
20
+ */
21
+ export const DEFAULT_NEAR_DUPLICATE_CONFIG = {
22
+ similarityThreshold: 0.6,
23
+ minPropsSimilarity: 0.5,
24
+ minJsxSimilarity: 0.4,
25
+ detectHookOpportunities: true,
26
+ detectHOCOpportunities: true,
27
+ weights: {
28
+ props: 0.25,
29
+ state: 0.15,
30
+ hooks: 0.15,
31
+ eventHandlers: 0.1,
32
+ jsxStructure: 0.2,
33
+ conditionalPatterns: 0.1,
34
+ dataPatterns: 0.05,
35
+ },
36
+ };
37
+ /**
38
+ * Built-in React hooks
39
+ */
40
+ export const REACT_HOOKS = new Set([
41
+ 'useState',
42
+ 'useEffect',
43
+ 'useContext',
44
+ 'useReducer',
45
+ 'useCallback',
46
+ 'useMemo',
47
+ 'useRef',
48
+ 'useImperativeHandle',
49
+ 'useLayoutEffect',
50
+ 'useDebugValue',
51
+ 'useDeferredValue',
52
+ 'useTransition',
53
+ 'useId',
54
+ 'useSyncExternalStore',
55
+ 'useInsertionEffect',
56
+ ]);
57
+ /**
58
+ * Common event handler patterns
59
+ */
60
+ export const EVENT_HANDLER_PATTERNS = [
61
+ 'onClick',
62
+ 'onChange',
63
+ 'onSubmit',
64
+ 'onBlur',
65
+ 'onFocus',
66
+ 'onKeyDown',
67
+ 'onKeyUp',
68
+ 'onKeyPress',
69
+ 'onMouseEnter',
70
+ 'onMouseLeave',
71
+ 'onScroll',
72
+ 'onLoad',
73
+ 'onError',
74
+ ];
75
+ // ============================================================================
76
+ // Helper Functions - Feature Extraction
77
+ // ============================================================================
78
+ /**
79
+ * Check if a node represents a React component
80
+ */
81
+ export function isReactComponentNode(node, content) {
82
+ if (node.type === 'function_declaration' ||
83
+ node.type === 'arrow_function' ||
84
+ node.type === 'function_expression') {
85
+ const name = extractComponentName(node, content);
86
+ if (!name || !/^[A-Z]/.test(name)) {
87
+ return false;
88
+ }
89
+ const nodeText = node.text;
90
+ return nodeText.includes('<') && (nodeText.includes('/>') || nodeText.includes('</'));
91
+ }
92
+ return false;
93
+ }
94
+ /**
95
+ * Extract component name from an AST node
96
+ */
97
+ export function extractComponentName(node, content) {
98
+ if (node.type === 'function_declaration') {
99
+ const nameNode = node.children.find(c => c.type === 'identifier');
100
+ return nameNode?.text;
101
+ }
102
+ const lines = content.split('\n');
103
+ const line = lines[node.startPosition.row];
104
+ if (line) {
105
+ const match = line.match(/(?:const|let|var|export\s+(?:const|let|var)?)\s+([A-Z][a-zA-Z0-9]*)\s*[=:]/);
106
+ if (match && match[1]) {
107
+ return match[1];
108
+ }
109
+ }
110
+ return undefined;
111
+ }
112
+ /**
113
+ * Extract props from a component
114
+ */
115
+ export function extractProps(node, _content) {
116
+ const props = [];
117
+ const nodeText = node.text;
118
+ // Extract from destructuring pattern: ({ prop1, prop2 = 'default', ...rest })
119
+ const destructuringMatch = nodeText.match(/\(\s*\{\s*([^}]+)\s*\}/);
120
+ if (destructuringMatch && destructuringMatch[1]) {
121
+ const propsStr = destructuringMatch[1];
122
+ const propParts = propsStr.split(',');
123
+ for (const part of propParts) {
124
+ const trimmed = part.trim();
125
+ if (trimmed.startsWith('...'))
126
+ continue; // Skip rest spread
127
+ // Match: propName, propName = default, propName: type
128
+ const propMatch = trimmed.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?:=\s*([^,]+))?/);
129
+ if (propMatch && propMatch[1]) {
130
+ const propName = propMatch[1];
131
+ const hasDefault = !!propMatch[2];
132
+ props.push({
133
+ name: propName,
134
+ hasDefault,
135
+ isRequired: !hasDefault,
136
+ isCallback: propName.startsWith('on') || propName.startsWith('handle'),
137
+ isChildren: propName === 'children',
138
+ });
139
+ }
140
+ }
141
+ }
142
+ // Also check for props.propName access
143
+ const directAccessMatches = nodeText.matchAll(/props\.([a-zA-Z_$][a-zA-Z0-9_$]*)/g);
144
+ for (const match of directAccessMatches) {
145
+ if (match[1] && !props.some(p => p.name === match[1])) {
146
+ props.push({
147
+ name: match[1],
148
+ hasDefault: false,
149
+ isRequired: true,
150
+ isCallback: match[1].startsWith('on') || match[1].startsWith('handle'),
151
+ isChildren: match[1] === 'children',
152
+ });
153
+ }
154
+ }
155
+ return props;
156
+ }
157
+ /**
158
+ * Extract state variables from a component
159
+ */
160
+ export function extractState(node) {
161
+ const state = [];
162
+ const nodeText = node.text;
163
+ // Match useState: const [value, setValue] = useState(initial)
164
+ const useStateMatches = nodeText.matchAll(/const\s+\[\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*,\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\]\s*=\s*useState\s*(?:<[^>]*>)?\s*\(([^)]*)\)/g);
165
+ for (const match of useStateMatches) {
166
+ if (match[1] && match[2]) {
167
+ const feature = {
168
+ name: match[1],
169
+ setter: match[2],
170
+ stateType: 'useState',
171
+ };
172
+ const initialValue = match[3]?.trim();
173
+ if (initialValue) {
174
+ feature.initialValue = initialValue;
175
+ }
176
+ state.push(feature);
177
+ }
178
+ }
179
+ // Match useReducer: const [state, dispatch] = useReducer(reducer, initial)
180
+ const useReducerMatches = nodeText.matchAll(/const\s+\[\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*,\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\]\s*=\s*useReducer/g);
181
+ for (const match of useReducerMatches) {
182
+ if (match[1] && match[2]) {
183
+ state.push({
184
+ name: match[1],
185
+ setter: match[2],
186
+ stateType: 'useReducer',
187
+ });
188
+ }
189
+ }
190
+ // Match useRef: const ref = useRef(initial)
191
+ const useRefMatches = nodeText.matchAll(/const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*useRef\s*(?:<[^>]*>)?\s*\(([^)]*)\)/g);
192
+ for (const match of useRefMatches) {
193
+ if (match[1]) {
194
+ const feature = {
195
+ name: match[1],
196
+ setter: `${match[1]}.current`,
197
+ stateType: 'useRef',
198
+ };
199
+ const initialValue = match[2]?.trim();
200
+ if (initialValue) {
201
+ feature.initialValue = initialValue;
202
+ }
203
+ state.push(feature);
204
+ }
205
+ }
206
+ return state;
207
+ }
208
+ /**
209
+ * Extract hooks used in a component
210
+ */
211
+ export function extractHooks(node) {
212
+ const hooks = [];
213
+ const nodeText = node.text;
214
+ // Match hook calls: useXxx(...) or use_xxx(...)
215
+ const hookMatches = nodeText.matchAll(/\b(use[A-Z][a-zA-Z0-9]*|use_[a-z][a-zA-Z0-9_]*)\s*\(/g);
216
+ const seenHooks = new Set();
217
+ for (const match of hookMatches) {
218
+ if (match[1] && !seenHooks.has(match[1])) {
219
+ seenHooks.add(match[1]);
220
+ const hookName = match[1];
221
+ const isBuiltIn = REACT_HOOKS.has(hookName);
222
+ const feature = {
223
+ name: hookName,
224
+ isBuiltIn,
225
+ };
226
+ // Try to extract dependencies for effect-like hooks
227
+ if (['useEffect', 'useCallback', 'useMemo', 'useLayoutEffect'].includes(hookName)) {
228
+ const depMatch = nodeText.match(new RegExp(`${hookName}\\s*\\([^)]*,\\s*\\[([^\\]]*)\\]`));
229
+ if (depMatch && depMatch[1]) {
230
+ feature.dependencies = depMatch[1].split(',').map(d => d.trim()).filter(d => d.length > 0);
231
+ }
232
+ }
233
+ hooks.push(feature);
234
+ }
235
+ }
236
+ return hooks;
237
+ }
238
+ /**
239
+ * Extract event handlers from a component
240
+ */
241
+ export function extractEventHandlers(node) {
242
+ const handlers = [];
243
+ const nodeText = node.text;
244
+ const seenHandlers = new Set();
245
+ // Match event handler props: onClick={handleClick} or onClick={() => ...}
246
+ for (const eventType of EVENT_HANDLER_PATTERNS) {
247
+ const handlerMatches = nodeText.matchAll(new RegExp(`${eventType}\\s*=\\s*\\{([^}]+)\\}`, 'g'));
248
+ for (const match of handlerMatches) {
249
+ if (match[1]) {
250
+ const handlerContent = match[1].trim();
251
+ const isInline = handlerContent.includes('=>') || handlerContent.includes('function');
252
+ const handlerName = isInline ? `inline_${eventType}` : handlerContent;
253
+ if (!seenHandlers.has(`${eventType}_${handlerName}`)) {
254
+ seenHandlers.add(`${eventType}_${handlerName}`);
255
+ handlers.push({
256
+ name: handlerName,
257
+ eventType,
258
+ isInline,
259
+ });
260
+ }
261
+ }
262
+ }
263
+ }
264
+ // Match handler function definitions: const handleClick = ...
265
+ const handlerDefMatches = nodeText.matchAll(/const\s+(handle[A-Z][a-zA-Z0-9]*|on[A-Z][a-zA-Z0-9]*)\s*=/g);
266
+ for (const match of handlerDefMatches) {
267
+ if (match[1] && !seenHandlers.has(match[1])) {
268
+ seenHandlers.add(match[1]);
269
+ handlers.push({
270
+ name: match[1],
271
+ eventType: 'custom',
272
+ isInline: false,
273
+ });
274
+ }
275
+ }
276
+ return handlers;
277
+ }
278
+ /**
279
+ * Extract JSX elements from a component
280
+ */
281
+ export function extractJSXElements(node) {
282
+ const elements = [];
283
+ const nodeText = node.text;
284
+ // Match JSX opening tags: <TagName prop1={...} prop2="...">
285
+ const jsxMatches = nodeText.matchAll(/<([A-Za-z][A-Za-z0-9.]*)\s*([^>]*?)(?:\/?>)/g);
286
+ for (const match of jsxMatches) {
287
+ if (match[1]) {
288
+ const tagName = match[1];
289
+ const propsStr = match[2] || '';
290
+ const isComponent = /^[A-Z]/.test(tagName);
291
+ // Extract prop names from the props string
292
+ const propMatches = propsStr.matchAll(/([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=/g);
293
+ const props = [];
294
+ for (const propMatch of propMatches) {
295
+ if (propMatch[1]) {
296
+ props.push(propMatch[1]);
297
+ }
298
+ }
299
+ elements.push({
300
+ tagName,
301
+ isComponent,
302
+ props,
303
+ depth: 0, // Simplified - would need proper parsing for accurate depth
304
+ });
305
+ }
306
+ }
307
+ return elements;
308
+ }
309
+ /**
310
+ * Extract conditional rendering patterns from a component
311
+ */
312
+ export function extractConditionalPatterns(node) {
313
+ const patterns = [];
314
+ const nodeText = node.text;
315
+ // Match ternary in JSX: {condition ? <A /> : <B />}
316
+ const ternaryMatches = nodeText.matchAll(/\{\s*([a-zA-Z_$][a-zA-Z0-9_$?.]*)\s*\?\s*</g);
317
+ for (const match of ternaryMatches) {
318
+ if (match[1]) {
319
+ patterns.push({
320
+ type: 'ternary',
321
+ condition: match[1],
322
+ });
323
+ }
324
+ }
325
+ // Match logical AND: {condition && <Component />}
326
+ const logicalAndMatches = nodeText.matchAll(/\{\s*([a-zA-Z_$][a-zA-Z0-9_$?.]*)\s*&&\s*</g);
327
+ for (const match of logicalAndMatches) {
328
+ if (match[1]) {
329
+ patterns.push({
330
+ type: 'logical-and',
331
+ condition: match[1],
332
+ });
333
+ }
334
+ }
335
+ // Match early returns: if (!condition) return null;
336
+ const earlyReturnMatches = nodeText.matchAll(/if\s*\(\s*!?\s*([a-zA-Z_$][a-zA-Z0-9_$?.]*)\s*\)\s*return\s*null/g);
337
+ for (const match of earlyReturnMatches) {
338
+ if (match[1]) {
339
+ patterns.push({
340
+ type: 'early-return',
341
+ condition: match[1],
342
+ });
343
+ }
344
+ }
345
+ return patterns;
346
+ }
347
+ /**
348
+ * Extract data fetching patterns from a component
349
+ */
350
+ export function extractDataPatterns(node) {
351
+ const patterns = [];
352
+ const nodeText = node.text;
353
+ // Check for useQuery (React Query)
354
+ if (nodeText.includes('useQuery')) {
355
+ patterns.push({
356
+ type: 'useQuery',
357
+ hasLoadingState: nodeText.includes('isLoading') || nodeText.includes('loading'),
358
+ hasErrorState: nodeText.includes('isError') || nodeText.includes('error'),
359
+ });
360
+ }
361
+ // Check for useSWR
362
+ if (nodeText.includes('useSWR')) {
363
+ patterns.push({
364
+ type: 'useSWR',
365
+ hasLoadingState: nodeText.includes('isLoading') || nodeText.includes('isValidating'),
366
+ hasErrorState: nodeText.includes('error'),
367
+ });
368
+ }
369
+ // Check for useEffect with fetch
370
+ if (nodeText.includes('useEffect') && (nodeText.includes('fetch(') || nodeText.includes('axios'))) {
371
+ patterns.push({
372
+ type: 'useEffect-fetch',
373
+ hasLoadingState: nodeText.includes('Loading') || nodeText.includes('loading'),
374
+ hasErrorState: nodeText.includes('Error') || nodeText.includes('error'),
375
+ });
376
+ }
377
+ return patterns;
378
+ }
379
+ /**
380
+ * Extract all semantic features from a component
381
+ */
382
+ export function extractSemanticFeatures(node, filePath, content) {
383
+ const name = extractComponentName(node, content);
384
+ if (!name) {
385
+ return null;
386
+ }
387
+ return {
388
+ name,
389
+ filePath,
390
+ line: node.startPosition.row + 1,
391
+ column: node.startPosition.column + 1,
392
+ props: extractProps(node, content),
393
+ stateVariables: extractState(node),
394
+ hooks: extractHooks(node),
395
+ eventHandlers: extractEventHandlers(node),
396
+ jsxElements: extractJSXElements(node),
397
+ conditionalPatterns: extractConditionalPatterns(node),
398
+ dataPatterns: extractDataPatterns(node),
399
+ sourceCode: node.text,
400
+ };
401
+ }
402
+ // ============================================================================
403
+ // Helper Functions - Similarity Calculation
404
+ // ============================================================================
405
+ /**
406
+ * Calculate Jaccard similarity between two sets
407
+ */
408
+ function jaccardSimilarity(set1, set2) {
409
+ if (set1.size === 0 && set2.size === 0) {
410
+ return 1.0;
411
+ }
412
+ const intersection = new Set([...set1].filter(x => set2.has(x)));
413
+ const union = new Set([...set1, ...set2]);
414
+ return intersection.size / union.size;
415
+ }
416
+ /**
417
+ * Calculate props similarity between two components
418
+ */
419
+ export function calculatePropsSimilarity(props1, props2) {
420
+ if (props1.length === 0 && props2.length === 0) {
421
+ return 1.0;
422
+ }
423
+ const names1 = new Set(props1.map(p => p.name));
424
+ const names2 = new Set(props2.map(p => p.name));
425
+ const nameSimilarity = jaccardSimilarity(names1, names2);
426
+ // Also consider prop characteristics
427
+ const callbacks1 = new Set(props1.filter(p => p.isCallback).map(p => p.name));
428
+ const callbacks2 = new Set(props2.filter(p => p.isCallback).map(p => p.name));
429
+ const callbackSimilarity = jaccardSimilarity(callbacks1, callbacks2);
430
+ return nameSimilarity * 0.7 + callbackSimilarity * 0.3;
431
+ }
432
+ /**
433
+ * Calculate state similarity between two components
434
+ */
435
+ export function calculateStateSimilarity(state1, state2) {
436
+ if (state1.length === 0 && state2.length === 0) {
437
+ return 1.0;
438
+ }
439
+ // Compare state types
440
+ const types1 = new Set(state1.map(s => s.stateType));
441
+ const types2 = new Set(state2.map(s => s.stateType));
442
+ const typeSimilarity = jaccardSimilarity(types1, types2);
443
+ // Compare number of state variables
444
+ const countDiff = Math.abs(state1.length - state2.length);
445
+ const maxCount = Math.max(state1.length, state2.length, 1);
446
+ const countSimilarity = 1 - (countDiff / maxCount);
447
+ return typeSimilarity * 0.6 + countSimilarity * 0.4;
448
+ }
449
+ /**
450
+ * Calculate hooks similarity between two components
451
+ */
452
+ export function calculateHooksSimilarity(hooks1, hooks2) {
453
+ if (hooks1.length === 0 && hooks2.length === 0) {
454
+ return 1.0;
455
+ }
456
+ const names1 = new Set(hooks1.map(h => h.name));
457
+ const names2 = new Set(hooks2.map(h => h.name));
458
+ return jaccardSimilarity(names1, names2);
459
+ }
460
+ /**
461
+ * Calculate event handlers similarity between two components
462
+ */
463
+ export function calculateEventHandlersSimilarity(handlers1, handlers2) {
464
+ if (handlers1.length === 0 && handlers2.length === 0) {
465
+ return 1.0;
466
+ }
467
+ const types1 = new Set(handlers1.map(h => h.eventType));
468
+ const types2 = new Set(handlers2.map(h => h.eventType));
469
+ return jaccardSimilarity(types1, types2);
470
+ }
471
+ /**
472
+ * Calculate JSX structure similarity between two components
473
+ */
474
+ export function calculateJSXSimilarity(elements1, elements2) {
475
+ if (elements1.length === 0 && elements2.length === 0) {
476
+ return 1.0;
477
+ }
478
+ // Compare tag names
479
+ const tags1 = new Set(elements1.map(e => e.tagName));
480
+ const tags2 = new Set(elements2.map(e => e.tagName));
481
+ const tagSimilarity = jaccardSimilarity(tags1, tags2);
482
+ // Compare component usage
483
+ const components1 = new Set(elements1.filter(e => e.isComponent).map(e => e.tagName));
484
+ const components2 = new Set(elements2.filter(e => e.isComponent).map(e => e.tagName));
485
+ const componentSimilarity = jaccardSimilarity(components1, components2);
486
+ // Compare element count
487
+ const countDiff = Math.abs(elements1.length - elements2.length);
488
+ const maxCount = Math.max(elements1.length, elements2.length, 1);
489
+ const countSimilarity = 1 - (countDiff / maxCount);
490
+ return tagSimilarity * 0.4 + componentSimilarity * 0.4 + countSimilarity * 0.2;
491
+ }
492
+ /**
493
+ * Calculate conditional patterns similarity
494
+ */
495
+ export function calculateConditionalSimilarity(patterns1, patterns2) {
496
+ if (patterns1.length === 0 && patterns2.length === 0) {
497
+ return 1.0;
498
+ }
499
+ const types1 = new Set(patterns1.map(p => p.type));
500
+ const types2 = new Set(patterns2.map(p => p.type));
501
+ return jaccardSimilarity(types1, types2);
502
+ }
503
+ /**
504
+ * Calculate data patterns similarity
505
+ */
506
+ export function calculateDataPatternsSimilarity(patterns1, patterns2) {
507
+ if (patterns1.length === 0 && patterns2.length === 0) {
508
+ return 1.0;
509
+ }
510
+ const types1 = new Set(patterns1.map(p => p.type));
511
+ const types2 = new Set(patterns2.map(p => p.type));
512
+ return jaccardSimilarity(types1, types2);
513
+ }
514
+ /**
515
+ * Calculate overall semantic similarity between two components
516
+ */
517
+ export function calculateSemanticSimilarity(comp1, comp2) {
518
+ return {
519
+ props: calculatePropsSimilarity(comp1.props, comp2.props),
520
+ state: calculateStateSimilarity(comp1.stateVariables, comp2.stateVariables),
521
+ hooks: calculateHooksSimilarity(comp1.hooks, comp2.hooks),
522
+ eventHandlers: calculateEventHandlersSimilarity(comp1.eventHandlers, comp2.eventHandlers),
523
+ jsxStructure: calculateJSXSimilarity(comp1.jsxElements, comp2.jsxElements),
524
+ conditionalPatterns: calculateConditionalSimilarity(comp1.conditionalPatterns, comp2.conditionalPatterns),
525
+ dataPatterns: calculateDataPatternsSimilarity(comp1.dataPatterns, comp2.dataPatterns),
526
+ };
527
+ }
528
+ /**
529
+ * Calculate weighted overall similarity from breakdown
530
+ */
531
+ export function calculateOverallSimilarity(breakdown, weights = DEFAULT_NEAR_DUPLICATE_CONFIG.weights) {
532
+ const totalWeight = Object.values(weights).reduce((sum, w) => sum + w, 0);
533
+ const weightedSum = breakdown.props * weights.props +
534
+ breakdown.state * weights.state +
535
+ breakdown.hooks * weights.hooks +
536
+ breakdown.eventHandlers * weights.eventHandlers +
537
+ breakdown.jsxStructure * weights.jsxStructure +
538
+ breakdown.conditionalPatterns * weights.conditionalPatterns +
539
+ breakdown.dataPatterns * weights.dataPatterns;
540
+ return weightedSum / totalWeight;
541
+ }
542
+ // ============================================================================
543
+ // Helper Functions - Abstraction Suggestions
544
+ // ============================================================================
545
+ /**
546
+ * Determine the best abstraction type for a pair of similar components
547
+ */
548
+ export function determineAbstractionType(comp1, comp2, breakdown) {
549
+ // If hooks are very similar and there's shared state logic, suggest shared hook
550
+ if (breakdown.hooks > 0.7 && breakdown.state > 0.6) {
551
+ const customHooks1 = comp1.hooks.filter(h => !h.isBuiltIn);
552
+ const customHooks2 = comp2.hooks.filter(h => !h.isBuiltIn);
553
+ if (customHooks1.length === 0 && customHooks2.length === 0 && comp1.stateVariables.length > 0) {
554
+ return 'shared-hook';
555
+ }
556
+ }
557
+ // If JSX structure is very similar with different props, suggest shared component
558
+ if (breakdown.jsxStructure > 0.7 && breakdown.props > 0.5) {
559
+ return 'shared-component';
560
+ }
561
+ // If there's similar conditional/data patterns, suggest HOC
562
+ if (breakdown.conditionalPatterns > 0.7 || breakdown.dataPatterns > 0.7) {
563
+ return 'higher-order';
564
+ }
565
+ // If event handlers are similar, suggest composition
566
+ if (breakdown.eventHandlers > 0.7) {
567
+ return 'composition';
568
+ }
569
+ // Default to shared component
570
+ return 'shared-component';
571
+ }
572
+ /**
573
+ * Generate abstraction suggestions for a pair of similar components
574
+ */
575
+ export function generateAbstractionSuggestions(comp1, comp2, breakdown, abstractionType) {
576
+ const suggestions = [];
577
+ // Find shared features
578
+ const sharedProps = comp1.props
579
+ .filter(p1 => comp2.props.some(p2 => p2.name === p1.name))
580
+ .map(p => p.name);
581
+ const sharedHooks = comp1.hooks
582
+ .filter(h1 => comp2.hooks.some(h2 => h2.name === h1.name))
583
+ .map(h => h.name);
584
+ const sharedElements = comp1.jsxElements
585
+ .filter(e1 => comp2.jsxElements.some(e2 => e2.tagName === e1.tagName))
586
+ .map(e => e.tagName);
587
+ switch (abstractionType) {
588
+ case 'shared-component':
589
+ suggestions.push({
590
+ type: 'shared-component',
591
+ description: `Create a shared component that accepts variant props to handle differences between '${comp1.name}' and '${comp2.name}'`,
592
+ confidence: breakdown.jsxStructure,
593
+ sharedFeatures: [...sharedProps, ...sharedElements],
594
+ exampleCode: generateSharedComponentExample(comp1, comp2, sharedProps),
595
+ });
596
+ break;
597
+ case 'shared-hook':
598
+ suggestions.push({
599
+ type: 'shared-hook',
600
+ description: `Extract shared state logic into a custom hook that can be used by both '${comp1.name}' and '${comp2.name}'`,
601
+ confidence: breakdown.hooks * breakdown.state,
602
+ sharedFeatures: sharedHooks,
603
+ exampleCode: generateSharedHookExample(comp1, comp2),
604
+ });
605
+ break;
606
+ case 'higher-order':
607
+ suggestions.push({
608
+ type: 'higher-order',
609
+ description: `Create a Higher-Order Component (HOC) to wrap shared behavior for '${comp1.name}' and '${comp2.name}'`,
610
+ confidence: Math.max(breakdown.conditionalPatterns, breakdown.dataPatterns),
611
+ sharedFeatures: [...sharedHooks, ...sharedProps],
612
+ });
613
+ break;
614
+ case 'composition':
615
+ suggestions.push({
616
+ type: 'composition',
617
+ description: `Use composition pattern to share common elements between '${comp1.name}' and '${comp2.name}'`,
618
+ confidence: breakdown.jsxStructure,
619
+ sharedFeatures: sharedElements,
620
+ });
621
+ break;
622
+ case 'render-props':
623
+ suggestions.push({
624
+ type: 'render-props',
625
+ description: `Use render props pattern to share logic while allowing different rendering`,
626
+ confidence: breakdown.state * breakdown.hooks,
627
+ sharedFeatures: [...sharedHooks, ...sharedProps],
628
+ });
629
+ break;
630
+ case 'utility-function':
631
+ suggestions.push({
632
+ type: 'utility-function',
633
+ description: `Extract shared logic into utility functions`,
634
+ confidence: 0.5,
635
+ sharedFeatures: sharedHooks,
636
+ });
637
+ break;
638
+ }
639
+ return suggestions;
640
+ }
641
+ /**
642
+ * Generate example code for shared component suggestion
643
+ */
644
+ function generateSharedComponentExample(comp1, comp2, sharedProps) {
645
+ const baseName = findCommonBaseName(comp1.name, comp2.name);
646
+ const propsStr = sharedProps.length > 0
647
+ ? `{ ${sharedProps.join(', ')}, variant }`
648
+ : '{ variant }';
649
+ return `// Suggested shared component
650
+ interface ${baseName}Props {
651
+ ${sharedProps.map(p => `${p}: unknown;`).join('\n ')}
652
+ variant: '${comp1.name.toLowerCase()}' | '${comp2.name.toLowerCase()}';
653
+ }
654
+
655
+ const ${baseName} = (${propsStr}: ${baseName}Props) => {
656
+ // Shared implementation with variant-specific rendering
657
+ };`;
658
+ }
659
+ /**
660
+ * Generate example code for shared hook suggestion
661
+ */
662
+ function generateSharedHookExample(comp1, comp2) {
663
+ const sharedState = comp1.stateVariables
664
+ .filter(s1 => comp2.stateVariables.some(s2 => s2.stateType === s1.stateType));
665
+ const hookName = `use${findCommonBaseName(comp1.name, comp2.name)}Logic`;
666
+ return `// Suggested shared hook
667
+ const ${hookName} = () => {
668
+ ${sharedState.map(s => `const [${s.name}, ${s.setter}] = ${s.stateType}(${s.initialValue || ''});`).join('\n ')}
669
+
670
+ // Shared logic here
671
+
672
+ return { ${sharedState.map(s => s.name).join(', ')} };
673
+ };`;
674
+ }
675
+ /**
676
+ * Find common base name between two component names
677
+ */
678
+ function findCommonBaseName(name1, name2) {
679
+ // Try to find common prefix
680
+ let commonPrefix = '';
681
+ const minLen = Math.min(name1.length, name2.length);
682
+ for (let i = 0; i < minLen; i++) {
683
+ if (name1[i] === name2[i]) {
684
+ commonPrefix += name1[i];
685
+ }
686
+ else {
687
+ break;
688
+ }
689
+ }
690
+ if (commonPrefix.length >= 3) {
691
+ return commonPrefix;
692
+ }
693
+ // Try to find common suffix
694
+ let commonSuffix = '';
695
+ for (let i = 0; i < minLen; i++) {
696
+ if (name1[name1.length - 1 - i] === name2[name2.length - 1 - i]) {
697
+ commonSuffix = name1[name1.length - 1 - i] + commonSuffix;
698
+ }
699
+ else {
700
+ break;
701
+ }
702
+ }
703
+ if (commonSuffix.length >= 3) {
704
+ return commonSuffix;
705
+ }
706
+ // Default to generic name
707
+ return 'Shared';
708
+ }
709
+ // ============================================================================
710
+ // Helper Functions - Analysis
711
+ // ============================================================================
712
+ /**
713
+ * Compare two components and return near-duplicate info if similar enough
714
+ */
715
+ export function compareComponentsSemanticly(comp1, comp2, config = DEFAULT_NEAR_DUPLICATE_CONFIG) {
716
+ const breakdown = calculateSemanticSimilarity(comp1, comp2);
717
+ const similarity = calculateOverallSimilarity(breakdown, config.weights);
718
+ // Check if similarity meets threshold
719
+ if (similarity < config.similarityThreshold) {
720
+ return null;
721
+ }
722
+ // Check minimum feature similarities
723
+ if (breakdown.props < config.minPropsSimilarity && breakdown.jsxStructure < config.minJsxSimilarity) {
724
+ return null;
725
+ }
726
+ const abstractionType = determineAbstractionType(comp1, comp2, breakdown);
727
+ const suggestions = generateAbstractionSuggestions(comp1, comp2, breakdown, abstractionType);
728
+ return {
729
+ component1: comp1,
730
+ component2: comp2,
731
+ similarity,
732
+ similarityBreakdown: breakdown,
733
+ suggestedAbstraction: abstractionType,
734
+ suggestions,
735
+ };
736
+ }
737
+ /**
738
+ * Analyze components for near-duplicates and abstraction opportunities
739
+ */
740
+ export function analyzeNearDuplicates(components, config = DEFAULT_NEAR_DUPLICATE_CONFIG) {
741
+ const pairs = [];
742
+ const componentWithOpportunities = new Set();
743
+ // Compare all pairs of components
744
+ for (let i = 0; i < components.length; i++) {
745
+ for (let j = i + 1; j < components.length; j++) {
746
+ const comp1 = components[i];
747
+ const comp2 = components[j];
748
+ if (!comp1 || !comp2)
749
+ continue;
750
+ const pair = compareComponentsSemanticly(comp1, comp2, config);
751
+ if (pair) {
752
+ pairs.push(pair);
753
+ componentWithOpportunities.add(`${comp1.filePath}:${comp1.line}`);
754
+ componentWithOpportunities.add(`${comp2.filePath}:${comp2.line}`);
755
+ }
756
+ }
757
+ }
758
+ // Group by abstraction type
759
+ const abstractionGroups = buildAbstractionGroups(pairs, components);
760
+ return {
761
+ pairs,
762
+ abstractionGroups,
763
+ totalComponents: components.length,
764
+ componentsWithOpportunities: componentWithOpportunities.size,
765
+ };
766
+ }
767
+ /**
768
+ * Build abstraction groups from pairs
769
+ */
770
+ function buildAbstractionGroups(pairs, allComponents) {
771
+ const groups = new Map();
772
+ const componentMap = new Map();
773
+ // Build component map
774
+ for (const comp of allComponents) {
775
+ componentMap.set(`${comp.filePath}:${comp.line}`, comp);
776
+ }
777
+ // Group components by abstraction type
778
+ for (const pair of pairs) {
779
+ const type = pair.suggestedAbstraction;
780
+ if (!groups.has(type)) {
781
+ groups.set(type, new Set());
782
+ }
783
+ const group = groups.get(type);
784
+ group.add(`${pair.component1.filePath}:${pair.component1.line}`);
785
+ group.add(`${pair.component2.filePath}:${pair.component2.line}`);
786
+ }
787
+ // Convert to AbstractionGroup array
788
+ const result = [];
789
+ for (const [type, componentKeys] of groups) {
790
+ const components = [];
791
+ for (const key of componentKeys) {
792
+ const comp = componentMap.get(key);
793
+ if (comp) {
794
+ components.push(comp);
795
+ }
796
+ }
797
+ // Find shared features across all components in group
798
+ const sharedFeatures = findSharedFeatures(components);
799
+ // Calculate average confidence from pairs
800
+ const relevantPairs = pairs.filter(p => p.suggestedAbstraction === type);
801
+ const avgConfidence = relevantPairs.length > 0
802
+ ? relevantPairs.reduce((sum, p) => sum + p.similarity, 0) / relevantPairs.length
803
+ : 0;
804
+ result.push({
805
+ abstractionType: type,
806
+ components,
807
+ sharedFeatures,
808
+ confidence: avgConfidence,
809
+ });
810
+ }
811
+ return result.sort((a, b) => b.confidence - a.confidence);
812
+ }
813
+ /**
814
+ * Find features shared across all components
815
+ */
816
+ function findSharedFeatures(components) {
817
+ if (components.length === 0)
818
+ return [];
819
+ const firstComp = components[0];
820
+ if (!firstComp)
821
+ return [];
822
+ const sharedProps = firstComp.props
823
+ .filter(p => components.every(c => c.props.some(cp => cp.name === p.name)))
824
+ .map(p => `prop:${p.name}`);
825
+ const sharedHooks = firstComp.hooks
826
+ .filter(h => components.every(c => c.hooks.some(ch => ch.name === h.name)))
827
+ .map(h => `hook:${h.name}`);
828
+ const sharedElements = firstComp.jsxElements
829
+ .filter(e => components.every(c => c.jsxElements.some(ce => ce.tagName === e.tagName)))
830
+ .map(e => `element:${e.tagName}`);
831
+ return [...sharedProps, ...sharedHooks, ...sharedElements];
832
+ }
833
+ /**
834
+ * Generate a refactoring suggestion message
835
+ */
836
+ export function generateRefactoringSuggestionMessage(pair) {
837
+ const { component1, component2, similarity, suggestedAbstraction } = pair;
838
+ const percentSimilar = Math.round(similarity * 100);
839
+ const abstractionDescriptions = {
840
+ 'shared-component': 'creating a shared component with variant props',
841
+ 'shared-hook': 'extracting shared logic into a custom hook',
842
+ 'higher-order': 'using a Higher-Order Component (HOC)',
843
+ 'render-props': 'using the render props pattern',
844
+ 'composition': 'using component composition',
845
+ 'utility-function': 'extracting utility functions',
846
+ };
847
+ return `Components '${component1.name}' and '${component2.name}' are ${percentSimilar}% semantically similar. ` +
848
+ `Consider ${abstractionDescriptions[suggestedAbstraction]}.`;
849
+ }
850
+ // ============================================================================
851
+ // Near Duplicate Detector Class
852
+ // ============================================================================
853
+ /**
854
+ * Detector for semantically similar components that could be abstracted
855
+ *
856
+ * Unlike DuplicateDetector which focuses on AST structure, this detector
857
+ * analyzes semantic/functional similarity including:
858
+ * - Similar prop patterns
859
+ * - Similar render patterns
860
+ * - Similar state management
861
+ * - Opportunities for shared hooks, HOCs, or render props
862
+ *
863
+ * @requirements 8.4 - THE Component_Detector SHALL detect near-duplicate components that should be abstracted
864
+ */
865
+ export class NearDuplicateDetector extends ASTDetector {
866
+ id = 'components/near-duplicate';
867
+ category = 'components';
868
+ subcategory = 'abstraction-candidates';
869
+ name = 'Near Duplicate Detector';
870
+ description = 'Detects semantically similar components that could be refactored into shared abstractions';
871
+ supportedLanguages = ['typescript', 'javascript'];
872
+ config;
873
+ constructor(config = {}) {
874
+ super();
875
+ this.config = { ...DEFAULT_NEAR_DUPLICATE_CONFIG, ...config };
876
+ }
877
+ /**
878
+ * Detect near-duplicate components in the project
879
+ */
880
+ async detect(context) {
881
+ const patterns = [];
882
+ const violations = [];
883
+ // Extract semantic features from all components
884
+ const allComponents = this.extractAllComponents(context);
885
+ if (allComponents.length < 2) {
886
+ return this.createEmptyResult();
887
+ }
888
+ // Analyze for near-duplicates
889
+ const analysis = analyzeNearDuplicates(allComponents, this.config);
890
+ // Create pattern matches for abstraction opportunities
891
+ for (const group of analysis.abstractionGroups) {
892
+ if (group.confidence > 0.5) {
893
+ patterns.push({
894
+ patternId: `abstraction-opportunity-${group.abstractionType}`,
895
+ location: { file: context.file, line: 1, column: 1 },
896
+ confidence: group.confidence,
897
+ isOutlier: false,
898
+ });
899
+ }
900
+ }
901
+ // Create violations for near-duplicates involving the current file
902
+ for (const pair of analysis.pairs) {
903
+ if (this.involvesCurrentFile(pair, context.file)) {
904
+ const violation = this.createNearDuplicateViolation(pair, context.file);
905
+ violations.push(violation);
906
+ }
907
+ }
908
+ const confidence = analysis.totalComponents > 0
909
+ ? 1 - (analysis.componentsWithOpportunities / analysis.totalComponents)
910
+ : 1.0;
911
+ return this.createResult(patterns, violations, confidence);
912
+ }
913
+ /**
914
+ * Generate a quick fix for near-duplicate violations
915
+ */
916
+ generateQuickFix(violation) {
917
+ // Extract component names from the violation message
918
+ const match = violation.message.match(/Components '([^']+)' and '([^']+)'/);
919
+ if (!match || !match[1] || !match[2]) {
920
+ return null;
921
+ }
922
+ const comp1Name = match[1];
923
+ const comp2Name = match[2];
924
+ const baseName = findCommonBaseName(comp1Name, comp2Name);
925
+ // Determine abstraction type from message
926
+ let abstractionType = 'shared-component';
927
+ if (violation.message.includes('custom hook')) {
928
+ abstractionType = 'shared-hook';
929
+ }
930
+ else if (violation.message.includes('HOC')) {
931
+ abstractionType = 'higher-order';
932
+ }
933
+ return {
934
+ title: `Extract shared ${abstractionType === 'shared-hook' ? 'hook' : 'component'}: ${baseName}`,
935
+ kind: 'refactor',
936
+ edit: {
937
+ changes: {},
938
+ documentChanges: [],
939
+ },
940
+ isPreferred: true,
941
+ confidence: 0.7,
942
+ preview: `Create a shared ${abstractionType} to reduce duplication between ${comp1Name} and ${comp2Name}`,
943
+ };
944
+ }
945
+ /**
946
+ * Extract all components from project files
947
+ */
948
+ extractAllComponents(context) {
949
+ const components = [];
950
+ // Extract from current file using AST if available
951
+ if (context.ast) {
952
+ const fileComponents = this.extractComponentsFromAST(context.ast, context.file, context.content);
953
+ components.push(...fileComponents);
954
+ }
955
+ else {
956
+ // Fallback to regex-based extraction
957
+ const fileComponents = this.extractComponentsFromContent(context.file, context.content);
958
+ components.push(...fileComponents);
959
+ }
960
+ return components;
961
+ }
962
+ /**
963
+ * Extract components from an AST
964
+ */
965
+ extractComponentsFromAST(ast, filePath, content) {
966
+ const components = [];
967
+ const functionNodes = this.findNodesByTypes(ast, [
968
+ 'function_declaration',
969
+ 'arrow_function',
970
+ 'function_expression',
971
+ ]);
972
+ for (const node of functionNodes) {
973
+ if (isReactComponentNode(node, content)) {
974
+ const features = extractSemanticFeatures(node, filePath, content);
975
+ if (features) {
976
+ components.push(features);
977
+ }
978
+ }
979
+ }
980
+ return components;
981
+ }
982
+ /**
983
+ * Extract components from content using regex (fallback)
984
+ */
985
+ extractComponentsFromContent(filePath, content) {
986
+ const components = [];
987
+ // Match component patterns
988
+ const patterns = [
989
+ // Arrow function: const Button = ({ ... }) => ...
990
+ /(?:export\s+)?const\s+([A-Z][a-zA-Z0-9]*)\s*(?::\s*(?:React\.)?(?:FC|FunctionComponent)\s*<[^>]*>\s*)?=\s*\([^)]*\)\s*=>\s*[\s\S]*?(?=\n(?:export\s+)?(?:const|function|class)\s+[A-Z]|\n*$)/g,
991
+ // Function declaration: function Button({ ... }) { ... }
992
+ /(?:export\s+)?function\s+([A-Z][a-zA-Z0-9]*)\s*\([^)]*\)\s*\{[\s\S]*?\n\}/g,
993
+ ];
994
+ for (const pattern of patterns) {
995
+ let match;
996
+ while ((match = pattern.exec(content)) !== null) {
997
+ const name = match[1];
998
+ if (!name)
999
+ continue;
1000
+ const sourceCode = match[0];
1001
+ const beforeMatch = content.slice(0, match.index);
1002
+ const line = beforeMatch.split('\n').length;
1003
+ // Create a mock AST node for feature extraction
1004
+ const mockNode = {
1005
+ type: 'function_declaration',
1006
+ text: sourceCode,
1007
+ startPosition: { row: line - 1, column: 0 },
1008
+ endPosition: { row: line - 1 + sourceCode.split('\n').length, column: 0 },
1009
+ children: [],
1010
+ };
1011
+ const features = extractSemanticFeatures(mockNode, filePath, content);
1012
+ if (features) {
1013
+ components.push(features);
1014
+ }
1015
+ }
1016
+ }
1017
+ return components;
1018
+ }
1019
+ /**
1020
+ * Check if a near-duplicate pair involves the current file
1021
+ */
1022
+ involvesCurrentFile(pair, currentFile) {
1023
+ return pair.component1.filePath === currentFile ||
1024
+ pair.component2.filePath === currentFile;
1025
+ }
1026
+ /**
1027
+ * Create a violation for a near-duplicate pair
1028
+ */
1029
+ createNearDuplicateViolation(pair, currentFile) {
1030
+ const { component1, component2, similarity, suggestedAbstraction } = pair;
1031
+ // Determine which component is in the current file
1032
+ const currentComponent = component1.filePath === currentFile ? component1 : component2;
1033
+ const otherComponent = component1.filePath === currentFile ? component2 : component1;
1034
+ const message = generateRefactoringSuggestionMessage(pair);
1035
+ const range = {
1036
+ start: { line: currentComponent.line, character: currentComponent.column },
1037
+ end: { line: currentComponent.line, character: currentComponent.column + currentComponent.name.length },
1038
+ };
1039
+ const abstractionDescriptions = {
1040
+ 'shared-component': 'shared component with variant props',
1041
+ 'shared-hook': 'custom hook for shared logic',
1042
+ 'higher-order': 'Higher-Order Component (HOC)',
1043
+ 'render-props': 'render props pattern',
1044
+ 'composition': 'component composition',
1045
+ 'utility-function': 'utility functions',
1046
+ };
1047
+ const quickFix = this.generateQuickFix({
1048
+ id: '',
1049
+ patternId: '',
1050
+ severity: 'info',
1051
+ file: currentFile,
1052
+ range,
1053
+ message,
1054
+ expected: '',
1055
+ actual: '',
1056
+ aiExplainAvailable: true,
1057
+ aiFixAvailable: true,
1058
+ firstSeen: new Date(),
1059
+ occurrences: 1,
1060
+ });
1061
+ const violation = {
1062
+ id: `near-duplicate-${currentComponent.name}-${otherComponent.name}-${currentFile.replace(/[^a-zA-Z0-9]/g, '-')}`,
1063
+ patternId: 'components/near-duplicate',
1064
+ severity: 'info',
1065
+ file: currentFile,
1066
+ range,
1067
+ message,
1068
+ expected: abstractionDescriptions[suggestedAbstraction],
1069
+ actual: `${Math.round(similarity * 100)}% semantic similarity`,
1070
+ aiExplainAvailable: true,
1071
+ aiFixAvailable: true,
1072
+ firstSeen: new Date(),
1073
+ occurrences: 1,
1074
+ };
1075
+ if (quickFix) {
1076
+ violation.quickFix = quickFix;
1077
+ }
1078
+ return violation;
1079
+ }
1080
+ }
1081
+ // ============================================================================
1082
+ // Factory Function
1083
+ // ============================================================================
1084
+ /**
1085
+ * Create a new NearDuplicateDetector instance
1086
+ */
1087
+ export function createNearDuplicateDetector(config = {}) {
1088
+ return new NearDuplicateDetector(config);
1089
+ }
1090
+ //# sourceMappingURL=near-duplicate.js.map