@oalacea/daemon 0.6.4 → 0.7.0
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/README.md +268 -58
- package/bin/Dockerfile +158 -16
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +22 -2
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/commands/command.types.d.ts +216 -0
- package/dist/cli/commands/command.types.d.ts.map +1 -0
- package/dist/cli/commands/command.types.js +64 -0
- package/dist/cli/commands/command.types.js.map +1 -0
- package/dist/cli/commands/history.command.d.ts +91 -0
- package/dist/cli/commands/history.command.d.ts.map +1 -0
- package/dist/cli/commands/history.command.js +336 -0
- package/dist/cli/commands/history.command.js.map +1 -0
- package/dist/cli/commands/index.d.ts +14 -3
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +7 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/optimize.command.d.ts +110 -0
- package/dist/cli/commands/optimize.command.d.ts.map +1 -0
- package/dist/cli/commands/optimize.command.js +497 -0
- package/dist/cli/commands/optimize.command.js.map +1 -0
- package/dist/cli/commands/report.command.d.ts +110 -0
- package/dist/cli/commands/report.command.d.ts.map +1 -0
- package/dist/cli/commands/report.command.js +532 -0
- package/dist/cli/commands/report.command.js.map +1 -0
- package/dist/cli/commands/review.command.d.ts +110 -0
- package/dist/cli/commands/review.command.d.ts.map +1 -0
- package/dist/cli/commands/review.command.js +520 -0
- package/dist/cli/commands/review.command.js.map +1 -0
- package/dist/cli/commands/score.command.d.ts +47 -0
- package/dist/cli/commands/score.command.d.ts.map +1 -0
- package/dist/cli/commands/score.command.js +261 -0
- package/dist/cli/commands/score.command.js.map +1 -0
- package/dist/cli/utils/index.d.ts +10 -0
- package/dist/cli/utils/index.d.ts.map +1 -0
- package/dist/cli/utils/index.js +10 -0
- package/dist/cli/utils/index.js.map +1 -0
- package/dist/cli/utils/output.d.ts +192 -0
- package/dist/cli/utils/output.d.ts.map +1 -0
- package/dist/cli/utils/output.js +411 -0
- package/dist/cli/utils/output.js.map +1 -0
- package/dist/cli/utils/progress.d.ts +204 -0
- package/dist/cli/utils/progress.d.ts.map +1 -0
- package/dist/cli/utils/progress.js +396 -0
- package/dist/cli/utils/progress.js.map +1 -0
- package/dist/core/types/index.d.ts +1 -0
- package/dist/core/types/index.d.ts.map +1 -1
- package/dist/core/types/project.types.d.ts +3 -3
- package/dist/core/types/project.types.d.ts.map +1 -1
- package/dist/core/types/scoring.types.d.ts +301 -0
- package/dist/core/types/scoring.types.d.ts.map +1 -0
- package/dist/core/types/scoring.types.js +8 -0
- package/dist/core/types/scoring.types.js.map +1 -0
- package/dist/services/detection/framework-detector.d.ts.map +1 -1
- package/dist/services/detection/framework-detector.js +74 -5
- package/dist/services/detection/framework-detector.js.map +1 -1
- package/dist/services/index.d.ts +12 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +14 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/optimization/detectors/bug-detector.d.ts +82 -0
- package/dist/services/optimization/detectors/bug-detector.d.ts.map +1 -0
- package/dist/services/optimization/detectors/bug-detector.js +443 -0
- package/dist/services/optimization/detectors/bug-detector.js.map +1 -0
- package/dist/services/optimization/detectors/code-smell-detector.d.ts +108 -0
- package/dist/services/optimization/detectors/code-smell-detector.d.ts.map +1 -0
- package/dist/services/optimization/detectors/code-smell-detector.js +569 -0
- package/dist/services/optimization/detectors/code-smell-detector.js.map +1 -0
- package/dist/services/optimization/detectors/index.d.ts +7 -0
- package/dist/services/optimization/detectors/index.d.ts.map +1 -0
- package/dist/services/optimization/detectors/index.js +7 -0
- package/dist/services/optimization/detectors/index.js.map +1 -0
- package/dist/services/optimization/detectors/perf-detector.d.ts +80 -0
- package/dist/services/optimization/detectors/perf-detector.d.ts.map +1 -0
- package/dist/services/optimization/detectors/perf-detector.js +451 -0
- package/dist/services/optimization/detectors/perf-detector.js.map +1 -0
- package/dist/services/optimization/index.d.ts +61 -0
- package/dist/services/optimization/index.d.ts.map +1 -0
- package/dist/services/optimization/index.js +69 -0
- package/dist/services/optimization/index.js.map +1 -0
- package/dist/services/optimization/optimization.service.d.ts +65 -0
- package/dist/services/optimization/optimization.service.d.ts.map +1 -0
- package/dist/services/optimization/optimization.service.js +511 -0
- package/dist/services/optimization/optimization.service.js.map +1 -0
- package/dist/services/optimization/optimization.types.d.ts +343 -0
- package/dist/services/optimization/optimization.types.d.ts.map +1 -0
- package/dist/services/optimization/optimization.types.js +8 -0
- package/dist/services/optimization/optimization.types.js.map +1 -0
- package/dist/services/optimization/optimizers/code-optimizer.d.ts +87 -0
- package/dist/services/optimization/optimizers/code-optimizer.d.ts.map +1 -0
- package/dist/services/optimization/optimizers/code-optimizer.js +436 -0
- package/dist/services/optimization/optimizers/code-optimizer.js.map +1 -0
- package/dist/services/optimization/optimizers/index.d.ts +7 -0
- package/dist/services/optimization/optimizers/index.d.ts.map +1 -0
- package/dist/services/optimization/optimizers/index.js +7 -0
- package/dist/services/optimization/optimizers/index.js.map +1 -0
- package/dist/services/optimization/optimizers/perf-optimizer.d.ts +64 -0
- package/dist/services/optimization/optimizers/perf-optimizer.d.ts.map +1 -0
- package/dist/services/optimization/optimizers/perf-optimizer.js +330 -0
- package/dist/services/optimization/optimizers/perf-optimizer.js.map +1 -0
- package/dist/services/optimization/optimizers/refact-optimizer.d.ts +82 -0
- package/dist/services/optimization/optimizers/refact-optimizer.d.ts.map +1 -0
- package/dist/services/optimization/optimizers/refact-optimizer.js +354 -0
- package/dist/services/optimization/optimizers/refact-optimizer.js.map +1 -0
- package/dist/services/optimization/patterns/anti-patterns.d.ts +31 -0
- package/dist/services/optimization/patterns/anti-patterns.d.ts.map +1 -0
- package/dist/services/optimization/patterns/anti-patterns.js +501 -0
- package/dist/services/optimization/patterns/anti-patterns.js.map +1 -0
- package/dist/services/optimization/patterns/index.d.ts +5 -0
- package/dist/services/optimization/patterns/index.d.ts.map +1 -0
- package/dist/services/optimization/patterns/index.js +5 -0
- package/dist/services/optimization/patterns/index.js.map +1 -0
- package/dist/services/reporting/export/chart.exporter.d.ts +59 -0
- package/dist/services/reporting/export/chart.exporter.d.ts.map +1 -0
- package/dist/services/reporting/export/chart.exporter.js +350 -0
- package/dist/services/reporting/export/chart.exporter.js.map +1 -0
- package/dist/services/reporting/export/index.d.ts +9 -0
- package/dist/services/reporting/export/index.d.ts.map +1 -0
- package/dist/services/reporting/export/index.js +10 -0
- package/dist/services/reporting/export/index.js.map +1 -0
- package/dist/services/reporting/export/pdf.exporter.d.ts +133 -0
- package/dist/services/reporting/export/pdf.exporter.d.ts.map +1 -0
- package/dist/services/reporting/export/pdf.exporter.js +270 -0
- package/dist/services/reporting/export/pdf.exporter.js.map +1 -0
- package/dist/services/reporting/history.service.d.ts +93 -0
- package/dist/services/reporting/history.service.d.ts.map +1 -0
- package/dist/services/reporting/history.service.js +285 -0
- package/dist/services/reporting/history.service.js.map +1 -0
- package/dist/services/reporting/index.d.ts +15 -0
- package/dist/services/reporting/index.d.ts.map +1 -0
- package/dist/services/reporting/index.js +16 -0
- package/dist/services/reporting/index.js.map +1 -0
- package/dist/services/reporting/report.service.d.ts +102 -0
- package/dist/services/reporting/report.service.d.ts.map +1 -0
- package/dist/services/reporting/report.service.js +240 -0
- package/dist/services/reporting/report.service.js.map +1 -0
- package/dist/services/reporting/reporting.types.d.ts +329 -0
- package/dist/services/reporting/reporting.types.d.ts.map +1 -0
- package/dist/services/reporting/reporting.types.js +8 -0
- package/dist/services/reporting/reporting.types.js.map +1 -0
- package/dist/services/reporting/templates/html.template.d.ts +81 -0
- package/dist/services/reporting/templates/html.template.d.ts.map +1 -0
- package/dist/services/reporting/templates/html.template.js +741 -0
- package/dist/services/reporting/templates/html.template.js.map +1 -0
- package/dist/services/reporting/templates/json.template.d.ts +85 -0
- package/dist/services/reporting/templates/json.template.d.ts.map +1 -0
- package/dist/services/reporting/templates/json.template.js +308 -0
- package/dist/services/reporting/templates/json.template.js.map +1 -0
- package/dist/services/reporting/templates/markdown.template.d.ts +69 -0
- package/dist/services/reporting/templates/markdown.template.d.ts.map +1 -0
- package/dist/services/reporting/templates/markdown.template.js +311 -0
- package/dist/services/reporting/templates/markdown.template.js.map +1 -0
- package/dist/services/reporting/trend-analyzer.d.ts +73 -0
- package/dist/services/reporting/trend-analyzer.d.ts.map +1 -0
- package/dist/services/reporting/trend-analyzer.js +291 -0
- package/dist/services/reporting/trend-analyzer.js.map +1 -0
- package/dist/services/review/analyzers/dependency-analyzer.d.ts +87 -0
- package/dist/services/review/analyzers/dependency-analyzer.d.ts.map +1 -0
- package/dist/services/review/analyzers/dependency-analyzer.js +458 -0
- package/dist/services/review/analyzers/dependency-analyzer.js.map +1 -0
- package/dist/services/review/analyzers/index.d.ts +13 -0
- package/dist/services/review/analyzers/index.d.ts.map +1 -0
- package/dist/services/review/analyzers/index.js +13 -0
- package/dist/services/review/analyzers/index.js.map +1 -0
- package/dist/services/review/analyzers/nestjs-analyzer.d.ts +210 -0
- package/dist/services/review/analyzers/nestjs-analyzer.d.ts.map +1 -0
- package/dist/services/review/analyzers/nestjs-analyzer.js +571 -0
- package/dist/services/review/analyzers/nestjs-analyzer.js.map +1 -0
- package/dist/services/review/analyzers/performance-analyzer.d.ts +91 -0
- package/dist/services/review/analyzers/performance-analyzer.d.ts.map +1 -0
- package/dist/services/review/analyzers/performance-analyzer.js +589 -0
- package/dist/services/review/analyzers/performance-analyzer.js.map +1 -0
- package/dist/services/review/analyzers/security-analyzer.d.ts +96 -0
- package/dist/services/review/analyzers/security-analyzer.d.ts.map +1 -0
- package/dist/services/review/analyzers/security-analyzer.js +512 -0
- package/dist/services/review/analyzers/security-analyzer.js.map +1 -0
- package/dist/services/review/analyzers/static-analyzer.d.ts +90 -0
- package/dist/services/review/analyzers/static-analyzer.d.ts.map +1 -0
- package/dist/services/review/analyzers/static-analyzer.js +423 -0
- package/dist/services/review/analyzers/static-analyzer.js.map +1 -0
- package/dist/services/review/fixers/auto-fixer.d.ts +94 -0
- package/dist/services/review/fixers/auto-fixer.d.ts.map +1 -0
- package/dist/services/review/fixers/auto-fixer.js +404 -0
- package/dist/services/review/fixers/auto-fixer.js.map +1 -0
- package/dist/services/review/fixers/index.d.ts +11 -0
- package/dist/services/review/fixers/index.d.ts.map +1 -0
- package/dist/services/review/fixers/index.js +11 -0
- package/dist/services/review/fixers/index.js.map +1 -0
- package/dist/services/review/fixers/refactor-suggester.d.ts +100 -0
- package/dist/services/review/fixers/refactor-suggester.d.ts.map +1 -0
- package/dist/services/review/fixers/refactor-suggester.js +555 -0
- package/dist/services/review/fixers/refactor-suggester.js.map +1 -0
- package/dist/services/review/fixers/test-generator.d.ts +99 -0
- package/dist/services/review/fixers/test-generator.d.ts.map +1 -0
- package/dist/services/review/fixers/test-generator.js +458 -0
- package/dist/services/review/fixers/test-generator.js.map +1 -0
- package/dist/services/review/index.d.ts +14 -0
- package/dist/services/review/index.d.ts.map +1 -0
- package/dist/services/review/index.js +14 -0
- package/dist/services/review/index.js.map +1 -0
- package/dist/services/review/reporters/fix-reporter.d.ts +67 -0
- package/dist/services/review/reporters/fix-reporter.d.ts.map +1 -0
- package/dist/services/review/reporters/fix-reporter.js +437 -0
- package/dist/services/review/reporters/fix-reporter.js.map +1 -0
- package/dist/services/review/reporters/index.d.ts +10 -0
- package/dist/services/review/reporters/index.d.ts.map +1 -0
- package/dist/services/review/reporters/index.js +10 -0
- package/dist/services/review/reporters/index.js.map +1 -0
- package/dist/services/review/reporters/score-reporter.d.ts +84 -0
- package/dist/services/review/reporters/score-reporter.d.ts.map +1 -0
- package/dist/services/review/reporters/score-reporter.js +560 -0
- package/dist/services/review/reporters/score-reporter.js.map +1 -0
- package/dist/services/review/review.service.d.ts +129 -0
- package/dist/services/review/review.service.d.ts.map +1 -0
- package/dist/services/review/review.service.js +396 -0
- package/dist/services/review/review.service.js.map +1 -0
- package/dist/services/review/review.types.d.ts +443 -0
- package/dist/services/review/review.types.d.ts.map +1 -0
- package/dist/services/review/review.types.js +11 -0
- package/dist/services/review/review.types.js.map +1 -0
- package/dist/services/scoring/dimensions/accessibility.analyzer.d.ts +53 -0
- package/dist/services/scoring/dimensions/accessibility.analyzer.d.ts.map +1 -0
- package/dist/services/scoring/dimensions/accessibility.analyzer.js +260 -0
- package/dist/services/scoring/dimensions/accessibility.analyzer.js.map +1 -0
- package/dist/services/scoring/dimensions/backend-logic.analyzer.d.ts +138 -0
- package/dist/services/scoring/dimensions/backend-logic.analyzer.d.ts.map +1 -0
- package/dist/services/scoring/dimensions/backend-logic.analyzer.js +713 -0
- package/dist/services/scoring/dimensions/backend-logic.analyzer.js.map +1 -0
- package/dist/services/scoring/dimensions/business-logic.analyzer.d.ts +142 -0
- package/dist/services/scoring/dimensions/business-logic.analyzer.d.ts.map +1 -0
- package/dist/services/scoring/dimensions/business-logic.analyzer.js +747 -0
- package/dist/services/scoring/dimensions/business-logic.analyzer.js.map +1 -0
- package/dist/services/scoring/dimensions/code-quality.analyzer.d.ts +142 -0
- package/dist/services/scoring/dimensions/code-quality.analyzer.d.ts.map +1 -0
- package/dist/services/scoring/dimensions/code-quality.analyzer.js +685 -0
- package/dist/services/scoring/dimensions/code-quality.analyzer.js.map +1 -0
- package/dist/services/scoring/dimensions/index.d.ts +18 -0
- package/dist/services/scoring/dimensions/index.d.ts.map +1 -0
- package/dist/services/scoring/dimensions/index.js +27 -0
- package/dist/services/scoring/dimensions/index.js.map +1 -0
- package/dist/services/scoring/dimensions/performance.analyzer.d.ts +125 -0
- package/dist/services/scoring/dimensions/performance.analyzer.d.ts.map +1 -0
- package/dist/services/scoring/dimensions/performance.analyzer.js +615 -0
- package/dist/services/scoring/dimensions/performance.analyzer.js.map +1 -0
- package/dist/services/scoring/dimensions/security.analyzer.d.ts +53 -0
- package/dist/services/scoring/dimensions/security.analyzer.d.ts.map +1 -0
- package/dist/services/scoring/dimensions/security.analyzer.js +327 -0
- package/dist/services/scoring/dimensions/security.analyzer.js.map +1 -0
- package/dist/services/scoring/dimensions/seo.analyzer.d.ts +77 -0
- package/dist/services/scoring/dimensions/seo.analyzer.d.ts.map +1 -0
- package/dist/services/scoring/dimensions/seo.analyzer.js +502 -0
- package/dist/services/scoring/dimensions/seo.analyzer.js.map +1 -0
- package/dist/services/scoring/dimensions/test-coverage.analyzer.d.ts +106 -0
- package/dist/services/scoring/dimensions/test-coverage.analyzer.d.ts.map +1 -0
- package/dist/services/scoring/dimensions/test-coverage.analyzer.js +496 -0
- package/dist/services/scoring/dimensions/test-coverage.analyzer.js.map +1 -0
- package/dist/services/scoring/dimensions/ui-ux.analyzer.d.ts +126 -0
- package/dist/services/scoring/dimensions/ui-ux.analyzer.d.ts.map +1 -0
- package/dist/services/scoring/dimensions/ui-ux.analyzer.js +665 -0
- package/dist/services/scoring/dimensions/ui-ux.analyzer.js.map +1 -0
- package/dist/services/scoring/index.d.ts +10 -0
- package/dist/services/scoring/index.d.ts.map +1 -0
- package/dist/services/scoring/index.js +10 -0
- package/dist/services/scoring/index.js.map +1 -0
- package/dist/services/scoring/scoring-service.d.ts +222 -0
- package/dist/services/scoring/scoring-service.d.ts.map +1 -0
- package/dist/services/scoring/scoring-service.js +636 -0
- package/dist/services/scoring/scoring-service.js.map +1 -0
- package/package.json +11 -3
- package/templates/README.md +183 -0
- package/templates/nestjs/controller.spec.ts +203 -0
- package/templates/nestjs/e2e/api.e2e-spec.ts +451 -0
- package/templates/nestjs/e2e/auth.e2e-spec.ts +533 -0
- package/templates/nestjs/fixtures/test-module.ts +311 -0
- package/templates/nestjs/guard.spec.ts +314 -0
- package/templates/nestjs/interceptor.spec.ts +458 -0
- package/templates/nestjs/module.spec.ts +173 -0
- package/templates/nestjs/pipe.spec.ts +474 -0
- package/templates/nestjs/service.spec.ts +296 -0
- package/templates/rust/Cargo.toml +72 -0
- package/templates/rust/actix-controller.test.rs +114 -0
- package/templates/rust/axum-handler.test.rs +117 -0
- package/templates/rust/integration.test.rs +63 -0
- package/templates/rust/rocket-route.test.rs +106 -0
- package/templates/rust/unit.test.rs +38 -0
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Quality Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Analyzes code quality by running ESLint, checking cyclomatic complexity,
|
|
5
|
+
* detecting code duplication, and enforcing naming conventions.
|
|
6
|
+
*
|
|
7
|
+
* @module services/scoring/dimensions/code-quality
|
|
8
|
+
*/
|
|
9
|
+
import { readFile, access, readdir } from 'node:fs/promises';
|
|
10
|
+
import { join, extname, relative } from 'node:path';
|
|
11
|
+
import { existsSync } from 'node:fs';
|
|
12
|
+
import { CommandExecutor } from '../../../shared/utils/command-executor.js';
|
|
13
|
+
import { createLogger } from '../../../shared/utils/logger.js';
|
|
14
|
+
/**
|
|
15
|
+
* Code Quality Analyzer
|
|
16
|
+
*
|
|
17
|
+
* Evaluates code quality across multiple dimensions:
|
|
18
|
+
* - ESLint violations
|
|
19
|
+
* - Cyclomatic complexity
|
|
20
|
+
* - Code duplication
|
|
21
|
+
* - Function length
|
|
22
|
+
* - Naming conventions
|
|
23
|
+
*/
|
|
24
|
+
export class CodeQualityAnalyzer {
|
|
25
|
+
/** Analyzer configuration */
|
|
26
|
+
config = {
|
|
27
|
+
dimension: 'code-quality',
|
|
28
|
+
defaultWeight: 0.15,
|
|
29
|
+
estimatedDuration: 45000,
|
|
30
|
+
supportedFrameworks: ['Next.js', 'React', 'Vue', 'Nuxt', 'Svelte', 'NestJS', 'Angular', 'Remix', 'SvelteKit', 'Astro', 'Gatsby'],
|
|
31
|
+
};
|
|
32
|
+
logger;
|
|
33
|
+
executor;
|
|
34
|
+
maxComplexity;
|
|
35
|
+
maxFunctionLength;
|
|
36
|
+
duplicationThreshold;
|
|
37
|
+
eslintCommand;
|
|
38
|
+
checkTypeScript;
|
|
39
|
+
constructor(options = {}) {
|
|
40
|
+
this.logger = createLogger('CodeQualityAnalyzer');
|
|
41
|
+
this.executor = new CommandExecutor();
|
|
42
|
+
this.maxComplexity = options.maxComplexity ?? 10;
|
|
43
|
+
this.maxFunctionLength = options.maxFunctionLength ?? 50;
|
|
44
|
+
this.duplicationThreshold = options.duplicationThreshold ?? 5;
|
|
45
|
+
this.eslintCommand = options.eslintCommand ?? 'npx eslint';
|
|
46
|
+
this.checkTypeScript = options.checkTypeScript ?? true;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get the dimension this analyzer handles
|
|
50
|
+
*/
|
|
51
|
+
getDimension() {
|
|
52
|
+
return 'code-quality';
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get the default weight for this dimension
|
|
56
|
+
*/
|
|
57
|
+
getWeight() {
|
|
58
|
+
return 0.15; // 15% weight in overall score
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Analyze code quality for a project
|
|
62
|
+
*
|
|
63
|
+
* @param projectPath - Path to the project root
|
|
64
|
+
* @param _framework - Detected framework (optional, for framework-specific analysis)
|
|
65
|
+
* @param _options - Scoring options (optional)
|
|
66
|
+
* @returns Dimension score with quality metrics
|
|
67
|
+
*/
|
|
68
|
+
async analyze(projectPath, _framework, _options) {
|
|
69
|
+
const startTime = performance.now();
|
|
70
|
+
this.logger.info(`Analyzing code quality for: ${projectPath}`);
|
|
71
|
+
const issues = [];
|
|
72
|
+
const improvements = [];
|
|
73
|
+
try {
|
|
74
|
+
// Run ESLint
|
|
75
|
+
const eslintResults = await this.runEslint(projectPath);
|
|
76
|
+
// Analyze complexity
|
|
77
|
+
const complexityMetrics = await this.analyzeComplexity(projectPath);
|
|
78
|
+
// Check for code duplication
|
|
79
|
+
const duplications = await this.detectDuplications(projectPath);
|
|
80
|
+
// Check function lengths
|
|
81
|
+
const longFunctions = await this.findLongFunctions(projectPath);
|
|
82
|
+
// Check naming conventions
|
|
83
|
+
const namingViolations = await this.checkNamingConventions(projectPath);
|
|
84
|
+
// Build issues list
|
|
85
|
+
issues.push(...this.identifyQualityIssues(eslintResults, complexityMetrics, duplications, longFunctions, namingViolations));
|
|
86
|
+
// Build improvements list
|
|
87
|
+
improvements.push(...this.generateQualityImprovements(eslintResults, complexityMetrics, duplications, longFunctions));
|
|
88
|
+
// Calculate final score
|
|
89
|
+
const score = this.calculateQualityScore(eslintResults, complexityMetrics, duplications, longFunctions);
|
|
90
|
+
const duration = Math.round(performance.now() - startTime);
|
|
91
|
+
return {
|
|
92
|
+
dimension: this.getDimension(),
|
|
93
|
+
score,
|
|
94
|
+
weight: this.getWeight(),
|
|
95
|
+
weightedScore: score * this.getWeight(),
|
|
96
|
+
issues,
|
|
97
|
+
improvements,
|
|
98
|
+
metadata: {
|
|
99
|
+
itemsChecked: await this.countSourceFiles(projectPath),
|
|
100
|
+
itemsPassed: await this.countPassedChecks(eslintResults, complexityMetrics, duplications),
|
|
101
|
+
metrics: {
|
|
102
|
+
eslintErrors: eslintResults.reduce((sum, r) => sum + r.errorCount, 0),
|
|
103
|
+
eslintWarnings: eslintResults.reduce((sum, r) => sum + r.warningCount, 0),
|
|
104
|
+
avgComplexity: this.calculateAverageComplexity(complexityMetrics),
|
|
105
|
+
maxComplexity: Math.max(0, ...complexityMetrics.map((m) => m.complexity)),
|
|
106
|
+
duplications: duplications.length,
|
|
107
|
+
longFunctions: longFunctions.length,
|
|
108
|
+
namingViolations: namingViolations.length,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
this.logger.error('Error analyzing code quality', error);
|
|
115
|
+
issues.push({
|
|
116
|
+
severity: 'high',
|
|
117
|
+
category: 'code-style',
|
|
118
|
+
description: `Failed to analyze code quality: ${error instanceof Error ? error.message : String(error)}`,
|
|
119
|
+
fixable: false,
|
|
120
|
+
});
|
|
121
|
+
const duration = Math.round(performance.now() - startTime);
|
|
122
|
+
return {
|
|
123
|
+
dimension: this.getDimension(),
|
|
124
|
+
score: 0,
|
|
125
|
+
weight: this.getWeight(),
|
|
126
|
+
weightedScore: 0,
|
|
127
|
+
issues,
|
|
128
|
+
improvements,
|
|
129
|
+
metadata: { error: String(error) },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Run ESLint and parse results
|
|
135
|
+
*/
|
|
136
|
+
async runEslint(projectPath) {
|
|
137
|
+
const results = [];
|
|
138
|
+
try {
|
|
139
|
+
// Check for ESLint config
|
|
140
|
+
const hasEslintConfig = await this.hasEslintConfig(projectPath);
|
|
141
|
+
if (!hasEslintConfig) {
|
|
142
|
+
this.logger.info('No ESLint config found, skipping ESLint analysis');
|
|
143
|
+
return results;
|
|
144
|
+
}
|
|
145
|
+
// Run ESLint with JSON formatter
|
|
146
|
+
const command = `${this.eslintCommand} . --format json --max-warnings 0`;
|
|
147
|
+
const execResult = await this.executor.execute(command, {
|
|
148
|
+
cwd: projectPath,
|
|
149
|
+
timeout: 60000,
|
|
150
|
+
silent: true,
|
|
151
|
+
});
|
|
152
|
+
if (!execResult.success || !execResult.data) {
|
|
153
|
+
// ESLint may have found issues, try to parse output
|
|
154
|
+
if (execResult.error) {
|
|
155
|
+
this.logger.warn('ESLint execution failed', execResult.error);
|
|
156
|
+
}
|
|
157
|
+
return results;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const eslintOutput = JSON.parse(execResult.data.stdout);
|
|
161
|
+
return Array.isArray(eslintOutput) ? eslintOutput : [eslintOutput];
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// Output not in expected format
|
|
165
|
+
return results;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
this.logger.warn('Failed to run ESLint', error);
|
|
170
|
+
return results;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Check if project has ESLint configuration
|
|
175
|
+
*/
|
|
176
|
+
async hasEslintConfig(projectPath) {
|
|
177
|
+
const configFiles = [
|
|
178
|
+
'.eslintrc.js',
|
|
179
|
+
'.eslintrc.cjs',
|
|
180
|
+
'.eslintrc.json',
|
|
181
|
+
'.eslintrc.yaml',
|
|
182
|
+
'.eslintrc.yml',
|
|
183
|
+
'eslint.config.js',
|
|
184
|
+
'eslint.config.mjs',
|
|
185
|
+
'.eslintrc',
|
|
186
|
+
];
|
|
187
|
+
for (const file of configFiles) {
|
|
188
|
+
try {
|
|
189
|
+
await access(join(projectPath, file));
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Also check package.json for eslintConfig
|
|
197
|
+
try {
|
|
198
|
+
const pkgPath = join(projectPath, 'package.json');
|
|
199
|
+
const pkgContent = await readFile(pkgPath, 'utf-8');
|
|
200
|
+
const pkg = JSON.parse(pkgContent);
|
|
201
|
+
if (pkg.eslintConfig) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// Ignore
|
|
207
|
+
}
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Analyze cyclomatic complexity of source files
|
|
212
|
+
*/
|
|
213
|
+
async analyzeComplexity(projectPath) {
|
|
214
|
+
const complexities = [];
|
|
215
|
+
const srcDir = join(projectPath, 'src');
|
|
216
|
+
if (!existsSync(srcDir)) {
|
|
217
|
+
return complexities;
|
|
218
|
+
}
|
|
219
|
+
await this.scanDirectoryForComplexity(srcDir, projectPath, complexities);
|
|
220
|
+
return complexities;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Recursively scan directory and calculate complexity
|
|
224
|
+
*/
|
|
225
|
+
async scanDirectoryForComplexity(dir, projectPath, complexities) {
|
|
226
|
+
try {
|
|
227
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
228
|
+
for (const entry of entries) {
|
|
229
|
+
const fullPath = join(dir, entry.name);
|
|
230
|
+
if (entry.isDirectory()) {
|
|
231
|
+
if (['node_modules', '.git', 'dist', 'build', 'coverage'].includes(entry.name)) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
await this.scanDirectoryForComplexity(fullPath, projectPath, complexities);
|
|
235
|
+
}
|
|
236
|
+
else if (entry.isFile()) {
|
|
237
|
+
const ext = extname(entry.name);
|
|
238
|
+
if (['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {
|
|
239
|
+
const metrics = await this.calculateFileComplexity(fullPath);
|
|
240
|
+
if (metrics) {
|
|
241
|
+
complexities.push(metrics);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
// Directory not accessible
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Calculate cyclomatic complexity for a single file
|
|
253
|
+
*/
|
|
254
|
+
async calculateFileComplexity(filepath) {
|
|
255
|
+
try {
|
|
256
|
+
const content = await readFile(filepath, 'utf-8');
|
|
257
|
+
const lines = content.split('\n');
|
|
258
|
+
// Simplified complexity calculation
|
|
259
|
+
// Count decision points: if, else, for, while, case, catch, ?, &&, ||
|
|
260
|
+
let complexity = 1; // Base complexity
|
|
261
|
+
let functionCount = 0;
|
|
262
|
+
const decisionKeywords = [
|
|
263
|
+
/\bif\b/g,
|
|
264
|
+
/\belse\b/g,
|
|
265
|
+
/\bfor\b/g,
|
|
266
|
+
/\bwhile\b/g,
|
|
267
|
+
/\bswitch\b/g,
|
|
268
|
+
/\bcase\b/g,
|
|
269
|
+
/\bcatch\b/g,
|
|
270
|
+
/\?/g,
|
|
271
|
+
/&&/g,
|
|
272
|
+
/\|\|/g,
|
|
273
|
+
];
|
|
274
|
+
for (const line of lines) {
|
|
275
|
+
const trimmed = line.trim();
|
|
276
|
+
// Count function declarations
|
|
277
|
+
if (/^\s*(async\s+)?(function\s+\w+|const\s+\w+\s*=\s*(async\s+)?\(?[\w\s,]*\)?\s*=>)/.test(trimmed)) {
|
|
278
|
+
functionCount++;
|
|
279
|
+
}
|
|
280
|
+
// Count decision points
|
|
281
|
+
for (const keyword of decisionKeywords) {
|
|
282
|
+
const matches = trimmed.match(keyword);
|
|
283
|
+
if (matches) {
|
|
284
|
+
complexity += matches.length;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
filepath: relative(process.cwd(), filepath),
|
|
290
|
+
complexity,
|
|
291
|
+
lineCount: lines.length,
|
|
292
|
+
functionCount,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Detect code duplication using simplified token analysis
|
|
301
|
+
*/
|
|
302
|
+
async detectDuplications(projectPath) {
|
|
303
|
+
const duplications = [];
|
|
304
|
+
const srcDir = join(projectPath, 'src');
|
|
305
|
+
if (!existsSync(srcDir)) {
|
|
306
|
+
return duplications;
|
|
307
|
+
}
|
|
308
|
+
// Collect all normalized lines from source files
|
|
309
|
+
const lineMap = new Map();
|
|
310
|
+
await this.collectNormalizedLines(srcDir, lineMap);
|
|
311
|
+
// Find duplicated lines
|
|
312
|
+
for (const [line, files] of Array.from(lineMap.entries())) {
|
|
313
|
+
if (files.length >= this.duplicationThreshold && line.length > 30) {
|
|
314
|
+
// Skip very short lines (likely not meaningful duplication)
|
|
315
|
+
duplications.push({
|
|
316
|
+
filepath: files[0],
|
|
317
|
+
lines: line,
|
|
318
|
+
occurrences: files.length,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return duplications.slice(0, 50); // Limit results
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Collect normalized lines from source files for duplication detection
|
|
326
|
+
*/
|
|
327
|
+
async collectNormalizedLines(dir, lineMap) {
|
|
328
|
+
try {
|
|
329
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
330
|
+
for (const entry of entries) {
|
|
331
|
+
const fullPath = join(dir, entry.name);
|
|
332
|
+
if (entry.isDirectory()) {
|
|
333
|
+
if (['node_modules', '.git', 'dist', 'build', 'coverage'].includes(entry.name)) {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
await this.collectNormalizedLines(fullPath, lineMap);
|
|
337
|
+
}
|
|
338
|
+
else if (entry.isFile()) {
|
|
339
|
+
const ext = extname(entry.name);
|
|
340
|
+
if (['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {
|
|
341
|
+
try {
|
|
342
|
+
const content = await readFile(fullPath, 'utf-8');
|
|
343
|
+
const lines = content.split('\n');
|
|
344
|
+
for (const line of lines) {
|
|
345
|
+
const trimmed = line.trim();
|
|
346
|
+
// Normalize: remove string literals, extra whitespace
|
|
347
|
+
const normalized = trimmed
|
|
348
|
+
.replace(/['"`].*?['"`]/g, '""')
|
|
349
|
+
.replace(/\s+/g, ' ')
|
|
350
|
+
.toLowerCase();
|
|
351
|
+
if (normalized.length > 20 && !normalized.startsWith('//') && !normalized.startsWith('*')) {
|
|
352
|
+
if (!lineMap.has(normalized)) {
|
|
353
|
+
lineMap.set(normalized, []);
|
|
354
|
+
}
|
|
355
|
+
lineMap.get(normalized).push(relative(process.cwd(), fullPath));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
// Skip files that can't be read
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
// Directory not accessible
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Find functions that exceed maximum length
|
|
372
|
+
*/
|
|
373
|
+
async findLongFunctions(projectPath) {
|
|
374
|
+
const longFunctions = [];
|
|
375
|
+
const srcDir = join(projectPath, 'src');
|
|
376
|
+
if (!existsSync(srcDir)) {
|
|
377
|
+
return longFunctions;
|
|
378
|
+
}
|
|
379
|
+
await this.scanForLongFunctions(srcDir, projectPath, longFunctions);
|
|
380
|
+
return longFunctions;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Scan directory for long functions
|
|
384
|
+
*/
|
|
385
|
+
async scanForLongFunctions(dir, projectPath, longFunctions) {
|
|
386
|
+
try {
|
|
387
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
388
|
+
for (const entry of entries) {
|
|
389
|
+
const fullPath = join(dir, entry.name);
|
|
390
|
+
if (entry.isDirectory()) {
|
|
391
|
+
if (['node_modules', '.git', 'dist', 'build', 'coverage'].includes(entry.name)) {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
await this.scanForLongFunctions(fullPath, projectPath, longFunctions);
|
|
395
|
+
}
|
|
396
|
+
else if (entry.isFile()) {
|
|
397
|
+
const ext = extname(entry.name);
|
|
398
|
+
if (['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {
|
|
399
|
+
await this.analyzeFileForLongFunctions(fullPath, longFunctions);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
// Directory not accessible
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Analyze a single file for long functions
|
|
410
|
+
*/
|
|
411
|
+
async analyzeFileForLongFunctions(filepath, longFunctions) {
|
|
412
|
+
try {
|
|
413
|
+
const content = await readFile(filepath, 'utf-8');
|
|
414
|
+
const lines = content.split('\n');
|
|
415
|
+
let currentFunction = null;
|
|
416
|
+
for (let i = 0; i < lines.length; i++) {
|
|
417
|
+
const line = lines[i].trim();
|
|
418
|
+
// Detect function start
|
|
419
|
+
const functionMatch = line.match(/(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:\(?[\w\s,]*\)?\s*=>)|(\w+)\s*\([^)]*\)\s*{)/);
|
|
420
|
+
if (functionMatch && !line.includes('=>')) {
|
|
421
|
+
const functionName = functionMatch[1] || functionMatch[2] || functionMatch[3] || 'anonymous';
|
|
422
|
+
currentFunction = { name: functionName, startLine: i, braceCount: 0 };
|
|
423
|
+
}
|
|
424
|
+
// Track braces for function scope
|
|
425
|
+
if (currentFunction) {
|
|
426
|
+
if (line.includes('{')) {
|
|
427
|
+
currentFunction.braceCount += (line.match(/{/g) || []).length;
|
|
428
|
+
}
|
|
429
|
+
if (line.includes('}')) {
|
|
430
|
+
currentFunction.braceCount -= (line.match(/}/g) || []).length;
|
|
431
|
+
// Function ended
|
|
432
|
+
if (currentFunction.braceCount === 0) {
|
|
433
|
+
const lineCount = i - currentFunction.startLine;
|
|
434
|
+
if (lineCount > this.maxFunctionLength) {
|
|
435
|
+
longFunctions.push({
|
|
436
|
+
filepath: relative(process.cwd(), filepath),
|
|
437
|
+
functionName: currentFunction.name,
|
|
438
|
+
lineCount,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
currentFunction = null;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
// Skip files that can't be read
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Check naming conventions
|
|
453
|
+
*/
|
|
454
|
+
async checkNamingConventions(projectPath) {
|
|
455
|
+
const violations = [];
|
|
456
|
+
const srcDir = join(projectPath, 'src');
|
|
457
|
+
if (!existsSync(srcDir)) {
|
|
458
|
+
return violations;
|
|
459
|
+
}
|
|
460
|
+
await this.scanForNamingViolations(srcDir, violations);
|
|
461
|
+
return violations;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Scan directory for naming convention violations
|
|
465
|
+
*/
|
|
466
|
+
async scanForNamingViolations(dir, violations) {
|
|
467
|
+
try {
|
|
468
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
469
|
+
for (const entry of entries) {
|
|
470
|
+
const fullPath = join(dir, entry.name);
|
|
471
|
+
if (entry.isDirectory()) {
|
|
472
|
+
if (['node_modules', '.git', 'dist', 'build', 'coverage'].includes(entry.name)) {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
await this.scanForNamingViolations(fullPath, violations);
|
|
476
|
+
}
|
|
477
|
+
else if (entry.isFile()) {
|
|
478
|
+
const ext = extname(entry.name);
|
|
479
|
+
// Check file naming
|
|
480
|
+
if (['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {
|
|
481
|
+
const expectedPattern = /^(?:[a-z][a-z0-9-]*|[A-Z][a-zA-Z0-9]*)$/;
|
|
482
|
+
if (!expectedPattern.test(entry.name.replace(ext, '')) && !entry.name.includes('.test.') && !entry.name.includes('.spec.')) {
|
|
483
|
+
violations.push({
|
|
484
|
+
filepath: relative(process.cwd(), fullPath),
|
|
485
|
+
issue: `File name "${entry.name}" does not follow kebab-case or PascalCase conventions`,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
493
|
+
// Directory not accessible
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Identify quality issues from metrics
|
|
498
|
+
*/
|
|
499
|
+
identifyQualityIssues(eslintResults, complexityMetrics, duplications, longFunctions, namingViolations) {
|
|
500
|
+
const issues = [];
|
|
501
|
+
// ESLint issues
|
|
502
|
+
const totalErrors = eslintResults.reduce((sum, r) => sum + r.errorCount, 0);
|
|
503
|
+
const totalWarnings = eslintResults.reduce((sum, r) => sum + r.warningCount, 0);
|
|
504
|
+
if (totalErrors > 0) {
|
|
505
|
+
issues.push({
|
|
506
|
+
severity: 'high',
|
|
507
|
+
category: 'code-style',
|
|
508
|
+
description: `ESLint found ${totalErrors} error(s) and ${totalWarnings} warning(s)`,
|
|
509
|
+
fixable: eslintResults.reduce((sum, r) => sum + r.fixableErrorCount, 0) > 0,
|
|
510
|
+
suggestion: 'Run eslint with --fix to auto-fix some issues',
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
// Complexity issues
|
|
514
|
+
const highComplexityFiles = complexityMetrics.filter((m) => m.complexity > this.maxComplexity);
|
|
515
|
+
if (highComplexityFiles.length > 0) {
|
|
516
|
+
issues.push({
|
|
517
|
+
severity: 'medium',
|
|
518
|
+
category: 'code-style',
|
|
519
|
+
description: `${highComplexityFiles.length} file(s) exceed complexity threshold of ${this.maxComplexity}`,
|
|
520
|
+
fixable: false,
|
|
521
|
+
suggestion: 'Refactor complex functions into smaller, more manageable pieces',
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
// Duplication issues
|
|
525
|
+
if (duplications.length > 0) {
|
|
526
|
+
issues.push({
|
|
527
|
+
severity: 'medium',
|
|
528
|
+
category: 'code-style',
|
|
529
|
+
description: `Found ${duplications.length} duplicated code pattern(s)`,
|
|
530
|
+
fixable: false,
|
|
531
|
+
suggestion: 'Extract duplicated code into reusable functions or utilities',
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
// Long function issues
|
|
535
|
+
if (longFunctions.length > 0) {
|
|
536
|
+
issues.push({
|
|
537
|
+
severity: 'medium',
|
|
538
|
+
category: 'code-style',
|
|
539
|
+
description: `${longFunctions.length} function(s) exceed ${this.maxFunctionLength} lines`,
|
|
540
|
+
fixable: false,
|
|
541
|
+
suggestion: 'Break down long functions into smaller, single-purpose functions',
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
// Naming violations
|
|
545
|
+
if (namingViolations.length > 0) {
|
|
546
|
+
issues.push({
|
|
547
|
+
severity: 'low',
|
|
548
|
+
category: 'code-style',
|
|
549
|
+
description: `${namingViolations.length} file(s) have naming convention violations`,
|
|
550
|
+
fixable: true,
|
|
551
|
+
suggestion: 'Rename files to follow kebab-case or PascalCase conventions',
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
return issues;
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Generate improvement suggestions
|
|
558
|
+
*/
|
|
559
|
+
generateQualityImprovements(eslintResults, complexityMetrics, duplications, longFunctions) {
|
|
560
|
+
const improvements = [];
|
|
561
|
+
// ESLint improvements
|
|
562
|
+
if (eslintResults.length > 0) {
|
|
563
|
+
const fixableIssues = eslintResults.reduce((sum, r) => sum + r.fixableErrorCount + r.fixableWarningCount, 0);
|
|
564
|
+
if (fixableIssues > 0) {
|
|
565
|
+
improvements.push({
|
|
566
|
+
type: 'refactor',
|
|
567
|
+
description: `Run eslint --fix to resolve ${fixableIssues} auto-fixable issue(s)`,
|
|
568
|
+
effort: 'quick',
|
|
569
|
+
impact: 'medium',
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
// Complexity improvements
|
|
574
|
+
const highComplexityFiles = complexityMetrics.filter((m) => m.complexity > this.maxComplexity);
|
|
575
|
+
if (highComplexityFiles.length > 0) {
|
|
576
|
+
improvements.push({
|
|
577
|
+
type: 'refactor',
|
|
578
|
+
description: `Reduce complexity in ${highComplexityFiles.length} file(s) by extracting complex logic`,
|
|
579
|
+
effort: 'moderate',
|
|
580
|
+
impact: 'high',
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
// Duplication improvements
|
|
584
|
+
if (duplications.length > 0) {
|
|
585
|
+
improvements.push({
|
|
586
|
+
type: 'refactor',
|
|
587
|
+
description: `Eliminate ${duplications.length} code duplication pattern(s)`,
|
|
588
|
+
effort: 'moderate',
|
|
589
|
+
impact: 'medium',
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
// Long function improvements
|
|
593
|
+
if (longFunctions.length > 0 && longFunctions.length <= 5) {
|
|
594
|
+
improvements.push({
|
|
595
|
+
type: 'refactor',
|
|
596
|
+
description: 'Break down long functions into smaller components',
|
|
597
|
+
effort: 'moderate',
|
|
598
|
+
impact: 'medium',
|
|
599
|
+
steps: [
|
|
600
|
+
'Identify the core responsibility of each function',
|
|
601
|
+
'Extract auxiliary logic into separate functions',
|
|
602
|
+
'Consider using design patterns for complex logic',
|
|
603
|
+
],
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
return improvements;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Calculate final quality score
|
|
610
|
+
*/
|
|
611
|
+
calculateQualityScore(eslintResults, complexityMetrics, duplications, longFunctions) {
|
|
612
|
+
let score = 100;
|
|
613
|
+
// ESLint penalty
|
|
614
|
+
const totalErrors = eslintResults.reduce((sum, r) => sum + r.errorCount, 0);
|
|
615
|
+
const totalWarnings = eslintResults.reduce((sum, r) => sum + r.warningCount, 0);
|
|
616
|
+
score -= totalErrors * 2;
|
|
617
|
+
score -= totalWarnings * 0.5;
|
|
618
|
+
// Complexity penalty
|
|
619
|
+
const avgComplexity = this.calculateAverageComplexity(complexityMetrics);
|
|
620
|
+
if (avgComplexity > this.maxComplexity) {
|
|
621
|
+
score -= (avgComplexity - this.maxComplexity) * 3;
|
|
622
|
+
}
|
|
623
|
+
// Duplication penalty
|
|
624
|
+
score -= duplications.length * 2;
|
|
625
|
+
// Long function penalty
|
|
626
|
+
score -= longFunctions.length * 3;
|
|
627
|
+
return Math.max(0, Math.min(100, score));
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Calculate average complexity
|
|
631
|
+
*/
|
|
632
|
+
calculateAverageComplexity(metrics) {
|
|
633
|
+
if (metrics.length === 0)
|
|
634
|
+
return 0;
|
|
635
|
+
const total = metrics.reduce((sum, m) => sum + m.complexity, 0);
|
|
636
|
+
return total / metrics.length;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Count source files in project
|
|
640
|
+
*/
|
|
641
|
+
async countSourceFiles(projectPath) {
|
|
642
|
+
const srcDir = join(projectPath, 'src');
|
|
643
|
+
if (!existsSync(srcDir))
|
|
644
|
+
return 0;
|
|
645
|
+
let count = 0;
|
|
646
|
+
const extensions = ['.ts', '.tsx', '.js', '.jsx'];
|
|
647
|
+
async function countDir(dir) {
|
|
648
|
+
try {
|
|
649
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
650
|
+
for (const entry of entries) {
|
|
651
|
+
const fullPath = join(dir, entry.name);
|
|
652
|
+
if (entry.isDirectory()) {
|
|
653
|
+
if (!['node_modules', '.git', 'dist', 'build', 'coverage'].includes(entry.name)) {
|
|
654
|
+
await countDir(fullPath);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
else if (entry.isFile() && extensions.includes(extname(entry.name))) {
|
|
658
|
+
count++;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
catch {
|
|
663
|
+
// Ignore
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
await countDir(srcDir);
|
|
667
|
+
return count;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Count passed checks
|
|
671
|
+
*/
|
|
672
|
+
async countPassedChecks(eslintResults, complexityMetrics, duplications) {
|
|
673
|
+
const totalErrors = eslintResults.reduce((sum, r) => sum + r.errorCount, 0);
|
|
674
|
+
const highComplexity = complexityMetrics.filter((m) => m.complexity > this.maxComplexity).length;
|
|
675
|
+
const passedIssues = totalErrors === 0 ? 1 : 0;
|
|
676
|
+
const passedComplexity = highComplexity === 0 ? 1 : 0;
|
|
677
|
+
const passedDuplications = duplications.length === 0 ? 1 : 0;
|
|
678
|
+
return passedIssues + passedComplexity + passedDuplications;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Default analyzer instance
|
|
683
|
+
*/
|
|
684
|
+
export const codeQualityAnalyzer = new CodeQualityAnalyzer();
|
|
685
|
+
//# sourceMappingURL=code-quality.analyzer.js.map
|