@sun-asterisk/sunlint 1.3.18 → 1.3.20
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/rules/enhanced-rules-registry.json +77 -18
- package/core/cli-program.js +9 -1
- package/core/github-annotate-service.js +986 -0
- package/core/output-service.js +294 -6
- package/core/summary-report-service.js +30 -30
- package/docs/GITHUB_ACTIONS_INTEGRATION.md +421 -0
- package/package.json +2 -1
- package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +392 -280
- package/rules/common/C017_constructor_logic/analyzer.js +137 -503
- package/rules/common/C017_constructor_logic/config.json +50 -0
- package/rules/common/C017_constructor_logic/symbol-based-analyzer.js +463 -0
- package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +463 -21
- package/rules/security/S011_secure_guid_generation/README.md +255 -0
- package/rules/security/S011_secure_guid_generation/analyzer.js +135 -0
- package/rules/security/S011_secure_guid_generation/config.json +56 -0
- package/rules/security/S011_secure_guid_generation/symbol-based-analyzer.js +609 -0
- package/rules/security/S028_file_upload_size_limits/README.md +537 -0
- package/rules/security/S028_file_upload_size_limits/analyzer.js +202 -0
- package/rules/security/S028_file_upload_size_limits/config.json +186 -0
- package/rules/security/S028_file_upload_size_limits/symbol-based-analyzer.js +530 -0
- package/rules/security/S041_session_token_invalidation/README.md +303 -0
- package/rules/security/S041_session_token_invalidation/analyzer.js +242 -0
- package/rules/security/S041_session_token_invalidation/config.json +175 -0
- package/rules/security/S041_session_token_invalidation/regex-based-analyzer.js +411 -0
- package/rules/security/S041_session_token_invalidation/symbol-based-analyzer.js +674 -0
- package/rules/security/S044_re_authentication_required/README.md +136 -0
- package/rules/security/S044_re_authentication_required/analyzer.js +242 -0
- package/rules/security/S044_re_authentication_required/config.json +161 -0
- package/rules/security/S044_re_authentication_required/regex-based-analyzer.js +329 -0
- package/rules/security/S044_re_authentication_required/symbol-based-analyzer.js +537 -0
- package/rules/security/S045_brute_force_protection/README.md +345 -0
- package/rules/security/S045_brute_force_protection/analyzer.js +336 -0
- package/rules/security/S045_brute_force_protection/config.json +139 -0
- package/rules/security/S045_brute_force_protection/symbol-based-analyzer.js +646 -0
- package/rules/common/C017_constructor_logic/semantic-analyzer.js +0 -340
|
@@ -1,551 +1,185 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* C017 Main Analyzer - Do not put business logic inside constructors
|
|
3
|
+
* Primary: Ensure constructors only initialize objects, not perform business logic, to improve testability.
|
|
4
|
+
* Fallback: Regex-based for all other cases
|
|
5
|
+
* node cli.js --rules=c017 --input=examples/rule-test-fixtures/rules/C017_constructor_logic/violations --engine=heuristic --verbose
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const C017SymbolBasedAnalyzer = require('./symbol-based-analyzer');
|
|
3
9
|
|
|
4
10
|
class C017Analyzer {
|
|
5
|
-
constructor() {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
13
|
+
console.log(`🔧 [C017] Constructor called with options:`, !!options);
|
|
14
|
+
console.log(`🔧 [C017] Options type:`, typeof options, Object.keys(options || {}));
|
|
15
|
+
}
|
|
16
|
+
|
|
6
17
|
this.ruleId = 'C017';
|
|
7
|
-
this.ruleName = '
|
|
8
|
-
this.description = '
|
|
9
|
-
|
|
18
|
+
this.ruleName = 'Do not put business logic inside constructors';
|
|
19
|
+
this.description = 'Ensure constructors only initialize objects, not perform business logic, to improve testability.';
|
|
20
|
+
this.semanticEngine = options.semanticEngine || null;
|
|
21
|
+
this.verbose = options.verbose || false;
|
|
22
|
+
|
|
23
|
+
// Configuration
|
|
24
|
+
this.config = {
|
|
25
|
+
useSymbolBased: true, // Primary approach
|
|
26
|
+
fallbackToRegex: false, // Only when symbol fails completely
|
|
27
|
+
symbolBasedOnly: false // Can be set to true for pure mode
|
|
28
|
+
};
|
|
10
29
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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}`);
|
|
30
|
+
// Initialize both analyzers
|
|
31
|
+
try {
|
|
32
|
+
this.symbolAnalyzer = new C017SymbolBasedAnalyzer(this.semanticEngine);
|
|
33
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
34
|
+
console.log(`🔧 [C017] Symbol analyzer created successfully`);
|
|
25
35
|
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error(`🔧 [C017] Error creating symbol analyzer:`, error);
|
|
26
38
|
}
|
|
27
|
-
|
|
28
|
-
return violations;
|
|
29
|
-
}
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
case 'typescript':
|
|
34
|
-
case 'javascript':
|
|
35
|
-
return this.analyzeTypeScript(filePath, content, config);
|
|
36
|
-
default:
|
|
37
|
-
return [];
|
|
40
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
41
|
+
console.log(`🔧 [C017] Constructor completed`);
|
|
38
42
|
}
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// Detect constructor start
|
|
51
|
-
if (this.isConstructorStart(trimmedLine)) {
|
|
52
|
-
const constructorInfo = this.extractConstructorInfo(lines, index);
|
|
53
|
-
|
|
54
|
-
// Debug logging to understand boundary detection
|
|
55
|
-
if (config?.verbose) {
|
|
56
|
-
console.log(`[DEBUG] Constructor found at line ${lineNumber}`);
|
|
57
|
-
console.log(`[DEBUG] Constructor body lines: ${constructorInfo.body.length}`);
|
|
58
|
-
console.log(`[DEBUG] Constructor content:`, constructorInfo.body.map((l, i) => `${lineNumber + i}: ${l}`));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const complexLogic = this.findComplexLogic(constructorInfo.body);
|
|
62
|
-
|
|
63
|
-
complexLogic.forEach(logic => {
|
|
64
|
-
violations.push({
|
|
65
|
-
ruleId: this.ruleId,
|
|
66
|
-
file: filePath,
|
|
67
|
-
line: lineNumber + logic.lineOffset,
|
|
68
|
-
column: logic.column,
|
|
69
|
-
message: `Constructor contains complex logic: ${logic.description}. Move to initialization methods`,
|
|
70
|
-
severity: 'warning',
|
|
71
|
-
code: logic.code,
|
|
72
|
-
type: logic.type,
|
|
73
|
-
confidence: logic.confidence,
|
|
74
|
-
suggestion: 'Move complex logic to separate initialization methods or lifecycle hooks'
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
return violations;
|
|
81
|
-
}
|
|
45
|
+
/**
|
|
46
|
+
* Initialize with semantic engine
|
|
47
|
+
*/
|
|
48
|
+
async initialize(semanticEngine = null) {
|
|
49
|
+
if (semanticEngine) {
|
|
50
|
+
this.semanticEngine = semanticEngine;
|
|
51
|
+
}
|
|
52
|
+
this.verbose = semanticEngine?.verbose || false;
|
|
82
53
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
54
|
+
// Initialize both analyzers
|
|
55
|
+
await this.symbolAnalyzer.initialize(semanticEngine);
|
|
86
56
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
let braceDepth = 0;
|
|
90
|
-
let foundConstructorBrace = false;
|
|
91
|
-
let inConstructor = false;
|
|
92
|
-
let parenthesesDepth = 0;
|
|
93
|
-
let inCallback = false;
|
|
57
|
+
// Ensure verbose flag is propagated
|
|
58
|
+
this.symbolAnalyzer.verbose = this.verbose;
|
|
94
59
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const trimmedLine = line.trim();
|
|
98
|
-
|
|
99
|
-
// Check if this is the constructor line with opening brace
|
|
100
|
-
if (this.isConstructorStart(trimmedLine)) {
|
|
101
|
-
inConstructor = true;
|
|
102
|
-
constructorLines.push(line);
|
|
103
|
-
|
|
104
|
-
// Special case: empty constructor () {}
|
|
105
|
-
if (trimmedLine.includes(') {}')) {
|
|
106
|
-
foundConstructorBrace = true;
|
|
107
|
-
braceDepth = 0; // Already closed
|
|
108
|
-
break; // Stop immediately for empty constructor
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Count braces and parentheses in the constructor line itself
|
|
112
|
-
for (const char of line) {
|
|
113
|
-
if (char === '{') {
|
|
114
|
-
foundConstructorBrace = true;
|
|
115
|
-
braceDepth = 1; // Start counting from 1 for the constructor block
|
|
116
|
-
} else if (char === '(') {
|
|
117
|
-
parenthesesDepth++;
|
|
118
|
-
} else if (char === ')') {
|
|
119
|
-
parenthesesDepth--;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
} else if (inConstructor && foundConstructorBrace) {
|
|
123
|
-
constructorLines.push(line);
|
|
124
|
-
|
|
125
|
-
// Track callback functions and nested structures
|
|
126
|
-
for (const char of line) {
|
|
127
|
-
if (char === '(') {
|
|
128
|
-
parenthesesDepth++;
|
|
129
|
-
// Detect callback patterns: .use(, .then(, .catch(, etc.
|
|
130
|
-
if (line.includes('.use(') || line.includes('.then(') ||
|
|
131
|
-
line.includes('.catch(') || line.includes('.finally(') ||
|
|
132
|
-
line.includes('=>')) {
|
|
133
|
-
inCallback = true;
|
|
134
|
-
}
|
|
135
|
-
} else if (char === ')') {
|
|
136
|
-
parenthesesDepth--;
|
|
137
|
-
if (parenthesesDepth <= 0) {
|
|
138
|
-
inCallback = false;
|
|
139
|
-
}
|
|
140
|
-
} else if (char === '{') {
|
|
141
|
-
braceDepth++;
|
|
142
|
-
} else if (char === '}') {
|
|
143
|
-
braceDepth--;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// If we've closed all braces and not in callback, we're done
|
|
148
|
-
if (braceDepth === 0 && !inCallback) {
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
|
-
} else if (inConstructor) {
|
|
152
|
-
// Haven't found the opening brace yet, keep looking
|
|
153
|
-
constructorLines.push(line);
|
|
154
|
-
|
|
155
|
-
// Check for empty constructor pattern: ) {}
|
|
156
|
-
if (trimmedLine.includes(') {}')) {
|
|
157
|
-
foundConstructorBrace = true;
|
|
158
|
-
braceDepth = 0; // Already closed
|
|
159
|
-
break; // Stop immediately
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
for (const char of line) {
|
|
163
|
-
if (char === '{') {
|
|
164
|
-
foundConstructorBrace = true;
|
|
165
|
-
braceDepth = 1;
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
for (const char of line) {
|
|
173
|
-
if (char === '{') {
|
|
174
|
-
foundConstructorBrace = true;
|
|
175
|
-
braceDepth = 1;
|
|
176
|
-
break;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
60
|
+
if (this.verbose) {
|
|
61
|
+
console.log(`🔧 [C017 Hybrid] Analyzer initialized - verbose: ${this.verbose}`);
|
|
180
62
|
}
|
|
181
|
-
|
|
182
|
-
return {
|
|
183
|
-
body: constructorLines,
|
|
184
|
-
startLine: startIndex
|
|
185
|
-
};
|
|
186
63
|
}
|
|
187
64
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
// Check if this is an empty constructor first
|
|
194
|
-
// TEMPORARILY DISABLED for debugging
|
|
195
|
-
// if (this.isEmptyConstructor(constructorBody)) {
|
|
196
|
-
// return complexLogic; // Return empty array - no violations for empty constructors
|
|
197
|
-
// }
|
|
198
|
-
|
|
199
|
-
constructorBody.forEach((line, index) => {
|
|
200
|
-
const trimmedLine = line.trim();
|
|
201
|
-
|
|
202
|
-
// Skip comments, empty lines, and constructor declaration
|
|
203
|
-
if (!trimmedLine ||
|
|
204
|
-
trimmedLine.startsWith('//') ||
|
|
205
|
-
trimmedLine.startsWith('/*') ||
|
|
206
|
-
trimmedLine.startsWith('*') ||
|
|
207
|
-
this.isConstructorStart(trimmedLine) ||
|
|
208
|
-
trimmedLine === '{' ||
|
|
209
|
-
trimmedLine === '}') {
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
65
|
+
async analyze(files, language, options = {}) {
|
|
66
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
67
|
+
console.log(`🔧 [C017] analyze() method called with ${files.length} files, language: ${language}`);
|
|
68
|
+
}
|
|
212
69
|
|
|
213
|
-
|
|
214
|
-
if (this.isCallbackStart(trimmedLine)) {
|
|
215
|
-
inCallbackFunction = true;
|
|
216
|
-
callbackDepth = 1;
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
70
|
+
const violations = [];
|
|
219
71
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
callbackDepth += openBraces - closeBraces;
|
|
225
|
-
|
|
226
|
-
if (callbackDepth <= 0) {
|
|
227
|
-
inCallbackFunction = false;
|
|
228
|
-
callbackDepth = 0;
|
|
72
|
+
for (const filePath of files) {
|
|
73
|
+
try {
|
|
74
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
75
|
+
console.log(`🔧 [C017] Processing file: ${filePath}`);
|
|
229
76
|
}
|
|
230
|
-
return; // Skip analysis inside callbacks
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Analyze line for complex logic patterns only if not in callback
|
|
234
|
-
const logicType = this.analyzeLineComplexity(trimmedLine);
|
|
235
|
-
|
|
236
|
-
if (logicType) {
|
|
237
|
-
complexLogic.push({
|
|
238
|
-
lineOffset: index,
|
|
239
|
-
column: line.indexOf(trimmedLine) + 1,
|
|
240
|
-
code: trimmedLine,
|
|
241
|
-
type: logicType.type,
|
|
242
|
-
description: logicType.description,
|
|
243
|
-
confidence: logicType.confidence
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
77
|
|
|
248
|
-
|
|
249
|
-
|
|
78
|
+
const fileViolations = await this.analyzeFile(filePath, options);
|
|
79
|
+
violations.push(...fileViolations);
|
|
250
80
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
// Skip empty lines, comments, and constructor declaration
|
|
257
|
-
if (!trimmed ||
|
|
258
|
-
trimmed.startsWith('//') ||
|
|
259
|
-
trimmed.startsWith('/*') ||
|
|
260
|
-
trimmed.startsWith('*') ||
|
|
261
|
-
this.isConstructorStart(trimmed) ||
|
|
262
|
-
trimmed === '{' ||
|
|
263
|
-
trimmed === '}' ||
|
|
264
|
-
trimmed === ') {}') {
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Skip constructor parameter declarations (NestJS style)
|
|
269
|
-
if (trimmed.startsWith('@') || // Decorators like @InjectRepository
|
|
270
|
-
trimmed.match(/^\s*(private|public|protected)\s+readonly\s+\w+/) || // DI parameters
|
|
271
|
-
trimmed.match(/^\s*(private|public|protected)\s+\w+/) || // Other parameters
|
|
272
|
-
trimmed.endsWith(',') || // Parameter continuation
|
|
273
|
-
trimmed.endsWith(') {')) { // Constructor parameter closing
|
|
274
|
-
return false;
|
|
81
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
82
|
+
console.log(`🔧 [C017] File ${filePath}: Found ${fileViolations.length} violations`);
|
|
83
|
+
}
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.warn(`❌ [C017] Analysis failed for ${filePath}:`, error.message);
|
|
275
86
|
}
|
|
276
|
-
|
|
277
|
-
// This is a meaningful line (logic, assignments, calls, etc.)
|
|
278
|
-
return true;
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
return meaningfulLines.length === 0;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
isCallbackStart(line) {
|
|
285
|
-
// Patterns that indicate callback function starts
|
|
286
|
-
const callbackPatterns = [
|
|
287
|
-
/\.(use|then|catch|finally|map|filter|forEach|reduce)\s*\(/,
|
|
288
|
-
/=>\s*\{/,
|
|
289
|
-
/function\s*\(/,
|
|
290
|
-
/\(\s*\w+\s*\)\s*=>/,
|
|
291
|
-
/interceptors\.(request|response)\.use\(/,
|
|
292
|
-
/\.addEventListener\(/,
|
|
293
|
-
/setTimeout\(/,
|
|
294
|
-
/setInterval\(/
|
|
295
|
-
];
|
|
296
|
-
|
|
297
|
-
return callbackPatterns.some(pattern => pattern.test(line));
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
analyzeLineComplexity(line) {
|
|
301
|
-
// Simple property assignments are OK
|
|
302
|
-
if (this.isSimpleAssignment(line)) {
|
|
303
|
-
return null;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Super calls are OK
|
|
307
|
-
if (this.isSuperCall(line)) {
|
|
308
|
-
return null;
|
|
309
87
|
}
|
|
310
88
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
return null;
|
|
89
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
90
|
+
console.log(`🔧 [C017] Total violations found: ${violations.length}`);
|
|
314
91
|
}
|
|
315
92
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
return null;
|
|
319
|
-
}
|
|
93
|
+
return violations;
|
|
94
|
+
}
|
|
320
95
|
|
|
321
|
-
|
|
322
|
-
if (
|
|
323
|
-
|
|
96
|
+
async analyzeFile(filePath, options = {}) {
|
|
97
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
98
|
+
console.log(`🔧 [C017] analyzeFile() called for: ${filePath}`);
|
|
324
99
|
}
|
|
325
100
|
|
|
326
|
-
//
|
|
327
|
-
if (this.
|
|
328
|
-
|
|
329
|
-
|
|
101
|
+
// 1. Try Symbol-based analysis first (primary)
|
|
102
|
+
if (this.config.useSymbolBased &&
|
|
103
|
+
this.semanticEngine?.project &&
|
|
104
|
+
this.semanticEngine?.initialized) {
|
|
105
|
+
try {
|
|
106
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
107
|
+
console.log(`🔧 [C017] Trying symbol-based analysis...`);
|
|
108
|
+
}
|
|
109
|
+
const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
110
|
+
if (sourceFile) {
|
|
111
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
112
|
+
console.log(`🔧 [C017] Source file found, analyzing with symbol-based...`);
|
|
113
|
+
}
|
|
114
|
+
const violations = await this.symbolAnalyzer.analyzeFileWithSymbols(filePath, { ...options, verbose: options.verbose });
|
|
330
115
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
return null;
|
|
334
|
-
}
|
|
116
|
+
// Mark violations with analysis strategy
|
|
117
|
+
violations.forEach(v => v.analysisStrategy = 'symbol-based');
|
|
335
118
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
confidence: 0.95
|
|
349
|
-
},
|
|
350
|
-
{
|
|
351
|
-
pattern: /\btry\s*{|\bcatch\s*\(|\bfinally\s*{/,
|
|
352
|
-
type: 'exception_handling',
|
|
353
|
-
description: 'exception handling (try/catch/finally)',
|
|
354
|
-
confidence: 0.8
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
pattern: /\.then\s*\(|\.catch\s*\(|await\s+/,
|
|
358
|
-
type: 'async_logic',
|
|
359
|
-
description: 'asynchronous operations',
|
|
360
|
-
confidence: 0.9
|
|
361
|
-
},
|
|
362
|
-
{
|
|
363
|
-
pattern: /\w+\s*\(\s*[^)]*\)\s*[;{]/,
|
|
364
|
-
type: 'method_call',
|
|
365
|
-
description: 'complex method calls',
|
|
366
|
-
confidence: 0.4 // Reduced confidence to minimize false positives
|
|
367
|
-
},
|
|
368
|
-
{
|
|
369
|
-
pattern: /new\s+\w+\s*\([^)]*\)\s*\./,
|
|
370
|
-
type: 'chained_instantiation',
|
|
371
|
-
description: 'chained object instantiation',
|
|
372
|
-
confidence: 0.8
|
|
119
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
120
|
+
console.log(`✅ [C017] Symbol-based analysis: ${violations.length} violations`);
|
|
121
|
+
}
|
|
122
|
+
return violations; // Return even if 0 violations - symbol analysis completed successfully
|
|
123
|
+
} else {
|
|
124
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
125
|
+
console.log(`⚠️ [C017] Source file not found in project`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.warn(`⚠️ [C017] Symbol analysis failed: ${error.message}`);
|
|
130
|
+
// Continue to fallback
|
|
373
131
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
132
|
+
} else {
|
|
133
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
134
|
+
console.log(`🔄 [C017] Symbol analysis conditions check:`);
|
|
135
|
+
console.log(` - useSymbolBased: ${this.config.useSymbolBased}`);
|
|
136
|
+
console.log(` - semanticEngine: ${!!this.semanticEngine}`);
|
|
137
|
+
console.log(` - semanticEngine.project: ${!!this.semanticEngine?.project}`);
|
|
138
|
+
console.log(` - semanticEngine.initialized: ${this.semanticEngine?.initialized}`);
|
|
139
|
+
console.log(`🔄 [C017] Symbol analysis unavailable, using regex fallback`);
|
|
379
140
|
}
|
|
380
141
|
}
|
|
381
142
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
return {
|
|
385
|
-
type: 'complex_expression',
|
|
386
|
-
description: 'complex expression or calculation',
|
|
387
|
-
confidence: 0.6
|
|
388
|
-
};
|
|
143
|
+
if (options?.verbose) {
|
|
144
|
+
console.log(`🔧 [C017] No analysis methods succeeded, returning empty`);
|
|
389
145
|
}
|
|
390
|
-
|
|
391
|
-
return null;
|
|
146
|
+
return [];
|
|
392
147
|
}
|
|
393
148
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
/^this\.\w+\s*=\s*\w+;?$/, // this.property = parameter;
|
|
399
|
-
/^this\.\w+\s*=\s*(null|undefined|true|false|\d+|'[^']*'|"[^"]*");?$/, // this.property = literal;
|
|
400
|
-
/^this\.\w+\s*=\s*this\.\w+\s*\.\s*get\s*\(/, // this.property = this.configService.get(
|
|
401
|
-
/^this\.\w+\s*=\s*new\s+\w+\s*\(/, // this.property = new SomeClass(
|
|
402
|
-
/^this\.\w+\s*=\s*\w+\s*\.\s*\w+\s*\(/, // this.property = service.method(
|
|
403
|
-
// Enhanced patterns for configuration initialization
|
|
404
|
-
/^this\.\w+\s*=\s*new\s+\w+Client\s*\(/, // this.s3 = new S3Client(
|
|
405
|
-
/^this\.\w+\s*=\s*new\s+\w+\s*\(\s*\{/, // this.prop = new Class({ config })
|
|
406
|
-
/^this\.\w+\s*=\s*\w+\s*\.\s*create\s*\(/, // this.prop = Factory.create(
|
|
407
|
-
/^this\.\w+\s*=\s*\w+\s*\.\s*getInstance\s*\(/, // this.prop = Service.getInstance(
|
|
408
|
-
];
|
|
149
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
150
|
+
console.log(`🔧 [C017] analyzeFileBasic() called for: ${filePath}`);
|
|
151
|
+
console.log(`🔧 [C017] semanticEngine exists: ${!!this.semanticEngine}`);
|
|
152
|
+
console.log(`🔧 [C017] symbolAnalyzer exists: ${!!this.symbolAnalyzer}`);
|
|
409
153
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
// Check if this is configuration/options object setup
|
|
415
|
-
const configPatterns = [
|
|
416
|
-
/^\s*\w+:\s*/, // Property definition in object literal
|
|
417
|
-
/level:\s*/, // Log level setting
|
|
418
|
-
/timestamp:\s*/, // Timestamp configuration
|
|
419
|
-
/formatters:\s*/, // Formatter configuration
|
|
420
|
-
/mixin:\s*/, // Mixin configuration
|
|
421
|
-
/transport:\s*/, // Transport configuration
|
|
422
|
-
/options:\s*/, // Options configuration
|
|
423
|
-
/target:\s*/, // Target configuration
|
|
424
|
-
/baseURL:\s*/, // Axios baseURL
|
|
425
|
-
/timeout:\s*/, // Axios timeout
|
|
426
|
-
/region:\s*/, // AWS region configuration
|
|
427
|
-
/credentials:\s*/, // AWS credentials
|
|
428
|
-
/endpoint:\s*/, // API endpoint
|
|
429
|
-
/apiVersion:\s*/, // API version
|
|
430
|
-
/maxRetries:\s*/, // Retry configuration
|
|
431
|
-
/headers:\s*/, // HTTP headers
|
|
432
|
-
/defaultHeaders:\s*/, // Default headers
|
|
433
|
-
/interceptors:\s*/, // Request/response interceptors
|
|
434
|
-
/transformRequest:\s*/, // Request transformation
|
|
435
|
-
/transformResponse:\s*/, // Response transformation
|
|
436
|
-
/validateStatus:\s*/, // Status validation
|
|
437
|
-
/responseType:\s*/, // Response type
|
|
438
|
-
/maxContentLength:\s*/, // Content length limit
|
|
439
|
-
/ssl:\s*/, // SSL configuration
|
|
440
|
-
/auth:\s*/, // Authentication
|
|
441
|
-
/withCredentials:\s*/, // CORS credentials
|
|
442
|
-
/maxRedirects:\s*/, // Max redirects
|
|
443
|
-
];
|
|
444
|
-
|
|
445
|
-
return configPatterns.some(pattern => pattern.test(line));
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
isInterceptorSetup(line) {
|
|
449
|
-
// Check if this is axios interceptor setup (should be allowed in constructor)
|
|
450
|
-
const interceptorPatterns = [
|
|
451
|
-
/interceptors\.(request|response)\.use/,
|
|
452
|
-
/\.use\s*\(/,
|
|
453
|
-
/\.then\s*\(/,
|
|
454
|
-
/\.catch\s*\(/,
|
|
455
|
-
/\.finally\s*\(/,
|
|
456
|
-
/=>\s*\{?/,
|
|
457
|
-
/=>\s*[^{]/, // Arrow function without braces
|
|
458
|
-
/function\s*\(/
|
|
459
|
-
];
|
|
154
|
+
try {
|
|
155
|
+
// Try symbol-based analysis first
|
|
156
|
+
if (this.semanticEngine?.isSymbolEngineReady?.() &&
|
|
157
|
+
this.semanticEngine.project) {
|
|
460
158
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
isSuperCall(line) {
|
|
465
|
-
return line.includes('super(') || line.startsWith('super.');
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
isParameterAssignment(line) {
|
|
469
|
-
// Check if it's just assigning constructor parameters to properties
|
|
470
|
-
return /^this\.\w+\s*=\s*\w+;?$/.test(line);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
isMobXSetup(line) {
|
|
474
|
-
// MobX patterns that are OK in constructor
|
|
475
|
-
const mobxPatterns = [
|
|
476
|
-
'makeObservable',
|
|
477
|
-
'makeAutoObservable',
|
|
478
|
-
'observable',
|
|
479
|
-
'action',
|
|
480
|
-
'computed',
|
|
481
|
-
'reaction',
|
|
482
|
-
'autorun',
|
|
483
|
-
'@observable',
|
|
484
|
-
'@action',
|
|
485
|
-
'@computed'
|
|
486
|
-
];
|
|
159
|
+
if (this.verbose) {
|
|
160
|
+
console.log(`🔍 [C017] Using symbol-based analysis for ${filePath}`);
|
|
161
|
+
}
|
|
487
162
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
)
|
|
163
|
+
const violations = await this.symbolAnalyzer.analyzeFileBasic(filePath, options);
|
|
164
|
+
return violations;
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (this.verbose) {
|
|
168
|
+
console.warn(`⚠️ [C017] Symbol analysis failed: ${error.message}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
492
171
|
}
|
|
493
172
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
'bind',
|
|
500
|
-
'addEventListener',
|
|
501
|
-
'removeEventListener',
|
|
502
|
-
'Object.assign',
|
|
503
|
-
'Object.defineProperty',
|
|
504
|
-
'console.log',
|
|
505
|
-
'console.warn',
|
|
506
|
-
// Common initialization patterns
|
|
507
|
-
'createLogger',
|
|
508
|
-
'initializeLogger',
|
|
509
|
-
'setupConfiguration',
|
|
510
|
-
'initializeService',
|
|
511
|
-
'configure',
|
|
512
|
-
'init',
|
|
513
|
-
'setup',
|
|
514
|
-
// Common service/config patterns
|
|
515
|
-
'get',
|
|
516
|
-
'getService',
|
|
517
|
-
'getInstance',
|
|
518
|
-
// Dependency injection patterns
|
|
519
|
-
'inject',
|
|
520
|
-
'resolve',
|
|
521
|
-
// Axios/HTTP setup patterns
|
|
522
|
-
'interceptors',
|
|
523
|
-
'use',
|
|
524
|
-
'defaults',
|
|
525
|
-
'timeout',
|
|
526
|
-
'baseURL',
|
|
527
|
-
'headers'
|
|
528
|
-
];
|
|
529
|
-
|
|
530
|
-
const lowerLine = line.toLowerCase();
|
|
531
|
-
return allowedMethods.some(method =>
|
|
532
|
-
lowerLine.includes(method.toLowerCase())
|
|
533
|
-
);
|
|
173
|
+
/**
|
|
174
|
+
* Methods for compatibility with different engine invocation patterns
|
|
175
|
+
*/
|
|
176
|
+
async analyzeFileWithSymbols(filePath, options = {}) {
|
|
177
|
+
return this.analyzeFile(filePath, options);
|
|
534
178
|
}
|
|
535
179
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
// Made more restrictive to reduce false positives
|
|
539
|
-
const complexityIndicators = [
|
|
540
|
-
/[+\-*/]\s*[+\-*/]/, // Multiple arithmetic operators
|
|
541
|
-
/\?\s*.*\s*:/, // Ternary operators
|
|
542
|
-
/&&.*&&|\|\|.*\|\|/, // Multiple logical operators (more restrictive)
|
|
543
|
-
/\[[^\]]*\].*\[[^\]]*\]/, // Multiple array accesses
|
|
544
|
-
/\.[^.]*\.[^.]*\./ // Triple+ chained property access (more restrictive)
|
|
545
|
-
];
|
|
546
|
-
|
|
547
|
-
return complexityIndicators.some(pattern => pattern.test(line));
|
|
180
|
+
async analyzeWithSemantics(filePath, options = {}) {
|
|
181
|
+
return this.analyzeFile(filePath, options);
|
|
548
182
|
}
|
|
549
183
|
}
|
|
550
184
|
|
|
551
|
-
module.exports =
|
|
185
|
+
module.exports = C017Analyzer;
|