react-code-smell-detector 1.2.0 → 1.3.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.
- package/LICENSE +21 -0
- package/README.md +46 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +18 -1
- package/dist/cli.js +93 -26
- package/dist/detectors/complexity.d.ts +17 -0
- package/dist/detectors/complexity.d.ts.map +1 -0
- package/dist/detectors/complexity.js +69 -0
- package/dist/detectors/imports.d.ts +22 -0
- package/dist/detectors/imports.d.ts.map +1 -0
- package/dist/detectors/imports.js +210 -0
- package/dist/detectors/index.d.ts +3 -0
- package/dist/detectors/index.d.ts.map +1 -1
- package/dist/detectors/index.js +4 -0
- package/dist/detectors/memoryLeak.d.ts +7 -0
- package/dist/detectors/memoryLeak.d.ts.map +1 -0
- package/dist/detectors/memoryLeak.js +111 -0
- package/dist/fixer.d.ts +23 -0
- package/dist/fixer.d.ts.map +1 -0
- package/dist/fixer.js +133 -0
- package/dist/git.d.ts +28 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +117 -0
- package/dist/reporter.js +13 -0
- package/dist/types/index.d.ts +7 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +9 -0
- package/dist/watcher.d.ts +16 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +89 -0
- package/package.json +8 -2
- package/src/analyzer.ts +0 -324
- package/src/cli.ts +0 -159
- package/src/detectors/accessibility.ts +0 -212
- package/src/detectors/deadCode.ts +0 -163
- package/src/detectors/debug.ts +0 -103
- package/src/detectors/dependencyArray.ts +0 -176
- package/src/detectors/hooksRules.ts +0 -101
- package/src/detectors/index.ts +0 -20
- package/src/detectors/javascript.ts +0 -169
- package/src/detectors/largeComponent.ts +0 -63
- package/src/detectors/magicValues.ts +0 -114
- package/src/detectors/memoization.ts +0 -177
- package/src/detectors/missingKey.ts +0 -105
- package/src/detectors/nestedTernary.ts +0 -75
- package/src/detectors/nextjs.ts +0 -124
- package/src/detectors/nodejs.ts +0 -199
- package/src/detectors/propDrilling.ts +0 -103
- package/src/detectors/reactNative.ts +0 -154
- package/src/detectors/security.ts +0 -179
- package/src/detectors/typescript.ts +0 -151
- package/src/detectors/useEffect.ts +0 -117
- package/src/htmlReporter.ts +0 -464
- package/src/index.ts +0 -4
- package/src/parser/index.ts +0 -195
- package/src/reporter.ts +0 -291
- package/src/types/index.ts +0 -165
- package/tsconfig.json +0 -19
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 vsthakur101
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -12,6 +12,12 @@ A CLI tool that analyzes React projects and detects common code smells, providin
|
|
|
12
12
|
- ♿ **Accessibility Checks**: Missing alt text, labels, ARIA attributes
|
|
13
13
|
- 🐛 **Debug Cleanup**: console.log, debugger, TODO/FIXME detection
|
|
14
14
|
- 🤖 **CI/CD Ready**: Exit codes and flags for pipeline integration
|
|
15
|
+
- 🔧 **Auto-Fix**: Automatically fix simple issues (console.log, var, ==, missing alt)
|
|
16
|
+
- 👀 **Watch Mode**: Re-analyze on file changes
|
|
17
|
+
- 📂 **Git Integration**: Analyze only modified files
|
|
18
|
+
- 🧮 **Complexity Metrics**: Cyclomatic and cognitive complexity scoring
|
|
19
|
+
- 💧 **Memory Leak Detection**: Find missing cleanup in useEffect
|
|
20
|
+
- 🔄 **Import Analysis**: Detect circular dependencies and barrel file issues
|
|
15
21
|
|
|
16
22
|
### Detected Code Smells
|
|
17
23
|
|
|
@@ -25,6 +31,9 @@ A CLI tool that analyzes React projects and detects common code smells, providin
|
|
|
25
31
|
| **Security Issues** | dangerouslySetInnerHTML, eval(), innerHTML, exposed API keys |
|
|
26
32
|
| **Accessibility** | Missing alt text, form labels, keyboard handlers, semantic HTML |
|
|
27
33
|
| **Debug Statements** | console.log, debugger statements, TODO/FIXME comments |
|
|
34
|
+
| **Memory Leaks** | Missing cleanup for event listeners, timers, subscriptions |
|
|
35
|
+
| **Code Complexity** | Cyclomatic complexity, cognitive complexity, deep nesting |
|
|
36
|
+
| **Import Issues** | Circular dependencies, barrel file imports, excessive imports |
|
|
28
37
|
| **Framework-Specific** | Next.js, React Native, Node.js, TypeScript issues |
|
|
29
38
|
|
|
30
39
|
## Installation
|
|
@@ -97,6 +106,9 @@ Or create manually:
|
|
|
97
106
|
| `-c, --config <file>` | Path to config file | `.smellrc.json` |
|
|
98
107
|
| `--ci` | CI mode: exit with code 1 if any issues | `false` |
|
|
99
108
|
| `--fail-on <severity>` | Exit code 1 threshold: error, warning, info | `error` |
|
|
109
|
+
| `--fix` | Auto-fix simple issues (console.log, var, ==, alt) | `false` |
|
|
110
|
+
| `--watch` | Watch mode: re-analyze on file changes | `false` |
|
|
111
|
+
| `--changed` | Only analyze git-modified files | `false` |
|
|
100
112
|
| `--max-effects <number>` | Max useEffects per component | `3` |
|
|
101
113
|
| `--max-props <number>` | Max props before warning | `7` |
|
|
102
114
|
| `--max-lines <number>` | Max lines per component | `300` |
|
|
@@ -104,6 +116,40 @@ Or create manually:
|
|
|
104
116
|
| `--exclude <patterns>` | Glob patterns to exclude | `node_modules,dist` |
|
|
105
117
|
| `-o, --output <file>` | Write output to file | - |
|
|
106
118
|
|
|
119
|
+
### Auto-Fix
|
|
120
|
+
|
|
121
|
+
Automatically fix simple issues:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Fix and show remaining issues
|
|
125
|
+
react-smell ./src --fix
|
|
126
|
+
|
|
127
|
+
# Fix only (no report)
|
|
128
|
+
react-smell ./src --fix -f json > /dev/null
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Fixable issues:
|
|
132
|
+
- `console.log`, `console.debug`, etc. → Removed
|
|
133
|
+
- `var x = 1` → `let x = 1`
|
|
134
|
+
- `a == b` → `a === b`
|
|
135
|
+
- `<img src="...">` → `<img src="..." alt="">`
|
|
136
|
+
|
|
137
|
+
### Watch Mode
|
|
138
|
+
|
|
139
|
+
Re-analyze on every file change:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
react-smell ./src --watch
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Git Integration
|
|
146
|
+
|
|
147
|
+
Only analyze modified files:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
react-smell ./src --changed
|
|
151
|
+
```
|
|
152
|
+
|
|
107
153
|
## Example Output
|
|
108
154
|
|
|
109
155
|
```
|
package/dist/analyzer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AA4BA,OAAO,EACL,cAAc,EAMd,cAAc,EAIf,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;CAClC;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CA0CtF;AA8PD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/analyzer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fg from 'fast-glob';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { parseFile } from './parser/index.js';
|
|
4
|
-
import { detectUseEffectOveruse, detectPropDrilling, analyzePropDrillingDepth, detectLargeComponent, detectUnmemoizedCalculations, detectMissingKeys, detectHooksRulesViolations, detectDependencyArrayIssues, detectNestedTernaries, detectDeadCode, detectMagicValues, detectNextjsIssues, detectReactNativeIssues, detectNodejsIssues, detectJavascriptIssues, detectTypescriptIssues, detectDebugStatements, detectSecurityIssues, detectAccessibilityIssues, } from './detectors/index.js';
|
|
4
|
+
import { detectUseEffectOveruse, detectPropDrilling, analyzePropDrillingDepth, detectLargeComponent, detectUnmemoizedCalculations, detectMissingKeys, detectHooksRulesViolations, detectDependencyArrayIssues, detectNestedTernaries, detectDeadCode, detectMagicValues, detectNextjsIssues, detectReactNativeIssues, detectNodejsIssues, detectJavascriptIssues, detectTypescriptIssues, detectDebugStatements, detectSecurityIssues, detectAccessibilityIssues, detectComplexity, detectMemoryLeaks, detectImportIssues, } from './detectors/index.js';
|
|
5
5
|
import { DEFAULT_CONFIG, } from './types/index.js';
|
|
6
6
|
export async function analyzeProject(options) {
|
|
7
7
|
const { rootDir, include = ['**/*.tsx', '**/*.jsx'], exclude = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/*.test.*', '**/*.spec.*'], config: userConfig = {}, } = options;
|
|
@@ -78,6 +78,10 @@ function analyzeFile(parseResult, filePath, config) {
|
|
|
78
78
|
smells.push(...detectDebugStatements(component, filePath, sourceCode, config));
|
|
79
79
|
smells.push(...detectSecurityIssues(component, filePath, sourceCode, config));
|
|
80
80
|
smells.push(...detectAccessibilityIssues(component, filePath, sourceCode, config));
|
|
81
|
+
// Complexity and Memory Leaks
|
|
82
|
+
smells.push(...detectComplexity(component, filePath, sourceCode, config));
|
|
83
|
+
smells.push(...detectMemoryLeaks(component, filePath, sourceCode, config));
|
|
84
|
+
smells.push(...detectImportIssues(component, filePath, sourceCode, config));
|
|
81
85
|
});
|
|
82
86
|
// Run cross-component analysis
|
|
83
87
|
smells.push(...analyzePropDrillingDepth(components, filePath, sourceCode, config));
|
|
@@ -170,6 +174,19 @@ function calculateSummary(files) {
|
|
|
170
174
|
'a11y-interactive-role': 0,
|
|
171
175
|
'a11y-keyboard': 0,
|
|
172
176
|
'a11y-semantic': 0,
|
|
177
|
+
// Complexity
|
|
178
|
+
'high-cyclomatic-complexity': 0,
|
|
179
|
+
'high-cognitive-complexity': 0,
|
|
180
|
+
// Memory leaks
|
|
181
|
+
'memory-leak-event-listener': 0,
|
|
182
|
+
'memory-leak-subscription': 0,
|
|
183
|
+
'memory-leak-timer': 0,
|
|
184
|
+
'memory-leak-async': 0,
|
|
185
|
+
// Import issues
|
|
186
|
+
'circular-dependency': 0,
|
|
187
|
+
'barrel-file-import': 0,
|
|
188
|
+
'namespace-import': 0,
|
|
189
|
+
'excessive-imports': 0,
|
|
173
190
|
};
|
|
174
191
|
const smellsBySeverity = {
|
|
175
192
|
error: 0,
|
package/dist/cli.js
CHANGED
|
@@ -6,18 +6,24 @@ import path from 'path';
|
|
|
6
6
|
import { analyzeProject, DEFAULT_CONFIG } from './analyzer.js';
|
|
7
7
|
import { reportResults } from './reporter.js';
|
|
8
8
|
import { generateHTMLReport } from './htmlReporter.js';
|
|
9
|
+
import { fixFile, isFixable } from './fixer.js';
|
|
10
|
+
import { startWatch } from './watcher.js';
|
|
11
|
+
import { getAllModifiedFiles, filterReactFiles, getGitInfo } from './git.js';
|
|
9
12
|
import fs from 'fs/promises';
|
|
10
13
|
const program = new Command();
|
|
11
14
|
program
|
|
12
15
|
.name('react-smell')
|
|
13
16
|
.description('Detect code smells in React projects')
|
|
14
|
-
.version('1.
|
|
17
|
+
.version('1.3.0')
|
|
15
18
|
.argument('[directory]', 'Directory to analyze', '.')
|
|
16
19
|
.option('-f, --format <format>', 'Output format: console, json, markdown, html', 'console')
|
|
17
20
|
.option('-s, --snippets', 'Show code snippets in output', false)
|
|
18
21
|
.option('-c, --config <file>', 'Path to config file')
|
|
19
22
|
.option('--ci', 'CI mode: exit with code 1 if any issues found')
|
|
20
23
|
.option('--fail-on <severity>', 'Exit with code 1 if issues of this severity or higher (error, warning, info)', 'error')
|
|
24
|
+
.option('--fix', 'Auto-fix simple issues (console.log, var, ==, missing alt)')
|
|
25
|
+
.option('--watch', 'Watch mode: re-analyze on file changes')
|
|
26
|
+
.option('--changed', 'Only analyze git-modified files')
|
|
21
27
|
.option('--max-effects <number>', 'Max useEffects per component', parseInt)
|
|
22
28
|
.option('--max-props <number>', 'Max props before warning', parseInt)
|
|
23
29
|
.option('--max-lines <number>', 'Max lines per component', parseInt)
|
|
@@ -34,38 +40,99 @@ program
|
|
|
34
40
|
console.error(chalk.red(`Error: Directory "${rootDir}" does not exist.`));
|
|
35
41
|
process.exit(1);
|
|
36
42
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const configContent = await fs.readFile(configPath, 'utf-8');
|
|
45
|
-
fileConfig = JSON.parse(configContent);
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
spinner.fail(`Could not load config file: ${error.message}`);
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
43
|
+
// Load config file if specified
|
|
44
|
+
let fileConfig = {};
|
|
45
|
+
if (options.config) {
|
|
46
|
+
try {
|
|
47
|
+
const configPath = path.resolve(process.cwd(), options.config);
|
|
48
|
+
const configContent = await fs.readFile(configPath, 'utf-8');
|
|
49
|
+
fileConfig = JSON.parse(configContent);
|
|
51
50
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.error(chalk.red(`Could not load config file: ${error.message}`));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Build config from options
|
|
57
|
+
const config = {
|
|
58
|
+
...DEFAULT_CONFIG,
|
|
59
|
+
...fileConfig,
|
|
60
|
+
...(options.maxEffects && { maxUseEffectsPerComponent: options.maxEffects }),
|
|
61
|
+
...(options.maxProps && { maxPropsCount: options.maxProps }),
|
|
62
|
+
...(options.maxLines && { maxComponentLines: options.maxLines }),
|
|
63
|
+
};
|
|
64
|
+
const include = options.include?.split(',').map((p) => p.trim()) || ['**/*.tsx', '**/*.jsx'];
|
|
65
|
+
const exclude = options.exclude?.split(',').map((p) => p.trim()) || ['**/node_modules/**', '**/dist/**'];
|
|
66
|
+
// Watch mode
|
|
67
|
+
if (options.watch) {
|
|
68
|
+
const watcher = startWatch({
|
|
63
69
|
rootDir,
|
|
64
70
|
include,
|
|
65
71
|
exclude,
|
|
66
72
|
config,
|
|
73
|
+
showSnippets: options.snippets,
|
|
74
|
+
});
|
|
75
|
+
// Handle Ctrl+C gracefully
|
|
76
|
+
process.on('SIGINT', () => {
|
|
77
|
+
watcher.close();
|
|
78
|
+
process.exit(0);
|
|
79
|
+
});
|
|
80
|
+
return; // Don't continue to regular analysis
|
|
81
|
+
}
|
|
82
|
+
// Git changed files mode
|
|
83
|
+
let filesToAnalyze;
|
|
84
|
+
if (options.changed) {
|
|
85
|
+
const gitInfo = getGitInfo(rootDir);
|
|
86
|
+
if (!gitInfo.isGitRepo) {
|
|
87
|
+
console.error(chalk.red('Error: --changed requires a git repository'));
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
const modifiedFiles = getAllModifiedFiles(rootDir);
|
|
91
|
+
filesToAnalyze = filterReactFiles(modifiedFiles);
|
|
92
|
+
if (filesToAnalyze.length === 0) {
|
|
93
|
+
console.log(chalk.green('✓ No modified React files to analyze'));
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
console.log(chalk.cyan(`\n📝 Analyzing ${filesToAnalyze.length} modified file(s)...\n`));
|
|
97
|
+
}
|
|
98
|
+
const spinner = ora('Analyzing React project...').start();
|
|
99
|
+
try {
|
|
100
|
+
const result = await analyzeProject({
|
|
101
|
+
rootDir,
|
|
102
|
+
include: filesToAnalyze ? undefined : include,
|
|
103
|
+
exclude: filesToAnalyze ? undefined : exclude,
|
|
104
|
+
config,
|
|
67
105
|
});
|
|
68
106
|
spinner.stop();
|
|
107
|
+
// Fix mode - apply auto-fixes
|
|
108
|
+
if (options.fix) {
|
|
109
|
+
const fixableSmells = result.files.flatMap(f => f.smells.filter(isFixable).map(s => ({ ...s, file: f.file })));
|
|
110
|
+
if (fixableSmells.length > 0) {
|
|
111
|
+
console.log(chalk.cyan(`\n🔧 Auto-fixing ${fixableSmells.length} issue(s)...\n`));
|
|
112
|
+
// Group by file
|
|
113
|
+
const smellsByFile = new Map();
|
|
114
|
+
fixableSmells.forEach(smell => {
|
|
115
|
+
const existing = smellsByFile.get(smell.file) || [];
|
|
116
|
+
existing.push(smell);
|
|
117
|
+
smellsByFile.set(smell.file, existing);
|
|
118
|
+
});
|
|
119
|
+
let totalFixed = 0;
|
|
120
|
+
for (const [file, smells] of smellsByFile) {
|
|
121
|
+
const fixResult = await fixFile(file, smells);
|
|
122
|
+
if (fixResult.fixedSmells.length > 0) {
|
|
123
|
+
console.log(chalk.green(` ✓ Fixed ${fixResult.fixedSmells.length} issue(s) in ${path.relative(rootDir, file)}`));
|
|
124
|
+
totalFixed += fixResult.fixedSmells.length;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
console.log(chalk.green(`\n✓ Fixed ${totalFixed} issue(s) total\n`));
|
|
128
|
+
// Re-analyze after fixes
|
|
129
|
+
const newResult = await analyzeProject({ rootDir, include, exclude, config });
|
|
130
|
+
console.log(chalk.dim(`Remaining issues: ${newResult.summary.totalSmells}\n`));
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
console.log(chalk.yellow('\nNo auto-fixable issues found\n'));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
69
136
|
let output;
|
|
70
137
|
if (options.format === 'html') {
|
|
71
138
|
output = generateHTMLReport(result, rootDir);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ParsedComponent } from '../parser/index.js';
|
|
2
|
+
import { CodeSmell, DetectorConfig } from '../types/index.js';
|
|
3
|
+
export interface ComplexityMetrics {
|
|
4
|
+
cyclomaticComplexity: number;
|
|
5
|
+
cognitiveComplexity: number;
|
|
6
|
+
maxNestingDepth: number;
|
|
7
|
+
linesOfCode: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Detect code complexity issues in a component
|
|
11
|
+
*/
|
|
12
|
+
export declare function detectComplexity(component: ParsedComponent, filePath: string, sourceCode: string, config: DetectorConfig): CodeSmell[];
|
|
13
|
+
/**
|
|
14
|
+
* Calculate complexity metrics for a component
|
|
15
|
+
*/
|
|
16
|
+
export declare function calculateComplexityMetrics(component: ParsedComponent): ComplexityMetrics;
|
|
17
|
+
//# sourceMappingURL=complexity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"complexity.d.ts","sourceRoot":"","sources":["../../src/detectors/complexity.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAE9D,MAAM,WAAW,iBAAiB;IAChC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,eAAe,EAC1B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,cAAc,GACrB,SAAS,EAAE,CAqCb;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,eAAe,GAAG,iBAAiB,CA6BxF"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect code complexity issues in a component
|
|
3
|
+
*/
|
|
4
|
+
export function detectComplexity(component, filePath, sourceCode, config) {
|
|
5
|
+
if (!config.checkComplexity)
|
|
6
|
+
return [];
|
|
7
|
+
const smells = [];
|
|
8
|
+
const metrics = calculateComplexityMetrics(component);
|
|
9
|
+
const thresholds = {
|
|
10
|
+
cyclomatic: config.maxCyclomaticComplexity || 10,
|
|
11
|
+
cognitive: config.maxCognitiveComplexity || 15,
|
|
12
|
+
nesting: config.maxNestingDepth || 4,
|
|
13
|
+
};
|
|
14
|
+
if (metrics.cyclomaticComplexity > thresholds.cyclomatic) {
|
|
15
|
+
smells.push({
|
|
16
|
+
type: 'high-cyclomatic-complexity',
|
|
17
|
+
severity: metrics.cyclomaticComplexity > thresholds.cyclomatic * 1.5 ? 'error' : 'warning',
|
|
18
|
+
message: `Component "${component.name}" has cyclomatic complexity of ${metrics.cyclomaticComplexity} (threshold: ${thresholds.cyclomatic})`,
|
|
19
|
+
file: filePath,
|
|
20
|
+
line: component.startLine,
|
|
21
|
+
column: 0,
|
|
22
|
+
suggestion: 'Break down complex logic into smaller functions.',
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (metrics.cognitiveComplexity > thresholds.cognitive) {
|
|
26
|
+
smells.push({
|
|
27
|
+
type: 'high-cognitive-complexity',
|
|
28
|
+
severity: metrics.cognitiveComplexity > thresholds.cognitive * 1.5 ? 'error' : 'warning',
|
|
29
|
+
message: `Component "${component.name}" has cognitive complexity of ${metrics.cognitiveComplexity} (threshold: ${thresholds.cognitive})`,
|
|
30
|
+
file: filePath,
|
|
31
|
+
line: component.startLine,
|
|
32
|
+
column: 0,
|
|
33
|
+
suggestion: 'Simplify nested conditions and flatten control flow.',
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return smells;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Calculate complexity metrics for a component
|
|
40
|
+
*/
|
|
41
|
+
export function calculateComplexityMetrics(component) {
|
|
42
|
+
let cyclomaticComplexity = 1;
|
|
43
|
+
let cognitiveComplexity = 0;
|
|
44
|
+
component.path.traverse({
|
|
45
|
+
IfStatement() { cyclomaticComplexity++; cognitiveComplexity++; },
|
|
46
|
+
ConditionalExpression() { cyclomaticComplexity++; cognitiveComplexity++; },
|
|
47
|
+
LogicalExpression(path) {
|
|
48
|
+
if (path.node.operator === '&&' || path.node.operator === '||') {
|
|
49
|
+
cyclomaticComplexity++;
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
SwitchCase(path) {
|
|
53
|
+
if (path.node.test !== null)
|
|
54
|
+
cyclomaticComplexity++;
|
|
55
|
+
},
|
|
56
|
+
ForStatement() { cyclomaticComplexity++; cognitiveComplexity += 2; },
|
|
57
|
+
ForInStatement() { cyclomaticComplexity++; cognitiveComplexity += 2; },
|
|
58
|
+
ForOfStatement() { cyclomaticComplexity++; cognitiveComplexity += 2; },
|
|
59
|
+
WhileStatement() { cyclomaticComplexity++; cognitiveComplexity += 2; },
|
|
60
|
+
DoWhileStatement() { cyclomaticComplexity++; cognitiveComplexity += 2; },
|
|
61
|
+
CatchClause() { cyclomaticComplexity++; cognitiveComplexity++; },
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
cyclomaticComplexity,
|
|
65
|
+
cognitiveComplexity,
|
|
66
|
+
maxNestingDepth: component.jsxDepth,
|
|
67
|
+
linesOfCode: component.endLine - component.startLine + 1,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CodeSmell, DetectorConfig } from '../types/index.js';
|
|
2
|
+
export interface ImportInfo {
|
|
3
|
+
source: string;
|
|
4
|
+
specifiers: string[];
|
|
5
|
+
line: number;
|
|
6
|
+
isDefault: boolean;
|
|
7
|
+
isNamespace: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface ImportAnalysisResult {
|
|
10
|
+
smells: CodeSmell[];
|
|
11
|
+
importGraph: Map<string, string[]>;
|
|
12
|
+
circularDeps: string[][];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Analyze imports across multiple files for issues
|
|
16
|
+
*/
|
|
17
|
+
export declare function analyzeImports(files: string[], rootDir: string, config: DetectorConfig): Promise<ImportAnalysisResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Standalone detection function matching other detector signatures
|
|
20
|
+
*/
|
|
21
|
+
export declare function detectImportIssues(component: any, filePath: string, sourceCode: string, config: DetectorConfig): CodeSmell[];
|
|
22
|
+
//# sourceMappingURL=imports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../src/detectors/imports.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAK9D,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACnC,YAAY,EAAE,MAAM,EAAE,EAAE,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,oBAAoB,CAAC,CAkD/B;AA8KD;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,GAAG,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,cAAc,GACrB,SAAS,EAAE,CAGb"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import * as t from '@babel/types';
|
|
2
|
+
import _traverse from '@babel/traverse';
|
|
3
|
+
import { parse } from '@babel/parser';
|
|
4
|
+
import fs from 'fs/promises';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
// Handle ESM/CJS interop
|
|
7
|
+
const traverse = typeof _traverse === 'function' ? _traverse : _traverse.default;
|
|
8
|
+
/**
|
|
9
|
+
* Analyze imports across multiple files for issues
|
|
10
|
+
*/
|
|
11
|
+
export async function analyzeImports(files, rootDir, config) {
|
|
12
|
+
const smells = [];
|
|
13
|
+
const importGraph = new Map();
|
|
14
|
+
// Build import graph
|
|
15
|
+
for (const file of files) {
|
|
16
|
+
try {
|
|
17
|
+
const content = await fs.readFile(file, 'utf-8');
|
|
18
|
+
const imports = extractImports(content, file);
|
|
19
|
+
const resolvedImports = [];
|
|
20
|
+
for (const imp of imports) {
|
|
21
|
+
// Skip external packages
|
|
22
|
+
if (!imp.source.startsWith('.') && !imp.source.startsWith('/')) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const resolvedPath = resolveImportPath(file, imp.source, rootDir);
|
|
26
|
+
if (resolvedPath) {
|
|
27
|
+
resolvedImports.push(resolvedPath);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
importGraph.set(file, resolvedImports);
|
|
31
|
+
// Detect import-related smells in this file
|
|
32
|
+
smells.push(...detectImportSmells(imports, file, content, config));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Skip files that can't be parsed
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Detect circular dependencies
|
|
39
|
+
const circularDeps = detectCircularDependencies(importGraph);
|
|
40
|
+
// Add circular dependency smells
|
|
41
|
+
for (const cycle of circularDeps) {
|
|
42
|
+
const cycleStr = cycle.map(f => path.basename(f)).join(' → ');
|
|
43
|
+
smells.push({
|
|
44
|
+
type: 'circular-dependency',
|
|
45
|
+
severity: 'warning',
|
|
46
|
+
message: `Circular dependency detected: ${cycleStr}`,
|
|
47
|
+
file: cycle[0],
|
|
48
|
+
line: 1,
|
|
49
|
+
column: 0,
|
|
50
|
+
suggestion: 'Refactor to break the cycle. Consider extracting shared logic to a separate module.',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return { smells, importGraph, circularDeps };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Extract all imports from a file
|
|
57
|
+
*/
|
|
58
|
+
function extractImports(sourceCode, filePath) {
|
|
59
|
+
const imports = [];
|
|
60
|
+
try {
|
|
61
|
+
const ast = parse(sourceCode, {
|
|
62
|
+
sourceType: 'module',
|
|
63
|
+
plugins: ['jsx', 'typescript'],
|
|
64
|
+
});
|
|
65
|
+
traverse(ast, {
|
|
66
|
+
ImportDeclaration(nodePath) {
|
|
67
|
+
const node = nodePath.node;
|
|
68
|
+
const specifiers = node.specifiers.map(spec => {
|
|
69
|
+
if (t.isImportDefaultSpecifier(spec))
|
|
70
|
+
return 'default';
|
|
71
|
+
if (t.isImportNamespaceSpecifier(spec))
|
|
72
|
+
return '*';
|
|
73
|
+
return spec.local.name;
|
|
74
|
+
});
|
|
75
|
+
imports.push({
|
|
76
|
+
source: node.source.value,
|
|
77
|
+
specifiers,
|
|
78
|
+
line: node.loc?.start.line || 0,
|
|
79
|
+
isDefault: node.specifiers.some(t.isImportDefaultSpecifier),
|
|
80
|
+
isNamespace: node.specifiers.some(t.isImportNamespaceSpecifier),
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Parse error, skip
|
|
87
|
+
}
|
|
88
|
+
return imports;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Resolve an import path relative to the importing file
|
|
92
|
+
*/
|
|
93
|
+
function resolveImportPath(fromFile, importSource, rootDir) {
|
|
94
|
+
if (!importSource.startsWith('.')) {
|
|
95
|
+
return null; // External package
|
|
96
|
+
}
|
|
97
|
+
const dir = path.dirname(fromFile);
|
|
98
|
+
let resolved = path.resolve(dir, importSource);
|
|
99
|
+
// Try common extensions
|
|
100
|
+
const extensions = ['.tsx', '.ts', '.jsx', '.js', '/index.tsx', '/index.ts', '/index.jsx', '/index.js'];
|
|
101
|
+
for (const ext of extensions) {
|
|
102
|
+
const tryPath = resolved + ext;
|
|
103
|
+
// We don't check existence here - just build the graph
|
|
104
|
+
if (!ext.includes('/')) {
|
|
105
|
+
return tryPath;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return resolved;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Detect circular dependencies in import graph using DFS
|
|
112
|
+
*/
|
|
113
|
+
function detectCircularDependencies(graph) {
|
|
114
|
+
const cycles = [];
|
|
115
|
+
const visited = new Set();
|
|
116
|
+
const recursionStack = new Set();
|
|
117
|
+
const path = [];
|
|
118
|
+
function dfs(node) {
|
|
119
|
+
visited.add(node);
|
|
120
|
+
recursionStack.add(node);
|
|
121
|
+
path.push(node);
|
|
122
|
+
const neighbors = graph.get(node) || [];
|
|
123
|
+
for (const neighbor of neighbors) {
|
|
124
|
+
if (!visited.has(neighbor)) {
|
|
125
|
+
dfs(neighbor);
|
|
126
|
+
}
|
|
127
|
+
else if (recursionStack.has(neighbor)) {
|
|
128
|
+
// Found a cycle
|
|
129
|
+
const cycleStart = path.indexOf(neighbor);
|
|
130
|
+
if (cycleStart !== -1) {
|
|
131
|
+
const cycle = path.slice(cycleStart).concat(neighbor);
|
|
132
|
+
// Avoid duplicate cycles
|
|
133
|
+
const cycleKey = [...cycle].sort().join('|');
|
|
134
|
+
if (!cycles.some(c => [...c].sort().join('|') === cycleKey)) {
|
|
135
|
+
cycles.push(cycle);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
path.pop();
|
|
141
|
+
recursionStack.delete(node);
|
|
142
|
+
}
|
|
143
|
+
for (const node of graph.keys()) {
|
|
144
|
+
if (!visited.has(node)) {
|
|
145
|
+
dfs(node);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return cycles;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Detect import-related code smells in a single file
|
|
152
|
+
*/
|
|
153
|
+
function detectImportSmells(imports, filePath, sourceCode, config) {
|
|
154
|
+
const smells = [];
|
|
155
|
+
// Check for barrel file imports (importing from index files)
|
|
156
|
+
for (const imp of imports) {
|
|
157
|
+
if (imp.source.endsWith('/index') || imp.source === '.') {
|
|
158
|
+
smells.push({
|
|
159
|
+
type: 'barrel-file-import',
|
|
160
|
+
severity: 'info',
|
|
161
|
+
message: `Barrel file import from "${imp.source}" may impact tree-shaking`,
|
|
162
|
+
file: filePath,
|
|
163
|
+
line: imp.line,
|
|
164
|
+
column: 0,
|
|
165
|
+
suggestion: 'Import directly from the source file instead of barrel/index files for better build optimization.',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Check for namespace imports (import * as)
|
|
170
|
+
for (const imp of imports) {
|
|
171
|
+
if (imp.isNamespace && !imp.source.startsWith('.')) {
|
|
172
|
+
smells.push({
|
|
173
|
+
type: 'namespace-import',
|
|
174
|
+
severity: 'info',
|
|
175
|
+
message: `Namespace import "* as" from "${imp.source}" may prevent tree-shaking`,
|
|
176
|
+
file: filePath,
|
|
177
|
+
line: imp.line,
|
|
178
|
+
column: 0,
|
|
179
|
+
suggestion: 'Import only the specific exports you need for better bundle size.',
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Check for too many imports from same source
|
|
184
|
+
const importCounts = new Map();
|
|
185
|
+
for (const imp of imports) {
|
|
186
|
+
const count = (importCounts.get(imp.source) || 0) + imp.specifiers.length;
|
|
187
|
+
importCounts.set(imp.source, count);
|
|
188
|
+
}
|
|
189
|
+
for (const [source, count] of importCounts) {
|
|
190
|
+
if (count > 10 && source.startsWith('.')) {
|
|
191
|
+
smells.push({
|
|
192
|
+
type: 'excessive-imports',
|
|
193
|
+
severity: 'warning',
|
|
194
|
+
message: `${count} imports from "${source}" suggests tight coupling`,
|
|
195
|
+
file: filePath,
|
|
196
|
+
line: imports.find(i => i.source === source)?.line || 1,
|
|
197
|
+
column: 0,
|
|
198
|
+
suggestion: 'Consider if this file has too many responsibilities or if modules should be reorganized.',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return smells;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Standalone detection function matching other detector signatures
|
|
206
|
+
*/
|
|
207
|
+
export function detectImportIssues(component, filePath, sourceCode, config) {
|
|
208
|
+
const imports = extractImports(sourceCode, filePath);
|
|
209
|
+
return detectImportSmells(imports, filePath, sourceCode, config);
|
|
210
|
+
}
|
|
@@ -16,4 +16,7 @@ export { detectTypescriptIssues } from './typescript.js';
|
|
|
16
16
|
export { detectDebugStatements } from './debug.js';
|
|
17
17
|
export { detectSecurityIssues } from './security.js';
|
|
18
18
|
export { detectAccessibilityIssues } from './accessibility.js';
|
|
19
|
+
export { detectComplexity, calculateComplexityMetrics } from './complexity.js';
|
|
20
|
+
export { detectMemoryLeaks } from './memoryLeak.js';
|
|
21
|
+
export { detectImportIssues, analyzeImports } from './imports.js';
|
|
19
22
|
//# 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,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpE,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"}
|
|
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,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpE,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,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/detectors/index.js
CHANGED
|
@@ -18,3 +18,7 @@ export { detectTypescriptIssues } from './typescript.js';
|
|
|
18
18
|
export { detectDebugStatements } from './debug.js';
|
|
19
19
|
export { detectSecurityIssues } from './security.js';
|
|
20
20
|
export { detectAccessibilityIssues } from './accessibility.js';
|
|
21
|
+
// Complexity, Memory Leaks, Imports
|
|
22
|
+
export { detectComplexity, calculateComplexityMetrics } from './complexity.js';
|
|
23
|
+
export { detectMemoryLeaks } from './memoryLeak.js';
|
|
24
|
+
export { detectImportIssues, analyzeImports } from './imports.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ParsedComponent } from '../parser/index.js';
|
|
2
|
+
import { CodeSmell, DetectorConfig } from '../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Detect potential memory leaks in React components
|
|
5
|
+
*/
|
|
6
|
+
export declare function detectMemoryLeaks(component: ParsedComponent, filePath: string, sourceCode: string, config: DetectorConfig): CodeSmell[];
|
|
7
|
+
//# sourceMappingURL=memoryLeak.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoryLeak.d.ts","sourceRoot":"","sources":["../../src/detectors/memoryLeak.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,iBAAiB,CAC/B,SAAS,EAAE,eAAe,EAC1B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,cAAc,GACrB,SAAS,EAAE,CAgEb"}
|