@aiready/consistency 0.4.1 → 0.6.0
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/.turbo/turbo-build.log +10 -10
- package/.turbo/turbo-test.log +49 -7
- package/PHASE5-RESULTS.md +277 -0
- package/dist/chunk-HAOJLJNB.mjs +1290 -0
- package/dist/chunk-IVRBV7SE.mjs +1295 -0
- package/dist/chunk-TXHPUU7A.mjs +863 -0
- package/dist/chunk-VODCPPET.mjs +1292 -0
- package/dist/chunk-WGH4TGZ3.mjs +1288 -0
- package/dist/cli.js +632 -184
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +1204 -177
- package/dist/index.mjs +581 -4
- package/package.json +3 -1
- package/src/analyzer.ts +4 -4
- package/src/analyzers/naming-ast.ts +375 -0
- package/src/analyzers/naming.ts +12 -1
- package/src/index.ts +2 -1
- package/src/utils/ast-parser.ts +181 -0
- package/src/utils/context-detector.ts +278 -0
- package/src/utils/scope-tracker.ts +221 -0
package/dist/cli.js
CHANGED
|
@@ -29,11 +29,436 @@ var import_commander = require("commander");
|
|
|
29
29
|
// src/analyzer.ts
|
|
30
30
|
var import_core3 = require("@aiready/core");
|
|
31
31
|
|
|
32
|
-
// src/analyzers/naming.ts
|
|
32
|
+
// src/analyzers/naming-ast.ts
|
|
33
33
|
var import_core = require("@aiready/core");
|
|
34
34
|
var import_path = require("path");
|
|
35
|
+
|
|
36
|
+
// src/utils/ast-parser.ts
|
|
37
|
+
var import_typescript_estree = require("@typescript-eslint/typescript-estree");
|
|
38
|
+
var import_fs = require("fs");
|
|
39
|
+
function parseFile(filePath, content) {
|
|
40
|
+
try {
|
|
41
|
+
const code = content ?? (0, import_fs.readFileSync)(filePath, "utf-8");
|
|
42
|
+
const isTypeScript = filePath.match(/\.tsx?$/);
|
|
43
|
+
return (0, import_typescript_estree.parse)(code, {
|
|
44
|
+
jsx: filePath.match(/\.[jt]sx$/i) !== null,
|
|
45
|
+
loc: true,
|
|
46
|
+
range: true,
|
|
47
|
+
comment: false,
|
|
48
|
+
tokens: false,
|
|
49
|
+
// Relaxed parsing for JavaScript files
|
|
50
|
+
sourceType: "module",
|
|
51
|
+
ecmaVersion: "latest",
|
|
52
|
+
// Only use TypeScript parser features for .ts/.tsx files
|
|
53
|
+
filePath: isTypeScript ? filePath : void 0
|
|
54
|
+
});
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.warn(`Failed to parse ${filePath}:`, error instanceof Error ? error.message : error);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function traverseAST(node, visitor, parent = null) {
|
|
61
|
+
if (!node) return;
|
|
62
|
+
visitor.enter?.(node, parent);
|
|
63
|
+
for (const key of Object.keys(node)) {
|
|
64
|
+
const value = node[key];
|
|
65
|
+
if (Array.isArray(value)) {
|
|
66
|
+
for (const child of value) {
|
|
67
|
+
if (child && typeof child === "object" && "type" in child) {
|
|
68
|
+
traverseAST(child, visitor, node);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} else if (value && typeof value === "object" && "type" in value) {
|
|
72
|
+
traverseAST(value, visitor, node);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
visitor.leave?.(node, parent);
|
|
76
|
+
}
|
|
77
|
+
function isLoopStatement(node) {
|
|
78
|
+
return [
|
|
79
|
+
"ForStatement",
|
|
80
|
+
"ForInStatement",
|
|
81
|
+
"ForOfStatement",
|
|
82
|
+
"WhileStatement",
|
|
83
|
+
"DoWhileStatement"
|
|
84
|
+
].includes(node.type);
|
|
85
|
+
}
|
|
86
|
+
function getFunctionName(node) {
|
|
87
|
+
switch (node.type) {
|
|
88
|
+
case "FunctionDeclaration":
|
|
89
|
+
return node.id?.name ?? null;
|
|
90
|
+
case "FunctionExpression":
|
|
91
|
+
return node.id?.name ?? null;
|
|
92
|
+
case "ArrowFunctionExpression":
|
|
93
|
+
return null;
|
|
94
|
+
// Arrow functions don't have names directly
|
|
95
|
+
case "MethodDefinition":
|
|
96
|
+
if (node.key.type === "Identifier") {
|
|
97
|
+
return node.key.name;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
default:
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function getLineNumber(node) {
|
|
105
|
+
return node.loc?.start.line ?? 0;
|
|
106
|
+
}
|
|
107
|
+
function isCoverageContext(node, ancestors) {
|
|
108
|
+
const coveragePatterns = /coverage|summary|metrics|pct|percent|statements|branches|functions|lines/i;
|
|
109
|
+
if (node.type === "Identifier" && coveragePatterns.test(node.name)) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
for (const ancestor of ancestors.slice(-3)) {
|
|
113
|
+
if (ancestor.type === "MemberExpression") {
|
|
114
|
+
const memberExpr = ancestor;
|
|
115
|
+
if (memberExpr.object.type === "Identifier" && coveragePatterns.test(memberExpr.object.name)) {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (ancestor.type === "ObjectPattern" || ancestor.type === "ObjectExpression") {
|
|
120
|
+
const parent = ancestors[ancestors.indexOf(ancestor) - 1];
|
|
121
|
+
if (parent?.type === "VariableDeclarator") {
|
|
122
|
+
const varDecl = parent;
|
|
123
|
+
if (varDecl.id.type === "Identifier" && coveragePatterns.test(varDecl.id.name)) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/utils/scope-tracker.ts
|
|
133
|
+
var ScopeTracker = class {
|
|
134
|
+
constructor(rootNode) {
|
|
135
|
+
this.allScopes = [];
|
|
136
|
+
this.rootScope = {
|
|
137
|
+
type: "global",
|
|
138
|
+
node: rootNode,
|
|
139
|
+
parent: null,
|
|
140
|
+
children: [],
|
|
141
|
+
variables: /* @__PURE__ */ new Map()
|
|
142
|
+
};
|
|
143
|
+
this.currentScope = this.rootScope;
|
|
144
|
+
this.allScopes.push(this.rootScope);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Enter a new scope
|
|
148
|
+
*/
|
|
149
|
+
enterScope(type, node) {
|
|
150
|
+
const newScope = {
|
|
151
|
+
type,
|
|
152
|
+
node,
|
|
153
|
+
parent: this.currentScope,
|
|
154
|
+
children: [],
|
|
155
|
+
variables: /* @__PURE__ */ new Map()
|
|
156
|
+
};
|
|
157
|
+
this.currentScope.children.push(newScope);
|
|
158
|
+
this.currentScope = newScope;
|
|
159
|
+
this.allScopes.push(newScope);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Exit current scope and return to parent
|
|
163
|
+
*/
|
|
164
|
+
exitScope() {
|
|
165
|
+
if (this.currentScope.parent) {
|
|
166
|
+
this.currentScope = this.currentScope.parent;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Declare a variable in the current scope
|
|
171
|
+
*/
|
|
172
|
+
declareVariable(name, node, line, options = {}) {
|
|
173
|
+
const varInfo = {
|
|
174
|
+
name,
|
|
175
|
+
node,
|
|
176
|
+
declarationLine: line,
|
|
177
|
+
references: [],
|
|
178
|
+
type: options.type,
|
|
179
|
+
isParameter: options.isParameter ?? false,
|
|
180
|
+
isDestructured: options.isDestructured ?? false,
|
|
181
|
+
isLoopVariable: options.isLoopVariable ?? false
|
|
182
|
+
};
|
|
183
|
+
this.currentScope.variables.set(name, varInfo);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Add a reference to a variable
|
|
187
|
+
*/
|
|
188
|
+
addReference(name, node) {
|
|
189
|
+
const varInfo = this.findVariable(name);
|
|
190
|
+
if (varInfo) {
|
|
191
|
+
varInfo.references.push(node);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Find a variable in current or parent scopes
|
|
196
|
+
*/
|
|
197
|
+
findVariable(name) {
|
|
198
|
+
let scope = this.currentScope;
|
|
199
|
+
while (scope) {
|
|
200
|
+
const varInfo = scope.variables.get(name);
|
|
201
|
+
if (varInfo) {
|
|
202
|
+
return varInfo;
|
|
203
|
+
}
|
|
204
|
+
scope = scope.parent;
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Get all variables in current scope (not including parent scopes)
|
|
210
|
+
*/
|
|
211
|
+
getCurrentScopeVariables() {
|
|
212
|
+
return Array.from(this.currentScope.variables.values());
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get all variables across all scopes
|
|
216
|
+
*/
|
|
217
|
+
getAllVariables() {
|
|
218
|
+
const allVars = [];
|
|
219
|
+
for (const scope of this.allScopes) {
|
|
220
|
+
allVars.push(...Array.from(scope.variables.values()));
|
|
221
|
+
}
|
|
222
|
+
return allVars;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Calculate actual usage count (references minus declaration)
|
|
226
|
+
*/
|
|
227
|
+
getUsageCount(varInfo) {
|
|
228
|
+
return varInfo.references.length;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Check if a variable is short-lived (used within N lines)
|
|
232
|
+
*/
|
|
233
|
+
isShortLived(varInfo, maxLines = 5) {
|
|
234
|
+
if (varInfo.references.length === 0) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
const declarationLine = varInfo.declarationLine;
|
|
238
|
+
const maxUsageLine = Math.max(
|
|
239
|
+
...varInfo.references.map((ref) => ref.loc?.start.line ?? declarationLine)
|
|
240
|
+
);
|
|
241
|
+
return maxUsageLine - declarationLine <= maxLines;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Check if a variable is used in a limited scope (e.g., only in one callback)
|
|
245
|
+
*/
|
|
246
|
+
isLocallyScoped(varInfo) {
|
|
247
|
+
if (varInfo.references.length === 0) return false;
|
|
248
|
+
const lines = varInfo.references.map((ref) => ref.loc?.start.line ?? 0);
|
|
249
|
+
const minLine = Math.min(...lines);
|
|
250
|
+
const maxLine = Math.max(...lines);
|
|
251
|
+
return maxLine - minLine <= 3;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Get current scope type
|
|
255
|
+
*/
|
|
256
|
+
getCurrentScopeType() {
|
|
257
|
+
return this.currentScope.type;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Check if currently in a loop scope
|
|
261
|
+
*/
|
|
262
|
+
isInLoop() {
|
|
263
|
+
let scope = this.currentScope;
|
|
264
|
+
while (scope) {
|
|
265
|
+
if (scope.type === "loop") {
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
scope = scope.parent;
|
|
269
|
+
}
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Check if currently in a function scope
|
|
274
|
+
*/
|
|
275
|
+
isInFunction() {
|
|
276
|
+
let scope = this.currentScope;
|
|
277
|
+
while (scope) {
|
|
278
|
+
if (scope.type === "function") {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
scope = scope.parent;
|
|
282
|
+
}
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Get the root scope
|
|
287
|
+
*/
|
|
288
|
+
getRootScope() {
|
|
289
|
+
return this.rootScope;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// src/utils/context-detector.ts
|
|
294
|
+
function detectFileType(filePath, ast) {
|
|
295
|
+
const path = filePath.toLowerCase();
|
|
296
|
+
if (path.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/) || path.includes("__tests__")) {
|
|
297
|
+
return "test";
|
|
298
|
+
}
|
|
299
|
+
if (path.endsWith(".d.ts") || path.includes("types")) {
|
|
300
|
+
return "types";
|
|
301
|
+
}
|
|
302
|
+
if (path.match(/config|\.config\.|rc\.|setup/) || path.includes("configuration")) {
|
|
303
|
+
return "config";
|
|
304
|
+
}
|
|
305
|
+
return "production";
|
|
306
|
+
}
|
|
307
|
+
function detectCodeLayer(ast) {
|
|
308
|
+
let hasAPIIndicators = 0;
|
|
309
|
+
let hasBusinessIndicators = 0;
|
|
310
|
+
let hasDataIndicators = 0;
|
|
311
|
+
let hasUtilityIndicators = 0;
|
|
312
|
+
traverseAST(ast, {
|
|
313
|
+
enter: (node) => {
|
|
314
|
+
if (node.type === "ImportDeclaration") {
|
|
315
|
+
const source = node.source.value;
|
|
316
|
+
if (source.match(/express|fastify|koa|@nestjs|axios|fetch|http/i)) {
|
|
317
|
+
hasAPIIndicators++;
|
|
318
|
+
}
|
|
319
|
+
if (source.match(/database|prisma|typeorm|sequelize|mongoose|pg|mysql/i)) {
|
|
320
|
+
hasDataIndicators++;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (node.type === "FunctionDeclaration" && node.id) {
|
|
324
|
+
const name = node.id.name;
|
|
325
|
+
if (name.match(/^(get|post|put|delete|patch|handle|api|route|controller)/i)) {
|
|
326
|
+
hasAPIIndicators++;
|
|
327
|
+
}
|
|
328
|
+
if (name.match(/^(calculate|process|validate|transform|compute|analyze)/i)) {
|
|
329
|
+
hasBusinessIndicators++;
|
|
330
|
+
}
|
|
331
|
+
if (name.match(/^(find|create|update|delete|save|fetch|query|insert)/i)) {
|
|
332
|
+
hasDataIndicators++;
|
|
333
|
+
}
|
|
334
|
+
if (name.match(/^(format|parse|convert|normalize|sanitize|encode|decode)/i)) {
|
|
335
|
+
hasUtilityIndicators++;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") {
|
|
339
|
+
if (node.type === "ExportNamedDeclaration" && node.declaration) {
|
|
340
|
+
if (node.declaration.type === "FunctionDeclaration" && node.declaration.id) {
|
|
341
|
+
const name = node.declaration.id.name;
|
|
342
|
+
if (name.match(/handler|route|api|controller/i)) {
|
|
343
|
+
hasAPIIndicators += 2;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
const scores = {
|
|
351
|
+
api: hasAPIIndicators,
|
|
352
|
+
business: hasBusinessIndicators,
|
|
353
|
+
data: hasDataIndicators,
|
|
354
|
+
utility: hasUtilityIndicators
|
|
355
|
+
};
|
|
356
|
+
const maxScore = Math.max(...Object.values(scores));
|
|
357
|
+
if (maxScore === 0) {
|
|
358
|
+
return "unknown";
|
|
359
|
+
}
|
|
360
|
+
if (scores.api === maxScore) return "api";
|
|
361
|
+
if (scores.data === maxScore) return "data";
|
|
362
|
+
if (scores.business === maxScore) return "business";
|
|
363
|
+
if (scores.utility === maxScore) return "utility";
|
|
364
|
+
return "unknown";
|
|
365
|
+
}
|
|
366
|
+
function calculateComplexity(node) {
|
|
367
|
+
let complexity = 1;
|
|
368
|
+
traverseAST(node, {
|
|
369
|
+
enter: (childNode) => {
|
|
370
|
+
switch (childNode.type) {
|
|
371
|
+
case "IfStatement":
|
|
372
|
+
case "ConditionalExpression":
|
|
373
|
+
// ternary
|
|
374
|
+
case "SwitchCase":
|
|
375
|
+
case "ForStatement":
|
|
376
|
+
case "ForInStatement":
|
|
377
|
+
case "ForOfStatement":
|
|
378
|
+
case "WhileStatement":
|
|
379
|
+
case "DoWhileStatement":
|
|
380
|
+
case "CatchClause":
|
|
381
|
+
complexity++;
|
|
382
|
+
break;
|
|
383
|
+
case "LogicalExpression":
|
|
384
|
+
if (childNode.operator === "&&" || childNode.operator === "||") {
|
|
385
|
+
complexity++;
|
|
386
|
+
}
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
return complexity;
|
|
392
|
+
}
|
|
393
|
+
function buildCodeContext(filePath, ast) {
|
|
394
|
+
const fileType = detectFileType(filePath, ast);
|
|
395
|
+
const codeLayer = detectCodeLayer(ast);
|
|
396
|
+
let totalComplexity = 0;
|
|
397
|
+
let functionCount = 0;
|
|
398
|
+
traverseAST(ast, {
|
|
399
|
+
enter: (node) => {
|
|
400
|
+
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
|
|
401
|
+
totalComplexity += calculateComplexity(node);
|
|
402
|
+
functionCount++;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
const avgComplexity = functionCount > 0 ? totalComplexity / functionCount : 1;
|
|
407
|
+
return {
|
|
408
|
+
fileType,
|
|
409
|
+
codeLayer,
|
|
410
|
+
complexity: Math.round(avgComplexity),
|
|
411
|
+
isTestFile: fileType === "test",
|
|
412
|
+
isTypeDefinition: fileType === "types"
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
function adjustSeverity(baseSeverity, context, issueType) {
|
|
416
|
+
if (context.isTestFile) {
|
|
417
|
+
if (baseSeverity === "minor") return "info";
|
|
418
|
+
if (baseSeverity === "major") return "minor";
|
|
419
|
+
}
|
|
420
|
+
if (context.isTypeDefinition) {
|
|
421
|
+
if (baseSeverity === "minor") return "info";
|
|
422
|
+
}
|
|
423
|
+
if (context.codeLayer === "api") {
|
|
424
|
+
if (baseSeverity === "info" && issueType === "unclear") return "minor";
|
|
425
|
+
if (baseSeverity === "minor" && issueType === "unclear") return "major";
|
|
426
|
+
}
|
|
427
|
+
if (context.complexity > 10) {
|
|
428
|
+
if (baseSeverity === "info") return "minor";
|
|
429
|
+
}
|
|
430
|
+
if (context.codeLayer === "utility") {
|
|
431
|
+
if (baseSeverity === "minor" && issueType === "abbreviation") return "info";
|
|
432
|
+
}
|
|
433
|
+
return baseSeverity;
|
|
434
|
+
}
|
|
435
|
+
function isAcceptableInContext(name, context, options) {
|
|
436
|
+
if (options.isLoopVariable && ["i", "j", "k", "l", "n", "m"].includes(name)) {
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
if (context.isTestFile) {
|
|
440
|
+
if (["a", "b", "c", "x", "y", "z"].includes(name) && options.isParameter) {
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (context.codeLayer === "utility" && ["x", "y", "z"].includes(name)) {
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
if (options.isDestructured) {
|
|
448
|
+
if (["s", "b", "f", "l"].includes(name)) {
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (options.isParameter && (options.complexity ?? context.complexity) < 3) {
|
|
453
|
+
if (name.length >= 2) {
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/analyzers/naming-ast.ts
|
|
35
461
|
var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
|
|
36
|
-
// Full English words (1-3 letters)
|
|
37
462
|
"day",
|
|
38
463
|
"key",
|
|
39
464
|
"net",
|
|
@@ -89,14 +514,12 @@ var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
|
|
|
89
514
|
"tmp",
|
|
90
515
|
"ext",
|
|
91
516
|
"sep",
|
|
92
|
-
// Prepositions and conjunctions
|
|
93
517
|
"and",
|
|
94
518
|
"from",
|
|
95
519
|
"how",
|
|
96
520
|
"pad",
|
|
97
521
|
"bar",
|
|
98
522
|
"non",
|
|
99
|
-
// Additional full words commonly flagged
|
|
100
523
|
"tax",
|
|
101
524
|
"cat",
|
|
102
525
|
"dog",
|
|
@@ -176,18 +599,15 @@ var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
|
|
|
176
599
|
"won"
|
|
177
600
|
]);
|
|
178
601
|
var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
179
|
-
// Standard identifiers
|
|
180
602
|
"id",
|
|
181
603
|
"uid",
|
|
182
604
|
"gid",
|
|
183
605
|
"pid",
|
|
184
|
-
// Loop counters and iterators
|
|
185
606
|
"i",
|
|
186
607
|
"j",
|
|
187
608
|
"k",
|
|
188
609
|
"n",
|
|
189
610
|
"m",
|
|
190
|
-
// Web/Network
|
|
191
611
|
"url",
|
|
192
612
|
"uri",
|
|
193
613
|
"api",
|
|
@@ -207,7 +627,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
207
627
|
"cors",
|
|
208
628
|
"ws",
|
|
209
629
|
"wss",
|
|
210
|
-
// Data formats
|
|
211
630
|
"json",
|
|
212
631
|
"xml",
|
|
213
632
|
"yaml",
|
|
@@ -216,7 +635,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
216
635
|
"css",
|
|
217
636
|
"svg",
|
|
218
637
|
"pdf",
|
|
219
|
-
// File types & extensions
|
|
220
638
|
"img",
|
|
221
639
|
"txt",
|
|
222
640
|
"doc",
|
|
@@ -228,7 +646,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
228
646
|
"jpg",
|
|
229
647
|
"png",
|
|
230
648
|
"gif",
|
|
231
|
-
// Databases
|
|
232
649
|
"db",
|
|
233
650
|
"sql",
|
|
234
651
|
"orm",
|
|
@@ -237,7 +654,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
237
654
|
"ddb",
|
|
238
655
|
"rds",
|
|
239
656
|
"nosql",
|
|
240
|
-
// File system
|
|
241
657
|
"fs",
|
|
242
658
|
"dir",
|
|
243
659
|
"tmp",
|
|
@@ -246,7 +662,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
246
662
|
"bin",
|
|
247
663
|
"lib",
|
|
248
664
|
"pkg",
|
|
249
|
-
// Operating system
|
|
250
665
|
"os",
|
|
251
666
|
"env",
|
|
252
667
|
"arg",
|
|
@@ -255,20 +670,17 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
255
670
|
"exe",
|
|
256
671
|
"cwd",
|
|
257
672
|
"pwd",
|
|
258
|
-
// UI/UX
|
|
259
673
|
"ui",
|
|
260
674
|
"ux",
|
|
261
675
|
"gui",
|
|
262
676
|
"dom",
|
|
263
677
|
"ref",
|
|
264
|
-
// Request/Response
|
|
265
678
|
"req",
|
|
266
679
|
"res",
|
|
267
680
|
"ctx",
|
|
268
681
|
"err",
|
|
269
682
|
"msg",
|
|
270
683
|
"auth",
|
|
271
|
-
// Mathematics/Computing
|
|
272
684
|
"max",
|
|
273
685
|
"min",
|
|
274
686
|
"avg",
|
|
@@ -286,7 +698,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
286
698
|
"int",
|
|
287
699
|
"num",
|
|
288
700
|
"idx",
|
|
289
|
-
// Time
|
|
290
701
|
"now",
|
|
291
702
|
"utc",
|
|
292
703
|
"tz",
|
|
@@ -296,7 +707,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
296
707
|
"min",
|
|
297
708
|
"yr",
|
|
298
709
|
"mo",
|
|
299
|
-
// Common patterns
|
|
300
710
|
"app",
|
|
301
711
|
"cfg",
|
|
302
712
|
"config",
|
|
@@ -315,7 +725,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
315
725
|
"post",
|
|
316
726
|
"sub",
|
|
317
727
|
"pub",
|
|
318
|
-
// Programming/Framework specific
|
|
319
728
|
"ts",
|
|
320
729
|
"js",
|
|
321
730
|
"jsx",
|
|
@@ -329,7 +738,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
329
738
|
"mod",
|
|
330
739
|
"opts",
|
|
331
740
|
"dev",
|
|
332
|
-
// Cloud/Infrastructure
|
|
333
741
|
"s3",
|
|
334
742
|
"ec2",
|
|
335
743
|
"sqs",
|
|
@@ -353,7 +761,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
353
761
|
"cf",
|
|
354
762
|
"cfn",
|
|
355
763
|
"ga",
|
|
356
|
-
// Metrics/Performance
|
|
357
764
|
"fcp",
|
|
358
765
|
"lcp",
|
|
359
766
|
"cls",
|
|
@@ -365,14 +772,12 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
365
772
|
"rps",
|
|
366
773
|
"tps",
|
|
367
774
|
"wpm",
|
|
368
|
-
// Testing & i18n
|
|
369
775
|
"po",
|
|
370
776
|
"e2e",
|
|
371
777
|
"a11y",
|
|
372
778
|
"i18n",
|
|
373
779
|
"l10n",
|
|
374
780
|
"spy",
|
|
375
|
-
// Domain-specific abbreviations (context-aware)
|
|
376
781
|
"sk",
|
|
377
782
|
"fy",
|
|
378
783
|
"faq",
|
|
@@ -383,7 +788,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
383
788
|
"kpi",
|
|
384
789
|
"ttl",
|
|
385
790
|
"pct",
|
|
386
|
-
// Technical abbreviations
|
|
387
791
|
"mac",
|
|
388
792
|
"hex",
|
|
389
793
|
"esm",
|
|
@@ -391,19 +795,27 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
391
795
|
"rec",
|
|
392
796
|
"loc",
|
|
393
797
|
"dup",
|
|
394
|
-
// Boolean helpers (these are intentional short names)
|
|
395
798
|
"is",
|
|
396
799
|
"has",
|
|
397
800
|
"can",
|
|
398
801
|
"did",
|
|
399
802
|
"was",
|
|
400
803
|
"are",
|
|
401
|
-
// Date/Time context (when in date contexts)
|
|
402
804
|
"d",
|
|
403
805
|
"t",
|
|
404
|
-
"dt"
|
|
806
|
+
"dt",
|
|
807
|
+
"s",
|
|
808
|
+
"b",
|
|
809
|
+
"f",
|
|
810
|
+
"l",
|
|
811
|
+
// Coverage metrics
|
|
812
|
+
"vid",
|
|
813
|
+
"pic",
|
|
814
|
+
"img",
|
|
815
|
+
"doc",
|
|
816
|
+
"msg"
|
|
405
817
|
]);
|
|
406
|
-
async function
|
|
818
|
+
async function analyzeNamingAST(files) {
|
|
407
819
|
const issues = [];
|
|
408
820
|
const rootDir = files.length > 0 ? (0, import_path.dirname)(files[0]) : process.cwd();
|
|
409
821
|
const config = (0, import_core.loadConfig)(rootDir);
|
|
@@ -411,184 +823,221 @@ async function analyzeNaming(files) {
|
|
|
411
823
|
const customAbbreviations = new Set(consistencyConfig?.acceptedAbbreviations || []);
|
|
412
824
|
const customShortWords = new Set(consistencyConfig?.shortWords || []);
|
|
413
825
|
const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
|
|
826
|
+
const allAbbreviations = /* @__PURE__ */ new Set([...ACCEPTABLE_ABBREVIATIONS, ...customAbbreviations]);
|
|
827
|
+
const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
|
|
414
828
|
for (const file of files) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
829
|
+
try {
|
|
830
|
+
const ast = parseFile(file);
|
|
831
|
+
if (!ast) continue;
|
|
832
|
+
const fileIssues = analyzeFileNamingAST(
|
|
833
|
+
file,
|
|
834
|
+
ast,
|
|
835
|
+
allAbbreviations,
|
|
836
|
+
allShortWords,
|
|
837
|
+
disabledChecks
|
|
838
|
+
);
|
|
839
|
+
issues.push(...fileIssues);
|
|
840
|
+
} catch (error) {
|
|
841
|
+
console.warn(`Skipping ${file} due to parse error:`, error);
|
|
842
|
+
}
|
|
418
843
|
}
|
|
419
844
|
return issues;
|
|
420
845
|
}
|
|
421
|
-
function
|
|
846
|
+
function analyzeFileNamingAST(file, ast, allAbbreviations, allShortWords, disabledChecks) {
|
|
422
847
|
const issues = [];
|
|
423
|
-
const
|
|
424
|
-
const
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
usageCount += matches.length;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
return usageCount >= 2 && usageCount <= 3;
|
|
444
|
-
};
|
|
445
|
-
lines.forEach((line, index) => {
|
|
446
|
-
const lineNumber = index + 1;
|
|
447
|
-
const contextWindow = getContextWindow(index);
|
|
448
|
-
if (!disabledChecks.has("single-letter")) {
|
|
449
|
-
const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
|
|
450
|
-
for (const match of singleLetterMatches) {
|
|
451
|
-
const letter = match[1].toLowerCase();
|
|
452
|
-
const isInLoopContext = line.includes("for") || /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) || line.includes("=>") || // Arrow function
|
|
453
|
-
/\w+\s*=>\s*/.test(line);
|
|
454
|
-
const isI18nContext = line.includes("useTranslation") || line.includes("i18n.t") || /\bt\s*\(['"]/.test(line);
|
|
455
|
-
const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
456
|
-
/[a-z]\s*=>/.test(line) || // s => on same line
|
|
457
|
-
// Multi-line arrow function detection: look for pattern in context window
|
|
458
|
-
new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
|
|
459
|
-
new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && /=>/.test(contextWindow);
|
|
460
|
-
const isShortLived = isShortLivedVariable(letter, index);
|
|
461
|
-
if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
|
|
462
|
-
if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
|
|
463
|
-
continue;
|
|
848
|
+
const scopeTracker = new ScopeTracker(ast);
|
|
849
|
+
const context = buildCodeContext(file, ast);
|
|
850
|
+
const ancestors = [];
|
|
851
|
+
traverseAST(ast, {
|
|
852
|
+
enter: (node, parent) => {
|
|
853
|
+
ancestors.push(node);
|
|
854
|
+
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
|
|
855
|
+
scopeTracker.enterScope("function", node);
|
|
856
|
+
if ("params" in node) {
|
|
857
|
+
for (const param of node.params) {
|
|
858
|
+
if (param.type === "Identifier") {
|
|
859
|
+
scopeTracker.declareVariable(param.name, param, getLineNumber(param), {
|
|
860
|
+
isParameter: true
|
|
861
|
+
});
|
|
862
|
+
} else if (param.type === "ObjectPattern" || param.type === "ArrayPattern") {
|
|
863
|
+
extractIdentifiersFromPattern(param, scopeTracker, true);
|
|
864
|
+
}
|
|
464
865
|
}
|
|
465
|
-
issues.push({
|
|
466
|
-
file,
|
|
467
|
-
line: lineNumber,
|
|
468
|
-
type: "poor-naming",
|
|
469
|
-
identifier: match[1],
|
|
470
|
-
severity: "minor",
|
|
471
|
-
suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
|
|
472
|
-
});
|
|
473
866
|
}
|
|
867
|
+
} else if (node.type === "BlockStatement") {
|
|
868
|
+
scopeTracker.enterScope("block", node);
|
|
869
|
+
} else if (isLoopStatement(node)) {
|
|
870
|
+
scopeTracker.enterScope("loop", node);
|
|
871
|
+
} else if (node.type === "ClassDeclaration") {
|
|
872
|
+
scopeTracker.enterScope("class", node);
|
|
474
873
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && new RegExp(`^\\s*${abbrev}\\s*=>`).test(line);
|
|
491
|
-
if (isArrowFunctionParam) {
|
|
492
|
-
continue;
|
|
874
|
+
if (node.type === "VariableDeclarator") {
|
|
875
|
+
if (node.id.type === "Identifier") {
|
|
876
|
+
const isInCoverage = isCoverageContext(node, ancestors);
|
|
877
|
+
scopeTracker.declareVariable(
|
|
878
|
+
node.id.name,
|
|
879
|
+
node.id,
|
|
880
|
+
getLineNumber(node.id),
|
|
881
|
+
{
|
|
882
|
+
type: "typeAnnotation" in node.id ? node.id.typeAnnotation : null,
|
|
883
|
+
isDestructured: false,
|
|
884
|
+
isLoopVariable: scopeTracker.getCurrentScopeType() === "loop"
|
|
885
|
+
}
|
|
886
|
+
);
|
|
887
|
+
} else if (node.id.type === "ObjectPattern" || node.id.type === "ArrayPattern") {
|
|
888
|
+
extractIdentifiersFromPattern(node.id, scopeTracker, false, ancestors);
|
|
493
889
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const isUserContext = /user|auth|account/i.test(line);
|
|
500
|
-
if (isUserContext && abbrev === "u") {
|
|
501
|
-
continue;
|
|
890
|
+
}
|
|
891
|
+
if (node.type === "Identifier" && parent) {
|
|
892
|
+
if (parent.type !== "VariableDeclarator" || parent.id !== node) {
|
|
893
|
+
if (parent.type !== "FunctionDeclaration" || parent.id !== node) {
|
|
894
|
+
scopeTracker.addReference(node.name, node);
|
|
502
895
|
}
|
|
503
896
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
|
|
511
|
-
});
|
|
897
|
+
}
|
|
898
|
+
},
|
|
899
|
+
leave: (node) => {
|
|
900
|
+
ancestors.pop();
|
|
901
|
+
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "BlockStatement" || isLoopStatement(node) || node.type === "ClassDeclaration") {
|
|
902
|
+
scopeTracker.exitScope();
|
|
512
903
|
}
|
|
513
904
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
905
|
+
});
|
|
906
|
+
const allVariables = scopeTracker.getAllVariables();
|
|
907
|
+
for (const varInfo of allVariables) {
|
|
908
|
+
const name = varInfo.name;
|
|
909
|
+
const line = varInfo.declarationLine;
|
|
910
|
+
if (disabledChecks.has("single-letter") && name.length === 1) continue;
|
|
911
|
+
if (disabledChecks.has("abbreviation") && name.length <= 3) continue;
|
|
912
|
+
const isInCoverage = ["s", "b", "f", "l"].includes(name) && varInfo.isDestructured;
|
|
913
|
+
if (isInCoverage) continue;
|
|
914
|
+
const functionComplexity = varInfo.node.type === "Identifier" && "parent" in varInfo.node ? calculateComplexity(varInfo.node) : context.complexity;
|
|
915
|
+
if (isAcceptableInContext(name, context, {
|
|
916
|
+
isLoopVariable: varInfo.isLoopVariable || allAbbreviations.has(name),
|
|
917
|
+
isParameter: varInfo.isParameter,
|
|
918
|
+
isDestructured: varInfo.isDestructured,
|
|
919
|
+
complexity: functionComplexity
|
|
920
|
+
})) {
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
if (name.length === 1 && !allAbbreviations.has(name) && !allShortWords.has(name)) {
|
|
924
|
+
const isShortLived = scopeTracker.isShortLived(varInfo, 5);
|
|
925
|
+
if (!isShortLived) {
|
|
518
926
|
issues.push({
|
|
519
927
|
file,
|
|
520
|
-
line
|
|
521
|
-
type: "
|
|
522
|
-
identifier:
|
|
523
|
-
severity: "minor",
|
|
524
|
-
suggestion: `Use
|
|
928
|
+
line,
|
|
929
|
+
type: "poor-naming",
|
|
930
|
+
identifier: name,
|
|
931
|
+
severity: adjustSeverity("minor", context, "poor-naming"),
|
|
932
|
+
suggestion: `Use descriptive variable name instead of single letter '${name}'`
|
|
525
933
|
});
|
|
526
934
|
}
|
|
935
|
+
continue;
|
|
527
936
|
}
|
|
528
|
-
if (
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
if (!name.match(/^(is|has|should|can|will|did)/i)) {
|
|
937
|
+
if (name.length >= 2 && name.length <= 3) {
|
|
938
|
+
if (!allShortWords.has(name.toLowerCase()) && !allAbbreviations.has(name.toLowerCase())) {
|
|
939
|
+
const isShortLived = scopeTracker.isShortLived(varInfo, 5);
|
|
940
|
+
if (!isShortLived) {
|
|
533
941
|
issues.push({
|
|
534
942
|
file,
|
|
535
|
-
line
|
|
536
|
-
type: "
|
|
943
|
+
line,
|
|
944
|
+
type: "abbreviation",
|
|
537
945
|
identifier: name,
|
|
538
|
-
severity: "info",
|
|
539
|
-
suggestion: `
|
|
946
|
+
severity: adjustSeverity("info", context, "abbreviation"),
|
|
947
|
+
suggestion: `Consider using full word instead of abbreviation '${name}'`
|
|
540
948
|
});
|
|
541
949
|
}
|
|
542
950
|
}
|
|
951
|
+
continue;
|
|
543
952
|
}
|
|
544
|
-
if (!disabledChecks.has("
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
}
|
|
556
|
-
const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/);
|
|
557
|
-
const isEventHandler = name.match(/^on[A-Z]/);
|
|
558
|
-
const isDescriptiveLong = name.length > 15;
|
|
559
|
-
const isReactHook = name.match(/^use[A-Z]/);
|
|
560
|
-
const isDescriptivePattern = name.match(/^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/) || name.match(/\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props|Data|Info|Details|State|Status|Response|Result)$/);
|
|
561
|
-
const isHelperPattern = name.match(/^(to|from|with|without|for|as|into)\w+/) || // toMetadata, withLogger, forPath
|
|
562
|
-
name.match(/^\w+(To|From|With|Without|For|As|Into)\w*$/);
|
|
563
|
-
const isUtilityName = ["cn", "proxy", "sitemap", "robots", "gtag"].includes(name);
|
|
564
|
-
const capitalCount = (name.match(/[A-Z]/g) || []).length;
|
|
565
|
-
const isCompoundWord = capitalCount >= 3;
|
|
566
|
-
const hasActionVerb = name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce|make|do|run|start|stop|build|parse|format|render|calculate|compute|generate|transform|convert|normalize|sanitize|encode|decode|compress|extract|merge|split|join|sort|compare|test|verify|ensure|apply|execute|invoke|call|emit|dispatch|trigger|listen|subscribe|unsubscribe|add|remove|clear|reset|toggle|enable|disable|open|close|connect|disconnect|send|receive|read|write|import|export|register|unregister|mount|unmount|track|store|persist|upsert|derive|classify|combine|discover|activate|require|assert|expect|mask|escape|sign|put|list|complete|page|safe|mock|pick|pluralize|text)/);
|
|
567
|
-
if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord && !isHelperPattern && !isUtilityName && !isReactHook) {
|
|
568
|
-
issues.push({
|
|
569
|
-
file,
|
|
570
|
-
line: lineNumber,
|
|
571
|
-
type: "unclear",
|
|
572
|
-
identifier: name,
|
|
573
|
-
severity: "info",
|
|
574
|
-
suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
|
|
575
|
-
});
|
|
576
|
-
}
|
|
953
|
+
if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
954
|
+
if (name.includes("_") && !name.startsWith("_") && name.toLowerCase() === name) {
|
|
955
|
+
const camelCase = name.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
956
|
+
issues.push({
|
|
957
|
+
file,
|
|
958
|
+
line,
|
|
959
|
+
type: "convention-mix",
|
|
960
|
+
identifier: name,
|
|
961
|
+
severity: adjustSeverity("minor", context, "convention-mix"),
|
|
962
|
+
suggestion: `Use camelCase '${camelCase}' instead of snake_case in TypeScript/JavaScript`
|
|
963
|
+
});
|
|
577
964
|
}
|
|
578
965
|
}
|
|
579
|
-
}
|
|
966
|
+
}
|
|
967
|
+
if (!disabledChecks.has("unclear")) {
|
|
968
|
+
traverseAST(ast, {
|
|
969
|
+
enter: (node) => {
|
|
970
|
+
if (node.type === "FunctionDeclaration" || node.type === "MethodDefinition") {
|
|
971
|
+
const name = getFunctionName(node);
|
|
972
|
+
if (!name) return;
|
|
973
|
+
const line = getLineNumber(node);
|
|
974
|
+
if (["main", "init", "setup", "bootstrap"].includes(name)) return;
|
|
975
|
+
const hasActionVerb = name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce|make|do|run|start|stop|build|parse|format|render|calculate|compute|generate|transform|convert|normalize|sanitize|encode|decode|compress|extract|merge|split|join|sort|compare|test|verify|ensure|apply|execute|invoke|call|emit|dispatch|trigger|listen|subscribe|unsubscribe|add|remove|clear|reset|toggle|enable|disable|open|close|connect|disconnect|send|receive|read|write|import|export|register|unregister|mount|unmount|track|store|persist|upsert|derive|classify|combine|discover|activate|require|assert|expect|mask|escape|sign|put|list|complete|page|safe|mock|pick|pluralize|text)/);
|
|
976
|
+
const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/);
|
|
977
|
+
const isEventHandler = name.match(/^on[A-Z]/);
|
|
978
|
+
const isDescriptiveLong = name.length > 15;
|
|
979
|
+
const isReactHook = name.match(/^use[A-Z]/);
|
|
980
|
+
const isHelperPattern = name.match(/^(to|from|with|without|for|as|into)\w+/);
|
|
981
|
+
const isUtilityName = ["cn", "proxy", "sitemap", "robots", "gtag"].includes(name);
|
|
982
|
+
const isDescriptivePattern = name.match(/^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/) || name.match(/\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props|Data|Info|Details|State|Status|Response|Result)$/);
|
|
983
|
+
const capitalCount = (name.match(/[A-Z]/g) || []).length;
|
|
984
|
+
const isCompoundWord = capitalCount >= 3;
|
|
985
|
+
if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isReactHook && !isHelperPattern && !isUtilityName && !isDescriptivePattern && !isCompoundWord) {
|
|
986
|
+
issues.push({
|
|
987
|
+
file,
|
|
988
|
+
line,
|
|
989
|
+
type: "unclear",
|
|
990
|
+
identifier: name,
|
|
991
|
+
severity: adjustSeverity("info", context, "unclear"),
|
|
992
|
+
suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
}
|
|
580
999
|
return issues;
|
|
581
1000
|
}
|
|
582
|
-
function
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
1001
|
+
function extractIdentifiersFromPattern(pattern, scopeTracker, isParameter, ancestors) {
|
|
1002
|
+
if (pattern.type === "ObjectPattern") {
|
|
1003
|
+
for (const prop of pattern.properties) {
|
|
1004
|
+
if (prop.type === "Property" && prop.value.type === "Identifier") {
|
|
1005
|
+
scopeTracker.declareVariable(
|
|
1006
|
+
prop.value.name,
|
|
1007
|
+
prop.value,
|
|
1008
|
+
getLineNumber(prop.value),
|
|
1009
|
+
{
|
|
1010
|
+
isParameter,
|
|
1011
|
+
isDestructured: true
|
|
1012
|
+
}
|
|
1013
|
+
);
|
|
1014
|
+
} else if (prop.type === "RestElement" && prop.argument.type === "Identifier") {
|
|
1015
|
+
scopeTracker.declareVariable(
|
|
1016
|
+
prop.argument.name,
|
|
1017
|
+
prop.argument,
|
|
1018
|
+
getLineNumber(prop.argument),
|
|
1019
|
+
{
|
|
1020
|
+
isParameter,
|
|
1021
|
+
isDestructured: true
|
|
1022
|
+
}
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
} else if (pattern.type === "ArrayPattern") {
|
|
1027
|
+
for (const element of pattern.elements) {
|
|
1028
|
+
if (element && element.type === "Identifier") {
|
|
1029
|
+
scopeTracker.declareVariable(
|
|
1030
|
+
element.name,
|
|
1031
|
+
element,
|
|
1032
|
+
getLineNumber(element),
|
|
1033
|
+
{
|
|
1034
|
+
isParameter,
|
|
1035
|
+
isDestructured: true
|
|
1036
|
+
}
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
590
1040
|
}
|
|
591
|
-
return { dominantConvention: "camelCase", conventionScore: 0.9 };
|
|
592
1041
|
}
|
|
593
1042
|
|
|
594
1043
|
// src/analyzers/patterns.ts
|
|
@@ -747,7 +1196,7 @@ async function analyzeConsistency(options) {
|
|
|
747
1196
|
...scanOptions
|
|
748
1197
|
} = options;
|
|
749
1198
|
const filePaths = await (0, import_core3.scanFiles)(scanOptions);
|
|
750
|
-
const namingIssues = checkNaming ? await
|
|
1199
|
+
const namingIssues = checkNaming ? await analyzeNamingAST(filePaths) : [];
|
|
751
1200
|
const patternIssues = checkPatterns ? await analyzePatterns(filePaths) : [];
|
|
752
1201
|
const results = [];
|
|
753
1202
|
const fileIssuesMap = /* @__PURE__ */ new Map();
|
|
@@ -806,7 +1255,6 @@ async function analyzeConsistency(options) {
|
|
|
806
1255
|
});
|
|
807
1256
|
}
|
|
808
1257
|
const recommendations = generateRecommendations(namingIssues, patternIssues);
|
|
809
|
-
const conventionAnalysis = detectNamingConventions(filePaths, namingIssues);
|
|
810
1258
|
return {
|
|
811
1259
|
summary: {
|
|
812
1260
|
totalIssues: namingIssues.length + patternIssues.length,
|
|
@@ -872,7 +1320,7 @@ function generateRecommendations(namingIssues, patternIssues) {
|
|
|
872
1320
|
|
|
873
1321
|
// src/cli.ts
|
|
874
1322
|
var import_chalk = __toESM(require("chalk"));
|
|
875
|
-
var
|
|
1323
|
+
var import_fs2 = require("fs");
|
|
876
1324
|
var import_path2 = require("path");
|
|
877
1325
|
var import_core4 = require("@aiready/core");
|
|
878
1326
|
var program = new import_commander.Command();
|
|
@@ -923,10 +1371,10 @@ EXAMPLES:
|
|
|
923
1371
|
directory
|
|
924
1372
|
);
|
|
925
1373
|
const dir = (0, import_path2.dirname)(outputPath);
|
|
926
|
-
if (!(0,
|
|
927
|
-
(0,
|
|
1374
|
+
if (!(0, import_fs2.existsSync)(dir)) {
|
|
1375
|
+
(0, import_fs2.mkdirSync)(dir, { recursive: true });
|
|
928
1376
|
}
|
|
929
|
-
(0,
|
|
1377
|
+
(0, import_fs2.writeFileSync)(outputPath, output);
|
|
930
1378
|
console.log(import_chalk.default.green(`\u2713 Report saved to ${outputPath}`));
|
|
931
1379
|
} else if (options.output === "markdown") {
|
|
932
1380
|
const markdown = generateMarkdownReport(report, elapsedTime);
|
|
@@ -936,10 +1384,10 @@ EXAMPLES:
|
|
|
936
1384
|
directory
|
|
937
1385
|
);
|
|
938
1386
|
const dir = (0, import_path2.dirname)(outputPath);
|
|
939
|
-
if (!(0,
|
|
940
|
-
(0,
|
|
1387
|
+
if (!(0, import_fs2.existsSync)(dir)) {
|
|
1388
|
+
(0, import_fs2.mkdirSync)(dir, { recursive: true });
|
|
941
1389
|
}
|
|
942
|
-
(0,
|
|
1390
|
+
(0, import_fs2.writeFileSync)(outputPath, markdown);
|
|
943
1391
|
console.log(import_chalk.default.green(`\u2713 Report saved to ${outputPath}`));
|
|
944
1392
|
} else {
|
|
945
1393
|
displayConsoleReport(report, elapsedTime);
|