@sun-asterisk/sunlint 1.3.18 → 1.3.20
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/rules/enhanced-rules-registry.json +77 -18
- package/core/cli-program.js +9 -1
- package/core/github-annotate-service.js +986 -0
- package/core/output-service.js +294 -6
- package/core/summary-report-service.js +30 -30
- package/docs/GITHUB_ACTIONS_INTEGRATION.md +421 -0
- package/package.json +2 -1
- package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +392 -280
- package/rules/common/C017_constructor_logic/analyzer.js +137 -503
- package/rules/common/C017_constructor_logic/config.json +50 -0
- package/rules/common/C017_constructor_logic/symbol-based-analyzer.js +463 -0
- package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +463 -21
- package/rules/security/S011_secure_guid_generation/README.md +255 -0
- package/rules/security/S011_secure_guid_generation/analyzer.js +135 -0
- package/rules/security/S011_secure_guid_generation/config.json +56 -0
- package/rules/security/S011_secure_guid_generation/symbol-based-analyzer.js +609 -0
- package/rules/security/S028_file_upload_size_limits/README.md +537 -0
- package/rules/security/S028_file_upload_size_limits/analyzer.js +202 -0
- package/rules/security/S028_file_upload_size_limits/config.json +186 -0
- package/rules/security/S028_file_upload_size_limits/symbol-based-analyzer.js +530 -0
- package/rules/security/S041_session_token_invalidation/README.md +303 -0
- package/rules/security/S041_session_token_invalidation/analyzer.js +242 -0
- package/rules/security/S041_session_token_invalidation/config.json +175 -0
- package/rules/security/S041_session_token_invalidation/regex-based-analyzer.js +411 -0
- package/rules/security/S041_session_token_invalidation/symbol-based-analyzer.js +674 -0
- package/rules/security/S044_re_authentication_required/README.md +136 -0
- package/rules/security/S044_re_authentication_required/analyzer.js +242 -0
- package/rules/security/S044_re_authentication_required/config.json +161 -0
- package/rules/security/S044_re_authentication_required/regex-based-analyzer.js +329 -0
- package/rules/security/S044_re_authentication_required/symbol-based-analyzer.js +537 -0
- package/rules/security/S045_brute_force_protection/README.md +345 -0
- package/rules/security/S045_brute_force_protection/analyzer.js +336 -0
- package/rules/security/S045_brute_force_protection/config.json +139 -0
- package/rules/security/S045_brute_force_protection/symbol-based-analyzer.js +646 -0
- package/rules/common/C017_constructor_logic/semantic-analyzer.js +0 -340
package/core/output-service.js
CHANGED
|
@@ -34,21 +34,59 @@ class OutputService {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
|
|
37
38
|
async outputResults(results, options, metadata = {}) {
|
|
38
|
-
//
|
|
39
|
-
const
|
|
39
|
+
// Handle GitHub annotation setup
|
|
40
|
+
const githubAnnotateConfig = this._prepareGitHubAnnotation(options);
|
|
41
|
+
|
|
42
|
+
// Generate report based on format (override format to json if github-annotate is enabled)
|
|
43
|
+
const effectiveFormat = githubAnnotateConfig.shouldAnnotate ? 'json' : options.format;
|
|
44
|
+
const report = this.generateReport(results, metadata, { ...options, format: effectiveFormat });
|
|
40
45
|
|
|
41
46
|
// Console output
|
|
42
47
|
if (!options.quiet) {
|
|
43
48
|
console.log(report.formatted);
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
// Determine output file (temp or user-specified)
|
|
52
|
+
let outputFile = options.output;
|
|
53
|
+
let shouldCleanupTempFile = false;
|
|
54
|
+
|
|
55
|
+
if (githubAnnotateConfig.shouldAnnotate && !outputFile) {
|
|
56
|
+
// Create temp file for GitHub annotation
|
|
57
|
+
outputFile = githubAnnotateConfig.tempFile;
|
|
58
|
+
shouldCleanupTempFile = true;
|
|
59
|
+
if (options.verbose) {
|
|
60
|
+
console.log(chalk.gray(`ℹ️ Created temporary report file for GitHub annotation: ${outputFile}`));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
46
64
|
// File output
|
|
47
|
-
if (
|
|
48
|
-
const outputData =
|
|
65
|
+
if (outputFile) {
|
|
66
|
+
const outputData = effectiveFormat === 'json' ? report.raw : report.formatted;
|
|
49
67
|
const content = typeof outputData === 'string' ? outputData : JSON.stringify(outputData, null, 2);
|
|
50
|
-
|
|
51
|
-
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
fs.writeFileSync(outputFile, content);
|
|
71
|
+
if (!shouldCleanupTempFile) {
|
|
72
|
+
console.log(chalk.green(`📄 Report saved to: ${outputFile}`));
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error(chalk.red(`❌ Failed to write report file: ${error.message}`));
|
|
76
|
+
if (shouldCleanupTempFile) {
|
|
77
|
+
this._cleanupTempFile(outputFile);
|
|
78
|
+
}
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// GitHub annotation
|
|
84
|
+
if (githubAnnotateConfig.shouldAnnotate) {
|
|
85
|
+
await this._handleGitHubAnnotation(
|
|
86
|
+
githubAnnotateConfig,
|
|
87
|
+
outputFile,
|
|
88
|
+
shouldCleanupTempFile
|
|
89
|
+
);
|
|
52
90
|
}
|
|
53
91
|
|
|
54
92
|
// Summary report output (new feature for CI/CD)
|
|
@@ -474,6 +512,256 @@ class OutputService {
|
|
|
474
512
|
};
|
|
475
513
|
}
|
|
476
514
|
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Prepare GitHub annotation configuration
|
|
518
|
+
* Check environment and prerequisites
|
|
519
|
+
* @param {Object} options - CLI options
|
|
520
|
+
* @returns {Object} Configuration object
|
|
521
|
+
* @private
|
|
522
|
+
*/
|
|
523
|
+
_prepareGitHubAnnotation(options) {
|
|
524
|
+
// Check if github-annotate flag is enabled
|
|
525
|
+
if (!options.githubAnnotate) {
|
|
526
|
+
return { shouldAnnotate: false };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Parse mode: true/'all' -> all, 'annotate' -> annotate, 'summary' -> summary
|
|
530
|
+
let mode = 'all'; // default
|
|
531
|
+
if (typeof options.githubAnnotate === 'string') {
|
|
532
|
+
const validModes = ['annotate', 'summary', 'all'];
|
|
533
|
+
if (validModes.includes(options.githubAnnotate.toLowerCase())) {
|
|
534
|
+
mode = options.githubAnnotate.toLowerCase();
|
|
535
|
+
} else {
|
|
536
|
+
console.log(chalk.yellow(`⚠️ Invalid --github-annotate mode: ${options.githubAnnotate}. Using default: all`));
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Check if we're in a GitHub Actions environment
|
|
541
|
+
const isGitHubActions = process.env.GITHUB_ACTIONS === 'true';
|
|
542
|
+
if (!isGitHubActions) {
|
|
543
|
+
if (options.verbose) {
|
|
544
|
+
console.log(chalk.yellow('⚠️ --github-annotate only works in GitHub Actions environment'));
|
|
545
|
+
}
|
|
546
|
+
return { shouldAnnotate: false };
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Get GitHub environment variables
|
|
550
|
+
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
551
|
+
const repo = process.env.GITHUB_REPOSITORY;
|
|
552
|
+
const githubToken = process.env.GITHUB_TOKEN || options.githubToken;
|
|
553
|
+
|
|
554
|
+
// Check if it's a PR event
|
|
555
|
+
const isPullRequestEvent = eventName === 'pull_request' || eventName === 'pull_request_target';
|
|
556
|
+
|
|
557
|
+
if (!isPullRequestEvent) {
|
|
558
|
+
if (options.verbose) {
|
|
559
|
+
console.log(chalk.yellow(`⚠️ GitHub annotation only works on pull_request events (current: ${eventName})`));
|
|
560
|
+
}
|
|
561
|
+
return { shouldAnnotate: false };
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Get PR number from GitHub context
|
|
565
|
+
let prNumber = null;
|
|
566
|
+
try {
|
|
567
|
+
// Try to get PR number from GITHUB_EVENT_PATH
|
|
568
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
569
|
+
if (eventPath && fs.existsSync(eventPath)) {
|
|
570
|
+
const event = JSON.parse(fs.readFileSync(eventPath, 'utf8'));
|
|
571
|
+
prNumber = event.pull_request?.number;
|
|
572
|
+
}
|
|
573
|
+
} catch (error) {
|
|
574
|
+
if (options.verbose) {
|
|
575
|
+
console.log(chalk.yellow(`⚠️ Failed to read GitHub event data: ${error.message}`));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Fallback: try from environment variable
|
|
580
|
+
if (!prNumber && process.env.GITHUB_PR_NUMBER) {
|
|
581
|
+
prNumber = parseInt(process.env.GITHUB_PR_NUMBER, 10);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Validate required data
|
|
585
|
+
if (!repo) {
|
|
586
|
+
console.log(chalk.yellow('⚠️ Missing GITHUB_REPOSITORY environment variable'));
|
|
587
|
+
return { shouldAnnotate: false };
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (!prNumber) {
|
|
591
|
+
console.log(chalk.yellow('⚠️ Could not determine PR number from GitHub context'));
|
|
592
|
+
return { shouldAnnotate: false };
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (!githubToken) {
|
|
596
|
+
console.log(chalk.yellow('⚠️ Missing GITHUB_TOKEN for authentication'));
|
|
597
|
+
return { shouldAnnotate: false };
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Generate temp file path if needed
|
|
601
|
+
const tempFile = path.join(
|
|
602
|
+
process.env.RUNNER_TEMP || '/tmp',
|
|
603
|
+
`sunlint-report-${Date.now()}-${Math.random().toString(36).substring(2, 11)}.json`
|
|
604
|
+
);
|
|
605
|
+
|
|
606
|
+
return {
|
|
607
|
+
shouldAnnotate: true,
|
|
608
|
+
mode,
|
|
609
|
+
repo,
|
|
610
|
+
prNumber,
|
|
611
|
+
githubToken,
|
|
612
|
+
tempFile,
|
|
613
|
+
eventName
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Handle GitHub annotation process
|
|
619
|
+
* @param {Object} config - GitHub annotation configuration
|
|
620
|
+
* @param {string} outputFile - Path to report file
|
|
621
|
+
* @param {boolean} shouldCleanup - Whether to cleanup temp file
|
|
622
|
+
* @private
|
|
623
|
+
*/
|
|
624
|
+
async _handleGitHubAnnotation(config, outputFile, shouldCleanup) {
|
|
625
|
+
const mode = config.mode || 'all';
|
|
626
|
+
const results = {};
|
|
627
|
+
|
|
628
|
+
try {
|
|
629
|
+
console.log(chalk.blue(`🔄 GitHub PR annotation mode: ${mode}`));
|
|
630
|
+
|
|
631
|
+
if (!config.repo || !config.prNumber || !config.githubToken) {
|
|
632
|
+
throw new Error('Missing required GitHub configuration');
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (!outputFile || !fs.existsSync(outputFile)) {
|
|
636
|
+
throw new Error(`Report file not found: ${outputFile}`);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Import services
|
|
640
|
+
const { annotate, postSummaryComment } = require('./github-annotate-service');
|
|
641
|
+
|
|
642
|
+
// Execute based on mode
|
|
643
|
+
const shouldAnnotate = mode === 'annotate' || mode === 'all';
|
|
644
|
+
const shouldSummary = mode === 'summary' || mode === 'all';
|
|
645
|
+
|
|
646
|
+
// 1. Inline comments (annotate mode)
|
|
647
|
+
if (shouldAnnotate) {
|
|
648
|
+
try {
|
|
649
|
+
console.log(chalk.blue('📝 Creating inline comments...'));
|
|
650
|
+
const annotateResult = await annotate({
|
|
651
|
+
jsonFile: outputFile,
|
|
652
|
+
githubToken: config.githubToken,
|
|
653
|
+
repo: config.repo,
|
|
654
|
+
prNumber: config.prNumber,
|
|
655
|
+
skipDuplicates: true
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
results.annotate = annotateResult;
|
|
659
|
+
|
|
660
|
+
if (annotateResult.success) {
|
|
661
|
+
console.log(chalk.green(`✅ Inline comments: ${annotateResult.stats.commentsCreated} created`));
|
|
662
|
+
if (annotateResult.stats.duplicatesSkipped > 0) {
|
|
663
|
+
console.log(chalk.gray(` • Duplicates skipped: ${annotateResult.stats.duplicatesSkipped}`));
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
} catch (error) {
|
|
667
|
+
console.log(chalk.red(`❌ Failed to create inline comments: ${error.message}`));
|
|
668
|
+
results.annotate = { success: false, error: error.message };
|
|
669
|
+
|
|
670
|
+
// Don't throw if we still need to create summary
|
|
671
|
+
if (!shouldSummary) {
|
|
672
|
+
throw error;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// 2. Summary comment (summary mode)
|
|
678
|
+
if (shouldSummary) {
|
|
679
|
+
try {
|
|
680
|
+
console.log(chalk.blue('💬 Creating summary comment...'));
|
|
681
|
+
const summaryResult = await postSummaryComment({
|
|
682
|
+
jsonFile: outputFile,
|
|
683
|
+
githubToken: config.githubToken,
|
|
684
|
+
repo: config.repo,
|
|
685
|
+
prNumber: config.prNumber
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
results.summary = summaryResult;
|
|
689
|
+
|
|
690
|
+
if (summaryResult.success) {
|
|
691
|
+
console.log(chalk.green(`✅ Summary comment: ${summaryResult.action}`));
|
|
692
|
+
if (summaryResult.stats) {
|
|
693
|
+
console.log(chalk.gray(` • Total violations: ${summaryResult.stats.totalViolations}`));
|
|
694
|
+
console.log(chalk.gray(` • Errors: ${summaryResult.stats.errorCount}, Warnings: ${summaryResult.stats.warningCount}`));
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
} catch (error) {
|
|
698
|
+
console.log(chalk.red(`❌ Failed to create summary comment: ${error.message}`));
|
|
699
|
+
results.summary = { success: false, error: error.message };
|
|
700
|
+
|
|
701
|
+
// Throw if both failed or if this is the only mode
|
|
702
|
+
if (!results.annotate || !results.annotate.success) {
|
|
703
|
+
throw error;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Final summary
|
|
709
|
+
const successCount = [results.annotate?.success, results.summary?.success].filter(Boolean).length;
|
|
710
|
+
const totalCount = [shouldAnnotate, shouldSummary].filter(Boolean).length;
|
|
711
|
+
|
|
712
|
+
if (successCount === totalCount) {
|
|
713
|
+
console.log(chalk.green(`\n✅ Successfully annotated PR #${config.prNumber} (${successCount}/${totalCount} tasks completed)`));
|
|
714
|
+
} else if (successCount > 0) {
|
|
715
|
+
console.log(chalk.yellow(`\n⚠️ Partially completed (${successCount}/${totalCount} tasks successful)`));
|
|
716
|
+
} else {
|
|
717
|
+
console.log(chalk.red(`\n❌ Annotation failed`));
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
} catch (error) {
|
|
721
|
+
console.log(chalk.red(`\n❌ Failed to annotate GitHub PR: ${error.message}`));
|
|
722
|
+
|
|
723
|
+
// Log detailed error in verbose mode
|
|
724
|
+
if (process.env.DEBUG === 'true' && error.stack) {
|
|
725
|
+
console.error(chalk.gray('Error stack:'), error.stack);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Show suggestions based on error type
|
|
729
|
+
if (error.name === 'ValidationError') {
|
|
730
|
+
console.log(chalk.yellow('💡 Hint: Check your GitHub environment variables'));
|
|
731
|
+
} else if (error.name === 'GitHubAPIError') {
|
|
732
|
+
console.log(chalk.yellow('💡 Hint: Check GitHub token permissions (needs pull-requests:write)'));
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
} finally {
|
|
736
|
+
// Cleanup temp file if needed
|
|
737
|
+
if (shouldCleanup) {
|
|
738
|
+
this._cleanupTempFile(outputFile);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return results;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Cleanup temporary file
|
|
747
|
+
* @param {string} filePath - Path to temp file
|
|
748
|
+
* @private
|
|
749
|
+
*/
|
|
750
|
+
_cleanupTempFile(filePath) {
|
|
751
|
+
try {
|
|
752
|
+
if (filePath && fs.existsSync(filePath)) {
|
|
753
|
+
fs.unlinkSync(filePath);
|
|
754
|
+
if (process.env.DEBUG === 'true') {
|
|
755
|
+
console.log(chalk.gray(`🗑️ Cleaned up temp file: ${filePath}`));
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
} catch (error) {
|
|
759
|
+
// Non-critical error, just log in debug mode
|
|
760
|
+
if (process.env.DEBUG === 'true') {
|
|
761
|
+
console.warn(chalk.yellow(`⚠️ Failed to cleanup temp file: ${error.message}`));
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
477
765
|
}
|
|
478
766
|
|
|
479
767
|
module.exports = OutputService;
|
|
@@ -13,7 +13,7 @@ class SummaryReportService {
|
|
|
13
13
|
// Load version from package.json
|
|
14
14
|
this.version = this._loadVersion();
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
/**
|
|
18
18
|
* Load version from package.json
|
|
19
19
|
* @returns {string} Package version
|
|
@@ -23,9 +23,9 @@ class SummaryReportService {
|
|
|
23
23
|
try {
|
|
24
24
|
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
|
25
25
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
26
|
-
return packageJson.version || '1.3.
|
|
26
|
+
return packageJson.version || '1.3.19';
|
|
27
27
|
} catch (error) {
|
|
28
|
-
return '1.3.
|
|
28
|
+
return '1.3.19'; // Fallback version
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -154,44 +154,44 @@ class SummaryReportService {
|
|
|
154
154
|
const gitInfo = this.getGitInfo(options.cwd);
|
|
155
155
|
|
|
156
156
|
// Override with environment variables if available (from CI/CD)
|
|
157
|
-
const repository_url = process.env.GITHUB_REPOSITORY
|
|
157
|
+
const repository_url = process.env.GITHUB_REPOSITORY
|
|
158
158
|
? `https://github.com/${process.env.GITHUB_REPOSITORY}`
|
|
159
159
|
: gitInfo.repository_url;
|
|
160
|
-
|
|
161
|
-
const repository_name = process.env.GITHUB_REPOSITORY
|
|
160
|
+
|
|
161
|
+
const repository_name = process.env.GITHUB_REPOSITORY
|
|
162
162
|
? process.env.GITHUB_REPOSITORY.split('/')[1]
|
|
163
163
|
: (gitInfo.repository_name || null);
|
|
164
|
-
|
|
164
|
+
|
|
165
165
|
const branch = process.env.GITHUB_REF_NAME || gitInfo.branch;
|
|
166
166
|
const commit_hash = process.env.GITHUB_SHA || gitInfo.commit_hash;
|
|
167
|
-
|
|
167
|
+
|
|
168
168
|
// Get commit details from GitHub context or git
|
|
169
|
-
const commit_message = process.env.GITHUB_EVENT_HEAD_COMMIT_MESSAGE
|
|
170
|
-
|| (process.env.GITHUB_EVENT_PATH
|
|
169
|
+
const commit_message = process.env.GITHUB_EVENT_HEAD_COMMIT_MESSAGE
|
|
170
|
+
|| (process.env.GITHUB_EVENT_PATH
|
|
171
171
|
? this._getGitHubEventData('head_commit.message')
|
|
172
172
|
: null)
|
|
173
173
|
|| gitInfo.commit_message;
|
|
174
|
-
|
|
174
|
+
|
|
175
175
|
const author_email = process.env.GITHUB_EVENT_HEAD_COMMIT_AUTHOR_EMAIL
|
|
176
|
-
|| (process.env.GITHUB_EVENT_PATH
|
|
176
|
+
|| (process.env.GITHUB_EVENT_PATH
|
|
177
177
|
? this._getGitHubEventData('head_commit.author.email')
|
|
178
178
|
: null)
|
|
179
179
|
|| gitInfo.author_email;
|
|
180
|
-
|
|
180
|
+
|
|
181
181
|
const author_name = process.env.GITHUB_EVENT_HEAD_COMMIT_AUTHOR_NAME
|
|
182
|
-
|| (process.env.GITHUB_EVENT_PATH
|
|
182
|
+
|| (process.env.GITHUB_EVENT_PATH
|
|
183
183
|
? this._getGitHubEventData('head_commit.author.name')
|
|
184
184
|
: null)
|
|
185
185
|
|| gitInfo.author_name;
|
|
186
|
-
|
|
186
|
+
|
|
187
187
|
// Get PR number from GitHub event or git
|
|
188
188
|
let pr_number = null;
|
|
189
189
|
if (process.env.GITHUB_EVENT_PATH) {
|
|
190
|
-
pr_number = this._getGitHubEventData('pull_request.number')
|
|
190
|
+
pr_number = this._getGitHubEventData('pull_request.number')
|
|
191
191
|
|| this._getGitHubEventData('number');
|
|
192
192
|
}
|
|
193
193
|
pr_number = pr_number || gitInfo.pr_number;
|
|
194
|
-
|
|
194
|
+
|
|
195
195
|
// Get project path (for mono-repo support)
|
|
196
196
|
const project_path = gitInfo.project_path;
|
|
197
197
|
|
|
@@ -203,20 +203,20 @@ class SummaryReportService {
|
|
|
203
203
|
if (!violation || typeof violation !== 'object') {
|
|
204
204
|
return; // Skip non-objects
|
|
205
205
|
}
|
|
206
|
-
|
|
206
|
+
|
|
207
207
|
// Skip objects that look like metadata/config (have nested objects like semanticEngine, project, etc.)
|
|
208
208
|
if (violation.semanticEngine || violation.project || violation.options) {
|
|
209
209
|
return; // Skip config objects
|
|
210
210
|
}
|
|
211
|
-
|
|
211
|
+
|
|
212
212
|
// Get ruleId from various possible fields
|
|
213
213
|
const ruleId = violation.ruleId || violation.rule || 'unknown';
|
|
214
|
-
|
|
214
|
+
|
|
215
215
|
// Ensure ruleId is a string (not an object)
|
|
216
216
|
if (typeof ruleId !== 'string') {
|
|
217
217
|
return; // Skip invalid ruleId
|
|
218
218
|
}
|
|
219
|
-
|
|
219
|
+
|
|
220
220
|
if (!violationsByRule[ruleId]) {
|
|
221
221
|
violationsByRule[ruleId] = {
|
|
222
222
|
rule_code: ruleId,
|
|
@@ -253,7 +253,7 @@ class SummaryReportService {
|
|
|
253
253
|
sunlint_version: options.version || this.version,
|
|
254
254
|
analysis_duration_ms: options.duration || 0,
|
|
255
255
|
violations: violationsSummary,
|
|
256
|
-
|
|
256
|
+
|
|
257
257
|
// Additional metadata for backwards compatibility
|
|
258
258
|
metadata: {
|
|
259
259
|
generated_at: new Date().toISOString(),
|
|
@@ -283,11 +283,11 @@ class SummaryReportService {
|
|
|
283
283
|
if (!eventPath || !fs.existsSync(eventPath)) {
|
|
284
284
|
return null;
|
|
285
285
|
}
|
|
286
|
-
|
|
286
|
+
|
|
287
287
|
const eventData = JSON.parse(fs.readFileSync(eventPath, 'utf8'));
|
|
288
288
|
const keys = path.split('.');
|
|
289
289
|
let value = eventData;
|
|
290
|
-
|
|
290
|
+
|
|
291
291
|
for (const key of keys) {
|
|
292
292
|
if (value && typeof value === 'object' && key in value) {
|
|
293
293
|
value = value[key];
|
|
@@ -295,7 +295,7 @@ class SummaryReportService {
|
|
|
295
295
|
return null;
|
|
296
296
|
}
|
|
297
297
|
}
|
|
298
|
-
|
|
298
|
+
|
|
299
299
|
return value;
|
|
300
300
|
} catch (error) {
|
|
301
301
|
return null;
|
|
@@ -311,7 +311,7 @@ class SummaryReportService {
|
|
|
311
311
|
*/
|
|
312
312
|
saveSummaryReport(violations, scoringSummary, outputPath, options = {}) {
|
|
313
313
|
const summaryReport = this.generateSummaryReport(violations, scoringSummary, options);
|
|
314
|
-
|
|
314
|
+
|
|
315
315
|
// Ensure directory exists
|
|
316
316
|
const dir = path.dirname(outputPath);
|
|
317
317
|
if (!fs.existsSync(dir)) {
|
|
@@ -320,7 +320,7 @@ class SummaryReportService {
|
|
|
320
320
|
|
|
321
321
|
// Write to file with pretty formatting
|
|
322
322
|
fs.writeFileSync(outputPath, JSON.stringify(summaryReport, null, 2), 'utf8');
|
|
323
|
-
|
|
323
|
+
|
|
324
324
|
return summaryReport;
|
|
325
325
|
}
|
|
326
326
|
|
|
@@ -340,7 +340,7 @@ class SummaryReportService {
|
|
|
340
340
|
const warningCount = summaryReport.warning_count || 0;
|
|
341
341
|
const violationsByRule = summaryReport.violations || [];
|
|
342
342
|
const violationsPerKLOC = summaryReport.quality?.metrics?.violationsPerKLOC || 0;
|
|
343
|
-
|
|
343
|
+
|
|
344
344
|
let output = '\n📊 Quality Summary Report\n';
|
|
345
345
|
output += '━'.repeat(50) + '\n';
|
|
346
346
|
output += `📈 Quality Score: ${score} (Grade: ${grade})\n`;
|
|
@@ -348,14 +348,14 @@ class SummaryReportService {
|
|
|
348
348
|
output += `📏 Lines of Code: ${linesOfCode.toLocaleString()}\n`;
|
|
349
349
|
output += `⚠️ Total Violations: ${totalViolations} (${errorCount} errors, ${warningCount} warnings)\n`;
|
|
350
350
|
output += `📊 Violations per KLOC: ${violationsPerKLOC}\n`;
|
|
351
|
-
|
|
351
|
+
|
|
352
352
|
if (violationsByRule.length > 0) {
|
|
353
353
|
output += '\n🔍 Top Violations by Rule:\n';
|
|
354
354
|
violationsByRule.slice(0, 10).forEach((item, index) => {
|
|
355
355
|
output += ` ${index + 1}. ${item.rule_code}: ${item.count} violations (${item.severity})\n`;
|
|
356
356
|
});
|
|
357
357
|
}
|
|
358
|
-
|
|
358
|
+
|
|
359
359
|
return output;
|
|
360
360
|
}
|
|
361
361
|
}
|