@sun-asterisk/sunlint 1.3.18 ā 1.3.19
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 +2 -1
- package/core/github-annotate-service.js +89 -0
- package/core/output-service.js +25 -0
- package/core/summary-report-service.js +30 -30
- package/package.json +3 -2
- 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
|
@@ -870,6 +870,23 @@
|
|
|
870
870
|
"status": "stable",
|
|
871
871
|
"tags": ["security", "secrets", "hardcoded"]
|
|
872
872
|
},
|
|
873
|
+
"S028": {
|
|
874
|
+
"name": "Limit upload file size and number of files per user",
|
|
875
|
+
"description": "File uploads must enforce size limits and file quantity limits to prevent resource exhaustion and DoS attacks. Both file size and number of files should be limited at the server-side.",
|
|
876
|
+
"category": "security",
|
|
877
|
+
"severity": "error",
|
|
878
|
+
"languages": ["typescript", "javascript", "java"],
|
|
879
|
+
"analyzer": "./rules/security/S028_file_upload_size_limits/analyzer.js",
|
|
880
|
+
"version": "1.0.0",
|
|
881
|
+
"status": "stable",
|
|
882
|
+
"tags": [
|
|
883
|
+
"security",
|
|
884
|
+
"file-upload",
|
|
885
|
+
"dos-prevention",
|
|
886
|
+
"resource-limits",
|
|
887
|
+
"owasp"
|
|
888
|
+
]
|
|
889
|
+
},
|
|
873
890
|
"S029": {
|
|
874
891
|
"name": "Require CSRF Protection",
|
|
875
892
|
"description": "Require CSRF protection for state-changing operations",
|
|
@@ -1079,16 +1096,27 @@
|
|
|
1079
1096
|
}
|
|
1080
1097
|
},
|
|
1081
1098
|
"S041": {
|
|
1082
|
-
"name": "
|
|
1083
|
-
"description": "
|
|
1099
|
+
"name": "Session Tokens must be invalidated after logout or expiration",
|
|
1100
|
+
"description": "Session tokens must be properly invalidated after logout or expiration to prevent session hijacking and unauthorized access. This includes clearing session data, invalidating JWT tokens, and ensuring proper session cleanup.",
|
|
1084
1101
|
"category": "security",
|
|
1085
1102
|
"severity": "error",
|
|
1086
1103
|
"languages": ["typescript", "javascript"],
|
|
1087
|
-
"analyzer": "
|
|
1088
|
-
"
|
|
1104
|
+
"analyzer": "./rules/security/S041_session_token_invalidation/analyzer.js",
|
|
1105
|
+
"config": "./rules/security/S041_session_token_invalidation/config.json",
|
|
1089
1106
|
"version": "1.0.0",
|
|
1090
1107
|
"status": "stable",
|
|
1091
|
-
"tags": ["security", "session", "logout"]
|
|
1108
|
+
"tags": ["security", "session", "token", "logout", "invalidation", "owasp"],
|
|
1109
|
+
"strategy": {
|
|
1110
|
+
"preferred": "ast",
|
|
1111
|
+
"fallbacks": ["ast", "regex"],
|
|
1112
|
+
"accuracy": {
|
|
1113
|
+
"ast": 95,
|
|
1114
|
+
"regex": 85
|
|
1115
|
+
}
|
|
1116
|
+
},
|
|
1117
|
+
"engineMappings": {
|
|
1118
|
+
"heuristic": ["rules/security/S041_session_token_invalidation/analyzer.js"]
|
|
1119
|
+
}
|
|
1092
1120
|
},
|
|
1093
1121
|
"S042": {
|
|
1094
1122
|
"name": "Require Periodic Reauthentication",
|
|
@@ -1115,28 +1143,49 @@
|
|
|
1115
1143
|
"tags": ["security", "session", "password"]
|
|
1116
1144
|
},
|
|
1117
1145
|
"S044": {
|
|
1118
|
-
"name": "
|
|
1119
|
-
"description": "Require
|
|
1146
|
+
"name": "Re-authentication Required for Sensitive Operations",
|
|
1147
|
+
"description": "Require re-authentication before performing sensitive operations such as password changes, email changes, profile updates, and other critical account modifications. This prevents unauthorized access to sensitive account functions even if a session is compromised.",
|
|
1120
1148
|
"category": "security",
|
|
1121
1149
|
"severity": "error",
|
|
1122
1150
|
"languages": ["typescript", "javascript"],
|
|
1123
|
-
"analyzer": "
|
|
1124
|
-
"
|
|
1151
|
+
"analyzer": "./rules/security/S044_re_authentication_required/analyzer.js",
|
|
1152
|
+
"config": "./rules/security/S044_re_authentication_required/config.json",
|
|
1125
1153
|
"version": "1.0.0",
|
|
1126
1154
|
"status": "stable",
|
|
1127
|
-
"tags": ["security", "
|
|
1155
|
+
"tags": ["security", "authentication", "re-authentication", "sensitive-operations", "owasp"],
|
|
1156
|
+
"strategy": {
|
|
1157
|
+
"preferred": "ast",
|
|
1158
|
+
"fallbacks": ["ast", "regex"],
|
|
1159
|
+
"accuracy": {
|
|
1160
|
+
"ast": 95,
|
|
1161
|
+
"regex": 85
|
|
1162
|
+
}
|
|
1163
|
+
},
|
|
1164
|
+
"engineMappings": {
|
|
1165
|
+
"heuristic": ["rules/security/S044_re_authentication_required/analyzer.js"]
|
|
1166
|
+
}
|
|
1128
1167
|
},
|
|
1129
1168
|
"S045": {
|
|
1130
|
-
"name": "
|
|
1131
|
-
"description": "Implement
|
|
1169
|
+
"name": "Brute-force Protection",
|
|
1170
|
+
"description": "Implement protection against brute-force attacks on authentication endpoints. This rule detects missing rate limiting, account lockout mechanisms, and other brute-force protection measures in authentication flows.",
|
|
1132
1171
|
"category": "security",
|
|
1133
|
-
"severity": "
|
|
1172
|
+
"severity": "error",
|
|
1134
1173
|
"languages": ["typescript", "javascript"],
|
|
1135
|
-
"analyzer": "
|
|
1136
|
-
"
|
|
1174
|
+
"analyzer": "./rules/security/S045_brute_force_protection/analyzer.js",
|
|
1175
|
+
"config": "./rules/security/S045_brute_force_protection/config.json",
|
|
1137
1176
|
"version": "1.0.0",
|
|
1138
1177
|
"status": "stable",
|
|
1139
|
-
"tags": ["security", "
|
|
1178
|
+
"tags": ["security", "authentication", "brute-force", "rate-limiting", "owasp"],
|
|
1179
|
+
"strategy": {
|
|
1180
|
+
"preferred": "heuristic",
|
|
1181
|
+
"fallbacks": ["heuristic"],
|
|
1182
|
+
"accuracy": {
|
|
1183
|
+
"heuristic": 95
|
|
1184
|
+
}
|
|
1185
|
+
},
|
|
1186
|
+
"engineMappings": {
|
|
1187
|
+
"heuristic": "rules/security/S045_brute_force_protection/analyzer.js"
|
|
1188
|
+
}
|
|
1140
1189
|
},
|
|
1141
1190
|
"S046": {
|
|
1142
1191
|
"name": "Secure Notification on Auth Change",
|
|
@@ -1239,8 +1288,16 @@
|
|
|
1239
1288
|
"name": "One Behavior per Test (AAA Pattern)",
|
|
1240
1289
|
"description": "Enforce single behavior testing - each test should verify exactly one action/behavior with clear Arrange-Act-Assert structure",
|
|
1241
1290
|
"category": "common",
|
|
1242
|
-
"severity": "warning",
|
|
1243
|
-
"languages": [
|
|
1291
|
+
"severity": "warning",
|
|
1292
|
+
"languages": [
|
|
1293
|
+
"typescript",
|
|
1294
|
+
"javascript",
|
|
1295
|
+
"java",
|
|
1296
|
+
"csharp",
|
|
1297
|
+
"swift",
|
|
1298
|
+
"kotlin",
|
|
1299
|
+
"python"
|
|
1300
|
+
],
|
|
1244
1301
|
"analyzer": "./rules/common/C065_one_behavior_per_test/analyzer.js",
|
|
1245
1302
|
"config": "./rules/common/C065_one_behavior_per_test/config.json",
|
|
1246
1303
|
"version": "1.0.0",
|
|
@@ -1451,6 +1508,8 @@
|
|
|
1451
1508
|
"category": "general",
|
|
1452
1509
|
"severity": "warning",
|
|
1453
1510
|
"languages": ["typescript", "javascript"],
|
|
1511
|
+
"analyzer": "./rules/common/C017_constructor_logic/analyzer.js",
|
|
1512
|
+
"config": "./rules/common/C017_constructor_logic/config.json",
|
|
1454
1513
|
"version": "1.0.0",
|
|
1455
1514
|
"status": "migrated",
|
|
1456
1515
|
"tags": ["migrated"],
|
package/core/cli-program.js
CHANGED
|
@@ -36,7 +36,8 @@ function createCliProgram() {
|
|
|
36
36
|
.option('-o, --output <file>', 'Output file path')
|
|
37
37
|
.option('--output-summary <file>', 'Output summary report file path (JSON format for CI/CD)')
|
|
38
38
|
.option('--upload-report [url]', 'Upload summary report to API endpoint after analysis (default: Sun* Coding Standards API)')
|
|
39
|
-
.option('--config <file>', 'Configuration file path (default: auto-discover)')
|
|
39
|
+
.option('--config <file>', 'Configuration file path (default: auto-discover)')
|
|
40
|
+
.option('--github-annotate', 'Annotate GitHub PR with results (requires --output and --format=json)');
|
|
40
41
|
|
|
41
42
|
// File targeting options
|
|
42
43
|
program
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Annotate Service
|
|
3
|
+
* Äį»c file JSON kįŗæt quįŗ£ vĆ comment annotation lĆŖn GitHub PR tʰʔng ứng
|
|
4
|
+
* Usage: githubAnnotateService.annotate({ jsonFile, githubToken, repo, prNumber })
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
let Octokit;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Annotate GitHub PR with SunLint results
|
|
12
|
+
* @param {Object} options
|
|
13
|
+
* @param {string} options.jsonFile - Path to JSON result file
|
|
14
|
+
* @param {string} options.githubToken - GitHub token (with repo:write)
|
|
15
|
+
* @param {string} options.repo - GitHub repo in format owner/repo
|
|
16
|
+
* @param {number} options.prNumber - Pull request number
|
|
17
|
+
*/
|
|
18
|
+
async function annotate({ jsonFile, githubToken, repo, prNumber }) {
|
|
19
|
+
if (!fs.existsSync(jsonFile)) {
|
|
20
|
+
throw new Error(`Result file not found: ${jsonFile}`);
|
|
21
|
+
}
|
|
22
|
+
const raw = JSON.parse(fs.readFileSync(jsonFile, 'utf8'));
|
|
23
|
+
let violations = [];
|
|
24
|
+
if (Array.isArray(raw)) {
|
|
25
|
+
const cwd = process.env.GITHUB_WORKSPACE || process.cwd();
|
|
26
|
+
for (const fileObj of raw) {
|
|
27
|
+
if (fileObj && fileObj.filePath && Array.isArray(fileObj.messages)) {
|
|
28
|
+
let relPath = fileObj.filePath;
|
|
29
|
+
if (relPath.startsWith(cwd)) {
|
|
30
|
+
relPath = relPath.slice(cwd.length);
|
|
31
|
+
if (relPath.startsWith('/') || relPath.startsWith('\\')) relPath = relPath.slice(1);
|
|
32
|
+
}
|
|
33
|
+
for (const msg of fileObj.messages) {
|
|
34
|
+
violations.push({
|
|
35
|
+
file: relPath,
|
|
36
|
+
line: msg.line,
|
|
37
|
+
rule: msg.ruleId,
|
|
38
|
+
severity: msg.severity === 2 ? 'error' : 'warning',
|
|
39
|
+
message: msg.message
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
violations = raw.violations || [];
|
|
46
|
+
}
|
|
47
|
+
console.log(violations);
|
|
48
|
+
const token = githubToken || process.env.GITHUB_TOKEN;
|
|
49
|
+
if (!token) throw new Error('Missing GitHub token');
|
|
50
|
+
const [owner, repoName] = repo.split('/');
|
|
51
|
+
if (!Octokit) {
|
|
52
|
+
// Dynamic import Äį» hį» trợ ESM
|
|
53
|
+
Octokit = (await import('@octokit/rest')).Octokit;
|
|
54
|
+
}
|
|
55
|
+
const octokit = new Octokit({ auth: token });
|
|
56
|
+
|
|
57
|
+
const { data: prData } = await octokit.pulls.get({ owner, repo: repoName, pull_number: prNumber });
|
|
58
|
+
const head_sha = prData.head.sha;
|
|
59
|
+
|
|
60
|
+
const filesRes = await octokit.pulls.listFiles({ owner, repo: repoName, pull_number: prNumber });
|
|
61
|
+
const prFiles = filesRes.data.map(f => f.filename);
|
|
62
|
+
|
|
63
|
+
const reviewComments = violations
|
|
64
|
+
.filter(v => prFiles.includes(v.file))
|
|
65
|
+
.map(v => ({
|
|
66
|
+
path: v.file,
|
|
67
|
+
line: v.line,
|
|
68
|
+
side: 'RIGHT',
|
|
69
|
+
body: `[${v.rule}] ${v.message}`
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
if (reviewComments.length === 0) return { message: 'No matching PR file violations to comment.' };
|
|
73
|
+
|
|
74
|
+
// Nįŗæu có error thƬ yĆŖu cįŗ§u thay Äį»i, khĆ“ng thƬ chį» comment
|
|
75
|
+
const hasError = violations.some(v => v.severity === 'error');
|
|
76
|
+
const eventType = hasError ? 'REQUEST_CHANGES' : 'COMMENT';
|
|
77
|
+
|
|
78
|
+
const reviewRes = await octokit.pulls.createReview({
|
|
79
|
+
owner,
|
|
80
|
+
repo: repoName,
|
|
81
|
+
pull_number: prNumber,
|
|
82
|
+
commit_id: head_sha,
|
|
83
|
+
event: eventType,
|
|
84
|
+
comments: reviewComments
|
|
85
|
+
});
|
|
86
|
+
return reviewRes.data;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { annotate };
|
package/core/output-service.js
CHANGED
|
@@ -34,6 +34,7 @@ class OutputService {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
|
|
37
38
|
async outputResults(results, options, metadata = {}) {
|
|
38
39
|
// Generate report based on format
|
|
39
40
|
const report = this.generateReport(results, metadata, options);
|
|
@@ -49,6 +50,30 @@ class OutputService {
|
|
|
49
50
|
const content = typeof outputData === 'string' ? outputData : JSON.stringify(outputData, null, 2);
|
|
50
51
|
fs.writeFileSync(options.output, content);
|
|
51
52
|
console.log(chalk.green(`š Report saved to: ${options.output}`));
|
|
53
|
+
|
|
54
|
+
// Annotate GitHub nįŗæu Äį»§ Äiį»u kiį»n
|
|
55
|
+
if (options.githubAnnotate && options.format === 'json') {
|
|
56
|
+
try {
|
|
57
|
+
const annotate = require('./github-annotate-service').annotate;
|
|
58
|
+
// Lįŗ„y cĆ”c biįŗæn mĆ“i trʰį»ng cįŗ§n thiįŗæt cho annotate
|
|
59
|
+
const repo = process.env.GITHUB_REPOSITORY || options.githubRepo;
|
|
60
|
+
const prNumber = process.env.GITHUB_PR_NUMBER ? parseInt(process.env.GITHUB_PR_NUMBER) : options.githubPrNumber;
|
|
61
|
+
const githubToken = process.env.GITHUB_TOKEN || options.githubToken;
|
|
62
|
+
if (repo && prNumber && githubToken) {
|
|
63
|
+
await annotate({
|
|
64
|
+
jsonFile: options.output,
|
|
65
|
+
githubToken,
|
|
66
|
+
repo,
|
|
67
|
+
prNumber
|
|
68
|
+
});
|
|
69
|
+
console.log(chalk.green('ā
Annotated GitHub PR with SunLint results.'));
|
|
70
|
+
} else {
|
|
71
|
+
console.log(chalk.yellow('ā ļø Missing GITHUB_REPOSITORY, GITHUB_PR_NUMBER, or GITHUB_TOKEN for GitHub annotation.'));
|
|
72
|
+
}
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.log(chalk.red('ā Failed to annotate GitHub PR:'), err.message);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
52
77
|
}
|
|
53
78
|
|
|
54
79
|
// Summary report output (new feature for CI/CD)
|
|
@@ -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
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sun-asterisk/sunlint",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.19",
|
|
4
4
|
"description": "āļø SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards",
|
|
5
5
|
"main": "cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
},
|
|
92
92
|
"dependencies": {
|
|
93
93
|
"@babel/parser": "^7.25.8",
|
|
94
|
+
"@octokit/rest": "^22.0.0",
|
|
94
95
|
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
|
95
96
|
"@typescript-eslint/parser": "^8.38.0",
|
|
96
97
|
"chalk": "^4.1.2",
|
|
@@ -138,4 +139,4 @@
|
|
|
138
139
|
"url": "https://github.com/sun-asterisk/engineer-excellence/issues"
|
|
139
140
|
},
|
|
140
141
|
"homepage": "https://github.com/sun-asterisk/engineer-excellence/tree/main/coding-quality/extensions/sunlint#readme"
|
|
141
|
-
}
|
|
142
|
+
}
|