@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.
- package/config/rule-analysis-strategies.js +18 -2
- package/engines/eslint-engine.js +9 -11
- package/engines/heuristic-engine.js +55 -31
- package/package.json +2 -1
- package/rules/README.md +252 -0
- package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
- package/rules/common/C002_no_duplicate_code/config.json +23 -0
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
- package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
- package/rules/common/C006_function_naming/analyzer.js +504 -0
- package/rules/common/C006_function_naming/config.json +86 -0
- package/rules/common/C006_function_naming/smart-analyzer.js +503 -0
- package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
- package/rules/common/C012_command_query_separation/analyzer.js +481 -0
- package/rules/common/C012_command_query_separation/ast-analyzer.js +495 -0
- package/rules/common/C013_no_dead_code/analyzer.js +206 -0
- package/rules/common/C014_dependency_injection/analyzer.js +338 -0
- package/rules/common/C017_constructor_logic/analyzer.js +314 -0
- package/rules/common/C019_log_level_usage/analyzer.js +362 -0
- package/rules/common/C019_log_level_usage/config.json +121 -0
- package/rules/common/C029_catch_block_logging/analyzer-backup.js +426 -0
- package/rules/common/C029_catch_block_logging/analyzer-fixed.js +130 -0
- package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +487 -0
- package/rules/common/C029_catch_block_logging/analyzer-simple.js +110 -0
- package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +755 -0
- package/rules/common/C029_catch_block_logging/analyzer.js +129 -0
- package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +441 -0
- package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +127 -0
- package/rules/common/C029_catch_block_logging/ast-analyzer.js +133 -0
- package/rules/common/C029_catch_block_logging/cfg-analyzer.js +408 -0
- package/rules/common/C029_catch_block_logging/config.json +59 -0
- package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +454 -0
- package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +700 -0
- package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +568 -0
- package/rules/common/C029_catch_block_logging/semantic-analyzer.js +459 -0
- package/rules/common/C031_validation_separation/analyzer.js +186 -0
- package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
- package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +296 -0
- package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
- package/rules/common/C043_no_console_or_print/analyzer.js +431 -0
- package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +590 -0
- package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
- package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
- package/rules/docs/C002_no_duplicate_code.md +57 -0
- package/rules/docs/C031_validation_separation.md +72 -0
- package/rules/index.js +155 -0
- package/rules/migration/converter.js +385 -0
- package/rules/migration/mapping.json +164 -0
- package/rules/parser/constants.js +31 -0
- package/rules/parser/file-config.js +80 -0
- package/rules/parser/rule-parser-simple.js +305 -0
- package/rules/parser/rule-parser.js +527 -0
- package/rules/security/S015_insecure_tls_certificate/analyzer.js +150 -0
- package/rules/security/S015_insecure_tls_certificate/ast-analyzer.js +237 -0
- package/rules/security/S023_no_json_injection/analyzer.js +278 -0
- package/rules/security/S023_no_json_injection/ast-analyzer.js +359 -0
- package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
- package/rules/security/S026_json_schema_validation/config.json +27 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +436 -0
- package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
- package/rules/security/S029_csrf_protection/analyzer.js +330 -0
- package/rules/tests/C002_no_duplicate_code.test.js +50 -0
- package/rules/universal/C010/generic.js +0 -0
- package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
- package/rules/utils/ast-utils.js +191 -0
- package/rules/utils/base-analyzer.js +98 -0
- package/rules/utils/pattern-matchers.js +239 -0
- package/rules/utils/rule-helpers.js +264 -0
- package/rules/utils/severity-constants.js +93 -0
- package/scripts/generate_insights.js +188 -0
- package/scripts/merge-reports.js +0 -424
- package/scripts/test-scripts/README.md +0 -22
- package/scripts/test-scripts/test-c041-comparison.js +0 -114
- package/scripts/test-scripts/test-c041-eslint.js +0 -67
- package/scripts/test-scripts/test-eslint-rules.js +0 -146
- package/scripts/test-scripts/test-real-world.js +0 -44
- package/scripts/test-scripts/test-rules-on-real-projects.js +0 -86
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST-based C043 Analyzer
|
|
3
|
+
* Mirrors ESLint's sophisticated implementation for perfect accuracy
|
|
4
|
+
* Leverages SunLint's existing AST infrastructure
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const astRegistry = require('../../../core/ast-modules');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
|
|
10
|
+
class C043NoConsoleOrPrintAnalyzer {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.ruleId = 'C043';
|
|
13
|
+
this.ruleName = 'No Console Or Print';
|
|
14
|
+
this.description = 'Do not use console.log or print in production code';
|
|
15
|
+
this.severity = 'warning';
|
|
16
|
+
this.astRegistry = astRegistry;
|
|
17
|
+
|
|
18
|
+
// Configuration mirroring ESLint's C043 rule
|
|
19
|
+
this.config = {
|
|
20
|
+
allowedMethods: new Set(['error', 'warn']),
|
|
21
|
+
allowInDevelopment: true,
|
|
22
|
+
allowInTests: true,
|
|
23
|
+
|
|
24
|
+
testFilePatterns: [
|
|
25
|
+
'.test.', '.spec.', '__tests__', '/test/', '/tests/',
|
|
26
|
+
'.test.ts', '.test.js', '.spec.ts', '.spec.js',
|
|
27
|
+
'test.tsx', 'spec.tsx',
|
|
28
|
+
'.stories.', '.story.' // Include stories files like ESLint
|
|
29
|
+
],
|
|
30
|
+
|
|
31
|
+
developmentPatterns: [
|
|
32
|
+
'.dev.', '.development.', '.debug.', '/dev/', '/development/'
|
|
33
|
+
],
|
|
34
|
+
|
|
35
|
+
developmentFlags: new Set([
|
|
36
|
+
'__DEV__', 'DEBUG', 'process.env.NODE_ENV', 'process.env.ENVIRONMENT',
|
|
37
|
+
'process.env.ENABLE_LOGGING', 'FEATURES.debug', 'BUILD_TYPE'
|
|
38
|
+
]),
|
|
39
|
+
|
|
40
|
+
consoleMethods: new Set([
|
|
41
|
+
'log', 'info', 'debug', 'trace', 'dir', 'dirxml', 'table',
|
|
42
|
+
'count', 'countReset', 'time', 'timeEnd', 'timeLog',
|
|
43
|
+
'assert', 'clear', 'group', 'groupCollapsed', 'groupEnd',
|
|
44
|
+
'profile', 'profileEnd', 'timeStamp'
|
|
45
|
+
]),
|
|
46
|
+
|
|
47
|
+
forbiddenFunctions: ['print', 'alert', 'confirm', 'prompt']
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async analyze(files, language, config) {
|
|
52
|
+
const violations = [];
|
|
53
|
+
|
|
54
|
+
// Batch processing to avoid memory issues
|
|
55
|
+
const batchSize = 100;
|
|
56
|
+
const totalFiles = files.length;
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < totalFiles; i += batchSize) {
|
|
59
|
+
const batch = files.slice(i, i + batchSize);
|
|
60
|
+
|
|
61
|
+
for (const filePath of batch) {
|
|
62
|
+
try {
|
|
63
|
+
// Skip test files if configured
|
|
64
|
+
if (this.config.allowInTests && this.isTestFile(filePath)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Skip development files if configured
|
|
69
|
+
if (this.config.allowInDevelopment && this.isDevelopmentFile(filePath)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
74
|
+
|
|
75
|
+
// Skip empty files or very large files (>1MB)
|
|
76
|
+
if (!fileContent.trim() || fileContent.length > 1024 * 1024) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const fileLanguage = this.getLanguageFromPath(filePath);
|
|
81
|
+
|
|
82
|
+
// Use regex-based analysis for now (AST can be added later)
|
|
83
|
+
// AST parsing can be slow on large files, so use fast regex approach
|
|
84
|
+
const regexViolations = await this.analyzeFileWithRegex(filePath, fileContent, language, config);
|
|
85
|
+
violations.push(...regexViolations);
|
|
86
|
+
|
|
87
|
+
} catch (error) {
|
|
88
|
+
// Skip problematic files silently to avoid stopping entire analysis
|
|
89
|
+
console.warn(`C043 skipping ${filePath}: ${error.message}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Give Node.js a chance to breathe between batches
|
|
94
|
+
if (i + batchSize < totalFiles) {
|
|
95
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return violations;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getLanguageFromPath(filePath) {
|
|
103
|
+
const ext = filePath.split('.').pop().toLowerCase();
|
|
104
|
+
|
|
105
|
+
const languageMap = {
|
|
106
|
+
'js': 'javascript',
|
|
107
|
+
'jsx': 'javascript',
|
|
108
|
+
'ts': 'typescript',
|
|
109
|
+
'tsx': 'typescript',
|
|
110
|
+
'mjs': 'javascript',
|
|
111
|
+
'cjs': 'javascript'
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return languageMap[ext] || 'javascript';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
analyzeAST(ast, filePath, fileContent) {
|
|
118
|
+
const violations = [];
|
|
119
|
+
const lines = fileContent.split('\n');
|
|
120
|
+
|
|
121
|
+
// Define visitor for AST traversal
|
|
122
|
+
const visitor = {
|
|
123
|
+
CallExpression: (node) => {
|
|
124
|
+
// Check for console method calls
|
|
125
|
+
if (this.isConsoleCall(node)) {
|
|
126
|
+
const methodName = this.getConsoleMethodName(node);
|
|
127
|
+
|
|
128
|
+
// Skip allowed methods (error, warn)
|
|
129
|
+
if (this.config.allowedMethods.has(methodName)) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check if in development context
|
|
134
|
+
if (this.config.allowInDevelopment && this.isInDevelopmentContext(node, ast)) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Create violation
|
|
139
|
+
const location = this.getNodeLocation(node);
|
|
140
|
+
if (location && location.line <= lines.length) {
|
|
141
|
+
violations.push({
|
|
142
|
+
ruleId: this.ruleId,
|
|
143
|
+
severity: this.severity,
|
|
144
|
+
message: `Do not use console.${methodName}() in production code. Use proper logging instead.`,
|
|
145
|
+
filePath: filePath,
|
|
146
|
+
line: location.line,
|
|
147
|
+
column: location.column,
|
|
148
|
+
source: lines[location.line - 1]?.trim() || '',
|
|
149
|
+
suggestion: `Consider using a proper logging library (logger.${methodName}())`
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Check for forbidden functions (print, alert, etc.)
|
|
155
|
+
if (this.isForbiddenFunctionCall(node)) {
|
|
156
|
+
const functionName = this.getFunctionName(node);
|
|
157
|
+
const location = this.getNodeLocation(node);
|
|
158
|
+
|
|
159
|
+
if (location && location.line <= lines.length) {
|
|
160
|
+
violations.push({
|
|
161
|
+
ruleId: this.ruleId,
|
|
162
|
+
severity: this.severity,
|
|
163
|
+
message: `Do not use ${functionName}() in production code. Use proper logging or UI notifications instead.`,
|
|
164
|
+
filePath: filePath,
|
|
165
|
+
line: location.line,
|
|
166
|
+
column: location.column,
|
|
167
|
+
source: lines[location.line - 1]?.trim() || '',
|
|
168
|
+
suggestion: `Consider using a logging library or proper UI notification system`
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// Traverse AST
|
|
176
|
+
this.traverseAST(ast, visitor);
|
|
177
|
+
|
|
178
|
+
return violations;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// AST Helper Methods
|
|
182
|
+
isConsoleCall(node) {
|
|
183
|
+
return node.type === 'CallExpression' &&
|
|
184
|
+
node.callee &&
|
|
185
|
+
node.callee.type === 'MemberExpression' &&
|
|
186
|
+
node.callee.object &&
|
|
187
|
+
node.callee.object.type === 'Identifier' &&
|
|
188
|
+
node.callee.object.name === 'console' &&
|
|
189
|
+
node.callee.property &&
|
|
190
|
+
this.config.consoleMethods.has(node.callee.property.name);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
getConsoleMethodName(node) {
|
|
194
|
+
if (node.callee && node.callee.property) {
|
|
195
|
+
return node.callee.property.name;
|
|
196
|
+
}
|
|
197
|
+
return 'log';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
isForbiddenFunctionCall(node) {
|
|
201
|
+
return node.type === 'CallExpression' &&
|
|
202
|
+
node.callee &&
|
|
203
|
+
node.callee.type === 'Identifier' &&
|
|
204
|
+
this.config.forbiddenFunctions.includes(node.callee.name);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
getFunctionName(node) {
|
|
208
|
+
if (node.callee && node.callee.name) {
|
|
209
|
+
return node.callee.name;
|
|
210
|
+
}
|
|
211
|
+
return 'unknown';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
isInDevelopmentContext(node, ast) {
|
|
215
|
+
// Check if node is within an if statement checking development flags
|
|
216
|
+
let parent = node.parent;
|
|
217
|
+
|
|
218
|
+
while (parent) {
|
|
219
|
+
if (parent.type === 'IfStatement') {
|
|
220
|
+
const test = parent.test;
|
|
221
|
+
if (this.isDevelopmentCondition(test)) {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
parent = parent.parent;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
isDevelopmentCondition(node) {
|
|
232
|
+
if (!node) return false;
|
|
233
|
+
|
|
234
|
+
// Check for various development condition patterns
|
|
235
|
+
if (node.type === 'Identifier' && this.config.developmentFlags.has(node.name)) {
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (node.type === 'MemberExpression') {
|
|
240
|
+
const source = this.nodeToString(node);
|
|
241
|
+
return Array.from(this.config.developmentFlags).some(flag => source.includes(flag));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (node.type === 'BinaryExpression') {
|
|
245
|
+
return this.isDevelopmentCondition(node.left) || this.isDevelopmentCondition(node.right);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
nodeToString(node) {
|
|
252
|
+
// Simple node to string conversion for pattern matching
|
|
253
|
+
if (node.type === 'Identifier') {
|
|
254
|
+
return node.name;
|
|
255
|
+
}
|
|
256
|
+
if (node.type === 'MemberExpression') {
|
|
257
|
+
return `${this.nodeToString(node.object)}.${node.property.name}`;
|
|
258
|
+
}
|
|
259
|
+
if (node.type === 'Literal') {
|
|
260
|
+
return String(node.value);
|
|
261
|
+
}
|
|
262
|
+
return '';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
getNodeLocation(node) {
|
|
266
|
+
if (node.loc) {
|
|
267
|
+
return {
|
|
268
|
+
line: node.loc.start.line,
|
|
269
|
+
column: node.loc.start.column + 1
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
traverseAST(node, visitor) {
|
|
276
|
+
if (!node || typeof node !== 'object') return;
|
|
277
|
+
|
|
278
|
+
// Prevent infinite recursion
|
|
279
|
+
if (node._visited) return;
|
|
280
|
+
node._visited = true;
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
// Visit current node
|
|
284
|
+
if (visitor[node.type]) {
|
|
285
|
+
visitor[node.type](node);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Traverse children with depth limit
|
|
289
|
+
const maxDepth = 100;
|
|
290
|
+
if ((node._depth || 0) > maxDepth) return;
|
|
291
|
+
|
|
292
|
+
for (const key in node) {
|
|
293
|
+
if (key === 'parent' || key === '_visited' || key === '_depth') continue; // Avoid circular references
|
|
294
|
+
|
|
295
|
+
const child = node[key];
|
|
296
|
+
if (Array.isArray(child)) {
|
|
297
|
+
for (const item of child) {
|
|
298
|
+
if (item && typeof item === 'object') {
|
|
299
|
+
item.parent = node; // Set parent reference
|
|
300
|
+
item._depth = (node._depth || 0) + 1;
|
|
301
|
+
this.traverseAST(item, visitor);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
} else if (child && typeof child === 'object') {
|
|
305
|
+
child.parent = node; // Set parent reference
|
|
306
|
+
child._depth = (node._depth || 0) + 1;
|
|
307
|
+
this.traverseAST(child, visitor);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
} finally {
|
|
311
|
+
// Clean up to prevent memory leaks
|
|
312
|
+
delete node._visited;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// File Classification Methods
|
|
317
|
+
isTestFile(filePath) {
|
|
318
|
+
return this.config.testFilePatterns.some(pattern =>
|
|
319
|
+
filePath.includes(pattern)
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
isDevelopmentFile(filePath) {
|
|
324
|
+
return this.config.developmentPatterns.some(pattern =>
|
|
325
|
+
filePath.includes(pattern)
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Regex Fallback Methods (for compatibility)
|
|
330
|
+
async analyzeWithRegexFallback(files, language, config) {
|
|
331
|
+
const violations = [];
|
|
332
|
+
|
|
333
|
+
for (const filePath of files) {
|
|
334
|
+
try {
|
|
335
|
+
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
336
|
+
const fileViolations = await this.analyzeFileWithRegex(filePath, fileContent, language, config);
|
|
337
|
+
violations.push(...fileViolations);
|
|
338
|
+
} catch (error) {
|
|
339
|
+
console.warn(`C043 regex fallback analysis error for ${filePath}:`, error.message);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return violations;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async analyzeFileWithRegex(filePath, fileContent, language, config) {
|
|
347
|
+
const violations = [];
|
|
348
|
+
|
|
349
|
+
// Skip test files and development files
|
|
350
|
+
if (this.isTestFile(filePath) || this.isDevelopmentFile(filePath)) {
|
|
351
|
+
return violations;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const lines = fileContent.split('\n');
|
|
355
|
+
|
|
356
|
+
// Simple regex patterns for fallback
|
|
357
|
+
const consolePattern = /\bconsole\.(log|debug|info|trace|dir|table|count|time|clear|group|assert|profile)\s*\(/g;
|
|
358
|
+
const forbiddenPattern = /\b(print|alert|confirm|prompt)\s*\(/g;
|
|
359
|
+
|
|
360
|
+
for (let i = 0; i < lines.length; i++) {
|
|
361
|
+
const line = lines[i];
|
|
362
|
+
const lineNumber = i + 1;
|
|
363
|
+
|
|
364
|
+
// Skip comments and strings (basic check)
|
|
365
|
+
if (this.isLineCommentOrString(line)) {
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Check console calls
|
|
370
|
+
consolePattern.lastIndex = 0; // Reset regex state
|
|
371
|
+
let match;
|
|
372
|
+
while ((match = consolePattern.exec(line)) !== null) {
|
|
373
|
+
const method = match[1];
|
|
374
|
+
if (!this.config.allowedMethods.has(method)) {
|
|
375
|
+
violations.push({
|
|
376
|
+
ruleId: this.ruleId,
|
|
377
|
+
severity: this.severity,
|
|
378
|
+
message: `Do not use console.${method}() in production code. Use proper logging instead.`,
|
|
379
|
+
filePath: filePath,
|
|
380
|
+
line: lineNumber,
|
|
381
|
+
column: match.index + 1,
|
|
382
|
+
source: line.trim(),
|
|
383
|
+
suggestion: `Consider using a proper logging library (logger.${method}())`
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
// Prevent infinite loop - limit to first match per line
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Check forbidden functions
|
|
391
|
+
forbiddenPattern.lastIndex = 0; // Reset regex state
|
|
392
|
+
while ((match = forbiddenPattern.exec(line)) !== null) {
|
|
393
|
+
const functionName = match[1];
|
|
394
|
+
violations.push({
|
|
395
|
+
ruleId: this.ruleId,
|
|
396
|
+
severity: this.severity,
|
|
397
|
+
message: `Do not use ${functionName}() in production code. Use proper logging or UI notifications instead.`,
|
|
398
|
+
filePath: filePath,
|
|
399
|
+
line: lineNumber,
|
|
400
|
+
column: match.index + 1,
|
|
401
|
+
source: line.trim(),
|
|
402
|
+
suggestion: `Consider using a logging library or proper UI notification system`
|
|
403
|
+
});
|
|
404
|
+
// Prevent infinite loop - limit to first match per line
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return violations;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
isLineCommentOrString(line) {
|
|
413
|
+
const trimmed = line.trim();
|
|
414
|
+
|
|
415
|
+
// Single line comment
|
|
416
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*')) {
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Simple string check - if more quotes before console than after, likely in string
|
|
421
|
+
const beforeConsole = line.split(/console\.|print\(|alert\(/)[0];
|
|
422
|
+
if (beforeConsole) {
|
|
423
|
+
const quotes = (beforeConsole.match(/['"]/g) || []).length;
|
|
424
|
+
return quotes % 2 === 1;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
module.exports = C043NoConsoleOrPrintAnalyzer;
|