@sun-asterisk/sunlint 1.3.53 → 1.3.54
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/core/cli-action-handler.js +21 -4
- package/package.json +1 -1
- package/rules/common/C024_no_scatter_hardcoded_constants/typescript/symbol-based-analyzer.js +36 -0
- package/rules/common/C041_no_sensitive_hardcode/typescript/symbol-based-analyzer.js +12 -0
- package/rules/common/C042_boolean_name_prefix/typescript/analyzer.js +27 -2
|
@@ -72,7 +72,7 @@ class CliActionHandler {
|
|
|
72
72
|
|
|
73
73
|
// Determine if we should proceed based on requested analyses
|
|
74
74
|
const hasSourceFiles = targetingResult.files.length > 0;
|
|
75
|
-
const willRunCodeQuality = rulesToRun.length > 0 && !this.isArchitectureOnly() && !this.isImpactOnly();
|
|
75
|
+
const willRunCodeQuality = rulesToRun.length > 0 && !this.isArchitectureOnly(rulesToRun) && !this.isImpactOnly(rulesToRun);
|
|
76
76
|
const willRunArchitecture = !!config.architecture?.enabled;
|
|
77
77
|
const willRunImpact = !!(this.options.impact || config.impact?.enabled);
|
|
78
78
|
|
|
@@ -93,7 +93,7 @@ class CliActionHandler {
|
|
|
93
93
|
let results = null;
|
|
94
94
|
|
|
95
95
|
// Run code quality analysis (unless --architecture or --impact is used alone)
|
|
96
|
-
if (rulesToRun.length > 0 && !this.isArchitectureOnly() && !this.isImpactOnly()) {
|
|
96
|
+
if (rulesToRun.length > 0 && !this.isArchitectureOnly(rulesToRun) && !this.isImpactOnly(rulesToRun)) {
|
|
97
97
|
results = await this.runModernAnalysis(rulesToRun, targetingResult.files, config);
|
|
98
98
|
} else {
|
|
99
99
|
results = { results: [], summary: { total: 0, errors: 0, warnings: 0 } };
|
|
@@ -530,12 +530,16 @@ class CliActionHandler {
|
|
|
530
530
|
* Check if only architecture analysis was requested (no code quality rules)
|
|
531
531
|
* Following Rule C006: Verb-noun naming
|
|
532
532
|
*/
|
|
533
|
-
isArchitectureOnly() {
|
|
533
|
+
isArchitectureOnly(rulesToRun = []) {
|
|
534
534
|
const isArchEnabled = this.options.architecture || this.loadedConfig?.architecture?.enabled;
|
|
535
535
|
const isImpactEnabled = this.options.impact || this.loadedConfig?.impact?.enabled;
|
|
536
536
|
|
|
537
|
+
// If rules were selected via config (extends/rules), this is not architecture-only
|
|
538
|
+
const hasConfigBasedRules = rulesToRun.length > 0 && this.hasConfigBasedRuleSelection();
|
|
539
|
+
|
|
537
540
|
return isArchEnabled &&
|
|
538
541
|
!isImpactEnabled &&
|
|
542
|
+
!hasConfigBasedRules &&
|
|
539
543
|
!this.options.all &&
|
|
540
544
|
!this.options.specific &&
|
|
541
545
|
!this.options.rule &&
|
|
@@ -545,16 +549,29 @@ class CliActionHandler {
|
|
|
545
549
|
!this.options.category;
|
|
546
550
|
}
|
|
547
551
|
|
|
552
|
+
/**
|
|
553
|
+
* Check if rules were explicitly configured via config file (extends or rules field)
|
|
554
|
+
* Following Rule C006: Verb-noun naming
|
|
555
|
+
*/
|
|
556
|
+
hasConfigBasedRuleSelection() {
|
|
557
|
+
const config = this.loadedConfig || {};
|
|
558
|
+
return !!(config.extends || (config.rules && Object.keys(config.rules).length > 0));
|
|
559
|
+
}
|
|
560
|
+
|
|
548
561
|
/**
|
|
549
562
|
* Check if only impact analysis was requested (no code quality rules)
|
|
550
563
|
* Following Rule C006: Verb-noun naming
|
|
551
564
|
*/
|
|
552
|
-
isImpactOnly() {
|
|
565
|
+
isImpactOnly(rulesToRun = []) {
|
|
553
566
|
const isArchEnabled = this.options.architecture || this.loadedConfig?.architecture?.enabled;
|
|
554
567
|
const isImpactEnabled = this.options.impact || this.loadedConfig?.impact?.enabled;
|
|
555
568
|
|
|
569
|
+
// If rules were selected via config (extends/rules), this is not impact-only
|
|
570
|
+
const hasConfigBasedRules = rulesToRun.length > 0 && this.hasConfigBasedRuleSelection();
|
|
571
|
+
|
|
556
572
|
return isImpactEnabled &&
|
|
557
573
|
!isArchEnabled &&
|
|
574
|
+
!hasConfigBasedRules &&
|
|
558
575
|
!this.options.all &&
|
|
559
576
|
!this.options.specific &&
|
|
560
577
|
!this.options.rule &&
|
package/package.json
CHANGED
package/rules/common/C024_no_scatter_hardcoded_constants/typescript/symbol-based-analyzer.js
CHANGED
|
@@ -480,6 +480,11 @@ class C024SymbolBasedAnalyzer {
|
|
|
480
480
|
return;
|
|
481
481
|
}
|
|
482
482
|
|
|
483
|
+
// Skip strings inside throw/new Error() - error messages don't need to be constants
|
|
484
|
+
if (this.isInThrowOrErrorContext(literal)) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
483
488
|
this.trackConstant(constantUsage, `string:${value}`, literal);
|
|
484
489
|
|
|
485
490
|
if (this.isInLogicContext(literal) || this.isInComparison(literal)) {
|
|
@@ -1122,6 +1127,37 @@ class C024SymbolBasedAnalyzer {
|
|
|
1122
1127
|
return false;
|
|
1123
1128
|
}
|
|
1124
1129
|
|
|
1130
|
+
isInThrowOrErrorContext(node) {
|
|
1131
|
+
let parent = node.getParent();
|
|
1132
|
+
let depth = 0;
|
|
1133
|
+
const maxDepth = 5;
|
|
1134
|
+
|
|
1135
|
+
while (parent && depth < maxDepth) {
|
|
1136
|
+
const kind = parent.getKind();
|
|
1137
|
+
|
|
1138
|
+
// Direct: throw new Error("message")
|
|
1139
|
+
if (kind === SyntaxKind.ThrowStatement) {
|
|
1140
|
+
return true;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// new Error("message") or new CustomError("message")
|
|
1144
|
+
if (kind === SyntaxKind.NewExpression) {
|
|
1145
|
+
const expression = parent.getExpression();
|
|
1146
|
+
if (expression) {
|
|
1147
|
+
const text = expression.getText();
|
|
1148
|
+
if (/Error$/.test(text) || /Exception$/.test(text)) {
|
|
1149
|
+
return true;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
parent = parent.getParent();
|
|
1155
|
+
depth++;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
return false;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1125
1161
|
trackConstant(constantUsage, key, node) {
|
|
1126
1162
|
if (!constantUsage.has(key)) {
|
|
1127
1163
|
constantUsage.set(key, []);
|
|
@@ -153,6 +153,13 @@ class C041SymbolBasedAnalyzer {
|
|
|
153
153
|
/^[A-Z_]+CODE[A-Z_]*$/, // ERROR_CODE, STATUS_CODE
|
|
154
154
|
/^[A-Z_]+STATUS[A-Z_]*$/, // USER_STATUS
|
|
155
155
|
/^[A-Z_]+TYPE[A-Z_]*$/, // MESSAGE_TYPE
|
|
156
|
+
/^[A-Z_]+NAME[A-Z_]*$/, // REFRESH_TOKEN_COOKIE_NAME, SESSION_TOKEN_HEADER_NAME
|
|
157
|
+
/^[A-Z_]+KEY[A-Z_]*_NAME$/, // API_KEY_PARAM_NAME
|
|
158
|
+
/^[A-Z_]+HEADER[A-Z_]*$/, // AUTH_TOKEN_HEADER, CSRF_TOKEN_HEADER
|
|
159
|
+
/^[A-Z_]+COOKIE[A-Z_]*$/, // SESSION_TOKEN_COOKIE, REFRESH_TOKEN_COOKIE
|
|
160
|
+
/^[A-Z_]+LABEL[A-Z_]*$/, // TOKEN_LABEL, PASSWORD_LABEL
|
|
161
|
+
/^[A-Z_]+FIELD[A-Z_]*$/, // PASSWORD_FIELD, TOKEN_FIELD
|
|
162
|
+
/^[A-Z_]+PARAM[A-Z_]*$/, // API_KEY_PARAM, TOKEN_PARAM
|
|
156
163
|
];
|
|
157
164
|
|
|
158
165
|
// Parent object names that contain service/activity mappings (not secrets)
|
|
@@ -300,6 +307,11 @@ class C041SymbolBasedAnalyzer {
|
|
|
300
307
|
const isSensitiveName = this.sensitiveVariableNames.some(pattern => pattern.test(name));
|
|
301
308
|
|
|
302
309
|
if (isSensitiveName) {
|
|
310
|
+
// Skip if the variable name is a naming/labeling constant (e.g., REFRESH_TOKEN_COOKIE_NAME)
|
|
311
|
+
if (this.isErrorMessageConstant(name)) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
303
315
|
const initText = initializer.getText();
|
|
304
316
|
|
|
305
317
|
// Skip if using env variables or config
|
|
@@ -171,12 +171,18 @@ class C042Analyzer {
|
|
|
171
171
|
|
|
172
172
|
isBooleanValue(value) {
|
|
173
173
|
const trimmedValue = value.trim();
|
|
174
|
-
|
|
174
|
+
|
|
175
175
|
// Direct boolean literals
|
|
176
176
|
if (trimmedValue === 'true' || trimmedValue === 'false') {
|
|
177
177
|
return true;
|
|
178
178
|
}
|
|
179
|
-
|
|
179
|
+
|
|
180
|
+
// Ternary expressions: the result type depends on the branches, not the condition
|
|
181
|
+
// e.g., `typeof value === "string" ? value : JSON.stringify(value)` is NOT boolean
|
|
182
|
+
if (this.isTernaryExpression(trimmedValue)) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
|
|
180
186
|
// Boolean expressions that clearly result in boolean
|
|
181
187
|
const booleanExpressions = [
|
|
182
188
|
/\w+\s*[<>!=]=/, // Comparisons
|
|
@@ -283,6 +289,25 @@ class C042Analyzer {
|
|
|
283
289
|
return false;
|
|
284
290
|
}
|
|
285
291
|
|
|
292
|
+
isTernaryExpression(value) {
|
|
293
|
+
// Detect ternary operator: condition ? trueValue : falseValue
|
|
294
|
+
// Must account for nested ternaries and template literals with colons
|
|
295
|
+
const questionIndex = value.indexOf('?');
|
|
296
|
+
if (questionIndex === -1) return false;
|
|
297
|
+
|
|
298
|
+
// Check there's a colon after the question mark (outside of template literals)
|
|
299
|
+
const afterQuestion = value.slice(questionIndex + 1);
|
|
300
|
+
// Simple heuristic: contains `:` that's not inside a template literal or object
|
|
301
|
+
let depth = 0;
|
|
302
|
+
for (let i = 0; i < afterQuestion.length; i++) {
|
|
303
|
+
const ch = afterQuestion[i];
|
|
304
|
+
if (ch === '(' || ch === '[' || ch === '{') depth++;
|
|
305
|
+
else if (ch === ')' || ch === ']' || ch === '}') depth--;
|
|
306
|
+
else if (ch === ':' && depth === 0) return true;
|
|
307
|
+
}
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
|
|
286
311
|
generateSuggestions(varName) {
|
|
287
312
|
const suggestions = [];
|
|
288
313
|
const baseName = varName.replace(/^(is|has|should|can|will|must|may|check)/i, '');
|