tech-debt-score 0.1.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.
Files changed (135) hide show
  1. package/DEVELOPMENT.md +147 -0
  2. package/LICENSE +21 -0
  3. package/README.md +200 -0
  4. package/SETUP_COMPLETE.md +188 -0
  5. package/TECHNICAL_DESIGN.md +563 -0
  6. package/dist/adapters/input/FileSystemReader.d.ts +11 -0
  7. package/dist/adapters/input/FileSystemReader.d.ts.map +1 -0
  8. package/dist/adapters/input/FileSystemReader.js +41 -0
  9. package/dist/adapters/input/FileSystemReader.js.map +1 -0
  10. package/dist/adapters/input/TypeScriptParser.d.ts +17 -0
  11. package/dist/adapters/input/TypeScriptParser.d.ts.map +1 -0
  12. package/dist/adapters/input/TypeScriptParser.js +305 -0
  13. package/dist/adapters/input/TypeScriptParser.js.map +1 -0
  14. package/dist/adapters/output/JsonExporter.d.ts +11 -0
  15. package/dist/adapters/output/JsonExporter.d.ts.map +1 -0
  16. package/dist/adapters/output/JsonExporter.js +46 -0
  17. package/dist/adapters/output/JsonExporter.js.map +1 -0
  18. package/dist/adapters/output/TerminalReporter.d.ts +13 -0
  19. package/dist/adapters/output/TerminalReporter.d.ts.map +1 -0
  20. package/dist/adapters/output/TerminalReporter.js +82 -0
  21. package/dist/adapters/output/TerminalReporter.js.map +1 -0
  22. package/dist/application/config/AnalysisConfig.d.ts +36 -0
  23. package/dist/application/config/AnalysisConfig.d.ts.map +1 -0
  24. package/dist/application/config/AnalysisConfig.js +19 -0
  25. package/dist/application/config/AnalysisConfig.js.map +1 -0
  26. package/dist/application/ports/IFileReader.d.ts +32 -0
  27. package/dist/application/ports/IFileReader.d.ts.map +1 -0
  28. package/dist/application/ports/IFileReader.js +6 -0
  29. package/dist/application/ports/IFileReader.js.map +1 -0
  30. package/dist/application/ports/IParser.d.ts +35 -0
  31. package/dist/application/ports/IParser.d.ts.map +1 -0
  32. package/dist/application/ports/IParser.js +6 -0
  33. package/dist/application/ports/IParser.js.map +1 -0
  34. package/dist/application/ports/IReporter.d.ts +24 -0
  35. package/dist/application/ports/IReporter.d.ts.map +1 -0
  36. package/dist/application/ports/IReporter.js +6 -0
  37. package/dist/application/ports/IReporter.js.map +1 -0
  38. package/dist/application/services/AnalysisService.d.ts +31 -0
  39. package/dist/application/services/AnalysisService.d.ts.map +1 -0
  40. package/dist/application/services/AnalysisService.js +161 -0
  41. package/dist/application/services/AnalysisService.js.map +1 -0
  42. package/dist/application/services/DependencyAnalyzer.d.ts +29 -0
  43. package/dist/application/services/DependencyAnalyzer.d.ts.map +1 -0
  44. package/dist/application/services/DependencyAnalyzer.js +128 -0
  45. package/dist/application/services/DependencyAnalyzer.js.map +1 -0
  46. package/dist/application/services/DuplicationDetector.d.ts +46 -0
  47. package/dist/application/services/DuplicationDetector.d.ts.map +1 -0
  48. package/dist/application/services/DuplicationDetector.js +165 -0
  49. package/dist/application/services/DuplicationDetector.js.map +1 -0
  50. package/dist/cli/commands/analyze.d.ts +2 -0
  51. package/dist/cli/commands/analyze.d.ts.map +1 -0
  52. package/dist/cli/commands/analyze.js +58 -0
  53. package/dist/cli/commands/analyze.js.map +1 -0
  54. package/dist/cli/index.d.ts +9 -0
  55. package/dist/cli/index.d.ts.map +1 -0
  56. package/dist/cli/index.js +76 -0
  57. package/dist/cli/index.js.map +1 -0
  58. package/dist/domain/entities/Finding.d.ts +42 -0
  59. package/dist/domain/entities/Finding.d.ts.map +1 -0
  60. package/dist/domain/entities/Finding.js +40 -0
  61. package/dist/domain/entities/Finding.js.map +1 -0
  62. package/dist/domain/entities/Metric.d.ts +38 -0
  63. package/dist/domain/entities/Metric.d.ts.map +1 -0
  64. package/dist/domain/entities/Metric.js +36 -0
  65. package/dist/domain/entities/Metric.js.map +1 -0
  66. package/dist/domain/entities/Rule.d.ts +41 -0
  67. package/dist/domain/entities/Rule.d.ts.map +1 -0
  68. package/dist/domain/entities/Rule.js +15 -0
  69. package/dist/domain/entities/Rule.js.map +1 -0
  70. package/dist/domain/entities/Score.d.ts +62 -0
  71. package/dist/domain/entities/Score.d.ts.map +1 -0
  72. package/dist/domain/entities/Score.js +40 -0
  73. package/dist/domain/entities/Score.js.map +1 -0
  74. package/dist/domain/index.d.ts +12 -0
  75. package/dist/domain/index.d.ts.map +1 -0
  76. package/dist/domain/index.js +14 -0
  77. package/dist/domain/index.js.map +1 -0
  78. package/dist/domain/rules/CircularDependencyRule.d.ts +13 -0
  79. package/dist/domain/rules/CircularDependencyRule.d.ts.map +1 -0
  80. package/dist/domain/rules/CircularDependencyRule.js +47 -0
  81. package/dist/domain/rules/CircularDependencyRule.js.map +1 -0
  82. package/dist/domain/rules/ComplexityRule.d.ts +15 -0
  83. package/dist/domain/rules/ComplexityRule.d.ts.map +1 -0
  84. package/dist/domain/rules/ComplexityRule.js +63 -0
  85. package/dist/domain/rules/ComplexityRule.js.map +1 -0
  86. package/dist/domain/rules/DuplicationRule.d.ts +15 -0
  87. package/dist/domain/rules/DuplicationRule.d.ts.map +1 -0
  88. package/dist/domain/rules/DuplicationRule.js +51 -0
  89. package/dist/domain/rules/DuplicationRule.js.map +1 -0
  90. package/dist/domain/rules/SizeRule.d.ts +16 -0
  91. package/dist/domain/rules/SizeRule.d.ts.map +1 -0
  92. package/dist/domain/rules/SizeRule.js +70 -0
  93. package/dist/domain/rules/SizeRule.js.map +1 -0
  94. package/dist/domain/rules/TypeSafetyRule.d.ts +13 -0
  95. package/dist/domain/rules/TypeSafetyRule.d.ts.map +1 -0
  96. package/dist/domain/rules/TypeSafetyRule.js +45 -0
  97. package/dist/domain/rules/TypeSafetyRule.js.map +1 -0
  98. package/dist/index.d.ts +2 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +2 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/shared/types.d.ts +16 -0
  103. package/dist/shared/types.d.ts.map +1 -0
  104. package/dist/shared/types.js +5 -0
  105. package/dist/shared/types.js.map +1 -0
  106. package/package.json +47 -0
  107. package/src/adapters/input/FileSystemReader.ts +51 -0
  108. package/src/adapters/input/TypeScriptParser.ts +367 -0
  109. package/src/adapters/output/JsonExporter.ts +48 -0
  110. package/src/adapters/output/TerminalReporter.ts +88 -0
  111. package/src/application/config/AnalysisConfig.ts +50 -0
  112. package/src/application/ports/IFileReader.ts +36 -0
  113. package/src/application/ports/IParser.ts +40 -0
  114. package/src/application/ports/IReporter.ts +26 -0
  115. package/src/application/services/AnalysisService.ts +199 -0
  116. package/src/application/services/DependencyAnalyzer.ts +158 -0
  117. package/src/application/services/DuplicationDetector.ts +207 -0
  118. package/src/cli/commands/analyze.ts +76 -0
  119. package/src/cli/index.ts +81 -0
  120. package/src/domain/entities/Finding.ts +79 -0
  121. package/src/domain/entities/Metric.ts +70 -0
  122. package/src/domain/entities/Rule.ts +49 -0
  123. package/src/domain/entities/Score.ts +94 -0
  124. package/src/domain/index.ts +15 -0
  125. package/src/domain/rules/CircularDependencyRule.ts +65 -0
  126. package/src/domain/rules/ComplexityRule.ts +88 -0
  127. package/src/domain/rules/DuplicationRule.ts +70 -0
  128. package/src/domain/rules/SizeRule.ts +98 -0
  129. package/src/domain/rules/TypeSafetyRule.ts +63 -0
  130. package/src/index.ts +0 -0
  131. package/src/shared/types.ts +18 -0
  132. package/tests/application/index.test.ts +12 -0
  133. package/tests/domain/index.test.ts +14 -0
  134. package/tests/e2e/index.test.ts +13 -0
  135. package/tsconfig.json +31 -0
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Application Port: Reporter Interface
3
+ * Output adapter interface for reporting analysis results
4
+ */
5
+ import type { Score } from '../../domain/entities/Score.js';
6
+ import type { Finding } from '../../domain/entities/Finding.js';
7
+ export interface AnalysisReport {
8
+ score: Score;
9
+ findings: Finding[];
10
+ metadata: {
11
+ filesAnalyzed: number;
12
+ duration: number;
13
+ timestamp: Date;
14
+ };
15
+ }
16
+ export interface IReporter {
17
+ /**
18
+ * Generate and output a report
19
+ *
20
+ * @param report - The analysis report data
21
+ */
22
+ generate(report: AnalysisReport): Promise<void>;
23
+ }
24
+ //# sourceMappingURL=IReporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IReporter.d.ts","sourceRoot":"","sources":["../../../src/application/ports/IReporter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAEhE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,QAAQ,EAAE;QACR,aAAa,EAAE,MAAM,CAAC;QACtB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,IAAI,CAAC;KACjB,CAAC;CACH;AAED,MAAM,WAAW,SAAS;IACxB;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjD"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Application Port: Reporter Interface
3
+ * Output adapter interface for reporting analysis results
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=IReporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IReporter.js","sourceRoot":"","sources":["../../../src/application/ports/IReporter.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Application Service: Analysis Service
3
+ * Orchestrates the entire code analysis workflow
4
+ */
5
+ import type { IFileReader } from '../ports/IFileReader.js';
6
+ import type { IParser } from '../ports/IParser.js';
7
+ import type { IReporter, AnalysisReport } from '../ports/IReporter.js';
8
+ import type { AnalysisConfig } from '../config/AnalysisConfig.js';
9
+ import type { Rule } from '../../domain/entities/Rule.js';
10
+ export declare class AnalysisService {
11
+ private readonly fileReader;
12
+ private readonly parser;
13
+ private readonly rules;
14
+ private readonly reporter;
15
+ constructor(fileReader: IFileReader, parser: IParser, rules: Rule[], reporter: IReporter);
16
+ /**
17
+ * Run the complete analysis workflow
18
+ */
19
+ analyze(config: AnalysisConfig): Promise<AnalysisReport>;
20
+ /**
21
+ * Analyze dependencies and code duplication
22
+ */
23
+ private analyzeAdvancedMetrics;
24
+ private getCategoryWeight;
25
+ /**
26
+ * Normalize weights so they sum to 1.0
27
+ * This handles cases where not all categories are active
28
+ */
29
+ private normalizeWeights;
30
+ }
31
+ //# sourceMappingURL=AnalysisService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnalysisService.d.ts","sourceRoot":"","sources":["../../../src/application/services/AnalysisService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,+BAA+B,CAAC;AAM1D,qBAAa,eAAe;IAExB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAHR,UAAU,EAAE,WAAW,EACvB,MAAM,EAAE,OAAO,EACf,KAAK,EAAE,IAAI,EAAE,EACb,QAAQ,EAAE,SAAS;IAGtC;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;IA8F9D;;OAEG;YACW,sBAAsB;IA4BpC,OAAO,CAAC,iBAAiB;IAyBzB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;CAkBzB"}
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Application Service: Analysis Service
3
+ * Orchestrates the entire code analysis workflow
4
+ */
5
+ import { ScoreCalculator } from '../../domain/entities/Score.js';
6
+ export class AnalysisService {
7
+ constructor(fileReader, parser, rules, reporter) {
8
+ this.fileReader = fileReader;
9
+ this.parser = parser;
10
+ this.rules = rules;
11
+ this.reporter = reporter;
12
+ }
13
+ /**
14
+ * Run the complete analysis workflow
15
+ */
16
+ async analyze(config) {
17
+ const startTime = Date.now();
18
+ // 1. Scan for files
19
+ console.log('📁 Scanning files...');
20
+ const filePaths = await this.fileReader.scan(config.rootPath, config.patterns, config.ignore);
21
+ console.log(` Found ${filePaths.length} files`);
22
+ // 2. Read and parse files
23
+ console.log('🔍 Parsing files...');
24
+ const allMetrics = [];
25
+ const fileContents = new Map();
26
+ for (const filePath of filePaths) {
27
+ const fileResult = await this.fileReader.read(filePath);
28
+ fileContents.set(filePath, fileResult.content);
29
+ if (this.parser.supports(filePath)) {
30
+ const parseResult = await this.parser.parse(filePath, fileResult.content);
31
+ if (parseResult.success) {
32
+ allMetrics.push(...parseResult.metrics);
33
+ }
34
+ else {
35
+ console.warn(` ⚠️ Failed to parse ${filePath}: ${parseResult.error}`);
36
+ }
37
+ }
38
+ }
39
+ console.log(` Extracted ${allMetrics.length} metrics`);
40
+ // 3. Analyze dependencies and duplication
41
+ console.log('🔗 Analyzing dependencies and duplication...');
42
+ const additionalMetrics = await this.analyzeAdvancedMetrics(fileContents);
43
+ allMetrics.push(...additionalMetrics);
44
+ console.log(` Added ${additionalMetrics.length} advanced metrics`);
45
+ // 4. Apply rules and collect findings
46
+ console.log('📊 Applying rules...');
47
+ const allFindings = [];
48
+ const rawCategoryScores = [];
49
+ for (const rule of this.rules) {
50
+ const findings = rule.evaluate(allMetrics);
51
+ allFindings.push(...findings);
52
+ const score = rule.calculateScore(findings);
53
+ rawCategoryScores.push({
54
+ name: rule.name,
55
+ score,
56
+ weight: this.getCategoryWeight(rule.id, config),
57
+ description: rule.description,
58
+ });
59
+ }
60
+ console.log(` Generated ${allFindings.length} findings`);
61
+ // Normalize weights to sum to 1.0 (in case not all categories are active)
62
+ const categoryScores = this.normalizeWeights(rawCategoryScores);
63
+ // 5. Calculate overall score
64
+ console.log('🎯 Calculating score...');
65
+ const overallScore = ScoreCalculator.calculateOverall(categoryScores);
66
+ const finalScore = {
67
+ overall: overallScore,
68
+ categories: categoryScores,
69
+ timestamp: new Date(),
70
+ metadata: {
71
+ filesAnalyzed: filePaths.length,
72
+ totalMetrics: allMetrics.length,
73
+ totalFindings: allFindings.length,
74
+ },
75
+ };
76
+ // 6. Prepare report
77
+ const duration = Date.now() - startTime;
78
+ const report = {
79
+ score: finalScore,
80
+ findings: allFindings,
81
+ metadata: {
82
+ filesAnalyzed: filePaths.length,
83
+ duration,
84
+ timestamp: new Date(),
85
+ },
86
+ };
87
+ // 7. Generate output
88
+ console.log('📋 Generating report...');
89
+ await this.reporter.generate(report);
90
+ return report;
91
+ }
92
+ /**
93
+ * Analyze dependencies and code duplication
94
+ */
95
+ async analyzeAdvancedMetrics(fileContents) {
96
+ const { DependencyAnalyzer } = await import('./DependencyAnalyzer.js');
97
+ const { DuplicationDetector } = await import('./DuplicationDetector.js');
98
+ const depAnalyzer = new DependencyAnalyzer();
99
+ const dupDetector = new DuplicationDetector();
100
+ const metrics = [];
101
+ // Analyze all files
102
+ for (const [filePath, content] of fileContents.entries()) {
103
+ if (filePath.endsWith('.ts') || filePath.endsWith('.tsx') ||
104
+ filePath.endsWith('.js') || filePath.endsWith('.jsx')) {
105
+ depAnalyzer.analyzeDependencies(filePath, content);
106
+ dupDetector.analyzeFile(filePath, content);
107
+ }
108
+ }
109
+ // Detect circular dependencies
110
+ const circularDeps = depAnalyzer.detectCircularDependencies();
111
+ metrics.push(...circularDeps);
112
+ // Detect code duplication
113
+ const duplicates = dupDetector.detectDuplicates();
114
+ metrics.push(...duplicates);
115
+ return metrics;
116
+ }
117
+ getCategoryWeight(ruleId, config) {
118
+ const weights = config.weights ?? {
119
+ complexity: 0.30,
120
+ size: 0.25,
121
+ typeSafety: 0.20,
122
+ codeQuality: 0.15,
123
+ structure: 0.10,
124
+ };
125
+ switch (ruleId) {
126
+ case 'complexity':
127
+ return weights.complexity;
128
+ case 'size':
129
+ return weights.size;
130
+ case 'type-safety':
131
+ return weights.typeSafety;
132
+ case 'duplication':
133
+ return weights.codeQuality;
134
+ case 'circular-dependency':
135
+ return weights.structure;
136
+ default:
137
+ return 0.05; // Default weight for unknown categories
138
+ }
139
+ }
140
+ /**
141
+ * Normalize weights so they sum to 1.0
142
+ * This handles cases where not all categories are active
143
+ */
144
+ normalizeWeights(categories) {
145
+ const totalWeight = categories.reduce((sum, cat) => sum + cat.weight, 0);
146
+ if (totalWeight === 0) {
147
+ // If all weights are 0, distribute evenly
148
+ const evenWeight = 1.0 / categories.length;
149
+ return categories.map(cat => ({
150
+ ...cat,
151
+ weight: evenWeight,
152
+ }));
153
+ }
154
+ // Normalize proportionally
155
+ return categories.map(cat => ({
156
+ ...cat,
157
+ weight: cat.weight / totalWeight,
158
+ }));
159
+ }
160
+ }
161
+ //# sourceMappingURL=AnalysisService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnalysisService.js","sourceRoot":"","sources":["../../../src/application/services/AnalysisService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Application Service: Dependency Analyzer
3
+ * Analyzes import/require statements to detect circular dependencies
4
+ */
5
+ import type { Metric } from '../../domain/entities/Metric.js';
6
+ export declare class DependencyAnalyzer {
7
+ private dependencyGraph;
8
+ /**
9
+ * Analyze files for import statements and build dependency graph
10
+ */
11
+ analyzeDependencies(filePath: string, content: string): void;
12
+ /**
13
+ * Detect circular dependencies using DFS
14
+ */
15
+ detectCircularDependencies(): Metric[];
16
+ /**
17
+ * Resolve relative import to absolute path
18
+ */
19
+ private resolveImportPath;
20
+ /**
21
+ * Get file name from path for display
22
+ */
23
+ private getFileName;
24
+ /**
25
+ * Clear the dependency graph (useful for new analysis)
26
+ */
27
+ clear(): void;
28
+ }
29
+ //# sourceMappingURL=DependencyAnalyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DependencyAnalyzer.d.ts","sourceRoot":"","sources":["../../../src/application/services/DependencyAnalyzer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iCAAiC,CAAC;AAQ9D,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,eAAe,CAA0C;IAEjE;;OAEG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAgC5D;;OAEG;IACH,0BAA0B,IAAI,MAAM,EAAE;IA0DtC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA0BzB;;OAEG;IACH,OAAO,CAAC,WAAW;IAKnB;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Application Service: Dependency Analyzer
3
+ * Analyzes import/require statements to detect circular dependencies
4
+ */
5
+ import * as ts from 'typescript';
6
+ import { resolve, dirname, join } from 'node:path';
7
+ import { MetricBuilder } from '../../domain/entities/Metric.js';
8
+ export class DependencyAnalyzer {
9
+ constructor() {
10
+ this.dependencyGraph = new Map();
11
+ }
12
+ /**
13
+ * Analyze files for import statements and build dependency graph
14
+ */
15
+ analyzeDependencies(filePath, content) {
16
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
17
+ const imports = [];
18
+ // Extract import statements
19
+ const visit = (node) => {
20
+ if (ts.isImportDeclaration(node)) {
21
+ const moduleSpecifier = node.moduleSpecifier;
22
+ if (ts.isStringLiteral(moduleSpecifier)) {
23
+ const importPath = this.resolveImportPath(filePath, moduleSpecifier.text);
24
+ if (importPath) {
25
+ imports.push(importPath);
26
+ }
27
+ }
28
+ }
29
+ ts.forEachChild(node, visit);
30
+ };
31
+ visit(sourceFile);
32
+ this.dependencyGraph.set(filePath, {
33
+ filePath,
34
+ imports,
35
+ });
36
+ }
37
+ /**
38
+ * Detect circular dependencies using DFS
39
+ */
40
+ detectCircularDependencies() {
41
+ const metrics = [];
42
+ const visited = new Set();
43
+ const recursionStack = new Set();
44
+ const cycles = new Set(); // Track unique cycles
45
+ const dfs = (filePath, path) => {
46
+ if (cycles.has(filePath))
47
+ return; // Already found this cycle
48
+ visited.add(filePath);
49
+ recursionStack.add(filePath);
50
+ const node = this.dependencyGraph.get(filePath);
51
+ if (!node) {
52
+ recursionStack.delete(filePath);
53
+ return;
54
+ }
55
+ for (const importPath of node.imports) {
56
+ if (!visited.has(importPath)) {
57
+ dfs(importPath, [...path, filePath]);
58
+ }
59
+ else if (recursionStack.has(importPath)) {
60
+ // Found a cycle!
61
+ const cycleStart = path.indexOf(importPath);
62
+ if (cycleStart !== -1) {
63
+ const cycle = [...path.slice(cycleStart), importPath];
64
+ const cycleKey = [...cycle].sort().join('->');
65
+ if (!cycles.has(cycleKey)) {
66
+ cycles.add(cycleKey);
67
+ // Create metric for this cycle
68
+ metrics.push(new MetricBuilder()
69
+ .withName('circular-dependency')
70
+ .withValue(cycle.length)
71
+ .withFilePath(filePath)
72
+ .withContext(cycle.map(p => this.getFileName(p)).join(' → '))
73
+ .build());
74
+ }
75
+ }
76
+ }
77
+ }
78
+ recursionStack.delete(filePath);
79
+ };
80
+ // Check all nodes for cycles
81
+ for (const filePath of this.dependencyGraph.keys()) {
82
+ if (!visited.has(filePath)) {
83
+ dfs(filePath, []);
84
+ }
85
+ }
86
+ return metrics;
87
+ }
88
+ /**
89
+ * Resolve relative import to absolute path
90
+ */
91
+ resolveImportPath(fromFile, importSpecifier) {
92
+ // Skip node_modules and external packages
93
+ if (!importSpecifier.startsWith('.')) {
94
+ return null;
95
+ }
96
+ try {
97
+ const dir = dirname(fromFile);
98
+ let resolvedPath = resolve(dir, importSpecifier);
99
+ // Try adding extensions if file doesn't exist
100
+ const extensions = ['.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.js'];
101
+ for (const ext of extensions) {
102
+ const pathWithExt = resolvedPath + ext;
103
+ // We can't check if file exists in this context, so just normalize
104
+ if (ext.startsWith('/')) {
105
+ return resolvedPath + ext;
106
+ }
107
+ }
108
+ return resolvedPath + '.ts'; // Default to .ts
109
+ }
110
+ catch {
111
+ return null;
112
+ }
113
+ }
114
+ /**
115
+ * Get file name from path for display
116
+ */
117
+ getFileName(filePath) {
118
+ const parts = filePath.split('/');
119
+ return parts[parts.length - 1] ?? filePath;
120
+ }
121
+ /**
122
+ * Clear the dependency graph (useful for new analysis)
123
+ */
124
+ clear() {
125
+ this.dependencyGraph.clear();
126
+ }
127
+ }
128
+ //# sourceMappingURL=DependencyAnalyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DependencyAnalyzer.js","sourceRoot":"","sources":["../../../src/application/services/DependencyAnalyzer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Application Service: Code Duplication Detector
3
+ * Detects duplicate code blocks using token-based similarity
4
+ */
5
+ import type { Metric } from '../../domain/entities/Metric.js';
6
+ export declare class DuplicationDetector {
7
+ private codeBlocks;
8
+ private readonly MIN_BLOCK_SIZE;
9
+ /**
10
+ * Analyze file for code blocks
11
+ */
12
+ analyzeFile(filePath: string, content: string): void;
13
+ /**
14
+ * Detect duplicates across all analyzed files
15
+ */
16
+ detectDuplicates(): Metric[];
17
+ /**
18
+ * Extract code block from function node
19
+ */
20
+ private extractCodeBlock;
21
+ /**
22
+ * Simple tokenization (normalize whitespace)
23
+ */
24
+ private tokenize;
25
+ /**
26
+ * Calculate similarity between two token strings
27
+ */
28
+ private calculateSimilarity;
29
+ /**
30
+ * Calculate Levenshtein distance
31
+ */
32
+ private levenshteinDistance;
33
+ /**
34
+ * Check if node is a function
35
+ */
36
+ private isFunctionNode;
37
+ /**
38
+ * Get function name for context
39
+ */
40
+ private getFunctionName;
41
+ /**
42
+ * Clear stored blocks
43
+ */
44
+ clear(): void;
45
+ }
46
+ //# sourceMappingURL=DuplicationDetector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DuplicationDetector.d.ts","sourceRoot":"","sources":["../../../src/application/services/DuplicationDetector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iCAAiC,CAAC;AAW9D,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAM;IAErC;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAsBpD;;OAEG;IACH,gBAAgB,IAAI,MAAM,EAAE;IA+C5B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAqBxB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAQhB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAc3B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA2B3B;;OAEG;IACH,OAAO,CAAC,cAAc;IAOtB;;OAEG;IACH,OAAO,CAAC,eAAe;IASvB;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Application Service: Code Duplication Detector
3
+ * Detects duplicate code blocks using token-based similarity
4
+ */
5
+ import * as ts from 'typescript';
6
+ import { MetricBuilder } from '../../domain/entities/Metric.js';
7
+ export class DuplicationDetector {
8
+ constructor() {
9
+ this.codeBlocks = [];
10
+ this.MIN_BLOCK_SIZE = 50; // Minimum token count
11
+ }
12
+ /**
13
+ * Analyze file for code blocks
14
+ */
15
+ analyzeFile(filePath, content) {
16
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
17
+ // Extract function bodies as code blocks
18
+ const visit = (node) => {
19
+ if (this.isFunctionNode(node)) {
20
+ const block = this.extractCodeBlock(sourceFile, filePath, node);
21
+ if (block && block.tokens.length >= this.MIN_BLOCK_SIZE) {
22
+ this.codeBlocks.push(block);
23
+ }
24
+ }
25
+ ts.forEachChild(node, visit);
26
+ };
27
+ visit(sourceFile);
28
+ }
29
+ /**
30
+ * Detect duplicates across all analyzed files
31
+ */
32
+ detectDuplicates() {
33
+ const metrics = [];
34
+ const seen = new Set();
35
+ for (let i = 0; i < this.codeBlocks.length; i++) {
36
+ if (seen.has(i))
37
+ continue;
38
+ const block1 = this.codeBlocks[i];
39
+ if (!block1)
40
+ continue;
41
+ let duplicateCount = 0;
42
+ for (let j = i + 1; j < this.codeBlocks.length; j++) {
43
+ if (seen.has(j))
44
+ continue;
45
+ const block2 = this.codeBlocks[j];
46
+ if (!block2)
47
+ continue;
48
+ const similarity = this.calculateSimilarity(block1.tokens, block2.tokens);
49
+ if (similarity >= 0.85) { // 85% similarity threshold
50
+ duplicateCount++;
51
+ seen.add(j);
52
+ }
53
+ }
54
+ if (duplicateCount > 0) {
55
+ metrics.push(new MetricBuilder()
56
+ .withName('code-duplication')
57
+ .withValue(duplicateCount)
58
+ .withFilePath(block1.filePath)
59
+ .withContext(block1.functionName || 'code block')
60
+ .withLocation({
61
+ startLine: block1.startLine,
62
+ endLine: block1.endLine,
63
+ startColumn: 0,
64
+ endColumn: 0,
65
+ })
66
+ .build());
67
+ }
68
+ }
69
+ return metrics;
70
+ }
71
+ /**
72
+ * Extract code block from function node
73
+ */
74
+ extractCodeBlock(sourceFile, filePath, node) {
75
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
76
+ const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
77
+ // Tokenize the code (simple whitespace normalization)
78
+ const text = node.getText(sourceFile);
79
+ const tokens = this.tokenize(text);
80
+ return {
81
+ filePath,
82
+ tokens,
83
+ startLine: start.line + 1,
84
+ endLine: end.line + 1,
85
+ functionName: this.getFunctionName(node),
86
+ };
87
+ }
88
+ /**
89
+ * Simple tokenization (normalize whitespace)
90
+ */
91
+ tokenize(code) {
92
+ return code
93
+ .replace(/\s+/g, ' ') // Normalize whitespace
94
+ .replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments
95
+ .replace(/\/\/.*/g, '') // Remove line comments
96
+ .trim();
97
+ }
98
+ /**
99
+ * Calculate similarity between two token strings
100
+ */
101
+ calculateSimilarity(tokens1, tokens2) {
102
+ if (tokens1 === tokens2)
103
+ return 1.0;
104
+ // Use Levenshtein distance for similarity
105
+ const len1 = tokens1.length;
106
+ const len2 = tokens2.length;
107
+ const maxLen = Math.max(len1, len2);
108
+ if (maxLen === 0)
109
+ return 1.0;
110
+ const distance = this.levenshteinDistance(tokens1, tokens2);
111
+ return 1 - distance / maxLen;
112
+ }
113
+ /**
114
+ * Calculate Levenshtein distance
115
+ */
116
+ levenshteinDistance(str1, str2) {
117
+ const len1 = str1.length;
118
+ const len2 = str2.length;
119
+ const matrix = [];
120
+ for (let i = 0; i <= len1; i++) {
121
+ matrix[i] = [i];
122
+ }
123
+ for (let j = 0; j <= len2; j++) {
124
+ matrix[0][j] = j;
125
+ }
126
+ for (let i = 1; i <= len1; i++) {
127
+ for (let j = 1; j <= len2; j++) {
128
+ const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
129
+ matrix[i][j] = Math.min(matrix[i - 1][j] + 1, // deletion
130
+ matrix[i][j - 1] + 1, // insertion
131
+ matrix[i - 1][j - 1] + cost // substitution
132
+ );
133
+ }
134
+ }
135
+ return matrix[len1][len2];
136
+ }
137
+ /**
138
+ * Check if node is a function
139
+ */
140
+ isFunctionNode(node) {
141
+ return ts.isFunctionDeclaration(node) ||
142
+ ts.isMethodDeclaration(node) ||
143
+ ts.isArrowFunction(node) ||
144
+ ts.isFunctionExpression(node);
145
+ }
146
+ /**
147
+ * Get function name for context
148
+ */
149
+ getFunctionName(node) {
150
+ if (ts.isFunctionDeclaration(node) && node.name) {
151
+ return node.name.getText();
152
+ }
153
+ else if (ts.isMethodDeclaration(node) && node.name) {
154
+ return node.name.getText();
155
+ }
156
+ return undefined;
157
+ }
158
+ /**
159
+ * Clear stored blocks
160
+ */
161
+ clear() {
162
+ this.codeBlocks = [];
163
+ }
164
+ }
165
+ //# sourceMappingURL=DuplicationDetector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DuplicationDetector.js","sourceRoot":"","sources":["../../../src/application/services/DuplicationDetector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function analyzeCommand(rootPath: string, jsonOutputPath?: string): Promise<void>;
2
+ //# sourceMappingURL=analyze.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/analyze.ts"],"names":[],"mappings":"AAyBA,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkD7F"}