@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,239 @@
1
+ /**
2
+ * Pattern Matchers for Heuristic Rules
3
+ * Common pattern matching utilities for code analysis
4
+ */
5
+
6
+ class PatternMatcher {
7
+ constructor() {
8
+ this.commonPatterns = this.loadCommonPatterns();
9
+ }
10
+
11
+ /**
12
+ * Load common patterns used across rules
13
+ * @returns {Object} Common patterns object
14
+ */
15
+ loadCommonPatterns() {
16
+ return {
17
+ // Function naming patterns
18
+ functionName: {
19
+ camelCase: /^[a-z][a-zA-Z0-9]*$/,
20
+ verbNoun: /^(get|set|is|has|can|should|will|create|update|delete|find|search|validate|process|handle|execute)[A-Z]/,
21
+ constructor: /^[A-Z][a-zA-Z0-9]*$/
22
+ },
23
+
24
+ // Variable naming patterns
25
+ variableName: {
26
+ camelCase: /^[a-z][a-zA-Z0-9]*$/,
27
+ constant: /^[A-Z][A-Z0-9_]*$/,
28
+ boolean: /^(is|has|can|should|will|did)[A-Z]/
29
+ },
30
+
31
+ // Log level patterns
32
+ logLevel: {
33
+ console: /console\.(log|info|warn|error|debug)/g,
34
+ logger: /(logger|log)\.(trace|debug|info|warn|error|fatal)/g,
35
+ customLogger: /\b(log|logger)\b.*\.(trace|debug|info|warn|error|fatal)/g
36
+ },
37
+
38
+ // Security patterns
39
+ security: {
40
+ hardcodedSecret: /(['"`])((?:password|secret|key|token|api_key|apikey)[:=]\s*[^'"`\s]+)\1/gi,
41
+ sqlInjection: /(query|execute)\s*\(\s*['"`][^'"`]*\+.*['"`]/g,
42
+ xss: /innerHTML\s*=\s*[^;]+/g
43
+ },
44
+
45
+ // TypeScript patterns
46
+ typescript: {
47
+ interface: /interface\s+([I]?[A-Z][a-zA-Z0-9]*)/g,
48
+ tsIgnore: /@ts-ignore(?!\s+.*:)/g,
49
+ emptyInterface: /interface\s+\w+\s*{\s*}/g
50
+ }
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Find matches for a pattern in content
56
+ * @param {string} content - Code content
57
+ * @param {RegExp} pattern - Pattern to match
58
+ * @returns {Array} Array of matches with line/column info
59
+ */
60
+ findMatches(content, pattern) {
61
+ const matches = [];
62
+ let match;
63
+
64
+ // Ensure pattern is global
65
+ const globalPattern = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : pattern.flags + 'g');
66
+
67
+ while ((match = globalPattern.exec(content)) !== null) {
68
+ const line = this.getLineNumber(content, match.index);
69
+ const column = match.index - this.getLineStart(content, match.index);
70
+
71
+ matches.push({
72
+ match: match[0],
73
+ groups: match.slice(1),
74
+ line: line,
75
+ column: column,
76
+ index: match.index,
77
+ lineContent: this.getLineContent(content, line)
78
+ });
79
+ }
80
+
81
+ return matches;
82
+ }
83
+
84
+ /**
85
+ * Find function naming violations
86
+ * @param {string} content - Code content
87
+ * @returns {Array} Array of violations
88
+ */
89
+ findFunctionNamingViolations(content) {
90
+ const violations = [];
91
+ const functionRegex = /(?:function\s+(\w+)|(\w+)\s*[:=]\s*(?:function|async\s+function|\([^)]*\)\s*=>))/g;
92
+ let match;
93
+
94
+ while ((match = functionRegex.exec(content)) !== null) {
95
+ const functionName = match[1] || match[2];
96
+ const line = this.getLineNumber(content, match.index);
97
+
98
+ // Skip if it's a constructor (starts with capital letter)
99
+ if (/^[A-Z]/.test(functionName)) {
100
+ continue;
101
+ }
102
+
103
+ // Check if it follows verb-noun pattern
104
+ if (!this.commonPatterns.functionName.verbNoun.test(functionName)) {
105
+ violations.push({
106
+ type: 'function-naming',
107
+ message: `Function name '${functionName}' should follow verb-noun pattern (e.g., getUserData, validateInput)`,
108
+ line: line,
109
+ column: match.index - this.getLineStart(content, match.index),
110
+ functionName: functionName
111
+ });
112
+ }
113
+ }
114
+
115
+ return violations;
116
+ }
117
+
118
+ /**
119
+ * Find log level usage violations
120
+ * @param {string} content - Code content
121
+ * @returns {Array} Array of violations
122
+ */
123
+ findLogLevelViolations(content) {
124
+ const violations = [];
125
+
126
+ // Check for console.log usage (should use appropriate log levels)
127
+ const consoleLogMatches = this.findMatches(content, /console\.log\s*\(/g);
128
+ consoleLogMatches.forEach(match => {
129
+ violations.push({
130
+ type: 'log-level',
131
+ message: 'Use appropriate log level instead of console.log (info, warn, error)',
132
+ line: match.line,
133
+ column: match.column,
134
+ suggestion: 'Replace with console.info, console.warn, or console.error'
135
+ });
136
+ });
137
+
138
+ return violations;
139
+ }
140
+
141
+ /**
142
+ * Find hardcoded secrets
143
+ * @param {string} content - Code content
144
+ * @returns {Array} Array of violations
145
+ */
146
+ findHardcodedSecrets(content) {
147
+ const violations = [];
148
+ const secretPatterns = [
149
+ /['"`](password|secret|key|token|api_key|apikey)['"`]\s*[:=]\s*['"`][a-zA-Z0-9+/=]{8,}['"`]/gi,
150
+ /(password|secret|key|token)\s*=\s*['"`][a-zA-Z0-9+/=]{8,}['"`]/gi
151
+ ];
152
+
153
+ secretPatterns.forEach(pattern => {
154
+ const matches = this.findMatches(content, pattern);
155
+ matches.forEach(match => {
156
+ violations.push({
157
+ type: 'hardcoded-secret',
158
+ message: 'Hardcoded secret detected. Use environment variables or secure configuration',
159
+ line: match.line,
160
+ column: match.column,
161
+ severity: 'error'
162
+ });
163
+ });
164
+ });
165
+
166
+ return violations;
167
+ }
168
+
169
+ /**
170
+ * Find TypeScript interface violations
171
+ * @param {string} content - Code content
172
+ * @returns {Array} Array of violations
173
+ */
174
+ findTypeScriptInterfaceViolations(content) {
175
+ const violations = [];
176
+
177
+ // Interface should start with 'I' prefix
178
+ const interfaceMatches = this.findMatches(content, /interface\s+([A-Z][a-zA-Z0-9]*)/g);
179
+ interfaceMatches.forEach(match => {
180
+ const interfaceName = match.groups[0];
181
+ if (!interfaceName.startsWith('I')) {
182
+ violations.push({
183
+ type: 'interface-naming',
184
+ message: `Interface '${interfaceName}' should start with 'I' prefix`,
185
+ line: match.line,
186
+ column: match.column,
187
+ suggestion: `I${interfaceName}`
188
+ });
189
+ }
190
+ });
191
+
192
+ return violations;
193
+ }
194
+
195
+ /**
196
+ * Get line number for character index
197
+ */
198
+ getLineNumber(content, index) {
199
+ return content.substring(0, index).split('\n').length;
200
+ }
201
+
202
+ /**
203
+ * Get line start position
204
+ */
205
+ getLineStart(content, index) {
206
+ const beforeIndex = content.substring(0, index);
207
+ const lastNewline = beforeIndex.lastIndexOf('\n');
208
+ return lastNewline === -1 ? 0 : lastNewline + 1;
209
+ }
210
+
211
+ /**
212
+ * Get line content
213
+ */
214
+ getLineContent(content, lineNumber) {
215
+ const lines = content.split('\n');
216
+ return lines[lineNumber - 1] || '';
217
+ }
218
+
219
+ /**
220
+ * Create a custom pattern matcher
221
+ * @param {string} name - Pattern name
222
+ * @param {RegExp} pattern - Regular expression
223
+ * @param {Function} validator - Optional validator function
224
+ * @returns {Function} Pattern matcher function
225
+ */
226
+ createMatcher(name, pattern, validator = null) {
227
+ return (content) => {
228
+ const matches = this.findMatches(content, pattern);
229
+
230
+ if (validator) {
231
+ return matches.filter(match => validator(match));
232
+ }
233
+
234
+ return matches;
235
+ };
236
+ }
237
+ }
238
+
239
+ module.exports = { PatternMatcher };
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Rule Helpers for Heuristic Rules
3
+ * Utilities for rule configuration, violation reporting, and common operations
4
+ */
5
+
6
+ class RuleHelper {
7
+ constructor() {
8
+ this.severityLevels = ['off', 'info', 'warn', 'error'];
9
+ this.violationTypes = ['syntax', 'style', 'security', 'performance', 'maintainability'];
10
+ }
11
+
12
+ /**
13
+ * Create a standard violation object
14
+ * @param {Object} options - Violation options
15
+ * @returns {Object} Standardized violation object
16
+ */
17
+ createViolation(options) {
18
+ const {
19
+ ruleId,
20
+ message,
21
+ line = 1,
22
+ column = 0,
23
+ severity = 'error',
24
+ type = 'style',
25
+ suggestion = null,
26
+ fix = null
27
+ } = options;
28
+
29
+ return {
30
+ ruleId: ruleId,
31
+ message: message,
32
+ line: line,
33
+ column: column,
34
+ severity: this.validateSeverity(severity),
35
+ type: type,
36
+ suggestion: suggestion,
37
+ fix: fix,
38
+ timestamp: new Date().toISOString()
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Validate severity level
44
+ * @param {string} severity - Severity level
45
+ * @returns {string} Valid severity level
46
+ */
47
+ validateSeverity(severity) {
48
+ return this.severityLevels.includes(severity) ? severity : 'error';
49
+ }
50
+
51
+ /**
52
+ * Load rule configuration with defaults
53
+ * @param {string} ruleId - Rule ID
54
+ * @param {Object} userConfig - User configuration
55
+ * @returns {Object} Merged configuration
56
+ */
57
+ loadRuleConfig(ruleId, userConfig = {}) {
58
+ const defaultConfig = {
59
+ enabled: true,
60
+ severity: 'error',
61
+ options: {},
62
+ patterns: {
63
+ include: ['**/*.js', '**/*.ts'],
64
+ exclude: ['**/*.test.*', '**/*.spec.*', 'node_modules/**']
65
+ }
66
+ };
67
+
68
+ return {
69
+ ...defaultConfig,
70
+ ...userConfig,
71
+ ruleId: ruleId,
72
+ patterns: {
73
+ ...defaultConfig.patterns,
74
+ ...(userConfig.patterns || {})
75
+ },
76
+ options: {
77
+ ...defaultConfig.options,
78
+ ...(userConfig.options || {})
79
+ }
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Check if file should be analyzed by rule
85
+ * @param {string} filePath - File path
86
+ * @param {Object} config - Rule configuration
87
+ * @returns {boolean} True if file should be analyzed
88
+ */
89
+ shouldAnalyzeFile(filePath, config) {
90
+ const { patterns } = config;
91
+
92
+ // Check exclusions first
93
+ if (patterns.exclude && patterns.exclude.length > 0) {
94
+ for (const pattern of patterns.exclude) {
95
+ if (this.matchPattern(filePath, pattern)) {
96
+ return false;
97
+ }
98
+ }
99
+ }
100
+
101
+ // Check inclusions
102
+ if (patterns.include && patterns.include.length > 0) {
103
+ for (const pattern of patterns.include) {
104
+ if (this.matchPattern(filePath, pattern)) {
105
+ return true;
106
+ }
107
+ }
108
+ return false; // No include patterns matched
109
+ }
110
+
111
+ return true; // No specific patterns, analyze by default
112
+ }
113
+
114
+ /**
115
+ * Simple pattern matching (supports * wildcards)
116
+ * @param {string} filePath - File path
117
+ * @param {string} pattern - Pattern to match
118
+ * @returns {boolean} True if pattern matches
119
+ */
120
+ matchPattern(filePath, pattern) {
121
+ // Convert glob pattern to regex
122
+ const regexPattern = pattern
123
+ .replace(/\./g, '\\.')
124
+ .replace(/\*\*/g, '.*')
125
+ .replace(/\*/g, '[^/]*')
126
+ .replace(/\?/g, '.');
127
+
128
+ const regex = new RegExp(`^${regexPattern}$`, 'i');
129
+ return regex.test(filePath);
130
+ }
131
+
132
+ /**
133
+ * Format violation message with context
134
+ * @param {Object} violation - Violation object
135
+ * @param {string} context - Additional context
136
+ * @returns {string} Formatted message
137
+ */
138
+ formatViolationMessage(violation, context = '') {
139
+ const { ruleId, message, line, column, severity } = violation;
140
+ const location = `${line}:${column}`;
141
+ const prefix = `[${severity.toUpperCase()}] ${ruleId}`;
142
+
143
+ let formatted = `${prefix} at ${location}: ${message}`;
144
+
145
+ if (context) {
146
+ formatted += `\n Context: ${context}`;
147
+ }
148
+
149
+ if (violation.suggestion) {
150
+ formatted += `\n Suggestion: ${violation.suggestion}`;
151
+ }
152
+
153
+ return formatted;
154
+ }
155
+
156
+ /**
157
+ * Group violations by type/severity
158
+ * @param {Array} violations - Array of violations
159
+ * @returns {Object} Grouped violations
160
+ */
161
+ groupViolations(violations) {
162
+ const grouped = {
163
+ bySeverity: {},
164
+ byType: {},
165
+ byRule: {}
166
+ };
167
+
168
+ violations.forEach(violation => {
169
+ // Group by severity
170
+ if (!grouped.bySeverity[violation.severity]) {
171
+ grouped.bySeverity[violation.severity] = [];
172
+ }
173
+ grouped.bySeverity[violation.severity].push(violation);
174
+
175
+ // Group by type
176
+ if (!grouped.byType[violation.type]) {
177
+ grouped.byType[violation.type] = [];
178
+ }
179
+ grouped.byType[violation.type].push(violation);
180
+
181
+ // Group by rule
182
+ if (!grouped.byRule[violation.ruleId]) {
183
+ grouped.byRule[violation.ruleId] = [];
184
+ }
185
+ grouped.byRule[violation.ruleId].push(violation);
186
+ });
187
+
188
+ return grouped;
189
+ }
190
+
191
+ /**
192
+ * Generate violation statistics
193
+ * @param {Array} violations - Array of violations
194
+ * @returns {Object} Statistics object
195
+ */
196
+ generateStats(violations) {
197
+ const grouped = this.groupViolations(violations);
198
+
199
+ return {
200
+ total: violations.length,
201
+ severity: {
202
+ error: (grouped.bySeverity.error || []).length,
203
+ warn: (grouped.bySeverity.warn || []).length,
204
+ info: (grouped.bySeverity.info || []).length
205
+ },
206
+ types: Object.keys(grouped.byType).map(type => ({
207
+ type,
208
+ count: grouped.byType[type].length
209
+ })),
210
+ rules: Object.keys(grouped.byRule).map(ruleId => ({
211
+ ruleId,
212
+ count: grouped.byRule[ruleId].length
213
+ })).sort((a, b) => b.count - a.count)
214
+ };
215
+ }
216
+
217
+ /**
218
+ * Check if rule should be skipped for file
219
+ * @param {string} content - File content
220
+ * @param {string} ruleId - Rule ID
221
+ * @returns {boolean} True if rule should be skipped
222
+ */
223
+ shouldSkipRule(content, ruleId) {
224
+ // Check for disable comments
225
+ const disablePatterns = [
226
+ `// sunlint-disable-next-line ${ruleId}`,
227
+ `/* sunlint-disable-next-line ${ruleId} */`,
228
+ `// sunlint-disable ${ruleId}`,
229
+ `/* sunlint-disable ${ruleId} */`
230
+ ];
231
+
232
+ return disablePatterns.some(pattern => content.includes(pattern));
233
+ }
234
+
235
+ /**
236
+ * Extract context around a violation
237
+ * @param {string} content - File content
238
+ * @param {number} line - Line number
239
+ * @param {number} contextLines - Number of context lines
240
+ * @returns {Object} Context information
241
+ */
242
+ extractContext(content, line, contextLines = 2) {
243
+ const lines = content.split('\n');
244
+ const startLine = Math.max(0, line - 1 - contextLines);
245
+ const endLine = Math.min(lines.length, line + contextLines);
246
+
247
+ const contextText = lines.slice(startLine, endLine)
248
+ .map((text, index) => {
249
+ const lineNum = startLine + index + 1;
250
+ const marker = lineNum === line ? '>' : ' ';
251
+ return `${marker} ${lineNum.toString().padStart(3)}: ${text}`;
252
+ })
253
+ .join('\n');
254
+
255
+ return {
256
+ startLine: startLine + 1,
257
+ endLine: endLine,
258
+ text: contextText,
259
+ violationLine: lines[line - 1] || ''
260
+ };
261
+ }
262
+ }
263
+
264
+ module.exports = { RuleHelper };
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Centralized Severity Constants for SunLint Rules
3
+ * Ensures consistency across all rule implementations
4
+ */
5
+
6
+ const SEVERITY = {
7
+ OFF: 'off',
8
+ INFO: 'info',
9
+ WARNING: 'warning',
10
+ ERROR: 'error'
11
+ };
12
+
13
+ // Default severities by rule category
14
+ const DEFAULT_SEVERITIES = {
15
+ // Quality rules - generally warnings (can be fixed incrementally)
16
+ QUALITY: SEVERITY.WARNING,
17
+
18
+ // Security rules - generally errors (must be fixed)
19
+ SECURITY: SEVERITY.ERROR,
20
+
21
+ // Performance rules - generally warnings
22
+ PERFORMANCE: SEVERITY.WARNING,
23
+
24
+ // Maintainability rules - generally warnings
25
+ MAINTAINABILITY: SEVERITY.WARNING,
26
+
27
+ // Best practices - generally warnings
28
+ BEST_PRACTICE: SEVERITY.WARNING,
29
+
30
+ // Critical security - always errors
31
+ CRITICAL_SECURITY: SEVERITY.ERROR
32
+ };
33
+
34
+ // Specific rule overrides (if needed)
35
+ const RULE_SEVERITY_OVERRIDES = {
36
+ // Security rules that should be errors
37
+ 'S001': SEVERITY.ERROR,
38
+ 'S002': SEVERITY.ERROR,
39
+ 'S005': SEVERITY.ERROR,
40
+ 'S012': SEVERITY.ERROR, // No hardcoded secrets
41
+ 'S013': SEVERITY.ERROR, // Always use TLS
42
+
43
+ // Quality rules that might be info for gradual adoption
44
+ // 'C007': SEVERITY.INFO, // Comment quality - can be relaxed initially
45
+
46
+ // Rules that should be strict errors
47
+ 'C043': SEVERITY.ERROR // No console.log in production
48
+ };
49
+
50
+ /**
51
+ * Get the appropriate severity for a rule
52
+ * @param {string} ruleId - The rule ID (e.g., 'C010', 'S005')
53
+ * @param {string} category - The rule category (quality, security, etc.)
54
+ * @param {string} [configOverride] - Override from configuration
55
+ * @returns {string} The severity level
56
+ */
57
+ function getSeverity(ruleId, category, configOverride = null) {
58
+ // 1. Configuration override has highest priority
59
+ if (configOverride && Object.values(SEVERITY).includes(configOverride)) {
60
+ return configOverride;
61
+ }
62
+
63
+ // 2. Rule-specific override
64
+ if (RULE_SEVERITY_OVERRIDES[ruleId]) {
65
+ return RULE_SEVERITY_OVERRIDES[ruleId];
66
+ }
67
+
68
+ // 3. Category default
69
+ const categoryKey = category?.toUpperCase();
70
+ if (DEFAULT_SEVERITIES[categoryKey]) {
71
+ return DEFAULT_SEVERITIES[categoryKey];
72
+ }
73
+
74
+ // 4. Fall back to warning
75
+ return SEVERITY.WARNING;
76
+ }
77
+
78
+ /**
79
+ * Validate severity value
80
+ * @param {string} severity - Severity to validate
81
+ * @returns {boolean} True if valid
82
+ */
83
+ function isValidSeverity(severity) {
84
+ return Object.values(SEVERITY).includes(severity);
85
+ }
86
+
87
+ module.exports = {
88
+ SEVERITY,
89
+ DEFAULT_SEVERITIES,
90
+ RULE_SEVERITY_OVERRIDES,
91
+ getSeverity,
92
+ isValidSeverity
93
+ };