@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.
Files changed (77) hide show
  1. package/config/rule-analysis-strategies.js +18 -2
  2. package/engines/eslint-engine.js +9 -11
  3. package/engines/heuristic-engine.js +55 -31
  4. package/package.json +2 -1
  5. package/rules/README.md +252 -0
  6. package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
  7. package/rules/common/C002_no_duplicate_code/config.json +23 -0
  8. package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
  9. package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
  10. package/rules/common/C006_function_naming/analyzer.js +504 -0
  11. package/rules/common/C006_function_naming/config.json +86 -0
  12. package/rules/common/C006_function_naming/smart-analyzer.js +503 -0
  13. package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
  14. package/rules/common/C012_command_query_separation/analyzer.js +481 -0
  15. package/rules/common/C012_command_query_separation/ast-analyzer.js +495 -0
  16. package/rules/common/C013_no_dead_code/analyzer.js +206 -0
  17. package/rules/common/C014_dependency_injection/analyzer.js +338 -0
  18. package/rules/common/C017_constructor_logic/analyzer.js +314 -0
  19. package/rules/common/C019_log_level_usage/analyzer.js +362 -0
  20. package/rules/common/C019_log_level_usage/config.json +121 -0
  21. package/rules/common/C029_catch_block_logging/analyzer-backup.js +426 -0
  22. package/rules/common/C029_catch_block_logging/analyzer-fixed.js +130 -0
  23. package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +487 -0
  24. package/rules/common/C029_catch_block_logging/analyzer-simple.js +110 -0
  25. package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +755 -0
  26. package/rules/common/C029_catch_block_logging/analyzer.js +129 -0
  27. package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +441 -0
  28. package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +127 -0
  29. package/rules/common/C029_catch_block_logging/ast-analyzer.js +133 -0
  30. package/rules/common/C029_catch_block_logging/cfg-analyzer.js +408 -0
  31. package/rules/common/C029_catch_block_logging/config.json +59 -0
  32. package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +454 -0
  33. package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +700 -0
  34. package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +568 -0
  35. package/rules/common/C029_catch_block_logging/semantic-analyzer.js +459 -0
  36. package/rules/common/C031_validation_separation/analyzer.js +186 -0
  37. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
  38. package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +296 -0
  39. package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
  40. package/rules/common/C043_no_console_or_print/analyzer.js +431 -0
  41. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +590 -0
  42. package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
  43. package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
  44. package/rules/docs/C002_no_duplicate_code.md +57 -0
  45. package/rules/docs/C031_validation_separation.md +72 -0
  46. package/rules/index.js +155 -0
  47. package/rules/migration/converter.js +385 -0
  48. package/rules/migration/mapping.json +164 -0
  49. package/rules/parser/constants.js +31 -0
  50. package/rules/parser/file-config.js +80 -0
  51. package/rules/parser/rule-parser-simple.js +305 -0
  52. package/rules/parser/rule-parser.js +527 -0
  53. package/rules/security/S015_insecure_tls_certificate/analyzer.js +150 -0
  54. package/rules/security/S015_insecure_tls_certificate/ast-analyzer.js +237 -0
  55. package/rules/security/S023_no_json_injection/analyzer.js +278 -0
  56. package/rules/security/S023_no_json_injection/ast-analyzer.js +359 -0
  57. package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
  58. package/rules/security/S026_json_schema_validation/config.json +27 -0
  59. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +436 -0
  60. package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
  61. package/rules/security/S029_csrf_protection/analyzer.js +330 -0
  62. package/rules/tests/C002_no_duplicate_code.test.js +50 -0
  63. package/rules/universal/C010/generic.js +0 -0
  64. package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
  65. package/rules/utils/ast-utils.js +191 -0
  66. package/rules/utils/base-analyzer.js +98 -0
  67. package/rules/utils/pattern-matchers.js +239 -0
  68. package/rules/utils/rule-helpers.js +264 -0
  69. package/rules/utils/severity-constants.js +93 -0
  70. package/scripts/generate_insights.js +188 -0
  71. package/scripts/merge-reports.js +0 -424
  72. package/scripts/test-scripts/README.md +0 -22
  73. package/scripts/test-scripts/test-c041-comparison.js +0 -114
  74. package/scripts/test-scripts/test-c041-eslint.js +0 -67
  75. package/scripts/test-scripts/test-eslint-rules.js +0 -146
  76. package/scripts/test-scripts/test-real-world.js +0 -44
  77. 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;
@@ -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.