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
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import * as t from '@babel/types';
|
|
2
|
+
/**
|
|
3
|
+
* Detect potential memory leaks in React components
|
|
4
|
+
*/
|
|
5
|
+
export function detectMemoryLeaks(component, filePath, sourceCode, config) {
|
|
6
|
+
if (!config.checkMemoryLeaks)
|
|
7
|
+
return [];
|
|
8
|
+
const smells = [];
|
|
9
|
+
// Check for setInterval without cleanup
|
|
10
|
+
component.path.traverse({
|
|
11
|
+
CallExpression(path) {
|
|
12
|
+
const node = path.node;
|
|
13
|
+
const { callee } = node;
|
|
14
|
+
if (t.isIdentifier(callee) && callee.name === 'setInterval') {
|
|
15
|
+
// Check if result is stored
|
|
16
|
+
const parent = path.parentPath;
|
|
17
|
+
if (!parent || !t.isVariableDeclarator(parent.node)) {
|
|
18
|
+
const loc = node.loc;
|
|
19
|
+
smells.push({
|
|
20
|
+
type: 'memory-leak-timer',
|
|
21
|
+
severity: 'error',
|
|
22
|
+
message: `setInterval without storing ID for cleanup in "${component.name}"`,
|
|
23
|
+
file: filePath,
|
|
24
|
+
line: loc?.start.line || 0,
|
|
25
|
+
column: loc?.start.column || 0,
|
|
26
|
+
suggestion: 'Store the interval ID and call clearInterval in cleanup.',
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
// Check useEffect hooks for cleanup issues
|
|
33
|
+
for (const effect of component.hooks.useEffect) {
|
|
34
|
+
const hasCleanup = checkEffectHasCleanup(effect);
|
|
35
|
+
const hasSubscription = checkEffectHasSubscription(effect, component);
|
|
36
|
+
const hasEventListener = checkEffectHasEventListener(effect, component);
|
|
37
|
+
if (hasEventListener && !hasCleanup) {
|
|
38
|
+
const loc = effect.loc;
|
|
39
|
+
smells.push({
|
|
40
|
+
type: 'memory-leak-event-listener',
|
|
41
|
+
severity: 'error',
|
|
42
|
+
message: `useEffect adds event listener without cleanup in "${component.name}"`,
|
|
43
|
+
file: filePath,
|
|
44
|
+
line: loc?.start.line || 0,
|
|
45
|
+
column: 0,
|
|
46
|
+
suggestion: 'Return a cleanup function that calls removeEventListener.',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (hasSubscription && !hasCleanup) {
|
|
50
|
+
const loc = effect.loc;
|
|
51
|
+
smells.push({
|
|
52
|
+
type: 'memory-leak-subscription',
|
|
53
|
+
severity: 'error',
|
|
54
|
+
message: `useEffect creates subscription without cleanup in "${component.name}"`,
|
|
55
|
+
file: filePath,
|
|
56
|
+
line: loc?.start.line || 0,
|
|
57
|
+
column: 0,
|
|
58
|
+
suggestion: 'Return a cleanup function that unsubscribes.',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return smells;
|
|
63
|
+
}
|
|
64
|
+
function checkEffectHasCleanup(effect) {
|
|
65
|
+
const args = effect.arguments;
|
|
66
|
+
if (args.length === 0)
|
|
67
|
+
return false;
|
|
68
|
+
const callback = args[0];
|
|
69
|
+
if (!t.isArrowFunctionExpression(callback) && !t.isFunctionExpression(callback)) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
// Check if callback body has a return statement
|
|
73
|
+
if (t.isBlockStatement(callback.body)) {
|
|
74
|
+
for (const stmt of callback.body.body) {
|
|
75
|
+
if (t.isReturnStatement(stmt) && stmt.argument) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
function checkEffectHasEventListener(effect, component) {
|
|
83
|
+
let hasListener = false;
|
|
84
|
+
const args = effect.arguments;
|
|
85
|
+
if (args.length === 0)
|
|
86
|
+
return false;
|
|
87
|
+
const callback = args[0];
|
|
88
|
+
if (!t.isArrowFunctionExpression(callback) && !t.isFunctionExpression(callback)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (t.isBlockStatement(callback.body)) {
|
|
92
|
+
const sourceCode = JSON.stringify(callback.body);
|
|
93
|
+
hasListener = sourceCode.includes('addEventListener');
|
|
94
|
+
}
|
|
95
|
+
return hasListener;
|
|
96
|
+
}
|
|
97
|
+
function checkEffectHasSubscription(effect, component) {
|
|
98
|
+
let hasSubscription = false;
|
|
99
|
+
const args = effect.arguments;
|
|
100
|
+
if (args.length === 0)
|
|
101
|
+
return false;
|
|
102
|
+
const callback = args[0];
|
|
103
|
+
if (!t.isArrowFunctionExpression(callback) && !t.isFunctionExpression(callback)) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
if (t.isBlockStatement(callback.body)) {
|
|
107
|
+
const sourceCode = JSON.stringify(callback.body);
|
|
108
|
+
hasSubscription = sourceCode.includes('subscribe') || sourceCode.includes('addListener');
|
|
109
|
+
}
|
|
110
|
+
return hasSubscription;
|
|
111
|
+
}
|
package/dist/fixer.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { CodeSmell, SmellType } from './types/index.js';
|
|
2
|
+
export interface FixResult {
|
|
3
|
+
file: string;
|
|
4
|
+
fixedSmells: CodeSmell[];
|
|
5
|
+
skippedSmells: CodeSmell[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Check if a smell type can be auto-fixed
|
|
9
|
+
*/
|
|
10
|
+
export declare function isFixable(smell: CodeSmell): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Apply fixes to a file based on detected smells
|
|
13
|
+
*/
|
|
14
|
+
export declare function fixFile(filePath: string, smells: CodeSmell[]): Promise<FixResult>;
|
|
15
|
+
/**
|
|
16
|
+
* Get list of fixable smell types
|
|
17
|
+
*/
|
|
18
|
+
export declare function getFixableTypes(): SmellType[];
|
|
19
|
+
/**
|
|
20
|
+
* Describe what fix will be applied for a smell type
|
|
21
|
+
*/
|
|
22
|
+
export declare function describeFixAction(type: SmellType): string;
|
|
23
|
+
//# sourceMappingURL=fixer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixer.d.ts","sourceRoot":"","sources":["../src/fixer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,SAAS,EAAE,CAAC;IACzB,aAAa,EAAE,SAAS,EAAE,CAAC;CAC5B;AAUD;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAEnD;AAED;;GAEG;AACH,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAuCvF;AAyED;;GAEG;AACH,wBAAgB,eAAe,IAAI,SAAS,EAAE,CAE7C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAazD"}
|
package/dist/fixer.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
// Smell types that can be auto-fixed
|
|
3
|
+
const FIXABLE_TYPES = [
|
|
4
|
+
'debug-statement',
|
|
5
|
+
'js-var-usage',
|
|
6
|
+
'js-loose-equality',
|
|
7
|
+
'a11y-missing-alt',
|
|
8
|
+
];
|
|
9
|
+
/**
|
|
10
|
+
* Check if a smell type can be auto-fixed
|
|
11
|
+
*/
|
|
12
|
+
export function isFixable(smell) {
|
|
13
|
+
return FIXABLE_TYPES.includes(smell.type);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Apply fixes to a file based on detected smells
|
|
17
|
+
*/
|
|
18
|
+
export async function fixFile(filePath, smells) {
|
|
19
|
+
const fixableSmells = smells.filter(isFixable);
|
|
20
|
+
const skippedSmells = smells.filter(s => !isFixable(s));
|
|
21
|
+
if (fixableSmells.length === 0) {
|
|
22
|
+
return { file: filePath, fixedSmells: [], skippedSmells };
|
|
23
|
+
}
|
|
24
|
+
let content = await fs.readFile(filePath, 'utf-8');
|
|
25
|
+
const lines = content.split('\n');
|
|
26
|
+
const fixedSmells = [];
|
|
27
|
+
// Sort by line descending to fix from bottom up (preserves line numbers)
|
|
28
|
+
const sortedSmells = [...fixableSmells].sort((a, b) => b.line - a.line);
|
|
29
|
+
for (const smell of sortedSmells) {
|
|
30
|
+
const lineIndex = smell.line - 1;
|
|
31
|
+
if (lineIndex < 0 || lineIndex >= lines.length)
|
|
32
|
+
continue;
|
|
33
|
+
const originalLine = lines[lineIndex];
|
|
34
|
+
const fixedLine = applyFix(smell, originalLine);
|
|
35
|
+
if (fixedLine !== originalLine) {
|
|
36
|
+
if (fixedLine === null) {
|
|
37
|
+
// Remove the line entirely
|
|
38
|
+
lines.splice(lineIndex, 1);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
lines[lineIndex] = fixedLine;
|
|
42
|
+
}
|
|
43
|
+
fixedSmells.push(smell);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Write back if any fixes were applied
|
|
47
|
+
if (fixedSmells.length > 0) {
|
|
48
|
+
await fs.writeFile(filePath, lines.join('\n'), 'utf-8');
|
|
49
|
+
}
|
|
50
|
+
return { file: filePath, fixedSmells, skippedSmells };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Apply a specific fix to a line of code
|
|
54
|
+
* Returns the fixed line, or null to remove the line
|
|
55
|
+
*/
|
|
56
|
+
function applyFix(smell, line) {
|
|
57
|
+
switch (smell.type) {
|
|
58
|
+
case 'debug-statement':
|
|
59
|
+
return fixDebugStatement(line);
|
|
60
|
+
case 'js-var-usage':
|
|
61
|
+
return fixVarUsage(line);
|
|
62
|
+
case 'js-loose-equality':
|
|
63
|
+
return fixLooseEquality(line);
|
|
64
|
+
case 'a11y-missing-alt':
|
|
65
|
+
return fixMissingAlt(line);
|
|
66
|
+
default:
|
|
67
|
+
return line;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Remove console.log, console.debug, debugger statements
|
|
72
|
+
*/
|
|
73
|
+
function fixDebugStatement(line) {
|
|
74
|
+
const trimmed = line.trim();
|
|
75
|
+
// If line is just a console statement or debugger, remove it
|
|
76
|
+
if (/^console\.(log|debug|info|warn|error|trace|dir)\s*\(.*\);?\s*$/.test(trimmed)) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
if (/^debugger;?\s*$/.test(trimmed)) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
// If console is part of a larger expression, comment it out
|
|
83
|
+
if (/console\.(log|debug|info|warn|error|trace|dir)\s*\(/.test(line)) {
|
|
84
|
+
return line.replace(/console\.(log|debug|info|warn|error|trace|dir)\s*\([^)]*\);?/g, '/* $& */');
|
|
85
|
+
}
|
|
86
|
+
return line;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Replace var with let or const
|
|
90
|
+
*/
|
|
91
|
+
function fixVarUsage(line) {
|
|
92
|
+
// Simple heuristic: if reassigned later, use let; otherwise const
|
|
93
|
+
// For auto-fix, default to let (safer)
|
|
94
|
+
return line.replace(/\bvar\s+/, 'let ');
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Replace == with === and != with !==
|
|
98
|
+
*/
|
|
99
|
+
function fixLooseEquality(line) {
|
|
100
|
+
return line
|
|
101
|
+
.replace(/([^=!])={2}([^=])/g, '$1===$2')
|
|
102
|
+
.replace(/!={1}([^=])/g, '!==$1');
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Add alt="" to img tags missing alt attribute
|
|
106
|
+
*/
|
|
107
|
+
function fixMissingAlt(line) {
|
|
108
|
+
// Add alt="" to <img> tags without alt
|
|
109
|
+
return line.replace(/<img\s+(?![^>]*\balt\b)([^>]*?)(\/?>)/gi, '<img $1alt="" $2');
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get list of fixable smell types
|
|
113
|
+
*/
|
|
114
|
+
export function getFixableTypes() {
|
|
115
|
+
return [...FIXABLE_TYPES];
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Describe what fix will be applied for a smell type
|
|
119
|
+
*/
|
|
120
|
+
export function describeFixAction(type) {
|
|
121
|
+
switch (type) {
|
|
122
|
+
case 'debug-statement':
|
|
123
|
+
return 'Remove console.log/debugger statements';
|
|
124
|
+
case 'js-var-usage':
|
|
125
|
+
return 'Replace var with let';
|
|
126
|
+
case 'js-loose-equality':
|
|
127
|
+
return 'Replace == with === and != with !==';
|
|
128
|
+
case 'a11y-missing-alt':
|
|
129
|
+
return 'Add alt="" to images';
|
|
130
|
+
default:
|
|
131
|
+
return 'Not auto-fixable';
|
|
132
|
+
}
|
|
133
|
+
}
|
package/dist/git.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface GitInfo {
|
|
2
|
+
isGitRepo: boolean;
|
|
3
|
+
currentBranch?: string;
|
|
4
|
+
changedFiles: string[];
|
|
5
|
+
stagedFiles: string[];
|
|
6
|
+
untrackedFiles: string[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Get git information for a directory
|
|
10
|
+
*/
|
|
11
|
+
export declare function getGitInfo(rootDir: string): GitInfo;
|
|
12
|
+
/**
|
|
13
|
+
* Get files changed since a specific commit or branch
|
|
14
|
+
*/
|
|
15
|
+
export declare function getFilesSince(rootDir: string, ref: string): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Get files changed in the last N commits
|
|
18
|
+
*/
|
|
19
|
+
export declare function getFilesFromLastCommits(rootDir: string, count?: number): string[];
|
|
20
|
+
/**
|
|
21
|
+
* Get all modified files (changed + staged + untracked)
|
|
22
|
+
*/
|
|
23
|
+
export declare function getAllModifiedFiles(rootDir: string): string[];
|
|
24
|
+
/**
|
|
25
|
+
* Filter file list to only include React/JS/TS files
|
|
26
|
+
*/
|
|
27
|
+
export declare function filterReactFiles(files: string[]): string[];
|
|
28
|
+
//# sourceMappingURL=git.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,OAAO;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CA0DnD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAcpE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,MAAU,GAAG,MAAM,EAAE,CAcpF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAW7D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAG1D"}
|
package/dist/git.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Get git information for a directory
|
|
5
|
+
*/
|
|
6
|
+
export function getGitInfo(rootDir) {
|
|
7
|
+
try {
|
|
8
|
+
// Check if it's a git repo
|
|
9
|
+
execSync('git rev-parse --is-inside-work-tree', {
|
|
10
|
+
cwd: rootDir,
|
|
11
|
+
stdio: 'pipe',
|
|
12
|
+
});
|
|
13
|
+
const currentBranch = execSync('git branch --show-current', {
|
|
14
|
+
cwd: rootDir,
|
|
15
|
+
encoding: 'utf-8',
|
|
16
|
+
}).trim();
|
|
17
|
+
// Get changed files (modified but not staged)
|
|
18
|
+
const changedOutput = execSync('git diff --name-only', {
|
|
19
|
+
cwd: rootDir,
|
|
20
|
+
encoding: 'utf-8',
|
|
21
|
+
});
|
|
22
|
+
const changedFiles = changedOutput
|
|
23
|
+
.split('\n')
|
|
24
|
+
.filter(f => f.trim())
|
|
25
|
+
.map(f => path.resolve(rootDir, f));
|
|
26
|
+
// Get staged files
|
|
27
|
+
const stagedOutput = execSync('git diff --cached --name-only', {
|
|
28
|
+
cwd: rootDir,
|
|
29
|
+
encoding: 'utf-8',
|
|
30
|
+
});
|
|
31
|
+
const stagedFiles = stagedOutput
|
|
32
|
+
.split('\n')
|
|
33
|
+
.filter(f => f.trim())
|
|
34
|
+
.map(f => path.resolve(rootDir, f));
|
|
35
|
+
// Get untracked files
|
|
36
|
+
const untrackedOutput = execSync('git ls-files --others --exclude-standard', {
|
|
37
|
+
cwd: rootDir,
|
|
38
|
+
encoding: 'utf-8',
|
|
39
|
+
});
|
|
40
|
+
const untrackedFiles = untrackedOutput
|
|
41
|
+
.split('\n')
|
|
42
|
+
.filter(f => f.trim())
|
|
43
|
+
.map(f => path.resolve(rootDir, f));
|
|
44
|
+
return {
|
|
45
|
+
isGitRepo: true,
|
|
46
|
+
currentBranch,
|
|
47
|
+
changedFiles,
|
|
48
|
+
stagedFiles,
|
|
49
|
+
untrackedFiles,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return {
|
|
54
|
+
isGitRepo: false,
|
|
55
|
+
changedFiles: [],
|
|
56
|
+
stagedFiles: [],
|
|
57
|
+
untrackedFiles: [],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get files changed since a specific commit or branch
|
|
63
|
+
*/
|
|
64
|
+
export function getFilesSince(rootDir, ref) {
|
|
65
|
+
try {
|
|
66
|
+
const output = execSync(`git diff --name-only ${ref}`, {
|
|
67
|
+
cwd: rootDir,
|
|
68
|
+
encoding: 'utf-8',
|
|
69
|
+
});
|
|
70
|
+
return output
|
|
71
|
+
.split('\n')
|
|
72
|
+
.filter(f => f.trim())
|
|
73
|
+
.map(f => path.resolve(rootDir, f));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get files changed in the last N commits
|
|
81
|
+
*/
|
|
82
|
+
export function getFilesFromLastCommits(rootDir, count = 1) {
|
|
83
|
+
try {
|
|
84
|
+
const output = execSync(`git diff --name-only HEAD~${count}`, {
|
|
85
|
+
cwd: rootDir,
|
|
86
|
+
encoding: 'utf-8',
|
|
87
|
+
});
|
|
88
|
+
return output
|
|
89
|
+
.split('\n')
|
|
90
|
+
.filter(f => f.trim())
|
|
91
|
+
.map(f => path.resolve(rootDir, f));
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get all modified files (changed + staged + untracked)
|
|
99
|
+
*/
|
|
100
|
+
export function getAllModifiedFiles(rootDir) {
|
|
101
|
+
const info = getGitInfo(rootDir);
|
|
102
|
+
if (!info.isGitRepo)
|
|
103
|
+
return [];
|
|
104
|
+
const allFiles = new Set([
|
|
105
|
+
...info.changedFiles,
|
|
106
|
+
...info.stagedFiles,
|
|
107
|
+
...info.untrackedFiles,
|
|
108
|
+
]);
|
|
109
|
+
return Array.from(allFiles);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Filter file list to only include React/JS/TS files
|
|
113
|
+
*/
|
|
114
|
+
export function filterReactFiles(files) {
|
|
115
|
+
const extensions = ['.tsx', '.jsx', '.ts', '.js'];
|
|
116
|
+
return files.filter(f => extensions.some(ext => f.endsWith(ext)));
|
|
117
|
+
}
|
package/dist/reporter.js
CHANGED
|
@@ -255,6 +255,19 @@ function formatSmellType(type) {
|
|
|
255
255
|
'a11y-interactive-role': 'āæ Interactive Role',
|
|
256
256
|
'a11y-keyboard': 'āæ Keyboard Handler',
|
|
257
257
|
'a11y-semantic': 'āæ Semantic HTML',
|
|
258
|
+
// Complexity
|
|
259
|
+
'high-cyclomatic-complexity': 'š§® High Cyclomatic Complexity',
|
|
260
|
+
'high-cognitive-complexity': 'š§ High Cognitive Complexity',
|
|
261
|
+
// Memory Leaks
|
|
262
|
+
'memory-leak-event-listener': 'š§ Event Listener Leak',
|
|
263
|
+
'memory-leak-subscription': 'š§ Subscription Leak',
|
|
264
|
+
'memory-leak-timer': 'š§ Timer Leak',
|
|
265
|
+
'memory-leak-async': 'š§ Async Leak Risk',
|
|
266
|
+
// Import Issues
|
|
267
|
+
'circular-dependency': 'š Circular Dependency',
|
|
268
|
+
'barrel-file-import': 'š¦ Barrel File Import',
|
|
269
|
+
'namespace-import': 'š¦ Namespace Import',
|
|
270
|
+
'excessive-imports': 'š¦ Excessive Imports',
|
|
258
271
|
};
|
|
259
272
|
return labels[type] || type;
|
|
260
273
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type SmellSeverity = 'error' | 'warning' | 'info';
|
|
2
|
-
export type SmellType = 'useEffect-overuse' | 'prop-drilling' | 'large-component' | 'unmemoized-calculation' | 'missing-dependency' | 'state-in-loop' | 'inline-function-prop' | 'deep-nesting' | 'missing-key' | 'hooks-rules-violation' | 'dependency-array-issue' | 'nested-ternary' | 'dead-code' | 'magic-value' | 'debug-statement' | 'todo-comment' | 'security-xss' | 'security-eval' | 'security-secrets' | 'a11y-missing-alt' | 'a11y-missing-label' | 'a11y-interactive-role' | 'a11y-keyboard' | 'a11y-semantic' | 'nextjs-client-server-boundary' | 'nextjs-missing-metadata' | 'nextjs-image-unoptimized' | 'nextjs-router-misuse' | 'rn-inline-style' | 'rn-missing-accessibility' | 'rn-performance-issue' | 'nodejs-callback-hell' | 'nodejs-unhandled-promise' | 'nodejs-sync-io' | 'nodejs-missing-error-handling' | 'js-var-usage' | 'js-loose-equality' | 'js-implicit-coercion' | 'js-global-pollution' | 'ts-any-usage' | 'ts-missing-return-type' | 'ts-non-null-assertion' | 'ts-type-assertion';
|
|
2
|
+
export type SmellType = 'useEffect-overuse' | 'prop-drilling' | 'large-component' | 'unmemoized-calculation' | 'missing-dependency' | 'state-in-loop' | 'inline-function-prop' | 'deep-nesting' | 'missing-key' | 'hooks-rules-violation' | 'dependency-array-issue' | 'nested-ternary' | 'dead-code' | 'magic-value' | 'debug-statement' | 'todo-comment' | 'security-xss' | 'security-eval' | 'security-secrets' | 'a11y-missing-alt' | 'a11y-missing-label' | 'a11y-interactive-role' | 'a11y-keyboard' | 'a11y-semantic' | 'nextjs-client-server-boundary' | 'nextjs-missing-metadata' | 'nextjs-image-unoptimized' | 'nextjs-router-misuse' | 'rn-inline-style' | 'rn-missing-accessibility' | 'rn-performance-issue' | 'nodejs-callback-hell' | 'nodejs-unhandled-promise' | 'nodejs-sync-io' | 'nodejs-missing-error-handling' | 'js-var-usage' | 'js-loose-equality' | 'js-implicit-coercion' | 'js-global-pollution' | 'ts-any-usage' | 'ts-missing-return-type' | 'ts-non-null-assertion' | 'ts-type-assertion' | 'high-cyclomatic-complexity' | 'high-cognitive-complexity' | 'memory-leak-event-listener' | 'memory-leak-subscription' | 'memory-leak-timer' | 'memory-leak-async' | 'circular-dependency' | 'barrel-file-import' | 'namespace-import' | 'excessive-imports';
|
|
3
3
|
export interface CodeSmell {
|
|
4
4
|
type: SmellType;
|
|
5
5
|
severity: SmellSeverity;
|
|
@@ -75,6 +75,12 @@ export interface DetectorConfig {
|
|
|
75
75
|
checkDebugStatements: boolean;
|
|
76
76
|
checkSecurity: boolean;
|
|
77
77
|
checkAccessibility: boolean;
|
|
78
|
+
checkComplexity: boolean;
|
|
79
|
+
maxCyclomaticComplexity: number;
|
|
80
|
+
maxCognitiveComplexity: number;
|
|
81
|
+
maxNestingDepth: number;
|
|
82
|
+
checkMemoryLeaks: boolean;
|
|
83
|
+
checkImports: boolean;
|
|
78
84
|
}
|
|
79
85
|
export declare const DEFAULT_CONFIG: DetectorConfig;
|
|
80
86
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
package/dist/types/index.js
CHANGED
|
@@ -22,4 +22,13 @@ export const DEFAULT_CONFIG = {
|
|
|
22
22
|
checkDebugStatements: true,
|
|
23
23
|
checkSecurity: true,
|
|
24
24
|
checkAccessibility: true,
|
|
25
|
+
// Complexity
|
|
26
|
+
checkComplexity: true,
|
|
27
|
+
maxCyclomaticComplexity: 10,
|
|
28
|
+
maxCognitiveComplexity: 15,
|
|
29
|
+
maxNestingDepth: 4,
|
|
30
|
+
// Memory leaks
|
|
31
|
+
checkMemoryLeaks: true,
|
|
32
|
+
// Import analysis
|
|
33
|
+
checkImports: true,
|
|
25
34
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DetectorConfig } from './types/index.js';
|
|
2
|
+
export interface WatchOptions {
|
|
3
|
+
rootDir: string;
|
|
4
|
+
include: string[];
|
|
5
|
+
exclude: string[];
|
|
6
|
+
config: DetectorConfig;
|
|
7
|
+
showSnippets: boolean;
|
|
8
|
+
onAnalysis?: (fileCount: number, issueCount: number) => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Start watching a project for changes and re-analyze on each change
|
|
12
|
+
*/
|
|
13
|
+
export declare function startWatch(options: WatchOptions): {
|
|
14
|
+
close: () => void;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=watcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,cAAc,EAAkB,MAAM,kBAAkB,CAAC;AAElE,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,cAAc,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG;IAAE,KAAK,EAAE,MAAM,IAAI,CAAA;CAAE,CAgGvE"}
|
package/dist/watcher.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import chokidar from 'chokidar';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { analyzeProject } from './analyzer.js';
|
|
5
|
+
import { reportResults } from './reporter.js';
|
|
6
|
+
/**
|
|
7
|
+
* Start watching a project for changes and re-analyze on each change
|
|
8
|
+
*/
|
|
9
|
+
export function startWatch(options) {
|
|
10
|
+
const { rootDir, include = ['**/*.tsx', '**/*.jsx'], exclude = ['**/node_modules/**', '**/dist/**', '**/build/**'], config, showSnippets, onAnalysis, } = options;
|
|
11
|
+
console.log(chalk.cyan('\nš Watch mode started'));
|
|
12
|
+
console.log(chalk.dim(` Watching: ${rootDir}`));
|
|
13
|
+
console.log(chalk.dim(` Patterns: ${include.join(', ')}`));
|
|
14
|
+
console.log(chalk.dim(' Press Ctrl+C to stop\n'));
|
|
15
|
+
// Debounce timer
|
|
16
|
+
let debounceTimer = null;
|
|
17
|
+
let isAnalyzing = false;
|
|
18
|
+
const runAnalysis = async () => {
|
|
19
|
+
if (isAnalyzing)
|
|
20
|
+
return;
|
|
21
|
+
isAnalyzing = true;
|
|
22
|
+
console.log(chalk.dim('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
23
|
+
console.log(chalk.cyan('š Re-analyzing...'));
|
|
24
|
+
try {
|
|
25
|
+
const result = await analyzeProject({
|
|
26
|
+
rootDir,
|
|
27
|
+
include,
|
|
28
|
+
exclude,
|
|
29
|
+
config,
|
|
30
|
+
});
|
|
31
|
+
// Clear console and show results
|
|
32
|
+
console.clear();
|
|
33
|
+
console.log(chalk.cyan('\nš Watch mode - Last update: ' + new Date().toLocaleTimeString()));
|
|
34
|
+
console.log(chalk.dim(' Press Ctrl+C to stop\n'));
|
|
35
|
+
const output = reportResults(result, {
|
|
36
|
+
format: 'console',
|
|
37
|
+
showCodeSnippets: showSnippets,
|
|
38
|
+
rootDir,
|
|
39
|
+
});
|
|
40
|
+
console.log(output);
|
|
41
|
+
if (onAnalysis) {
|
|
42
|
+
onAnalysis(result.summary.totalFiles, result.summary.totalSmells);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error(chalk.red('Analysis error:'), error.message);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
isAnalyzing = false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
// Debounced analysis trigger
|
|
53
|
+
const triggerAnalysis = () => {
|
|
54
|
+
if (debounceTimer) {
|
|
55
|
+
clearTimeout(debounceTimer);
|
|
56
|
+
}
|
|
57
|
+
debounceTimer = setTimeout(runAnalysis, 300);
|
|
58
|
+
};
|
|
59
|
+
// Set up file watcher
|
|
60
|
+
const watchPatterns = include.map(p => path.join(rootDir, p));
|
|
61
|
+
const watcher = chokidar.watch(watchPatterns, {
|
|
62
|
+
ignored: exclude.map(p => {
|
|
63
|
+
if (p.startsWith('**/'))
|
|
64
|
+
return p;
|
|
65
|
+
return path.join(rootDir, p);
|
|
66
|
+
}),
|
|
67
|
+
persistent: true,
|
|
68
|
+
ignoreInitial: false,
|
|
69
|
+
awaitWriteFinish: {
|
|
70
|
+
stabilityThreshold: 100,
|
|
71
|
+
pollInterval: 50,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
watcher
|
|
75
|
+
.on('add', () => triggerAnalysis())
|
|
76
|
+
.on('change', () => triggerAnalysis())
|
|
77
|
+
.on('unlink', () => triggerAnalysis())
|
|
78
|
+
.on('error', error => console.error(chalk.red('Watcher error:'), error));
|
|
79
|
+
// Initial analysis
|
|
80
|
+
triggerAnalysis();
|
|
81
|
+
return {
|
|
82
|
+
close: () => {
|
|
83
|
+
if (debounceTimer)
|
|
84
|
+
clearTimeout(debounceTimer);
|
|
85
|
+
watcher.close();
|
|
86
|
+
console.log(chalk.yellow('\nš Watch mode stopped'));
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-code-smell-detector",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Detect code smells in React projects - useEffect overuse, prop drilling, large components, security issues, accessibility, and more",
|
|
3
|
+
"version": "1.3.1",
|
|
4
|
+
"description": "Detect code smells in React projects - useEffect overuse, prop drilling, large components, security issues, accessibility, memory leaks, and more",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"react-smell": "dist/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
10
15
|
"scripts": {
|
|
11
16
|
"build": "tsc",
|
|
12
17
|
"dev": "tsc --watch",
|
|
@@ -29,6 +34,7 @@
|
|
|
29
34
|
"@babel/traverse": "^7.23.0",
|
|
30
35
|
"@babel/types": "^7.23.0",
|
|
31
36
|
"chalk": "^5.3.0",
|
|
37
|
+
"chokidar": "^5.0.0",
|
|
32
38
|
"commander": "^11.1.0",
|
|
33
39
|
"fast-glob": "^3.3.2",
|
|
34
40
|
"ora": "^8.0.1"
|