mcp-react-toolkit 1.0.1 → 1.3.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 (275) hide show
  1. package/README.md +194 -44
  2. package/bin/cli.mjs +59 -0
  3. package/node_modules/@mcp-showcase/shared/build/McpServerBase.d.ts +18 -0
  4. package/node_modules/@mcp-showcase/shared/build/McpServerBase.d.ts.map +1 -0
  5. package/node_modules/@mcp-showcase/shared/build/McpServerBase.js +74 -0
  6. package/node_modules/@mcp-showcase/shared/build/McpServerBase.js.map +1 -0
  7. package/node_modules/@mcp-showcase/shared/build/ToolRegistry.d.ts +9 -0
  8. package/node_modules/@mcp-showcase/shared/build/ToolRegistry.d.ts.map +1 -0
  9. package/node_modules/@mcp-showcase/shared/build/ToolRegistry.js +22 -0
  10. package/node_modules/@mcp-showcase/shared/build/ToolRegistry.js.map +1 -0
  11. package/node_modules/@mcp-showcase/shared/build/fs.d.ts +8 -0
  12. package/node_modules/@mcp-showcase/shared/build/fs.d.ts.map +1 -0
  13. package/node_modules/@mcp-showcase/shared/build/fs.js +45 -0
  14. package/node_modules/@mcp-showcase/shared/build/fs.js.map +1 -0
  15. package/node_modules/@mcp-showcase/shared/build/index.d.ts +5 -0
  16. package/node_modules/@mcp-showcase/shared/build/index.d.ts.map +1 -0
  17. package/node_modules/@mcp-showcase/shared/build/index.js +5 -0
  18. package/node_modules/@mcp-showcase/shared/build/index.js.map +1 -0
  19. package/node_modules/@mcp-showcase/shared/build/types.d.ts +36 -0
  20. package/node_modules/@mcp-showcase/shared/build/types.d.ts.map +1 -0
  21. package/node_modules/@mcp-showcase/shared/build/types.js +5 -0
  22. package/node_modules/@mcp-showcase/shared/build/types.js.map +1 -0
  23. package/node_modules/@mcp-showcase/shared/package.json +24 -0
  24. package/node_modules/@mcp-showcase/shared/src/McpServerBase.ts +100 -0
  25. package/node_modules/@mcp-showcase/shared/src/ToolRegistry.ts +38 -0
  26. package/node_modules/@mcp-showcase/shared/src/fs.ts +49 -0
  27. package/node_modules/@mcp-showcase/shared/src/index.ts +12 -0
  28. package/node_modules/@mcp-showcase/shared/src/types.ts +44 -0
  29. package/node_modules/@mcp-showcase/shared/tsconfig.json +8 -0
  30. package/package.json +38 -4
  31. package/tools/accessibility-checker/build/index.js +9 -5
  32. package/tools/accessibility-checker/build/index.js.map +1 -1
  33. package/tools/accessibility-checker/build/rules.d.ts.map +1 -1
  34. package/tools/accessibility-checker/build/rules.js +325 -94
  35. package/tools/accessibility-checker/build/rules.js.map +1 -1
  36. package/tools/code-modernizer/build/tools/01-convert-to-typescript.d.ts.map +1 -1
  37. package/tools/code-modernizer/build/tools/01-convert-to-typescript.js +65 -50
  38. package/tools/code-modernizer/build/tools/01-convert-to-typescript.js.map +1 -1
  39. package/tools/code-modernizer/build/types.d.ts +1 -0
  40. package/tools/code-modernizer/build/types.d.ts.map +1 -1
  41. package/tools/code-modernizer/build/utils/ast-parser.d.ts.map +1 -1
  42. package/tools/code-modernizer/build/utils/ast-parser.js +30 -14
  43. package/tools/code-modernizer/build/utils/ast-parser.js.map +1 -1
  44. package/tools/code-modernizer/build/utils/type-generator.d.ts +1 -1
  45. package/tools/code-modernizer/build/utils/type-generator.d.ts.map +1 -1
  46. package/tools/code-modernizer/build/utils/type-generator.js +72 -23
  47. package/tools/code-modernizer/build/utils/type-generator.js.map +1 -1
  48. package/tools/component-factory/build/index.js +59 -7
  49. package/tools/component-factory/build/index.js.map +1 -1
  50. package/tools/component-fixer/README.md +44 -0
  51. package/tools/component-fixer/build/index.d.ts +3 -0
  52. package/tools/component-fixer/build/index.d.ts.map +1 -0
  53. package/tools/component-fixer/build/index.js +647 -0
  54. package/tools/component-fixer/build/index.js.map +1 -0
  55. package/tools/component-fixer/package.json +20 -0
  56. package/tools/component-reviewer/README.md +54 -0
  57. package/tools/component-reviewer/build/index.d.ts +39 -0
  58. package/tools/component-reviewer/build/index.d.ts.map +1 -0
  59. package/tools/component-reviewer/build/index.js +946 -0
  60. package/tools/component-reviewer/build/index.js.map +1 -0
  61. package/tools/component-reviewer/package.json +20 -0
  62. package/tools/dep-auditor/build/index.d.ts +1 -0
  63. package/tools/dep-auditor/build/index.d.ts.map +1 -1
  64. package/tools/dep-auditor/build/index.js +71 -16
  65. package/tools/dep-auditor/build/index.js.map +1 -1
  66. package/tools/generate-tests/build/analyzer.d.ts +14 -0
  67. package/tools/generate-tests/build/analyzer.d.ts.map +1 -1
  68. package/tools/generate-tests/build/analyzer.js +96 -42
  69. package/tools/generate-tests/build/analyzer.js.map +1 -1
  70. package/tools/generate-tests/build/generators.d.ts.map +1 -1
  71. package/tools/generate-tests/build/generators.js +304 -79
  72. package/tools/generate-tests/build/generators.js.map +1 -1
  73. package/tools/generate-tests/build/index.js +29 -10
  74. package/tools/generate-tests/build/index.js.map +1 -1
  75. package/tools/json-viewer/build/index.js +29 -6
  76. package/tools/json-viewer/build/index.js.map +1 -1
  77. package/tools/legacy-analyzer/README.md +66 -0
  78. package/tools/legacy-analyzer/build/index.d.ts +3 -0
  79. package/tools/legacy-analyzer/build/index.d.ts.map +1 -0
  80. package/tools/legacy-analyzer/build/index.js +209 -0
  81. package/tools/legacy-analyzer/build/index.js.map +1 -0
  82. package/tools/legacy-analyzer/build/tools/01-detect-project-tech.d.ts +3 -0
  83. package/tools/legacy-analyzer/build/tools/01-detect-project-tech.d.ts.map +1 -0
  84. package/tools/legacy-analyzer/build/tools/01-detect-project-tech.js +115 -0
  85. package/tools/legacy-analyzer/build/tools/01-detect-project-tech.js.map +1 -0
  86. package/tools/legacy-analyzer/build/tools/02-analyze-folder-structure.d.ts +3 -0
  87. package/tools/legacy-analyzer/build/tools/02-analyze-folder-structure.d.ts.map +1 -0
  88. package/tools/legacy-analyzer/build/tools/02-analyze-folder-structure.js +85 -0
  89. package/tools/legacy-analyzer/build/tools/02-analyze-folder-structure.js.map +1 -0
  90. package/tools/legacy-analyzer/build/tools/03-analyze-components.d.ts +3 -0
  91. package/tools/legacy-analyzer/build/tools/03-analyze-components.d.ts.map +1 -0
  92. package/tools/legacy-analyzer/build/tools/03-analyze-components.js +87 -0
  93. package/tools/legacy-analyzer/build/tools/03-analyze-components.js.map +1 -0
  94. package/tools/legacy-analyzer/build/tools/04-analyze-state-management.d.ts +3 -0
  95. package/tools/legacy-analyzer/build/tools/04-analyze-state-management.d.ts.map +1 -0
  96. package/tools/legacy-analyzer/build/tools/04-analyze-state-management.js +133 -0
  97. package/tools/legacy-analyzer/build/tools/04-analyze-state-management.js.map +1 -0
  98. package/tools/legacy-analyzer/build/tools/05-analyze-api-layer.d.ts +3 -0
  99. package/tools/legacy-analyzer/build/tools/05-analyze-api-layer.d.ts.map +1 -0
  100. package/tools/legacy-analyzer/build/tools/05-analyze-api-layer.js +160 -0
  101. package/tools/legacy-analyzer/build/tools/05-analyze-api-layer.js.map +1 -0
  102. package/tools/legacy-analyzer/build/tools/06-analyze-routing.d.ts +3 -0
  103. package/tools/legacy-analyzer/build/tools/06-analyze-routing.d.ts.map +1 -0
  104. package/tools/legacy-analyzer/build/tools/06-analyze-routing.js +150 -0
  105. package/tools/legacy-analyzer/build/tools/06-analyze-routing.js.map +1 -0
  106. package/tools/legacy-analyzer/build/tools/07-analyze-styling.d.ts +3 -0
  107. package/tools/legacy-analyzer/build/tools/07-analyze-styling.d.ts.map +1 -0
  108. package/tools/legacy-analyzer/build/tools/07-analyze-styling.js +131 -0
  109. package/tools/legacy-analyzer/build/tools/07-analyze-styling.js.map +1 -0
  110. package/tools/legacy-analyzer/build/tools/08-analyze-assets.d.ts +3 -0
  111. package/tools/legacy-analyzer/build/tools/08-analyze-assets.d.ts.map +1 -0
  112. package/tools/legacy-analyzer/build/tools/08-analyze-assets.js +85 -0
  113. package/tools/legacy-analyzer/build/tools/08-analyze-assets.js.map +1 -0
  114. package/tools/legacy-analyzer/build/tools/09-detect-anti-patterns.d.ts +3 -0
  115. package/tools/legacy-analyzer/build/tools/09-detect-anti-patterns.d.ts.map +1 -0
  116. package/tools/legacy-analyzer/build/tools/09-detect-anti-patterns.js +329 -0
  117. package/tools/legacy-analyzer/build/tools/09-detect-anti-patterns.js.map +1 -0
  118. package/tools/legacy-analyzer/build/tools/10-detect-duplication.d.ts +3 -0
  119. package/tools/legacy-analyzer/build/tools/10-detect-duplication.d.ts.map +1 -0
  120. package/tools/legacy-analyzer/build/tools/10-detect-duplication.js +192 -0
  121. package/tools/legacy-analyzer/build/tools/10-detect-duplication.js.map +1 -0
  122. package/tools/legacy-analyzer/build/tools/11-analyze-dependencies-usage.d.ts +3 -0
  123. package/tools/legacy-analyzer/build/tools/11-analyze-dependencies-usage.d.ts.map +1 -0
  124. package/tools/legacy-analyzer/build/tools/11-analyze-dependencies-usage.js +232 -0
  125. package/tools/legacy-analyzer/build/tools/11-analyze-dependencies-usage.js.map +1 -0
  126. package/tools/legacy-analyzer/build/tools/12-analyze-legacy-app.d.ts +3 -0
  127. package/tools/legacy-analyzer/build/tools/12-analyze-legacy-app.d.ts.map +1 -0
  128. package/tools/legacy-analyzer/build/tools/12-analyze-legacy-app.js +247 -0
  129. package/tools/legacy-analyzer/build/tools/12-analyze-legacy-app.js.map +1 -0
  130. package/tools/legacy-analyzer/build/tools/13-detect-features.d.ts +3 -0
  131. package/tools/legacy-analyzer/build/tools/13-detect-features.d.ts.map +1 -0
  132. package/tools/legacy-analyzer/build/tools/13-detect-features.js +141 -0
  133. package/tools/legacy-analyzer/build/tools/13-detect-features.js.map +1 -0
  134. package/tools/legacy-analyzer/build/tools/14-classify-files.d.ts +3 -0
  135. package/tools/legacy-analyzer/build/tools/14-classify-files.d.ts.map +1 -0
  136. package/tools/legacy-analyzer/build/tools/14-classify-files.js +76 -0
  137. package/tools/legacy-analyzer/build/tools/14-classify-files.js.map +1 -0
  138. package/tools/legacy-analyzer/build/tools/15-detect-shared-modules.d.ts +3 -0
  139. package/tools/legacy-analyzer/build/tools/15-detect-shared-modules.d.ts.map +1 -0
  140. package/tools/legacy-analyzer/build/tools/15-detect-shared-modules.js +70 -0
  141. package/tools/legacy-analyzer/build/tools/15-detect-shared-modules.js.map +1 -0
  142. package/tools/legacy-analyzer/build/tools/16-design-target-structure.d.ts +3 -0
  143. package/tools/legacy-analyzer/build/tools/16-design-target-structure.d.ts.map +1 -0
  144. package/tools/legacy-analyzer/build/tools/16-design-target-structure.js +26 -0
  145. package/tools/legacy-analyzer/build/tools/16-design-target-structure.js.map +1 -0
  146. package/tools/legacy-analyzer/build/tools/17-map-files-to-target.d.ts +3 -0
  147. package/tools/legacy-analyzer/build/tools/17-map-files-to-target.d.ts.map +1 -0
  148. package/tools/legacy-analyzer/build/tools/17-map-files-to-target.js +108 -0
  149. package/tools/legacy-analyzer/build/tools/17-map-files-to-target.js.map +1 -0
  150. package/tools/legacy-analyzer/build/tools/18-detect-boundary-violations.d.ts +3 -0
  151. package/tools/legacy-analyzer/build/tools/18-detect-boundary-violations.d.ts.map +1 -0
  152. package/tools/legacy-analyzer/build/tools/18-detect-boundary-violations.js +137 -0
  153. package/tools/legacy-analyzer/build/tools/18-detect-boundary-violations.js.map +1 -0
  154. package/tools/legacy-analyzer/build/tools/19-suggest-module-splitting.d.ts +3 -0
  155. package/tools/legacy-analyzer/build/tools/19-suggest-module-splitting.d.ts.map +1 -0
  156. package/tools/legacy-analyzer/build/tools/19-suggest-module-splitting.js +160 -0
  157. package/tools/legacy-analyzer/build/tools/19-suggest-module-splitting.js.map +1 -0
  158. package/tools/legacy-analyzer/build/tools/20-naming-standardizer.d.ts +3 -0
  159. package/tools/legacy-analyzer/build/tools/20-naming-standardizer.d.ts.map +1 -0
  160. package/tools/legacy-analyzer/build/tools/20-naming-standardizer.js +162 -0
  161. package/tools/legacy-analyzer/build/tools/20-naming-standardizer.js.map +1 -0
  162. package/tools/legacy-analyzer/build/tools/21-generate-refactor-plan.d.ts +3 -0
  163. package/tools/legacy-analyzer/build/tools/21-generate-refactor-plan.d.ts.map +1 -0
  164. package/tools/legacy-analyzer/build/tools/21-generate-refactor-plan.js +108 -0
  165. package/tools/legacy-analyzer/build/tools/21-generate-refactor-plan.js.map +1 -0
  166. package/tools/legacy-analyzer/build/tools/22-refactor-folder-structure.d.ts +3 -0
  167. package/tools/legacy-analyzer/build/tools/22-refactor-folder-structure.d.ts.map +1 -0
  168. package/tools/legacy-analyzer/build/tools/22-refactor-folder-structure.js +98 -0
  169. package/tools/legacy-analyzer/build/tools/22-refactor-folder-structure.js.map +1 -0
  170. package/tools/legacy-analyzer/build/types.d.ts +413 -0
  171. package/tools/legacy-analyzer/build/types.d.ts.map +1 -0
  172. package/tools/legacy-analyzer/build/types.js +12 -0
  173. package/tools/legacy-analyzer/build/types.js.map +1 -0
  174. package/tools/legacy-analyzer/build/utils/ast-parser.d.ts +34 -0
  175. package/tools/legacy-analyzer/build/utils/ast-parser.d.ts.map +1 -0
  176. package/tools/legacy-analyzer/build/utils/ast-parser.js +394 -0
  177. package/tools/legacy-analyzer/build/utils/ast-parser.js.map +1 -0
  178. package/tools/legacy-analyzer/build/utils/file-scanner.d.ts +51 -0
  179. package/tools/legacy-analyzer/build/utils/file-scanner.d.ts.map +1 -0
  180. package/tools/legacy-analyzer/build/utils/file-scanner.js +174 -0
  181. package/tools/legacy-analyzer/build/utils/file-scanner.js.map +1 -0
  182. package/tools/legacy-analyzer/build/utils/import-tracker.d.ts +38 -0
  183. package/tools/legacy-analyzer/build/utils/import-tracker.d.ts.map +1 -0
  184. package/tools/legacy-analyzer/build/utils/import-tracker.js +194 -0
  185. package/tools/legacy-analyzer/build/utils/import-tracker.js.map +1 -0
  186. package/tools/legacy-analyzer/build/utils/refactor-helpers.d.ts +88 -0
  187. package/tools/legacy-analyzer/build/utils/refactor-helpers.d.ts.map +1 -0
  188. package/tools/legacy-analyzer/build/utils/refactor-helpers.js +538 -0
  189. package/tools/legacy-analyzer/build/utils/refactor-helpers.js.map +1 -0
  190. package/tools/legacy-analyzer/package.json +20 -0
  191. package/tools/lighthouse-runner/README.md +45 -0
  192. package/tools/lighthouse-runner/build/index.d.ts +6 -0
  193. package/tools/lighthouse-runner/build/index.d.ts.map +1 -0
  194. package/tools/lighthouse-runner/build/index.js +295 -0
  195. package/tools/lighthouse-runner/build/index.js.map +1 -0
  196. package/tools/lighthouse-runner/package.json +20 -0
  197. package/tools/monorepo-manager/build/utils.d.ts.map +1 -1
  198. package/tools/monorepo-manager/build/utils.js +12 -9
  199. package/tools/monorepo-manager/build/utils.js.map +1 -1
  200. package/tools/performance-audit/README.md +37 -0
  201. package/tools/performance-audit/build/index.d.ts +13 -0
  202. package/tools/performance-audit/build/index.d.ts.map +1 -0
  203. package/tools/performance-audit/build/index.js +311 -0
  204. package/tools/performance-audit/build/index.js.map +1 -0
  205. package/tools/performance-audit/package.json +20 -0
  206. package/tools/quality-pipeline/build/index.js +55 -15
  207. package/tools/quality-pipeline/build/index.js.map +1 -1
  208. package/tools/render-analyzer/README.md +43 -0
  209. package/tools/render-analyzer/build/index.d.ts +25 -0
  210. package/tools/render-analyzer/build/index.d.ts.map +1 -0
  211. package/tools/render-analyzer/build/index.js +342 -0
  212. package/tools/render-analyzer/build/index.js.map +1 -0
  213. package/tools/render-analyzer/package.json +20 -0
  214. package/tools/shared/build/fs.d.ts +8 -0
  215. package/tools/shared/build/fs.d.ts.map +1 -0
  216. package/tools/shared/build/fs.js +45 -0
  217. package/tools/shared/build/fs.js.map +1 -0
  218. package/tools/shared/build/index.d.ts +1 -0
  219. package/tools/shared/build/index.d.ts.map +1 -1
  220. package/tools/shared/build/index.js +1 -0
  221. package/tools/shared/build/index.js.map +1 -1
  222. package/tools/shared/package.json +2 -1
  223. package/tools/storybook-generator/README.md +39 -0
  224. package/tools/storybook-generator/build/index.d.ts +13 -0
  225. package/tools/storybook-generator/build/index.d.ts.map +1 -0
  226. package/tools/storybook-generator/build/index.js +478 -0
  227. package/tools/storybook-generator/build/index.js.map +1 -0
  228. package/tools/storybook-generator/package.json +20 -0
  229. package/tools/test-gap-analyzer/README.md +41 -0
  230. package/tools/test-gap-analyzer/build/index.d.ts +20 -0
  231. package/tools/test-gap-analyzer/build/index.d.ts.map +1 -0
  232. package/tools/test-gap-analyzer/build/index.js +371 -0
  233. package/tools/test-gap-analyzer/build/index.js.map +1 -0
  234. package/tools/test-gap-analyzer/package.json +20 -0
  235. package/tools/typescript-enforcer/build/scanner.d.ts.map +1 -1
  236. package/tools/typescript-enforcer/build/scanner.js +13 -1
  237. package/tools/typescript-enforcer/build/scanner.js.map +1 -1
  238. package/tools/typescript-enforcer/build/types.d.ts +1 -0
  239. package/tools/typescript-enforcer/build/types.d.ts.map +1 -1
  240. package/CONTRIBUTING.md +0 -157
  241. package/demo/legacy-app/src/App.jsx +0 -12
  242. package/demo/legacy-app/src/components/Dashboard.jsx +0 -51
  243. package/demo/legacy-app/src/components/UserCard.jsx +0 -32
  244. package/demo/legacy-app/src/hooks/useUsers.js +0 -38
  245. package/demo/legacy-app/src/utils/api.js +0 -30
  246. package/glama.json +0 -4
  247. package/mcp-publisher +0 -0
  248. package/server.json +0 -20
  249. package/tools/accessibility-checker/build/rules.test.d.ts +0 -2
  250. package/tools/accessibility-checker/build/rules.test.d.ts.map +0 -1
  251. package/tools/accessibility-checker/build/rules.test.js.map +0 -1
  252. package/tools/code-modernizer/build/utils/file-ops.test.d.ts +0 -2
  253. package/tools/code-modernizer/build/utils/file-ops.test.d.ts.map +0 -1
  254. package/tools/code-modernizer/build/utils/file-ops.test.js.map +0 -1
  255. package/tools/component-factory/build/utils.test.d.ts +0 -2
  256. package/tools/component-factory/build/utils.test.d.ts.map +0 -1
  257. package/tools/component-factory/build/utils.test.js.map +0 -1
  258. package/tools/dep-auditor/build/index.test.d.ts +0 -2
  259. package/tools/dep-auditor/build/index.test.d.ts.map +0 -1
  260. package/tools/dep-auditor/build/index.test.js.map +0 -1
  261. package/tools/generate-tests/build/analyzer.test.d.ts +0 -2
  262. package/tools/generate-tests/build/analyzer.test.d.ts.map +0 -1
  263. package/tools/generate-tests/build/analyzer.test.js.map +0 -1
  264. package/tools/json-viewer/build/utils.test.d.ts +0 -2
  265. package/tools/json-viewer/build/utils.test.d.ts.map +0 -1
  266. package/tools/json-viewer/build/utils.test.js.map +0 -1
  267. package/tools/monorepo-manager/build/utils.test.d.ts +0 -2
  268. package/tools/monorepo-manager/build/utils.test.d.ts.map +0 -1
  269. package/tools/monorepo-manager/build/utils.test.js.map +0 -1
  270. package/tools/quality-pipeline/build/utils.test.d.ts +0 -2
  271. package/tools/quality-pipeline/build/utils.test.d.ts.map +0 -1
  272. package/tools/quality-pipeline/build/utils.test.js.map +0 -1
  273. package/tools/typescript-enforcer/build/scanner.test.d.ts +0 -2
  274. package/tools/typescript-enforcer/build/scanner.test.d.ts.map +0 -1
  275. package/tools/typescript-enforcer/build/scanner.test.js.map +0 -1
@@ -0,0 +1,946 @@
1
+ #!/usr/bin/env node
2
+ import { McpServerBase } from '@mcp-showcase/shared';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import { execSync } from 'child_process';
6
+ import { fileURLToPath } from 'url';
7
+ import { dirname } from 'path';
8
+ // ============================================================================
9
+ // PATHS (ES module compatible)
10
+ // ============================================================================
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+ // ============================================================================
14
+ // FILE ANALYSIS HELPERS
15
+ // ============================================================================
16
+ function readFileContent(filePath) {
17
+ try {
18
+ return fs.readFileSync(filePath, 'utf-8');
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ function getLines(content) {
25
+ return content.split('\n');
26
+ }
27
+ function findComponentFile(componentDir, componentName) {
28
+ const extensions = ['.tsx', '.ts', '.jsx', '.js'];
29
+ for (const ext of extensions) {
30
+ const filePath = path.join(componentDir, `${componentName}${ext}`);
31
+ if (fs.existsSync(filePath))
32
+ return filePath;
33
+ }
34
+ return null;
35
+ }
36
+ function findTestFile(componentDir, componentName) {
37
+ const patterns = [
38
+ `${componentName}.test.tsx`,
39
+ `${componentName}.test.ts`,
40
+ `${componentName}.test.js`,
41
+ `${componentName}.spec.tsx`,
42
+ `${componentName}.spec.ts`,
43
+ `${componentName}.spec.js`,
44
+ `${componentName}.stories.tsx`,
45
+ ];
46
+ for (const pattern of patterns) {
47
+ const filePath = path.join(componentDir, pattern);
48
+ if (fs.existsSync(filePath))
49
+ return filePath;
50
+ }
51
+ return null;
52
+ }
53
+ // ============================================================================
54
+ // TYPESCRIPT ANALYSIS
55
+ // ============================================================================
56
+ export function analyzeTypeScript(content, lines) {
57
+ const issues = [];
58
+ let issueCounter = 0;
59
+ lines.forEach((line, index) => {
60
+ const lineNum = index + 1;
61
+ const anyMatches = line.match(/:\s*any\b|<any>|\bas\s+any\b/g);
62
+ if (anyMatches) {
63
+ issues.push({
64
+ id: `TS-${String(++issueCounter).padStart(3, '0')}`,
65
+ category: 'type-safety',
66
+ severity: 'warning',
67
+ line: lineNum,
68
+ code: line.trim(),
69
+ message: "Avoid using 'any' type - it bypasses TypeScript's type checking",
70
+ suggestion: 'Use specific types, unknown, or create a proper interface',
71
+ fixable: true,
72
+ fixType: 'replace',
73
+ docs: 'https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#any',
74
+ });
75
+ }
76
+ if (line.match(/\bas\s+[A-Z]/g) && !line.includes('as const')) {
77
+ issues.push({
78
+ id: `TS-${String(++issueCounter).padStart(3, '0')}`,
79
+ category: 'type-safety',
80
+ severity: 'info',
81
+ line: lineNum,
82
+ code: line.trim(),
83
+ message: 'Type assertion detected - prefer type guards or proper typing',
84
+ suggestion: 'Use type guards (typeof, instanceof) or proper type definitions',
85
+ fixable: false,
86
+ });
87
+ }
88
+ if (line.match(/(?:const|let|function)\s+\w+\s*=?\s*(?:\([^)]*\)|\([^)]*\)\s*=>)/)) {
89
+ const hasReturnType = line.match(/\)\s*:\s*\w+/);
90
+ const isExported = content.includes(`export`);
91
+ const isComponent = line.match(/[A-Z]\w*\s*=/);
92
+ if (!hasReturnType && (isExported || isComponent)) {
93
+ issues.push({
94
+ id: `TS-${String(++issueCounter).padStart(3, '0')}`,
95
+ category: 'type-safety',
96
+ severity: 'info',
97
+ line: lineNum,
98
+ code: line.trim(),
99
+ message: 'Consider adding explicit return type for better documentation',
100
+ suggestion: 'Add return type annotation after function parameters',
101
+ fixable: false,
102
+ });
103
+ }
104
+ }
105
+ if (line.includes('!.')) {
106
+ issues.push({
107
+ id: `TS-${String(++issueCounter).padStart(3, '0')}`,
108
+ category: 'type-safety',
109
+ severity: 'warning',
110
+ line: lineNum,
111
+ code: line.trim(),
112
+ message: 'Non-null assertion operator (!) can hide runtime errors',
113
+ suggestion: 'Use optional chaining (?.) with nullish coalescing (??) instead',
114
+ fixable: true,
115
+ fixType: 'replace',
116
+ });
117
+ }
118
+ });
119
+ return issues;
120
+ }
121
+ // ============================================================================
122
+ // REACT PATTERNS ANALYSIS
123
+ // ============================================================================
124
+ export function analyzeReactPatterns(content, lines, componentName) {
125
+ const issues = [];
126
+ let issueCounter = 0;
127
+ if (!content.includes('displayName') && !content.includes('forwardRef')) {
128
+ issues.push({
129
+ id: `REACT-${String(++issueCounter).padStart(3, '0')}`,
130
+ category: 'react-patterns',
131
+ severity: 'info',
132
+ message: 'Missing displayName for better React DevTools debugging',
133
+ suggestion: `Add: ${componentName}.displayName = '${componentName}';`,
134
+ fixable: true,
135
+ fixType: 'add',
136
+ });
137
+ }
138
+ lines.forEach((line, index) => {
139
+ const lineNum = index + 1;
140
+ if (line.match(/(?:onClick|onChange|onSubmit|onFocus|onBlur)=\{.*=>/)) {
141
+ issues.push({
142
+ id: `REACT-${String(++issueCounter).padStart(3, '0')}`,
143
+ category: 'performance',
144
+ severity: 'warning',
145
+ line: lineNum,
146
+ code: line.trim(),
147
+ message: 'Inline arrow function creates new reference on each render',
148
+ suggestion: 'Extract to useCallback or define outside JSX',
149
+ fixable: true,
150
+ fixType: 'refactor',
151
+ });
152
+ }
153
+ if (line.match(/(?:style|className)=\{\{[^}]+\}\}/)) {
154
+ issues.push({
155
+ id: `REACT-${String(++issueCounter).padStart(3, '0')}`,
156
+ category: 'performance',
157
+ severity: 'warning',
158
+ line: lineNum,
159
+ code: line.trim(),
160
+ message: 'Inline object literal creates new reference on each render',
161
+ suggestion: 'Extract to a constant outside the component or useMemo',
162
+ fixable: true,
163
+ fixType: 'refactor',
164
+ });
165
+ }
166
+ });
167
+ const useStateComplexPattern = /useState\((?:\{[^}]+\}|\[[^\]]+\]|JSON\.|localStorage)/g;
168
+ const complexStateMatches = content.match(useStateComplexPattern);
169
+ if (complexStateMatches) {
170
+ issues.push({
171
+ id: `REACT-${String(++issueCounter).padStart(3, '0')}`,
172
+ category: 'performance',
173
+ severity: 'info',
174
+ message: 'Complex initial state should use lazy initialization',
175
+ suggestion: 'Use useState(() => expensiveComputation()) for expensive initial values',
176
+ fixable: true,
177
+ fixType: 'refactor',
178
+ });
179
+ }
180
+ const useEffectCount = (content.match(/useEffect\(/g) || []).length;
181
+ const cleanupCount = (content.match(/return\s+\(\)\s*=>|return\s+function/g) || []).length;
182
+ if (useEffectCount > cleanupCount && content.includes('addEventListener')) {
183
+ issues.push({
184
+ id: `REACT-${String(++issueCounter).padStart(3, '0')}`,
185
+ category: 'react-patterns',
186
+ severity: 'warning',
187
+ message: 'useEffect with subscriptions/event listeners should have cleanup',
188
+ suggestion: 'Return a cleanup function from useEffect to prevent memory leaks',
189
+ fixable: true,
190
+ fixType: 'add',
191
+ });
192
+ }
193
+ lines.forEach((line, index) => {
194
+ if (line.includes('.map(') && !content.substring(content.indexOf('.map(')).includes('key=')) {
195
+ issues.push({
196
+ id: `REACT-${String(++issueCounter).padStart(3, '0')}`,
197
+ category: 'react-patterns',
198
+ severity: 'error',
199
+ line: index + 1,
200
+ code: line.trim(),
201
+ message: 'Missing key prop in list rendering',
202
+ suggestion: 'Add unique key prop to each element in the map callback',
203
+ fixable: true,
204
+ fixType: 'add',
205
+ });
206
+ }
207
+ });
208
+ const propDrillingPattern = /(\w+):\s*(\w+Props)/g;
209
+ let propDrillCount = 0;
210
+ let match;
211
+ while ((match = propDrillingPattern.exec(content)) !== null) {
212
+ propDrillCount++;
213
+ }
214
+ if (propDrillCount > 2) {
215
+ issues.push({
216
+ id: `REACT-${String(++issueCounter).padStart(3, '0')}`,
217
+ category: 'react-patterns',
218
+ severity: 'info',
219
+ message: 'Possible prop drilling detected - consider using Context or composition',
220
+ suggestion: 'Use React.createContext or component composition to reduce prop drilling',
221
+ fixable: false,
222
+ });
223
+ }
224
+ return issues;
225
+ }
226
+ // ============================================================================
227
+ // ACCESSIBILITY ANALYSIS
228
+ // ============================================================================
229
+ export function analyzeAccessibility(content, lines, _componentName) {
230
+ const issues = [];
231
+ let issueCounter = 0;
232
+ lines.forEach((line, index) => {
233
+ const lineNum = index + 1;
234
+ if (line.match(/<img\s/) && !line.includes('alt=')) {
235
+ issues.push({
236
+ id: `A11Y-${String(++issueCounter).padStart(3, '0')}`,
237
+ category: 'accessibility',
238
+ severity: 'error',
239
+ line: lineNum,
240
+ code: line.trim(),
241
+ message: 'Image missing alt attribute',
242
+ suggestion: 'Add alt attribute describing the image or alt="" for decorative images',
243
+ fixable: true,
244
+ fixType: 'add',
245
+ wcag: '1.1.1',
246
+ });
247
+ }
248
+ if (line.match(/<input\s/) && !line.includes('aria-label') && !line.includes('aria-labelledby')) {
249
+ const hasId = line.match(/id=["'](\w+)["']/);
250
+ if (hasId) {
251
+ const labelPattern = new RegExp(`htmlFor=["']${hasId[1]}["']|for=["']${hasId[1]}["']`);
252
+ if (!content.match(labelPattern)) {
253
+ issues.push({
254
+ id: `A11Y-${String(++issueCounter).padStart(3, '0')}`,
255
+ category: 'accessibility',
256
+ severity: 'error',
257
+ line: lineNum,
258
+ code: line.trim(),
259
+ message: 'Form input missing associated label',
260
+ suggestion: 'Add <label htmlFor="..."> or aria-label attribute',
261
+ fixable: true,
262
+ fixType: 'add',
263
+ wcag: '1.3.1',
264
+ });
265
+ }
266
+ }
267
+ else {
268
+ issues.push({
269
+ id: `A11Y-${String(++issueCounter).padStart(3, '0')}`,
270
+ category: 'accessibility',
271
+ severity: 'warning',
272
+ line: lineNum,
273
+ code: line.trim(),
274
+ message: 'Form input should have id with associated label or aria-label',
275
+ suggestion: 'Add id attribute and matching <label htmlFor>, or use aria-label',
276
+ fixable: true,
277
+ fixType: 'add',
278
+ wcag: '1.3.1',
279
+ });
280
+ }
281
+ }
282
+ if (line.match(/<div[^>]*onClick/)) {
283
+ if (!line.includes('role=') && !line.includes('tabIndex')) {
284
+ issues.push({
285
+ id: `A11Y-${String(++issueCounter).padStart(3, '0')}`,
286
+ category: 'accessibility',
287
+ severity: 'error',
288
+ line: lineNum,
289
+ code: line.trim(),
290
+ message: 'Clickable div missing role and keyboard accessibility',
291
+ suggestion: 'Add role="button" tabIndex={0} and onKeyDown handler, or use <button>',
292
+ fixable: true,
293
+ fixType: 'add',
294
+ wcag: '4.1.2',
295
+ });
296
+ }
297
+ }
298
+ if (line.match(/tabIndex=["'][1-9]/)) {
299
+ issues.push({
300
+ id: `A11Y-${String(++issueCounter).padStart(3, '0')}`,
301
+ category: 'accessibility',
302
+ severity: 'warning',
303
+ line: lineNum,
304
+ code: line.trim(),
305
+ message: 'Avoid positive tabIndex values - they disrupt natural tab order',
306
+ suggestion: 'Use tabIndex={0} for focusable elements or tabIndex={-1} for programmatic focus',
307
+ fixable: true,
308
+ fixType: 'replace',
309
+ wcag: '2.4.3',
310
+ });
311
+ }
312
+ if (line.match(/<(?:video|audio)[^>]*autoPlay/)) {
313
+ issues.push({
314
+ id: `A11Y-${String(++issueCounter).padStart(3, '0')}`,
315
+ category: 'accessibility',
316
+ severity: 'warning',
317
+ line: lineNum,
318
+ code: line.trim(),
319
+ message: 'Autoplay media can be disruptive to users',
320
+ suggestion: 'Remove autoplay or add controls and muted attribute',
321
+ fixable: true,
322
+ fixType: 'replace',
323
+ wcag: '1.4.2',
324
+ });
325
+ }
326
+ });
327
+ if (content.includes('onClick') || content.includes('onKeyDown')) {
328
+ if (!content.includes('focus') && !content.includes('focus-visible') && !content.includes('outline')) {
329
+ issues.push({
330
+ id: `A11Y-${String(++issueCounter).padStart(3, '0')}`,
331
+ category: 'accessibility',
332
+ severity: 'warning',
333
+ message: 'Interactive elements should have visible focus styles',
334
+ suggestion: 'Add :focus or :focus-visible styles for keyboard navigation',
335
+ fixable: true,
336
+ fixType: 'add',
337
+ wcag: '2.4.7',
338
+ });
339
+ }
340
+ }
341
+ return issues;
342
+ }
343
+ // ============================================================================
344
+ // CODE QUALITY ANALYSIS
345
+ // ============================================================================
346
+ export function analyzeCodeQuality(content, lines, componentName) {
347
+ const issues = [];
348
+ let issueCounter = 0;
349
+ if (componentName.includes('-')) {
350
+ issues.push({
351
+ id: `QUAL-${String(++issueCounter).padStart(3, '0')}`,
352
+ category: 'code-quality',
353
+ severity: 'error',
354
+ message: `Component name "${componentName}" contains hyphens which are invalid JavaScript identifiers`,
355
+ suggestion: `Rename to "${componentName.replace(/-([a-z])/g, (_, c) => c.toUpperCase())}" (PascalCase without hyphens)`,
356
+ fixable: false,
357
+ });
358
+ }
359
+ lines.forEach((line, index) => {
360
+ const lineNum = index + 1;
361
+ const exportMatch = line.match(/export\s+(?:interface|type|const|function|class)\s+([\w-]+)/);
362
+ if (exportMatch && exportMatch[1].includes('-')) {
363
+ issues.push({
364
+ id: `QUAL-${String(++issueCounter).padStart(3, '0')}`,
365
+ category: 'code-quality',
366
+ severity: 'error',
367
+ line: lineNum,
368
+ code: line.trim(),
369
+ message: `Exported identifier "${exportMatch[1]}" contains hyphens which are invalid in JavaScript/TypeScript`,
370
+ suggestion: `Rename to PascalCase: "${exportMatch[1].replace(/-([a-z])/g, (_, c) => c.toUpperCase())}"`,
371
+ fixable: false,
372
+ });
373
+ }
374
+ });
375
+ lines.forEach((line, index) => {
376
+ const lineNum = index + 1;
377
+ const todoMatch = line.match(/\/\/\s*(TODO|FIXME|HACK|XXX):\s*(.+)/i);
378
+ if (todoMatch) {
379
+ issues.push({
380
+ id: `QUAL-${String(++issueCounter).padStart(3, '0')}`,
381
+ category: 'code-quality',
382
+ severity: 'info',
383
+ line: lineNum,
384
+ code: line.trim(),
385
+ message: `${todoMatch[1].toUpperCase()} comment found: ${todoMatch[2].trim()}`,
386
+ suggestion: 'Address this item before production release',
387
+ fixable: false,
388
+ });
389
+ }
390
+ });
391
+ lines.forEach((line, index) => {
392
+ if (line.match(/console\.(log|debug|info)\(/)) {
393
+ issues.push({
394
+ id: `QUAL-${String(++issueCounter).padStart(3, '0')}`,
395
+ category: 'code-quality',
396
+ severity: 'warning',
397
+ line: index + 1,
398
+ code: line.trim(),
399
+ message: 'Console statement found - should be removed in production',
400
+ suggestion: 'Remove console statement or use a proper logging library',
401
+ fixable: true,
402
+ fixType: 'remove',
403
+ });
404
+ }
405
+ });
406
+ if (lines.length > 300) {
407
+ issues.push({
408
+ id: `QUAL-${String(++issueCounter).padStart(3, '0')}`,
409
+ category: 'code-quality',
410
+ severity: 'warning',
411
+ message: `Component file is ${lines.length} lines (max recommended: 300)`,
412
+ suggestion: 'Consider splitting into smaller components or extracting custom hooks',
413
+ fixable: false,
414
+ });
415
+ }
416
+ return issues;
417
+ }
418
+ // ============================================================================
419
+ // SECURITY ANALYSIS
420
+ // ============================================================================
421
+ function analyzeSecurity(content, lines) {
422
+ const issues = [];
423
+ let issueCounter = 0;
424
+ lines.forEach((line, index) => {
425
+ const lineNum = index + 1;
426
+ if (line.includes('dangerouslySetInnerHTML')) {
427
+ issues.push({
428
+ id: `SEC-${String(++issueCounter).padStart(3, '0')}`,
429
+ category: 'security',
430
+ severity: 'error',
431
+ line: lineNum,
432
+ code: line.trim(),
433
+ message: 'dangerouslySetInnerHTML can introduce XSS vulnerabilities',
434
+ suggestion: 'Sanitize HTML content or use a safe rendering method',
435
+ fixable: false,
436
+ docs: 'https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml',
437
+ });
438
+ }
439
+ if (line.includes('eval(') || line.includes('new Function(')) {
440
+ issues.push({
441
+ id: `SEC-${String(++issueCounter).padStart(3, '0')}`,
442
+ category: 'security',
443
+ severity: 'error',
444
+ line: lineNum,
445
+ code: line.trim(),
446
+ message: 'eval() or new Function() usage is a security risk',
447
+ suggestion: 'Avoid dynamic code execution - use safe alternatives',
448
+ fixable: false,
449
+ });
450
+ }
451
+ if (line.includes('.innerHTML')) {
452
+ issues.push({
453
+ id: `SEC-${String(++issueCounter).padStart(3, '0')}`,
454
+ category: 'security',
455
+ severity: 'warning',
456
+ line: lineNum,
457
+ code: line.trim(),
458
+ message: 'Direct innerHTML manipulation can lead to XSS',
459
+ suggestion: "Use React's declarative rendering instead",
460
+ fixable: true,
461
+ fixType: 'refactor',
462
+ });
463
+ }
464
+ if (line.match(/(?:api[_-]?key|secret|password|token)\s*[:=]\s*["'][^"']+["']/i)) {
465
+ issues.push({
466
+ id: `SEC-${String(++issueCounter).padStart(3, '0')}`,
467
+ category: 'security',
468
+ severity: 'error',
469
+ line: lineNum,
470
+ code: line.replace(/["'][^"']+["']/, '"***"'),
471
+ message: 'Possible hardcoded secret detected',
472
+ suggestion: 'Use environment variables for sensitive values',
473
+ fixable: true,
474
+ fixType: 'refactor',
475
+ });
476
+ }
477
+ });
478
+ return issues;
479
+ }
480
+ // ============================================================================
481
+ // STYLING ANALYSIS
482
+ // ============================================================================
483
+ function analyzeStyling(content, lines) {
484
+ const issues = [];
485
+ let issueCounter = 0;
486
+ lines.forEach((line, index) => {
487
+ const lineNum = index + 1;
488
+ const colorPattern = /(?:background|color|border|bg)[^=]*=["'](?:#[0-9a-fA-F]{3,8}|rgb|rgba|hsl)/g;
489
+ if (line.match(colorPattern)) {
490
+ issues.push({
491
+ id: `STYLE-${String(++issueCounter).padStart(3, '0')}`,
492
+ category: 'styling',
493
+ severity: 'info',
494
+ line: lineNum,
495
+ code: line.trim(),
496
+ message: 'Hardcoded color value detected',
497
+ suggestion: 'Use design tokens or Tailwind color classes for consistency',
498
+ fixable: true,
499
+ fixType: 'replace',
500
+ });
501
+ }
502
+ if (line.match(/style=\{\{[^}]+\}\}/)) {
503
+ issues.push({
504
+ id: `STYLE-${String(++issueCounter).padStart(3, '0')}`,
505
+ category: 'styling',
506
+ severity: 'info',
507
+ line: lineNum,
508
+ code: line.trim(),
509
+ message: 'Inline style detected',
510
+ suggestion: 'Consider using Tailwind classes or CSS modules for better maintainability',
511
+ fixable: false,
512
+ });
513
+ }
514
+ if (line.includes('!important')) {
515
+ issues.push({
516
+ id: `STYLE-${String(++issueCounter).padStart(3, '0')}`,
517
+ category: 'styling',
518
+ severity: 'warning',
519
+ line: lineNum,
520
+ code: line.trim(),
521
+ message: '!important usage indicates specificity issues',
522
+ suggestion: 'Restructure CSS to avoid !important by using proper specificity',
523
+ fixable: true,
524
+ fixType: 'refactor',
525
+ });
526
+ }
527
+ });
528
+ return issues;
529
+ }
530
+ // ============================================================================
531
+ // TESTING ANALYSIS
532
+ // ============================================================================
533
+ function analyzeTesting(testContent, componentName) {
534
+ const issues = [];
535
+ let issueCounter = 0;
536
+ if (!testContent) {
537
+ issues.push({
538
+ id: `TEST-${String(++issueCounter).padStart(3, '0')}`,
539
+ category: 'testing',
540
+ severity: 'warning',
541
+ message: 'No test file found for this component',
542
+ suggestion: `Create ${componentName}.test.tsx with unit tests`,
543
+ fixable: false,
544
+ });
545
+ return issues;
546
+ }
547
+ if (!testContent.includes('aria') && !testContent.includes('role') && !testContent.includes('accessible')) {
548
+ issues.push({
549
+ id: `TEST-${String(++issueCounter).padStart(3, '0')}`,
550
+ category: 'testing',
551
+ severity: 'info',
552
+ message: 'No accessibility tests found',
553
+ suggestion: 'Add tests for ARIA attributes and screen reader compatibility',
554
+ fixable: false,
555
+ });
556
+ }
557
+ if (!testContent.includes('error') && !testContent.includes('Error')) {
558
+ issues.push({
559
+ id: `TEST-${String(++issueCounter).padStart(3, '0')}`,
560
+ category: 'testing',
561
+ severity: 'info',
562
+ message: 'No error state tests found',
563
+ suggestion: 'Add tests for error handling and edge cases',
564
+ fixable: false,
565
+ });
566
+ }
567
+ if (!testContent.includes('fireEvent') && !testContent.includes('userEvent')) {
568
+ issues.push({
569
+ id: `TEST-${String(++issueCounter).padStart(3, '0')}`,
570
+ category: 'testing',
571
+ severity: 'info',
572
+ message: 'No user interaction tests found',
573
+ suggestion: 'Add tests for click, input, and keyboard interactions',
574
+ fixable: false,
575
+ });
576
+ }
577
+ if (testContent.includes('toMatchSnapshot')) {
578
+ issues.push({
579
+ id: `TEST-${String(++issueCounter).padStart(3, '0')}`,
580
+ category: 'testing',
581
+ severity: 'info',
582
+ message: 'Snapshot test detected - consider behavioral tests instead',
583
+ suggestion: 'Prefer testing behavior and user interactions over snapshots',
584
+ fixable: false,
585
+ });
586
+ }
587
+ return issues;
588
+ }
589
+ // ============================================================================
590
+ // METRICS CALCULATION
591
+ // ============================================================================
592
+ function calculateMetrics(content, issues) {
593
+ const lines = content.split('\n');
594
+ const decisionPatterns = [
595
+ /\bif\s*\(/g,
596
+ /\belse\s+if\b/g,
597
+ /\bfor\s*\(/g,
598
+ /\bwhile\s*\(/g,
599
+ /\b\?\s*[^:]+\s*:/g,
600
+ /\b&&\b/g,
601
+ /\b\|\|\b/g,
602
+ ];
603
+ let complexity = 1;
604
+ decisionPatterns.forEach(pattern => {
605
+ const matches = content.match(pattern);
606
+ if (matches)
607
+ complexity += matches.length;
608
+ });
609
+ const maintainability = Math.max(0, Math.min(100, 100 - (complexity * 2) - (issues.length * 3) - (lines.length > 200 ? 10 : 0)));
610
+ const internalImports = (content.match(/from\s+['"]\.\//g) || []).length;
611
+ const externalImports = (content.match(/from\s+['"][^./]/g) || []).length;
612
+ return {
613
+ linesOfCode: lines.filter(l => l.trim().length > 0).length,
614
+ complexity,
615
+ maintainability,
616
+ dependencies: {
617
+ internal: internalImports,
618
+ external: externalImports,
619
+ unused: [],
620
+ },
621
+ };
622
+ }
623
+ // ============================================================================
624
+ // PATTERN DETECTION
625
+ // ============================================================================
626
+ function detectPatterns(content) {
627
+ const detected = [];
628
+ const suggested = [];
629
+ if (content.includes('useState'))
630
+ detected.push('state-management');
631
+ if (content.includes('useEffect'))
632
+ detected.push('side-effects');
633
+ if (content.includes('useContext'))
634
+ detected.push('context-consumer');
635
+ if (content.includes('createContext'))
636
+ detected.push('context-provider');
637
+ if (content.includes('forwardRef'))
638
+ detected.push('ref-forwarding');
639
+ if (content.includes('useMemo'))
640
+ detected.push('memoization');
641
+ if (content.includes('useCallback'))
642
+ detected.push('callback-memoization');
643
+ if (content.includes('React.memo'))
644
+ detected.push('component-memoization');
645
+ if (content.includes('children'))
646
+ detected.push('composition');
647
+ if (content.match(/render\w*=\{/))
648
+ detected.push('render-prop');
649
+ if (content.includes('useReducer'))
650
+ detected.push('reducer-pattern');
651
+ if (content.includes('useState') && content.includes('useEffect') && !content.includes('useReducer')) {
652
+ suggested.push('custom-hook');
653
+ }
654
+ if (content.includes('props.') && (content.match(/props\.\w+/g)?.length ?? 0) > 5) {
655
+ suggested.push('destructuring');
656
+ }
657
+ if (detected.includes('state-management') && !detected.includes('context-consumer')) {
658
+ suggested.push('context');
659
+ }
660
+ return { detected, suggested };
661
+ }
662
+ // ============================================================================
663
+ // GRADE CALCULATION
664
+ // ============================================================================
665
+ export function calculateGrade(score) {
666
+ if (score >= 95)
667
+ return 'A+';
668
+ if (score >= 85)
669
+ return 'A';
670
+ if (score >= 70)
671
+ return 'B';
672
+ if (score >= 55)
673
+ return 'C';
674
+ if (score >= 40)
675
+ return 'D';
676
+ return 'F';
677
+ }
678
+ function calculateEstimatedFixTime(issues) {
679
+ let minutes = 0;
680
+ issues.forEach(issue => {
681
+ switch (issue.severity) {
682
+ case 'error':
683
+ minutes += 5;
684
+ break;
685
+ case 'warning':
686
+ minutes += 3;
687
+ break;
688
+ case 'info':
689
+ minutes += 1;
690
+ break;
691
+ }
692
+ });
693
+ if (minutes < 60)
694
+ return `${minutes}min`;
695
+ return `${Math.round(minutes / 60)}h`;
696
+ }
697
+ // ============================================================================
698
+ // TYPESCRIPT & TEST EXECUTION
699
+ // ============================================================================
700
+ function runTypeScriptCheck(componentDir) {
701
+ try {
702
+ const tsconfigPath = findTsconfig(componentDir);
703
+ if (!tsconfigPath) {
704
+ return { errors: ['No tsconfig.json found'], passed: true };
705
+ }
706
+ execSync(`npx tsc --noEmit --project ${tsconfigPath}`, {
707
+ cwd: componentDir,
708
+ stdio: 'pipe',
709
+ timeout: 30000,
710
+ });
711
+ return { errors: [], passed: true };
712
+ }
713
+ catch (error) {
714
+ const err = error;
715
+ const output = err.stdout?.toString() || err.stderr?.toString() || err.message;
716
+ const errors = output.split('\n').filter((line) => line.trim().length > 0);
717
+ return { errors: errors.slice(0, 10), passed: false };
718
+ }
719
+ }
720
+ function runTests(componentDir, componentName) {
721
+ const testFile = findTestFile(componentDir, componentName);
722
+ if (!testFile) {
723
+ return { passed: 0, failed: 0, errors: ['No test file found'] };
724
+ }
725
+ try {
726
+ const output = execSync(`npx vitest run ${testFile} --reporter=json 2>&1`, {
727
+ cwd: path.join(componentDir, '..', '..'),
728
+ stdio: 'pipe',
729
+ timeout: 60000,
730
+ }).toString();
731
+ try {
732
+ const result = JSON.parse(output);
733
+ const testResult = result.testResults?.[0];
734
+ return {
735
+ passed: testResult?.numPassedTests || result.numPassedTests || 0,
736
+ failed: testResult?.numFailedTests || result.numFailedTests || 0,
737
+ errors: testResult?.message ? [testResult.message] : [],
738
+ };
739
+ }
740
+ catch {
741
+ if (output.includes('Tests') && output.includes('passed')) {
742
+ return { passed: 1, failed: 0, errors: [] };
743
+ }
744
+ return { passed: 0, failed: 0, errors: ['Failed to parse test output'] };
745
+ }
746
+ }
747
+ catch (error) {
748
+ const err = error;
749
+ const output = err.stdout?.toString() || err.stderr?.toString() || err.message;
750
+ if (output.includes('FAIL') || output.includes('failed')) {
751
+ return { passed: 0, failed: 1, errors: [output.substring(0, 500)] };
752
+ }
753
+ return { passed: 0, failed: 0, errors: [output.substring(0, 500)] };
754
+ }
755
+ }
756
+ function findTsconfig(dir) {
757
+ let current = dir;
758
+ while (current !== '/' && current !== '.') {
759
+ const tsconfig = path.join(current, 'tsconfig.json');
760
+ if (fs.existsSync(tsconfig))
761
+ return tsconfig;
762
+ current = path.dirname(current);
763
+ }
764
+ return null;
765
+ }
766
+ // ============================================================================
767
+ // QUICK FIXES GENERATION
768
+ // ============================================================================
769
+ function generateQuickFixes(issues) {
770
+ const quickFixes = [];
771
+ const fixableIssues = issues.filter(i => i.fixable);
772
+ const typeIssues = fixableIssues.filter(i => i.category === 'type-safety');
773
+ if (typeIssues.length > 0) {
774
+ quickFixes.push({
775
+ id: 'fix-types',
776
+ description: `Fix ${typeIssues.length} type safety issues`,
777
+ issueIds: typeIssues.map(i => i.id),
778
+ automated: true,
779
+ });
780
+ }
781
+ const a11yIssues = fixableIssues.filter(i => i.category === 'accessibility');
782
+ if (a11yIssues.length > 0) {
783
+ quickFixes.push({
784
+ id: 'fix-a11y',
785
+ description: `Fix ${a11yIssues.length} accessibility issues`,
786
+ issueIds: a11yIssues.map(i => i.id),
787
+ automated: true,
788
+ });
789
+ }
790
+ const perfIssues = fixableIssues.filter(i => i.category === 'performance');
791
+ if (perfIssues.length > 0) {
792
+ quickFixes.push({
793
+ id: 'fix-perf',
794
+ description: `Fix ${perfIssues.length} performance issues`,
795
+ issueIds: perfIssues.map(i => i.id),
796
+ automated: false,
797
+ });
798
+ }
799
+ return quickFixes;
800
+ }
801
+ // ============================================================================
802
+ // MAIN SERVER CLASS
803
+ // ============================================================================
804
+ class ComponentReviewerServer extends McpServerBase {
805
+ constructor() {
806
+ super({ name: 'component-reviewer', version: '3.0.0' });
807
+ }
808
+ registerTools() {
809
+ this.addTool('review', 'Comprehensive React component review - analyzes TypeScript, React patterns, accessibility, performance, security, code quality, and testing', {
810
+ type: 'object',
811
+ properties: {
812
+ path: {
813
+ type: 'string',
814
+ description: 'Path to the component file or directory to review',
815
+ },
816
+ },
817
+ required: ['path'],
818
+ }, this.handleReview.bind(this));
819
+ }
820
+ async handleReview(args) {
821
+ const { path: componentPath } = args;
822
+ try {
823
+ const resolvedPath = path.resolve(componentPath);
824
+ if (!fs.existsSync(resolvedPath)) {
825
+ throw new Error(`Component path does not exist: ${componentPath}`);
826
+ }
827
+ let componentDir;
828
+ let componentName;
829
+ const stat = fs.statSync(resolvedPath);
830
+ if (stat.isFile()) {
831
+ componentDir = path.dirname(resolvedPath);
832
+ componentName = path.basename(resolvedPath, path.extname(resolvedPath));
833
+ }
834
+ else {
835
+ componentDir = resolvedPath;
836
+ componentName = path.basename(resolvedPath);
837
+ }
838
+ const componentFile = findComponentFile(componentDir, componentName);
839
+ if (!componentFile) {
840
+ throw new Error(`Component file not found for: ${componentName}`);
841
+ }
842
+ const content = readFileContent(componentFile);
843
+ if (!content) {
844
+ throw new Error(`Could not read component file: ${componentFile}`);
845
+ }
846
+ const lines = getLines(content);
847
+ const testFile = findTestFile(componentDir, componentName);
848
+ const testContent = testFile ? readFileContent(testFile) : null;
849
+ const typeScriptIssues = analyzeTypeScript(content, lines);
850
+ const reactIssues = analyzeReactPatterns(content, lines, componentName);
851
+ const accessibilityIssues = analyzeAccessibility(content, lines, componentName);
852
+ const codeQualityIssues = analyzeCodeQuality(content, lines, componentName);
853
+ const securityIssues = analyzeSecurity(content, lines);
854
+ const stylingIssues = analyzeStyling(content, lines);
855
+ const testingIssues = analyzeTesting(testContent, componentName);
856
+ const allIssues = [
857
+ ...typeScriptIssues,
858
+ ...reactIssues,
859
+ ...accessibilityIssues,
860
+ ...codeQualityIssues,
861
+ ...securityIssues,
862
+ ...stylingIssues,
863
+ ...testingIssues,
864
+ ];
865
+ const tsResult = runTypeScriptCheck(componentDir);
866
+ const testResults = runTests(componentDir, componentName);
867
+ const metrics = calculateMetrics(content, allIssues);
868
+ const patterns = detectPatterns(content);
869
+ const quickFixes = generateQuickFixes(allIssues);
870
+ const criticalIssues = allIssues.filter(i => i.severity === 'error').length;
871
+ const warningIssues = allIssues.filter(i => i.severity === 'warning').length;
872
+ const infoIssues = allIssues.filter(i => i.severity === 'info').length;
873
+ const categories = {
874
+ 'type-safety': 0,
875
+ 'react-patterns': 0,
876
+ 'accessibility': 0,
877
+ 'performance': 0,
878
+ 'code-quality': 0,
879
+ 'security': 0,
880
+ 'styling': 0,
881
+ 'testing': 0,
882
+ };
883
+ allIssues.forEach(issue => {
884
+ categories[issue.category]++;
885
+ });
886
+ let score = 100;
887
+ score -= criticalIssues * 10;
888
+ score -= warningIssues * 5;
889
+ score -= infoIssues * 1;
890
+ score -= tsResult.passed ? 0 : 20;
891
+ score -= testResults.failed * 10;
892
+ score = Math.max(0, Math.min(100, score));
893
+ const summary = {
894
+ component: componentName,
895
+ file: path.relative(process.cwd(), componentFile),
896
+ linesOfCode: metrics.linesOfCode,
897
+ overallScore: score,
898
+ grade: calculateGrade(score),
899
+ totalIssues: allIssues.length,
900
+ criticalIssues,
901
+ warningIssues,
902
+ infoIssues,
903
+ estimatedFixTime: calculateEstimatedFixTime(allIssues),
904
+ categories,
905
+ };
906
+ const result = {
907
+ success: true,
908
+ summary,
909
+ issues: allIssues,
910
+ metrics,
911
+ patterns,
912
+ quickFixes,
913
+ typescriptErrors: tsResult.errors,
914
+ testResults,
915
+ };
916
+ return {
917
+ content: [{
918
+ type: 'text',
919
+ text: JSON.stringify(result, null, 2),
920
+ }],
921
+ };
922
+ }
923
+ catch (error) {
924
+ return {
925
+ content: [{
926
+ type: 'text',
927
+ text: JSON.stringify({
928
+ success: false,
929
+ error: {
930
+ code: error instanceof Error ? error.constructor.name : 'UNKNOWN_ERROR',
931
+ message: error instanceof Error ? error.message : String(error),
932
+ suggestion: 'Check input parameters and ensure the component path is valid.',
933
+ timestamp: new Date().toISOString(),
934
+ },
935
+ }, null, 2),
936
+ }],
937
+ isError: true,
938
+ };
939
+ }
940
+ }
941
+ }
942
+ // ============================================================================
943
+ // ENTRY POINT
944
+ // ============================================================================
945
+ new ComponentReviewerServer().run().catch(console.error);
946
+ //# sourceMappingURL=index.js.map