react-code-smell-detector 1.4.2 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +227 -22
  2. package/dist/__tests__/aiRefactoring.test.d.ts +2 -0
  3. package/dist/__tests__/aiRefactoring.test.d.ts.map +1 -0
  4. package/dist/__tests__/aiRefactoring.test.js +86 -0
  5. package/dist/__tests__/analyzer-real.test.d.ts +2 -0
  6. package/dist/__tests__/analyzer-real.test.d.ts.map +1 -0
  7. package/dist/__tests__/analyzer-real.test.js +149 -0
  8. package/dist/__tests__/analyzer.test.d.ts +2 -0
  9. package/dist/__tests__/analyzer.test.d.ts.map +1 -0
  10. package/dist/__tests__/analyzer.test.js +173 -0
  11. package/dist/__tests__/baseline.test.d.ts +2 -0
  12. package/dist/__tests__/baseline.test.d.ts.map +1 -0
  13. package/dist/__tests__/baseline.test.js +136 -0
  14. package/dist/__tests__/bundleAnalyzer.test.d.ts +2 -0
  15. package/dist/__tests__/bundleAnalyzer.test.d.ts.map +1 -0
  16. package/dist/__tests__/bundleAnalyzer.test.js +182 -0
  17. package/dist/__tests__/customRules.test.d.ts +2 -0
  18. package/dist/__tests__/customRules.test.d.ts.map +1 -0
  19. package/dist/__tests__/customRules.test.js +283 -0
  20. package/dist/__tests__/detectors/index.test.d.ts +2 -0
  21. package/dist/__tests__/detectors/index.test.d.ts.map +1 -0
  22. package/dist/__tests__/detectors/index.test.js +1012 -0
  23. package/dist/__tests__/detectors/newDetectors.test.d.ts +2 -0
  24. package/dist/__tests__/detectors/newDetectors.test.d.ts.map +1 -0
  25. package/dist/__tests__/detectors/newDetectors.test.js +333 -0
  26. package/dist/__tests__/docGenerator.test.d.ts +2 -0
  27. package/dist/__tests__/docGenerator.test.d.ts.map +1 -0
  28. package/dist/__tests__/docGenerator.test.js +157 -0
  29. package/dist/__tests__/fixer.test.d.ts +2 -0
  30. package/dist/__tests__/fixer.test.d.ts.map +1 -0
  31. package/dist/__tests__/fixer.test.js +193 -0
  32. package/dist/__tests__/git.test.d.ts +2 -0
  33. package/dist/__tests__/git.test.d.ts.map +1 -0
  34. package/dist/__tests__/git.test.js +38 -0
  35. package/dist/__tests__/graphGenerator.test.d.ts +2 -0
  36. package/dist/__tests__/graphGenerator.test.d.ts.map +1 -0
  37. package/dist/__tests__/graphGenerator.test.js +190 -0
  38. package/dist/__tests__/htmlReporter.test.d.ts +2 -0
  39. package/dist/__tests__/htmlReporter.test.d.ts.map +1 -0
  40. package/dist/__tests__/htmlReporter.test.js +258 -0
  41. package/dist/__tests__/interactiveFixer.test.d.ts +2 -0
  42. package/dist/__tests__/interactiveFixer.test.d.ts.map +1 -0
  43. package/dist/__tests__/interactiveFixer.test.js +231 -0
  44. package/dist/__tests__/parser.test.d.ts +2 -0
  45. package/dist/__tests__/parser.test.d.ts.map +1 -0
  46. package/dist/__tests__/parser.test.js +56 -0
  47. package/dist/__tests__/performanceBudget.test.d.ts +2 -0
  48. package/dist/__tests__/performanceBudget.test.d.ts.map +1 -0
  49. package/dist/__tests__/performanceBudget.test.js +242 -0
  50. package/dist/__tests__/prComments.test.d.ts +2 -0
  51. package/dist/__tests__/prComments.test.d.ts.map +1 -0
  52. package/dist/__tests__/prComments.test.js +118 -0
  53. package/dist/__tests__/reporter.test.d.ts +2 -0
  54. package/dist/__tests__/reporter.test.d.ts.map +1 -0
  55. package/dist/__tests__/reporter.test.js +136 -0
  56. package/dist/__tests__/watcher.test.d.ts +2 -0
  57. package/dist/__tests__/watcher.test.d.ts.map +1 -0
  58. package/dist/__tests__/watcher.test.js +161 -0
  59. package/dist/__tests__/webhooks.test.d.ts +2 -0
  60. package/dist/__tests__/webhooks.test.d.ts.map +1 -0
  61. package/dist/__tests__/webhooks.test.js +209 -0
  62. package/dist/aiRefactoring.d.ts +29 -0
  63. package/dist/aiRefactoring.d.ts.map +1 -0
  64. package/dist/aiRefactoring.js +290 -0
  65. package/dist/analyzer.d.ts.map +1 -1
  66. package/dist/analyzer.js +33 -1
  67. package/dist/cli.js +123 -1
  68. package/dist/detectors/contextApi.d.ts +11 -0
  69. package/dist/detectors/contextApi.d.ts.map +1 -0
  70. package/dist/detectors/contextApi.js +151 -0
  71. package/dist/detectors/errorBoundary.d.ts +11 -0
  72. package/dist/detectors/errorBoundary.d.ts.map +1 -0
  73. package/dist/detectors/errorBoundary.js +167 -0
  74. package/dist/detectors/formValidation.d.ts +11 -0
  75. package/dist/detectors/formValidation.d.ts.map +1 -0
  76. package/dist/detectors/formValidation.js +193 -0
  77. package/dist/detectors/index.d.ts +6 -0
  78. package/dist/detectors/index.d.ts.map +1 -1
  79. package/dist/detectors/index.js +12 -0
  80. package/dist/detectors/serverComponents.d.ts +11 -0
  81. package/dist/detectors/serverComponents.d.ts.map +1 -0
  82. package/dist/detectors/serverComponents.js +222 -0
  83. package/dist/detectors/stateManagement.d.ts +11 -0
  84. package/dist/detectors/stateManagement.d.ts.map +1 -0
  85. package/dist/detectors/stateManagement.js +193 -0
  86. package/dist/detectors/testingGaps.d.ts +15 -0
  87. package/dist/detectors/testingGaps.d.ts.map +1 -0
  88. package/dist/detectors/testingGaps.js +182 -0
  89. package/dist/docGenerator.d.ts +37 -0
  90. package/dist/docGenerator.d.ts.map +1 -0
  91. package/dist/docGenerator.js +306 -0
  92. package/dist/guide.d.ts +9 -0
  93. package/dist/guide.d.ts.map +1 -0
  94. package/dist/guide.js +922 -0
  95. package/dist/index.d.ts +5 -0
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +5 -0
  98. package/dist/interactiveFixer.d.ts +20 -0
  99. package/dist/interactiveFixer.d.ts.map +1 -0
  100. package/dist/interactiveFixer.js +178 -0
  101. package/dist/performanceBudget.d.ts +54 -0
  102. package/dist/performanceBudget.d.ts.map +1 -0
  103. package/dist/performanceBudget.js +218 -0
  104. package/dist/prComments.d.ts +47 -0
  105. package/dist/prComments.d.ts.map +1 -0
  106. package/dist/prComments.js +233 -0
  107. package/dist/types/index.d.ts +12 -1
  108. package/dist/types/index.d.ts.map +1 -1
  109. package/dist/types/index.js +18 -0
  110. package/package.json +10 -4
@@ -0,0 +1,193 @@
1
+ import * as t from '@babel/types';
2
+ /**
3
+ * Detect form-related anti-patterns and validation issues
4
+ */
5
+ export function detectFormIssues(component, filePath, sourceCode, config) {
6
+ if (!config.checkForms)
7
+ return [];
8
+ const smells = [];
9
+ // Track form elements and their attributes
10
+ const formElements = [];
11
+ const inputElements = [];
12
+ const labeledInputIds = new Set();
13
+ const inputIds = new Set();
14
+ component.path.traverse({
15
+ JSXElement(path) {
16
+ const openingElement = path.node.openingElement;
17
+ if (!t.isJSXIdentifier(openingElement.name))
18
+ return;
19
+ const elementName = openingElement.name.name.toLowerCase();
20
+ const loc = openingElement.loc;
21
+ const attributes = openingElement.attributes;
22
+ // Check form elements
23
+ if (elementName === 'form') {
24
+ const hasOnSubmit = attributes.some(attr => t.isJSXAttribute(attr) &&
25
+ t.isJSXIdentifier(attr.name) &&
26
+ attr.name.name === 'onSubmit');
27
+ const hasAction = attributes.some(attr => t.isJSXAttribute(attr) &&
28
+ t.isJSXIdentifier(attr.name) &&
29
+ attr.name.name === 'action');
30
+ formElements.push({
31
+ line: loc?.start.line || 0,
32
+ hasOnSubmit,
33
+ hasAction,
34
+ });
35
+ if (!hasOnSubmit && !hasAction) {
36
+ smells.push({
37
+ type: 'form-without-onsubmit',
38
+ severity: 'warning',
39
+ message: `Form without onSubmit handler in "${component.name}"`,
40
+ file: filePath,
41
+ line: loc?.start.line || 0,
42
+ column: loc?.start.column || 0,
43
+ suggestion: 'Add an onSubmit handler to handle form submission and prevent default behavior.',
44
+ });
45
+ }
46
+ }
47
+ // Check input elements
48
+ if (elementName === 'input' || elementName === 'textarea' || elementName === 'select') {
49
+ const hasValue = attributes.some(attr => t.isJSXAttribute(attr) &&
50
+ t.isJSXIdentifier(attr.name) &&
51
+ (attr.name.name === 'value' || attr.name.name === 'checked'));
52
+ const hasDefaultValue = attributes.some(attr => t.isJSXAttribute(attr) &&
53
+ t.isJSXIdentifier(attr.name) &&
54
+ (attr.name.name === 'defaultValue' || attr.name.name === 'defaultChecked'));
55
+ const hasOnChange = attributes.some(attr => t.isJSXAttribute(attr) &&
56
+ t.isJSXIdentifier(attr.name) &&
57
+ attr.name.name === 'onChange');
58
+ const hasName = attributes.some(attr => t.isJSXAttribute(attr) &&
59
+ t.isJSXIdentifier(attr.name) &&
60
+ attr.name.name === 'name');
61
+ // Get input type
62
+ let inputType = 'text';
63
+ const typeAttr = attributes.find(attr => t.isJSXAttribute(attr) &&
64
+ t.isJSXIdentifier(attr.name) &&
65
+ attr.name.name === 'type');
66
+ if (typeAttr && t.isJSXAttribute(typeAttr) && t.isStringLiteral(typeAttr.value)) {
67
+ inputType = typeAttr.value.value;
68
+ }
69
+ // Get input id for label checking
70
+ const idAttr = attributes.find(attr => t.isJSXAttribute(attr) &&
71
+ t.isJSXIdentifier(attr.name) &&
72
+ attr.name.name === 'id');
73
+ if (idAttr && t.isJSXAttribute(idAttr) && t.isStringLiteral(idAttr.value)) {
74
+ inputIds.add(idAttr.value.value);
75
+ }
76
+ inputElements.push({
77
+ line: loc?.start.line || 0,
78
+ hasValue,
79
+ hasOnChange,
80
+ hasName,
81
+ type: inputType,
82
+ });
83
+ // Check for uncontrolled inputs (no value or defaultValue)
84
+ if (!hasValue && !hasDefaultValue && !hasOnChange) {
85
+ // Skip hidden, submit, button, reset types
86
+ const skipTypes = ['hidden', 'submit', 'button', 'reset', 'image'];
87
+ if (!skipTypes.includes(inputType)) {
88
+ smells.push({
89
+ type: 'uncontrolled-form',
90
+ severity: 'info',
91
+ message: `Uncontrolled ${elementName} element in "${component.name}" - consider using controlled components`,
92
+ file: filePath,
93
+ line: loc?.start.line || 0,
94
+ column: loc?.start.column || 0,
95
+ suggestion: 'Use value and onChange for controlled components, or ref for uncontrolled access.',
96
+ });
97
+ }
98
+ }
99
+ // Check for controlled input without onChange (will be read-only)
100
+ if (hasValue && !hasOnChange && !hasDefaultValue) {
101
+ smells.push({
102
+ type: 'uncontrolled-form',
103
+ severity: 'error',
104
+ message: `Input has value prop but no onChange handler in "${component.name}" - input will be read-only`,
105
+ file: filePath,
106
+ line: loc?.start.line || 0,
107
+ column: loc?.start.column || 0,
108
+ suggestion: 'Add an onChange handler or use defaultValue instead of value.',
109
+ });
110
+ }
111
+ }
112
+ // Check for label elements and collect their "for/htmlFor" attributes
113
+ if (elementName === 'label') {
114
+ const forAttr = attributes.find(attr => t.isJSXAttribute(attr) &&
115
+ t.isJSXIdentifier(attr.name) &&
116
+ (attr.name.name === 'htmlFor' || attr.name.name === 'for'));
117
+ if (forAttr && t.isJSXAttribute(forAttr) && t.isStringLiteral(forAttr.value)) {
118
+ labeledInputIds.add(forAttr.value.value);
119
+ }
120
+ }
121
+ },
122
+ });
123
+ // Check for inputs without associated labels
124
+ inputIds.forEach(id => {
125
+ if (!labeledInputIds.has(id)) {
126
+ // This is already handled by accessibility detector, but we note it for forms
127
+ }
128
+ });
129
+ // Check for missing form validation patterns
130
+ const hasValidationLibrary = hasFormValidation(sourceCode);
131
+ if (formElements.length > 0 && !hasValidationLibrary) {
132
+ smells.push({
133
+ type: 'missing-form-validation',
134
+ severity: 'info',
135
+ message: `Form in "${component.name}" without apparent validation library`,
136
+ file: filePath,
137
+ line: formElements[0].line,
138
+ column: 0,
139
+ suggestion: 'Consider using a form validation library like react-hook-form, formik, or yup for robust validation.',
140
+ });
141
+ }
142
+ return smells;
143
+ }
144
+ /**
145
+ * Check if code uses common form validation patterns/libraries
146
+ */
147
+ function hasFormValidation(sourceCode) {
148
+ const validationPatterns = [
149
+ 'useForm', // react-hook-form
150
+ 'useFormik', // formik
151
+ 'Formik', // formik component
152
+ 'yup.', // yup validation
153
+ 'zod.', // zod validation
154
+ '.validate(', // custom validation
155
+ 'validator.', // validator.js
156
+ 'Joi.', // joi validation
157
+ 'required:', // validation rules
158
+ 'pattern:', // regex validation
159
+ 'minLength:', // length validation
160
+ 'schema.validate', // schema validation
161
+ ];
162
+ return validationPatterns.some(pattern => sourceCode.includes(pattern));
163
+ }
164
+ /**
165
+ * Detect form-related patterns at file level
166
+ */
167
+ export function detectFormPatterns(sourceCode, filePath, config) {
168
+ if (!config.checkForms)
169
+ return [];
170
+ const smells = [];
171
+ const lines = sourceCode.split('\n');
172
+ // Check for form submission without preventDefault
173
+ lines.forEach((line, index) => {
174
+ // Pattern: onSubmit={(e) => { ... }} without e.preventDefault()
175
+ if (line.includes('onSubmit') && line.includes('=>')) {
176
+ // Look ahead for preventDefault in nearby lines
177
+ const nextLines = lines.slice(index, index + 5).join('\n');
178
+ if (!nextLines.includes('preventDefault')) {
179
+ smells.push({
180
+ type: 'form-without-onsubmit',
181
+ severity: 'warning',
182
+ message: 'Form onSubmit handler may be missing e.preventDefault()',
183
+ file: filePath,
184
+ line: index + 1,
185
+ column: 0,
186
+ suggestion: 'Call e.preventDefault() in form submit handlers to prevent default page reload.',
187
+ codeSnippet: line.trim(),
188
+ });
189
+ }
190
+ }
191
+ });
192
+ return smells;
193
+ }
@@ -20,4 +20,10 @@ export { detectComplexity } from './complexity.js';
20
20
  export { detectMemoryLeaks } from './memoryLeak.js';
21
21
  export { detectImportIssues, analyzeImports } from './imports.js';
22
22
  export { detectUnusedCode } from './unusedCode.js';
23
+ export { detectServerComponentIssues, detectAsyncComponentIssues } from './serverComponents.js';
24
+ export { detectContextIssues, detectContextProviderIssues } from './contextApi.js';
25
+ export { detectErrorBoundaryIssues, detectMissingErrorBoundaries } from './errorBoundary.js';
26
+ export { detectFormIssues, detectFormPatterns } from './formValidation.js';
27
+ export { detectStateManagementIssues, detectStateManagementPatterns } from './stateManagement.js';
28
+ export { detectTestingGaps, generateTestSuggestions } from './testingGaps.js';
23
29
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/detectors/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AACjF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAE/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/detectors/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AACjF,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAE/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAAE,2BAA2B,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AAEhG,OAAO,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,iBAAiB,CAAC;AAEnF,OAAO,EAAE,yBAAyB,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAE7F,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE3E,OAAO,EAAE,2BAA2B,EAAE,6BAA6B,EAAE,MAAM,sBAAsB,CAAC;AAElG,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -23,3 +23,15 @@ export { detectComplexity } from './complexity.js';
23
23
  export { detectMemoryLeaks } from './memoryLeak.js';
24
24
  export { detectImportIssues, analyzeImports } from './imports.js';
25
25
  export { detectUnusedCode } from './unusedCode.js';
26
+ // Server Components (React 19)
27
+ export { detectServerComponentIssues, detectAsyncComponentIssues } from './serverComponents.js';
28
+ // Context API Analysis
29
+ export { detectContextIssues, detectContextProviderIssues } from './contextApi.js';
30
+ // Error Boundary Detection
31
+ export { detectErrorBoundaryIssues, detectMissingErrorBoundaries } from './errorBoundary.js';
32
+ // Form Validation
33
+ export { detectFormIssues, detectFormPatterns } from './formValidation.js';
34
+ // State Management
35
+ export { detectStateManagementIssues, detectStateManagementPatterns } from './stateManagement.js';
36
+ // Testing Gaps
37
+ export { detectTestingGaps, generateTestSuggestions } from './testingGaps.js';
@@ -0,0 +1,11 @@
1
+ import { ParsedComponent } from '../parser/index.js';
2
+ import { CodeSmell, DetectorConfig } from '../types/index.js';
3
+ /**
4
+ * Detect React 19 Server/Client component boundary issues
5
+ */
6
+ export declare function detectServerComponentIssues(component: ParsedComponent, filePath: string, sourceCode: string, config: DetectorConfig, imports?: string[]): CodeSmell[];
7
+ /**
8
+ * Detect proper async component patterns in React 19
9
+ */
10
+ export declare function detectAsyncComponentIssues(component: ParsedComponent, filePath: string, sourceCode: string, config: DetectorConfig): CodeSmell[];
11
+ //# sourceMappingURL=serverComponents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serverComponents.d.ts","sourceRoot":"","sources":["../../src/detectors/serverComponents.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAqC9D;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,SAAS,EAAE,eAAe,EAC1B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,MAAM,EAAO,GACrB,SAAS,EAAE,CA+Jb;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,eAAe,EAC1B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,cAAc,GACrB,SAAS,EAAE,CAmCb"}
@@ -0,0 +1,222 @@
1
+ import * as t from '@babel/types';
2
+ // Hooks that are not allowed in Server Components
3
+ const CLIENT_ONLY_HOOKS = [
4
+ 'useState',
5
+ 'useReducer',
6
+ 'useEffect',
7
+ 'useLayoutEffect',
8
+ 'useInsertionEffect',
9
+ 'useRef',
10
+ 'useImperativeHandle',
11
+ 'useSyncExternalStore',
12
+ 'useTransition',
13
+ 'useDeferredValue',
14
+ 'useOptimistic',
15
+ 'useFormStatus',
16
+ 'useActionState',
17
+ ];
18
+ // Browser-only APIs
19
+ const BROWSER_APIS = [
20
+ 'window',
21
+ 'document',
22
+ 'localStorage',
23
+ 'sessionStorage',
24
+ 'navigator',
25
+ 'location',
26
+ 'history',
27
+ 'alert',
28
+ 'confirm',
29
+ 'prompt',
30
+ 'fetch', // Can be used in Server Components but behaves differently
31
+ ];
32
+ // Event handler props (indicate client interactivity)
33
+ const EVENT_HANDLER_PATTERN = /^on[A-Z]/;
34
+ /**
35
+ * Detect React 19 Server/Client component boundary issues
36
+ */
37
+ export function detectServerComponentIssues(component, filePath, sourceCode, config, imports = []) {
38
+ if (!config.checkServerComponents)
39
+ return [];
40
+ const smells = [];
41
+ const lines = sourceCode.split('\n');
42
+ // Check if file has 'use client' or 'use server' directive
43
+ const hasUseClient = lines.some(line => /^['"]use client['"];?\s*$/.test(line.trim()));
44
+ const hasUseServer = lines.some(line => /^['"]use server['"];?\s*$/.test(line.trim()));
45
+ // Detect if this is a Server Component (no 'use client' directive in app/ directory)
46
+ const isInAppDir = filePath.includes('/app/') || filePath.includes('\\app\\');
47
+ const isServerComponent = isInAppDir && !hasUseClient;
48
+ if (isServerComponent) {
49
+ // Check for client-only hooks in Server Components
50
+ const clientHooksUsed = [];
51
+ for (const hookName of CLIENT_ONLY_HOOKS) {
52
+ const hooks = component.hooks[hookName];
53
+ if (hooks && hooks.length > 0) {
54
+ clientHooksUsed.push(hookName);
55
+ }
56
+ }
57
+ // Also traverse for any hook calls
58
+ component.path.traverse({
59
+ CallExpression(path) {
60
+ const callee = path.node.callee;
61
+ if (t.isIdentifier(callee) && CLIENT_ONLY_HOOKS.includes(callee.name)) {
62
+ if (!clientHooksUsed.includes(callee.name)) {
63
+ clientHooksUsed.push(callee.name);
64
+ }
65
+ }
66
+ },
67
+ });
68
+ if (clientHooksUsed.length > 0) {
69
+ smells.push({
70
+ type: 'server-component-hooks',
71
+ severity: 'error',
72
+ message: `Server Component "${component.name}" uses client-only hooks: ${clientHooksUsed.join(', ')}`,
73
+ file: filePath,
74
+ line: component.startLine,
75
+ column: 0,
76
+ suggestion: `Add 'use client' directive at the top of the file, or move stateful logic to a Client Component.`,
77
+ });
78
+ }
79
+ // Check for event handlers in JSX (onClick, onChange, etc.)
80
+ const eventHandlers = [];
81
+ component.path.traverse({
82
+ JSXAttribute(path) {
83
+ if (t.isJSXIdentifier(path.node.name)) {
84
+ const name = path.node.name.name;
85
+ if (EVENT_HANDLER_PATTERN.test(name)) {
86
+ if (!eventHandlers.includes(name)) {
87
+ eventHandlers.push(name);
88
+ }
89
+ }
90
+ }
91
+ },
92
+ });
93
+ if (eventHandlers.length > 0) {
94
+ smells.push({
95
+ type: 'server-component-events',
96
+ severity: 'error',
97
+ message: `Server Component "${component.name}" uses event handlers: ${eventHandlers.join(', ')}`,
98
+ file: filePath,
99
+ line: component.startLine,
100
+ column: 0,
101
+ suggestion: `Add 'use client' directive or extract interactive elements to a Client Component.`,
102
+ });
103
+ }
104
+ // Check for browser APIs usage
105
+ const browserApisUsed = [];
106
+ component.path.traverse({
107
+ Identifier(path) {
108
+ const name = path.node.name;
109
+ if (BROWSER_APIS.includes(name)) {
110
+ // Make sure it's not a property access like obj.window
111
+ if (!t.isMemberExpression(path.parent) || !t.isIdentifier(path.parent.property) || path.parent.object === path.node) {
112
+ if (!browserApisUsed.includes(name)) {
113
+ browserApisUsed.push(name);
114
+ }
115
+ }
116
+ }
117
+ },
118
+ });
119
+ if (browserApisUsed.length > 0) {
120
+ smells.push({
121
+ type: 'server-component-browser-api',
122
+ severity: 'warning',
123
+ message: `Server Component "${component.name}" may use browser APIs: ${browserApisUsed.join(', ')}`,
124
+ file: filePath,
125
+ line: component.startLine,
126
+ column: 0,
127
+ suggestion: `Browser APIs are not available in Server Components. Move to a Client Component or use conditional checks.`,
128
+ });
129
+ }
130
+ }
131
+ // Check for 'use server' in client components (async actions)
132
+ if (hasUseClient && hasUseServer) {
133
+ smells.push({
134
+ type: 'mixed-directives',
135
+ severity: 'error',
136
+ message: `File has both 'use client' and 'use server' directives`,
137
+ file: filePath,
138
+ line: 1,
139
+ column: 0,
140
+ suggestion: `A file can only be either a Client Component or contain Server Actions, not both. Separate them into different files.`,
141
+ });
142
+ }
143
+ // Check for async component without 'use server' actions (they should be in Server Components)
144
+ if (!isServerComponent && !hasUseServer) {
145
+ let hasAsyncServerAction = false;
146
+ component.path.traverse({
147
+ FunctionDeclaration(path) {
148
+ if (path.node.async) {
149
+ // Check if body starts with 'use server'
150
+ const body = path.node.body;
151
+ if (body.directives?.some(d => d.value.value === 'use server')) {
152
+ hasAsyncServerAction = true;
153
+ }
154
+ }
155
+ },
156
+ ArrowFunctionExpression(path) {
157
+ if (path.node.async && t.isBlockStatement(path.node.body)) {
158
+ const directives = path.node.body.directives;
159
+ if (directives?.some(d => d.value.value === 'use server')) {
160
+ hasAsyncServerAction = true;
161
+ }
162
+ }
163
+ },
164
+ });
165
+ // This is fine - Server Actions can be defined inline with 'use server' inside
166
+ }
167
+ // Check for passing server actions incorrectly to client components
168
+ // (This is complex to detect statically, so we provide guidance)
169
+ // Check for importing Server Component into Client Component (potential issue)
170
+ if (hasUseClient) {
171
+ // Look for imports that might be Server Components
172
+ const serverComponentImports = imports.filter(imp => {
173
+ // Heuristic: imports from app/ directory without 'use client'
174
+ return imp.includes('/app/') && !imp.includes('.client');
175
+ });
176
+ // Can't definitively detect, but can warn about patterns
177
+ }
178
+ return smells;
179
+ }
180
+ /**
181
+ * Detect proper async component patterns in React 19
182
+ */
183
+ export function detectAsyncComponentIssues(component, filePath, sourceCode, config) {
184
+ if (!config.checkServerComponents)
185
+ return [];
186
+ const smells = [];
187
+ const lines = sourceCode.split('\n');
188
+ const hasUseClient = lines.some(line => /^['"]use client['"];?\s*$/.test(line.trim()));
189
+ // Check if component is async
190
+ let isAsync = false;
191
+ const node = component.path.node;
192
+ if (t.isFunctionDeclaration(node) || t.isFunctionExpression(node) || t.isArrowFunctionExpression(node)) {
193
+ isAsync = node.async || false;
194
+ }
195
+ // Async components in Client Components are not allowed
196
+ if (isAsync && hasUseClient) {
197
+ smells.push({
198
+ type: 'async-client-component',
199
+ severity: 'error',
200
+ message: `Client Component "${component.name}" is async, which is not supported`,
201
+ file: filePath,
202
+ line: component.startLine,
203
+ column: 0,
204
+ suggestion: `Remove async from the component. Use useEffect or React Query for data fetching in Client Components.`,
205
+ });
206
+ }
207
+ // Check for proper Suspense usage with async Server Components
208
+ if (isAsync && !hasUseClient) {
209
+ // Good pattern - async Server Component
210
+ // Could check if parent has Suspense boundary, but that requires cross-file analysis
211
+ }
212
+ return smells;
213
+ }
214
+ /**
215
+ * Get code snippet for context
216
+ */
217
+ function getCodeSnippet(sourceCode, line) {
218
+ const lines = sourceCode.split('\n');
219
+ const startLine = Math.max(0, line - 2);
220
+ const endLine = Math.min(lines.length, line + 1);
221
+ return lines.slice(startLine, endLine).join('\n');
222
+ }
@@ -0,0 +1,11 @@
1
+ import { ParsedComponent } from '../parser/index.js';
2
+ import { CodeSmell, DetectorConfig } from '../types/index.js';
3
+ /**
4
+ * Detect state management anti-patterns (Redux, Zustand, MobX, etc.)
5
+ */
6
+ export declare function detectStateManagementIssues(component: ParsedComponent, filePath: string, sourceCode: string, config: DetectorConfig): CodeSmell[];
7
+ /**
8
+ * Detect file-level state management patterns
9
+ */
10
+ export declare function detectStateManagementPatterns(sourceCode: string, filePath: string, config: DetectorConfig): CodeSmell[];
11
+ //# sourceMappingURL=stateManagement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stateManagement.d.ts","sourceRoot":"","sources":["../../src/detectors/stateManagement.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAE9D;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,SAAS,EAAE,eAAe,EAC1B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,cAAc,GACrB,SAAS,EAAE,CA6Hb;AAiDD;;GAEG;AACH,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,cAAc,GACrB,SAAS,EAAE,CAyCb"}