@sun-asterisk/sunlint 1.3.0 → 1.3.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 (124) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/CONTRIBUTING.md +249 -605
  3. package/README.md +3 -4
  4. package/config/ci-cd.json +54 -0
  5. package/config/development.json +56 -0
  6. package/config/large-project.json +143 -0
  7. package/config/presets/all.json +0 -1
  8. package/config/release.json +70 -0
  9. package/config/rule-analysis-strategies.js +38 -3
  10. package/config/rules/enhanced-rules-registry.json +474 -1179
  11. package/config/rules/rules-registry-generated.json +3 -3
  12. package/core/cli-action-handler.js +24 -30
  13. package/core/cli-program.js +11 -3
  14. package/core/config-merger.js +29 -2
  15. package/core/enhanced-rules-registry.js +3 -2
  16. package/core/semantic-engine.js +129 -19
  17. package/core/semantic-rule-base.js +4 -2
  18. package/core/unified-rule-registry.js +1 -1
  19. package/docs/COMMAND-EXAMPLES.md +134 -0
  20. package/docs/LARGE-PROJECT-GUIDE.md +324 -0
  21. package/engines/heuristic-engine.js +135 -16
  22. package/integrations/eslint/plugin/index.js +0 -2
  23. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
  24. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
  25. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
  26. package/origin-rules/common-en.md +19 -15
  27. package/package.json +1 -1
  28. package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
  29. package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
  30. package/rules/common/C006_function_naming/analyzer.js +29 -3
  31. package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
  32. package/rules/common/C010_limit_block_nesting/config.json +64 -0
  33. package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
  34. package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
  35. package/rules/common/C013_no_dead_code/analyzer.js +75 -177
  36. package/rules/common/C013_no_dead_code/config.json +61 -0
  37. package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
  38. package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
  39. package/rules/common/C014_dependency_injection/analyzer.js +48 -313
  40. package/rules/common/C014_dependency_injection/config.json +26 -0
  41. package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
  42. package/rules/common/C017_constructor_logic/analyzer.js +254 -17
  43. package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
  44. package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
  45. package/rules/common/C018_no_throw_generic_error/config.json +50 -0
  46. package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
  47. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
  48. package/rules/common/C019_log_level_usage/analyzer.js +110 -317
  49. package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
  50. package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
  51. package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
  52. package/rules/common/C023_no_duplicate_variable/config.json +50 -0
  53. package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
  54. package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
  55. package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
  56. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
  57. package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
  58. package/rules/common/C033_separate_service_repository/README.md +78 -0
  59. package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
  60. package/rules/common/C033_separate_service_repository/config.json +50 -0
  61. package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
  62. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
  63. package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
  64. package/rules/common/C035_error_logging_context/analyzer.js +232 -0
  65. package/rules/common/C035_error_logging_context/config.json +54 -0
  66. package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
  67. package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
  68. package/rules/common/C040_centralized_validation/analyzer.js +165 -0
  69. package/rules/common/C040_centralized_validation/config.json +46 -0
  70. package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
  71. package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
  72. package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
  73. package/rules/common/C076_explicit_function_types/README.md +30 -0
  74. package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
  75. package/rules/common/C076_explicit_function_types/config.json +15 -0
  76. package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
  77. package/rules/index.js +6 -1
  78. package/rules/parser/rule-parser.js +13 -2
  79. package/rules/security/S005_no_origin_auth/README.md +226 -0
  80. package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
  81. package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
  82. package/rules/security/S005_no_origin_auth/config.json +85 -0
  83. package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
  84. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
  85. package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
  86. package/rules/security/S007_no_plaintext_otp/README.md +198 -0
  87. package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
  88. package/rules/security/S007_no_plaintext_otp/config.json +79 -0
  89. package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
  90. package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
  91. package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
  92. package/rules/security/S009_no_insecure_encryption/README.md +158 -0
  93. package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
  94. package/rules/security/S009_no_insecure_encryption/config.json +55 -0
  95. package/rules/security/S010_no_insecure_encryption/README.md +224 -0
  96. package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
  97. package/rules/security/S010_no_insecure_encryption/config.json +48 -0
  98. package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
  99. package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
  100. package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
  101. package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
  102. package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
  103. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
  104. package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
  105. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
  106. package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
  107. package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
  108. package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
  109. package/rules/security/S055_content_type_validation/README.md +176 -0
  110. package/rules/security/S055_content_type_validation/analyzer.js +312 -0
  111. package/rules/security/S055_content_type_validation/config.json +48 -0
  112. package/rules/utils/rule-helpers.js +140 -1
  113. package/scripts/consolidate-config.js +116 -0
  114. package/scripts/prepare-release.sh +1 -1
  115. package/config/rules/rules-registry.json +0 -765
  116. package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
  117. package/docs/FUTURE_PACKAGES.md +0 -83
  118. package/docs/HEURISTIC_VS_AI.md +0 -113
  119. package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
  120. package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
  121. package/docs/RELEASE_GUIDE.md +0 -230
  122. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
  123. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
  124. package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Heuristic analyzer for S055 - Content-Type Validation in REST Services
3
+ * Purpose: Detect REST endpoints that process request body without validating Content-Type
4
+ * Based on OWASP ASVS 13.2.5 - Input Validation
5
+ */
6
+
7
+ class S055Analyzer {
8
+ constructor() {
9
+ this.ruleId = 'S055';
10
+ this.ruleName = 'Content-Type Validation in REST Services';
11
+ this.description = 'Verify that REST services explicitly check the incoming Content-Type';
12
+
13
+ // HTTP methods that typically have request bodies
14
+ this.httpMethodsWithBody = [
15
+ 'post', 'put', 'patch', 'delete'
16
+ ];
17
+
18
+ // Patterns that indicate request body usage
19
+ this.requestBodyPatterns = [
20
+ // Express.js patterns
21
+ /req\.body/i,
22
+ /request\.body/i,
23
+
24
+ // NestJS patterns
25
+ /@Body\(\)/i,
26
+ /@Body\([^)]*\)/i,
27
+
28
+ // Generic body access patterns
29
+ /\.body\s*[;\.,\]\}]/i,
30
+ /body\s*[:=]/i,
31
+ ];
32
+
33
+ // Patterns that indicate Content-Type validation
34
+ this.contentTypeValidationPatterns = [
35
+ // Express.js validation methods
36
+ /req\.is\s*\(\s*['"`][^'"`]*application\/[^'"`]*['"`]\s*\)/i,
37
+ /request\.is\s*\(\s*['"`][^'"`]*application\/[^'"`]*['"`]\s*\)/i,
38
+
39
+ // Direct header checks
40
+ /req\.headers\s*\[\s*['"`]content-type['"`]\s*\]/i,
41
+ /request\.headers\s*\[\s*['"`]content-type['"`]\s*\]/i,
42
+ /req\.get\s*\(\s*['"`]content-type['"`]\s*\)/i,
43
+ /request\.get\s*\(\s*['"`]content-type['"`]\s*\)/i,
44
+
45
+ // Content-Type comparison
46
+ /content-type\s*[=!]==?\s*['"`]application\//i,
47
+ /['"`]application\/[^'"`]*['"`]\s*[=!]==?\s*.*content-type/i,
48
+
49
+ // Middleware patterns
50
+ /express\.json\s*\(/i,
51
+ /bodyParser\.json\s*\(/i,
52
+ /app\.use\s*\([^)]*json[^)]*\)/i,
53
+
54
+ // NestJS decorators
55
+ /@Header\s*\(\s*['"`]Content-Type['"`]/i,
56
+ /@UseInterceptors\s*\([^)]*ContentType[^)]*\)/i,
57
+
58
+ // Custom validation functions
59
+ /validateContentType/i,
60
+ /checkContentType/i,
61
+ /verifyContentType/i,
62
+ ];
63
+
64
+ // Patterns that indicate HTTP method handlers
65
+ this.httpHandlerPatterns = [
66
+ // Express.js route definitions
67
+ /app\.(post|put|patch|delete)\s*\(/i,
68
+ /router\.(post|put|patch|delete)\s*\(/i,
69
+ /express\(\)\.(post|put|patch|delete)\s*\(/i,
70
+
71
+ // NestJS decorators
72
+ /@(Post|Put|Patch|Delete)\s*\(/i,
73
+
74
+ // Generic handler patterns
75
+ /(post|put|patch|delete)\s*:\s*(async\s+)?function/i,
76
+ /(post|put|patch|delete)\s*:\s*\(/i,
77
+
78
+ // Function names indicating HTTP handlers
79
+ /function\s+(handle|process)?(Post|Put|Patch|Delete)/i,
80
+ /const\s+\w*(post|put|patch|delete)\w*\s*=/i,
81
+ /let\s+\w*(post|put|patch|delete)\w*\s*=/i,
82
+ ];
83
+
84
+ // Safe patterns to exclude from violations
85
+ this.safePatterns = [
86
+ // Comments and documentation
87
+ /\/\/|\/\*|\*\/|@param|@return|@example/,
88
+
89
+ // Import/export statements
90
+ /import|export|require|module\.exports/i,
91
+
92
+ // Type definitions
93
+ /interface|type|enum|declare/i,
94
+
95
+ // Test files patterns
96
+ /describe\s*\(|it\s*\(|test\s*\(|expect\s*\(/i,
97
+
98
+ // Configuration and constants
99
+ /const\s+\w+\s*=\s*['"`]/i,
100
+
101
+ // Logging and debugging
102
+ /console\.|logger\.|log\(/i,
103
+
104
+ // Middleware already handling Content-Type
105
+ /express\.json|bodyParser\.json|multer\(/i,
106
+ ];
107
+
108
+ // Patterns indicating secure implementations
109
+ this.secureImplementationPatterns = [
110
+ // Middleware usage that handles Content-Type
111
+ /app\.use\s*\([^)]*express\.json[^)]*\)/i,
112
+ /app\.use\s*\([^)]*bodyParser\.json[^)]*\)/i,
113
+
114
+ // Global Content-Type validation
115
+ /app\.use\s*\([^)]*validateContentType[^)]*\)/i,
116
+ /app\.use\s*\([^)]*checkContentType[^)]*\)/i,
117
+ ];
118
+ }
119
+
120
+ async analyze(files, language, options = {}) {
121
+ const violations = [];
122
+
123
+ for (const filePath of files) {
124
+ // Skip test files, build directories, and node_modules
125
+ if (this.shouldSkipFile(filePath)) {
126
+ continue;
127
+ }
128
+
129
+ try {
130
+ const content = require('fs').readFileSync(filePath, 'utf8');
131
+ const fileViolations = this.analyzeFile(content, filePath, options);
132
+ violations.push(...fileViolations);
133
+ } catch (error) {
134
+ if (options.verbose) {
135
+ console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
136
+ }
137
+ }
138
+ }
139
+
140
+ return violations;
141
+ }
142
+
143
+ shouldSkipFile(filePath) {
144
+ const skipPatterns = [
145
+ 'test/', 'tests/', '__tests__/', '.test.', '.spec.',
146
+ 'node_modules/', 'build/', 'dist/', '.next/', 'coverage/',
147
+ 'vendor/', 'mocks/', '.mock.',
148
+ // Config files
149
+ 'config/', 'configs/', '.config.',
150
+ // Static assets
151
+ 'public/', 'static/', 'assets/',
152
+ ];
153
+
154
+ return skipPatterns.some(pattern => filePath.includes(pattern));
155
+ }
156
+
157
+ analyzeFile(content, filePath, options = {}) {
158
+ const violations = [];
159
+ const lines = content.split('\n');
160
+
161
+ // First, check if file has global Content-Type validation (middleware)
162
+ const hasGlobalValidation = this.hasGlobalContentTypeValidation(content);
163
+
164
+ lines.forEach((line, index) => {
165
+ const lineNumber = index + 1;
166
+ const trimmedLine = line.trim();
167
+
168
+ // Skip comments, imports, and empty lines
169
+ if (this.shouldSkipLine(trimmedLine)) {
170
+ return;
171
+ }
172
+
173
+ // Check for potential Content-Type validation violations
174
+ const violation = this.checkForContentTypeViolation(
175
+ line,
176
+ lineNumber,
177
+ filePath,
178
+ content,
179
+ hasGlobalValidation
180
+ );
181
+ if (violation) {
182
+ violations.push(violation);
183
+ }
184
+ });
185
+
186
+ return violations;
187
+ }
188
+
189
+ shouldSkipLine(line) {
190
+ return (
191
+ line.length === 0 ||
192
+ this.safePatterns.some(pattern => pattern.test(line))
193
+ );
194
+ }
195
+
196
+ hasGlobalContentTypeValidation(content) {
197
+ return this.secureImplementationPatterns.some(pattern => pattern.test(content));
198
+ }
199
+
200
+ checkForContentTypeViolation(line, lineNumber, filePath, fullContent, hasGlobalValidation) {
201
+ // Check if line contains request body usage
202
+ const hasRequestBodyUsage = this.requestBodyPatterns.some(pattern => pattern.test(line));
203
+
204
+ if (!hasRequestBodyUsage) {
205
+ return null;
206
+ }
207
+
208
+ // Check if this line is part of an HTTP handler
209
+ const isInHttpHandler = this.isInHttpHandlerContext(line, lineNumber, fullContent);
210
+
211
+ if (!isInHttpHandler) {
212
+ return null;
213
+ }
214
+
215
+ // Skip if there's global validation
216
+ if (hasGlobalValidation) {
217
+ return null;
218
+ }
219
+
220
+ // Check if there's local Content-Type validation in the same function/handler
221
+ const hasLocalValidation = this.hasLocalContentTypeValidation(lineNumber, fullContent);
222
+
223
+ if (hasLocalValidation) {
224
+ return null;
225
+ }
226
+
227
+ // Check if this is a NestJS handler with proper decorators
228
+ if (this.isSecureNestJSHandler(lineNumber, fullContent)) {
229
+ return null;
230
+ }
231
+
232
+ return {
233
+ ruleId: this.ruleId,
234
+ severity: 'error',
235
+ message: 'REST endpoint processes request body without validating Content-Type header. This can lead to security vulnerabilities.',
236
+ line: lineNumber,
237
+ column: this.findPatternColumn(line, this.requestBodyPatterns),
238
+ filePath: filePath,
239
+ type: 'missing_content_type_validation',
240
+ details: 'Consider adding Content-Type validation using req.is("application/json") or checking req.headers["content-type"] before processing request body.'
241
+ };
242
+ }
243
+
244
+ isInHttpHandlerContext(line, lineNumber, fullContent) {
245
+ const lines = fullContent.split('\n');
246
+
247
+ // Check previous lines for HTTP handler patterns
248
+ const contextRange = Math.max(0, lineNumber - 10); // Check up to 10 lines back
249
+
250
+ for (let i = contextRange; i < lineNumber; i++) {
251
+ const contextLine = lines[i];
252
+ if (this.httpHandlerPatterns.some(pattern => pattern.test(contextLine))) {
253
+ return true;
254
+ }
255
+ }
256
+
257
+ // Check current line
258
+ if (this.httpHandlerPatterns.some(pattern => pattern.test(line))) {
259
+ return true;
260
+ }
261
+
262
+ return false;
263
+ }
264
+
265
+ hasLocalContentTypeValidation(lineNumber, fullContent) {
266
+ const lines = fullContent.split('\n');
267
+
268
+ // Check surrounding lines for Content-Type validation
269
+ const startLine = Math.max(0, lineNumber - 15);
270
+ const endLine = Math.min(lines.length, lineNumber + 10);
271
+
272
+ for (let i = startLine; i < endLine; i++) {
273
+ const checkLine = lines[i];
274
+ if (this.contentTypeValidationPatterns.some(pattern => pattern.test(checkLine))) {
275
+ return true;
276
+ }
277
+ }
278
+
279
+ return false;
280
+ }
281
+
282
+ isSecureNestJSHandler(lineNumber, fullContent) {
283
+ const lines = fullContent.split('\n');
284
+
285
+ // Check previous lines for NestJS decorators that handle Content-Type
286
+ const contextRange = Math.max(0, lineNumber - 5);
287
+
288
+ for (let i = contextRange; i < lineNumber; i++) {
289
+ const line = lines[i];
290
+ if (/@Header\s*\(\s*['"`]Content-Type['"`]/i.test(line)) {
291
+ return true;
292
+ }
293
+ if (/@UseInterceptors\s*\([^)]*ContentType[^)]*\)/i.test(line)) {
294
+ return true;
295
+ }
296
+ }
297
+
298
+ return false;
299
+ }
300
+
301
+ findPatternColumn(line, patterns) {
302
+ for (const pattern of patterns) {
303
+ const match = pattern.exec(line);
304
+ if (match) {
305
+ return match.index + 1;
306
+ }
307
+ }
308
+ return 1;
309
+ }
310
+ }
311
+
312
+ module.exports = S055Analyzer;
@@ -0,0 +1,48 @@
1
+ {
2
+ "ruleId": "S055",
3
+ "name": "Content-Type Validation in REST Services",
4
+ "description": "Verify that REST services explicitly check the incoming Content-Type to be the expected one",
5
+ "category": "security",
6
+ "severity": "error",
7
+ "languages": ["typescript", "javascript"],
8
+ "tags": ["security", "owasp", "rest-api", "input-validation", "asvs"],
9
+ "enabled": true,
10
+ "fixable": false,
11
+ "engine": "heuristic",
12
+ "metadata": {
13
+ "owaspCategory": "OWASP ASVS 13.2.5",
14
+ "cweId": "CWE-20",
15
+ "description": "REST services should validate the Content-Type header to ensure they receive data in the expected format. Missing Content-Type validation can lead to security vulnerabilities where attackers send malicious data in unexpected formats.",
16
+ "impact": "Medium - Data injection, parsing errors, security bypass",
17
+ "likelihood": "Medium",
18
+ "remediation": "Always validate Content-Type header before processing request body in REST endpoints"
19
+ },
20
+ "patterns": {
21
+ "vulnerable": [
22
+ "Processing req.body without checking Content-Type",
23
+ "Accepting any Content-Type in REST endpoints",
24
+ "Missing Content-Type validation in Express/NestJS handlers",
25
+ "Directly parsing request body without format validation"
26
+ ],
27
+ "secure": [
28
+ "Using req.is('application/json') to validate Content-Type",
29
+ "Checking req.headers['content-type'] before processing",
30
+ "Rejecting requests with unexpected Content-Type",
31
+ "Using middleware for Content-Type validation"
32
+ ]
33
+ },
34
+ "examples": {
35
+ "violations": [
36
+ "app.post('/api/users', (req, res) => { const user = req.body; });",
37
+ "router.put('/data', (req, res) => { processData(req.body); });",
38
+ "async createUser(req, res) { await userService.create(req.body); }",
39
+ "@Post() create(@Body() data: any) { return this.service.create(data); }"
40
+ ],
41
+ "fixes": [
42
+ "if (!req.is('application/json')) return res.status(415).send('Unsupported Media Type');",
43
+ "if (req.headers['content-type'] !== 'application/json') throw new Error('Invalid Content-Type');",
44
+ "app.use(express.json({ type: 'application/json' }));",
45
+ "@Post() @Header('Content-Type', 'application/json') create(@Body() data: CreateDto) {}"
46
+ ]
47
+ }
48
+ }
@@ -261,4 +261,143 @@ class RuleHelper {
261
261
  }
262
262
  }
263
263
 
264
- module.exports = { RuleHelper };
264
+ /**
265
+ * Comment Detection Utilities
266
+ * Reusable functions for detecting and handling comments in source code
267
+ */
268
+ class CommentDetector {
269
+ /**
270
+ * Check if a line is within a block comment region
271
+ * @param {string[]} lines - Array of lines
272
+ * @param {number} lineIndex - Current line index (0-based)
273
+ * @returns {boolean} True if line is in block comment
274
+ */
275
+ static isLineInBlockComment(lines, lineIndex) {
276
+ let inBlockComment = false;
277
+
278
+ for (let i = 0; i <= lineIndex; i++) {
279
+ const line = lines[i];
280
+
281
+ // Check for block comment start
282
+ if (line.includes('/*')) {
283
+ inBlockComment = true;
284
+ }
285
+
286
+ // Check for block comment end on same line or later lines
287
+ if (line.includes('*/')) {
288
+ inBlockComment = false;
289
+ }
290
+ }
291
+
292
+ return inBlockComment;
293
+ }
294
+
295
+ /**
296
+ * Check if a specific position in a line is inside a comment
297
+ * @param {string} line - Line content
298
+ * @param {number} position - Character position in line
299
+ * @returns {boolean} True if position is inside a comment
300
+ */
301
+ static isPositionInComment(line, position) {
302
+ // Check if position is after // comment
303
+ const singleLineCommentPos = line.indexOf('//');
304
+ if (singleLineCommentPos !== -1 && position > singleLineCommentPos) {
305
+ return true;
306
+ }
307
+
308
+ // Check if position is inside /* */ comment on same line
309
+ let pos = 0;
310
+ while (pos < line.length) {
311
+ const commentStart = line.indexOf('/*', pos);
312
+ const commentEnd = line.indexOf('*/', pos);
313
+
314
+ if (commentStart !== -1 && commentEnd !== -1 && commentStart < commentEnd) {
315
+ if (position >= commentStart && position <= commentEnd + 1) {
316
+ return true;
317
+ }
318
+ pos = commentEnd + 2;
319
+ } else {
320
+ break;
321
+ }
322
+ }
323
+
324
+ return false;
325
+ }
326
+
327
+ /**
328
+ * Clean line by removing comments but preserving structure for regex matching
329
+ * @param {string} line - Original line
330
+ * @returns {object} { cleanLine, commentRanges }
331
+ */
332
+ static cleanLineForMatching(line) {
333
+ let cleanLine = line;
334
+ const commentRanges = [];
335
+
336
+ // Track /* */ comments
337
+ let pos = 0;
338
+ while (pos < cleanLine.length) {
339
+ const commentStart = cleanLine.indexOf('/*', pos);
340
+ const commentEnd = cleanLine.indexOf('*/', pos);
341
+
342
+ if (commentStart !== -1 && commentEnd !== -1 && commentStart < commentEnd) {
343
+ commentRanges.push({ start: commentStart, end: commentEnd + 2 });
344
+ // Replace with spaces to preserve positions
345
+ const spaces = ' '.repeat(commentEnd + 2 - commentStart);
346
+ cleanLine = cleanLine.substring(0, commentStart) + spaces + cleanLine.substring(commentEnd + 2);
347
+ pos = commentEnd + 2;
348
+ } else {
349
+ break;
350
+ }
351
+ }
352
+
353
+ // Track // comments
354
+ const singleCommentPos = cleanLine.indexOf('//');
355
+ if (singleCommentPos !== -1) {
356
+ commentRanges.push({ start: singleCommentPos, end: cleanLine.length });
357
+ cleanLine = cleanLine.substring(0, singleCommentPos);
358
+ }
359
+
360
+ return { cleanLine, commentRanges };
361
+ }
362
+
363
+ /**
364
+ * Filter out comment lines from analysis
365
+ * @param {string[]} lines - Array of lines
366
+ * @returns {Array} Array of {line, lineNumber, isComment} objects
367
+ */
368
+ static filterCommentLines(lines) {
369
+ const result = [];
370
+ let inBlockComment = false;
371
+
372
+ lines.forEach((line, index) => {
373
+ const trimmedLine = line.trim();
374
+
375
+ // Track block comments
376
+ if (trimmedLine.includes('/*')) {
377
+ inBlockComment = true;
378
+ }
379
+ if (trimmedLine.includes('*/')) {
380
+ inBlockComment = false;
381
+ result.push({ line, lineNumber: index + 1, isComment: true });
382
+ return;
383
+ }
384
+ if (inBlockComment) {
385
+ result.push({ line, lineNumber: index + 1, isComment: true });
386
+ return;
387
+ }
388
+
389
+ // Check single line comments
390
+ const isComment = trimmedLine.startsWith('//') || trimmedLine.startsWith('#');
391
+
392
+ result.push({
393
+ line,
394
+ lineNumber: index + 1,
395
+ isComment
396
+ });
397
+ });
398
+
399
+ return result;
400
+ }
401
+ }
402
+
403
+ module.exports = { RuleHelper, CommentDetector };
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Script to consolidate all rules from rules-registry.json into enhanced-rules-registry.json
5
+ * then remove the old file to avoid config conflicts
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ const oldRegistryPath = '/Users/bach.ngoc.hoai/Docs/ee/coding-quality/extensions/sunlint/config/rules/rules-registry.json';
12
+ const enhancedRegistryPath = '/Users/bach.ngoc.hoai/Docs/ee/coding-quality/extensions/sunlint/config/rules/enhanced-rules-registry.json';
13
+
14
+ console.log('🔄 Consolidating rule configurations...');
15
+
16
+ try {
17
+ // Read both files
18
+ const oldRegistry = JSON.parse(fs.readFileSync(oldRegistryPath, 'utf8'));
19
+ const enhancedRegistry = JSON.parse(fs.readFileSync(enhancedRegistryPath, 'utf8'));
20
+
21
+ console.log(`📊 Old registry has ${Object.keys(oldRegistry.rules).length} rules`);
22
+ console.log(`📊 Enhanced registry has ${Object.keys(enhancedRegistry.rules).length} rules`);
23
+
24
+ // Track what was added
25
+ let addedRules = [];
26
+ let skippedRules = [];
27
+
28
+ // Add rules from old registry that don't exist in enhanced registry
29
+ for (const [ruleId, ruleConfig] of Object.entries(oldRegistry.rules)) {
30
+ if (!enhancedRegistry.rules[ruleId]) {
31
+ console.log(`➕ Adding rule ${ruleId}: ${ruleConfig.name}`);
32
+ enhancedRegistry.rules[ruleId] = ruleConfig;
33
+ addedRules.push(ruleId);
34
+ } else {
35
+ console.log(`⏭️ Skipping rule ${ruleId} (already exists in enhanced registry)`);
36
+ skippedRules.push(ruleId);
37
+ }
38
+ }
39
+
40
+ // Merge categories if needed
41
+ if (oldRegistry.categories) {
42
+ for (const [categoryId, categoryConfig] of Object.entries(oldRegistry.categories)) {
43
+ if (!enhancedRegistry.categories) {
44
+ enhancedRegistry.categories = {};
45
+ }
46
+ if (!enhancedRegistry.categories[categoryId]) {
47
+ console.log(`➕ Adding category ${categoryId}: ${categoryConfig.name}`);
48
+ enhancedRegistry.categories[categoryId] = categoryConfig;
49
+ } else {
50
+ // Merge rules from old category
51
+ const existingRules = new Set(enhancedRegistry.categories[categoryId].rules);
52
+ const newRules = categoryConfig.rules.filter(rule => !existingRules.has(rule));
53
+ if (newRules.length > 0) {
54
+ console.log(`🔄 Merging ${newRules.length} rules into category ${categoryId}`);
55
+ enhancedRegistry.categories[categoryId].rules.push(...newRules);
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ // Merge presets if needed
62
+ if (oldRegistry.presets) {
63
+ for (const [presetId, presetConfig] of Object.entries(oldRegistry.presets)) {
64
+ if (!enhancedRegistry.presets) {
65
+ enhancedRegistry.presets = {};
66
+ }
67
+ if (!enhancedRegistry.presets[presetId]) {
68
+ console.log(`➕ Adding preset ${presetId}: ${presetConfig.name}`);
69
+ enhancedRegistry.presets[presetId] = presetConfig;
70
+ }
71
+ }
72
+ }
73
+
74
+ // Merge languages if needed
75
+ if (oldRegistry.languages) {
76
+ for (const [langId, langConfig] of Object.entries(oldRegistry.languages)) {
77
+ if (!enhancedRegistry.languages) {
78
+ enhancedRegistry.languages = {};
79
+ }
80
+ if (!enhancedRegistry.languages[langId]) {
81
+ console.log(`➕ Adding language ${langId}`);
82
+ enhancedRegistry.languages[langId] = langConfig;
83
+ }
84
+ }
85
+ }
86
+
87
+ // Update metadata
88
+ if (enhancedRegistry.metadata) {
89
+ enhancedRegistry.metadata.totalRules = Object.keys(enhancedRegistry.rules).length;
90
+ enhancedRegistry.metadata.lastUpdated = new Date().toISOString().split('T')[0];
91
+ enhancedRegistry.metadata.consolidatedFrom = oldRegistryPath;
92
+ }
93
+
94
+ // Write enhanced registry back
95
+ fs.writeFileSync(enhancedRegistryPath, JSON.stringify(enhancedRegistry, null, 2));
96
+
97
+ console.log('✅ Consolidation completed!');
98
+ console.log(`📊 Total rules now: ${Object.keys(enhancedRegistry.rules).length}`);
99
+ console.log(`➕ Added rules: ${addedRules.length} - ${addedRules.join(', ')}`);
100
+ console.log(`⏭️ Skipped rules: ${skippedRules.length} - ${skippedRules.join(', ')}`);
101
+
102
+ // Create backup of old registry before deletion
103
+ const backupPath = oldRegistryPath + '.backup';
104
+ fs.copyFileSync(oldRegistryPath, backupPath);
105
+ console.log(`💾 Created backup: ${backupPath}`);
106
+
107
+ // Remove old registry
108
+ fs.unlinkSync(oldRegistryPath);
109
+ console.log(`🗑️ Removed old registry: ${oldRegistryPath}`);
110
+
111
+ console.log('🎉 Configuration consolidation complete!');
112
+
113
+ } catch (error) {
114
+ console.error('❌ Error during consolidation:', error);
115
+ process.exit(1);
116
+ }
@@ -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, sunlint, hybrid
138
+ --typescript-engine <type> # Engine: eslint, heuristic, hybrid
139
139
 
140
140
  # Output Control
141
141
  --format <format> # Output: eslint, json, summary, table