@sun-asterisk/sunlint 1.3.31 → 1.3.33
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 +47 -0
- package/core/architecture-integration.js +220 -0
- package/core/cli-action-handler.js +98 -9
- package/core/cli-program.js +13 -1
- package/core/output-service.js +87 -24
- package/core/scoring-service.js +65 -20
- package/core/upload-service.js +43 -9
- package/package.json +6 -5
- package/rules/common/C006_function_naming/analyzer.js +12 -1
- package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +19 -0
- package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js +335 -3600
- package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js.backup +3853 -0
- package/scripts/copy-arch-detect.js +78 -0
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ Sun Lint is a universal coding standards checker providing comprehensive code qu
|
|
|
19
19
|
- ✅ **Advanced File Targeting**: Include/exclude patterns, language filtering
|
|
20
20
|
- ✅ **Quality Scoring System**: Automated quality score (0-100) with grade (A+ to F)
|
|
21
21
|
- ✅ **Summary Reports**: JSON format for CI/CD dashboards and management reports
|
|
22
|
+
- ✅ **Architecture Detection**: Detect architecture patterns (Layered, Modular, MVVM, VIPER) with health scoring
|
|
22
23
|
|
|
23
24
|
### **🏗️ Architecture**
|
|
24
25
|
|
|
@@ -128,6 +129,52 @@ sunlint --all --input=src --output-summary=quality.json
|
|
|
128
129
|
- Quality gate setup
|
|
129
130
|
- Trending analysis
|
|
130
131
|
|
|
132
|
+
## 🏛️ **Architecture Detection** 🆕
|
|
133
|
+
|
|
134
|
+
Detect and analyze architecture patterns in your codebase with health scoring and violation detection.
|
|
135
|
+
|
|
136
|
+
### **Features**
|
|
137
|
+
- **Pattern Detection**: Layered, Modular, MVVM, VIPER, Clean Architecture
|
|
138
|
+
- **Health Score (0-100)**: Architecture compliance scoring
|
|
139
|
+
- **Violation Detection**: Identify architecture anti-patterns
|
|
140
|
+
- **Markdown Reports**: Detailed analysis reports for documentation
|
|
141
|
+
|
|
142
|
+
### **Quick Usage**
|
|
143
|
+
```bash
|
|
144
|
+
# Architecture analysis only
|
|
145
|
+
sunlint --architecture --input=src
|
|
146
|
+
|
|
147
|
+
# Combined: Code quality + Architecture
|
|
148
|
+
sunlint --all --architecture --input=src
|
|
149
|
+
|
|
150
|
+
# Generate markdown report
|
|
151
|
+
sunlint --architecture --arch-report --input=src
|
|
152
|
+
|
|
153
|
+
# Target specific patterns
|
|
154
|
+
sunlint --architecture --arch-patterns=mvvm,layered --input=src
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### **Example Output**
|
|
158
|
+
```
|
|
159
|
+
🏛️ Architecture Analysis:
|
|
160
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
161
|
+
• Pattern: LAYERED (69% confidence)
|
|
162
|
+
• Health Score: 85/100
|
|
163
|
+
• Violations: 3
|
|
164
|
+
|
|
165
|
+
Top Architecture Violations:
|
|
166
|
+
⚠ Interface/Contract Definitions - score 20% (threshold: 50%)
|
|
167
|
+
⚠ Middleware/Interceptor Layer - score 0% (threshold: 50%)
|
|
168
|
+
... and 1 more
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### **Options**
|
|
172
|
+
| Option | Description |
|
|
173
|
+
|--------|-------------|
|
|
174
|
+
| `--architecture` | Enable architecture pattern detection |
|
|
175
|
+
| `--arch-patterns <patterns>` | Target specific patterns (comma-separated: layered, modular, mvvm, viper) |
|
|
176
|
+
| `--arch-report` | Generate separate markdown report |
|
|
177
|
+
|
|
131
178
|
## 📦 **Installation**
|
|
132
179
|
|
|
133
180
|
### **Global Installation (Recommended)**
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Architecture Integration for SunLint
|
|
3
|
+
* Wraps architecture-detection module for seamless integration
|
|
4
|
+
* Following Rule C005: Single responsibility - handle architecture analysis integration
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
|
|
11
|
+
class ArchitectureIntegration {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.options = options;
|
|
14
|
+
this.archModule = null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Load architecture detection module
|
|
19
|
+
* Tries bundled version first, then falls back to local development path
|
|
20
|
+
*/
|
|
21
|
+
async loadArchitectureModule() {
|
|
22
|
+
if (this.archModule) {
|
|
23
|
+
return this.archModule;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Try bundled version first (engines/arch-detect)
|
|
27
|
+
const bundledPath = path.join(__dirname, '..', 'engines', 'arch-detect', 'index.js');
|
|
28
|
+
|
|
29
|
+
if (fs.existsSync(bundledPath)) {
|
|
30
|
+
try {
|
|
31
|
+
this.archModule = require(bundledPath);
|
|
32
|
+
if (this.options.verbose) {
|
|
33
|
+
console.log(chalk.gray('📦 Loaded bundled architecture-detection'));
|
|
34
|
+
}
|
|
35
|
+
return this.archModule;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (this.options.verbose) {
|
|
38
|
+
console.log(chalk.yellow(`⚠️ Failed to load bundled module: ${error.message}`));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Fallback: Try local development path
|
|
44
|
+
const devPaths = [
|
|
45
|
+
path.join(__dirname, '..', '..', '..', '..', 'architecture-detection', 'dist', 'index.js'),
|
|
46
|
+
path.join(__dirname, '..', '..', '..', 'architecture-detection', 'dist', 'index.js'),
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
for (const devPath of devPaths) {
|
|
50
|
+
if (fs.existsSync(devPath)) {
|
|
51
|
+
try {
|
|
52
|
+
this.archModule = require(devPath);
|
|
53
|
+
if (this.options.verbose) {
|
|
54
|
+
console.log(chalk.gray(`📦 Loaded architecture-detection from: ${devPath}`));
|
|
55
|
+
}
|
|
56
|
+
return this.archModule;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (this.options.verbose) {
|
|
59
|
+
console.log(chalk.yellow(`⚠️ Failed to load from ${devPath}: ${error.message}`));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
throw new Error(
|
|
66
|
+
'Architecture detection module not found. Run "npm run build" to bundle it, ' +
|
|
67
|
+
'or ensure architecture-detection is built in the parent directory.'
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Parse architecture patterns from CLI option
|
|
73
|
+
*/
|
|
74
|
+
parsePatterns() {
|
|
75
|
+
if (!this.options.archPatterns) {
|
|
76
|
+
return undefined; // Use default patterns
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const patternMap = {
|
|
80
|
+
'layered': 'LAYERED',
|
|
81
|
+
'modular': 'MODULAR',
|
|
82
|
+
'mvvm': 'MVVM',
|
|
83
|
+
'viper': 'VIPER',
|
|
84
|
+
'presentation': 'PRESENTATION',
|
|
85
|
+
'clean': 'CLEAN_ARCHITECTURE',
|
|
86
|
+
'tdd': 'TDD_CLEAN_ARCHITECTURE',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const patterns = this.options.archPatterns
|
|
90
|
+
.split(',')
|
|
91
|
+
.map(p => p.trim().toLowerCase())
|
|
92
|
+
.map(p => patternMap[p] || p.toUpperCase())
|
|
93
|
+
.filter(Boolean);
|
|
94
|
+
|
|
95
|
+
return patterns.length > 0 ? patterns : undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Run architecture analysis on project
|
|
100
|
+
* @param {string} projectPath - Path to analyze
|
|
101
|
+
* @returns {Object} Architecture analysis results
|
|
102
|
+
*/
|
|
103
|
+
async analyze(projectPath) {
|
|
104
|
+
const archModule = await this.loadArchitectureModule();
|
|
105
|
+
const { ArchitectureAnalyzer } = archModule;
|
|
106
|
+
|
|
107
|
+
if (!ArchitectureAnalyzer) {
|
|
108
|
+
throw new Error('ArchitectureAnalyzer not found in architecture-detection module');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const analyzer = new ArchitectureAnalyzer({
|
|
112
|
+
patterns: this.parsePatterns(),
|
|
113
|
+
respectGitignore: true,
|
|
114
|
+
verbose: this.options.verbose,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (this.options.verbose) {
|
|
118
|
+
console.log(chalk.blue(`🏛️ Analyzing architecture: ${projectPath}`));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const result = await analyzer.analyze(projectPath);
|
|
122
|
+
|
|
123
|
+
// Convert to SunLint-compatible format
|
|
124
|
+
return this.convertToSunLintFormat(result, analyzer, projectPath);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Convert architecture results to SunLint format
|
|
129
|
+
*/
|
|
130
|
+
convertToSunLintFormat(result, analyzer, projectPath) {
|
|
131
|
+
const violations = [];
|
|
132
|
+
|
|
133
|
+
// Convert architecture violations to SunLint violation format
|
|
134
|
+
// Check violationAssessment.violations from the new format
|
|
135
|
+
const violationAssessment = result.violationAssessment;
|
|
136
|
+
if (violationAssessment && violationAssessment.violations && violationAssessment.violations.length > 0) {
|
|
137
|
+
for (const violation of violationAssessment.violations) {
|
|
138
|
+
violations.push({
|
|
139
|
+
ruleId: `ARCH-${violation.type || 'VIOLATION'}`,
|
|
140
|
+
severity: this.mapSeverity(violation.impact),
|
|
141
|
+
message: violation.description || violation.ruleName,
|
|
142
|
+
file: violation.affectedFiles?.[0] || projectPath,
|
|
143
|
+
line: 1,
|
|
144
|
+
column: 1,
|
|
145
|
+
category: 'architecture',
|
|
146
|
+
source: 'architecture-detection',
|
|
147
|
+
details: {
|
|
148
|
+
ruleName: violation.ruleName,
|
|
149
|
+
impactReason: violation.impactReason,
|
|
150
|
+
suggestedFix: violation.suggestedFix,
|
|
151
|
+
affectedFiles: violation.affectedFiles,
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Generate markdown report if requested
|
|
158
|
+
let markdownReport = null;
|
|
159
|
+
if (this.options.archReport) {
|
|
160
|
+
try {
|
|
161
|
+
markdownReport = analyzer.formatAsMarkdown(result);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (this.options.verbose) {
|
|
164
|
+
console.log(chalk.yellow(`⚠️ Could not generate markdown report: ${error.message}`));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Map from hybridAnalysis structure
|
|
170
|
+
const hybridAnalysis = result.hybridAnalysis || {};
|
|
171
|
+
const healthScore = violationAssessment?.healthScore || 100;
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
summary: {
|
|
175
|
+
primaryPattern: hybridAnalysis.primaryPattern || result.primaryPattern || 'UNKNOWN',
|
|
176
|
+
primaryConfidence: hybridAnalysis.confidence || 0,
|
|
177
|
+
secondaryPatterns: (hybridAnalysis.secondaryPatterns || []).map(p => ({
|
|
178
|
+
pattern: p,
|
|
179
|
+
confidence: 0.5, // Default confidence for secondary patterns
|
|
180
|
+
})),
|
|
181
|
+
healthScore: healthScore,
|
|
182
|
+
violationCount: violations.length,
|
|
183
|
+
analysisTime: result.metadata?.analysisTimeMs || 0,
|
|
184
|
+
isHybrid: hybridAnalysis.isHybrid || false,
|
|
185
|
+
combination: hybridAnalysis.combination || null,
|
|
186
|
+
},
|
|
187
|
+
violations,
|
|
188
|
+
markdownReport,
|
|
189
|
+
raw: result,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Map architecture severity to SunLint severity
|
|
195
|
+
*/
|
|
196
|
+
mapSeverity(severity) {
|
|
197
|
+
const severityMap = {
|
|
198
|
+
'critical': 'error',
|
|
199
|
+
'high': 'error',
|
|
200
|
+
'medium': 'warning',
|
|
201
|
+
'low': 'info',
|
|
202
|
+
};
|
|
203
|
+
return severityMap[severity?.toLowerCase()] || 'warning';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Save markdown report to file
|
|
208
|
+
*/
|
|
209
|
+
async saveReport(markdownContent, projectPath) {
|
|
210
|
+
const projectName = path.basename(projectPath);
|
|
211
|
+
const date = new Date().toISOString().split('T')[0].replace(/-/g, '_');
|
|
212
|
+
const fileName = `sun_arch_report_${projectName}_${date}.md`;
|
|
213
|
+
const outputPath = path.join(process.cwd(), fileName);
|
|
214
|
+
|
|
215
|
+
fs.writeFileSync(outputPath, markdownContent, 'utf8');
|
|
216
|
+
return outputPath;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
module.exports = { ArchitectureIntegration };
|
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
const chalk = require('chalk');
|
|
9
9
|
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
10
11
|
const ConfigManager = require('./config-manager');
|
|
11
12
|
const RuleSelectionService = require('./rule-selection-service');
|
|
12
13
|
const AnalysisOrchestrator = require('./analysis-orchestrator');
|
|
13
14
|
const OutputService = require('./output-service');
|
|
14
15
|
const GitUtils = require('./git-utils');
|
|
15
16
|
const FileTargetingService = require('./file-targeting-service');
|
|
17
|
+
const { ArchitectureIntegration } = require('./architecture-integration');
|
|
16
18
|
|
|
17
19
|
// Legacy orchestrator for fallback
|
|
18
20
|
// const LegacyOrchestrator = require('./legacy-analysis-orchestrator'); // Removed
|
|
@@ -79,11 +81,25 @@ class CliActionHandler {
|
|
|
79
81
|
|
|
80
82
|
// Run analysis with appropriate orchestrator
|
|
81
83
|
const startTime = Date.now();
|
|
82
|
-
|
|
84
|
+
let results = null;
|
|
85
|
+
|
|
86
|
+
// Run code quality analysis (unless --architecture is used alone)
|
|
87
|
+
if (rulesToRun.length > 0 && !this.isArchitectureOnly()) {
|
|
88
|
+
results = await this.runModernAnalysis(rulesToRun, targetingResult.files, config);
|
|
89
|
+
} else {
|
|
90
|
+
results = { results: [], summary: { total: 0, errors: 0, warnings: 0 } };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Run architecture analysis if requested
|
|
94
|
+
if (this.options.architecture) {
|
|
95
|
+
const architectureResults = await this.runArchitectureAnalysis();
|
|
96
|
+
results.architecture = architectureResults;
|
|
97
|
+
}
|
|
98
|
+
|
|
83
99
|
const duration = Date.now() - startTime;
|
|
84
100
|
|
|
85
101
|
// Output results
|
|
86
|
-
await this.outputService.outputResults(results, this.options, {
|
|
102
|
+
await this.outputService.outputResults(results, this.options, {
|
|
87
103
|
duration,
|
|
88
104
|
rulesRun: rulesToRun.length,
|
|
89
105
|
rulesChecked: rulesToRun.length
|
|
@@ -121,9 +137,10 @@ class CliActionHandler {
|
|
|
121
137
|
enabledEngines: this.determineEnabledEngines(config),
|
|
122
138
|
aiConfig: config.ai || {},
|
|
123
139
|
eslintConfig: config.eslint || {},
|
|
124
|
-
heuristicConfig: {
|
|
140
|
+
heuristicConfig: {
|
|
125
141
|
...config.heuristic || {},
|
|
126
142
|
targetFiles: this.options.targetFiles, // Pass filtered files for semantic optimization
|
|
143
|
+
projectPath: this.getProjectPath(), // Pass target project path for semantic engine
|
|
127
144
|
maxSemanticFiles: this.options.maxSemanticFiles ? parseInt(this.options.maxSemanticFiles) : 1000,
|
|
128
145
|
verbose: this.options.verbose // Pass verbose for debugging
|
|
129
146
|
}
|
|
@@ -422,13 +439,39 @@ class CliActionHandler {
|
|
|
422
439
|
*/
|
|
423
440
|
async applyFileTargeting(config) {
|
|
424
441
|
// Handle both string and array input patterns
|
|
425
|
-
const inputPaths = Array.isArray(this.options.input)
|
|
426
|
-
? this.options.input
|
|
442
|
+
const inputPaths = Array.isArray(this.options.input)
|
|
443
|
+
? this.options.input
|
|
427
444
|
: [this.options.input];
|
|
428
|
-
|
|
445
|
+
|
|
429
446
|
return await this.fileTargetingService.getTargetFiles(inputPaths, config, this.options);
|
|
430
447
|
}
|
|
431
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Get project path from input for semantic engine
|
|
451
|
+
* Returns the directory path of the target for proper file resolution
|
|
452
|
+
*/
|
|
453
|
+
getProjectPath() {
|
|
454
|
+
const input = Array.isArray(this.options.input)
|
|
455
|
+
? this.options.input[0]
|
|
456
|
+
: this.options.input;
|
|
457
|
+
|
|
458
|
+
if (!input) {
|
|
459
|
+
return process.cwd();
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const absolutePath = path.isAbsolute(input) ? input : path.resolve(process.cwd(), input);
|
|
463
|
+
|
|
464
|
+
// If input is a file, return its directory; if directory, return as-is
|
|
465
|
+
try {
|
|
466
|
+
if (fs.existsSync(absolutePath) && fs.statSync(absolutePath).isFile()) {
|
|
467
|
+
return path.dirname(absolutePath);
|
|
468
|
+
}
|
|
469
|
+
return absolutePath;
|
|
470
|
+
} catch {
|
|
471
|
+
return absolutePath;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
432
475
|
/**
|
|
433
476
|
* Display analysis information
|
|
434
477
|
* Following Rule C006: Verb-noun naming
|
|
@@ -466,18 +509,64 @@ class CliActionHandler {
|
|
|
466
509
|
*/
|
|
467
510
|
handleExit(results) {
|
|
468
511
|
if (this.options.noExit) return;
|
|
469
|
-
|
|
512
|
+
|
|
470
513
|
// Check if any violations were found
|
|
471
|
-
const hasViolations = results.results?.some(result =>
|
|
514
|
+
const hasViolations = results.results?.some(result =>
|
|
472
515
|
result.violations && result.violations.length > 0
|
|
473
516
|
);
|
|
474
|
-
|
|
517
|
+
|
|
475
518
|
if (hasViolations && this.options.failOnViolations !== false) {
|
|
476
519
|
process.exit(1);
|
|
477
520
|
} else {
|
|
478
521
|
process.exit(0);
|
|
479
522
|
}
|
|
480
523
|
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Check if only architecture analysis was requested (no code quality rules)
|
|
527
|
+
* Following Rule C006: Verb-noun naming
|
|
528
|
+
*/
|
|
529
|
+
isArchitectureOnly() {
|
|
530
|
+
return this.options.architecture &&
|
|
531
|
+
!this.options.all &&
|
|
532
|
+
!this.options.rule &&
|
|
533
|
+
!this.options.rules &&
|
|
534
|
+
!this.options.quality &&
|
|
535
|
+
!this.options.security &&
|
|
536
|
+
!this.options.category;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Run architecture analysis using architecture-detection module
|
|
541
|
+
* Following Rule C006: Verb-noun naming
|
|
542
|
+
*/
|
|
543
|
+
async runArchitectureAnalysis() {
|
|
544
|
+
if (!this.options.quiet) {
|
|
545
|
+
console.log(chalk.blue('🏛️ Running architecture analysis...'));
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
try {
|
|
549
|
+
const integration = new ArchitectureIntegration(this.options);
|
|
550
|
+
const projectPath = this.getProjectPath();
|
|
551
|
+
const results = await integration.analyze(projectPath);
|
|
552
|
+
|
|
553
|
+
// Save markdown report if requested
|
|
554
|
+
if (this.options.archReport && results.markdownReport) {
|
|
555
|
+
const reportPath = await integration.saveReport(results.markdownReport, projectPath);
|
|
556
|
+
if (!this.options.quiet) {
|
|
557
|
+
console.log(chalk.green(`📄 Architecture report saved: ${reportPath}`));
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return results;
|
|
562
|
+
} catch (error) {
|
|
563
|
+
console.error(chalk.yellow(`⚠️ Architecture analysis failed: ${error.message}`));
|
|
564
|
+
if (this.options.debug) {
|
|
565
|
+
console.error(error.stack);
|
|
566
|
+
}
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
481
570
|
}
|
|
482
571
|
|
|
483
572
|
module.exports = CliActionHandler;
|
package/core/cli-program.js
CHANGED
|
@@ -23,6 +23,12 @@ function createCliProgram() {
|
|
|
23
23
|
.option('--quality', 'Run all code quality rules')
|
|
24
24
|
.option('--security', 'Run all secure coding rules');
|
|
25
25
|
|
|
26
|
+
// Architecture Analysis options
|
|
27
|
+
program
|
|
28
|
+
.option('--architecture', 'Enable architecture pattern detection (layered, modular, mvvm, viper)')
|
|
29
|
+
.option('--arch-patterns <patterns>', 'Target specific architecture patterns (comma-separated)')
|
|
30
|
+
.option('--arch-report', 'Generate separate architecture MD report');
|
|
31
|
+
|
|
26
32
|
// TypeScript specific options (Phase 1 focus)
|
|
27
33
|
program
|
|
28
34
|
.option('--typescript', 'Enable TypeScript-specific analysis')
|
|
@@ -156,11 +162,17 @@ Advanced File Targeting:
|
|
|
156
162
|
|
|
157
163
|
Large Project Optimization:
|
|
158
164
|
$ sunlint --all --input=. --max-semantic-files=500 # Conservative analysis
|
|
159
|
-
$ sunlint --all --input=. --max-semantic-files=2000 # Comprehensive analysis
|
|
165
|
+
$ sunlint --all --input=. --max-semantic-files=2000 # Comprehensive analysis
|
|
160
166
|
$ sunlint --all --input=. --max-semantic-files=-1 # Unlimited (all files)
|
|
161
167
|
$ sunlint --all --input=. --max-semantic-files=0 # Disable semantic analysis
|
|
162
168
|
$ sunlint --all --changed-files --max-semantic-files=300 # Fast CI analysis
|
|
163
169
|
|
|
170
|
+
Architecture Analysis:
|
|
171
|
+
$ sunlint --all --architecture --input=src # Code quality + architecture
|
|
172
|
+
$ sunlint --architecture --input=src # Architecture only
|
|
173
|
+
$ sunlint --architecture --arch-report --input=src # Generate MD report
|
|
174
|
+
$ sunlint --architecture --arch-patterns=mvvm,layered --input=src
|
|
175
|
+
|
|
164
176
|
Sun* Engineering - Coding Standards Made Simple ☀️
|
|
165
177
|
`);
|
|
166
178
|
|
package/core/output-service.js
CHANGED
|
@@ -115,6 +115,74 @@ class OutputService {
|
|
|
115
115
|
if (!options.quiet && options.format !== 'json') {
|
|
116
116
|
console.log(report.summary);
|
|
117
117
|
}
|
|
118
|
+
|
|
119
|
+
// Output architecture results if available
|
|
120
|
+
if (results.architecture && !options.quiet) {
|
|
121
|
+
this.outputArchitectureResults(results.architecture, options);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Output architecture analysis results
|
|
127
|
+
* @param {Object} archResults - Architecture analysis results
|
|
128
|
+
* @param {Object} options - Output options
|
|
129
|
+
*/
|
|
130
|
+
outputArchitectureResults(archResults, options) {
|
|
131
|
+
if (!archResults || !archResults.summary) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const summary = archResults.summary;
|
|
136
|
+
|
|
137
|
+
console.log(chalk.blue('\n🏛️ Architecture Analysis:'));
|
|
138
|
+
console.log('━'.repeat(50));
|
|
139
|
+
|
|
140
|
+
// Primary pattern
|
|
141
|
+
const confidence = Math.round(summary.primaryConfidence * 100);
|
|
142
|
+
console.log(`• Pattern: ${chalk.cyan(summary.primaryPattern)} (${confidence}% confidence)`);
|
|
143
|
+
|
|
144
|
+
// Secondary patterns
|
|
145
|
+
if (summary.secondaryPatterns && summary.secondaryPatterns.length > 0) {
|
|
146
|
+
const secondary = summary.secondaryPatterns
|
|
147
|
+
.map(p => `${p.pattern} (${Math.round(p.confidence * 100)}%)`)
|
|
148
|
+
.join(', ');
|
|
149
|
+
console.log(`• Secondary: ${chalk.gray(secondary)}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Health score
|
|
153
|
+
const healthScore = Math.round(summary.healthScore);
|
|
154
|
+
const healthColor = healthScore >= 80 ? chalk.green :
|
|
155
|
+
healthScore >= 60 ? chalk.yellow : chalk.red;
|
|
156
|
+
console.log(`• Health Score: ${healthColor(healthScore + '/100')}`);
|
|
157
|
+
|
|
158
|
+
// Violations
|
|
159
|
+
if (summary.violationCount > 0) {
|
|
160
|
+
console.log(`• Violations: ${chalk.red(summary.violationCount)}`);
|
|
161
|
+
|
|
162
|
+
// Show first 5 violations
|
|
163
|
+
if (archResults.violations && archResults.violations.length > 0) {
|
|
164
|
+
console.log(chalk.gray('\nTop Architecture Violations:'));
|
|
165
|
+
archResults.violations.slice(0, 5).forEach((v, i) => {
|
|
166
|
+
const severity = v.severity === 'error' ? chalk.red('✗') : chalk.yellow('⚠');
|
|
167
|
+
console.log(` ${severity} ${v.message}`);
|
|
168
|
+
if (v.file) {
|
|
169
|
+
console.log(chalk.gray(` → ${v.file}:${v.line || 1}`));
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
if (archResults.violations.length > 5) {
|
|
173
|
+
console.log(chalk.gray(` ... and ${archResults.violations.length - 5} more`));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
console.log(`• Violations: ${chalk.green('None')}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Report file info
|
|
181
|
+
if (archResults.markdownReport && options.archReport) {
|
|
182
|
+
console.log(chalk.gray('\n📄 Full architecture report saved'));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
console.log();
|
|
118
186
|
}
|
|
119
187
|
|
|
120
188
|
generateAndSaveSummaryReport(violations, results, options, metadata) {
|
|
@@ -463,35 +531,30 @@ class OutputService {
|
|
|
463
531
|
});
|
|
464
532
|
|
|
465
533
|
if (uploadResult.success) {
|
|
466
|
-
if (!options.quiet) {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
console.log(chalk.blue(`👤 Submitted by: ${responseData.actor}`));
|
|
485
|
-
}
|
|
486
|
-
} catch (parseError) {
|
|
487
|
-
// If response is not JSON, show raw response
|
|
534
|
+
if (!options.quiet && uploadResult.response) {
|
|
535
|
+
try {
|
|
536
|
+
const responseData = JSON.parse(uploadResult.response);
|
|
537
|
+
if (responseData.message) {
|
|
538
|
+
console.log(chalk.blue(`💬 Server response: ${responseData.message}`));
|
|
539
|
+
}
|
|
540
|
+
if (responseData.report_id) {
|
|
541
|
+
console.log(chalk.blue(`📝 Report ID: ${responseData.report_id}`));
|
|
542
|
+
}
|
|
543
|
+
if (responseData.repository) {
|
|
544
|
+
console.log(chalk.blue(`🏠 Repository: ${responseData.repository}`));
|
|
545
|
+
}
|
|
546
|
+
if (responseData.actor) {
|
|
547
|
+
console.log(chalk.blue(`👤 Submitted by: ${responseData.actor}`));
|
|
548
|
+
}
|
|
549
|
+
} catch (parseError) {
|
|
550
|
+
// If response is not JSON, show raw response if verbose
|
|
551
|
+
if (options.verbose) {
|
|
488
552
|
console.log(chalk.gray(`📄 Response: ${uploadResult.response.substring(0, 200)}...`));
|
|
489
553
|
}
|
|
490
554
|
}
|
|
491
555
|
}
|
|
492
556
|
} else {
|
|
493
|
-
|
|
494
|
-
|
|
557
|
+
// Error already logged in upload-service.js
|
|
495
558
|
if (options.verbose && uploadResult.errorContext) {
|
|
496
559
|
console.warn('Upload error details:', uploadResult.errorContext);
|
|
497
560
|
}
|