@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,481 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C012 Heuristic Analyzer - Command Query Separation
|
|
3
|
+
*
|
|
4
|
+
* Uses regex and pattern matching to detect violations of CQS principle:
|
|
5
|
+
* - Functions that both modify state and return meaningful values
|
|
6
|
+
* - Methods that mix command and query operations
|
|
7
|
+
* - Fallback for when AST parsing fails
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
class C012Analyzer {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.ruleId = 'C012';
|
|
16
|
+
this.ruleName = 'Command Query Separation';
|
|
17
|
+
this.description = 'Separate commands (modify state) from queries (return data)';
|
|
18
|
+
this.severity = 'warning';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async analyze(files, language, config = {}) {
|
|
22
|
+
const violations = [];
|
|
23
|
+
|
|
24
|
+
for (const filePath of files) {
|
|
25
|
+
try {
|
|
26
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
27
|
+
const fileViolations = await this.analyzeFile(filePath, content, language, config);
|
|
28
|
+
violations.push(...fileViolations);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.warn(`C012 analysis failed for ${filePath}:`, error.message);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return violations;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async analyzeFile(filePath, content, language, config) {
|
|
38
|
+
const violations = [];
|
|
39
|
+
const lines = content.split('\n');
|
|
40
|
+
|
|
41
|
+
// Skip non-supported files
|
|
42
|
+
if (!this.isSupportedFile(filePath)) {
|
|
43
|
+
return violations;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (let i = 0; i < lines.length; i++) {
|
|
47
|
+
const line = lines[i];
|
|
48
|
+
const violation = this.analyzeLine(line, i + 1, lines, filePath);
|
|
49
|
+
if (violation) {
|
|
50
|
+
violations.push(violation);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return violations;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
isSupportedFile(filePath) {
|
|
58
|
+
const supportedExtensions = ['.js', '.jsx', '.ts', '.tsx', '.java', '.kt', '.dart', '.cs'];
|
|
59
|
+
return supportedExtensions.some(ext => filePath.endsWith(ext));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
analyzeLine(line, lineNumber, allLines, filePath) {
|
|
63
|
+
const trimmedLine = line.trim();
|
|
64
|
+
|
|
65
|
+
// Skip comments and empty lines
|
|
66
|
+
if (!trimmedLine || this.isComment(trimmedLine)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check for function declarations
|
|
71
|
+
const functionInfo = this.extractFunctionInfo(trimmedLine, lineNumber, allLines);
|
|
72
|
+
if (!functionInfo) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Skip allowed functions
|
|
77
|
+
if (this.isAllowedFunction(functionInfo.name)) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Extract function body
|
|
82
|
+
const functionBody = this.extractFunctionBody(allLines, lineNumber - 1);
|
|
83
|
+
if (!functionBody) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check for CQS violation
|
|
88
|
+
const violation = this.checkCQSViolation(functionInfo, functionBody, lineNumber, filePath);
|
|
89
|
+
return violation;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
isComment(line) {
|
|
93
|
+
return line.startsWith('//') || line.startsWith('/*') || line.startsWith('*');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
extractFunctionInfo(line, lineNumber, allLines) {
|
|
97
|
+
// Function declaration patterns
|
|
98
|
+
const patterns = [
|
|
99
|
+
// JavaScript/TypeScript functions
|
|
100
|
+
/(?:async\s+)?function\s+(\w+)\s*\(/,
|
|
101
|
+
/(\w+)\s*:\s*(?:async\s+)?\([^)]*\)\s*=>/,
|
|
102
|
+
/(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/,
|
|
103
|
+
/(\w+)\s*\([^)]*\)\s*\{/,
|
|
104
|
+
|
|
105
|
+
// Method definitions
|
|
106
|
+
/(?:public|private|protected)?\s*(?:async\s+)?(\w+)\s*\([^)]*\)\s*[:{]/,
|
|
107
|
+
/(\w+)\s*\([^)]*\)\s*:\s*\w+\s*\{/, // TypeScript with return type
|
|
108
|
+
|
|
109
|
+
// Java/Kotlin/C# methods
|
|
110
|
+
/(?:public|private|protected)\s+(?:static\s+)?(?:\w+\s+)?(\w+)\s*\([^)]*\)\s*\{/,
|
|
111
|
+
|
|
112
|
+
// Dart methods
|
|
113
|
+
/(?:\w+\s+)?(\w+)\s*\([^)]*\)\s*(?:async\s*)?\{/
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
for (const pattern of patterns) {
|
|
117
|
+
const match = line.match(pattern);
|
|
118
|
+
if (match) {
|
|
119
|
+
return {
|
|
120
|
+
name: match[1],
|
|
121
|
+
fullLine: line,
|
|
122
|
+
lineNumber
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
extractFunctionBody(lines, startIndex) {
|
|
131
|
+
let braceCount = 0;
|
|
132
|
+
let body = '';
|
|
133
|
+
let inFunction = false;
|
|
134
|
+
|
|
135
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
136
|
+
const line = lines[i];
|
|
137
|
+
|
|
138
|
+
// Count braces
|
|
139
|
+
for (const char of line) {
|
|
140
|
+
if (char === '{') {
|
|
141
|
+
braceCount++;
|
|
142
|
+
inFunction = true;
|
|
143
|
+
} else if (char === '}') {
|
|
144
|
+
braceCount--;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (inFunction) {
|
|
149
|
+
body += line + '\n';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Function complete
|
|
153
|
+
if (inFunction && braceCount === 0) {
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Safety limit
|
|
158
|
+
if (i - startIndex > 100) {
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return body;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
checkCQSViolation(functionInfo, functionBody, lineNumber, filePath) {
|
|
167
|
+
const hasStateModification = this.hasStateModification(functionBody);
|
|
168
|
+
const hasReturnValue = this.hasReturnValue(functionBody);
|
|
169
|
+
|
|
170
|
+
// CQS violation: both command and query behavior
|
|
171
|
+
if (hasStateModification && hasReturnValue) {
|
|
172
|
+
// NEW: Check if this is an acceptable pattern
|
|
173
|
+
if (this.isAcceptablePattern(functionInfo.name, functionBody)) {
|
|
174
|
+
return null; // Allow acceptable patterns
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
ruleId: this.ruleId,
|
|
179
|
+
file: filePath,
|
|
180
|
+
line: lineNumber,
|
|
181
|
+
column: functionInfo.fullLine.indexOf(functionInfo.name) + 1,
|
|
182
|
+
message: `Function '${functionInfo.name}' violates Command Query Separation: both modifies state and returns value`,
|
|
183
|
+
severity: this.severity,
|
|
184
|
+
code: functionInfo.fullLine.trim(),
|
|
185
|
+
type: 'cqs_violation',
|
|
186
|
+
confidence: this.calculateConfidence(hasStateModification, hasReturnValue, functionBody),
|
|
187
|
+
suggestion: this.getSuggestion(functionInfo.name)
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
hasStateModification(functionBody) {
|
|
195
|
+
const modificationPatterns = [
|
|
196
|
+
// Assignment operations
|
|
197
|
+
/\w+\s*=\s*[^=]/,
|
|
198
|
+
/this\.\w+\s*=/,
|
|
199
|
+
/\w+\.\w+\s*=/,
|
|
200
|
+
|
|
201
|
+
// Update operations
|
|
202
|
+
/\+\+\w+/,
|
|
203
|
+
/\w+\+\+/,
|
|
204
|
+
/--\w+/,
|
|
205
|
+
/\w+--/,
|
|
206
|
+
/\w+\s*\+=\s*/,
|
|
207
|
+
/\w+\s*-=\s*/,
|
|
208
|
+
/\w+\s*\*=\s*/,
|
|
209
|
+
/\w+\s*\/=\s*/,
|
|
210
|
+
|
|
211
|
+
// Method calls that modify state
|
|
212
|
+
/\.(?:push|pop|shift|unshift|splice|sort|reverse|fill)\s*\(/,
|
|
213
|
+
/\.(?:set|delete|clear|add|remove|update)\s*\(/,
|
|
214
|
+
/\.(?:save|store|persist|insert|append|prepend)\s*\(/,
|
|
215
|
+
/\.(?:setState|dispatch|emit|trigger)\s*\(/,
|
|
216
|
+
|
|
217
|
+
// Array/object mutations
|
|
218
|
+
/\[\w+\]\s*=/,
|
|
219
|
+
/\{\s*\w+\s*:\s*\w+\s*\}/,
|
|
220
|
+
|
|
221
|
+
// Property modifications
|
|
222
|
+
/\w+\[\w+\]\s*=/,
|
|
223
|
+
/Object\.assign\s*\(/,
|
|
224
|
+
|
|
225
|
+
// State modification keywords
|
|
226
|
+
/\b(?:increment|decrement|modify|change|alter|mutate)\s*\(/,
|
|
227
|
+
/\bset[A-Z]\w*\s*\(/,
|
|
228
|
+
/\bupdate[A-Z]\w*\s*\(/
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
return modificationPatterns.some(pattern => pattern.test(functionBody));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
hasReturnValue(functionBody) {
|
|
235
|
+
const returnPatterns = [
|
|
236
|
+
// Return statements with values (excluding simple booleans)
|
|
237
|
+
/return\s+(?!true|false|null|undefined|;|\}|$)\w+/,
|
|
238
|
+
/return\s+(?!true|false)[^;\}]+[^;\s\}]/,
|
|
239
|
+
/return\s+\w+\s*\([^)]*\)/,
|
|
240
|
+
/return\s+\w+\.\w+/,
|
|
241
|
+
/return\s+new\s+\w+/,
|
|
242
|
+
/return\s+\{[^}]+\}/,
|
|
243
|
+
/return\s+\[[^\]]+\]/,
|
|
244
|
+
/return\s+`[^`]+`/,
|
|
245
|
+
/return\s+["'][^"']+["']/,
|
|
246
|
+
/return\s+\d+/,
|
|
247
|
+
|
|
248
|
+
// Arrow function expressions
|
|
249
|
+
/=>\s*(?!true|false|null|undefined)\w+/,
|
|
250
|
+
/=>\s*\w+\s*\([^)]*\)/,
|
|
251
|
+
/=>\s*\w+\.\w+/,
|
|
252
|
+
/=>\s*\{[^}]+\}/,
|
|
253
|
+
/=>\s*\[[^\]]+\]/,
|
|
254
|
+
|
|
255
|
+
// Function expressions that return values
|
|
256
|
+
/function[^}]*return\s+(?!true|false|null|undefined)\w+/
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
// Exclude simple success/failure indicators
|
|
260
|
+
const simpleReturnPatterns = [
|
|
261
|
+
/return\s+true\s*;?$/,
|
|
262
|
+
/return\s+false\s*;?$/,
|
|
263
|
+
/return\s+null\s*;?$/,
|
|
264
|
+
/return\s+undefined\s*;?$/,
|
|
265
|
+
/return\s*;?$/
|
|
266
|
+
];
|
|
267
|
+
|
|
268
|
+
const hasComplexReturn = returnPatterns.some(pattern => pattern.test(functionBody));
|
|
269
|
+
const hasOnlySimpleReturn = simpleReturnPatterns.some(pattern => pattern.test(functionBody));
|
|
270
|
+
|
|
271
|
+
return hasComplexReturn && !hasOnlySimpleReturn;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
isAllowedFunction(functionName) {
|
|
275
|
+
const allowedPatterns = [
|
|
276
|
+
// Constructor and lifecycle
|
|
277
|
+
/^constructor$/,
|
|
278
|
+
/^componentDidMount$/,
|
|
279
|
+
/^componentWillUnmount$/,
|
|
280
|
+
/^useEffect$/,
|
|
281
|
+
/^init$/,
|
|
282
|
+
/^setup$/,
|
|
283
|
+
|
|
284
|
+
// Test functions
|
|
285
|
+
/^test/,
|
|
286
|
+
/^it$/,
|
|
287
|
+
/^describe$/,
|
|
288
|
+
/^before/,
|
|
289
|
+
/^after/,
|
|
290
|
+
|
|
291
|
+
// Getters/setters
|
|
292
|
+
/^get\w+$/,
|
|
293
|
+
/^set\w+$/,
|
|
294
|
+
|
|
295
|
+
// Factory/builder patterns (allowed to create and return)
|
|
296
|
+
/^create\w+$/,
|
|
297
|
+
/^build\w+$/,
|
|
298
|
+
/^make\w+$/,
|
|
299
|
+
/^new\w+$/,
|
|
300
|
+
/Factory$/,
|
|
301
|
+
/Builder$/,
|
|
302
|
+
|
|
303
|
+
// Configuration and initialization
|
|
304
|
+
/^configure\w+$/,
|
|
305
|
+
/^initialize\w+$/,
|
|
306
|
+
/^setup\w+$/,
|
|
307
|
+
|
|
308
|
+
// Toggle operations (expected to modify and return new state)
|
|
309
|
+
/^toggle\w+$/,
|
|
310
|
+
/^switch\w+$/,
|
|
311
|
+
|
|
312
|
+
// Array operations (modify and return)
|
|
313
|
+
/^push$/,
|
|
314
|
+
/^pop$/,
|
|
315
|
+
/^shift$/,
|
|
316
|
+
/^unshift$/,
|
|
317
|
+
/^splice$/,
|
|
318
|
+
|
|
319
|
+
// Standard methods
|
|
320
|
+
/^toString$/,
|
|
321
|
+
/^valueOf$/,
|
|
322
|
+
/^render$/,
|
|
323
|
+
/^main$/,
|
|
324
|
+
|
|
325
|
+
// Event handlers
|
|
326
|
+
/^on[A-Z]/,
|
|
327
|
+
/^handle[A-Z]/,
|
|
328
|
+
/Handler$/,
|
|
329
|
+
|
|
330
|
+
// Utility and helper methods
|
|
331
|
+
/Helper$/,
|
|
332
|
+
/Util$/,
|
|
333
|
+
/Utils$/
|
|
334
|
+
];
|
|
335
|
+
|
|
336
|
+
return allowedPatterns.some(pattern => pattern.test(functionName));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
calculateConfidence(hasStateModification, hasReturnValue, functionBody) {
|
|
340
|
+
let confidence = 0.6;
|
|
341
|
+
|
|
342
|
+
// Higher confidence for clear violations
|
|
343
|
+
if (hasStateModification && hasReturnValue) {
|
|
344
|
+
confidence = 0.8;
|
|
345
|
+
|
|
346
|
+
// Even higher if multiple modification patterns
|
|
347
|
+
const modificationCount = this.countModificationPatterns(functionBody);
|
|
348
|
+
if (modificationCount > 2) {
|
|
349
|
+
confidence = 0.9;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Higher if complex return values
|
|
353
|
+
if (this.hasComplexReturnValue(functionBody)) {
|
|
354
|
+
confidence = Math.min(0.95, confidence + 0.1);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return confidence;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
countModificationPatterns(functionBody) {
|
|
362
|
+
const patterns = [
|
|
363
|
+
/\w+\s*=/,
|
|
364
|
+
/\+\+|\--/,
|
|
365
|
+
/\+=|\-=|\*=|\/=/,
|
|
366
|
+
/\.(?:push|pop|set|delete|save|update)\s*\(/
|
|
367
|
+
];
|
|
368
|
+
|
|
369
|
+
let count = 0;
|
|
370
|
+
patterns.forEach(pattern => {
|
|
371
|
+
const matches = functionBody.match(new RegExp(pattern.source, 'g'));
|
|
372
|
+
if (matches) {
|
|
373
|
+
count += matches.length;
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
return count;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
isAcceptablePattern(functionName, functionBody) {
|
|
381
|
+
// NEW: Practical CQS - Allow acceptable patterns per strategy
|
|
382
|
+
|
|
383
|
+
// 1. CRUD Operations (single operation + return)
|
|
384
|
+
const crudPatterns = [
|
|
385
|
+
/^(create|insert|add|save|store)\w*$/i,
|
|
386
|
+
/^(update|modify|edit|change)\w*$/i,
|
|
387
|
+
/^(upsert|merge)\w*$/i,
|
|
388
|
+
/^(delete|remove|destroy)\w*$/i
|
|
389
|
+
];
|
|
390
|
+
|
|
391
|
+
if (crudPatterns.some(pattern => pattern.test(functionName))) {
|
|
392
|
+
// Check if it's a simple CRUD - single operation
|
|
393
|
+
const queryCount = this.countDatabaseOperations(functionBody);
|
|
394
|
+
if (queryCount <= 1) {
|
|
395
|
+
return true; // Single query + return is acceptable
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// 2. Transaction-based Operations
|
|
400
|
+
if (this.isTransactionBased(functionBody)) {
|
|
401
|
+
return true; // Multiple operations in transaction are atomic
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// 3. ORM Standard Patterns
|
|
405
|
+
const ormPatterns = [
|
|
406
|
+
/^findOrCreate\w*$/i,
|
|
407
|
+
/^findAndUpdate\w*$/i,
|
|
408
|
+
/^findAndModify\w*$/i,
|
|
409
|
+
/^saveAndReturn\w*$/i,
|
|
410
|
+
/^selectForUpdate\w*$/i
|
|
411
|
+
];
|
|
412
|
+
|
|
413
|
+
if (ormPatterns.some(pattern => pattern.test(functionName))) {
|
|
414
|
+
return true; // Standard ORM patterns including selectForUpdate
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// 4. Factory patterns (create and return by design)
|
|
418
|
+
const factoryPatterns = [
|
|
419
|
+
/^(build|construct|generate|produce)\w*$/i,
|
|
420
|
+
/^(transform|convert|map)\w*$/i
|
|
421
|
+
];
|
|
422
|
+
|
|
423
|
+
if (factoryPatterns.some(pattern => pattern.test(functionName))) {
|
|
424
|
+
return true; // Factory patterns expected to create and return
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return false; // Not an acceptable pattern - flag as violation
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
countDatabaseOperations(functionBody) {
|
|
431
|
+
const dbOperationPatterns = [
|
|
432
|
+
/\.(save|insert|create|update|delete|remove)\s*\(/gi,
|
|
433
|
+
/\.(find|findOne|findBy|query|execute)\s*\(/gi,
|
|
434
|
+
/\.(upsert|merge|replace)\s*\(/gi,
|
|
435
|
+
/repository\.\w+\s*\(/gi,
|
|
436
|
+
/manager\.\w+\s*\(/gi
|
|
437
|
+
];
|
|
438
|
+
|
|
439
|
+
let count = 0;
|
|
440
|
+
dbOperationPatterns.forEach(pattern => {
|
|
441
|
+
const matches = functionBody.match(pattern);
|
|
442
|
+
if (matches) {
|
|
443
|
+
count += matches.length;
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
return count;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
isTransactionBased(functionBody) {
|
|
451
|
+
const transactionPatterns = [
|
|
452
|
+
/\.transaction\s*\(/gi,
|
|
453
|
+
/withTransaction/gi,
|
|
454
|
+
/runInTransaction/gi,
|
|
455
|
+
/beginTransaction/gi,
|
|
456
|
+
/startTransaction/gi,
|
|
457
|
+
/manager\.transaction/gi,
|
|
458
|
+
/queryRunner\.startTransaction/gi
|
|
459
|
+
];
|
|
460
|
+
|
|
461
|
+
return transactionPatterns.some(pattern => pattern.test(functionBody));
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
hasComplexReturnValue(functionBody) {
|
|
465
|
+
const complexReturnPatterns = [
|
|
466
|
+
/return\s+\w+\s*\([^)]*\)/, // Function calls
|
|
467
|
+
/return\s+new\s+\w+/, // Object creation
|
|
468
|
+
/return\s+\{[^}]+\}/, // Object literals
|
|
469
|
+
/return\s+\[[^\]]+\]/, // Array literals
|
|
470
|
+
/return\s+\w+\.\w+/ // Property access
|
|
471
|
+
];
|
|
472
|
+
|
|
473
|
+
return complexReturnPatterns.some(pattern => pattern.test(functionBody));
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
getSuggestion(functionName) {
|
|
477
|
+
return `Split '${functionName}' into separate command (modify state) and query (return data) functions`;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
module.exports = C012Analyzer;
|