@sun-asterisk/sunlint 1.3.0 → 1.3.1
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/CHANGELOG.md +68 -1
- package/CONTRIBUTING.md +1179 -54
- package/README.md +3 -4
- package/config/ci-cd.json +54 -0
- package/config/development.json +56 -0
- package/config/large-project.json +143 -0
- package/config/presets/all.json +0 -1
- package/config/release.json +70 -0
- package/config/rule-analysis-strategies.js +23 -4
- package/config/rules/S027-categories.json +122 -0
- package/config/rules/enhanced-rules-registry.json +136 -75
- package/config/rules/rules-registry-generated.json +2 -2
- package/config/rules/rules-registry.json +13 -1
- package/core/cli-action-handler.js +24 -30
- package/core/cli-program.js +11 -3
- package/core/config-merger.js +29 -2
- package/core/enhanced-rules-registry.js +3 -3
- package/core/semantic-engine.js +117 -19
- package/core/unified-rule-registry.js +1 -1
- package/docs/COMMAND-EXAMPLES.md +134 -0
- package/docs/LARGE-PROJECT-GUIDE.md +324 -0
- package/engines/heuristic-engine.js +71 -13
- package/integrations/eslint/plugin/index.js +0 -2
- package/origin-rules/common-en.md +8 -8
- package/package.json +1 -1
- package/rules/common/C017_constructor_logic/analyzer.js +254 -17
- package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
- package/rules/common/C033_separate_service_repository/README.md +78 -0
- package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
- package/rules/common/C033_separate_service_repository/config.json +50 -0
- package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
- package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
- package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
- package/rules/common/C035_error_logging_context/analyzer.js +230 -0
- package/rules/common/C035_error_logging_context/config.json +54 -0
- package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
- package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
- package/rules/common/C040_centralized_validation/analyzer.js +165 -0
- package/rules/common/C040_centralized_validation/config.json +46 -0
- package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
- package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
- package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
- package/rules/common/C076_explicit_function_types/README.md +30 -0
- package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
- package/rules/common/C076_explicit_function_types/config.json +15 -0
- package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
- package/rules/index.js +1 -0
- package/rules/parser/rule-parser.js +13 -2
- package/rules/security/S005_no_origin_auth/README.md +226 -0
- package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
- package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
- package/rules/security/S005_no_origin_auth/config.json +85 -0
- package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
- package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
- package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
- package/rules/security/S007_no_plaintext_otp/README.md +198 -0
- package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
- package/rules/security/S007_no_plaintext_otp/config.json +79 -0
- package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
- package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
- package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
- package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
- package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
- package/scripts/prepare-release.sh +1 -1
- package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
- package/docs/FUTURE_PACKAGES.md +0 -83
- package/docs/HEURISTIC_VS_AI.md +0 -113
- package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
- package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
- package/docs/RELEASE_GUIDE.md +0 -230
- package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
- package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
{
|
|
2
|
+
"S027": {
|
|
3
|
+
"categories": [
|
|
4
|
+
{
|
|
5
|
+
"name": "AWS Credentials",
|
|
6
|
+
"severity": "critical",
|
|
7
|
+
"description": "AWS access keys, secret keys, and session tokens",
|
|
8
|
+
"patterns": [
|
|
9
|
+
"AKIA[0-9A-Z]{16}",
|
|
10
|
+
"(aws[-_]?)?(secret[-_]?access[-_]?key|access[-_]?key[-_]?id|awssecretaccesskey|awsaccesskeyid)\\s*[=:]\\s*[\"']?[A-Za-z0-9\\/+=]{20,40}[\"']?",
|
|
11
|
+
"(aws[-_]?)?session[-_]?token\\s*[=:]\\s*[\"']?[A-Za-z0-9\\/+=]{100,}[\"']?"
|
|
12
|
+
],
|
|
13
|
+
"exclude_patterns": [
|
|
14
|
+
"(test|mock|fake|example|demo)[-_]?aws",
|
|
15
|
+
"AWS_REGION|AWS_DEFAULT_REGION"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "JWT & Authentication Tokens",
|
|
20
|
+
"severity": "critical",
|
|
21
|
+
"description": "JWT tokens and authentication credentials",
|
|
22
|
+
"patterns": [
|
|
23
|
+
"eyJ[A-Za-z0-9\\-_=]+\\.[A-Za-z0-9\\-_=]+\\.?[A-Za-z0-9\\-_.+/=]*",
|
|
24
|
+
"(jwt|bearer|auth|authtoken|jwttoken)[-_]?(token|secret)?\\s*[=:]\\s*[\"']?[a-zA-Z0-9\\-_=]{20,}[\"']?",
|
|
25
|
+
"authorization\\s*[=:]\\s*[\"']?(bearer|basic)\\s+[a-zA-Z0-9\\-_=]{10,}[\"']?"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "API Keys & Secrets",
|
|
30
|
+
"severity": "high",
|
|
31
|
+
"description": "Generic API keys and secret tokens",
|
|
32
|
+
"patterns": [
|
|
33
|
+
"(api[-_]?key|apikey|secret[-_]?key|secretkey|access[-_]?token|accesstoken)\\s*[=:]\\s*[\"']?[a-zA-Z0-9\\-_=]{16,}[\"']?",
|
|
34
|
+
"(client[-_]?secret|clientsecret|app[-_]?secret|appsecret)\\s*[=:]\\s*[\"']?[a-zA-Z0-9\\-_=]{20,}[\"']?",
|
|
35
|
+
"(private[-_]?key|privatekey|encryption[-_]?key|encryptionkey)\\s*[=:]\\s*[\"']?[a-zA-Z0-9\\-_=]{20,}[\"']?",
|
|
36
|
+
"(password|secret)\\s*[=:]\\s*[\"'][a-zA-Z0-9\\-_=]{6,}[\"']"
|
|
37
|
+
],
|
|
38
|
+
"exclude_patterns": [
|
|
39
|
+
"(display|row|sort|primary|foreign)[-_]?key",
|
|
40
|
+
"key(value|path|name|code|id|index)",
|
|
41
|
+
"^key$",
|
|
42
|
+
"(test|mock|demo|example).*password",
|
|
43
|
+
"password.*(test|mock|demo|example|123|dummy)",
|
|
44
|
+
"wrongpassword|correctpassword|testpassword"
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "Database Credentials",
|
|
49
|
+
"severity": "high",
|
|
50
|
+
"description": "Database connection strings and passwords",
|
|
51
|
+
"patterns": [
|
|
52
|
+
"(mongodb|mysql|postgres|redis):\\/\\/[^\\/\\s'\"]+:[^\\/\\s'\"]+@[^\\/\\s'\"]+",
|
|
53
|
+
"(db|database|dbpassword|databasepassword)[-_]?(password|pass|pwd|secret)?\\s*[=:]\\s*[\"']?[a-zA-Z0-9\\-_=]{6,}[\"']?",
|
|
54
|
+
"connection[-_]?string\\s*[=:]\\s*[\"']?[^\"'\\s]{20,}[\"']?"
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "Third-party Service Keys",
|
|
59
|
+
"severity": "high",
|
|
60
|
+
"description": "GitHub, Slack, Stripe and other service tokens",
|
|
61
|
+
"patterns": [
|
|
62
|
+
"gh[pousr]_[A-Za-z0-9_]{36}",
|
|
63
|
+
"xox[baprs]-[A-Za-z0-9-]+",
|
|
64
|
+
"sk_live_[A-Za-z0-9]{24,}",
|
|
65
|
+
"(github|slack|stripe|paypal)[-_]?(token|key|secret)[\\s:=]+[\"']?[a-zA-Z0-9\\-_=]{16,}[\"']?"
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "Suspicious Variable Names",
|
|
70
|
+
"severity": "medium",
|
|
71
|
+
"description": "Variables with sensitive naming patterns",
|
|
72
|
+
"patterns": [
|
|
73
|
+
"(client|app|service)[-_]?(id|key|token|secret)[\"']?\\s*[:=]\\s*[\"'][A-Za-z0-9\\-_=]{12,}[\"']?",
|
|
74
|
+
"(oauth|openid)[-_]?(client[-_]?id|secret)[\\s:=]+[\"']?[a-zA-Z0-9\\-_=]{10,}[\"']?"
|
|
75
|
+
],
|
|
76
|
+
"exclude_patterns": [
|
|
77
|
+
"(send|verify|update|register|reset).*password",
|
|
78
|
+
"password.*(reset|verify|update|first|time)"
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "Base64 Encoded Secrets",
|
|
83
|
+
"severity": "medium",
|
|
84
|
+
"description": "Potentially encoded sensitive data",
|
|
85
|
+
"patterns": [
|
|
86
|
+
"(?:^|[\\s=:'\"])([A-Za-z0-9+\\/]{64,}={0,2})(?:[\\s'\";}]|$)"
|
|
87
|
+
],
|
|
88
|
+
"exclude_patterns": [
|
|
89
|
+
"^[a-zA-Z0-9+\\/]*$",
|
|
90
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
91
|
+
"(test|demo|example|sample|mock)",
|
|
92
|
+
"^\\/[a-zA-Z0-9\\/\\-_]+$",
|
|
93
|
+
"^[a-zA-Z0-9\\/\\-_\\.]+$",
|
|
94
|
+
"[a-zA-Z]+[a-zA-Z\\/\\-_]{20,}",
|
|
95
|
+
"[a-zA-Z]+Slice\\/",
|
|
96
|
+
"[a-zA-Z]+Company\\/",
|
|
97
|
+
"[a-zA-Z]+Management\\/",
|
|
98
|
+
"[a-zA-Z]+Component",
|
|
99
|
+
"[a-zA-Z]+Setting",
|
|
100
|
+
"[a-zA-Z]+Match",
|
|
101
|
+
"Selector$",
|
|
102
|
+
"Controller$",
|
|
103
|
+
"Service$",
|
|
104
|
+
"Api$",
|
|
105
|
+
"slice\\/",
|
|
106
|
+
"Component\\/",
|
|
107
|
+
"management\\/",
|
|
108
|
+
"company\\/",
|
|
109
|
+
"import.*from",
|
|
110
|
+
"require\\("
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"name": "Environment Variables",
|
|
115
|
+
"severity": "low",
|
|
116
|
+
"description": "Public environment variables that might leak info",
|
|
117
|
+
"patterns": [
|
|
118
|
+
"NEXT_PUBLIC_[A-Z0-9_]+[\\s:=]+[\"'][^\"']+[\"']",
|
|
119
|
+
"react_app_[A-Z0-9_]+[\\s:=]+[\"'][^\"']+[\"']"
|
|
120
|
+
],
|
|
121
|
+
"exclude_patterns": [
|
|
122
|
+
"NODE_ENV|ENV|ENVIRONMENT|MODE|DEBUG"
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"name": "File Path Leaks",
|
|
127
|
+
"severity": "low",
|
|
128
|
+
"description": "Sensitive file patterns",
|
|
129
|
+
"patterns": [
|
|
130
|
+
"^\\s*['\"]?\\.env(\\.[a-zA-Z0-9_]+)?['\"]?\\s*$",
|
|
131
|
+
"^\\s*['\"]?(secrets?|credentials?|private[-_]?keys?)\\.(json|ya?ml|ts|js)['\"]?\\s*$",
|
|
132
|
+
"^\\s*['\"]?(id_rsa|id_dsa|\\.pem|\\.p12|\\.pfx)['\"]?\\s*$"
|
|
133
|
+
],
|
|
134
|
+
"exclude_patterns": [
|
|
135
|
+
"process\\.env\\.",
|
|
136
|
+
"import.*from",
|
|
137
|
+
"require\\(",
|
|
138
|
+
"NODE_ENV|ENVIRONMENT|MODE|DEBUG"
|
|
139
|
+
]
|
|
140
|
+
}
|
|
141
|
+
],
|
|
142
|
+
"global_exclude_patterns": [
|
|
143
|
+
"(test|mock|fake|dummy|placeholder)\\.(js|ts|jsx|tsx)$",
|
|
144
|
+
"\\.(test|spec|mock)\\.",
|
|
145
|
+
"__tests__|\\/tests?\\/|\\/spec\\/",
|
|
146
|
+
"process\\.env\\.",
|
|
147
|
+
"import.*from.*['\"]",
|
|
148
|
+
"require\\(['\"]"
|
|
149
|
+
],
|
|
150
|
+
"min_length": 8,
|
|
151
|
+
"max_length": 1000
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
class S027CategorizedAnalyzer {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.ruleId = 'S027';
|
|
7
|
+
this.ruleName = 'No Hardcoded Secrets (Categorized)';
|
|
8
|
+
this.description = 'Phát hiện thông tin bảo mật theo categories với độ ưu tiên khác nhau';
|
|
9
|
+
|
|
10
|
+
// Load categories config
|
|
11
|
+
this.config = this.loadConfig();
|
|
12
|
+
this.categories = this.config.categories;
|
|
13
|
+
this.globalExcludePatterns = this.config.global_exclude_patterns.map(p => new RegExp(p, 'i'));
|
|
14
|
+
this.minLength = this.config.min_length || 8;
|
|
15
|
+
this.maxLength = this.config.max_length || 1000;
|
|
16
|
+
|
|
17
|
+
// Compile patterns for performance
|
|
18
|
+
this.compilePatterns();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
loadConfig() {
|
|
22
|
+
const configPath = path.join(__dirname, 'categories.json');
|
|
23
|
+
try {
|
|
24
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
25
|
+
return config.S027;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('Failed to load S027 categories config:', error.message);
|
|
28
|
+
return { categories: [], global_exclude_patterns: [] };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
compilePatterns() {
|
|
33
|
+
this.categories.forEach(category => {
|
|
34
|
+
category.compiledPatterns = category.patterns.map(p => ({
|
|
35
|
+
regex: new RegExp(p, 'gm'),
|
|
36
|
+
original: p
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
if (category.exclude_patterns) {
|
|
40
|
+
category.compiledExcludePatterns = category.exclude_patterns.map(p => new RegExp(p, 'i'));
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async analyze(files, language, options = {}) {
|
|
46
|
+
const violations = [];
|
|
47
|
+
this.currentFilePath = '';
|
|
48
|
+
|
|
49
|
+
for (const filePath of files) {
|
|
50
|
+
// Skip build/dist/node_modules
|
|
51
|
+
if (this.shouldSkipFile(filePath)) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.currentFilePath = filePath;
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
59
|
+
const fileViolations = this.analyzeFile(content, filePath);
|
|
60
|
+
violations.push(...fileViolations);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (options.verbose) {
|
|
63
|
+
console.error(`Error analyzing ${filePath}:`, error.message);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return violations;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
shouldSkipFile(filePath) {
|
|
72
|
+
const skipPatterns = [
|
|
73
|
+
'build/', 'dist/', 'node_modules/', '.git/',
|
|
74
|
+
'coverage/', '.next/', '.cache/', 'tmp/',
|
|
75
|
+
'.lock', '.log', '.min.js', '.bundle.js'
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
return skipPatterns.some(pattern => filePath.includes(pattern));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
analyzeFile(content, filePath) {
|
|
82
|
+
const violations = [];
|
|
83
|
+
// Handle different line endings (Windows \r\n, Unix \n, Mac \r)
|
|
84
|
+
const lines = content.split(/\r?\n/);
|
|
85
|
+
|
|
86
|
+
// Check if this is a test file for context
|
|
87
|
+
const isTestFile = this.isTestFile(filePath);
|
|
88
|
+
|
|
89
|
+
lines.forEach((line, index) => {
|
|
90
|
+
const lineNumber = index + 1;
|
|
91
|
+
const trimmedLine = line.trim();
|
|
92
|
+
|
|
93
|
+
// Skip comments and imports
|
|
94
|
+
if (this.isCommentOrImport(trimmedLine)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Check global exclude patterns first
|
|
99
|
+
if (this.matchesGlobalExcludes(line)) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check each category
|
|
104
|
+
this.categories.forEach(category => {
|
|
105
|
+
const categoryViolations = this.checkCategory(
|
|
106
|
+
category, line, lineNumber, filePath, isTestFile
|
|
107
|
+
);
|
|
108
|
+
violations.push(...categoryViolations);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return violations;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
isTestFile(filePath) {
|
|
116
|
+
const testPatterns = [
|
|
117
|
+
/\.(test|spec)\./i,
|
|
118
|
+
/__tests__/i,
|
|
119
|
+
/\/tests?\//i,
|
|
120
|
+
/\/spec\//i,
|
|
121
|
+
/setupTests/i,
|
|
122
|
+
/testSetup/i,
|
|
123
|
+
/test[-_]/i, // Matches test- or test_
|
|
124
|
+
/^.*\/test[^\/]*\.js$/i // Matches files starting with test
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
return testPatterns.some(pattern => pattern.test(filePath));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
isCommentOrImport(line) {
|
|
131
|
+
return line.startsWith('//') || line.startsWith('/*') ||
|
|
132
|
+
line.startsWith('import') || line.startsWith('export') ||
|
|
133
|
+
line.startsWith('*') || line.startsWith('<');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
matchesGlobalExcludes(line) {
|
|
137
|
+
return this.globalExcludePatterns.some(pattern => pattern.test(line));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
checkCategory(category, line, lineNumber, filePath, isTestFile) {
|
|
141
|
+
const violations = [];
|
|
142
|
+
|
|
143
|
+
category.compiledPatterns.forEach(({ regex, original }) => {
|
|
144
|
+
let match;
|
|
145
|
+
|
|
146
|
+
// Reset regex lastIndex for global patterns
|
|
147
|
+
regex.lastIndex = 0;
|
|
148
|
+
|
|
149
|
+
while ((match = regex.exec(line)) !== null) {
|
|
150
|
+
const matchedText = match[0];
|
|
151
|
+
const column = match.index + 1;
|
|
152
|
+
|
|
153
|
+
// Check length constraints
|
|
154
|
+
if (matchedText.length < this.minLength || matchedText.length > this.maxLength) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Check category-specific excludes
|
|
159
|
+
if (category.compiledExcludePatterns &&
|
|
160
|
+
category.compiledExcludePatterns.some(pattern => pattern.test(matchedText))) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Be more lenient in test files for lower severity categories
|
|
165
|
+
// But still report critical/high severity issues even in test files
|
|
166
|
+
if (isTestFile && category.severity === 'low') {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
violations.push({
|
|
171
|
+
file: filePath,
|
|
172
|
+
line: lineNumber,
|
|
173
|
+
column: column,
|
|
174
|
+
message: `[${category.name}] Potential ${category.severity} security risk: '${matchedText}'. ${category.description}`,
|
|
175
|
+
severity: this.mapSeverity(category.severity),
|
|
176
|
+
ruleId: this.ruleId,
|
|
177
|
+
category: category.name,
|
|
178
|
+
categoryDescription: category.description,
|
|
179
|
+
matchedPattern: original,
|
|
180
|
+
matchedText: matchedText
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return violations;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
mapSeverity(categorySeverity) {
|
|
189
|
+
const severityMap = {
|
|
190
|
+
'critical': 'error',
|
|
191
|
+
'high': 'warning',
|
|
192
|
+
'medium': 'warning',
|
|
193
|
+
'low': 'info'
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
return severityMap[categorySeverity] || 'warning';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Method for getting category statistics
|
|
200
|
+
getCategoryStats(violations) {
|
|
201
|
+
const stats = {};
|
|
202
|
+
|
|
203
|
+
violations.forEach(violation => {
|
|
204
|
+
const category = violation.category;
|
|
205
|
+
if (!stats[category]) {
|
|
206
|
+
stats[category] = {
|
|
207
|
+
count: 0,
|
|
208
|
+
severity: violation.severity,
|
|
209
|
+
files: new Set()
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
stats[category].count++;
|
|
213
|
+
stats[category].files.add(violation.file);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Convert Set to array for JSON serialization
|
|
217
|
+
Object.keys(stats).forEach(category => {
|
|
218
|
+
stats[category].files = Array.from(stats[category].files);
|
|
219
|
+
stats[category].fileCount = stats[category].files.length;
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return stats;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Method for filtering by category
|
|
226
|
+
filterByCategory(violations, categoryNames) {
|
|
227
|
+
if (!categoryNames || categoryNames.length === 0) {
|
|
228
|
+
return violations;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return violations.filter(violation =>
|
|
232
|
+
categoryNames.includes(violation.category)
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Method for filtering by severity
|
|
237
|
+
filterBySeverity(violations, minSeverity = 'info') {
|
|
238
|
+
const severityOrder = ['info', 'warning', 'error'];
|
|
239
|
+
const minIndex = severityOrder.indexOf(minSeverity);
|
|
240
|
+
|
|
241
|
+
if (minIndex === -1) return violations;
|
|
242
|
+
|
|
243
|
+
return violations.filter(violation => {
|
|
244
|
+
const violationIndex = severityOrder.indexOf(violation.severity);
|
|
245
|
+
return violationIndex >= minIndex;
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
module.exports = S027CategorizedAnalyzer;
|
|
@@ -135,7 +135,7 @@ sunlint --rule=C006 --input=src --format=summary
|
|
|
135
135
|
|
|
136
136
|
# TypeScript Analysis
|
|
137
137
|
--typescript # Enable TypeScript analysis
|
|
138
|
-
--typescript-engine <type> # Engine: eslint,
|
|
138
|
+
--typescript-engine <type> # Engine: eslint, heuristic, hybrid
|
|
139
139
|
|
|
140
140
|
# Output Control
|
|
141
141
|
--format <format> # Output: eslint, json, summary, table
|