@sun-asterisk/sunlint 1.2.1 ā 1.2.2
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/config/rule-analysis-strategies.js +18 -2
- package/engines/eslint-engine.js +9 -11
- package/engines/heuristic-engine.js +55 -31
- package/package.json +2 -1
- package/rules/README.md +252 -0
- package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
- package/rules/common/C002_no_duplicate_code/config.json +23 -0
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
- package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
- package/rules/common/C006_function_naming/analyzer.js +504 -0
- package/rules/common/C006_function_naming/config.json +86 -0
- package/rules/common/C006_function_naming/smart-analyzer.js +503 -0
- package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
- package/rules/common/C012_command_query_separation/analyzer.js +481 -0
- package/rules/common/C012_command_query_separation/ast-analyzer.js +495 -0
- package/rules/common/C013_no_dead_code/analyzer.js +206 -0
- package/rules/common/C014_dependency_injection/analyzer.js +338 -0
- package/rules/common/C017_constructor_logic/analyzer.js +314 -0
- package/rules/common/C019_log_level_usage/analyzer.js +362 -0
- package/rules/common/C019_log_level_usage/config.json +121 -0
- package/rules/common/C029_catch_block_logging/analyzer-backup.js +426 -0
- package/rules/common/C029_catch_block_logging/analyzer-fixed.js +130 -0
- package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +487 -0
- package/rules/common/C029_catch_block_logging/analyzer-simple.js +110 -0
- package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +755 -0
- package/rules/common/C029_catch_block_logging/analyzer.js +129 -0
- package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +441 -0
- package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +127 -0
- package/rules/common/C029_catch_block_logging/ast-analyzer.js +133 -0
- package/rules/common/C029_catch_block_logging/cfg-analyzer.js +408 -0
- package/rules/common/C029_catch_block_logging/config.json +59 -0
- package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +454 -0
- package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +700 -0
- package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +568 -0
- package/rules/common/C029_catch_block_logging/semantic-analyzer.js +459 -0
- package/rules/common/C031_validation_separation/analyzer.js +186 -0
- package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
- package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +296 -0
- package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
- package/rules/common/C043_no_console_or_print/analyzer.js +431 -0
- package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +590 -0
- package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
- package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
- package/rules/docs/C002_no_duplicate_code.md +57 -0
- package/rules/docs/C031_validation_separation.md +72 -0
- package/rules/index.js +155 -0
- package/rules/migration/converter.js +385 -0
- package/rules/migration/mapping.json +164 -0
- package/rules/parser/constants.js +31 -0
- package/rules/parser/file-config.js +80 -0
- package/rules/parser/rule-parser-simple.js +305 -0
- package/rules/parser/rule-parser.js +527 -0
- package/rules/security/S015_insecure_tls_certificate/analyzer.js +150 -0
- package/rules/security/S015_insecure_tls_certificate/ast-analyzer.js +237 -0
- package/rules/security/S023_no_json_injection/analyzer.js +278 -0
- package/rules/security/S023_no_json_injection/ast-analyzer.js +359 -0
- package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
- package/rules/security/S026_json_schema_validation/config.json +27 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +436 -0
- package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
- package/rules/security/S029_csrf_protection/analyzer.js +330 -0
- package/rules/tests/C002_no_duplicate_code.test.js +50 -0
- package/rules/universal/C010/generic.js +0 -0
- package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
- package/rules/utils/ast-utils.js +191 -0
- package/rules/utils/base-analyzer.js +98 -0
- package/rules/utils/pattern-matchers.js +239 -0
- package/rules/utils/rule-helpers.js +264 -0
- package/rules/utils/severity-constants.js +93 -0
- package/scripts/generate_insights.js +188 -0
- package/scripts/merge-reports.js +0 -424
- package/scripts/test-scripts/README.md +0 -22
- package/scripts/test-scripts/test-c041-comparison.js +0 -114
- package/scripts/test-scripts/test-c041-eslint.js +0 -67
- package/scripts/test-scripts/test-eslint-rules.js +0 -146
- package/scripts/test-scripts/test-real-world.js +0 -44
- package/scripts/test-scripts/test-rules-on-real-projects.js +0 -86
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { SimpleRuleParser } = require('../rules/parser/rule-parser-simple.js');
|
|
6
|
+
|
|
7
|
+
class SunLintInsightGenerator {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.parser = new SimpleRuleParser();
|
|
10
|
+
this.heuristicRulesPath = path.join(__dirname, '../rules/common');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Get implemented rules
|
|
14
|
+
getImplementedRules() {
|
|
15
|
+
const implemented = new Set();
|
|
16
|
+
if (fs.existsSync(this.heuristicRulesPath)) {
|
|
17
|
+
const dirs = fs.readdirSync(this.heuristicRulesPath);
|
|
18
|
+
dirs.forEach(dir => {
|
|
19
|
+
const match = dir.match(/^([A-Z]\d+)_/);
|
|
20
|
+
if (match) {
|
|
21
|
+
implemented.add(match[1]);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return implemented;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Assess priority based on rule content
|
|
29
|
+
assessPriority(rule) {
|
|
30
|
+
const text = `${rule.title} ${rule.description} ${rule.detail || ''}`.toLowerCase();
|
|
31
|
+
const principles = rule.principles || [];
|
|
32
|
+
|
|
33
|
+
if (principles.includes('SECURITY')) return 'High';
|
|
34
|
+
if (principles.includes('PERFORMANCE')) return 'High';
|
|
35
|
+
if (principles.includes('RELIABILITY')) return 'Medium-High';
|
|
36
|
+
|
|
37
|
+
const impactKeywords = {
|
|
38
|
+
high: ['security', 'memory', 'performance', 'crash', 'null pointer', 'xss', 'injection', 'vulnerability', 'race condition'],
|
|
39
|
+
medium: ['testability', 'maintainability', 'readability', 'coupling', 'cohesion', 'dependency'],
|
|
40
|
+
low: ['naming', 'style', 'convention', 'formatting', 'comment']
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
for (const keyword of impactKeywords.high) {
|
|
44
|
+
if (text.includes(keyword)) return 'High';
|
|
45
|
+
}
|
|
46
|
+
for (const keyword of impactKeywords.medium) {
|
|
47
|
+
if (text.includes(keyword)) return 'Medium';
|
|
48
|
+
}
|
|
49
|
+
for (const keyword of impactKeywords.low) {
|
|
50
|
+
if (text.includes(keyword)) return 'Low';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return 'Medium';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Generate actionable insights
|
|
57
|
+
generateInsights() {
|
|
58
|
+
console.log('š Analyzing SunLint rules...');
|
|
59
|
+
const allRules = this.parser.parseAllRules();
|
|
60
|
+
const activatedRules = this.parser.filterRules(allRules, { status: 'activated' });
|
|
61
|
+
const implementedRules = this.getImplementedRules();
|
|
62
|
+
|
|
63
|
+
console.log('\nš === SUNLINT HEURISTIC ENGINE ANALYSIS ===\n');
|
|
64
|
+
|
|
65
|
+
// Overall statistics
|
|
66
|
+
const totalImplemented = activatedRules.filter(rule => implementedRules.has(rule.id)).length;
|
|
67
|
+
const implementationRate = ((totalImplemented / activatedRules.length) * 100).toFixed(1);
|
|
68
|
+
|
|
69
|
+
console.log(`šÆ **Current Implementation Status:**`);
|
|
70
|
+
console.log(` ⢠Total Activated Rules: ${activatedRules.length}`);
|
|
71
|
+
console.log(` ⢠Implemented in Heuristic: ${totalImplemented} (${implementationRate}%)`);
|
|
72
|
+
console.log(` ⢠Remaining to Implement: ${activatedRules.length - totalImplemented}\n`);
|
|
73
|
+
|
|
74
|
+
// Priority analysis
|
|
75
|
+
const priorities = { 'High': 0, 'Medium-High': 0, 'Medium': 0, 'Low': 0 };
|
|
76
|
+
const notImplementedByPriority = { 'High': [], 'Medium-High': [], 'Medium': [], 'Low': [] };
|
|
77
|
+
|
|
78
|
+
activatedRules.forEach(rule => {
|
|
79
|
+
const priority = this.assessPriority(rule);
|
|
80
|
+
priorities[priority]++;
|
|
81
|
+
|
|
82
|
+
if (!implementedRules.has(rule.id)) {
|
|
83
|
+
notImplementedByPriority[priority].push(rule);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
console.log(`š„ **Priority Breakdown:**`);
|
|
88
|
+
Object.entries(priorities).forEach(([priority, count]) => {
|
|
89
|
+
const missing = notImplementedByPriority[priority].length;
|
|
90
|
+
const implemented = count - missing;
|
|
91
|
+
const rate = count > 0 ? ((implemented / count) * 100).toFixed(1) : '0.0';
|
|
92
|
+
console.log(` ⢠${priority}: ${implemented}/${count} implemented (${rate}%) - ${missing} missing`);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Category analysis
|
|
96
|
+
console.log(`\nš **Implementation by Category:**`);
|
|
97
|
+
const categories = {
|
|
98
|
+
'C': 'Common Code Quality',
|
|
99
|
+
'T': 'TypeScript',
|
|
100
|
+
'R': 'ReactJS',
|
|
101
|
+
'S': 'Security',
|
|
102
|
+
'J': 'Java',
|
|
103
|
+
'K': 'Kotlin Mobile',
|
|
104
|
+
'D': 'Dart/Flutter',
|
|
105
|
+
'SW': 'Swift'
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
Object.entries(categories).forEach(([prefix, name]) => {
|
|
109
|
+
const categoryRules = activatedRules.filter(rule => rule.id.startsWith(prefix));
|
|
110
|
+
const categoryImplemented = categoryRules.filter(rule => implementedRules.has(rule.id)).length;
|
|
111
|
+
const rate = categoryRules.length > 0 ? ((categoryImplemented / categoryRules.length) * 100).toFixed(1) : '0.0';
|
|
112
|
+
console.log(` ⢠${name}: ${categoryImplemented}/${categoryRules.length} (${rate}%)`);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Top missing high-priority rules
|
|
116
|
+
console.log(`\nšØ **Top 10 Missing High-Priority Rules:**`);
|
|
117
|
+
notImplementedByPriority['High'].slice(0, 10).forEach((rule, i) => {
|
|
118
|
+
console.log(` ${i+1}. ${rule.id}: ${rule.title}`);
|
|
119
|
+
console.log(` ā ${rule.description?.substring(0, 80)}...`);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Quick wins (easy to implement)
|
|
123
|
+
console.log(`\nā” **Quick Wins (Low Complexity, High Impact):**`);
|
|
124
|
+
const quickWins = notImplementedByPriority['High'].filter(rule => {
|
|
125
|
+
const text = rule.title.toLowerCase();
|
|
126
|
+
return text.includes('console.log') || text.includes('print') ||
|
|
127
|
+
text.includes('hardcode') || text.includes('sensitive');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
quickWins.slice(0, 5).forEach((rule, i) => {
|
|
131
|
+
console.log(` ${i+1}. ${rule.id}: ${rule.title} - Text/Regex patterns`);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Performance impact rules
|
|
135
|
+
console.log(`\nā” **Performance-Critical Missing Rules:**`);
|
|
136
|
+
const perfRules = notImplementedByPriority['High'].filter(rule =>
|
|
137
|
+
rule.principles?.includes('PERFORMANCE') ||
|
|
138
|
+
rule.description?.toLowerCase().includes('performance')
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
perfRules.slice(0, 5).forEach((rule, i) => {
|
|
142
|
+
console.log(` ${i+1}. ${rule.id}: ${rule.title}`);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Security rules
|
|
146
|
+
console.log(`\nš **Security-Critical Missing Rules:**`);
|
|
147
|
+
const securityRules = notImplementedByPriority['High'].filter(rule =>
|
|
148
|
+
rule.principles?.includes('SECURITY') ||
|
|
149
|
+
rule.description?.toLowerCase().includes('security')
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
securityRules.slice(0, 5).forEach((rule, i) => {
|
|
153
|
+
console.log(` ${i+1}. ${rule.id}: ${rule.title}`);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Recommendations
|
|
157
|
+
console.log(`\nš” **Actionable Recommendations:**`);
|
|
158
|
+
console.log(` 1. Focus on High Priority rules first: ${notImplementedByPriority['High'].length} rules remaining`);
|
|
159
|
+
console.log(` 2. Implement Common (C) rules first - highest coverage impact`);
|
|
160
|
+
console.log(` 3. Start with Low complexity rules using text/regex patterns`);
|
|
161
|
+
console.log(` 4. Performance rules should be prioritized for production impact`);
|
|
162
|
+
console.log(` 5. Security rules are critical for safe code practices`);
|
|
163
|
+
console.log(` 6. Consider AST analysis for complex rules (Medium-High complexity)`);
|
|
164
|
+
|
|
165
|
+
console.log(`\nš **ROI Analysis:**`);
|
|
166
|
+
const commonMissing = notImplementedByPriority['High'].filter(rule => rule.id.startsWith('C')).length;
|
|
167
|
+
console.log(` ⢠Common rules impact: All languages (${commonMissing} High-priority missing)`);
|
|
168
|
+
console.log(` ⢠Language-specific rules: Limited scope but deep impact`);
|
|
169
|
+
console.log(` ⢠Implementation effort: Low complexity rules = 1-2 days, High = 1-2 weeks`);
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
total: activatedRules.length,
|
|
173
|
+
implemented: totalImplemented,
|
|
174
|
+
missing: activatedRules.length - totalImplemented,
|
|
175
|
+
highPriorityMissing: notImplementedByPriority['High'].length,
|
|
176
|
+
quickWins: quickWins.length,
|
|
177
|
+
recommendations: notImplementedByPriority
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Run analysis
|
|
183
|
+
if (require.main === module) {
|
|
184
|
+
const analyzer = new SunLintInsightGenerator();
|
|
185
|
+
analyzer.generateInsights();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
module.exports = SunLintInsightGenerator;
|
package/scripts/merge-reports.js
DELETED
|
@@ -1,424 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Report Merger - Combines ESLint and SunLint results into unified report
|
|
5
|
-
* Usage: node merge-reports.js eslint-results.json sunlint-results.json --format=summary --output=unified.json
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const { Command } = require('commander');
|
|
11
|
-
const chalk = require('chalk');
|
|
12
|
-
|
|
13
|
-
class ReportMerger {
|
|
14
|
-
constructor() {
|
|
15
|
-
this.version = '1.0.0';
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async mergeReports(eslintPath, sunlintPath, options = {}) {
|
|
19
|
-
const eslintReport = this.loadReport(eslintPath, 'eslint');
|
|
20
|
-
const sunlintReport = this.loadReport(sunlintPath, 'sunlint');
|
|
21
|
-
|
|
22
|
-
const unifiedReport = {
|
|
23
|
-
metadata: {
|
|
24
|
-
timestamp: new Date().toISOString(),
|
|
25
|
-
merger_version: this.version,
|
|
26
|
-
input_files: {
|
|
27
|
-
eslint: eslintPath,
|
|
28
|
-
sunlint: sunlintPath
|
|
29
|
-
},
|
|
30
|
-
tools: {
|
|
31
|
-
eslint: this.extractESLintMetadata(eslintReport),
|
|
32
|
-
sunlint: this.extractSunLintMetadata(sunlintReport)
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
summary: this.generateSummary(eslintReport, sunlintReport),
|
|
36
|
-
violations: this.mergeViolations(eslintReport, sunlintReport),
|
|
37
|
-
files: this.mergeFileAnalysis(eslintReport, sunlintReport),
|
|
38
|
-
rules: this.mergeRuleAnalysis(eslintReport, sunlintReport),
|
|
39
|
-
raw_reports: {
|
|
40
|
-
eslint: eslintReport,
|
|
41
|
-
sunlint: sunlintReport
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
return unifiedReport;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
loadReport(filePath, toolName) {
|
|
49
|
-
if (!fs.existsSync(filePath)) {
|
|
50
|
-
console.warn(chalk.yellow(`ā ļø ${toolName} report not found: ${filePath}`));
|
|
51
|
-
return this.getEmptyReport(toolName);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
56
|
-
return JSON.parse(content);
|
|
57
|
-
} catch (error) {
|
|
58
|
-
console.error(chalk.red(`ā Failed to parse ${toolName} report: ${error.message}`));
|
|
59
|
-
return this.getEmptyReport(toolName);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
getEmptyReport(toolName) {
|
|
64
|
-
return {
|
|
65
|
-
tool: toolName,
|
|
66
|
-
files: [],
|
|
67
|
-
violations: [],
|
|
68
|
-
summary: { total: 0, errors: 0, warnings: 0, info: 0 }
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
extractESLintMetadata(report) {
|
|
73
|
-
return {
|
|
74
|
-
version: report.version || 'unknown',
|
|
75
|
-
rules_count: report.rules ? Object.keys(report.rules).length : 0,
|
|
76
|
-
files_analyzed: report.files ? report.files.length : 0
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
extractSunLintMetadata(report) {
|
|
81
|
-
return {
|
|
82
|
-
version: report.version || 'unknown',
|
|
83
|
-
rules_count: report.rulesRun || 0,
|
|
84
|
-
files_analyzed: report.filesAnalyzed || 0,
|
|
85
|
-
ai_enabled: report.aiEnabled || false
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
generateSummary(eslintReport, sunlintReport) {
|
|
90
|
-
const eslintSummary = this.getSummaryFromReport(eslintReport, 'eslint');
|
|
91
|
-
const sunlintSummary = this.getSummaryFromReport(sunlintReport, 'sunlint');
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
total: {
|
|
95
|
-
files: eslintSummary.files + sunlintSummary.files,
|
|
96
|
-
violations: eslintSummary.violations + sunlintSummary.violations,
|
|
97
|
-
errors: eslintSummary.errors + sunlintSummary.errors,
|
|
98
|
-
warnings: eslintSummary.warnings + sunlintSummary.warnings,
|
|
99
|
-
info: eslintSummary.info + sunlintSummary.info
|
|
100
|
-
},
|
|
101
|
-
by_tool: {
|
|
102
|
-
eslint: eslintSummary,
|
|
103
|
-
sunlint: sunlintSummary
|
|
104
|
-
},
|
|
105
|
-
breakdown: {
|
|
106
|
-
overlap_files: this.countOverlapFiles(eslintReport, sunlintReport),
|
|
107
|
-
unique_eslint_rules: this.getUniqueRules(eslintReport, 'eslint'),
|
|
108
|
-
unique_sunlint_rules: this.getUniqueRules(sunlintReport, 'sunlint')
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
getSummaryFromReport(report, toolName) {
|
|
114
|
-
if (toolName === 'eslint') {
|
|
115
|
-
// ESLint format
|
|
116
|
-
const violations = this.extractESLintViolations(report);
|
|
117
|
-
return {
|
|
118
|
-
files: report.files ? report.files.length : 0,
|
|
119
|
-
violations: violations.length,
|
|
120
|
-
errors: violations.filter(v => v.severity === 'error').length,
|
|
121
|
-
warnings: violations.filter(v => v.severity === 'warning').length,
|
|
122
|
-
info: violations.filter(v => v.severity === 'info').length
|
|
123
|
-
};
|
|
124
|
-
} else {
|
|
125
|
-
// SunLint format
|
|
126
|
-
const violations = report.results ?
|
|
127
|
-
report.results.flatMap(r => r.violations || []) : [];
|
|
128
|
-
return {
|
|
129
|
-
files: report.filesAnalyzed || 0,
|
|
130
|
-
violations: violations.length,
|
|
131
|
-
errors: violations.filter(v => v.severity === 'error').length,
|
|
132
|
-
warnings: violations.filter(v => v.severity === 'warning').length,
|
|
133
|
-
info: violations.filter(v => v.severity === 'info').length
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
mergeViolations(eslintReport, sunlintReport) {
|
|
139
|
-
const eslintViolations = this.extractESLintViolations(eslintReport);
|
|
140
|
-
const sunlintViolations = this.extractSunLintViolations(sunlintReport);
|
|
141
|
-
|
|
142
|
-
const allViolations = [
|
|
143
|
-
...eslintViolations.map(v => ({ ...v, tool: 'eslint' })),
|
|
144
|
-
...sunlintViolations.map(v => ({ ...v, tool: 'sunlint' }))
|
|
145
|
-
];
|
|
146
|
-
|
|
147
|
-
// Sort by severity and file
|
|
148
|
-
return allViolations.sort((a, b) => {
|
|
149
|
-
const severityOrder = { error: 0, warning: 1, info: 2 };
|
|
150
|
-
if (a.severity !== b.severity) {
|
|
151
|
-
return severityOrder[a.severity] - severityOrder[b.severity];
|
|
152
|
-
}
|
|
153
|
-
return a.file.localeCompare(b.file);
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
extractESLintViolations(report) {
|
|
158
|
-
if (!report.files || !Array.isArray(report.files)) return [];
|
|
159
|
-
|
|
160
|
-
return report.files.flatMap(file =>
|
|
161
|
-
(file.messages || []).map(msg => ({
|
|
162
|
-
file: file.filePath || file.file,
|
|
163
|
-
line: msg.line,
|
|
164
|
-
column: msg.column,
|
|
165
|
-
ruleId: msg.ruleId,
|
|
166
|
-
message: msg.message,
|
|
167
|
-
severity: this.mapESLintSeverity(msg.severity)
|
|
168
|
-
}))
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
extractSunLintViolations(report) {
|
|
173
|
-
if (!report.results || !Array.isArray(report.results)) return [];
|
|
174
|
-
|
|
175
|
-
return report.results.flatMap(result =>
|
|
176
|
-
(result.violations || []).map(violation => ({
|
|
177
|
-
file: violation.file,
|
|
178
|
-
line: violation.line,
|
|
179
|
-
column: violation.column,
|
|
180
|
-
ruleId: violation.ruleId,
|
|
181
|
-
message: violation.message,
|
|
182
|
-
severity: violation.severity
|
|
183
|
-
}))
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
mapESLintSeverity(severity) {
|
|
188
|
-
switch (severity) {
|
|
189
|
-
case 1: return 'warning';
|
|
190
|
-
case 2: return 'error';
|
|
191
|
-
default: return 'info';
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
mergeFileAnalysis(eslintReport, sunlintReport) {
|
|
196
|
-
const fileMap = new Map();
|
|
197
|
-
|
|
198
|
-
// Process ESLint files
|
|
199
|
-
if (eslintReport.files) {
|
|
200
|
-
eslintReport.files.forEach(file => {
|
|
201
|
-
const fileName = file.filePath || file.file;
|
|
202
|
-
fileMap.set(fileName, {
|
|
203
|
-
file: fileName,
|
|
204
|
-
eslint: {
|
|
205
|
-
violations: file.messages ? file.messages.length : 0,
|
|
206
|
-
rules_triggered: [...new Set((file.messages || []).map(m => m.ruleId))]
|
|
207
|
-
},
|
|
208
|
-
sunlint: { violations: 0, rules_triggered: [] }
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Process SunLint files
|
|
214
|
-
if (sunlintReport.results) {
|
|
215
|
-
sunlintReport.results.forEach(result => {
|
|
216
|
-
const fileName = result.file;
|
|
217
|
-
if (!fileMap.has(fileName)) {
|
|
218
|
-
fileMap.set(fileName, {
|
|
219
|
-
file: fileName,
|
|
220
|
-
eslint: { violations: 0, rules_triggered: [] },
|
|
221
|
-
sunlint: { violations: 0, rules_triggered: [] }
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const fileData = fileMap.get(fileName);
|
|
226
|
-
fileData.sunlint = {
|
|
227
|
-
violations: result.violations ? result.violations.length : 0,
|
|
228
|
-
rules_triggered: [...new Set((result.violations || []).map(v => v.ruleId))]
|
|
229
|
-
};
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return Array.from(fileMap.values());
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
mergeRuleAnalysis(eslintReport, sunlintReport) {
|
|
237
|
-
const ruleMap = new Map();
|
|
238
|
-
|
|
239
|
-
// Analyze ESLint rules
|
|
240
|
-
this.extractESLintViolations(eslintReport).forEach(violation => {
|
|
241
|
-
if (!ruleMap.has(violation.ruleId)) {
|
|
242
|
-
ruleMap.set(violation.ruleId, {
|
|
243
|
-
rule: violation.ruleId,
|
|
244
|
-
tool: 'eslint',
|
|
245
|
-
violations: 0,
|
|
246
|
-
files: new Set()
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
const rule = ruleMap.get(violation.ruleId);
|
|
250
|
-
rule.violations++;
|
|
251
|
-
rule.files.add(violation.file);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// Analyze SunLint rules
|
|
255
|
-
this.extractSunLintViolations(sunlintReport).forEach(violation => {
|
|
256
|
-
if (!ruleMap.has(violation.ruleId)) {
|
|
257
|
-
ruleMap.set(violation.ruleId, {
|
|
258
|
-
rule: violation.ruleId,
|
|
259
|
-
tool: 'sunlint',
|
|
260
|
-
violations: 0,
|
|
261
|
-
files: new Set()
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
const rule = ruleMap.get(violation.ruleId);
|
|
265
|
-
rule.violations++;
|
|
266
|
-
rule.files.add(violation.file);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
// Convert Sets to arrays and sort
|
|
270
|
-
return Array.from(ruleMap.values())
|
|
271
|
-
.map(rule => ({
|
|
272
|
-
...rule,
|
|
273
|
-
files: Array.from(rule.files),
|
|
274
|
-
files_count: rule.files.size
|
|
275
|
-
}))
|
|
276
|
-
.sort((a, b) => b.violations - a.violations);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
countOverlapFiles(eslintReport, sunlintReport) {
|
|
280
|
-
const eslintFiles = new Set();
|
|
281
|
-
const sunlintFiles = new Set();
|
|
282
|
-
|
|
283
|
-
if (eslintReport.files) {
|
|
284
|
-
eslintReport.files.forEach(f => eslintFiles.add(f.filePath || f.file));
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (sunlintReport.results) {
|
|
288
|
-
sunlintReport.results.forEach(r => sunlintFiles.add(r.file));
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
return [...eslintFiles].filter(file => sunlintFiles.has(file)).length;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
getUniqueRules(report, toolName) {
|
|
295
|
-
if (toolName === 'eslint') {
|
|
296
|
-
return [...new Set(this.extractESLintViolations(report).map(v => v.ruleId))];
|
|
297
|
-
} else {
|
|
298
|
-
return [...new Set(this.extractSunLintViolations(report).map(v => v.ruleId))];
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
formatReport(unifiedReport, format) {
|
|
303
|
-
switch (format) {
|
|
304
|
-
case 'json':
|
|
305
|
-
return JSON.stringify(unifiedReport, null, 2);
|
|
306
|
-
case 'summary':
|
|
307
|
-
return this.formatSummary(unifiedReport);
|
|
308
|
-
case 'github':
|
|
309
|
-
return this.formatGitHub(unifiedReport);
|
|
310
|
-
case 'detailed':
|
|
311
|
-
return this.formatDetailed(unifiedReport);
|
|
312
|
-
default:
|
|
313
|
-
return this.formatSummary(unifiedReport);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
formatSummary(report) {
|
|
318
|
-
const { summary } = report;
|
|
319
|
-
return `
|
|
320
|
-
šÆ Sun* Engineering - Unified Code Quality Report
|
|
321
|
-
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
322
|
-
|
|
323
|
-
š Overall Summary:
|
|
324
|
-
Files analyzed: ${summary.total.files}
|
|
325
|
-
Total violations: ${summary.total.violations}
|
|
326
|
-
|
|
327
|
-
šØ By Severity:
|
|
328
|
-
Errors: ${summary.total.errors}
|
|
329
|
-
Warnings: ${summary.total.warnings}
|
|
330
|
-
Info: ${summary.total.info}
|
|
331
|
-
|
|
332
|
-
š§ By Tool:
|
|
333
|
-
ESLint: ${summary.by_tool.eslint.violations} violations (${summary.by_tool.eslint.files} files)
|
|
334
|
-
SunLint: ${summary.by_tool.sunlint.violations} violations (${summary.by_tool.sunlint.files} files)
|
|
335
|
-
|
|
336
|
-
š Coverage:
|
|
337
|
-
Overlap files: ${summary.breakdown.overlap_files}
|
|
338
|
-
ESLint rules: ${summary.breakdown.unique_eslint_rules.length}
|
|
339
|
-
SunLint rules: ${summary.breakdown.unique_sunlint_rules.length}
|
|
340
|
-
|
|
341
|
-
Generated: ${report.metadata.timestamp}
|
|
342
|
-
`;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
formatGitHub(report) {
|
|
346
|
-
const annotations = report.violations
|
|
347
|
-
.filter(v => v.severity === 'error')
|
|
348
|
-
.slice(0, 10) // GitHub limits annotations
|
|
349
|
-
.map(v => `::error file=${v.file},line=${v.line}::${v.message} (${v.ruleId})`)
|
|
350
|
-
.join('\n');
|
|
351
|
-
|
|
352
|
-
return annotations + '\n' + this.formatSummary(report);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
formatDetailed(report) {
|
|
356
|
-
let output = this.formatSummary(report);
|
|
357
|
-
|
|
358
|
-
output += '\nš Top Violated Rules:\n';
|
|
359
|
-
report.rules.slice(0, 10).forEach(rule => {
|
|
360
|
-
output += ` ${rule.rule}: ${rule.violations} violations (${rule.files_count} files) [${rule.tool}]\n`;
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
output += '\nš Most Problematic Files:\n';
|
|
364
|
-
const filesByViolations = report.files
|
|
365
|
-
.map(f => ({
|
|
366
|
-
...f,
|
|
367
|
-
total: f.eslint.violations + f.sunlint.violations
|
|
368
|
-
}))
|
|
369
|
-
.sort((a, b) => b.total - a.total)
|
|
370
|
-
.slice(0, 10);
|
|
371
|
-
|
|
372
|
-
filesByViolations.forEach(file => {
|
|
373
|
-
output += ` ${file.file}: ${file.total} violations (ESLint: ${file.eslint.violations}, SunLint: ${file.sunlint.violations})\n`;
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
return output;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// CLI
|
|
381
|
-
const program = new Command();
|
|
382
|
-
|
|
383
|
-
program
|
|
384
|
-
.name('merge-reports')
|
|
385
|
-
.description('Merge ESLint and SunLint reports into unified report')
|
|
386
|
-
.version('1.0.0')
|
|
387
|
-
.argument('<eslint-report>', 'ESLint JSON report file')
|
|
388
|
-
.argument('<sunlint-report>', 'SunLint JSON report file')
|
|
389
|
-
.option('-f, --format <format>', 'Output format (json,summary,github,detailed)', 'summary')
|
|
390
|
-
.option('-o, --output <file>', 'Output file (default: console)')
|
|
391
|
-
.option('--verbose', 'Verbose logging');
|
|
392
|
-
|
|
393
|
-
program.action(async (eslintReport, sunlintReport, options) => {
|
|
394
|
-
try {
|
|
395
|
-
const merger = new ReportMerger();
|
|
396
|
-
|
|
397
|
-
if (options.verbose) {
|
|
398
|
-
console.log(chalk.blue('š Merging reports...'));
|
|
399
|
-
console.log(`ESLint: ${eslintReport}`);
|
|
400
|
-
console.log(`SunLint: ${sunlintReport}`);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const unifiedReport = await merger.mergeReports(eslintReport, sunlintReport, options);
|
|
404
|
-
const formattedOutput = merger.formatReport(unifiedReport, options.format);
|
|
405
|
-
|
|
406
|
-
if (options.output) {
|
|
407
|
-
fs.writeFileSync(options.output, formattedOutput);
|
|
408
|
-
console.log(chalk.green(`ā
Unified report saved: ${options.output}`));
|
|
409
|
-
} else {
|
|
410
|
-
console.log(formattedOutput);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
} catch (error) {
|
|
414
|
-
console.error(chalk.red('ā Report merge failed:'), error.message);
|
|
415
|
-
if (options.verbose) {
|
|
416
|
-
console.error(error.stack);
|
|
417
|
-
}
|
|
418
|
-
process.exit(1);
|
|
419
|
-
}
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
program.parse();
|
|
423
|
-
|
|
424
|
-
module.exports = ReportMerger;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# Test Scripts
|
|
2
|
-
|
|
3
|
-
This directory contains various test scripts for SunLint development and validation.
|
|
4
|
-
|
|
5
|
-
## Files
|
|
6
|
-
|
|
7
|
-
- `test-eslint-rules.js` - Tests ESLint rule implementations
|
|
8
|
-
- `test-rules-on-real-projects.js` - Tests rules on real project samples
|
|
9
|
-
- `test-real-world.js` - Real-world testing scenarios
|
|
10
|
-
- `test-c041-eslint.js` - Specific tests for C041 ESLint rule
|
|
11
|
-
- `test-c041-comparison.js` - Comparison tests between Heuristic and ESLint for C041
|
|
12
|
-
|
|
13
|
-
## Usage
|
|
14
|
-
|
|
15
|
-
Run any test script from the project root:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
node scripts/test-scripts/test-eslint-rules.js
|
|
19
|
-
node scripts/test-scripts/test-rules-on-real-projects.js
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
These scripts are used for development, validation, and debugging of SunLint rules.
|