@sun-asterisk/sunlint 1.3.34 → 1.3.35
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/core/architecture-integration.js +16 -7
- package/core/auto-performance-manager.js +1 -1
- package/core/cli-action-handler.js +92 -2
- package/core/cli-program.js +96 -138
- package/core/file-targeting-service.js +62 -4
- package/core/git-utils.js +19 -12
- package/core/github-annotate-service.js +326 -11
- package/core/html-report-generator.js +326 -731
- package/core/impact-integration.js +433 -0
- package/core/output-service.js +293 -21
- package/core/scoring-service.js +3 -2
- package/engines/arch-detect/core/analyzer.js +413 -0
- package/engines/arch-detect/core/index.js +22 -0
- package/engines/arch-detect/engine/hybrid-detector.js +176 -0
- package/engines/arch-detect/engine/index.js +24 -0
- package/engines/arch-detect/engine/rule-executor.js +228 -0
- package/engines/arch-detect/engine/score-calculator.js +214 -0
- package/engines/arch-detect/engine/violation-detector.js +616 -0
- package/engines/arch-detect/index.js +50 -0
- package/engines/arch-detect/rules/base-rule.js +187 -0
- package/engines/arch-detect/rules/index.js +35 -0
- package/engines/arch-detect/rules/layered/index.js +28 -0
- package/engines/arch-detect/rules/layered/l001-presentation-layer.js +237 -0
- package/engines/arch-detect/rules/layered/l002-business-layer.js +215 -0
- package/engines/arch-detect/rules/layered/l003-data-layer.js +229 -0
- package/engines/arch-detect/rules/layered/l004-model-layer.js +204 -0
- package/engines/arch-detect/rules/layered/l005-layer-separation.js +215 -0
- package/engines/arch-detect/rules/layered/l006-dependency-direction.js +221 -0
- package/engines/arch-detect/rules/layered/layered-rules-collection.js +445 -0
- package/engines/arch-detect/rules/modular/index.js +27 -0
- package/engines/arch-detect/rules/modular/m001-feature-modules.js +238 -0
- package/engines/arch-detect/rules/modular/m002-core-module.js +169 -0
- package/engines/arch-detect/rules/modular/m003-module-declaration.js +186 -0
- package/engines/arch-detect/rules/modular/m004-public-api.js +171 -0
- package/engines/arch-detect/rules/modular/m005-no-deep-imports.js +220 -0
- package/engines/arch-detect/rules/modular/modular-rules-collection.js +357 -0
- package/engines/arch-detect/rules/presentation/index.js +27 -0
- package/engines/arch-detect/rules/presentation/pr001-view-layer.js +221 -0
- package/engines/arch-detect/rules/presentation/pr002-presentation-logic.js +192 -0
- package/engines/arch-detect/rules/presentation/pr004-data-binding.js +187 -0
- package/engines/arch-detect/rules/presentation/pr006-router-layer.js +185 -0
- package/engines/arch-detect/rules/presentation/pr007-interactor-layer.js +181 -0
- package/engines/arch-detect/rules/presentation/presentation-rules-collection.js +507 -0
- package/engines/arch-detect/rules/project-scanner/index.js +31 -0
- package/engines/arch-detect/rules/project-scanner/ps001-project-root.js +213 -0
- package/engines/arch-detect/rules/project-scanner/ps002-language-detection.js +192 -0
- package/engines/arch-detect/rules/project-scanner/ps003-framework-detection.js +339 -0
- package/engines/arch-detect/rules/project-scanner/ps004-build-system.js +171 -0
- package/engines/arch-detect/rules/project-scanner/ps005-source-directory.js +163 -0
- package/engines/arch-detect/rules/project-scanner/ps006-test-directory.js +184 -0
- package/engines/arch-detect/rules/project-scanner/ps007-documentation.js +149 -0
- package/engines/arch-detect/rules/project-scanner/ps008-cicd-detection.js +163 -0
- package/engines/arch-detect/rules/project-scanner/ps009-code-quality.js +152 -0
- package/engines/arch-detect/rules/project-scanner/ps010-statistics.js +180 -0
- package/engines/arch-detect/rules/rule-registry.js +111 -0
- package/engines/arch-detect/types/context.types.js +60 -0
- package/engines/arch-detect/types/enums.js +161 -0
- package/engines/arch-detect/types/index.js +25 -0
- package/engines/arch-detect/types/result.types.js +7 -0
- package/engines/arch-detect/types/rule.types.js +7 -0
- package/engines/arch-detect/utils/file-scanner.js +411 -0
- package/engines/arch-detect/utils/index.js +23 -0
- package/engines/arch-detect/utils/pattern-matcher.js +328 -0
- package/engines/impact/cli.js +106 -0
- package/engines/impact/config/default-config.js +54 -0
- package/engines/impact/core/change-detector.js +258 -0
- package/engines/impact/core/detectors/database-detector.js +1317 -0
- package/engines/impact/core/detectors/endpoint-detector.js +55 -0
- package/engines/impact/core/impact-analyzer.js +124 -0
- package/engines/impact/core/report-generator.js +462 -0
- package/engines/impact/core/utils/ast-parser.js +241 -0
- package/engines/impact/core/utils/dependency-graph.js +159 -0
- package/engines/impact/core/utils/file-utils.js +116 -0
- package/engines/impact/core/utils/git-utils.js +203 -0
- package/engines/impact/core/utils/logger.js +13 -0
- package/engines/impact/core/utils/method-call-graph.js +1192 -0
- package/engines/impact/index.js +135 -0
- package/engines/impact/package.json +29 -0
- package/package.json +18 -43
- package/scripts/build-release.sh +0 -0
- package/scripts/copy-impact-analyzer.js +135 -0
- package/scripts/install.sh +0 -0
- package/scripts/manual-release.sh +0 -0
- package/scripts/pre-release-test.sh +0 -0
- package/scripts/prepare-release.sh +0 -0
- package/scripts/quick-performance-test.js +0 -0
- package/scripts/setup-github-registry.sh +0 -0
- package/scripts/trigger-release.sh +0 -0
- package/scripts/verify-install.sh +0 -0
- package/templates/combined-report.html +1418 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Architecture Analyzer
|
|
4
|
+
* Core orchestrator cho việc phân tích kiến trúc project
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.ArchitectureAnalyzer = void 0;
|
|
41
|
+
const path = __importStar(require("node:path"));
|
|
42
|
+
const hybrid_detector_1 = require("../engine/hybrid-detector");
|
|
43
|
+
const rule_executor_1 = require("../engine/rule-executor");
|
|
44
|
+
const violation_detector_1 = require("../engine/violation-detector");
|
|
45
|
+
const rule_registry_1 = require("../rules/rule-registry");
|
|
46
|
+
const types_1 = require("../types");
|
|
47
|
+
const enums_1 = require("../types/enums");
|
|
48
|
+
const file_scanner_1 = require("../utils/file-scanner");
|
|
49
|
+
// Import all rules to register them
|
|
50
|
+
require("../rules/project-scanner");
|
|
51
|
+
require("../rules/layered");
|
|
52
|
+
require("../rules/modular");
|
|
53
|
+
require("../rules/presentation");
|
|
54
|
+
class ArchitectureAnalyzer {
|
|
55
|
+
constructor(options = {}) {
|
|
56
|
+
this.ruleExecutor = new rule_executor_1.RuleExecutor();
|
|
57
|
+
this.hybridDetector = new hybrid_detector_1.HybridDetector();
|
|
58
|
+
this.violationDetector = new violation_detector_1.ViolationDetector();
|
|
59
|
+
// Merge options với default config
|
|
60
|
+
this.config = {
|
|
61
|
+
...types_1.DEFAULT_CONFIG,
|
|
62
|
+
scanner: {
|
|
63
|
+
...types_1.DEFAULT_CONFIG.scanner,
|
|
64
|
+
respectGitignore: options.respectGitignore ?? types_1.DEFAULT_CONFIG.scanner.respectGitignore,
|
|
65
|
+
},
|
|
66
|
+
analyzer: {
|
|
67
|
+
...types_1.DEFAULT_CONFIG.analyzer,
|
|
68
|
+
patterns: options.patterns || types_1.DEFAULT_CONFIG.analyzer.patterns,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Analyze một project
|
|
74
|
+
*/
|
|
75
|
+
async analyze(projectPath) {
|
|
76
|
+
const startTime = Date.now();
|
|
77
|
+
const absolutePath = path.resolve(projectPath);
|
|
78
|
+
console.log(`\n🔍 Analyzing project: ${absolutePath}\n`);
|
|
79
|
+
// Step 1: Scan project
|
|
80
|
+
console.log('📁 Scanning project structure...');
|
|
81
|
+
const scanner = new file_scanner_1.FileScanner({
|
|
82
|
+
...this.config.scanner,
|
|
83
|
+
projectPath: absolutePath,
|
|
84
|
+
});
|
|
85
|
+
const scanResult = await scanner.scan();
|
|
86
|
+
// Collect all directories
|
|
87
|
+
const directories = this.collectDirectories(scanResult.tree);
|
|
88
|
+
// Step 2: Build project context
|
|
89
|
+
const context = {
|
|
90
|
+
projectPath: absolutePath,
|
|
91
|
+
files: scanResult.files,
|
|
92
|
+
directories,
|
|
93
|
+
directoryTree: scanResult.tree,
|
|
94
|
+
config: this.config,
|
|
95
|
+
cache: new Map(),
|
|
96
|
+
projectInfo: {
|
|
97
|
+
projectRoot: absolutePath,
|
|
98
|
+
},
|
|
99
|
+
helpers: {
|
|
100
|
+
readFile: async (filePath) => file_scanner_1.FileScanner.readFileContent(filePath),
|
|
101
|
+
fileExists: (filePath) => file_scanner_1.FileScanner.fileExists(filePath),
|
|
102
|
+
findFiles: (pattern) => file_scanner_1.FileScanner.findFilesByPattern(scanResult.files, pattern),
|
|
103
|
+
findFolders: (pattern) => file_scanner_1.FileScanner.findFoldersByPattern(scanResult.tree, pattern),
|
|
104
|
+
searchInFiles: async (pattern, files) => file_scanner_1.FileScanner.searchInFiles(pattern, files || scanResult.files),
|
|
105
|
+
getExtension: (filePath) => filePath.split('.').pop() || '',
|
|
106
|
+
getLanguage: (filePath) => file_scanner_1.FileScanner.getLanguageFromPath(filePath),
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
console.log(` Found ${context.files.length} files in ${directories.length} directories\n`);
|
|
110
|
+
// Step 3: Execute all pattern rules
|
|
111
|
+
console.log('🔎 Detecting architecture patterns...\n');
|
|
112
|
+
const patternResults = await this.ruleExecutor.executeAll(context);
|
|
113
|
+
// Step 4: Detect hybrid patterns
|
|
114
|
+
const hybridResult = this.hybridDetector.detectHybrid(patternResults);
|
|
115
|
+
// Step 5: Detect violations for primary pattern
|
|
116
|
+
console.log('⚠️ Detecting architecture violations...');
|
|
117
|
+
const primaryPatternResult = patternResults.find((p) => p.pattern === hybridResult.primaryPattern);
|
|
118
|
+
const violationAssessment = primaryPatternResult
|
|
119
|
+
? this.violationDetector.detectViolations(primaryPatternResult)
|
|
120
|
+
: undefined;
|
|
121
|
+
// Step 6: Generate recommendations
|
|
122
|
+
const recommendations = this.generateRecommendations(patternResults, hybridResult);
|
|
123
|
+
// Step 7: Build final result
|
|
124
|
+
const analysisTime = Date.now() - startTime;
|
|
125
|
+
const result = {
|
|
126
|
+
projectPath: absolutePath,
|
|
127
|
+
timestamp: new Date().toISOString(),
|
|
128
|
+
patterns: patternResults,
|
|
129
|
+
hybridAnalysis: hybridResult,
|
|
130
|
+
primaryPattern: hybridResult.primaryPattern,
|
|
131
|
+
violationAssessment,
|
|
132
|
+
recommendations,
|
|
133
|
+
metadata: {
|
|
134
|
+
totalFiles: context.files.length,
|
|
135
|
+
totalDirectories: directories.length,
|
|
136
|
+
analysisTimeMs: analysisTime,
|
|
137
|
+
rulesExecuted: this.countExecutedRules(patternResults),
|
|
138
|
+
engineVersion: '1.0.0',
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Collect all directories from tree
|
|
145
|
+
*/
|
|
146
|
+
collectDirectories(tree) {
|
|
147
|
+
const dirs = [tree];
|
|
148
|
+
for (const sub of tree.subdirectories) {
|
|
149
|
+
dirs.push(...this.collectDirectories(sub));
|
|
150
|
+
}
|
|
151
|
+
return dirs;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Count total rules executed
|
|
155
|
+
*/
|
|
156
|
+
countExecutedRules(results) {
|
|
157
|
+
return results.reduce((sum, r) => sum + r.ruleResults.length, 0);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Generate recommendations based on analysis
|
|
161
|
+
*/
|
|
162
|
+
generateRecommendations(patternResults, hybridResult) {
|
|
163
|
+
const recommendations = [];
|
|
164
|
+
// Add hybrid recommendations
|
|
165
|
+
const hybridRecs = this.hybridDetector.getRecommendation(hybridResult);
|
|
166
|
+
recommendations.push(...hybridRecs);
|
|
167
|
+
// Add pattern-specific recommendations
|
|
168
|
+
for (const result of patternResults) {
|
|
169
|
+
if (result.suggestions) {
|
|
170
|
+
recommendations.push(...result.suggestions.slice(0, 3));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Remove duplicates
|
|
174
|
+
return [...new Set(recommendations)];
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Format result as text
|
|
178
|
+
*/
|
|
179
|
+
formatAsText(result) {
|
|
180
|
+
const lines = [];
|
|
181
|
+
lines.push('╔══════════════════════════════════════════════════════════════╗');
|
|
182
|
+
lines.push('║ ARCHITECTURE DETECTION REPORT ║');
|
|
183
|
+
lines.push('╚══════════════════════════════════════════════════════════════╝');
|
|
184
|
+
lines.push('');
|
|
185
|
+
lines.push(`📂 Project: ${result.projectPath}`);
|
|
186
|
+
lines.push(`📅 Analyzed: ${result.timestamp}`);
|
|
187
|
+
lines.push(`⏱️ Analysis time: ${result.metadata.analysisTimeMs}ms`);
|
|
188
|
+
lines.push('');
|
|
189
|
+
// Primary pattern
|
|
190
|
+
lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
191
|
+
if (result.hybridAnalysis.isHybrid) {
|
|
192
|
+
lines.push(`🏛️ HYBRID ARCHITECTURE: ${result.hybridAnalysis.combination}`);
|
|
193
|
+
lines.push(` Primary: ${result.hybridAnalysis.primaryPattern}`);
|
|
194
|
+
lines.push(` Secondary: ${result.hybridAnalysis.secondaryPatterns.join(', ')}`);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
lines.push(`🏛️ PRIMARY ARCHITECTURE: ${result.primaryPattern}`);
|
|
198
|
+
}
|
|
199
|
+
lines.push(` Confidence: ${(result.hybridAnalysis.confidence * 100).toFixed(1)}%`);
|
|
200
|
+
lines.push('');
|
|
201
|
+
// Pattern details
|
|
202
|
+
lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
203
|
+
lines.push('PATTERN DETECTION RESULTS:');
|
|
204
|
+
lines.push('');
|
|
205
|
+
for (const pattern of result.patterns) {
|
|
206
|
+
const scorePercent = (pattern.score.normalizedScore * 100).toFixed(1);
|
|
207
|
+
const bar = this.createProgressBar(pattern.score.normalizedScore, 20);
|
|
208
|
+
const icon = this.getClassificationIcon(pattern.classification);
|
|
209
|
+
lines.push(`${icon} ${pattern.pattern.toString().padEnd(15)} ${bar} ${scorePercent}% (${pattern.classification})`);
|
|
210
|
+
if (pattern.variant) {
|
|
211
|
+
lines.push(` └─ Variant: ${pattern.variant}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
lines.push('');
|
|
215
|
+
// Recommendations
|
|
216
|
+
if (result.recommendations.length > 0) {
|
|
217
|
+
lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
218
|
+
lines.push('💡 RECOMMENDATIONS:');
|
|
219
|
+
lines.push('');
|
|
220
|
+
for (const rec of result.recommendations.slice(0, 5)) {
|
|
221
|
+
lines.push(` • ${rec}`);
|
|
222
|
+
}
|
|
223
|
+
lines.push('');
|
|
224
|
+
}
|
|
225
|
+
// Statistics
|
|
226
|
+
lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
227
|
+
lines.push('📊 STATISTICS:');
|
|
228
|
+
lines.push(` Files analyzed: ${result.metadata.totalFiles}`);
|
|
229
|
+
lines.push(` Directories scanned: ${result.metadata.totalDirectories}`);
|
|
230
|
+
lines.push(` Rules executed: ${result.metadata.rulesExecuted}`);
|
|
231
|
+
lines.push('');
|
|
232
|
+
return lines.join('\n');
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Format result as markdown
|
|
236
|
+
*/
|
|
237
|
+
formatAsMarkdown(result) {
|
|
238
|
+
const lines = [];
|
|
239
|
+
const projectName = path.basename(result.projectPath);
|
|
240
|
+
const analyzedDate = new Date(result.timestamp).toLocaleString('vi-VN');
|
|
241
|
+
lines.push('# Architecture Detection Report');
|
|
242
|
+
lines.push('');
|
|
243
|
+
lines.push(`| | |`);
|
|
244
|
+
lines.push(`|---|---|`);
|
|
245
|
+
lines.push(`| **Project** | ${projectName} |`);
|
|
246
|
+
lines.push(`| **Analyzed** | ${analyzedDate} |`);
|
|
247
|
+
lines.push(`| **Analysis time** | ${result.metadata.analysisTimeMs}ms |`);
|
|
248
|
+
lines.push('');
|
|
249
|
+
// Primary pattern
|
|
250
|
+
lines.push('## Detected Architecture');
|
|
251
|
+
lines.push('');
|
|
252
|
+
if (result.hybridAnalysis.isHybrid) {
|
|
253
|
+
lines.push(`**Hybrid Architecture:** ${result.hybridAnalysis.combination}`);
|
|
254
|
+
lines.push(`- Primary: ${result.hybridAnalysis.primaryPattern}`);
|
|
255
|
+
lines.push(`- Secondary: ${result.hybridAnalysis.secondaryPatterns.join(', ')}`);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
lines.push(`**Primary Architecture:** ${result.primaryPattern}`);
|
|
259
|
+
}
|
|
260
|
+
lines.push(`**Confidence:** ${(result.hybridAnalysis.confidence * 100).toFixed(1)}%`);
|
|
261
|
+
lines.push('');
|
|
262
|
+
// Pattern details table
|
|
263
|
+
lines.push('## Pattern Detection Results');
|
|
264
|
+
lines.push('');
|
|
265
|
+
lines.push('| Pattern | Score | Classification | Confidence |');
|
|
266
|
+
lines.push('|---------|-------|----------------|------------|');
|
|
267
|
+
for (const pattern of result.patterns) {
|
|
268
|
+
const scorePercent = (pattern.score.normalizedScore * 100).toFixed(1);
|
|
269
|
+
const confPercent = (pattern.confidence * 100).toFixed(1);
|
|
270
|
+
lines.push(`| ${pattern.pattern} | ${scorePercent}% | ${pattern.classification} | ${confPercent}% |`);
|
|
271
|
+
}
|
|
272
|
+
lines.push('');
|
|
273
|
+
// Recommendations
|
|
274
|
+
if (result.recommendations.length > 0) {
|
|
275
|
+
lines.push('## Recommendations');
|
|
276
|
+
lines.push('');
|
|
277
|
+
for (const rec of result.recommendations) {
|
|
278
|
+
lines.push(`- ${rec}`);
|
|
279
|
+
}
|
|
280
|
+
lines.push('');
|
|
281
|
+
}
|
|
282
|
+
// Violation Assessment
|
|
283
|
+
if (result.violationAssessment && result.violationAssessment.violations.length > 0) {
|
|
284
|
+
const va = result.violationAssessment;
|
|
285
|
+
lines.push('## Architecture Health Assessment');
|
|
286
|
+
lines.push('');
|
|
287
|
+
lines.push(`**Health Score:** ${this.violationDetector.getHealthIcon(va.healthScore)} ${va.healthScore.toFixed(0)}/100`);
|
|
288
|
+
lines.push('');
|
|
289
|
+
lines.push(`**Assessment:** ${va.overallAssessment}`);
|
|
290
|
+
lines.push('');
|
|
291
|
+
// Violation summary
|
|
292
|
+
lines.push('### Violation Summary');
|
|
293
|
+
lines.push('');
|
|
294
|
+
lines.push('| Impact | Count |');
|
|
295
|
+
lines.push('|--------|-------|');
|
|
296
|
+
if (va.summary.critical > 0)
|
|
297
|
+
lines.push(`| 🔴 Critical | ${va.summary.critical} |`);
|
|
298
|
+
if (va.summary.high > 0)
|
|
299
|
+
lines.push(`| 🟠 High | ${va.summary.high} |`);
|
|
300
|
+
if (va.summary.medium > 0)
|
|
301
|
+
lines.push(`| 🟡 Medium | ${va.summary.medium} |`);
|
|
302
|
+
if (va.summary.low > 0)
|
|
303
|
+
lines.push(`| 🟢 Low | ${va.summary.low} |`);
|
|
304
|
+
lines.push(`| **Total** | **${va.summary.total}** |`);
|
|
305
|
+
lines.push('');
|
|
306
|
+
// Violation details
|
|
307
|
+
lines.push('### Violation Details');
|
|
308
|
+
lines.push('');
|
|
309
|
+
for (const violation of va.violations) {
|
|
310
|
+
const icon = this.violationDetector.getImpactIcon(violation.impact);
|
|
311
|
+
lines.push(`#### ${icon} ${violation.ruleName}`);
|
|
312
|
+
lines.push('');
|
|
313
|
+
lines.push(`| Attribute | Value |`);
|
|
314
|
+
lines.push(`|-----------|-------|`);
|
|
315
|
+
lines.push(`| **Impact Level** | ${violation.impact} |`);
|
|
316
|
+
lines.push(`| **Score** | ${violation.description.match(/\d+%/)?.[0] || 'N/A'} |`);
|
|
317
|
+
lines.push(`| **Type** | ${violation.type} |`);
|
|
318
|
+
lines.push('');
|
|
319
|
+
lines.push(`**Problem:** ${violation.impactReason}`);
|
|
320
|
+
lines.push('');
|
|
321
|
+
// Affected Files - SPECIFIC to this project
|
|
322
|
+
if (violation.affectedFiles && violation.affectedFiles.length > 0) {
|
|
323
|
+
lines.push('**📁 Affected Files trong project này:**');
|
|
324
|
+
lines.push('```');
|
|
325
|
+
for (const file of violation.affectedFiles) {
|
|
326
|
+
lines.push(file);
|
|
327
|
+
}
|
|
328
|
+
lines.push('```');
|
|
329
|
+
lines.push('');
|
|
330
|
+
}
|
|
331
|
+
// Examples found - SPECIFIC to this project
|
|
332
|
+
if (violation.examples && violation.examples.length > 0) {
|
|
333
|
+
lines.push('**📍 Ví dụ cụ thể tìm thấy:**');
|
|
334
|
+
lines.push('```');
|
|
335
|
+
for (const example of violation.examples) {
|
|
336
|
+
lines.push(example);
|
|
337
|
+
}
|
|
338
|
+
lines.push('```');
|
|
339
|
+
lines.push('');
|
|
340
|
+
}
|
|
341
|
+
lines.push('**⚠️ Hậu quả nếu không fix:**');
|
|
342
|
+
for (const consequence of violation.consequences) {
|
|
343
|
+
lines.push(`- ${consequence}`);
|
|
344
|
+
}
|
|
345
|
+
lines.push('');
|
|
346
|
+
lines.push(`**✅ Cách fix:** ${violation.suggestedFix}`);
|
|
347
|
+
lines.push('');
|
|
348
|
+
lines.push('---');
|
|
349
|
+
lines.push('');
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
lines.push('## Architecture Health Assessment');
|
|
354
|
+
lines.push('');
|
|
355
|
+
lines.push('💚 **Health Score:** 100/100');
|
|
356
|
+
lines.push('');
|
|
357
|
+
lines.push('**No violations detected.** The architecture follows best practices.');
|
|
358
|
+
lines.push('');
|
|
359
|
+
}
|
|
360
|
+
// Statistics
|
|
361
|
+
lines.push('## Statistics');
|
|
362
|
+
lines.push('');
|
|
363
|
+
lines.push(`- Files analyzed: ${result.metadata.totalFiles}`);
|
|
364
|
+
lines.push(`- Directories scanned: ${result.metadata.totalDirectories}`);
|
|
365
|
+
lines.push(`- Rules executed: ${result.metadata.rulesExecuted}`);
|
|
366
|
+
lines.push('');
|
|
367
|
+
return lines.join('\n');
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Create progress bar
|
|
371
|
+
*/
|
|
372
|
+
createProgressBar(value, width) {
|
|
373
|
+
const filled = Math.round(value * width);
|
|
374
|
+
const empty = width - filled;
|
|
375
|
+
return '█'.repeat(filled) + '░'.repeat(empty);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Get icon for classification
|
|
379
|
+
*/
|
|
380
|
+
getClassificationIcon(classification) {
|
|
381
|
+
switch (classification) {
|
|
382
|
+
case enums_1.PatternClassification.STRONG_MATCH:
|
|
383
|
+
return '✅';
|
|
384
|
+
case enums_1.PatternClassification.DETECTED:
|
|
385
|
+
return '🟢';
|
|
386
|
+
case enums_1.PatternClassification.POSSIBLE:
|
|
387
|
+
return '🟡';
|
|
388
|
+
case enums_1.PatternClassification.WEAK_SIGNAL:
|
|
389
|
+
return '🟠';
|
|
390
|
+
default:
|
|
391
|
+
return '⚪';
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Get registered rules count
|
|
396
|
+
*/
|
|
397
|
+
getRegisteredRulesCount() {
|
|
398
|
+
return rule_registry_1.ruleRegistry.getAllRules().length;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* List all registered rules
|
|
402
|
+
*/
|
|
403
|
+
listRegisteredRules() {
|
|
404
|
+
return rule_registry_1.ruleRegistry.getAllRules().map((rule) => ({
|
|
405
|
+
id: rule.id,
|
|
406
|
+
name: rule.name,
|
|
407
|
+
pattern: rule.applicableTo,
|
|
408
|
+
}));
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
exports.ArchitectureAnalyzer = ArchitectureAnalyzer;
|
|
412
|
+
exports.default = ArchitectureAnalyzer;
|
|
413
|
+
//# sourceMappingURL=analyzer.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Core Index
|
|
4
|
+
* Export core components
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
18
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
__exportStar(require("./analyzer"), exports);
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Hybrid Detector
|
|
4
|
+
* Phát hiện hybrid patterns (kết hợp nhiều patterns)
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.HybridDetector = void 0;
|
|
8
|
+
const enums_1 = require("../types/enums");
|
|
9
|
+
/**
|
|
10
|
+
* Common hybrid combinations
|
|
11
|
+
*/
|
|
12
|
+
const HYBRID_COMBINATIONS = [
|
|
13
|
+
{
|
|
14
|
+
combination: 'LAYERED+MODULAR',
|
|
15
|
+
patterns: [enums_1.PatternType.LAYERED, enums_1.PatternType.MODULAR],
|
|
16
|
+
description: 'Layered architecture with modular organization (e.g., NestJS, Angular)',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
combination: 'MODULAR+MVVM',
|
|
20
|
+
patterns: [enums_1.PatternType.MODULAR, enums_1.PatternType.MVVM],
|
|
21
|
+
description: 'Feature modules with MVVM pattern per module',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
combination: 'LAYERED+MVVM',
|
|
25
|
+
patterns: [enums_1.PatternType.LAYERED, enums_1.PatternType.MVVM],
|
|
26
|
+
description: 'Layered architecture with MVVM presentation (e.g., Android apps)',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
combination: 'MODULAR+VIPER',
|
|
30
|
+
patterns: [enums_1.PatternType.MODULAR, enums_1.PatternType.VIPER],
|
|
31
|
+
description: 'Feature modules with VIPER pattern per module (iOS)',
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
class HybridDetector {
|
|
35
|
+
/**
|
|
36
|
+
* Detect hybrid patterns từ multiple pattern results
|
|
37
|
+
*/
|
|
38
|
+
detectHybrid(results) {
|
|
39
|
+
// Get detected patterns (above threshold)
|
|
40
|
+
const detectedPatterns = results.filter((r) => r.classification === enums_1.PatternClassification.STRONG_MATCH ||
|
|
41
|
+
r.classification === enums_1.PatternClassification.DETECTED);
|
|
42
|
+
if (detectedPatterns.length <= 1) {
|
|
43
|
+
// Not a hybrid
|
|
44
|
+
const primary = this.getPrimaryPattern(results);
|
|
45
|
+
return {
|
|
46
|
+
isHybrid: false,
|
|
47
|
+
primaryPattern: primary?.pattern || enums_1.PatternType.LAYERED,
|
|
48
|
+
secondaryPatterns: [],
|
|
49
|
+
combination: primary?.pattern.toString() || 'UNKNOWN',
|
|
50
|
+
analysis: primary
|
|
51
|
+
? `Single pattern detected: ${primary.pattern} with ${(primary.score.normalizedScore * 100).toFixed(1)}% match`
|
|
52
|
+
: 'No clear pattern detected',
|
|
53
|
+
confidence: primary?.confidence || 0,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// Sort by score
|
|
57
|
+
const sorted = [...detectedPatterns].sort((a, b) => b.score.normalizedScore - a.score.normalizedScore);
|
|
58
|
+
const primary = sorted[0];
|
|
59
|
+
const secondary = sorted.slice(1);
|
|
60
|
+
// Check for known hybrid combinations
|
|
61
|
+
const detectedTypes = sorted.map((r) => r.pattern);
|
|
62
|
+
const matchedCombination = this.findMatchingCombination(detectedTypes);
|
|
63
|
+
// Build combination string
|
|
64
|
+
const combinationStr = sorted.map((r) => r.pattern).join('+');
|
|
65
|
+
// Calculate hybrid confidence
|
|
66
|
+
const confidence = this.calculateHybridConfidence(sorted);
|
|
67
|
+
// Generate analysis
|
|
68
|
+
const analysis = this.generateAnalysis(sorted, matchedCombination);
|
|
69
|
+
return {
|
|
70
|
+
isHybrid: true,
|
|
71
|
+
primaryPattern: primary.pattern,
|
|
72
|
+
secondaryPatterns: secondary.map((r) => r.pattern),
|
|
73
|
+
combination: matchedCombination?.combination || combinationStr,
|
|
74
|
+
analysis,
|
|
75
|
+
confidence,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get primary pattern from results
|
|
80
|
+
*/
|
|
81
|
+
getPrimaryPattern(results) {
|
|
82
|
+
if (results.length === 0)
|
|
83
|
+
return null;
|
|
84
|
+
// Sort by score and return highest
|
|
85
|
+
const sorted = [...results].sort((a, b) => b.score.normalizedScore - a.score.normalizedScore);
|
|
86
|
+
// Only return if above weak signal threshold
|
|
87
|
+
if (sorted[0].score.normalizedScore >= 0.2) {
|
|
88
|
+
return sorted[0];
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Find matching known hybrid combination
|
|
94
|
+
*/
|
|
95
|
+
findMatchingCombination(patterns) {
|
|
96
|
+
for (const combo of HYBRID_COMBINATIONS) {
|
|
97
|
+
const matched = combo.patterns.every((p) => patterns.includes(p));
|
|
98
|
+
if (matched) {
|
|
99
|
+
return combo;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Calculate hybrid confidence
|
|
106
|
+
*/
|
|
107
|
+
calculateHybridConfidence(sortedResults) {
|
|
108
|
+
if (sortedResults.length < 2)
|
|
109
|
+
return 0;
|
|
110
|
+
// Base confidence from pattern scores
|
|
111
|
+
let confidence = sortedResults.reduce((sum, r) => sum + r.confidence, 0) / sortedResults.length;
|
|
112
|
+
// Boost if patterns complement each other
|
|
113
|
+
const primary = sortedResults[0].pattern;
|
|
114
|
+
const secondary = sortedResults[1].pattern;
|
|
115
|
+
// Layered + Modular is a common and valid combination
|
|
116
|
+
if ((primary === enums_1.PatternType.LAYERED && secondary === enums_1.PatternType.MODULAR) ||
|
|
117
|
+
(primary === enums_1.PatternType.MODULAR && secondary === enums_1.PatternType.LAYERED)) {
|
|
118
|
+
confidence += 0.1;
|
|
119
|
+
}
|
|
120
|
+
// Presentation + Layered is common in client apps
|
|
121
|
+
if ((primary === enums_1.PatternType.MVVM || primary === enums_1.PatternType.VIPER) &&
|
|
122
|
+
secondary === enums_1.PatternType.LAYERED) {
|
|
123
|
+
confidence += 0.1;
|
|
124
|
+
}
|
|
125
|
+
return Math.min(confidence, 1);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Generate analysis text
|
|
129
|
+
*/
|
|
130
|
+
generateAnalysis(sortedResults, matchedCombination) {
|
|
131
|
+
const parts = [];
|
|
132
|
+
if (matchedCombination) {
|
|
133
|
+
parts.push(matchedCombination.description);
|
|
134
|
+
}
|
|
135
|
+
parts.push('Detected patterns:');
|
|
136
|
+
for (const result of sortedResults) {
|
|
137
|
+
const scorePercent = (result.score.normalizedScore * 100).toFixed(1);
|
|
138
|
+
parts.push(`- ${result.pattern}: ${scorePercent}% (${result.classification})`);
|
|
139
|
+
}
|
|
140
|
+
// Add primary pattern analysis
|
|
141
|
+
const primary = sortedResults[0];
|
|
142
|
+
parts.push(`\nPrimary architecture: ${primary.pattern}`);
|
|
143
|
+
if (primary.variant) {
|
|
144
|
+
parts.push(`Variant: ${primary.variant}`);
|
|
145
|
+
}
|
|
146
|
+
return parts.join('\n');
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get recommendation for hybrid architecture
|
|
150
|
+
*/
|
|
151
|
+
getRecommendation(hybridResult) {
|
|
152
|
+
const recommendations = [];
|
|
153
|
+
if (!hybridResult.isHybrid) {
|
|
154
|
+
recommendations.push('Consider if a hybrid approach might better suit your needs');
|
|
155
|
+
return recommendations;
|
|
156
|
+
}
|
|
157
|
+
// Recommendations based on combination
|
|
158
|
+
if (hybridResult.combination.includes('LAYERED') &&
|
|
159
|
+
hybridResult.combination.includes('MODULAR')) {
|
|
160
|
+
recommendations.push('Ensure module boundaries are respected to maintain modularity');
|
|
161
|
+
recommendations.push('Consider using barrel files (index.ts) for module public APIs');
|
|
162
|
+
}
|
|
163
|
+
if (hybridResult.combination.includes('MVVM')) {
|
|
164
|
+
recommendations.push('Ensure ViewModels do not depend on View implementations');
|
|
165
|
+
recommendations.push('Use data binding consistently across the application');
|
|
166
|
+
}
|
|
167
|
+
if (hybridResult.combination.includes('VIPER')) {
|
|
168
|
+
recommendations.push('Ensure each VIPER module has all 5 components');
|
|
169
|
+
recommendations.push('Router should be the only component handling navigation');
|
|
170
|
+
}
|
|
171
|
+
return recommendations;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
exports.HybridDetector = HybridDetector;
|
|
175
|
+
exports.default = HybridDetector;
|
|
176
|
+
//# sourceMappingURL=hybrid-detector.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Engine Index
|
|
4
|
+
* Export tất cả engine components
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
18
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
__exportStar(require("./hybrid-detector"), exports);
|
|
22
|
+
__exportStar(require("./rule-executor"), exports);
|
|
23
|
+
__exportStar(require("./score-calculator"), exports);
|
|
24
|
+
//# sourceMappingURL=index.js.map
|