@sun-asterisk/sunlint 1.3.30 → 1.3.32
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/core/cli-action-handler.js +32 -4
- package/package.json +1 -1
- package/rules/common/C006_function_naming/analyzer.js +289 -145
- package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js +335 -3599
- package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js.backup +3853 -0
- package/rules/security/S031_secure_session_cookies/analyzer.js +67 -112
- package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +0 -296
|
@@ -45,11 +45,92 @@ class SmartC006Analyzer {
|
|
|
45
45
|
// Side effect patterns indicate actions
|
|
46
46
|
ACTION: [/console\./, /fetch\(/, /\.send\(/, /\.post\(/]
|
|
47
47
|
};
|
|
48
|
+
|
|
49
|
+
// 📚 COMPREHENSIVE VERB LIST (from C006 rule specification)
|
|
50
|
+
this.acceptedVerbs = [
|
|
51
|
+
// Getters/Queries
|
|
52
|
+
'get', 'fetch', 'retrieve', 'find', 'search', 'query', 'load',
|
|
53
|
+
// Setters/Modifiers
|
|
54
|
+
'set', 'update', 'modify', 'change', 'edit', 'alter', 'transform',
|
|
55
|
+
// Creation
|
|
56
|
+
'create', 'build', 'make', 'generate', 'construct', 'produce',
|
|
57
|
+
// Deletion
|
|
58
|
+
'delete', 'remove', 'destroy', 'clean', 'clear', 'reset',
|
|
59
|
+
// Validation
|
|
60
|
+
'validate', 'verify', 'check', 'confirm', 'ensure', 'test', 'compare',
|
|
61
|
+
// Computation
|
|
62
|
+
'calculate', 'compute', 'parse', 'format', 'convert',
|
|
63
|
+
// Communication
|
|
64
|
+
'send', 'receive', 'transmit', 'broadcast', 'emit', 'publish',
|
|
65
|
+
// Collections
|
|
66
|
+
'map', 'filter', 'sort', 'group', 'merge', 'split', 'add', 'append', 'insert',
|
|
67
|
+
'push', 'pop', 'shift', 'splice', 'unshift', 'slice', 'concat', 'join',
|
|
68
|
+
// State checks (Boolean)
|
|
69
|
+
'is', 'has', 'can', 'should', 'will', 'does', 'contains', 'includes', 'exists',
|
|
70
|
+
// UI actions
|
|
71
|
+
'show', 'hide', 'display', 'render', 'draw', 'toggle', 'enable', 'disable',
|
|
72
|
+
'activate', 'deactivate', 'select', 'deselect', 'focus', 'blur',
|
|
73
|
+
// Lifecycle
|
|
74
|
+
'connect', 'disconnect', 'open', 'close', 'start', 'stop', 'run', 'refresh',
|
|
75
|
+
'initialize', 'init', 'setup', 'teardown', 'shutdown', 'restart', 'reload',
|
|
76
|
+
'restore', 'resume', 'pause', 'suspend',
|
|
77
|
+
// Event Handling
|
|
78
|
+
'on', 'trigger', 'fire', 'dispatch', 'invoke', 'call', 'emit', 'handle',
|
|
79
|
+
// Monitoring
|
|
80
|
+
'count', 'measure', 'monitor', 'watch', 'track', 'observe', 'log', 'record',
|
|
81
|
+
// Navigation
|
|
82
|
+
'navigate', 'redirect', 'route', 'go', 'move', 'scroll',
|
|
83
|
+
// Data Operations
|
|
84
|
+
'save', 'store', 'persist', 'cache', 'serialize', 'deserialize',
|
|
85
|
+
// Detection/Analysis
|
|
86
|
+
'detect', 'analyze', 'scan', 'inspect', 'evaluate', 'assess',
|
|
87
|
+
// Computation & Transformation
|
|
88
|
+
'calculate', 'compute', 'parse', 'format', 'convert',
|
|
89
|
+
'normalize', 'sanitize', 'encode', 'decode', 'encrypt', 'decrypt',
|
|
90
|
+
'compress', 'decompress', 'zip', 'unzip', 'tar', 'untar', // ← Add these
|
|
91
|
+
'stringify', 'objectify',
|
|
92
|
+
// State Management
|
|
93
|
+
'apply', 'revert', 'undo', 'redo', 'commit', 'rollback',
|
|
94
|
+
// Async Operations
|
|
95
|
+
'await', 'defer', 'debounce', 'throttle', 'delay',
|
|
96
|
+
// Error Handling
|
|
97
|
+
'throw', 'catch', 'handle', 'recover', 'retry',
|
|
98
|
+
// Copying/Cloning
|
|
99
|
+
'copy', 'clone', 'duplicate', 'replicate',
|
|
100
|
+
// Comparison
|
|
101
|
+
'equals', 'match', 'differ', 'compare',
|
|
102
|
+
// Registration
|
|
103
|
+
'register', 'unregister', 'subscribe', 'unsubscribe',
|
|
104
|
+
// Import/Export
|
|
105
|
+
'import', 'export', 'download', 'upload',
|
|
106
|
+
// Expansion/Collapse
|
|
107
|
+
'expand', 'collapse', 'maximize', 'minimize',
|
|
108
|
+
// Submission
|
|
109
|
+
'submit', 'cancel', 'abort', 'complete', 'finish',
|
|
110
|
+
// Preparation
|
|
111
|
+
'prepare', 'ready', 'preload', 'precache',
|
|
112
|
+
// Extraction
|
|
113
|
+
'extract', 'derive', 'obtain', 'acquire',
|
|
114
|
+
// Replacement
|
|
115
|
+
'replace', 'swap', 'substitute', 'override',
|
|
116
|
+
// Binding
|
|
117
|
+
'bind', 'unbind', 'attach', 'detach',
|
|
118
|
+
// Notification
|
|
119
|
+
'notify', 'alert', 'warn', 'inform',
|
|
120
|
+
// Execution
|
|
121
|
+
'execute', 'perform', 'process', 'run'
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
// ⚠️ VAGUE/GENERIC VERBS TO FLAG (should have more specific verbs)
|
|
125
|
+
this.vagueVerbs = [
|
|
126
|
+
'do', 'handle', 'manage', 'process', 'execute', 'perform',
|
|
127
|
+
'something', 'stuff', 'thing', 'work', 'data', 'item'
|
|
128
|
+
];
|
|
48
129
|
}
|
|
49
130
|
|
|
50
131
|
async analyze(files, language, config) {
|
|
51
132
|
const violations = [];
|
|
52
|
-
|
|
133
|
+
|
|
53
134
|
if (config.verbose) {
|
|
54
135
|
console.log(`🔧 [DEBUG] Starting Smart C006 Analysis on ${files.length} files...`);
|
|
55
136
|
}
|
|
@@ -69,7 +150,7 @@ class SmartC006Analyzer {
|
|
|
69
150
|
if (config.verbose) {
|
|
70
151
|
console.log(`🔧 [DEBUG] Smart C006 Analysis complete: ${violations.length} violations found`);
|
|
71
152
|
}
|
|
72
|
-
|
|
153
|
+
|
|
73
154
|
return violations;
|
|
74
155
|
}
|
|
75
156
|
|
|
@@ -80,10 +161,10 @@ class SmartC006Analyzer {
|
|
|
80
161
|
|
|
81
162
|
const violations = [];
|
|
82
163
|
const lines = content.split('\n');
|
|
83
|
-
|
|
164
|
+
|
|
84
165
|
// 🏗️ TIER 1: ARCHITECTURAL CONTEXT ANALYSIS
|
|
85
166
|
const architecturalContext = this.analyzeArchitecturalContext(filePath, content);
|
|
86
|
-
|
|
167
|
+
|
|
87
168
|
// Parse TypeScript/JavaScript code
|
|
88
169
|
const sourceFile = ts.createSourceFile(
|
|
89
170
|
filePath,
|
|
@@ -102,13 +183,13 @@ class SmartC006Analyzer {
|
|
|
102
183
|
architecturalContext,
|
|
103
184
|
content
|
|
104
185
|
);
|
|
105
|
-
|
|
186
|
+
|
|
106
187
|
if (analysis.isViolation && analysis.confidence >= this.confidenceThresholds.LOW) {
|
|
107
188
|
const namePosition = sourceFile.getLineAndCharacterOfPosition(node.name.getStart());
|
|
108
189
|
const line = namePosition.line + 1;
|
|
109
190
|
const column = namePosition.character + 1;
|
|
110
191
|
const lineText = lines[line - 1]?.trim() || '';
|
|
111
|
-
|
|
192
|
+
|
|
112
193
|
violations.push({
|
|
113
194
|
ruleId: this.ruleId,
|
|
114
195
|
file: filePath,
|
|
@@ -134,13 +215,13 @@ class SmartC006Analyzer {
|
|
|
134
215
|
architecturalContext,
|
|
135
216
|
content
|
|
136
217
|
);
|
|
137
|
-
|
|
218
|
+
|
|
138
219
|
if (analysis.isViolation && analysis.confidence >= this.confidenceThresholds.LOW) {
|
|
139
220
|
const namePosition = sourceFile.getLineAndCharacterOfPosition(node.name.getStart());
|
|
140
221
|
const line = namePosition.line + 1;
|
|
141
222
|
const column = namePosition.character + 1;
|
|
142
223
|
const lineText = lines[line - 1]?.trim() || '';
|
|
143
|
-
|
|
224
|
+
|
|
144
225
|
violations.push({
|
|
145
226
|
ruleId: this.ruleId,
|
|
146
227
|
file: filePath,
|
|
@@ -158,7 +239,7 @@ class SmartC006Analyzer {
|
|
|
158
239
|
}
|
|
159
240
|
|
|
160
241
|
// Analyze arrow functions assigned to variables
|
|
161
|
-
if (ts.isVariableDeclaration(node) && node.name && ts.isIdentifier(node.name) &&
|
|
242
|
+
if (ts.isVariableDeclaration(node) && node.name && ts.isIdentifier(node.name) &&
|
|
162
243
|
node.initializer && ts.isArrowFunction(node.initializer)) {
|
|
163
244
|
const analysis = this.smartAnalyzeFunctionName(
|
|
164
245
|
node.name.text,
|
|
@@ -167,13 +248,13 @@ class SmartC006Analyzer {
|
|
|
167
248
|
architecturalContext,
|
|
168
249
|
content
|
|
169
250
|
);
|
|
170
|
-
|
|
251
|
+
|
|
171
252
|
if (analysis.isViolation && analysis.confidence >= this.confidenceThresholds.LOW) {
|
|
172
253
|
const namePosition = sourceFile.getLineAndCharacterOfPosition(node.name.getStart());
|
|
173
254
|
const line = namePosition.line + 1;
|
|
174
255
|
const column = namePosition.character + 1;
|
|
175
256
|
const lineText = lines[line - 1]?.trim() || '';
|
|
176
|
-
|
|
257
|
+
|
|
177
258
|
violations.push({
|
|
178
259
|
ruleId: this.ruleId,
|
|
179
260
|
file: filePath,
|
|
@@ -189,7 +270,7 @@ class SmartC006Analyzer {
|
|
|
189
270
|
});
|
|
190
271
|
}
|
|
191
272
|
}
|
|
192
|
-
|
|
273
|
+
|
|
193
274
|
ts.forEachChild(node, visit);
|
|
194
275
|
};
|
|
195
276
|
|
|
@@ -204,7 +285,7 @@ class SmartC006Analyzer {
|
|
|
204
285
|
analyzeArchitecturalContext(filePath, content) {
|
|
205
286
|
const fileName = path.basename(filePath, path.extname(filePath)).toLowerCase();
|
|
206
287
|
const fileDir = path.dirname(filePath).toLowerCase();
|
|
207
|
-
|
|
288
|
+
|
|
208
289
|
// Detect architectural layer
|
|
209
290
|
let layer = 'UNKNOWN';
|
|
210
291
|
for (const [layerName, patterns] of Object.entries(this.architecturalLayers)) {
|
|
@@ -213,12 +294,12 @@ class SmartC006Analyzer {
|
|
|
213
294
|
break;
|
|
214
295
|
}
|
|
215
296
|
}
|
|
216
|
-
|
|
297
|
+
|
|
217
298
|
// Analyze imports for additional context
|
|
218
299
|
const imports = this.extractImports(content);
|
|
219
300
|
const isReactComponent = imports.some(imp => imp.includes('react')) || content.includes('JSX.Element');
|
|
220
301
|
const isTestFile = fileName.includes('test') || fileName.includes('spec');
|
|
221
|
-
|
|
302
|
+
|
|
222
303
|
return {
|
|
223
304
|
layer,
|
|
224
305
|
isReactComponent,
|
|
@@ -234,83 +315,96 @@ class SmartC006Analyzer {
|
|
|
234
315
|
*/
|
|
235
316
|
analyzeSemanticIntent(functionNode, sourceFile, content) {
|
|
236
317
|
if (!functionNode.body) return 'UNKNOWN';
|
|
237
|
-
|
|
318
|
+
|
|
238
319
|
const functionText = content.substring(
|
|
239
320
|
functionNode.body.getStart(),
|
|
240
321
|
functionNode.body.getEnd()
|
|
241
322
|
);
|
|
242
|
-
|
|
323
|
+
|
|
243
324
|
// Check for different semantic patterns
|
|
244
325
|
for (const [intent, patterns] of Object.entries(this.semanticPatterns)) {
|
|
245
326
|
if (patterns.some(pattern => pattern.test(functionText))) {
|
|
246
327
|
return intent;
|
|
247
328
|
}
|
|
248
329
|
}
|
|
249
|
-
|
|
330
|
+
|
|
250
331
|
return 'UNKNOWN';
|
|
251
332
|
}
|
|
252
333
|
|
|
253
334
|
/**
|
|
254
|
-
* 🎯
|
|
255
|
-
*
|
|
335
|
+
* 🎯 ENHANCED VERB DETECTION - FIXED VERSION
|
|
336
|
+
* Properly detects verbs including compound verbs
|
|
256
337
|
*/
|
|
257
|
-
|
|
258
|
-
//
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
'get', 'set', 'is', 'has', 'can', 'should', 'will', 'does',
|
|
275
|
-
'create', 'build', 'make', 'generate', 'construct', 'produce',
|
|
276
|
-
'update', 'modify', 'change', 'edit', 'alter', 'transform',
|
|
277
|
-
'delete', 'remove', 'destroy', 'clean', 'clear', 'reset',
|
|
278
|
-
'load', 'save', 'fetch', 'retrieve', 'find', 'search', 'query',
|
|
279
|
-
'validate', 'verify', 'check', 'confirm', 'ensure', 'test',
|
|
280
|
-
'calculate', 'compute', 'compare', 'parse', 'format', 'convert',
|
|
281
|
-
'send', 'receive', 'transmit', 'broadcast', 'emit', 'publish',
|
|
282
|
-
'map', 'filter', 'sort', 'group', 'merge', 'split',
|
|
283
|
-
'connect', 'disconnect', 'open', 'close', 'start', 'stop', 'run',
|
|
284
|
-
'show', 'hide', 'display', 'render', 'draw', 'paint', 'animate',
|
|
285
|
-
'add', 'append', 'insert', 'push', 'pop', 'shift', 'splice',
|
|
286
|
-
'count', 'measure', 'monitor', 'watch', 'track', 'observe',
|
|
287
|
-
'refresh', 'restore', 'reload', 'retry', 'resume', 'redirect',
|
|
288
|
-
'select', 'toggle', 'switch', 'enable', 'disable', 'activate',
|
|
289
|
-
'expand', 'collapse', 'scroll', 'navigate', 'submit', 'cancel',
|
|
290
|
-
'on', 'trigger', 'fire', 'dispatch', 'invoke', 'call'
|
|
291
|
-
];
|
|
292
|
-
|
|
293
|
-
// Strategy 2: Check if starts with known verb
|
|
294
|
-
const startsWithVerb = verbPrefixes.some(verb => {
|
|
295
|
-
const verbPattern = new RegExp(`^${verb}[A-Z]?`, 'i');
|
|
296
|
-
return verbPattern.test(functionName);
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
if (startsWithVerb) return true;
|
|
300
|
-
|
|
301
|
-
// Strategy 3: Common verb suffixes that indicate actions
|
|
302
|
-
const actionSuffixes = ['ize', 'ise', 'fy', 'ate', 'en'];
|
|
303
|
-
if (actionSuffixes.some(suffix => functionName.endsWith(suffix))) {
|
|
304
|
-
return true;
|
|
338
|
+
extractVerbFromName(functionName) {
|
|
339
|
+
// Convert to lowercase for comparison
|
|
340
|
+
const lowerName = functionName.toLowerCase();
|
|
341
|
+
|
|
342
|
+
// Find the longest matching verb from the accepted verbs list
|
|
343
|
+
let matchedVerb = null;
|
|
344
|
+
let maxLength = 0;
|
|
345
|
+
|
|
346
|
+
for (const verb of this.acceptedVerbs) {
|
|
347
|
+
// Check if function name starts with this verb
|
|
348
|
+
// Must be followed by uppercase letter, end of string, or be exact match
|
|
349
|
+
const verbPattern = new RegExp(`^${verb}([A-Z].*|$)`);
|
|
350
|
+
|
|
351
|
+
if (verbPattern.test(functionName) && verb.length > maxLength) {
|
|
352
|
+
matchedVerb = verb;
|
|
353
|
+
maxLength = verb.length;
|
|
354
|
+
}
|
|
305
355
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
356
|
+
|
|
357
|
+
return matchedVerb;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* 🎯 CHECK IF NAME FOLLOWS VERB-NOUN PATTERN
|
|
362
|
+
*/
|
|
363
|
+
isVerbNounPattern(functionName) {
|
|
364
|
+
// Strategy 1: Extract verb from accepted verbs list
|
|
365
|
+
const verb = this.extractVerbFromName(functionName);
|
|
366
|
+
|
|
367
|
+
if (verb) {
|
|
368
|
+
// Check if it's a vague verb (should be flagged even if technically a verb)
|
|
369
|
+
const isVagueVerb = this.vagueVerbs.some(vague => {
|
|
370
|
+
const vaguePattern = new RegExp(`^${vague}([A-Z].*|$)`, 'i');
|
|
371
|
+
return vaguePattern.test(functionName);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
if (isVagueVerb) {
|
|
375
|
+
return { isValid: false, verb: null, reason: 'vague_verb' };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Valid verb found
|
|
379
|
+
return { isValid: true, verb, reason: null };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Strategy 2: Check for verb-like patterns (morphology)
|
|
383
|
+
const verbSuffixes = ['ize', 'ise', 'fy', 'ate', 'en', 'ing', 'ed'];
|
|
384
|
+
for (const suffix of verbSuffixes) {
|
|
385
|
+
const suffixPattern = new RegExp(`^[a-z]+${suffix}([A-Z].*|$)`, 'i');
|
|
386
|
+
if (suffixPattern.test(functionName)) {
|
|
387
|
+
return { isValid: true, verb: 'morphological', reason: null };
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Strategy 3: Check for verb prefixes (re-, un-, pre-, de-, dis-)
|
|
392
|
+
const verbPrefixes = ['re', 'un', 'pre', 'de', 'dis', 'over', 'under', 'out', 'up'];
|
|
393
|
+
for (const prefix of verbPrefixes) {
|
|
394
|
+
// Must be followed by a known verb or capital letter
|
|
395
|
+
const prefixPattern = new RegExp(`^${prefix}[A-Z]`, 'i');
|
|
396
|
+
if (prefixPattern.test(functionName)) {
|
|
397
|
+
// Check if the part after prefix is a known verb
|
|
398
|
+
const remainder = functionName.substring(prefix.length);
|
|
399
|
+
const remainderVerb = this.extractVerbFromName(remainder);
|
|
400
|
+
if (remainderVerb) {
|
|
401
|
+
return { isValid: true, verb: `${prefix}${remainderVerb}`, reason: null };
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// No verb pattern found
|
|
407
|
+
return { isValid: false, verb: null, reason: 'no_verb' };
|
|
314
408
|
}
|
|
315
409
|
|
|
316
410
|
/**
|
|
@@ -323,29 +417,29 @@ class SmartC006Analyzer {
|
|
|
323
417
|
requiredPatterns: [],
|
|
324
418
|
suggestions: []
|
|
325
419
|
};
|
|
326
|
-
|
|
420
|
+
|
|
327
421
|
// React components have different naming conventions
|
|
328
422
|
if (architecturalContext.isReactComponent) {
|
|
329
423
|
rules.allowedPatterns.push(/^[A-Z][a-zA-Z]*$/); // PascalCase components
|
|
330
424
|
rules.allowedPatterns.push(/^use[A-Z][a-zA-Z]*$/); // React hooks
|
|
331
425
|
rules.allowedPatterns.push(/^handle[A-Z][a-zA-Z]*$/); // Event handlers
|
|
332
426
|
}
|
|
333
|
-
|
|
427
|
+
|
|
334
428
|
// Test files have different patterns
|
|
335
429
|
if (architecturalContext.isTestFile) {
|
|
336
430
|
rules.allowedPatterns.push(/^(test|it|describe|should|expect)[A-Z]?/);
|
|
337
431
|
}
|
|
338
|
-
|
|
432
|
+
|
|
339
433
|
// Data layer functions often have CRUD patterns
|
|
340
434
|
if (architecturalContext.layer === 'DATA') {
|
|
341
|
-
rules.suggestions.push('Consider CRUD verbs: create, read, update, delete');
|
|
435
|
+
rules.suggestions.push('Consider CRUD verbs: create, read, update, delete, fetch, save');
|
|
342
436
|
}
|
|
343
|
-
|
|
437
|
+
|
|
344
438
|
// UI layer functions often have interaction verbs
|
|
345
439
|
if (architecturalContext.layer === 'UI') {
|
|
346
|
-
rules.suggestions.push('Consider UI verbs: show, hide, toggle, render, display');
|
|
440
|
+
rules.suggestions.push('Consider UI verbs: show, hide, toggle, render, display, handle');
|
|
347
441
|
}
|
|
348
|
-
|
|
442
|
+
|
|
349
443
|
return rules;
|
|
350
444
|
}
|
|
351
445
|
|
|
@@ -358,88 +452,103 @@ class SmartC006Analyzer {
|
|
|
358
452
|
if (this.isSpecialFunction(functionName, architecturalContext)) {
|
|
359
453
|
return { isViolation: false };
|
|
360
454
|
}
|
|
361
|
-
|
|
455
|
+
|
|
362
456
|
// Get semantic intent
|
|
363
457
|
const semanticIntent = this.analyzeSemanticIntent(functionNode, sourceFile, content);
|
|
364
|
-
|
|
458
|
+
|
|
365
459
|
// Get context-specific rules
|
|
366
460
|
const contextRules = this.getContextSpecificRules(architecturalContext, semanticIntent);
|
|
367
|
-
|
|
461
|
+
|
|
368
462
|
// Check if allowed by context-specific patterns
|
|
369
463
|
if (contextRules.allowedPatterns.some(pattern => pattern.test(functionName))) {
|
|
370
464
|
return { isViolation: false };
|
|
371
465
|
}
|
|
372
|
-
|
|
466
|
+
|
|
373
467
|
// Check if name follows verb-noun pattern
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
if (
|
|
468
|
+
const verbCheck = this.isVerbNounPattern(functionName);
|
|
469
|
+
|
|
470
|
+
if (verbCheck.isValid) {
|
|
377
471
|
return { isViolation: false };
|
|
378
472
|
}
|
|
379
|
-
|
|
473
|
+
|
|
380
474
|
// 🧮 CONFIDENCE CALCULATION
|
|
381
475
|
let confidence = 0.5; // Base confidence
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
476
|
+
let violationType = 'no_verb';
|
|
477
|
+
|
|
478
|
+
if (verbCheck.reason === 'vague_verb') {
|
|
479
|
+
violationType = 'vague_verb';
|
|
480
|
+
confidence = 0.9; // High confidence for vague verbs
|
|
481
|
+
} else if (verbCheck.reason === 'no_verb') {
|
|
482
|
+
violationType = 'no_verb';
|
|
483
|
+
|
|
484
|
+
// Check for obviously wrong patterns
|
|
485
|
+
const commonNounPatterns = [
|
|
486
|
+
/^(user|data|info|item|list|config|settings|options|params|args|props|state|value|result|response|request|error|message|event|callback|handler)([A-Z][a-z]*)*$/i,
|
|
487
|
+
/^[a-z]+$/, // Simple lowercase words
|
|
488
|
+
/^[a-z]+[A-Z][a-z]+$/ // Simple camelCase nouns
|
|
489
|
+
];
|
|
490
|
+
|
|
491
|
+
if (commonNounPatterns.some(pattern => pattern.test(functionName))) {
|
|
492
|
+
confidence = 0.8; // High confidence for obvious nouns
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Boost confidence for clearly generic patterns
|
|
496
|
+
const vagueFunctionNames = [
|
|
497
|
+
'something', 'stuff', 'thing', 'data', 'info', 'item',
|
|
498
|
+
'work', 'task', 'job', 'action', 'operation'
|
|
499
|
+
];
|
|
500
|
+
|
|
501
|
+
if (vagueFunctionNames.some(vague => functionName.toLowerCase().includes(vague.toLowerCase()))) {
|
|
502
|
+
confidence = 0.95; // Very high confidence
|
|
503
|
+
}
|
|
401
504
|
}
|
|
402
|
-
|
|
403
|
-
// Reduce confidence for complex names (might have hidden
|
|
404
|
-
if (functionName.length >
|
|
405
|
-
confidence -= 0.
|
|
505
|
+
|
|
506
|
+
// Reduce confidence for complex names (might have hidden patterns)
|
|
507
|
+
if (functionName.length > 20) {
|
|
508
|
+
confidence -= 0.15;
|
|
406
509
|
}
|
|
407
|
-
|
|
510
|
+
|
|
408
511
|
// Reduce confidence for utils/helpers (more flexible naming)
|
|
409
512
|
if (architecturalContext.layer === 'UTILS') {
|
|
410
513
|
confidence -= 0.2;
|
|
411
514
|
}
|
|
412
|
-
|
|
515
|
+
|
|
413
516
|
// Reduce confidence for test files
|
|
414
517
|
if (architecturalContext.isTestFile) {
|
|
415
518
|
confidence -= 0.3;
|
|
416
519
|
}
|
|
417
|
-
|
|
520
|
+
|
|
418
521
|
// Cap confidence
|
|
419
522
|
confidence = Math.min(Math.max(confidence, 0.1), 1.0);
|
|
420
|
-
|
|
523
|
+
|
|
421
524
|
// 💬 INTELLIGENT MESSAGING
|
|
422
525
|
const context = {
|
|
423
526
|
layer: architecturalContext.layer,
|
|
424
527
|
intent: semanticIntent,
|
|
425
528
|
isReactComponent: architecturalContext.isReactComponent,
|
|
426
|
-
isTestFile: architecturalContext.isTestFile
|
|
529
|
+
isTestFile: architecturalContext.isTestFile,
|
|
530
|
+
violationType
|
|
427
531
|
};
|
|
428
|
-
|
|
429
|
-
let reason =
|
|
430
|
-
let suggestion =
|
|
532
|
+
|
|
533
|
+
let reason = '';
|
|
534
|
+
let suggestion = '';
|
|
535
|
+
|
|
536
|
+
if (violationType === 'vague_verb') {
|
|
537
|
+
reason = `Function '${functionName}' uses vague verb. Use more specific action verbs (see C006 accepted verbs list)`;
|
|
538
|
+
suggestion = this.generateSmartSuggestion(functionName, semanticIntent, architecturalContext, true);
|
|
539
|
+
} else {
|
|
540
|
+
reason = `Function '${functionName}' must start with a verb (verb-noun pattern required by C006)`;
|
|
541
|
+
suggestion = this.generateSmartSuggestion(functionName, semanticIntent, architecturalContext, false);
|
|
542
|
+
}
|
|
431
543
|
|
|
432
544
|
if (architecturalContext.layer !== 'UNKNOWN') {
|
|
433
545
|
reason += ` (${architecturalContext.layer} layer)`;
|
|
434
546
|
}
|
|
435
547
|
|
|
436
|
-
// Add helpful note about accepted verbs
|
|
437
|
-
reason += `. Accepted verbs: get, set, create, update, delete, validate, check, compare, etc. See: https://coding-standards.sun-asterisk.vn/rules/rule/C006/`;
|
|
438
|
-
|
|
439
548
|
return {
|
|
440
549
|
isViolation: true,
|
|
441
550
|
reason,
|
|
442
|
-
type:
|
|
551
|
+
type: `smart_naming_violation_${violationType}`,
|
|
443
552
|
confidence,
|
|
444
553
|
suggestion,
|
|
445
554
|
context
|
|
@@ -449,27 +558,51 @@ class SmartC006Analyzer {
|
|
|
449
558
|
/**
|
|
450
559
|
* 💡 SMART SUGGESTION GENERATOR
|
|
451
560
|
*/
|
|
452
|
-
generateSmartSuggestion(functionName, semanticIntent, architecturalContext) {
|
|
453
|
-
|
|
454
|
-
|
|
561
|
+
generateSmartSuggestion(functionName, semanticIntent, architecturalContext, isVagueVerb) {
|
|
562
|
+
// If it's a vague verb, extract the noun part
|
|
563
|
+
let baseNoun = functionName;
|
|
564
|
+
|
|
565
|
+
if (isVagueVerb) {
|
|
566
|
+
// Try to extract the noun part after the vague verb
|
|
567
|
+
for (const vagueVerb of this.vagueVerbs) {
|
|
568
|
+
const pattern = new RegExp(`^${vagueVerb}([A-Z].*)$`, 'i');
|
|
569
|
+
const match = functionName.match(pattern);
|
|
570
|
+
if (match) {
|
|
571
|
+
baseNoun = match[1];
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Ensure first letter is capitalized for camelCase
|
|
578
|
+
baseNoun = baseNoun.charAt(0).toUpperCase() + baseNoun.slice(1);
|
|
579
|
+
|
|
580
|
+
const suggestions = [];
|
|
581
|
+
|
|
455
582
|
switch (semanticIntent) {
|
|
456
583
|
case 'GETTER':
|
|
457
|
-
|
|
584
|
+
suggestions.push(`get${baseNoun}()`, `fetch${baseNoun}()`, `retrieve${baseNoun}()`);
|
|
585
|
+
break;
|
|
458
586
|
case 'SETTER':
|
|
459
|
-
|
|
587
|
+
suggestions.push(`set${baseNoun}()`, `update${baseNoun}()`, `save${baseNoun}()`);
|
|
588
|
+
break;
|
|
460
589
|
case 'CHECKER':
|
|
461
|
-
|
|
590
|
+
suggestions.push(`is${baseNoun}()`, `has${baseNoun}()`, `can${baseNoun}()`, `should${baseNoun}()`);
|
|
591
|
+
break;
|
|
462
592
|
case 'ACTION':
|
|
463
|
-
|
|
593
|
+
suggestions.push(`process${baseNoun}()`, `handle${baseNoun}()`, `execute${baseNoun}()`);
|
|
594
|
+
break;
|
|
464
595
|
default:
|
|
465
596
|
if (architecturalContext.layer === 'DATA') {
|
|
466
|
-
|
|
597
|
+
suggestions.push(`fetch${baseNoun}()`, `create${baseNoun}()`, `save${baseNoun}()`, `load${baseNoun}()`);
|
|
598
|
+
} else if (architecturalContext.layer === 'UI') {
|
|
599
|
+
suggestions.push(`render${baseNoun}()`, `show${baseNoun}()`, `display${baseNoun}()`, `draw${baseNoun}()`);
|
|
600
|
+
} else {
|
|
601
|
+
suggestions.push(`get${baseNoun}()`, `set${baseNoun}()`, `process${baseNoun}()`, `handle${baseNoun}()`);
|
|
467
602
|
}
|
|
468
|
-
if (architecturalContext.layer === 'UI') {
|
|
469
|
-
return `render${baseNoun}() or show${baseNoun}()`;
|
|
470
|
-
}
|
|
471
|
-
return `get${baseNoun}() or process${baseNoun}()`;
|
|
472
603
|
}
|
|
604
|
+
|
|
605
|
+
return suggestions.slice(0, 3).join(' or ');
|
|
473
606
|
}
|
|
474
607
|
|
|
475
608
|
/**
|
|
@@ -482,29 +615,40 @@ class SmartC006Analyzer {
|
|
|
482
615
|
'onCreate', 'onDestroy', 'onStart', 'onStop',
|
|
483
616
|
'onPause', 'onResume', 'onSaveInstanceState',
|
|
484
617
|
'equals', 'hashCode', 'compareTo', 'clone',
|
|
485
|
-
'finalize', 'notify', 'notifyAll', 'wait'
|
|
618
|
+
'finalize', 'notify', 'notifyAll', 'wait', 'bootstrap',
|
|
486
619
|
];
|
|
487
620
|
|
|
621
|
+
// ignore by patterns
|
|
622
|
+
const patternsToIgnore = [
|
|
623
|
+
/^(setup|config|bootstrap).*/,
|
|
624
|
+
/^_.*/,
|
|
625
|
+
/Factory$/
|
|
626
|
+
];
|
|
627
|
+
|
|
628
|
+
if (patternsToIgnore.some(pattern => pattern.test(name))) {
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
631
|
+
|
|
488
632
|
// Basic special function check
|
|
489
633
|
if (specialFunctions.includes(name) || name.startsWith('_') || name.startsWith('$')) {
|
|
490
634
|
return true;
|
|
491
635
|
}
|
|
492
|
-
|
|
636
|
+
|
|
493
637
|
// React component names (PascalCase)
|
|
494
638
|
if (architecturalContext.isReactComponent && /^[A-Z][a-zA-Z]*$/.test(name)) {
|
|
495
639
|
return true;
|
|
496
640
|
}
|
|
497
|
-
|
|
641
|
+
|
|
498
642
|
// React hooks
|
|
499
643
|
if (name.startsWith('use') && /^use[A-Z]/.test(name)) {
|
|
500
644
|
return true;
|
|
501
645
|
}
|
|
502
|
-
|
|
646
|
+
|
|
503
647
|
// Test function names
|
|
504
648
|
if (architecturalContext.isTestFile && /^(test|it|describe|should|expect)/.test(name)) {
|
|
505
649
|
return true;
|
|
506
650
|
}
|
|
507
|
-
|
|
651
|
+
|
|
508
652
|
return false;
|
|
509
653
|
}
|
|
510
654
|
|
|
@@ -515,11 +659,11 @@ class SmartC006Analyzer {
|
|
|
515
659
|
const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
|
|
516
660
|
const imports = [];
|
|
517
661
|
let match;
|
|
518
|
-
|
|
662
|
+
|
|
519
663
|
while ((match = importRegex.exec(content)) !== null) {
|
|
520
664
|
imports.push(match[1]);
|
|
521
665
|
}
|
|
522
|
-
|
|
666
|
+
|
|
523
667
|
return imports;
|
|
524
668
|
}
|
|
525
669
|
|