@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,338 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ class C014Analyzer {
5
+ constructor() {
6
+ this.ruleId = 'C014';
7
+ this.ruleName = 'Dependency Injection Pattern';
8
+ this.description = 'Dùng Dependency Injection thay vì new trực tiếp trong logic';
9
+ }
10
+
11
+ async analyze(files, language, options = {}) {
12
+ const violations = [];
13
+
14
+ for (const filePath of files) {
15
+ if (options.verbose) {
16
+ console.log(`🔍 Running C014 analysis on ${path.basename(filePath)}`);
17
+ }
18
+
19
+ try {
20
+ const content = fs.readFileSync(filePath, 'utf8');
21
+ const fileViolations = await this.analyzeFile(filePath, content, language, options);
22
+ violations.push(...fileViolations);
23
+ } catch (error) {
24
+ console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
25
+ }
26
+ }
27
+
28
+ return violations;
29
+ }
30
+
31
+ async analyzeFile(filePath, content, language, config) {
32
+ switch (language) {
33
+ case 'typescript':
34
+ case 'javascript':
35
+ return this.analyzeTypeScript(filePath, content, config);
36
+ default:
37
+ return [];
38
+ }
39
+ }
40
+
41
+ async analyzeTypeScript(filePath, content, config) {
42
+ const violations = [];
43
+ const lines = content.split('\n');
44
+
45
+ lines.forEach((line, index) => {
46
+ const lineNumber = index + 1;
47
+ const trimmedLine = line.trim();
48
+
49
+ // Skip comments and imports
50
+ if (this.isCommentOrImport(trimmedLine)) {
51
+ return;
52
+ }
53
+
54
+ // Find new expressions
55
+ const newExpressions = this.findNewExpressions(trimmedLine, line);
56
+
57
+ newExpressions.forEach(expr => {
58
+ if (this.shouldFlag(expr.className, line, filePath)) {
59
+ violations.push({
60
+ ruleId: this.ruleId,
61
+ file: filePath,
62
+ line: lineNumber,
63
+ column: expr.column,
64
+ message: `Avoid direct instantiation of '${expr.className}'. Use dependency injection or factory pattern instead`,
65
+ severity: 'warning',
66
+ code: trimmedLine,
67
+ type: 'direct_instantiation',
68
+ confidence: expr.confidence,
69
+ suggestion: `Consider injecting ${expr.className} as a dependency or using a factory`
70
+ });
71
+ }
72
+ });
73
+ });
74
+
75
+ return violations;
76
+ }
77
+
78
+ isCommentOrImport(line) {
79
+ const trimmed = line.trim();
80
+ return trimmed.startsWith('//') ||
81
+ trimmed.startsWith('/*') ||
82
+ trimmed.startsWith('*') ||
83
+ trimmed.startsWith('import ') ||
84
+ trimmed.startsWith('export ');
85
+ }
86
+
87
+ findNewExpressions(line, originalLine) {
88
+ const expressions = [];
89
+ const regex = /\bnew\s+([A-Z][a-zA-Z0-9_]*)\s*\(/g;
90
+
91
+ let match;
92
+ while ((match = regex.exec(line)) !== null) {
93
+ expressions.push({
94
+ className: match[1],
95
+ column: match.index + 1,
96
+ confidence: this.calculateConfidence(match[1], line)
97
+ });
98
+ }
99
+
100
+ return expressions;
101
+ }
102
+
103
+ shouldFlag(className, line, filePath) {
104
+ // Don't flag built-in JavaScript/TypeScript classes and common patterns
105
+ const allowedClasses = [
106
+ // Built-in JavaScript classes
107
+ 'Date', 'Array', 'Object', 'String', 'Number', 'Boolean', 'RegExp',
108
+ 'Map', 'Set', 'WeakMap', 'WeakSet', 'Promise', 'Error', 'TypeError',
109
+ 'RangeError', 'SyntaxError', 'ReferenceError', 'EvalError', 'URIError',
110
+ 'Function', 'Symbol', 'BigInt', 'ArrayBuffer', 'DataView',
111
+ 'Int8Array', 'Uint8Array', 'Uint8ClampedArray', 'Int16Array', 'Uint16Array',
112
+ 'Int32Array', 'Uint32Array', 'Float32Array', 'Float64Array',
113
+
114
+ // DOM classes (Frontend)
115
+ 'FormData', 'Headers', 'Request', 'Response', 'URLSearchParams',
116
+ 'URL', 'Blob', 'File', 'FileReader', 'Image', 'Audio', 'Video',
117
+ 'XMLHttpRequest', 'WebSocket', 'Worker', 'MessageChannel',
118
+ 'MutationObserver', 'ResizeObserver', 'IntersectionObserver',
119
+ 'AbortController', 'CustomEvent', 'Event', 'ErrorEvent',
120
+
121
+ // Node.js classes
122
+ 'Buffer', 'URL', 'URLSearchParams', 'ReadableStream', 'WritableStream',
123
+
124
+ // React/Next.js common patterns
125
+ 'NextResponse', 'Response', 'Headers', 'FormData',
126
+
127
+ // Common library classes that are okay to instantiate directly
128
+ 'Axios', 'HttpClient', 'Logger', 'Configuration', 'Config', 'AppConfig', 'UserSettings',
129
+ 'AppConfiguration', 'SystemLogger', 'Cache', 'Monitor', 'Connection', 'DatabaseConfig'
130
+ ];
131
+
132
+ if (allowedClasses.includes(className)) {
133
+ return false;
134
+ }
135
+
136
+ // Don't flag if it's clearly for testing
137
+ if (this.isTestContext(line, filePath)) {
138
+ return false;
139
+ }
140
+
141
+ // Don't flag if it's in React hooks or state management
142
+ if (this.isReactHookContext(line)) {
143
+ return false;
144
+ }
145
+
146
+ // Don't flag if it's in a factory pattern or builder pattern
147
+ if (this.isFactoryOrBuilderPattern(line)) {
148
+ return false;
149
+ }
150
+
151
+ // Don't flag if it's a utility class or static helper
152
+ if (this.isUtilityClass(className, line)) {
153
+ return false;
154
+ }
155
+
156
+ // Don't flag if it's in constructor or initialization context
157
+ if (this.isConstructorOrInitContext(line)) {
158
+ return false;
159
+ }
160
+
161
+ // Don't flag if it's immediate usage pattern (e.g., new Date().getTime())
162
+ if (this.isImmediateUsagePattern(line)) {
163
+ return false;
164
+ }
165
+
166
+ return true;
167
+ }
168
+
169
+ isTestContext(line, filePath) {
170
+ // Check if file is a test file
171
+ const testPatterns = ['.test.', '.spec.', '__tests__', '/tests/'];
172
+ const isTestFile = testPatterns.some(pattern => filePath.includes(pattern));
173
+
174
+ if (isTestFile) {
175
+ return true;
176
+ }
177
+
178
+ // Check if line contains test-related keywords - use word boundaries to avoid false matches
179
+ const testKeywords = ['test', 'describe', 'expect', 'mock', 'spy', 'stub'];
180
+ const lowerLine = line.toLowerCase();
181
+
182
+ // Use word boundaries to avoid false positives like "Repository" containing "it"
183
+ const hasTestKeywords = testKeywords.some(keyword => {
184
+ const wordBoundaryRegex = new RegExp(`\\b${keyword}\\b`);
185
+ return wordBoundaryRegex.test(lowerLine);
186
+ });
187
+
188
+ // Special case for "it" - only match as standalone word or in specific contexts
189
+ const hasItKeyword = /\bit\s*\(/.test(lowerLine); // it( function calls
190
+
191
+ return hasTestKeywords || hasItKeyword;
192
+ }
193
+
194
+ isFactoryOrBuilderPattern(line) {
195
+ const lowerLine = line.toLowerCase();
196
+ const patterns = [
197
+ 'factory',
198
+ 'builder',
199
+ 'create',
200
+ 'build',
201
+ 'make',
202
+ 'construct',
203
+ 'getinstance',
204
+ 'newinstance',
205
+ '=> new',
206
+ 'return new'
207
+ ];
208
+
209
+ return patterns.some(pattern => lowerLine.includes(pattern));
210
+ }
211
+
212
+ isUtilityClass(className, line) {
213
+ // Common utility class patterns based on name - but exclude business logic handlers
214
+ const utilityPatterns = [
215
+ /Helper$/,
216
+ /Util$/,
217
+ /Utils$/,
218
+ /Manager$/,
219
+ /Processor$/,
220
+ /Validator$/,
221
+ /Parser$/,
222
+ /Formatter$/,
223
+ /Converter$/
224
+ ];
225
+
226
+ // Business logic handlers should not be considered utilities
227
+ const businessLogicExceptions = [
228
+ 'EventHandler',
229
+ 'CommandHandler',
230
+ 'RequestHandler',
231
+ 'ActionHandler',
232
+ 'MessageHandler',
233
+ 'HttpHandler',
234
+ 'ApiHandler'
235
+ ];
236
+
237
+ if (businessLogicExceptions.includes(className)) {
238
+ return false; // These should be flagged as DI violations
239
+ }
240
+
241
+ const isUtilityName = utilityPatterns.some(pattern => pattern.test(className));
242
+
243
+ if (isUtilityName) {
244
+ return true;
245
+ }
246
+
247
+ // Check if it's used as return value (factory pattern)
248
+ const factoryContext = [
249
+ 'return new',
250
+ 'export new',
251
+ 'export default new'
252
+ ];
253
+
254
+ return factoryContext.some(context => line.includes(context));
255
+ }
256
+
257
+ isConstructorOrInitContext(line) {
258
+ const lowerLine = line.toLowerCase();
259
+ const patterns = [
260
+ 'constructor',
261
+ 'init',
262
+ 'setup',
263
+ 'configure',
264
+ 'this.',
265
+ 'super(',
266
+ 'initialize'
267
+ ];
268
+
269
+ return patterns.some(pattern => lowerLine.includes(pattern));
270
+ }
271
+
272
+ isReactHookContext(line) {
273
+ // Check for React hooks patterns
274
+ const reactHookPatterns = [
275
+ 'useState(',
276
+ 'useEffect(',
277
+ 'useMemo(',
278
+ 'useCallback(',
279
+ 'useRef(',
280
+ 'useContext(',
281
+ 'useReducer(',
282
+ 'useImperativeHandle(',
283
+ 'useLayoutEffect(',
284
+ 'useDebugValue(',
285
+ // Custom hooks
286
+ 'use[A-Z]'
287
+ ];
288
+
289
+ return reactHookPatterns.some(pattern => {
290
+ if (pattern.includes('[A-Z]')) {
291
+ return new RegExp(pattern).test(line);
292
+ }
293
+ return line.includes(pattern);
294
+ });
295
+ }
296
+
297
+ isImmediateUsagePattern(line) {
298
+ // Check for immediate usage patterns like new Date().getTime()
299
+ const immediatePatterns = [
300
+ /new\s+\w+\(\)\./, // new Date().getTime(), new Error().stack
301
+ /new\s+\w+\([^)]*\)\./, // new Date(value).getTime()
302
+ /window\.dispatchEvent\(new\s+/, // window.dispatchEvent(new Event())
303
+ /document\.dispatchEvent\(new\s+/, // document.dispatchEvent(new Event())
304
+ ];
305
+
306
+ // Special case: standalone built-in class instantiation (not assignment)
307
+ const standaloneBuiltinPattern = /(?:^|\s|;)\s*new\s+(Date|Error|Array|Set|Map|Promise|RegExp|URL|FormData|Event|FileReader|XMLHttpRequest)\(\)\s*;?\s*$/;
308
+ if (standaloneBuiltinPattern.test(line)) {
309
+ return true;
310
+ }
311
+
312
+ return immediatePatterns.some(pattern => pattern.test(line));
313
+ }
314
+
315
+ calculateConfidence(className, line) {
316
+ let confidence = 0.7; // Base confidence
317
+
318
+ // Increase confidence for business logic classes
319
+ if (className.includes('Service') || className.includes('Repository') ||
320
+ className.includes('Controller') || className.includes('Component')) {
321
+ confidence += 0.2;
322
+ }
323
+
324
+ // Decrease confidence if in constructor or initialization context
325
+ if (line.includes('constructor') || line.includes('init') || line.includes('setup')) {
326
+ confidence -= 0.1;
327
+ }
328
+
329
+ // Decrease confidence if it looks like configuration
330
+ if (line.includes('config') || line.includes('options') || line.includes('settings')) {
331
+ confidence -= 0.1;
332
+ }
333
+
334
+ return Math.max(0.3, Math.min(1.0, confidence));
335
+ }
336
+ }
337
+
338
+ module.exports = new C014Analyzer();
@@ -0,0 +1,314 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ class C017Analyzer {
5
+ constructor() {
6
+ this.ruleId = 'C017';
7
+ this.ruleName = 'Constructor Logic Limitation';
8
+ this.description = 'Không gán logic xử lý vào constructor';
9
+ }
10
+
11
+ async analyze(files, language, options = {}) {
12
+ const violations = [];
13
+
14
+ for (const filePath of files) {
15
+ if (options.verbose) {
16
+ console.log(`🔍 Running C017 analysis on ${path.basename(filePath)}`);
17
+ }
18
+
19
+ try {
20
+ const content = fs.readFileSync(filePath, 'utf8');
21
+ const fileViolations = await this.analyzeFile(filePath, content, language, options);
22
+ violations.push(...fileViolations);
23
+ } catch (error) {
24
+ console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
25
+ }
26
+ }
27
+
28
+ return violations;
29
+ }
30
+
31
+ async analyzeFile(filePath, content, language, config) {
32
+ switch (language) {
33
+ case 'typescript':
34
+ case 'javascript':
35
+ return this.analyzeTypeScript(filePath, content, config);
36
+ default:
37
+ return [];
38
+ }
39
+ }
40
+
41
+ async analyzeTypeScript(filePath, content, config) {
42
+ const violations = [];
43
+ const lines = content.split('\n');
44
+
45
+ // Find constructor blocks and analyze their content
46
+ lines.forEach((line, index) => {
47
+ const lineNumber = index + 1;
48
+ const trimmedLine = line.trim();
49
+
50
+ // Detect constructor start
51
+ if (this.isConstructorStart(trimmedLine)) {
52
+ const constructorInfo = this.extractConstructorInfo(lines, index);
53
+
54
+ const complexLogic = this.findComplexLogic(constructorInfo.body);
55
+
56
+ complexLogic.forEach(logic => {
57
+ violations.push({
58
+ ruleId: this.ruleId,
59
+ file: filePath,
60
+ line: lineNumber + logic.lineOffset,
61
+ column: logic.column,
62
+ message: `Constructor contains complex logic: ${logic.description}. Move to initialization methods`,
63
+ severity: 'warning',
64
+ code: logic.code,
65
+ type: logic.type,
66
+ confidence: logic.confidence,
67
+ suggestion: 'Move complex logic to separate initialization methods or lifecycle hooks'
68
+ });
69
+ });
70
+ }
71
+ });
72
+
73
+ return violations;
74
+ }
75
+
76
+ isConstructorStart(line) {
77
+ return line.includes('constructor(') || line.match(/constructor\s*\(/);
78
+ }
79
+
80
+ extractConstructorInfo(lines, startIndex) {
81
+ const constructorLines = [];
82
+ let braceDepth = 0;
83
+ let foundConstructorBrace = false;
84
+
85
+ for (let i = startIndex; i < lines.length; i++) {
86
+ const line = lines[i];
87
+ constructorLines.push(line);
88
+
89
+ // Check if this is the constructor line with opening brace
90
+ if (this.isConstructorStart(line.trim())) {
91
+ // Count braces in the constructor line itself
92
+ for (const char of line) {
93
+ if (char === '{') {
94
+ foundConstructorBrace = true;
95
+ braceDepth = 1; // Start counting from 1 for the constructor block
96
+ }
97
+ }
98
+ } else if (foundConstructorBrace) {
99
+ // Count braces in subsequent lines
100
+ for (const char of line) {
101
+ if (char === '{') {
102
+ braceDepth++;
103
+ } else if (char === '}') {
104
+ braceDepth--;
105
+ }
106
+ }
107
+
108
+ // If we've closed all braces, we're done
109
+ if (braceDepth === 0) {
110
+ break;
111
+ }
112
+ }
113
+ }
114
+
115
+ return {
116
+ body: constructorLines,
117
+ startLine: startIndex
118
+ };
119
+ }
120
+
121
+ findComplexLogic(constructorBody) {
122
+ const complexLogic = [];
123
+
124
+ constructorBody.forEach((line, index) => {
125
+ const trimmedLine = line.trim();
126
+
127
+ // Skip comments, empty lines, and constructor declaration
128
+ if (!trimmedLine ||
129
+ trimmedLine.startsWith('//') ||
130
+ trimmedLine.startsWith('/*') ||
131
+ trimmedLine.startsWith('*') ||
132
+ this.isConstructorStart(trimmedLine) ||
133
+ trimmedLine === '{' ||
134
+ trimmedLine === '}') {
135
+ return;
136
+ }
137
+
138
+ // Analyze line for complex logic patterns
139
+ const logicType = this.analyzeLineComplexity(trimmedLine);
140
+
141
+ if (logicType) {
142
+ complexLogic.push({
143
+ lineOffset: index,
144
+ column: line.indexOf(trimmedLine) + 1,
145
+ code: trimmedLine,
146
+ type: logicType.type,
147
+ description: logicType.description,
148
+ confidence: logicType.confidence
149
+ });
150
+ }
151
+ });
152
+
153
+ return complexLogic;
154
+ }
155
+
156
+ analyzeLineComplexity(line) {
157
+ // Simple property assignments are OK
158
+ if (this.isSimpleAssignment(line)) {
159
+ return null;
160
+ }
161
+
162
+ // Super calls are OK
163
+ if (this.isSuperCall(line)) {
164
+ return null;
165
+ }
166
+
167
+ // Parameter assignments are OK
168
+ if (this.isParameterAssignment(line)) {
169
+ return null;
170
+ }
171
+
172
+ // MobX decorators/observables setup are OK
173
+ if (this.isMobXSetup(line)) {
174
+ return null;
175
+ }
176
+
177
+ // Allowed method calls are OK
178
+ if (this.isAllowedMethodCall(line)) {
179
+ return null;
180
+ }
181
+
182
+ // Detect complex logic patterns
183
+ const complexPatterns = [
184
+ {
185
+ pattern: /\bif\s*\(|\belse\s|\bswitch\s*\(/,
186
+ type: 'conditional_logic',
187
+ description: 'conditional statements (if/else/switch)',
188
+ confidence: 0.9
189
+ },
190
+ {
191
+ pattern: /\bfor\s*\(|\bwhile\s*\(|\bdo\s+/,
192
+ type: 'loop_logic',
193
+ description: 'loops (for/while)',
194
+ confidence: 0.95
195
+ },
196
+ {
197
+ pattern: /\btry\s*{|\bcatch\s*\(|\bfinally\s*{/,
198
+ type: 'exception_handling',
199
+ description: 'exception handling (try/catch/finally)',
200
+ confidence: 0.8
201
+ },
202
+ {
203
+ pattern: /\.then\s*\(|\.catch\s*\(|await\s+/,
204
+ type: 'async_logic',
205
+ description: 'asynchronous operations',
206
+ confidence: 0.9
207
+ },
208
+ {
209
+ pattern: /\w+\s*\(\s*[^)]*\)\s*[;{]/,
210
+ type: 'method_call',
211
+ description: 'complex method calls',
212
+ confidence: 0.6 // Lower confidence, only flag complex calls
213
+ },
214
+ {
215
+ pattern: /new\s+\w+\s*\([^)]*\)\s*\./,
216
+ type: 'chained_instantiation',
217
+ description: 'chained object instantiation',
218
+ confidence: 0.8
219
+ }
220
+ ];
221
+
222
+ for (const { pattern, type, description, confidence } of complexPatterns) {
223
+ if (pattern.test(line)) {
224
+ return { type, description, confidence };
225
+ }
226
+ }
227
+
228
+ // Check for complex expressions
229
+ if (this.hasComplexExpression(line)) {
230
+ return {
231
+ type: 'complex_expression',
232
+ description: 'complex expression or calculation',
233
+ confidence: 0.6
234
+ };
235
+ }
236
+
237
+ return null;
238
+ }
239
+
240
+ isSimpleAssignment(line) {
241
+ // Patterns for simple assignments
242
+ const simplePatterns = [
243
+ /^this\.\w+\s*=\s*[^;]+;?$/, // this.property = value;
244
+ /^this\.\w+\s*=\s*\w+;?$/, // this.property = parameter;
245
+ /^this\.\w+\s*=\s*(null|undefined|true|false|\d+|'[^']*'|"[^"]*");?$/, // this.property = literal;
246
+ ];
247
+
248
+ return simplePatterns.some(pattern => pattern.test(line));
249
+ }
250
+
251
+ isSuperCall(line) {
252
+ return line.includes('super(') || line.startsWith('super.');
253
+ }
254
+
255
+ isParameterAssignment(line) {
256
+ // Check if it's just assigning constructor parameters to properties
257
+ return /^this\.\w+\s*=\s*\w+;?$/.test(line);
258
+ }
259
+
260
+ isMobXSetup(line) {
261
+ // MobX patterns that are OK in constructor
262
+ const mobxPatterns = [
263
+ 'makeObservable',
264
+ 'makeAutoObservable',
265
+ 'observable',
266
+ 'action',
267
+ 'computed',
268
+ 'reaction',
269
+ 'autorun',
270
+ '@observable',
271
+ '@action',
272
+ '@computed'
273
+ ];
274
+
275
+ const lowerLine = line.toLowerCase();
276
+ return mobxPatterns.some(pattern =>
277
+ lowerLine.includes(pattern.toLowerCase())
278
+ );
279
+ }
280
+
281
+ isAllowedMethodCall(line) {
282
+ // Method calls that are typically OK in constructors
283
+ const allowedMethods = [
284
+ 'makeObservable',
285
+ 'makeAutoObservable',
286
+ 'bind',
287
+ 'addEventListener',
288
+ 'removeEventListener',
289
+ 'Object.assign',
290
+ 'Object.defineProperty',
291
+ 'console.log',
292
+ 'console.warn'
293
+ ];
294
+
295
+ return allowedMethods.some(method =>
296
+ line.includes(method)
297
+ );
298
+ }
299
+
300
+ hasComplexExpression(line) {
301
+ // Check for complex expressions (multiple operators, complex calculations)
302
+ const complexityIndicators = [
303
+ /[+\-*/]\s*[+\-*/]/, // Multiple arithmetic operators
304
+ /\?\s*.*\s*:/, // Ternary operators
305
+ /&&|\|\|/, // Logical operators
306
+ /\[[^\]]*\]/, // Array access
307
+ /\.[^.]*\./ // Chained property access
308
+ ];
309
+
310
+ return complexityIndicators.some(pattern => pattern.test(line));
311
+ }
312
+ }
313
+
314
+ module.exports = new C017Analyzer();