react-code-smell-detector 1.1.1 → 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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +115 -11
  3. package/dist/analyzer.d.ts.map +1 -1
  4. package/dist/analyzer.js +65 -2
  5. package/dist/cli.js +134 -33
  6. package/dist/detectors/accessibility.d.ts +12 -0
  7. package/dist/detectors/accessibility.d.ts.map +1 -0
  8. package/dist/detectors/accessibility.js +191 -0
  9. package/dist/detectors/complexity.d.ts +17 -0
  10. package/dist/detectors/complexity.d.ts.map +1 -0
  11. package/dist/detectors/complexity.js +69 -0
  12. package/dist/detectors/debug.d.ts +10 -0
  13. package/dist/detectors/debug.d.ts.map +1 -0
  14. package/dist/detectors/debug.js +87 -0
  15. package/dist/detectors/imports.d.ts +22 -0
  16. package/dist/detectors/imports.d.ts.map +1 -0
  17. package/dist/detectors/imports.js +210 -0
  18. package/dist/detectors/index.d.ts +6 -0
  19. package/dist/detectors/index.d.ts.map +1 -1
  20. package/dist/detectors/index.js +8 -0
  21. package/dist/detectors/memoryLeak.d.ts +7 -0
  22. package/dist/detectors/memoryLeak.d.ts.map +1 -0
  23. package/dist/detectors/memoryLeak.js +111 -0
  24. package/dist/detectors/security.d.ts +12 -0
  25. package/dist/detectors/security.d.ts.map +1 -0
  26. package/dist/detectors/security.js +161 -0
  27. package/dist/fixer.d.ts +23 -0
  28. package/dist/fixer.d.ts.map +1 -0
  29. package/dist/fixer.js +133 -0
  30. package/dist/git.d.ts +28 -0
  31. package/dist/git.d.ts.map +1 -0
  32. package/dist/git.js +117 -0
  33. package/dist/htmlReporter.d.ts +6 -0
  34. package/dist/htmlReporter.d.ts.map +1 -0
  35. package/dist/htmlReporter.js +453 -0
  36. package/dist/reporter.js +26 -0
  37. package/dist/types/index.d.ts +10 -1
  38. package/dist/types/index.d.ts.map +1 -1
  39. package/dist/types/index.js +13 -0
  40. package/dist/watcher.d.ts +16 -0
  41. package/dist/watcher.d.ts.map +1 -0
  42. package/dist/watcher.js +89 -0
  43. package/package.json +8 -2
  44. package/src/analyzer.ts +0 -269
  45. package/src/cli.ts +0 -125
  46. package/src/detectors/deadCode.ts +0 -163
  47. package/src/detectors/dependencyArray.ts +0 -176
  48. package/src/detectors/hooksRules.ts +0 -101
  49. package/src/detectors/index.ts +0 -16
  50. package/src/detectors/javascript.ts +0 -169
  51. package/src/detectors/largeComponent.ts +0 -63
  52. package/src/detectors/magicValues.ts +0 -114
  53. package/src/detectors/memoization.ts +0 -177
  54. package/src/detectors/missingKey.ts +0 -105
  55. package/src/detectors/nestedTernary.ts +0 -75
  56. package/src/detectors/nextjs.ts +0 -124
  57. package/src/detectors/nodejs.ts +0 -199
  58. package/src/detectors/propDrilling.ts +0 -103
  59. package/src/detectors/reactNative.ts +0 -154
  60. package/src/detectors/typescript.ts +0 -151
  61. package/src/detectors/useEffect.ts +0 -117
  62. package/src/index.ts +0 -4
  63. package/src/parser/index.ts +0 -195
  64. package/src/reporter.ts +0 -278
  65. package/src/types/index.ts +0 -144
  66. 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
+ }
@@ -0,0 +1,12 @@
1
+ import { ParsedComponent } from '../parser/index.js';
2
+ import { CodeSmell, DetectorConfig } from '../types/index.js';
3
+ /**
4
+ * Detects security vulnerabilities:
5
+ * - dangerouslySetInnerHTML usage
6
+ * - eval() and Function() constructor
7
+ * - innerHTML assignments
8
+ * - Unsafe URLs (javascript:, data:)
9
+ * - Exposed secrets/API keys
10
+ */
11
+ export declare function detectSecurityIssues(component: ParsedComponent, filePath: string, sourceCode: string, config?: DetectorConfig): CodeSmell[];
12
+ //# sourceMappingURL=security.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/detectors/security.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAkB,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAkB,MAAM,mBAAmB,CAAC;AAE9E;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,eAAe,EAC1B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,cAA+B,GACtC,SAAS,EAAE,CAiKb"}
@@ -0,0 +1,161 @@
1
+ import * as t from '@babel/types';
2
+ import { getCodeSnippet } from '../parser/index.js';
3
+ import { DEFAULT_CONFIG } from '../types/index.js';
4
+ /**
5
+ * Detects security vulnerabilities:
6
+ * - dangerouslySetInnerHTML usage
7
+ * - eval() and Function() constructor
8
+ * - innerHTML assignments
9
+ * - Unsafe URLs (javascript:, data:)
10
+ * - Exposed secrets/API keys
11
+ */
12
+ export function detectSecurityIssues(component, filePath, sourceCode, config = DEFAULT_CONFIG) {
13
+ if (!config.checkSecurity)
14
+ return [];
15
+ const smells = [];
16
+ // Detect dangerouslySetInnerHTML
17
+ component.path.traverse({
18
+ JSXAttribute(path) {
19
+ if (t.isJSXIdentifier(path.node.name) &&
20
+ path.node.name.name === 'dangerouslySetInnerHTML') {
21
+ const loc = path.node.loc;
22
+ smells.push({
23
+ type: 'security-xss',
24
+ severity: 'error',
25
+ message: `dangerouslySetInnerHTML is a security risk in "${component.name}"`,
26
+ file: filePath,
27
+ line: loc?.start.line || 0,
28
+ column: loc?.start.column || 0,
29
+ suggestion: 'Sanitize HTML with DOMPurify or use a safe alternative like converting to React elements',
30
+ codeSnippet: getCodeSnippet(sourceCode, loc?.start.line || 0),
31
+ });
32
+ }
33
+ },
34
+ });
35
+ // Detect eval() and Function() constructor
36
+ component.path.traverse({
37
+ CallExpression(path) {
38
+ const { callee } = path.node;
39
+ // eval()
40
+ if (t.isIdentifier(callee) && callee.name === 'eval') {
41
+ const loc = path.node.loc;
42
+ smells.push({
43
+ type: 'security-eval',
44
+ severity: 'error',
45
+ message: `eval() is a critical security risk in "${component.name}"`,
46
+ file: filePath,
47
+ line: loc?.start.line || 0,
48
+ column: loc?.start.column || 0,
49
+ suggestion: 'Never use eval(). Parse JSON with JSON.parse() or restructure logic to avoid dynamic code execution.',
50
+ codeSnippet: getCodeSnippet(sourceCode, loc?.start.line || 0),
51
+ });
52
+ }
53
+ },
54
+ // new Function()
55
+ NewExpression(path) {
56
+ const { callee } = path.node;
57
+ if (t.isIdentifier(callee) && callee.name === 'Function') {
58
+ const loc = path.node.loc;
59
+ smells.push({
60
+ type: 'security-eval',
61
+ severity: 'error',
62
+ message: `new Function() is equivalent to eval() and is a security risk`,
63
+ file: filePath,
64
+ line: loc?.start.line || 0,
65
+ column: loc?.start.column || 0,
66
+ suggestion: 'Avoid creating functions from strings. Restructure to use static function definitions.',
67
+ codeSnippet: getCodeSnippet(sourceCode, loc?.start.line || 0),
68
+ });
69
+ }
70
+ },
71
+ });
72
+ // Detect innerHTML assignments
73
+ component.path.traverse({
74
+ AssignmentExpression(path) {
75
+ const { left } = path.node;
76
+ if (t.isMemberExpression(left) && t.isIdentifier(left.property)) {
77
+ if (left.property.name === 'innerHTML' || left.property.name === 'outerHTML') {
78
+ const loc = path.node.loc;
79
+ smells.push({
80
+ type: 'security-xss',
81
+ severity: 'warning',
82
+ message: `Direct ${left.property.name} assignment can lead to XSS`,
83
+ file: filePath,
84
+ line: loc?.start.line || 0,
85
+ column: loc?.start.column || 0,
86
+ suggestion: 'Use textContent for plain text, or sanitize HTML with DOMPurify',
87
+ codeSnippet: getCodeSnippet(sourceCode, loc?.start.line || 0),
88
+ });
89
+ }
90
+ }
91
+ },
92
+ });
93
+ // Detect unsafe URLs (javascript:, data:)
94
+ component.path.traverse({
95
+ JSXAttribute(path) {
96
+ if (!t.isJSXIdentifier(path.node.name))
97
+ return;
98
+ const propName = path.node.name.name;
99
+ if (!['href', 'src', 'action'].includes(propName))
100
+ return;
101
+ const value = path.node.value;
102
+ if (t.isStringLiteral(value)) {
103
+ const url = value.value.toLowerCase().trim();
104
+ if (url.startsWith('javascript:')) {
105
+ const loc = path.node.loc;
106
+ smells.push({
107
+ type: 'security-xss',
108
+ severity: 'error',
109
+ message: `javascript: URLs are a security risk`,
110
+ file: filePath,
111
+ line: loc?.start.line || 0,
112
+ column: loc?.start.column || 0,
113
+ suggestion: 'Use onClick handlers instead of javascript: URLs',
114
+ codeSnippet: getCodeSnippet(sourceCode, loc?.start.line || 0),
115
+ });
116
+ }
117
+ if (url.startsWith('data:') && propName === 'href') {
118
+ const loc = path.node.loc;
119
+ smells.push({
120
+ type: 'security-xss',
121
+ severity: 'warning',
122
+ message: `data: URLs in href can be a security risk`,
123
+ file: filePath,
124
+ line: loc?.start.line || 0,
125
+ column: loc?.start.column || 0,
126
+ suggestion: 'Validate and sanitize data URLs, or use blob URLs instead',
127
+ codeSnippet: getCodeSnippet(sourceCode, loc?.start.line || 0),
128
+ });
129
+ }
130
+ }
131
+ },
132
+ });
133
+ // Detect potential exposed secrets
134
+ const secretPatterns = [
135
+ { pattern: /['"](?:sk[-_]live|pk[-_]live|api[-_]?key|secret[-_]?key|access[-_]?token|auth[-_]?token)['"]\s*[:=]\s*['"][a-zA-Z0-9-_]{20,}/i, name: 'API key' },
136
+ { pattern: /['"](?:ghp|gho|ghu|ghs|ghr)_[a-zA-Z0-9]{36,}['"]/i, name: 'GitHub token' },
137
+ { pattern: /['"]AKIA[A-Z0-9]{16}['"]/i, name: 'AWS access key' },
138
+ { pattern: /password\s*[:=]\s*['"][^'"]{8,}['"]/i, name: 'Hardcoded password' },
139
+ ];
140
+ const lines = sourceCode.split('\n');
141
+ lines.forEach((line, index) => {
142
+ const lineNum = index + 1;
143
+ if (lineNum < component.startLine || lineNum > component.endLine)
144
+ return;
145
+ secretPatterns.forEach(({ pattern, name }) => {
146
+ if (pattern.test(line)) {
147
+ smells.push({
148
+ type: 'security-secrets',
149
+ severity: 'error',
150
+ message: `Potential ${name} exposed in code`,
151
+ file: filePath,
152
+ line: lineNum,
153
+ column: 0,
154
+ suggestion: 'Move secrets to environment variables (.env) and never commit them to version control',
155
+ codeSnippet: getCodeSnippet(sourceCode, lineNum),
156
+ });
157
+ }
158
+ });
159
+ });
160
+ return smells;
161
+ }
@@ -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
+ }
@@ -0,0 +1,6 @@
1
+ import { AnalysisResult } from './types/index.js';
2
+ /**
3
+ * Generate a beautiful HTML report with charts and styling
4
+ */
5
+ export declare function generateHTMLReport(result: AnalysisResult, rootDir: string): string;
6
+ //# sourceMappingURL=htmlReporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"htmlReporter.d.ts","sourceRoot":"","sources":["../src/htmlReporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAiB,MAAM,kBAAkB,CAAC;AAGjE;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CA8YlF"}