@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,237 @@
1
+ /**
2
+ * AST-based analyzer for S015 - Insecure TLS Certificate Detection
3
+ * Detects usage of insecure TLS certificate configurations like rejectUnauthorized: false
4
+ */
5
+
6
+ const babel = require('@babel/parser');
7
+ const traverse = require('@babel/traverse').default;
8
+
9
+ class S015ASTAnalyzer {
10
+ constructor() {
11
+ this.ruleId = 'S015';
12
+ this.ruleName = 'Insecure TLS Certificate';
13
+ this.description = 'Prevent usage of insecure TLS certificate configurations';
14
+ }
15
+
16
+ async analyze(files, language, options = {}) {
17
+ const violations = [];
18
+
19
+ for (const filePath of files) {
20
+ try {
21
+ const content = require('fs').readFileSync(filePath, 'utf8');
22
+ const fileViolations = await this.analyzeFile(content, filePath, options);
23
+ violations.push(...fileViolations);
24
+ } catch (error) {
25
+ if (options.verbose) {
26
+ console.warn(`⚠️ S015 AST analysis failed for ${filePath}: ${error.message}`);
27
+ }
28
+ }
29
+ }
30
+
31
+ return violations;
32
+ }
33
+
34
+ async analyzeFile(content, filePath, options = {}) {
35
+ const violations = [];
36
+
37
+ try {
38
+ // Parse with TypeScript/JavaScript support
39
+ const ast = babel.parse(content, {
40
+ sourceType: 'module',
41
+ allowImportExportEverywhere: true,
42
+ allowReturnOutsideFunction: true,
43
+ plugins: [
44
+ 'typescript',
45
+ 'jsx',
46
+ 'objectRestSpread',
47
+ 'functionBind',
48
+ 'exportDefaultFrom',
49
+ 'decorators-legacy',
50
+ 'classProperties',
51
+ 'asyncGenerators',
52
+ 'dynamicImport'
53
+ ]
54
+ });
55
+
56
+ // Traverse AST to find insecure TLS configurations
57
+ traverse(ast, {
58
+ Property: (path) => {
59
+ this.checkTLSProperty(path, violations, filePath);
60
+ },
61
+ ObjectExpression: (path) => {
62
+ this.checkHTTPSOptions(path, violations, filePath);
63
+ }
64
+ });
65
+
66
+ } catch (parseError) {
67
+ if (options.verbose) {
68
+ console.warn(`⚠️ S015 parse failed for ${filePath}: ${parseError.message}`);
69
+ }
70
+ // Fall back to regex analysis if AST parsing fails
71
+ return this.analyzeWithRegex(content, filePath, options);
72
+ }
73
+
74
+ return violations;
75
+ }
76
+
77
+ checkTLSProperty(path, violations, filePath) {
78
+ const node = path.node;
79
+
80
+ // Check for rejectUnauthorized: false
81
+ if (this.isPropertyKey(node.key, 'rejectUnauthorized')) {
82
+ if (this.isFalseLiteral(node.value)) {
83
+ violations.push({
84
+ ruleId: this.ruleId,
85
+ severity: 'error',
86
+ message: 'Untrusted/self-signed/expired certificate accepted. Only use trusted certificates in production.',
87
+ line: node.loc ? node.loc.start.line : 1,
88
+ column: node.loc ? node.loc.start.column + 1 : 1,
89
+ filePath: filePath,
90
+ type: 'insecure_tls_config'
91
+ });
92
+ }
93
+ }
94
+
95
+ // Check for other insecure TLS options
96
+ const insecureOptions = [
97
+ 'checkServerIdentity',
98
+ 'secureProtocol',
99
+ 'ciphers',
100
+ 'secureOptions'
101
+ ];
102
+
103
+ if (insecureOptions.some(opt => this.isPropertyKey(node.key, opt))) {
104
+ if (this.hasInsecureValue(node.value, node.key.name || node.key.value)) {
105
+ violations.push({
106
+ ruleId: this.ruleId,
107
+ severity: 'warning',
108
+ message: `Potentially insecure TLS option '${node.key.name || node.key.value}'. Review configuration for security.`,
109
+ line: node.loc ? node.loc.start.line : 1,
110
+ column: node.loc ? node.loc.start.column + 1 : 1,
111
+ filePath: filePath,
112
+ type: 'potentially_insecure_tls'
113
+ });
114
+ }
115
+ }
116
+ }
117
+
118
+ checkHTTPSOptions(path, violations, filePath) {
119
+ const node = path.node;
120
+
121
+ // Look for HTTPS server configurations
122
+ const parent = path.parent;
123
+ if (parent && parent.type === 'CallExpression') {
124
+ const callee = parent.callee;
125
+
126
+ // Check for https.createServer(options, ...)
127
+ if (this.isHTTPSCreateServer(callee)) {
128
+ // Check each property in the options object
129
+ node.properties.forEach(prop => {
130
+ if (prop.type === 'ObjectProperty') {
131
+ if (this.isPropertyKey(prop.key, 'rejectUnauthorized') &&
132
+ this.isFalseLiteral(prop.value)) {
133
+ violations.push({
134
+ ruleId: this.ruleId,
135
+ severity: 'error',
136
+ message: 'HTTPS server configured with rejectUnauthorized: false. This disables certificate validation.',
137
+ line: prop.loc ? prop.loc.start.line : 1,
138
+ column: prop.loc ? prop.loc.start.column + 1 : 1,
139
+ filePath: filePath,
140
+ type: 'https_server_insecure'
141
+ });
142
+ }
143
+ }
144
+ });
145
+ }
146
+ }
147
+ }
148
+
149
+ isPropertyKey(key, expectedName) {
150
+ return (key.type === 'Identifier' && key.name === expectedName) ||
151
+ (key.type === 'StringLiteral' && key.value === expectedName) ||
152
+ (key.type === 'Literal' && key.value === expectedName);
153
+ }
154
+
155
+ isFalseLiteral(value) {
156
+ return (value.type === 'BooleanLiteral' && value.value === false) ||
157
+ (value.type === 'Literal' && value.value === false);
158
+ }
159
+
160
+ isHTTPSCreateServer(callee) {
161
+ return (callee.type === 'MemberExpression' &&
162
+ callee.object.name === 'https' &&
163
+ callee.property.name === 'createServer') ||
164
+ (callee.type === 'Identifier' && callee.name === 'createServer');
165
+ }
166
+
167
+ hasInsecureValue(value, propertyName) {
168
+ // Check for potentially insecure values based on property type
169
+ if (propertyName === 'checkServerIdentity' && this.isFalseLiteral(value)) {
170
+ return true;
171
+ }
172
+
173
+ if (propertyName === 'secureProtocol' && value.type === 'StringLiteral') {
174
+ const insecureProtocols = ['SSLv2', 'SSLv3', 'TLSv1', 'TLSv1_method'];
175
+ return insecureProtocols.some(protocol =>
176
+ value.value.toLowerCase().includes(protocol.toLowerCase())
177
+ );
178
+ }
179
+
180
+ if (propertyName === 'ciphers' && value.type === 'StringLiteral') {
181
+ const weakCiphers = ['NULL', 'RC4', 'DES', 'MD5'];
182
+ return weakCiphers.some(cipher =>
183
+ value.value.toUpperCase().includes(cipher)
184
+ );
185
+ }
186
+
187
+ return false;
188
+ }
189
+
190
+ // Fallback regex analysis
191
+ analyzeWithRegex(content, filePath, options = {}) {
192
+ const violations = [];
193
+ const lines = content.split('\n');
194
+
195
+ lines.forEach((line, index) => {
196
+ const lineNumber = index + 1;
197
+
198
+ // Check for rejectUnauthorized: false
199
+ if (/rejectUnauthorized\s*:\s*false/i.test(line)) {
200
+ violations.push({
201
+ ruleId: this.ruleId,
202
+ severity: 'error',
203
+ message: 'Untrusted/self-signed/expired certificate accepted. Only use trusted certificates in production.',
204
+ line: lineNumber,
205
+ column: line.indexOf('rejectUnauthorized') + 1,
206
+ filePath: filePath,
207
+ type: 'insecure_tls_config_regex'
208
+ });
209
+ }
210
+
211
+ // Check for other insecure patterns
212
+ const insecurePatterns = [
213
+ { pattern: /checkServerIdentity\s*:\s*false/i, message: 'Server identity check disabled' },
214
+ { pattern: /secureProtocol\s*:\s*['"]SSL/i, message: 'Insecure SSL protocol used' },
215
+ { pattern: /secureProtocol\s*:\s*['"]TLSv1['"]/i, message: 'Insecure TLS v1.0 protocol used' }
216
+ ];
217
+
218
+ insecurePatterns.forEach(({ pattern, message }) => {
219
+ if (pattern.test(line)) {
220
+ violations.push({
221
+ ruleId: this.ruleId,
222
+ severity: 'warning',
223
+ message: `${message}. Use secure TLS configuration.`,
224
+ line: lineNumber,
225
+ column: line.search(pattern) + 1,
226
+ filePath: filePath,
227
+ type: 'insecure_tls_pattern_regex'
228
+ });
229
+ }
230
+ });
231
+ });
232
+
233
+ return violations;
234
+ }
235
+ }
236
+
237
+ module.exports = S015ASTAnalyzer;
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Heuristic analyzer for: S023 – No JSON Injection
3
+ * Purpose: Prevent JSON injection attacks and unsafe JSON handling
4
+ * Detects: unsafe JSON.parse(), eval() with JSON, JSON.stringify in HTML context
5
+ */
6
+
7
+ class S023Analyzer {
8
+ constructor() {
9
+ this.ruleId = 'S023';
10
+ this.ruleName = 'No JSON Injection';
11
+ this.description = 'Prevent JSON injection attacks and unsafe JSON handling';
12
+
13
+ // Patterns that indicate user input sources
14
+ this.userInputPatterns = [
15
+ /localStorage\.getItem/,
16
+ /sessionStorage\.getItem/,
17
+ /window\.location/,
18
+ /location\.(search|hash|href)/,
19
+ /URLSearchParams/,
20
+ /req\.(body|query|params)/,
21
+ /request\.(body|query|params)/,
22
+ /document\.cookie/,
23
+ /window\.name/,
24
+ /postMessage/,
25
+ /fetch\(/,
26
+ /axios\./,
27
+ /xhr\./
28
+ ];
29
+
30
+ // Patterns that indicate validation/safety measures
31
+ this.validationPatterns = [
32
+ /try\s*\{/,
33
+ /catch\s*\(/,
34
+ /if\s*\(/,
35
+ /typeof\s+/,
36
+ /instanceof\s+/,
37
+ /\.length\s*[><=]/,
38
+ /validate/i,
39
+ /check/i,
40
+ /isValid/i,
41
+ /sanitize/i,
42
+ /escape/i,
43
+ /filter/i
44
+ ];
45
+
46
+ // HTML context patterns
47
+ this.htmlContextPatterns = [
48
+ /innerHTML/,
49
+ /outerHTML/,
50
+ /insertAdjacentHTML/,
51
+ /document\.write/,
52
+ /\.html\(/,
53
+ /<script/i,
54
+ /<\/script>/i
55
+ ];
56
+
57
+ // JSON patterns for eval detection
58
+ this.jsonPatterns = [
59
+ /json/i,
60
+ /\{.*\}/,
61
+ /\[.*\]/,
62
+ /parse/i,
63
+ /stringify/i
64
+ ];
65
+ }
66
+
67
+ async analyze(files, language, options = {}) {
68
+ const violations = [];
69
+
70
+ for (const filePath of files) {
71
+ try {
72
+ const fileViolations = await this.analyzeFile(filePath, language, options);
73
+ violations.push(...fileViolations);
74
+ } catch (error) {
75
+ if (options.verbose) {
76
+ console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
77
+ }
78
+ }
79
+ }
80
+
81
+ return violations;
82
+ }
83
+
84
+ async analyzeFile(filePath, language, options = {}) {
85
+ switch (language) {
86
+ case 'typescript':
87
+ case 'javascript':
88
+ return this.analyzeJavaScript(filePath, options);
89
+ default:
90
+ return [];
91
+ }
92
+ }
93
+
94
+ async analyzeJavaScript(filePath, options = {}) {
95
+ try {
96
+ // Try AST analysis first (preferred method)
97
+ const astAnalyzer = require('./ast-analyzer.js');
98
+ const astViolations = await astAnalyzer.analyze([filePath], 'javascript', options);
99
+ if (astViolations.length > 0) {
100
+ return astViolations;
101
+ }
102
+ } catch (astError) {
103
+ if (options.verbose) {
104
+ console.log(`⚠️ AST analysis failed for ${filePath}, falling back to regex`);
105
+ }
106
+ }
107
+
108
+ // Fallback to regex analysis
109
+ return this.analyzeWithRegex(filePath, options);
110
+ }
111
+
112
+ async analyzeWithRegex(filePath, options = {}) {
113
+ const fs = require('fs');
114
+ const path = require('path');
115
+
116
+ try {
117
+ const content = fs.readFileSync(filePath, 'utf8');
118
+ const violations = [];
119
+ const lines = content.split('\n');
120
+
121
+ // 1. Check JSON.parse() calls
122
+ const jsonParseViolations = this.checkJsonParseCalls(content, lines, filePath);
123
+ violations.push(...jsonParseViolations);
124
+
125
+ // 2. Check eval() with JSON patterns
126
+ const evalViolations = this.checkEvalWithJson(content, lines, filePath);
127
+ violations.push(...evalViolations);
128
+
129
+ // 3. Check JSON.stringify in HTML context
130
+ const htmlViolations = this.checkJsonStringifyInHtml(content, lines, filePath);
131
+ violations.push(...htmlViolations);
132
+
133
+ return violations;
134
+ } catch (error) {
135
+ if (options.verbose) {
136
+ console.warn(`⚠️ Failed to read file ${filePath}: ${error.message}`);
137
+ }
138
+ return [];
139
+ }
140
+ }
141
+
142
+ checkJsonParseCalls(content, lines, filePath) {
143
+ const violations = [];
144
+ const jsonParsePattern = /JSON\.parse\s*\(\s*([^)]+)\)/g;
145
+ let match;
146
+
147
+ while ((match = jsonParsePattern.exec(content)) !== null) {
148
+ const lineNumber = content.substring(0, match.index).split('\n').length;
149
+ const lineText = lines[lineNumber - 1] || '';
150
+ const argument = match[1].trim();
151
+
152
+ // Check if argument is from user input
153
+ if (this.isUserInputArgument(argument)) {
154
+ // Check if there's validation around this call
155
+ if (!this.hasValidationContext(content, match.index, lineNumber, lines)) {
156
+ violations.push({
157
+ ruleId: this.ruleId,
158
+ file: filePath,
159
+ line: lineNumber,
160
+ column: match.index - content.lastIndexOf('\n', match.index),
161
+ message: 'Unsafe JSON parsing - validate input before parsing',
162
+ severity: 'warning',
163
+ code: lineText.trim(),
164
+ type: 'unsafe_json_parse',
165
+ confidence: 0.8,
166
+ suggestion: 'Validate input before parsing JSON or use try-catch block'
167
+ });
168
+ }
169
+ }
170
+ }
171
+
172
+ return violations;
173
+ }
174
+
175
+ checkEvalWithJson(content, lines, filePath) {
176
+ const violations = [];
177
+ const evalPattern = /eval\s*\(\s*([^)]+)\)/g;
178
+ let match;
179
+
180
+ while ((match = evalPattern.exec(content)) !== null) {
181
+ const lineNumber = content.substring(0, match.index).split('\n').length;
182
+ const lineText = lines[lineNumber - 1] || '';
183
+ const argument = match[1].trim();
184
+
185
+ // Check if eval contains JSON patterns
186
+ if (this.containsJsonPattern(argument)) {
187
+ violations.push({
188
+ ruleId: this.ruleId,
189
+ file: filePath,
190
+ line: lineNumber,
191
+ column: match.index - content.lastIndexOf('\n', match.index),
192
+ message: 'Never use eval() to process JSON data - use JSON.parse() instead',
193
+ severity: 'error',
194
+ code: lineText.trim(),
195
+ type: 'eval_json',
196
+ confidence: 0.9,
197
+ suggestion: 'Use JSON.parse() instead of eval() for parsing JSON'
198
+ });
199
+ }
200
+ }
201
+
202
+ return violations;
203
+ }
204
+
205
+ checkJsonStringifyInHtml(content, lines, filePath) {
206
+ const violations = [];
207
+ const jsonStringifyPattern = /JSON\.stringify\s*\([^)]+\)/g;
208
+ let match;
209
+
210
+ while ((match = jsonStringifyPattern.exec(content)) !== null) {
211
+ const lineNumber = content.substring(0, match.index).split('\n').length;
212
+ const lineText = lines[lineNumber - 1] || '';
213
+
214
+ // Check if JSON.stringify is used in HTML context
215
+ if (this.isInHtmlContext(content, match.index)) {
216
+ violations.push({
217
+ ruleId: this.ruleId,
218
+ file: filePath,
219
+ line: lineNumber,
220
+ column: match.index - content.lastIndexOf('\n', match.index),
221
+ message: 'JSON.stringify output should be escaped when used in HTML context',
222
+ severity: 'warning',
223
+ code: lineText.trim(),
224
+ type: 'json_stringify_html',
225
+ confidence: 0.7,
226
+ suggestion: 'Escape JSON.stringify output when inserting into HTML'
227
+ });
228
+ }
229
+ }
230
+
231
+ return violations;
232
+ }
233
+
234
+ isUserInputArgument(argument) {
235
+ return this.userInputPatterns.some(pattern => pattern.test(argument));
236
+ }
237
+
238
+ hasValidationContext(content, matchIndex, lineNumber, lines) {
239
+ // Check surrounding lines for validation patterns
240
+ const startLine = Math.max(0, lineNumber - 3);
241
+ const endLine = Math.min(lines.length, lineNumber + 2);
242
+
243
+ for (let i = startLine; i < endLine; i++) {
244
+ const line = lines[i] || '';
245
+ if (this.validationPatterns.some(pattern => pattern.test(line))) {
246
+ return true;
247
+ }
248
+ }
249
+
250
+ // Check if the call is inside a try block
251
+ const beforeText = content.substring(Math.max(0, matchIndex - 200), matchIndex);
252
+ const afterText = content.substring(matchIndex, Math.min(content.length, matchIndex + 100));
253
+
254
+ return /try\s*\{[^}]*$/.test(beforeText) || /catch\s*\(/.test(afterText);
255
+ }
256
+
257
+ containsJsonPattern(text) {
258
+ return this.jsonPatterns.some(pattern => pattern.test(text));
259
+ }
260
+
261
+ isInHtmlContext(content, matchIndex) {
262
+ // Check surrounding context for HTML patterns
263
+ const contextStart = Math.max(0, matchIndex - 100);
264
+ const contextEnd = Math.min(content.length, matchIndex + 100);
265
+ const context = content.substring(contextStart, contextEnd);
266
+
267
+ return this.htmlContextPatterns.some(pattern => pattern.test(context));
268
+ }
269
+
270
+ // Utility method for file extension checking
271
+ isSupportedFile(filePath) {
272
+ const supportedExtensions = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
273
+ const path = require('path');
274
+ return supportedExtensions.includes(path.extname(filePath));
275
+ }
276
+ }
277
+
278
+ module.exports = new S023Analyzer();