react-code-smell-detector 1.5.0 → 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 (87) hide show
  1. package/README.md +20 -0
  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__/performanceBudget.test.js +195 -44
  45. package/dist/__tests__/reporter.test.d.ts +2 -0
  46. package/dist/__tests__/reporter.test.d.ts.map +1 -0
  47. package/dist/__tests__/reporter.test.js +136 -0
  48. package/dist/__tests__/watcher.test.d.ts +2 -0
  49. package/dist/__tests__/watcher.test.d.ts.map +1 -0
  50. package/dist/__tests__/watcher.test.js +161 -0
  51. package/dist/__tests__/webhooks.test.d.ts +2 -0
  52. package/dist/__tests__/webhooks.test.d.ts.map +1 -0
  53. package/dist/__tests__/webhooks.test.js +209 -0
  54. package/dist/aiRefactoring.d.ts +29 -0
  55. package/dist/aiRefactoring.d.ts.map +1 -0
  56. package/dist/aiRefactoring.js +290 -0
  57. package/dist/analyzer.d.ts.map +1 -1
  58. package/dist/analyzer.js +23 -0
  59. package/dist/cli.js +17 -0
  60. package/dist/detectors/contextApi.d.ts +11 -0
  61. package/dist/detectors/contextApi.d.ts.map +1 -0
  62. package/dist/detectors/contextApi.js +151 -0
  63. package/dist/detectors/errorBoundary.d.ts +11 -0
  64. package/dist/detectors/errorBoundary.d.ts.map +1 -0
  65. package/dist/detectors/errorBoundary.js +167 -0
  66. package/dist/detectors/formValidation.d.ts +11 -0
  67. package/dist/detectors/formValidation.d.ts.map +1 -0
  68. package/dist/detectors/formValidation.js +193 -0
  69. package/dist/detectors/index.d.ts +5 -0
  70. package/dist/detectors/index.d.ts.map +1 -1
  71. package/dist/detectors/index.js +10 -0
  72. package/dist/detectors/stateManagement.d.ts +11 -0
  73. package/dist/detectors/stateManagement.d.ts.map +1 -0
  74. package/dist/detectors/stateManagement.js +193 -0
  75. package/dist/detectors/testingGaps.d.ts +15 -0
  76. package/dist/detectors/testingGaps.d.ts.map +1 -0
  77. package/dist/detectors/testingGaps.js +182 -0
  78. package/dist/guide.d.ts +9 -0
  79. package/dist/guide.d.ts.map +1 -0
  80. package/dist/guide.js +922 -0
  81. package/dist/index.d.ts +1 -0
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +1 -0
  84. package/dist/types/index.d.ts +11 -1
  85. package/dist/types/index.d.ts.map +1 -1
  86. package/dist/types/index.js +16 -0
  87. package/package.json +1 -1
package/README.md CHANGED
@@ -81,6 +81,26 @@ npm install -D react-code-smell-detector
81
81
  react-smell /path/to/react/project
82
82
  ```
83
83
 
84
+ ### Interactive Guide
85
+
86
+ Launch an interactive tutorial to learn all features:
87
+
88
+ ```bash
89
+ react-smell guide
90
+ ```
91
+
92
+ ### Demo Project
93
+
94
+ Create a demo project with examples of all detectable code smells:
95
+
96
+ ```bash
97
+ react-smell demo
98
+ # Creates ./react-smell-demo with sample components
99
+
100
+ # Or specify a directory
101
+ react-smell demo /path/to/directory
102
+ ```
103
+
84
104
  ### With Code Snippets
85
105
 
86
106
  ```bash
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=aiRefactoring.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aiRefactoring.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/aiRefactoring.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,86 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { getQuickRefactoringTemplates } from '../aiRefactoring.js';
3
+ describe('AI Refactoring', () => {
4
+ describe('getQuickRefactoringTemplates', () => {
5
+ it('should return templates for useEffect-overuse', () => {
6
+ const templates = getQuickRefactoringTemplates('useEffect-overuse');
7
+ expect(templates.length).toBeGreaterThan(0);
8
+ expect(templates.some(t => t.includes('effect'))).toBe(true);
9
+ });
10
+ it('should return templates for prop-drilling', () => {
11
+ const templates = getQuickRefactoringTemplates('prop-drilling');
12
+ expect(templates.length).toBeGreaterThan(0);
13
+ expect(templates.some(t => t.includes('Context') || t.includes('composition'))).toBe(true);
14
+ });
15
+ it('should return templates for large-component', () => {
16
+ const templates = getQuickRefactoringTemplates('large-component');
17
+ expect(templates.length).toBeGreaterThan(0);
18
+ expect(templates.some(t => t.includes('Extract') || t.includes('component'))).toBe(true);
19
+ });
20
+ it('should return templates for context-overuse', () => {
21
+ const templates = getQuickRefactoringTemplates('context-overuse');
22
+ expect(templates.length).toBeGreaterThan(0);
23
+ expect(templates.some(t => t.includes('context'))).toBe(true);
24
+ });
25
+ it('should return templates for missing-error-boundary', () => {
26
+ const templates = getQuickRefactoringTemplates('missing-error-boundary');
27
+ expect(templates.length).toBeGreaterThan(0);
28
+ expect(templates.some(t => t.includes('ErrorBoundary') || t.includes('Suspense'))).toBe(true);
29
+ });
30
+ it('should return templates for state-sync-anti-pattern', () => {
31
+ const templates = getQuickRefactoringTemplates('state-sync-anti-pattern');
32
+ expect(templates.length).toBeGreaterThan(0);
33
+ expect(templates.some(t => t.includes('Derive') || t.includes('useMemo'))).toBe(true);
34
+ });
35
+ it('should return generic template for unknown smell types', () => {
36
+ const templates = getQuickRefactoringTemplates('unknown-smell-type');
37
+ expect(templates.length).toBeGreaterThan(0);
38
+ expect(templates[0]).toContain('Review');
39
+ });
40
+ });
41
+ describe('AIRefactoringConfig', () => {
42
+ it('should accept valid config object', () => {
43
+ const config = {
44
+ apiKey: 'test-key',
45
+ model: 'gpt-4',
46
+ maxTokens: 1000,
47
+ temperature: 0.3,
48
+ provider: 'openai',
49
+ };
50
+ expect(config.apiKey).toBe('test-key');
51
+ expect(config.model).toBe('gpt-4');
52
+ expect(config.provider).toBe('openai');
53
+ });
54
+ it('should accept anthropic provider', () => {
55
+ const config = {
56
+ apiKey: 'test-key',
57
+ model: 'claude-3-sonnet',
58
+ provider: 'anthropic',
59
+ };
60
+ expect(config.provider).toBe('anthropic');
61
+ });
62
+ });
63
+ describe('AIRefactoringSuggestion type', () => {
64
+ it('should have expected structure', () => {
65
+ const suggestion = {
66
+ smell: {
67
+ type: 'debug-statement',
68
+ severity: 'warning',
69
+ message: 'Test',
70
+ file: '/test.tsx',
71
+ line: 1,
72
+ column: 0,
73
+ suggestion: 'Remove it',
74
+ },
75
+ originalCode: 'console.log("test")',
76
+ suggestedCode: '// removed',
77
+ explanation: 'Debug statements should be removed',
78
+ confidence: 0.9,
79
+ estimatedEffort: 'low',
80
+ };
81
+ expect(suggestion.confidence).toBeGreaterThanOrEqual(0);
82
+ expect(suggestion.confidence).toBeLessThanOrEqual(1);
83
+ expect(['low', 'medium', 'high']).toContain(suggestion.estimatedEffort);
84
+ });
85
+ });
86
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=analyzer-real.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer-real.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/analyzer-real.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,149 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { analyzeProject } from '../analyzer.js';
3
+ import * as fg from 'fast-glob';
4
+ import * as fs from 'fs/promises';
5
+ // Mock fast-glob
6
+ vi.mock('fast-glob', () => ({
7
+ default: vi.fn(),
8
+ }));
9
+ // Mock fs/promises for parseFile
10
+ vi.mock('fs/promises', async (importOriginal) => {
11
+ const actual = await importOriginal();
12
+ return {
13
+ ...actual,
14
+ default: actual,
15
+ readFile: vi.fn(),
16
+ };
17
+ });
18
+ describe('Analyzer - Real Tests', () => {
19
+ beforeEach(() => {
20
+ vi.clearAllMocks();
21
+ });
22
+ describe('analyzeProject', () => {
23
+ it('should return empty results when no files found', async () => {
24
+ fg.default.mockResolvedValue([]);
25
+ const result = await analyzeProject({
26
+ rootDir: '/fake/path',
27
+ });
28
+ expect(result.files).toEqual([]);
29
+ expect(result.summary.totalFiles).toBe(0);
30
+ expect(result.summary.totalComponents).toBe(0);
31
+ expect(result.summary.totalSmells).toBe(0);
32
+ });
33
+ it('should call fast-glob with correct patterns', async () => {
34
+ fg.default.mockResolvedValue([]);
35
+ await analyzeProject({
36
+ rootDir: '/fake/path',
37
+ include: ['**/*.tsx'],
38
+ });
39
+ expect(fg.default).toHaveBeenCalled();
40
+ });
41
+ it('should handle parse errors gracefully', async () => {
42
+ fg.default.mockResolvedValue(['/fake/path/broken.tsx']);
43
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
44
+ const result = await analyzeProject({
45
+ rootDir: '/fake/path',
46
+ });
47
+ // Should return empty since file doesn't exist
48
+ expect(result.files.length).toBe(0);
49
+ warnSpy.mockRestore();
50
+ });
51
+ it('should use custom include patterns', async () => {
52
+ fg.default.mockResolvedValue([]);
53
+ await analyzeProject({
54
+ rootDir: '/fake/path',
55
+ include: ['**/*.ts'],
56
+ exclude: ['**/node_modules/**'],
57
+ });
58
+ expect(fg.default).toHaveBeenCalledWith(expect.arrayContaining([expect.stringContaining('.ts')]), expect.objectContaining({ ignore: ['**/node_modules/**'] }));
59
+ });
60
+ it('should merge user config with defaults', async () => {
61
+ fg.default.mockResolvedValue([]);
62
+ const result = await analyzeProject({
63
+ rootDir: '/fake/path',
64
+ config: { maxUseEffectsPerComponent: 5 },
65
+ });
66
+ expect(result.summary).toBeDefined();
67
+ });
68
+ });
69
+ describe('Technical Debt Score Calculation', () => {
70
+ it('should calculate grade A for clean projects', async () => {
71
+ const cleanCode = `
72
+ function CleanComponent() {
73
+ return <div>Clean</div>;
74
+ }
75
+ `;
76
+ fg.default.mockResolvedValue(['/fake/path/Clean.tsx']);
77
+ fs.readFile.mockResolvedValue(cleanCode);
78
+ const result = await analyzeProject({
79
+ rootDir: '/fake/path',
80
+ config: { checkDebugStatements: false },
81
+ });
82
+ expect(result.debtScore.grade).toBe('A');
83
+ expect(result.debtScore.score).toBeGreaterThanOrEqual(90);
84
+ });
85
+ it('should estimate refactor time based on issues', async () => {
86
+ fg.default.mockResolvedValue([]);
87
+ const result = await analyzeProject({
88
+ rootDir: '/fake/path',
89
+ });
90
+ expect(result.debtScore.estimatedRefactorTime).toBeDefined();
91
+ });
92
+ it('should include breakdown scores', async () => {
93
+ fg.default.mockResolvedValue([]);
94
+ const result = await analyzeProject({
95
+ rootDir: '/fake/path',
96
+ });
97
+ expect(result.debtScore.breakdown).toBeDefined();
98
+ expect(result.debtScore.breakdown.useEffectScore).toBeDefined();
99
+ expect(result.debtScore.breakdown.propDrillingScore).toBeDefined();
100
+ expect(result.debtScore.breakdown.componentSizeScore).toBeDefined();
101
+ expect(result.debtScore.breakdown.memoizationScore).toBeDefined();
102
+ });
103
+ });
104
+ describe('Smell Ignore Comments', () => {
105
+ it('should filter smells with @smell-ignore comment', async () => {
106
+ // Test behavior: when no files match, result should be empty
107
+ fg.default.mockResolvedValue([]);
108
+ const result = await analyzeProject({
109
+ rootDir: '/fake/path',
110
+ config: { checkDebugStatements: true },
111
+ });
112
+ // Empty result with no smells
113
+ expect(result.files.length).toBe(0);
114
+ expect(result.summary.totalSmells).toBe(0);
115
+ });
116
+ });
117
+ describe('Summary Calculation', () => {
118
+ it('should have smells by type structure', async () => {
119
+ fg.default.mockResolvedValue([]);
120
+ const result = await analyzeProject({
121
+ rootDir: '/fake/path',
122
+ config: { checkDebugStatements: true },
123
+ });
124
+ // Should have smellsByType object even if empty
125
+ expect(result.summary.smellsByType).toBeDefined();
126
+ });
127
+ it('should aggregate smells by severity', async () => {
128
+ fg.default.mockResolvedValue([]);
129
+ const result = await analyzeProject({
130
+ rootDir: '/fake/path',
131
+ });
132
+ expect(result.summary.smellsBySeverity).toBeDefined();
133
+ expect(result.summary.smellsBySeverity.error).toBeDefined();
134
+ expect(result.summary.smellsBySeverity.warning).toBeDefined();
135
+ expect(result.summary.smellsBySeverity.info).toBeDefined();
136
+ });
137
+ });
138
+ describe('Component Info Collection', () => {
139
+ it('should return components array in file results', async () => {
140
+ // Test that result structure includes components array
141
+ fg.default.mockResolvedValue([]);
142
+ const result = await analyzeProject({
143
+ rootDir: '/fake/path',
144
+ });
145
+ // For non-existent files, we get empty results
146
+ expect(Array.isArray(result.files)).toBe(true);
147
+ });
148
+ });
149
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=analyzer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/analyzer.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,173 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ // Mock dependencies for analyzer
3
+ vi.mock('fast-glob', () => ({
4
+ default: vi.fn().mockResolvedValue([]),
5
+ }));
6
+ vi.mock('ora', () => ({
7
+ default: () => ({
8
+ start: vi.fn().mockReturnThis(),
9
+ succeed: vi.fn().mockReturnThis(),
10
+ fail: vi.fn().mockReturnThis(),
11
+ stop: vi.fn().mockReturnThis(),
12
+ text: '',
13
+ }),
14
+ }));
15
+ describe('Analyzer', () => {
16
+ beforeEach(() => {
17
+ vi.clearAllMocks();
18
+ });
19
+ describe('analyzeProject', () => {
20
+ it('should accept project path', () => {
21
+ const projectPath = '/path/to/project';
22
+ expect(typeof projectPath).toBe('string');
23
+ });
24
+ it('should accept options object', () => {
25
+ const options = {
26
+ verbose: true,
27
+ fix: false,
28
+ format: 'json',
29
+ };
30
+ expect(options.verbose).toBe(true);
31
+ expect(options.fix).toBe(false);
32
+ expect(options.format).toBe('json');
33
+ });
34
+ it('should handle default options', () => {
35
+ const defaultOptions = {
36
+ verbose: false,
37
+ fix: false,
38
+ format: 'console',
39
+ include: ['**/*.tsx', '**/*.jsx', '**/*.ts', '**/*.js'],
40
+ exclude: ['node_modules/**', 'dist/**', 'build/**'],
41
+ };
42
+ expect(defaultOptions.include).toContain('**/*.tsx');
43
+ expect(defaultOptions.exclude).toContain('node_modules/**');
44
+ });
45
+ it('should support various output formats', () => {
46
+ const formats = ['json', 'console', 'markdown', 'html', 'sarif'];
47
+ formats.forEach(format => {
48
+ expect(['json', 'console', 'markdown', 'html', 'sarif']).toContain(format);
49
+ });
50
+ });
51
+ });
52
+ describe('File pattern matching', () => {
53
+ it('should match React file extensions', () => {
54
+ const patterns = ['**/*.tsx', '**/*.jsx', '**/*.ts', '**/*.js'];
55
+ const testFiles = [
56
+ 'src/App.tsx',
57
+ 'src/utils/helper.ts',
58
+ 'components/Button.jsx',
59
+ 'lib/index.js',
60
+ ];
61
+ testFiles.forEach(file => {
62
+ const matches = patterns.some(pattern => {
63
+ const ext = pattern.replace('**/*', '');
64
+ return file.endsWith(ext);
65
+ });
66
+ expect(matches).toBe(true);
67
+ });
68
+ });
69
+ it('should exclude node_modules and build dirs', () => {
70
+ const excludePatterns = ['node_modules/**', 'dist/**', 'build/**'];
71
+ const shouldExclude = (file) => {
72
+ return excludePatterns.some(pattern => {
73
+ const dir = pattern.replace('/**', '');
74
+ return file.startsWith(dir + '/') || file.startsWith(dir);
75
+ });
76
+ };
77
+ expect(shouldExclude('node_modules/react/index.js')).toBe(true);
78
+ expect(shouldExclude('dist/bundle.js')).toBe(true);
79
+ expect(shouldExclude('build/output.js')).toBe(true);
80
+ expect(shouldExclude('src/App.tsx')).toBe(false);
81
+ });
82
+ });
83
+ describe('Options validation', () => {
84
+ it('should validate format option', () => {
85
+ const validFormats = ['json', 'console', 'markdown', 'html', 'sarif'];
86
+ const isValidFormat = (format) => validFormats.includes(format);
87
+ expect(isValidFormat('json')).toBe(true);
88
+ expect(isValidFormat('invalid')).toBe(false);
89
+ });
90
+ it('should validate threshold is positive number', () => {
91
+ const isValidThreshold = (threshold) => {
92
+ return typeof threshold === 'number' && threshold >= 0;
93
+ };
94
+ expect(isValidThreshold(10)).toBe(true);
95
+ expect(isValidThreshold(0)).toBe(true);
96
+ expect(isValidThreshold(-1)).toBe(false);
97
+ expect(isValidThreshold('10')).toBe(false);
98
+ });
99
+ it('should validate custom rules path', () => {
100
+ const isValidPath = (path) => {
101
+ return typeof path === 'string' && path.length > 0;
102
+ };
103
+ expect(isValidPath('./rules.json')).toBe(true);
104
+ expect(isValidPath('')).toBe(false);
105
+ expect(isValidPath(null)).toBe(false);
106
+ });
107
+ });
108
+ describe('Analysis results structure', () => {
109
+ it('should have correct result shape', () => {
110
+ const mockResult = {
111
+ smells: [],
112
+ filesAnalyzed: 10,
113
+ totalSmells: 0,
114
+ byType: {},
115
+ bySeverity: { error: 0, warning: 0, info: 0 },
116
+ };
117
+ expect(mockResult).toHaveProperty('smells');
118
+ expect(mockResult).toHaveProperty('filesAnalyzed');
119
+ expect(mockResult).toHaveProperty('totalSmells');
120
+ expect(mockResult).toHaveProperty('byType');
121
+ expect(mockResult).toHaveProperty('bySeverity');
122
+ });
123
+ it('should aggregate smells by type', () => {
124
+ const smells = [
125
+ { type: 'debug-statement' },
126
+ { type: 'debug-statement' },
127
+ { type: 'js-var-usage' },
128
+ ];
129
+ const byType = smells.reduce((acc, s) => {
130
+ acc[s.type] = (acc[s.type] || 0) + 1;
131
+ return acc;
132
+ }, {});
133
+ expect(byType['debug-statement']).toBe(2);
134
+ expect(byType['js-var-usage']).toBe(1);
135
+ });
136
+ it('should aggregate smells by severity', () => {
137
+ const smells = [
138
+ { severity: 'error' },
139
+ { severity: 'warning' },
140
+ { severity: 'warning' },
141
+ { severity: 'info' },
142
+ ];
143
+ const bySeverity = smells.reduce((acc, s) => {
144
+ acc[s.severity] = (acc[s.severity] || 0) + 1;
145
+ return acc;
146
+ }, { error: 0, warning: 0, info: 0 });
147
+ expect(bySeverity.error).toBe(1);
148
+ expect(bySeverity.warning).toBe(2);
149
+ expect(bySeverity.info).toBe(1);
150
+ });
151
+ });
152
+ describe('Performance', () => {
153
+ it('should batch file processing efficiently', () => {
154
+ const files = Array(100).fill(null).map((_, i) => `file${i}.tsx`);
155
+ const batchSize = 10;
156
+ const batches = [];
157
+ for (let i = 0; i < files.length; i += batchSize) {
158
+ batches.push(files.slice(i, i + batchSize));
159
+ }
160
+ expect(batches).toHaveLength(10);
161
+ expect(batches[0]).toHaveLength(10);
162
+ });
163
+ it('should handle concurrent file analysis', async () => {
164
+ const analyzeFile = async (file) => {
165
+ return { file, smells: [] };
166
+ };
167
+ const files = ['a.tsx', 'b.tsx', 'c.tsx'];
168
+ const results = await Promise.all(files.map(analyzeFile));
169
+ expect(results).toHaveLength(3);
170
+ expect(results[0].file).toBe('a.tsx');
171
+ });
172
+ });
173
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=baseline.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/baseline.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,136 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { initializeBaseline, recordBaseline, getTrendAnalysis } from '../baseline.js';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import os from 'os';
6
+ describe('Baseline', () => {
7
+ let tempDir;
8
+ beforeEach(() => {
9
+ // Create a temporary directory for tests
10
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'baseline-test-'));
11
+ });
12
+ afterEach(() => {
13
+ // Clean up temporary directory
14
+ try {
15
+ fs.rmSync(tempDir, { recursive: true });
16
+ }
17
+ catch {
18
+ // Ignore cleanup errors
19
+ }
20
+ });
21
+ const createSmell = (type) => ({
22
+ type: type,
23
+ severity: 'warning',
24
+ message: 'Test smell',
25
+ file: '/test.tsx',
26
+ line: 1,
27
+ column: 0,
28
+ suggestion: 'Fix it',
29
+ });
30
+ describe('initializeBaseline', () => {
31
+ it('should create baseline file if it does not exist', () => {
32
+ initializeBaseline(tempDir);
33
+ const baselinePath = path.join(tempDir, '.smellrc-baseline.json');
34
+ expect(fs.existsSync(baselinePath)).toBe(true);
35
+ });
36
+ it('should not overwrite existing baseline file', () => {
37
+ const baselinePath = path.join(tempDir, '.smellrc-baseline.json');
38
+ // Create initial baseline
39
+ initializeBaseline(tempDir);
40
+ // Add a record
41
+ recordBaseline(tempDir, [createSmell('debug-statement')]);
42
+ // Reinitialize
43
+ initializeBaseline(tempDir);
44
+ // Check that record still exists
45
+ const data = JSON.parse(fs.readFileSync(baselinePath, 'utf-8'));
46
+ expect(data.records.length).toBeGreaterThan(0);
47
+ });
48
+ it('should create valid JSON structure', () => {
49
+ initializeBaseline(tempDir);
50
+ const baselinePath = path.join(tempDir, '.smellrc-baseline.json');
51
+ const data = JSON.parse(fs.readFileSync(baselinePath, 'utf-8'));
52
+ expect(data.version).toBe('1.0');
53
+ expect(Array.isArray(data.records)).toBe(true);
54
+ });
55
+ });
56
+ describe('recordBaseline', () => {
57
+ it('should add a record to baseline', () => {
58
+ initializeBaseline(tempDir);
59
+ const smells = [createSmell('debug-statement'), createSmell('js-var-usage')];
60
+ const record = recordBaseline(tempDir, smells);
61
+ expect(record.totalSmells).toBe(2);
62
+ expect(record.byType['debug-statement']).toBe(1);
63
+ expect(record.byType['js-var-usage']).toBe(1);
64
+ });
65
+ it('should include timestamp', () => {
66
+ initializeBaseline(tempDir);
67
+ const record = recordBaseline(tempDir, [createSmell('debug-statement')]);
68
+ expect(record.timestamp).toBeDefined();
69
+ expect(new Date(record.timestamp).getTime()).toBeLessThanOrEqual(Date.now());
70
+ });
71
+ it('should include commit hash when provided', () => {
72
+ initializeBaseline(tempDir);
73
+ const record = recordBaseline(tempDir, [createSmell('debug-statement')], 'abc123');
74
+ expect(record.commit).toBe('abc123');
75
+ });
76
+ it('should keep only last 50 records', () => {
77
+ initializeBaseline(tempDir);
78
+ // Add 55 records
79
+ for (let i = 0; i < 55; i++) {
80
+ recordBaseline(tempDir, [createSmell('debug-statement')]);
81
+ }
82
+ const baselinePath = path.join(tempDir, '.smellrc-baseline.json');
83
+ const data = JSON.parse(fs.readFileSync(baselinePath, 'utf-8'));
84
+ expect(data.records.length).toBe(50);
85
+ });
86
+ it('should limit stored smells to 100', () => {
87
+ initializeBaseline(tempDir);
88
+ // Create 150 smells
89
+ const manySmells = Array(150).fill(null).map(() => createSmell('debug-statement'));
90
+ const record = recordBaseline(tempDir, manySmells);
91
+ expect(record.totalSmells).toBe(150);
92
+ expect(record.smells.length).toBe(100);
93
+ });
94
+ });
95
+ describe('getTrendAnalysis', () => {
96
+ it('should return stable when no baseline exists', () => {
97
+ const trend = getTrendAnalysis(tempDir);
98
+ expect(trend.trend).toBe('stable');
99
+ expect(trend.improved).toBe(0);
100
+ expect(trend.worsened).toBe(0);
101
+ });
102
+ it('should return stable with only one record', () => {
103
+ initializeBaseline(tempDir);
104
+ recordBaseline(tempDir, [createSmell('debug-statement')]);
105
+ const trend = getTrendAnalysis(tempDir);
106
+ expect(trend.trend).toBe('stable');
107
+ });
108
+ it('should detect improving trend', () => {
109
+ initializeBaseline(tempDir);
110
+ // First record: 5 smells
111
+ recordBaseline(tempDir, Array(5).fill(null).map(() => createSmell('debug-statement')));
112
+ // Second record: 3 smells (improved)
113
+ recordBaseline(tempDir, Array(3).fill(null).map(() => createSmell('debug-statement')));
114
+ const trend = getTrendAnalysis(tempDir);
115
+ expect(trend.trend).toBe('improving');
116
+ expect(trend.improved).toBe(2);
117
+ });
118
+ it('should detect worsening trend', () => {
119
+ initializeBaseline(tempDir);
120
+ // First record: 3 smells
121
+ recordBaseline(tempDir, Array(3).fill(null).map(() => createSmell('debug-statement')));
122
+ // Second record: 5 smells (worsened)
123
+ recordBaseline(tempDir, Array(5).fill(null).map(() => createSmell('debug-statement')));
124
+ const trend = getTrendAnalysis(tempDir);
125
+ expect(trend.trend).toBe('worsening');
126
+ expect(trend.worsened).toBe(2);
127
+ });
128
+ it('should return stable when same count', () => {
129
+ initializeBaseline(tempDir);
130
+ recordBaseline(tempDir, Array(3).fill(null).map(() => createSmell('debug-statement')));
131
+ recordBaseline(tempDir, Array(3).fill(null).map(() => createSmell('js-var-usage')));
132
+ const trend = getTrendAnalysis(tempDir);
133
+ expect(trend.trend).toBe('stable');
134
+ });
135
+ });
136
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=bundleAnalyzer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundleAnalyzer.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/bundleAnalyzer.test.ts"],"names":[],"mappings":""}