@aiready/consistency 0.16.0 → 0.16.3
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 +12 -10
- package/dist/chunk-AASFXGUR.mjs +1622 -0
- package/dist/chunk-AR7DIZLP.mjs +827 -0
- package/dist/chunk-BMILMNKJ.mjs +1633 -0
- package/dist/chunk-HJCP36VW.mjs +821 -0
- package/dist/chunk-QOIPVP6P.mjs +1607 -0
- package/dist/chunk-RMEQWG52.mjs +1633 -0
- package/dist/chunk-XVW5DKJQ.mjs +1619 -0
- package/dist/cli.js +277 -1019
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +320 -1242
- package/dist/index.mjs +58 -230
- package/package.json +2 -2
- package/src/__tests__/contract.test.ts +18 -2
- package/src/analyzer.ts +49 -28
- package/src/analyzers/naming-ast.ts +188 -328
- package/src/analyzers/naming.ts +52 -365
- package/src/analyzers/patterns.ts +51 -228
- package/src/types.ts +10 -10
- package/src/utils/context-detector.ts +23 -10
package/dist/index.js
CHANGED
|
@@ -40,7 +40,10 @@ __export(index_exports, {
|
|
|
40
40
|
module.exports = __toCommonJS(index_exports);
|
|
41
41
|
|
|
42
42
|
// src/analyzer.ts
|
|
43
|
-
var
|
|
43
|
+
var import_core5 = require("@aiready/core");
|
|
44
|
+
|
|
45
|
+
// src/analyzers/naming-ast.ts
|
|
46
|
+
var import_core2 = require("@aiready/core");
|
|
44
47
|
|
|
45
48
|
// src/utils/ast-parser.ts
|
|
46
49
|
var import_typescript_estree = require("@typescript-eslint/typescript-estree");
|
|
@@ -92,214 +95,12 @@ function isLoopStatement(node) {
|
|
|
92
95
|
"DoWhileStatement"
|
|
93
96
|
].includes(node.type);
|
|
94
97
|
}
|
|
95
|
-
function getFunctionName(node) {
|
|
96
|
-
switch (node.type) {
|
|
97
|
-
case "FunctionDeclaration":
|
|
98
|
-
return node.id?.name ?? null;
|
|
99
|
-
case "FunctionExpression":
|
|
100
|
-
return node.id?.name ?? null;
|
|
101
|
-
case "ArrowFunctionExpression":
|
|
102
|
-
return null;
|
|
103
|
-
// Arrow functions don't have names directly
|
|
104
|
-
case "MethodDefinition":
|
|
105
|
-
if (node.key.type === "Identifier") {
|
|
106
|
-
return node.key.name;
|
|
107
|
-
}
|
|
108
|
-
return null;
|
|
109
|
-
default:
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
98
|
function getLineNumber(node) {
|
|
114
99
|
return node.loc?.start.line ?? 0;
|
|
115
100
|
}
|
|
116
|
-
function isCoverageContext(node, ancestors) {
|
|
117
|
-
const coveragePatterns = /coverage|summary|metrics|pct|percent|statements|branches|functions|lines/i;
|
|
118
|
-
if (node.type === "Identifier" && coveragePatterns.test(node.name)) {
|
|
119
|
-
return true;
|
|
120
|
-
}
|
|
121
|
-
for (const ancestor of ancestors.slice(-3)) {
|
|
122
|
-
if (ancestor.type === "MemberExpression") {
|
|
123
|
-
const memberExpr = ancestor;
|
|
124
|
-
if (memberExpr.object.type === "Identifier" && coveragePatterns.test(memberExpr.object.name)) {
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
if (ancestor.type === "ObjectPattern" || ancestor.type === "ObjectExpression") {
|
|
129
|
-
const parent = ancestors[ancestors.indexOf(ancestor) - 1];
|
|
130
|
-
if (parent?.type === "VariableDeclarator") {
|
|
131
|
-
const varDecl = parent;
|
|
132
|
-
if (varDecl.id.type === "Identifier" && coveragePatterns.test(varDecl.id.name)) {
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// src/utils/scope-tracker.ts
|
|
142
|
-
var ScopeTracker = class {
|
|
143
|
-
constructor(rootNode) {
|
|
144
|
-
this.allScopes = [];
|
|
145
|
-
this.rootScope = {
|
|
146
|
-
type: "global",
|
|
147
|
-
node: rootNode,
|
|
148
|
-
parent: null,
|
|
149
|
-
children: [],
|
|
150
|
-
variables: /* @__PURE__ */ new Map()
|
|
151
|
-
};
|
|
152
|
-
this.currentScope = this.rootScope;
|
|
153
|
-
this.allScopes.push(this.rootScope);
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Enter a new scope
|
|
157
|
-
*/
|
|
158
|
-
enterScope(type, node) {
|
|
159
|
-
const newScope = {
|
|
160
|
-
type,
|
|
161
|
-
node,
|
|
162
|
-
parent: this.currentScope,
|
|
163
|
-
children: [],
|
|
164
|
-
variables: /* @__PURE__ */ new Map()
|
|
165
|
-
};
|
|
166
|
-
this.currentScope.children.push(newScope);
|
|
167
|
-
this.currentScope = newScope;
|
|
168
|
-
this.allScopes.push(newScope);
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Exit current scope and return to parent
|
|
172
|
-
*/
|
|
173
|
-
exitScope() {
|
|
174
|
-
if (this.currentScope.parent) {
|
|
175
|
-
this.currentScope = this.currentScope.parent;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Declare a variable in the current scope
|
|
180
|
-
*/
|
|
181
|
-
declareVariable(name, node, line, options = {}) {
|
|
182
|
-
const varInfo = {
|
|
183
|
-
name,
|
|
184
|
-
node,
|
|
185
|
-
declarationLine: line,
|
|
186
|
-
references: [],
|
|
187
|
-
type: options.type,
|
|
188
|
-
isParameter: options.isParameter ?? false,
|
|
189
|
-
isDestructured: options.isDestructured ?? false,
|
|
190
|
-
isLoopVariable: options.isLoopVariable ?? false
|
|
191
|
-
};
|
|
192
|
-
this.currentScope.variables.set(name, varInfo);
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Add a reference to a variable
|
|
196
|
-
*/
|
|
197
|
-
addReference(name, node) {
|
|
198
|
-
const varInfo = this.findVariable(name);
|
|
199
|
-
if (varInfo) {
|
|
200
|
-
varInfo.references.push(node);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Find a variable in current or parent scopes
|
|
205
|
-
*/
|
|
206
|
-
findVariable(name) {
|
|
207
|
-
let scope = this.currentScope;
|
|
208
|
-
while (scope) {
|
|
209
|
-
const varInfo = scope.variables.get(name);
|
|
210
|
-
if (varInfo) {
|
|
211
|
-
return varInfo;
|
|
212
|
-
}
|
|
213
|
-
scope = scope.parent;
|
|
214
|
-
}
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Get all variables in current scope (not including parent scopes)
|
|
219
|
-
*/
|
|
220
|
-
getCurrentScopeVariables() {
|
|
221
|
-
return Array.from(this.currentScope.variables.values());
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Get all variables across all scopes
|
|
225
|
-
*/
|
|
226
|
-
getAllVariables() {
|
|
227
|
-
const allVars = [];
|
|
228
|
-
for (const scope of this.allScopes) {
|
|
229
|
-
allVars.push(...Array.from(scope.variables.values()));
|
|
230
|
-
}
|
|
231
|
-
return allVars;
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Calculate actual usage count (references minus declaration)
|
|
235
|
-
*/
|
|
236
|
-
getUsageCount(varInfo) {
|
|
237
|
-
return varInfo.references.length;
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* Check if a variable is short-lived (used within N lines)
|
|
241
|
-
*/
|
|
242
|
-
isShortLived(varInfo, maxLines = 5) {
|
|
243
|
-
if (varInfo.references.length === 0) {
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
246
|
-
const declarationLine = varInfo.declarationLine;
|
|
247
|
-
const maxUsageLine = Math.max(
|
|
248
|
-
...varInfo.references.map((ref) => ref.loc?.start.line ?? declarationLine)
|
|
249
|
-
);
|
|
250
|
-
return maxUsageLine - declarationLine <= maxLines;
|
|
251
|
-
}
|
|
252
|
-
/**
|
|
253
|
-
* Check if a variable is used in a limited scope (e.g., only in one callback)
|
|
254
|
-
*/
|
|
255
|
-
isLocallyScoped(varInfo) {
|
|
256
|
-
if (varInfo.references.length === 0) return false;
|
|
257
|
-
const lines = varInfo.references.map((ref) => ref.loc?.start.line ?? 0);
|
|
258
|
-
const minLine = Math.min(...lines);
|
|
259
|
-
const maxLine = Math.max(...lines);
|
|
260
|
-
return maxLine - minLine <= 3;
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Get current scope type
|
|
264
|
-
*/
|
|
265
|
-
getCurrentScopeType() {
|
|
266
|
-
return this.currentScope.type;
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Check if currently in a loop scope
|
|
270
|
-
*/
|
|
271
|
-
isInLoop() {
|
|
272
|
-
let scope = this.currentScope;
|
|
273
|
-
while (scope) {
|
|
274
|
-
if (scope.type === "loop") {
|
|
275
|
-
return true;
|
|
276
|
-
}
|
|
277
|
-
scope = scope.parent;
|
|
278
|
-
}
|
|
279
|
-
return false;
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Check if currently in a function scope
|
|
283
|
-
*/
|
|
284
|
-
isInFunction() {
|
|
285
|
-
let scope = this.currentScope;
|
|
286
|
-
while (scope) {
|
|
287
|
-
if (scope.type === "function") {
|
|
288
|
-
return true;
|
|
289
|
-
}
|
|
290
|
-
scope = scope.parent;
|
|
291
|
-
}
|
|
292
|
-
return false;
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Get the root scope
|
|
296
|
-
*/
|
|
297
|
-
getRootScope() {
|
|
298
|
-
return this.rootScope;
|
|
299
|
-
}
|
|
300
|
-
};
|
|
301
101
|
|
|
302
102
|
// src/utils/context-detector.ts
|
|
103
|
+
var import_core = require("@aiready/core");
|
|
303
104
|
function detectFileType(filePath, ast) {
|
|
304
105
|
void ast;
|
|
305
106
|
const path = filePath.toLowerCase();
|
|
@@ -427,24 +228,34 @@ function buildCodeContext(filePath, ast) {
|
|
|
427
228
|
};
|
|
428
229
|
}
|
|
429
230
|
function adjustSeverity(baseSeverity, context, issueType) {
|
|
231
|
+
const getEnum = (s) => {
|
|
232
|
+
if (s === import_core.Severity.Critical || s === "critical") return import_core.Severity.Critical;
|
|
233
|
+
if (s === import_core.Severity.Major || s === "major") return import_core.Severity.Major;
|
|
234
|
+
if (s === import_core.Severity.Minor || s === "minor") return import_core.Severity.Minor;
|
|
235
|
+
return import_core.Severity.Info;
|
|
236
|
+
};
|
|
237
|
+
let currentSev = getEnum(baseSeverity);
|
|
430
238
|
if (context.isTestFile) {
|
|
431
|
-
if (
|
|
432
|
-
if (
|
|
239
|
+
if (currentSev === import_core.Severity.Minor) currentSev = import_core.Severity.Info;
|
|
240
|
+
if (currentSev === import_core.Severity.Major) currentSev = import_core.Severity.Minor;
|
|
433
241
|
}
|
|
434
242
|
if (context.isTypeDefinition) {
|
|
435
|
-
if (
|
|
243
|
+
if (currentSev === import_core.Severity.Minor) currentSev = import_core.Severity.Info;
|
|
436
244
|
}
|
|
437
245
|
if (context.codeLayer === "api") {
|
|
438
|
-
if (
|
|
439
|
-
|
|
246
|
+
if (currentSev === import_core.Severity.Info && issueType === "unclear")
|
|
247
|
+
currentSev = import_core.Severity.Minor;
|
|
248
|
+
if (currentSev === import_core.Severity.Minor && issueType === "unclear")
|
|
249
|
+
currentSev = import_core.Severity.Major;
|
|
440
250
|
}
|
|
441
251
|
if (context.complexity > 10) {
|
|
442
|
-
if (
|
|
252
|
+
if (currentSev === import_core.Severity.Info) currentSev = import_core.Severity.Minor;
|
|
443
253
|
}
|
|
444
254
|
if (context.codeLayer === "utility") {
|
|
445
|
-
if (
|
|
255
|
+
if (currentSev === import_core.Severity.Minor && issueType === "abbreviation")
|
|
256
|
+
currentSev = import_core.Severity.Info;
|
|
446
257
|
}
|
|
447
|
-
return
|
|
258
|
+
return currentSev;
|
|
448
259
|
}
|
|
449
260
|
function isAcceptableInContext(name, context, options) {
|
|
450
261
|
if (options.isLoopVariable && ["i", "j", "k", "l", "n", "m"].includes(name)) {
|
|
@@ -471,651 +282,157 @@ function isAcceptableInContext(name, context, options) {
|
|
|
471
282
|
return false;
|
|
472
283
|
}
|
|
473
284
|
|
|
474
|
-
// src/utils/config-loader.ts
|
|
475
|
-
var import_core = require("@aiready/core");
|
|
476
|
-
var import_path = require("path");
|
|
477
|
-
|
|
478
|
-
// src/analyzers/naming-constants.ts
|
|
479
|
-
var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
|
|
480
|
-
// Full English words (1-3 letters)
|
|
481
|
-
"day",
|
|
482
|
-
"key",
|
|
483
|
-
"net",
|
|
484
|
-
"to",
|
|
485
|
-
"go",
|
|
486
|
-
"for",
|
|
487
|
-
"not",
|
|
488
|
-
"new",
|
|
489
|
-
"old",
|
|
490
|
-
"top",
|
|
491
|
-
"end",
|
|
492
|
-
"run",
|
|
493
|
-
"try",
|
|
494
|
-
"use",
|
|
495
|
-
"get",
|
|
496
|
-
"set",
|
|
497
|
-
"add",
|
|
498
|
-
"put",
|
|
499
|
-
"map",
|
|
500
|
-
"log",
|
|
501
|
-
"row",
|
|
502
|
-
"col",
|
|
503
|
-
"tab",
|
|
504
|
-
"box",
|
|
505
|
-
"div",
|
|
506
|
-
"nav",
|
|
507
|
-
"tag",
|
|
508
|
-
"any",
|
|
509
|
-
"all",
|
|
510
|
-
"one",
|
|
511
|
-
"two",
|
|
512
|
-
"out",
|
|
513
|
-
"off",
|
|
514
|
-
"on",
|
|
515
|
-
"yes",
|
|
516
|
-
"no",
|
|
517
|
-
"now",
|
|
518
|
-
"max",
|
|
519
|
-
"min",
|
|
520
|
-
"sum",
|
|
521
|
-
"avg",
|
|
522
|
-
"ref",
|
|
523
|
-
"src",
|
|
524
|
-
"dst",
|
|
525
|
-
"raw",
|
|
526
|
-
"def",
|
|
527
|
-
"sub",
|
|
528
|
-
"pub",
|
|
529
|
-
"pre",
|
|
530
|
-
"mid",
|
|
531
|
-
"alt",
|
|
532
|
-
"opt",
|
|
533
|
-
"tmp",
|
|
534
|
-
"ext",
|
|
535
|
-
"sep",
|
|
536
|
-
// Prepositions and conjunctions
|
|
537
|
-
"and",
|
|
538
|
-
"from",
|
|
539
|
-
"how",
|
|
540
|
-
"pad",
|
|
541
|
-
"bar",
|
|
542
|
-
"non",
|
|
543
|
-
// Additional full words commonly flagged
|
|
544
|
-
"tax",
|
|
545
|
-
"cat",
|
|
546
|
-
"dog",
|
|
547
|
-
"car",
|
|
548
|
-
"bus",
|
|
549
|
-
"web",
|
|
550
|
-
"app",
|
|
551
|
-
"war",
|
|
552
|
-
"law",
|
|
553
|
-
"pay",
|
|
554
|
-
"buy",
|
|
555
|
-
"win",
|
|
556
|
-
"cut",
|
|
557
|
-
"hit",
|
|
558
|
-
"hot",
|
|
559
|
-
"pop",
|
|
560
|
-
"job",
|
|
561
|
-
"age",
|
|
562
|
-
"act",
|
|
563
|
-
"let",
|
|
564
|
-
"lot",
|
|
565
|
-
"bad",
|
|
566
|
-
"big",
|
|
567
|
-
"far",
|
|
568
|
-
"few",
|
|
569
|
-
"own",
|
|
570
|
-
"per",
|
|
571
|
-
"red",
|
|
572
|
-
"low",
|
|
573
|
-
"see",
|
|
574
|
-
"six",
|
|
575
|
-
"ten",
|
|
576
|
-
"way",
|
|
577
|
-
"who",
|
|
578
|
-
"why",
|
|
579
|
-
"yet",
|
|
580
|
-
"via",
|
|
581
|
-
"due",
|
|
582
|
-
"fee",
|
|
583
|
-
"fun",
|
|
584
|
-
"gas",
|
|
585
|
-
"gay",
|
|
586
|
-
"god",
|
|
587
|
-
"gun",
|
|
588
|
-
"guy",
|
|
589
|
-
"ice",
|
|
590
|
-
"ill",
|
|
591
|
-
"kid",
|
|
592
|
-
"mad",
|
|
593
|
-
"man",
|
|
594
|
-
"mix",
|
|
595
|
-
"mom",
|
|
596
|
-
"mrs",
|
|
597
|
-
"nor",
|
|
598
|
-
"odd",
|
|
599
|
-
"oil",
|
|
600
|
-
"pan",
|
|
601
|
-
"pet",
|
|
602
|
-
"pit",
|
|
603
|
-
"pot",
|
|
604
|
-
"pow",
|
|
605
|
-
"pro",
|
|
606
|
-
"raw",
|
|
607
|
-
"rep",
|
|
608
|
-
"rid",
|
|
609
|
-
"sad",
|
|
610
|
-
"sea",
|
|
611
|
-
"sit",
|
|
612
|
-
"sky",
|
|
613
|
-
"son",
|
|
614
|
-
"tea",
|
|
615
|
-
"tie",
|
|
616
|
-
"tip",
|
|
617
|
-
"van",
|
|
618
|
-
"war",
|
|
619
|
-
"win",
|
|
620
|
-
"won"
|
|
621
|
-
]);
|
|
622
|
-
var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
623
|
-
// Standard identifiers
|
|
624
|
-
"id",
|
|
625
|
-
"uid",
|
|
626
|
-
"gid",
|
|
627
|
-
"pid",
|
|
628
|
-
// Loop counters and iterators
|
|
629
|
-
"i",
|
|
630
|
-
"j",
|
|
631
|
-
"k",
|
|
632
|
-
"n",
|
|
633
|
-
"m",
|
|
634
|
-
// Web/Network
|
|
635
|
-
"url",
|
|
636
|
-
"uri",
|
|
637
|
-
"api",
|
|
638
|
-
"cdn",
|
|
639
|
-
"dns",
|
|
640
|
-
"ip",
|
|
641
|
-
"tcp",
|
|
642
|
-
"udp",
|
|
643
|
-
"http",
|
|
644
|
-
"ssl",
|
|
645
|
-
"tls",
|
|
646
|
-
"utm",
|
|
647
|
-
"seo",
|
|
648
|
-
"rss",
|
|
649
|
-
"xhr",
|
|
650
|
-
"ajax",
|
|
651
|
-
"cors",
|
|
652
|
-
"ws",
|
|
653
|
-
"wss",
|
|
654
|
-
// Data formats
|
|
655
|
-
"json",
|
|
656
|
-
"xml",
|
|
657
|
-
"yaml",
|
|
658
|
-
"csv",
|
|
659
|
-
"html",
|
|
660
|
-
"css",
|
|
661
|
-
"svg",
|
|
662
|
-
"pdf",
|
|
663
|
-
// File types & extensions
|
|
664
|
-
"img",
|
|
665
|
-
"txt",
|
|
666
|
-
"doc",
|
|
667
|
-
"docx",
|
|
668
|
-
"xlsx",
|
|
669
|
-
"ppt",
|
|
670
|
-
"md",
|
|
671
|
-
"rst",
|
|
672
|
-
"jpg",
|
|
673
|
-
"png",
|
|
674
|
-
"gif",
|
|
675
|
-
// Databases
|
|
676
|
-
"db",
|
|
677
|
-
"sql",
|
|
678
|
-
"orm",
|
|
679
|
-
"dao",
|
|
680
|
-
"dto",
|
|
681
|
-
"ddb",
|
|
682
|
-
"rds",
|
|
683
|
-
"nosql",
|
|
684
|
-
// File system
|
|
685
|
-
"fs",
|
|
686
|
-
"dir",
|
|
687
|
-
"tmp",
|
|
688
|
-
"src",
|
|
689
|
-
"dst",
|
|
690
|
-
"bin",
|
|
691
|
-
"lib",
|
|
692
|
-
"pkg",
|
|
693
|
-
// Operating system
|
|
694
|
-
"os",
|
|
695
|
-
"env",
|
|
696
|
-
"arg",
|
|
697
|
-
"cli",
|
|
698
|
-
"cmd",
|
|
699
|
-
"exe",
|
|
700
|
-
"cwd",
|
|
701
|
-
"pwd",
|
|
702
|
-
// UI/UX
|
|
703
|
-
"ui",
|
|
704
|
-
"ux",
|
|
705
|
-
"gui",
|
|
706
|
-
"dom",
|
|
707
|
-
"ref",
|
|
708
|
-
// Request/Response
|
|
709
|
-
"req",
|
|
710
|
-
"res",
|
|
711
|
-
"ctx",
|
|
712
|
-
"err",
|
|
713
|
-
"msg",
|
|
714
|
-
"auth",
|
|
715
|
-
// Mathematics/Computing
|
|
716
|
-
"max",
|
|
717
|
-
"min",
|
|
718
|
-
"avg",
|
|
719
|
-
"sum",
|
|
720
|
-
"abs",
|
|
721
|
-
"cos",
|
|
722
|
-
"sin",
|
|
723
|
-
"tan",
|
|
724
|
-
"log",
|
|
725
|
-
"exp",
|
|
726
|
-
"pow",
|
|
727
|
-
"sqrt",
|
|
728
|
-
"std",
|
|
729
|
-
"var",
|
|
730
|
-
"int",
|
|
731
|
-
"num",
|
|
732
|
-
"idx",
|
|
733
|
-
// Time
|
|
734
|
-
"now",
|
|
735
|
-
"utc",
|
|
736
|
-
"tz",
|
|
737
|
-
"ms",
|
|
738
|
-
"sec",
|
|
739
|
-
"hr",
|
|
740
|
-
"min",
|
|
741
|
-
"yr",
|
|
742
|
-
"mo",
|
|
743
|
-
// Common patterns
|
|
744
|
-
"app",
|
|
745
|
-
"cfg",
|
|
746
|
-
"config",
|
|
747
|
-
"init",
|
|
748
|
-
"len",
|
|
749
|
-
"val",
|
|
750
|
-
"str",
|
|
751
|
-
"obj",
|
|
752
|
-
"arr",
|
|
753
|
-
"gen",
|
|
754
|
-
"def",
|
|
755
|
-
"raw",
|
|
756
|
-
"new",
|
|
757
|
-
"old",
|
|
758
|
-
"pre",
|
|
759
|
-
"post",
|
|
760
|
-
"sub",
|
|
761
|
-
"pub",
|
|
762
|
-
// Programming/Framework specific
|
|
763
|
-
"ts",
|
|
764
|
-
"js",
|
|
765
|
-
"jsx",
|
|
766
|
-
"tsx",
|
|
767
|
-
"py",
|
|
768
|
-
"rb",
|
|
769
|
-
"vue",
|
|
770
|
-
"re",
|
|
771
|
-
"fn",
|
|
772
|
-
"fns",
|
|
773
|
-
"mod",
|
|
774
|
-
"opts",
|
|
775
|
-
"dev",
|
|
776
|
-
// Cloud/Infrastructure
|
|
777
|
-
"s3",
|
|
778
|
-
"ec2",
|
|
779
|
-
"sqs",
|
|
780
|
-
"sns",
|
|
781
|
-
"vpc",
|
|
782
|
-
"ami",
|
|
783
|
-
"iam",
|
|
784
|
-
"acl",
|
|
785
|
-
"elb",
|
|
786
|
-
"alb",
|
|
787
|
-
"nlb",
|
|
788
|
-
"aws",
|
|
789
|
-
"ses",
|
|
790
|
-
"gst",
|
|
791
|
-
"cdk",
|
|
792
|
-
"btn",
|
|
793
|
-
"buf",
|
|
794
|
-
"agg",
|
|
795
|
-
"ocr",
|
|
796
|
-
"ai",
|
|
797
|
-
"cf",
|
|
798
|
-
"cfn",
|
|
799
|
-
"ga",
|
|
800
|
-
// Metrics/Performance
|
|
801
|
-
"fcp",
|
|
802
|
-
"lcp",
|
|
803
|
-
"cls",
|
|
804
|
-
"ttfb",
|
|
805
|
-
"tti",
|
|
806
|
-
"fid",
|
|
807
|
-
"fps",
|
|
808
|
-
"qps",
|
|
809
|
-
"rps",
|
|
810
|
-
"tps",
|
|
811
|
-
"wpm",
|
|
812
|
-
// Testing & i18n
|
|
813
|
-
"po",
|
|
814
|
-
"e2e",
|
|
815
|
-
"a11y",
|
|
816
|
-
"i18n",
|
|
817
|
-
"l10n",
|
|
818
|
-
"spy",
|
|
819
|
-
// Domain-specific abbreviations (context-aware)
|
|
820
|
-
"sk",
|
|
821
|
-
"fy",
|
|
822
|
-
"faq",
|
|
823
|
-
"og",
|
|
824
|
-
"seo",
|
|
825
|
-
"cta",
|
|
826
|
-
"roi",
|
|
827
|
-
"kpi",
|
|
828
|
-
"ttl",
|
|
829
|
-
"pct",
|
|
830
|
-
// Technical abbreviations
|
|
831
|
-
"mac",
|
|
832
|
-
"hex",
|
|
833
|
-
"esm",
|
|
834
|
-
"git",
|
|
835
|
-
"rec",
|
|
836
|
-
"loc",
|
|
837
|
-
"dup",
|
|
838
|
-
// Boolean helpers (these are intentional short names)
|
|
839
|
-
"is",
|
|
840
|
-
"has",
|
|
841
|
-
"can",
|
|
842
|
-
"did",
|
|
843
|
-
"was",
|
|
844
|
-
"are",
|
|
845
|
-
// Date/Time context (when in date contexts)
|
|
846
|
-
"d",
|
|
847
|
-
"t",
|
|
848
|
-
"dt",
|
|
849
|
-
// Coverage metrics (industry standard: statements/branches/functions/lines)
|
|
850
|
-
"s",
|
|
851
|
-
"b",
|
|
852
|
-
"f",
|
|
853
|
-
"l",
|
|
854
|
-
// Common media/content abbreviations
|
|
855
|
-
"vid",
|
|
856
|
-
"pic",
|
|
857
|
-
"img",
|
|
858
|
-
"doc",
|
|
859
|
-
"msg"
|
|
860
|
-
]);
|
|
861
|
-
function snakeCaseToCamelCase(str) {
|
|
862
|
-
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
863
|
-
}
|
|
864
|
-
function detectNamingConventions(files, allIssues) {
|
|
865
|
-
const camelCaseCount = allIssues.filter(
|
|
866
|
-
(i) => i.type === "convention-mix"
|
|
867
|
-
).length;
|
|
868
|
-
const totalChecks = files.length * 10;
|
|
869
|
-
if (camelCaseCount / totalChecks > 0.3) {
|
|
870
|
-
return { dominantConvention: "mixed", conventionScore: 0.5 };
|
|
871
|
-
}
|
|
872
|
-
return { dominantConvention: "camelCase", conventionScore: 0.9 };
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
// src/utils/config-loader.ts
|
|
876
|
-
async function loadNamingConfig(files) {
|
|
877
|
-
const rootDir = files.length > 0 ? (0, import_path.dirname)(files[0]) : process.cwd();
|
|
878
|
-
const config = await (0, import_core.loadConfig)(rootDir);
|
|
879
|
-
const consistencyConfig = config?.tools?.["consistency"];
|
|
880
|
-
const customAbbreviations = new Set(
|
|
881
|
-
consistencyConfig?.acceptedAbbreviations || []
|
|
882
|
-
);
|
|
883
|
-
const customShortWords = new Set(consistencyConfig?.shortWords || []);
|
|
884
|
-
const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
|
|
885
|
-
const allAbbreviations = /* @__PURE__ */ new Set([
|
|
886
|
-
...ACCEPTABLE_ABBREVIATIONS,
|
|
887
|
-
...customAbbreviations
|
|
888
|
-
]);
|
|
889
|
-
const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
|
|
890
|
-
return {
|
|
891
|
-
customAbbreviations,
|
|
892
|
-
customShortWords,
|
|
893
|
-
disabledChecks,
|
|
894
|
-
allAbbreviations,
|
|
895
|
-
allShortWords
|
|
896
|
-
};
|
|
897
|
-
}
|
|
898
|
-
|
|
899
285
|
// src/analyzers/naming-ast.ts
|
|
900
|
-
async function analyzeNamingAST(
|
|
901
|
-
const
|
|
902
|
-
const
|
|
903
|
-
const supportedFiles = files.filter(
|
|
904
|
-
(file) => /\.(js|jsx|ts|tsx)$/i.test(file)
|
|
905
|
-
);
|
|
906
|
-
for (const file of supportedFiles) {
|
|
286
|
+
async function analyzeNamingAST(filePaths) {
|
|
287
|
+
const allIssues = [];
|
|
288
|
+
for (const filePath of filePaths) {
|
|
907
289
|
try {
|
|
908
|
-
const ast = parseFile(
|
|
290
|
+
const ast = parseFile(filePath);
|
|
909
291
|
if (!ast) continue;
|
|
910
|
-
const
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
disabledChecks
|
|
916
|
-
);
|
|
917
|
-
issues.push(...fileIssues);
|
|
918
|
-
} catch (error) {
|
|
919
|
-
console.warn(`Skipping ${file} due to parse error:`, error);
|
|
292
|
+
const context = buildCodeContext(filePath, ast);
|
|
293
|
+
const issues = analyzeIdentifiers(ast, filePath, context);
|
|
294
|
+
allIssues.push(...issues);
|
|
295
|
+
} catch (err) {
|
|
296
|
+
void err;
|
|
920
297
|
}
|
|
921
298
|
}
|
|
922
|
-
return
|
|
299
|
+
return allIssues;
|
|
923
300
|
}
|
|
924
|
-
function
|
|
301
|
+
function analyzeIdentifiers(ast, filePath, context) {
|
|
925
302
|
const issues = [];
|
|
926
|
-
const scopeTracker = new ScopeTracker(
|
|
927
|
-
const context = buildCodeContext(file, ast);
|
|
928
|
-
const ancestors = [];
|
|
303
|
+
const scopeTracker = new ScopeTracker();
|
|
929
304
|
traverseAST(ast, {
|
|
930
|
-
enter: (node
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
getLineNumber(param),
|
|
941
|
-
{
|
|
942
|
-
isParameter: true
|
|
943
|
-
}
|
|
944
|
-
);
|
|
945
|
-
} else if (param.type === "ObjectPattern" || param.type === "ArrayPattern") {
|
|
946
|
-
extractIdentifiersFromPattern(param, scopeTracker, true);
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
} else if (node.type === "BlockStatement") {
|
|
951
|
-
scopeTracker.enterScope("block", node);
|
|
952
|
-
} else if (isLoopStatement(node)) {
|
|
953
|
-
scopeTracker.enterScope("loop", node);
|
|
954
|
-
} else if (node.type === "ClassDeclaration") {
|
|
955
|
-
scopeTracker.enterScope("class", node);
|
|
956
|
-
}
|
|
957
|
-
if (node.type === "VariableDeclarator") {
|
|
958
|
-
if (node.id.type === "Identifier") {
|
|
959
|
-
void isCoverageContext(node, ancestors);
|
|
960
|
-
scopeTracker.declareVariable(
|
|
961
|
-
node.id.name,
|
|
962
|
-
node.id,
|
|
963
|
-
getLineNumber(node.id),
|
|
964
|
-
{
|
|
965
|
-
type: "typeAnnotation" in node.id ? node.id.typeAnnotation : null,
|
|
966
|
-
isDestructured: false,
|
|
967
|
-
isLoopVariable: scopeTracker.getCurrentScopeType() === "loop"
|
|
968
|
-
}
|
|
969
|
-
);
|
|
970
|
-
} else if (node.id.type === "ObjectPattern" || node.id.type === "ArrayPattern") {
|
|
971
|
-
extractIdentifiersFromPattern(
|
|
972
|
-
node.id,
|
|
973
|
-
scopeTracker,
|
|
974
|
-
false,
|
|
975
|
-
ancestors
|
|
976
|
-
);
|
|
977
|
-
}
|
|
305
|
+
enter: (node) => {
|
|
306
|
+
if (node.type === "VariableDeclarator" && node.id.type === "Identifier") {
|
|
307
|
+
const isParameter = false;
|
|
308
|
+
const isLoopVariable = isLoopStatement(node.parent?.parent);
|
|
309
|
+
scopeTracker.declareVariable(
|
|
310
|
+
node.id.name,
|
|
311
|
+
node.id,
|
|
312
|
+
getLineNumber(node.id),
|
|
313
|
+
{ isParameter, isLoopVariable }
|
|
314
|
+
);
|
|
978
315
|
}
|
|
979
|
-
if (node.type === "
|
|
980
|
-
|
|
981
|
-
if (
|
|
982
|
-
scopeTracker.
|
|
316
|
+
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
|
|
317
|
+
node.params.forEach((param) => {
|
|
318
|
+
if (param.type === "Identifier") {
|
|
319
|
+
scopeTracker.declareVariable(
|
|
320
|
+
param.name,
|
|
321
|
+
param,
|
|
322
|
+
getLineNumber(param),
|
|
323
|
+
{ isParameter: true }
|
|
324
|
+
);
|
|
325
|
+
} else if (param.type === "ObjectPattern") {
|
|
326
|
+
extractDestructuredIdentifiers(param, true, scopeTracker);
|
|
983
327
|
}
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
},
|
|
987
|
-
leave: (node) => {
|
|
988
|
-
ancestors.pop();
|
|
989
|
-
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "BlockStatement" || isLoopStatement(node) || node.type === "ClassDeclaration") {
|
|
990
|
-
scopeTracker.exitScope();
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
});
|
|
994
|
-
const allVariables = scopeTracker.getAllVariables();
|
|
995
|
-
for (const varInfo of allVariables) {
|
|
996
|
-
const name = varInfo.name;
|
|
997
|
-
const line = varInfo.declarationLine;
|
|
998
|
-
if (disabledChecks.has("single-letter") && name.length === 1) continue;
|
|
999
|
-
if (disabledChecks.has("abbreviation") && name.length <= 3) continue;
|
|
1000
|
-
const isInCoverage = ["s", "b", "f", "l"].includes(name) && varInfo.isDestructured;
|
|
1001
|
-
if (isInCoverage) continue;
|
|
1002
|
-
const functionComplexity = varInfo.node.type === "Identifier" && "parent" in varInfo.node ? calculateComplexity(varInfo.node) : context.complexity;
|
|
1003
|
-
if (isAcceptableInContext(name, context, {
|
|
1004
|
-
isLoopVariable: varInfo.isLoopVariable || allAbbreviations.has(name),
|
|
1005
|
-
isParameter: varInfo.isParameter,
|
|
1006
|
-
isDestructured: varInfo.isDestructured,
|
|
1007
|
-
complexity: functionComplexity
|
|
1008
|
-
})) {
|
|
1009
|
-
continue;
|
|
1010
|
-
}
|
|
1011
|
-
if (name.length === 1 && !allAbbreviations.has(name) && !allShortWords.has(name)) {
|
|
1012
|
-
const isShortLived = scopeTracker.isShortLived(varInfo, 5);
|
|
1013
|
-
if (!isShortLived) {
|
|
1014
|
-
issues.push({
|
|
1015
|
-
file,
|
|
1016
|
-
line,
|
|
1017
|
-
type: "poor-naming",
|
|
1018
|
-
identifier: name,
|
|
1019
|
-
severity: adjustSeverity("minor", context, "poor-naming"),
|
|
1020
|
-
suggestion: `Use descriptive variable name instead of single letter '${name}'`
|
|
1021
328
|
});
|
|
1022
329
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
issues
|
|
1030
|
-
|
|
1031
|
-
line,
|
|
1032
|
-
type: "abbreviation",
|
|
1033
|
-
identifier: name,
|
|
1034
|
-
severity: adjustSeverity("info", context, "abbreviation"),
|
|
1035
|
-
suggestion: `Consider using full word instead of abbreviation '${name}'`
|
|
1036
|
-
});
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
continue;
|
|
1040
|
-
}
|
|
1041
|
-
if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
1042
|
-
if (name.includes("_") && !name.startsWith("_") && name.toLowerCase() === name) {
|
|
1043
|
-
const camelCase = name.replace(
|
|
1044
|
-
/_([a-z])/g,
|
|
1045
|
-
(_, letter) => letter.toUpperCase()
|
|
330
|
+
if ((node.type === "ClassDeclaration" || node.type === "TSInterfaceDeclaration" || node.type === "TSTypeAliasDeclaration") && node.id) {
|
|
331
|
+
checkNamingConvention(
|
|
332
|
+
node.id.name,
|
|
333
|
+
"PascalCase",
|
|
334
|
+
node.id,
|
|
335
|
+
filePath,
|
|
336
|
+
issues,
|
|
337
|
+
context
|
|
1046
338
|
);
|
|
1047
|
-
issues.push({
|
|
1048
|
-
file,
|
|
1049
|
-
line,
|
|
1050
|
-
type: "convention-mix",
|
|
1051
|
-
identifier: name,
|
|
1052
|
-
severity: adjustSeverity("minor", context, "convention-mix"),
|
|
1053
|
-
suggestion: `Use camelCase '${camelCase}' instead of snake_case in TypeScript/JavaScript`
|
|
1054
|
-
});
|
|
1055
339
|
}
|
|
1056
340
|
}
|
|
341
|
+
});
|
|
342
|
+
for (const varInfo of scopeTracker.getVariables()) {
|
|
343
|
+
checkVariableNaming(varInfo, filePath, issues, context);
|
|
1057
344
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
"proxy",
|
|
1079
|
-
"sitemap",
|
|
1080
|
-
"robots",
|
|
1081
|
-
"gtag"
|
|
1082
|
-
].includes(name);
|
|
1083
|
-
const isLanguageKeyword = [
|
|
1084
|
-
"constructor",
|
|
1085
|
-
"toString",
|
|
1086
|
-
"valueOf",
|
|
1087
|
-
"toJSON"
|
|
1088
|
-
].includes(name);
|
|
1089
|
-
const isFrameworkPattern = name.match(
|
|
1090
|
-
/^(goto|fill|click|select|submit|wait|expect)\w*/
|
|
1091
|
-
);
|
|
1092
|
-
const isDescriptivePattern = name.match(
|
|
1093
|
-
/^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/
|
|
1094
|
-
) || name.match(
|
|
1095
|
-
/\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props|Data|Info|Details|State|Status|Response|Result)$/
|
|
1096
|
-
);
|
|
1097
|
-
const capitalCount = (name.match(/[A-Z]/g) || []).length;
|
|
1098
|
-
const isCompoundWord = capitalCount >= 3;
|
|
1099
|
-
if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isReactHook && !isHelperPattern && !isUtilityName && !isDescriptivePattern && !isCompoundWord && !isLanguageKeyword && !isFrameworkPattern) {
|
|
1100
|
-
issues.push({
|
|
1101
|
-
file,
|
|
1102
|
-
line,
|
|
1103
|
-
type: "unclear",
|
|
1104
|
-
identifier: name,
|
|
1105
|
-
severity: adjustSeverity("info", context, "unclear"),
|
|
1106
|
-
suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
|
|
1107
|
-
});
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
345
|
+
return issues;
|
|
346
|
+
}
|
|
347
|
+
function checkNamingConvention(name, convention, node, file, issues, context) {
|
|
348
|
+
let isValid = true;
|
|
349
|
+
if (convention === "PascalCase") {
|
|
350
|
+
isValid = /^[A-Z][a-zA-Z0-9]*$/.test(name);
|
|
351
|
+
} else if (convention === "camelCase") {
|
|
352
|
+
isValid = /^[a-z][a-zA-Z0-9]*$/.test(name);
|
|
353
|
+
} else if (convention === "UPPER_CASE") {
|
|
354
|
+
isValid = /^[A-Z][A-Z0-9_]*$/.test(name);
|
|
355
|
+
}
|
|
356
|
+
if (!isValid) {
|
|
357
|
+
const severity = adjustSeverity(import_core2.Severity.Info, context, "convention-mix");
|
|
358
|
+
issues.push({
|
|
359
|
+
file,
|
|
360
|
+
line: getLineNumber(node),
|
|
361
|
+
type: "convention-mix",
|
|
362
|
+
identifier: name,
|
|
363
|
+
severity,
|
|
364
|
+
suggestion: `Follow ${convention} for this identifier`
|
|
1111
365
|
});
|
|
1112
366
|
}
|
|
1113
|
-
return issues;
|
|
1114
367
|
}
|
|
1115
|
-
function
|
|
1116
|
-
|
|
1117
|
-
if (
|
|
1118
|
-
|
|
368
|
+
function checkVariableNaming(varInfo, file, issues, context) {
|
|
369
|
+
const { name, node, line, options } = varInfo;
|
|
370
|
+
if (isAcceptableInContext(name, context, options)) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (name.length === 1 && !options.isLoopVariable) {
|
|
374
|
+
const severity = adjustSeverity(import_core2.Severity.Minor, context, "poor-naming");
|
|
375
|
+
issues.push({
|
|
376
|
+
file,
|
|
377
|
+
line,
|
|
378
|
+
type: "poor-naming",
|
|
379
|
+
identifier: name,
|
|
380
|
+
severity,
|
|
381
|
+
suggestion: "Use a more descriptive name than a single letter"
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
const vagueNames = [
|
|
385
|
+
"data",
|
|
386
|
+
"info",
|
|
387
|
+
"item",
|
|
388
|
+
"obj",
|
|
389
|
+
"val",
|
|
390
|
+
"tmp",
|
|
391
|
+
"temp",
|
|
392
|
+
"thing",
|
|
393
|
+
"stuff"
|
|
394
|
+
];
|
|
395
|
+
if (vagueNames.includes(name.toLowerCase())) {
|
|
396
|
+
const severity = adjustSeverity(import_core2.Severity.Minor, context, "poor-naming");
|
|
397
|
+
issues.push({
|
|
398
|
+
file,
|
|
399
|
+
line,
|
|
400
|
+
type: "poor-naming",
|
|
401
|
+
identifier: name,
|
|
402
|
+
severity,
|
|
403
|
+
suggestion: `Avoid vague names like '${name}'. What does this data represent?`
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
if (name.length > 1 && name.length <= 3 && !options.isLoopVariable && !isCommonAbbreviation(name)) {
|
|
407
|
+
const severity = adjustSeverity(import_core2.Severity.Info, context, "abbreviation");
|
|
408
|
+
issues.push({
|
|
409
|
+
file,
|
|
410
|
+
line,
|
|
411
|
+
type: "abbreviation",
|
|
412
|
+
identifier: name,
|
|
413
|
+
severity,
|
|
414
|
+
suggestion: "Avoid non-standard abbreviations"
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function isCommonAbbreviation(name) {
|
|
419
|
+
const common = ["id", "db", "fs", "os", "ip", "ui", "ux", "api", "env", "url"];
|
|
420
|
+
return common.includes(name.toLowerCase());
|
|
421
|
+
}
|
|
422
|
+
var ScopeTracker = class {
|
|
423
|
+
constructor() {
|
|
424
|
+
this.variables = [];
|
|
425
|
+
}
|
|
426
|
+
declareVariable(name, node, line, options = {}) {
|
|
427
|
+
this.variables.push({ name, node, line, options });
|
|
428
|
+
}
|
|
429
|
+
getVariables() {
|
|
430
|
+
return this.variables;
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
function extractDestructuredIdentifiers(node, isParameter, scopeTracker) {
|
|
434
|
+
if (node.type === "ObjectPattern") {
|
|
435
|
+
node.properties.forEach((prop) => {
|
|
1119
436
|
if (prop.type === "Property" && prop.value.type === "Identifier") {
|
|
1120
437
|
scopeTracker.declareVariable(
|
|
1121
438
|
prop.value.name,
|
|
@@ -1126,21 +443,11 @@ function extractIdentifiersFromPattern(pattern, scopeTracker, isParameter, ances
|
|
|
1126
443
|
isDestructured: true
|
|
1127
444
|
}
|
|
1128
445
|
);
|
|
1129
|
-
} else if (prop.type === "RestElement" && prop.argument.type === "Identifier") {
|
|
1130
|
-
scopeTracker.declareVariable(
|
|
1131
|
-
prop.argument.name,
|
|
1132
|
-
prop.argument,
|
|
1133
|
-
getLineNumber(prop.argument),
|
|
1134
|
-
{
|
|
1135
|
-
isParameter,
|
|
1136
|
-
isDestructured: true
|
|
1137
|
-
}
|
|
1138
|
-
);
|
|
1139
446
|
}
|
|
1140
|
-
}
|
|
1141
|
-
} else if (
|
|
1142
|
-
for (const element of
|
|
1143
|
-
if (element
|
|
447
|
+
});
|
|
448
|
+
} else if (node.type === "ArrayPattern") {
|
|
449
|
+
for (const element of node.elements) {
|
|
450
|
+
if (element?.type === "Identifier") {
|
|
1144
451
|
scopeTracker.declareVariable(
|
|
1145
452
|
element.name,
|
|
1146
453
|
element,
|
|
@@ -1156,10 +463,10 @@ function extractIdentifiersFromPattern(pattern, scopeTracker, isParameter, ances
|
|
|
1156
463
|
}
|
|
1157
464
|
|
|
1158
465
|
// src/analyzers/naming-python.ts
|
|
1159
|
-
var
|
|
466
|
+
var import_core3 = require("@aiready/core");
|
|
1160
467
|
async function analyzePythonNaming(files) {
|
|
1161
468
|
const issues = [];
|
|
1162
|
-
const parser = (0,
|
|
469
|
+
const parser = (0, import_core3.getParser)("dummy.py");
|
|
1163
470
|
if (!parser) {
|
|
1164
471
|
console.warn("Python parser not available");
|
|
1165
472
|
return issues;
|
|
@@ -1203,7 +510,7 @@ async function analyzePythonNaming(files) {
|
|
|
1203
510
|
return issues;
|
|
1204
511
|
}
|
|
1205
512
|
function checkPythonNaming(identifier, type, file, line) {
|
|
1206
|
-
const parser = (0,
|
|
513
|
+
const parser = (0, import_core3.getParser)("dummy.py");
|
|
1207
514
|
const conventions = parser?.getNamingConventions();
|
|
1208
515
|
if (!conventions) return null;
|
|
1209
516
|
if (conventions.exceptions?.includes(identifier)) {
|
|
@@ -1278,162 +585,63 @@ function toPascalCase(str) {
|
|
|
1278
585
|
}
|
|
1279
586
|
|
|
1280
587
|
// src/analyzers/patterns.ts
|
|
1281
|
-
var
|
|
1282
|
-
|
|
588
|
+
var import_fs2 = require("fs");
|
|
589
|
+
var import_core4 = require("@aiready/core");
|
|
590
|
+
async function analyzePatterns(filePaths) {
|
|
1283
591
|
const issues = [];
|
|
1284
|
-
const
|
|
1285
|
-
|
|
1286
|
-
const
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
const patterns = {
|
|
1294
|
-
tryCatch: [],
|
|
1295
|
-
throwsError: [],
|
|
1296
|
-
returnsNull: [],
|
|
1297
|
-
returnsError: []
|
|
592
|
+
const contents = /* @__PURE__ */ new Map();
|
|
593
|
+
const tryCatchPattern = /try\s*\{/g;
|
|
594
|
+
const catchPattern = /catch\s*\(\s*(\w+)\s*\)/g;
|
|
595
|
+
const styleStats = {
|
|
596
|
+
tryCatch: 0,
|
|
597
|
+
thenCatch: 0,
|
|
598
|
+
asyncAwait: 0,
|
|
599
|
+
commonJs: 0,
|
|
600
|
+
esm: 0
|
|
1298
601
|
};
|
|
1299
|
-
for (const
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
if (content.match(/return \{ error:/)) {
|
|
1311
|
-
patterns.returnsError.push(file);
|
|
602
|
+
for (const filePath of filePaths) {
|
|
603
|
+
try {
|
|
604
|
+
const content = (0, import_fs2.readFileSync)(filePath, "utf-8");
|
|
605
|
+
contents.set(filePath, content);
|
|
606
|
+
if (content.match(tryCatchPattern)) styleStats.tryCatch++;
|
|
607
|
+
if (content.match(/\.catch\s*\(/)) styleStats.thenCatch++;
|
|
608
|
+
if (content.match(/\bawait\b/)) styleStats.asyncAwait++;
|
|
609
|
+
if (content.match(/\brequire\s*\(/)) styleStats.commonJs++;
|
|
610
|
+
if (content.match(/\bimport\b.*\bfrom\b/)) styleStats.esm++;
|
|
611
|
+
} catch (err) {
|
|
612
|
+
void err;
|
|
1312
613
|
}
|
|
1313
614
|
}
|
|
1314
|
-
const
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
if (strategiesUsed > 2) {
|
|
615
|
+
const totalFiles = filePaths.length;
|
|
616
|
+
if (styleStats.tryCatch > 0 && styleStats.thenCatch > 0) {
|
|
617
|
+
const dominant = styleStats.tryCatch >= styleStats.thenCatch ? "try-catch" : ".catch()";
|
|
618
|
+
const minority = dominant === "try-catch" ? ".catch()" : "try-catch";
|
|
1319
619
|
issues.push({
|
|
1320
|
-
files:
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
...patterns.returnsNull,
|
|
1325
|
-
...patterns.returnsError
|
|
1326
|
-
])
|
|
1327
|
-
],
|
|
620
|
+
files: filePaths.filter((f) => {
|
|
621
|
+
const c = contents.get(f) || "";
|
|
622
|
+
return minority === "try-catch" ? c.match(tryCatchPattern) : c.match(/\.catch\s*\(/);
|
|
623
|
+
}),
|
|
1328
624
|
type: "error-handling",
|
|
1329
|
-
description:
|
|
1330
|
-
examples: [
|
|
1331
|
-
|
|
1332
|
-
patterns.throwsError.length > 0 ? `Throws errors in ${patterns.throwsError.length} files` : "",
|
|
1333
|
-
patterns.returnsNull.length > 0 ? `Returns null in ${patterns.returnsNull.length} files` : "",
|
|
1334
|
-
patterns.returnsError.length > 0 ? `Returns error objects in ${patterns.returnsError.length} files` : ""
|
|
1335
|
-
].filter((e) => e),
|
|
1336
|
-
severity: "major"
|
|
625
|
+
description: `Mixed error handling styles: codebase primarily uses ${dominant}, but found ${minority} in some files.`,
|
|
626
|
+
examples: [dominant, minority],
|
|
627
|
+
severity: import_core4.Severity.Minor
|
|
1337
628
|
});
|
|
1338
629
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
async function analyzeAsyncPatterns(files) {
|
|
1342
|
-
const patterns = {
|
|
1343
|
-
asyncAwait: [],
|
|
1344
|
-
promises: [],
|
|
1345
|
-
callbacks: []
|
|
1346
|
-
};
|
|
1347
|
-
for (const file of files) {
|
|
1348
|
-
const content = await (0, import_core3.readFileContent)(file);
|
|
1349
|
-
if (content.match(/async\s+(function|\(|[a-zA-Z])/)) {
|
|
1350
|
-
patterns.asyncAwait.push(file);
|
|
1351
|
-
}
|
|
1352
|
-
if (content.match(/\.then\(/) || content.match(/\.catch\(/)) {
|
|
1353
|
-
patterns.promises.push(file);
|
|
1354
|
-
}
|
|
1355
|
-
if (content.match(/callback\s*\(/) || content.match(/\(\s*err\s*,/)) {
|
|
1356
|
-
patterns.callbacks.push(file);
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
const issues = [];
|
|
1360
|
-
if (patterns.callbacks.length > 0 && patterns.asyncAwait.length > 0) {
|
|
1361
|
-
issues.push({
|
|
1362
|
-
files: [...patterns.callbacks, ...patterns.asyncAwait],
|
|
1363
|
-
type: "async-style",
|
|
1364
|
-
description: "Mixed async patterns: callbacks and async/await",
|
|
1365
|
-
examples: [
|
|
1366
|
-
`Callbacks found in: ${patterns.callbacks.slice(0, 3).join(", ")}`,
|
|
1367
|
-
`Async/await used in: ${patterns.asyncAwait.slice(0, 3).join(", ")}`
|
|
1368
|
-
],
|
|
1369
|
-
severity: "minor"
|
|
1370
|
-
});
|
|
1371
|
-
}
|
|
1372
|
-
if (patterns.promises.length > patterns.asyncAwait.length * 0.3 && patterns.asyncAwait.length > 0) {
|
|
1373
|
-
issues.push({
|
|
1374
|
-
files: patterns.promises,
|
|
1375
|
-
type: "async-style",
|
|
1376
|
-
description: "Consider using async/await instead of promise chains for consistency",
|
|
1377
|
-
examples: patterns.promises.slice(0, 5),
|
|
1378
|
-
severity: "info"
|
|
1379
|
-
});
|
|
1380
|
-
}
|
|
1381
|
-
return issues;
|
|
1382
|
-
}
|
|
1383
|
-
async function analyzeImportStyles(files) {
|
|
1384
|
-
const patterns = {
|
|
1385
|
-
esModules: [],
|
|
1386
|
-
commonJS: [],
|
|
1387
|
-
mixed: []
|
|
1388
|
-
};
|
|
1389
|
-
for (const file of files) {
|
|
1390
|
-
const content = await (0, import_core3.readFileContent)(file);
|
|
1391
|
-
const hasESM = content.match(/^import\s+/m);
|
|
1392
|
-
const hasCJS = hasActualRequireCalls(content);
|
|
1393
|
-
if (hasESM && hasCJS) {
|
|
1394
|
-
patterns.mixed.push(file);
|
|
1395
|
-
} else if (hasESM) {
|
|
1396
|
-
patterns.esModules.push(file);
|
|
1397
|
-
} else if (hasCJS) {
|
|
1398
|
-
patterns.commonJS.push(file);
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
const issues = [];
|
|
1402
|
-
if (patterns.mixed.length > 0) {
|
|
630
|
+
if (styleStats.commonJs > 0 && styleStats.esm > 0) {
|
|
631
|
+
const minority = styleStats.esm >= styleStats.commonJs ? "CommonJS (require)" : "ESM (import)";
|
|
1403
632
|
issues.push({
|
|
1404
|
-
files:
|
|
633
|
+
files: filePaths.filter((f) => {
|
|
634
|
+
const c = contents.get(f) || "";
|
|
635
|
+
return minority === "CommonJS (require)" ? c.match(/\brequire\s*\(/) : c.match(/\bimport\b/);
|
|
636
|
+
}),
|
|
1405
637
|
type: "import-style",
|
|
1406
|
-
description:
|
|
1407
|
-
examples:
|
|
1408
|
-
severity:
|
|
638
|
+
description: `Mixed module systems: found both ESM and CommonJS.`,
|
|
639
|
+
examples: ['import X from "y"', 'const X = require("y")'],
|
|
640
|
+
severity: import_core4.Severity.Major
|
|
1409
641
|
});
|
|
1410
642
|
}
|
|
1411
|
-
if (patterns.esModules.length > 0 && patterns.commonJS.length > 0) {
|
|
1412
|
-
const ratio = patterns.commonJS.length / (patterns.esModules.length + patterns.commonJS.length);
|
|
1413
|
-
if (ratio > 0.2 && ratio < 0.8) {
|
|
1414
|
-
issues.push({
|
|
1415
|
-
files: [...patterns.esModules, ...patterns.commonJS],
|
|
1416
|
-
type: "import-style",
|
|
1417
|
-
description: "Inconsistent import styles across project",
|
|
1418
|
-
examples: [
|
|
1419
|
-
`ES modules: ${patterns.esModules.length} files`,
|
|
1420
|
-
`CommonJS: ${patterns.commonJS.length} files`
|
|
1421
|
-
],
|
|
1422
|
-
severity: "minor"
|
|
1423
|
-
});
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
643
|
return issues;
|
|
1427
644
|
}
|
|
1428
|
-
function hasActualRequireCalls(content) {
|
|
1429
|
-
let cleaned = content.replace(/\/\/.*$/gm, "");
|
|
1430
|
-
cleaned = cleaned.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
1431
|
-
cleaned = cleaned.replace(/"[^"\n]*"/g, '""');
|
|
1432
|
-
cleaned = cleaned.replace(/'[^'\n]*'/g, "''");
|
|
1433
|
-
cleaned = cleaned.replace(/`[^`]*`/g, "``");
|
|
1434
|
-
cleaned = cleaned.replace(/\/[^/\n]*require[^/\n]*\/[gimsuvy]*/g, "");
|
|
1435
|
-
return /require\s*\(/.test(cleaned);
|
|
1436
|
-
}
|
|
1437
645
|
|
|
1438
646
|
// src/analyzer.ts
|
|
1439
647
|
async function analyzeConsistency(options) {
|
|
@@ -1442,11 +650,11 @@ async function analyzeConsistency(options) {
|
|
|
1442
650
|
checkPatterns = true,
|
|
1443
651
|
checkArchitecture = false,
|
|
1444
652
|
// Not implemented yet
|
|
1445
|
-
minSeverity =
|
|
653
|
+
minSeverity = import_core5.Severity.Info,
|
|
1446
654
|
...scanOptions
|
|
1447
655
|
} = options;
|
|
1448
656
|
void checkArchitecture;
|
|
1449
|
-
const filePaths = await (0,
|
|
657
|
+
const filePaths = await (0, import_core5.scanFiles)(scanOptions);
|
|
1450
658
|
const tsJsFiles = filePaths.filter((f) => /\.(ts|tsx|js|jsx)$/i.test(f));
|
|
1451
659
|
const pythonFiles = filePaths.filter((f) => /\.py$/i.test(f));
|
|
1452
660
|
let namingIssues = [];
|
|
@@ -1463,9 +671,9 @@ async function analyzeConsistency(options) {
|
|
|
1463
671
|
continue;
|
|
1464
672
|
}
|
|
1465
673
|
const consistencyIssue = {
|
|
1466
|
-
type: issue.type === "convention-mix" ?
|
|
674
|
+
type: issue.type === "convention-mix" ? import_core5.IssueType.NamingInconsistency : import_core5.IssueType.NamingQuality,
|
|
1467
675
|
category: "naming",
|
|
1468
|
-
severity: issue.severity,
|
|
676
|
+
severity: getSeverityEnum(issue.severity),
|
|
1469
677
|
message: `${issue.type}: ${issue.identifier}`,
|
|
1470
678
|
location: {
|
|
1471
679
|
file: issue.file,
|
|
@@ -1484,9 +692,9 @@ async function analyzeConsistency(options) {
|
|
|
1484
692
|
continue;
|
|
1485
693
|
}
|
|
1486
694
|
const consistencyIssue = {
|
|
1487
|
-
type:
|
|
695
|
+
type: import_core5.IssueType.PatternInconsistency,
|
|
1488
696
|
category: "patterns",
|
|
1489
|
-
severity: issue.severity,
|
|
697
|
+
severity: getSeverityEnum(issue.severity),
|
|
1490
698
|
message: issue.description,
|
|
1491
699
|
location: {
|
|
1492
700
|
file: issue.files[0] || "multiple files",
|
|
@@ -1513,16 +721,17 @@ async function analyzeConsistency(options) {
|
|
|
1513
721
|
});
|
|
1514
722
|
}
|
|
1515
723
|
results.sort((fileResultA, fileResultB) => {
|
|
1516
|
-
const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
|
|
1517
724
|
const maxSeverityA = Math.min(
|
|
1518
|
-
...fileResultA.issues.map(
|
|
1519
|
-
|
|
1520
|
-
|
|
725
|
+
...fileResultA.issues.map((i) => {
|
|
726
|
+
const val = getSeverityLevel(i.severity);
|
|
727
|
+
return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
|
|
728
|
+
})
|
|
1521
729
|
);
|
|
1522
730
|
const maxSeverityB = Math.min(
|
|
1523
|
-
...fileResultB.issues.map(
|
|
1524
|
-
|
|
1525
|
-
|
|
731
|
+
...fileResultB.issues.map((i) => {
|
|
732
|
+
const val = getSeverityLevel(i.severity);
|
|
733
|
+
return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
|
|
734
|
+
})
|
|
1526
735
|
);
|
|
1527
736
|
if (maxSeverityA !== maxSeverityB) {
|
|
1528
737
|
return maxSeverityA - maxSeverityB;
|
|
@@ -1548,16 +757,52 @@ async function analyzeConsistency(options) {
|
|
|
1548
757
|
recommendations
|
|
1549
758
|
};
|
|
1550
759
|
}
|
|
760
|
+
function getSeverityLevel(s) {
|
|
761
|
+
if (s === import_core5.Severity.Critical || s === "critical") return 4;
|
|
762
|
+
if (s === import_core5.Severity.Major || s === "major") return 3;
|
|
763
|
+
if (s === import_core5.Severity.Minor || s === "minor") return 2;
|
|
764
|
+
if (s === import_core5.Severity.Info || s === "info") return 1;
|
|
765
|
+
return 0;
|
|
766
|
+
}
|
|
767
|
+
function getSeverityEnum(s) {
|
|
768
|
+
const val = getSeverityLevel(s);
|
|
769
|
+
switch (val) {
|
|
770
|
+
case 4:
|
|
771
|
+
return import_core5.Severity.Critical;
|
|
772
|
+
case 3:
|
|
773
|
+
return import_core5.Severity.Major;
|
|
774
|
+
case 2:
|
|
775
|
+
return import_core5.Severity.Minor;
|
|
776
|
+
case 1:
|
|
777
|
+
return import_core5.Severity.Info;
|
|
778
|
+
default:
|
|
779
|
+
return import_core5.Severity.Info;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
1551
782
|
function shouldIncludeSeverity(severity, minSeverity) {
|
|
1552
|
-
|
|
1553
|
-
return severityLevels[severity] >= severityLevels[minSeverity];
|
|
783
|
+
return getSeverityLevel(severity) >= getSeverityLevel(minSeverity);
|
|
1554
784
|
}
|
|
1555
785
|
function calculateConsistencyScore(issues) {
|
|
1556
|
-
|
|
1557
|
-
const
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
786
|
+
let totalWeight = 0;
|
|
787
|
+
for (const issue of issues) {
|
|
788
|
+
const val = getSeverityLevel(issue.severity);
|
|
789
|
+
switch (val) {
|
|
790
|
+
case 4:
|
|
791
|
+
totalWeight += 10;
|
|
792
|
+
break;
|
|
793
|
+
case 3:
|
|
794
|
+
totalWeight += 5;
|
|
795
|
+
break;
|
|
796
|
+
case 2:
|
|
797
|
+
totalWeight += 2;
|
|
798
|
+
break;
|
|
799
|
+
case 1:
|
|
800
|
+
totalWeight += 1;
|
|
801
|
+
break;
|
|
802
|
+
default:
|
|
803
|
+
totalWeight += 1;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
1561
806
|
return Math.max(0, 1 - totalWeight / 100);
|
|
1562
807
|
}
|
|
1563
808
|
function generateRecommendations(namingIssues, patternIssues) {
|
|
@@ -1611,243 +856,76 @@ function generateRecommendations(namingIssues, patternIssues) {
|
|
|
1611
856
|
}
|
|
1612
857
|
|
|
1613
858
|
// src/analyzers/naming.ts
|
|
1614
|
-
var
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
const { customAbbreviations, customShortWords, disabledChecks } = await loadNamingConfig(files);
|
|
1618
|
-
for (const file of files) {
|
|
1619
|
-
const content = await (0, import_core5.readFileContent)(file);
|
|
1620
|
-
const fileIssues = analyzeFileNaming(
|
|
1621
|
-
file,
|
|
1622
|
-
content,
|
|
1623
|
-
customAbbreviations,
|
|
1624
|
-
customShortWords,
|
|
1625
|
-
disabledChecks
|
|
1626
|
-
);
|
|
1627
|
-
issues.push(...fileIssues);
|
|
1628
|
-
}
|
|
1629
|
-
return issues;
|
|
1630
|
-
}
|
|
1631
|
-
function analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks) {
|
|
859
|
+
var import_fs3 = require("fs");
|
|
860
|
+
var import_core6 = require("@aiready/core");
|
|
861
|
+
async function analyzeNaming(filePaths) {
|
|
1632
862
|
const issues = [];
|
|
1633
|
-
const
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
const getContextWindow = (index, windowSize = 3) => {
|
|
1641
|
-
const start = Math.max(0, index - windowSize);
|
|
1642
|
-
const end = Math.min(lines.length, index + windowSize + 1);
|
|
1643
|
-
return lines.slice(start, end).join("\n");
|
|
1644
|
-
};
|
|
1645
|
-
const isShortLivedVariable = (varName, declarationIndex) => {
|
|
1646
|
-
const searchRange = 5;
|
|
1647
|
-
const endIndex = Math.min(lines.length, declarationIndex + searchRange + 1);
|
|
1648
|
-
let usageCount = 0;
|
|
1649
|
-
for (let i = declarationIndex; i < endIndex; i++) {
|
|
1650
|
-
const regex = new RegExp(`\\b${varName}\\b`, "g");
|
|
1651
|
-
const matches = lines[i].match(regex);
|
|
1652
|
-
if (matches) {
|
|
1653
|
-
usageCount += matches.length;
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
return usageCount >= 2 && usageCount <= 3;
|
|
1657
|
-
};
|
|
1658
|
-
lines.forEach((line, index) => {
|
|
1659
|
-
const lineNumber = index + 1;
|
|
1660
|
-
const contextWindow = getContextWindow(index);
|
|
1661
|
-
if (!disabledChecks.has("single-letter")) {
|
|
1662
|
-
const singleLetterMatches = line.matchAll(
|
|
1663
|
-
/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi
|
|
1664
|
-
);
|
|
1665
|
-
for (const match of singleLetterMatches) {
|
|
1666
|
-
const letter = match[1].toLowerCase();
|
|
1667
|
-
const isCoverageContext2 = /coverage|summary|metrics|pct|percent/i.test(line) || /\.(?:statements|branches|functions|lines)\.pct/i.test(line);
|
|
1668
|
-
if (isCoverageContext2 && ["s", "b", "f", "l"].includes(letter)) {
|
|
1669
|
-
continue;
|
|
1670
|
-
}
|
|
1671
|
-
const isInLoopContext = line.includes("for") || /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) || line.includes("=>") || // Arrow function
|
|
1672
|
-
/\w+\s*=>\s*/.test(line);
|
|
1673
|
-
const isI18nContext = line.includes("useTranslation") || line.includes("i18n.t") || /\bt\s*\(['"]/.test(line);
|
|
1674
|
-
const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
1675
|
-
/[a-z]\s*=>/.test(line) || // s => on same line
|
|
1676
|
-
// Multi-line arrow function detection: look for pattern in context window
|
|
1677
|
-
new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
|
|
1678
|
-
new RegExp(
|
|
1679
|
-
`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`
|
|
1680
|
-
).test(lines[index - 1] || "") && /=>/.test(contextWindow);
|
|
1681
|
-
const isShortLived = isShortLivedVariable(letter, index);
|
|
1682
|
-
if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
|
|
1683
|
-
if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
|
|
1684
|
-
continue;
|
|
1685
|
-
}
|
|
863
|
+
for (const filePath of filePaths) {
|
|
864
|
+
try {
|
|
865
|
+
const content = (0, import_fs3.readFileSync)(filePath, "utf-8");
|
|
866
|
+
const lines = content.split("\n");
|
|
867
|
+
lines.forEach((line, index) => {
|
|
868
|
+
const singleLetterMatch = line.match(/\b(const|let|var)\s+([a-hj-km-np-zA-Z])\s*=/);
|
|
869
|
+
if (singleLetterMatch) {
|
|
1686
870
|
issues.push({
|
|
1687
|
-
file,
|
|
1688
|
-
line:
|
|
871
|
+
file: filePath,
|
|
872
|
+
line: index + 1,
|
|
1689
873
|
type: "poor-naming",
|
|
1690
|
-
identifier:
|
|
1691
|
-
severity:
|
|
1692
|
-
suggestion:
|
|
874
|
+
identifier: singleLetterMatch[2],
|
|
875
|
+
severity: import_core6.Severity.Minor,
|
|
876
|
+
suggestion: "Use a more descriptive name than a single letter"
|
|
1693
877
|
});
|
|
1694
878
|
}
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
if (allAbbreviations.has(abbrev)) {
|
|
1707
|
-
continue;
|
|
1708
|
-
}
|
|
1709
|
-
const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
1710
|
-
new RegExp(`\\b${abbrev}\\s*=>`).test(line) || // s => on same line
|
|
1711
|
-
// Multi-line arrow function: check context window
|
|
1712
|
-
new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
|
|
1713
|
-
new RegExp(
|
|
1714
|
-
`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`
|
|
1715
|
-
).test(lines[index - 1] || "") && new RegExp(`^\\s*${abbrev}\\s*=>`).test(line);
|
|
1716
|
-
if (isArrowFunctionParam) {
|
|
1717
|
-
continue;
|
|
1718
|
-
}
|
|
1719
|
-
if (abbrev.length <= 2) {
|
|
1720
|
-
const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
|
|
1721
|
-
if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
|
|
1722
|
-
continue;
|
|
1723
|
-
}
|
|
1724
|
-
const isUserContext = /user|auth|account/i.test(line);
|
|
1725
|
-
if (isUserContext && abbrev === "u") {
|
|
1726
|
-
continue;
|
|
879
|
+
if (filePath.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
880
|
+
const snakeCaseMatch = line.match(/\b(const|let|var|function)\s+([a-z]+_[a-z0-9_]+)\b/);
|
|
881
|
+
if (snakeCaseMatch) {
|
|
882
|
+
issues.push({
|
|
883
|
+
file: filePath,
|
|
884
|
+
line: index + 1,
|
|
885
|
+
type: "convention-mix",
|
|
886
|
+
identifier: snakeCaseMatch[2],
|
|
887
|
+
severity: import_core6.Severity.Info,
|
|
888
|
+
suggestion: "Use camelCase instead of snake_case in TypeScript/JavaScript"
|
|
889
|
+
});
|
|
1727
890
|
}
|
|
1728
891
|
}
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
void camelCaseVars;
|
|
1744
|
-
const snakeCaseVars = line.match(
|
|
1745
|
-
/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/
|
|
1746
|
-
);
|
|
1747
|
-
if (snakeCaseVars) {
|
|
1748
|
-
issues.push({
|
|
1749
|
-
file,
|
|
1750
|
-
line: lineNumber,
|
|
1751
|
-
type: "convention-mix",
|
|
1752
|
-
identifier: snakeCaseVars[1],
|
|
1753
|
-
severity: "minor",
|
|
1754
|
-
suggestion: `Use camelCase '${snakeCaseToCamelCase(snakeCaseVars[1])}' instead of snake_case in TypeScript/JavaScript`
|
|
1755
|
-
});
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
1758
|
-
if (!disabledChecks.has("unclear")) {
|
|
1759
|
-
const booleanMatches = line.matchAll(
|
|
1760
|
-
/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi
|
|
1761
|
-
);
|
|
1762
|
-
for (const match of booleanMatches) {
|
|
1763
|
-
const name = match[1];
|
|
1764
|
-
if (!name.match(/^(is|has|should|can|will|did)/i)) {
|
|
1765
|
-
issues.push({
|
|
1766
|
-
file,
|
|
1767
|
-
line: lineNumber,
|
|
1768
|
-
type: "unclear",
|
|
1769
|
-
identifier: name,
|
|
1770
|
-
severity: "info",
|
|
1771
|
-
suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
|
|
1772
|
-
});
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
}
|
|
1776
|
-
if (!disabledChecks.has("unclear")) {
|
|
1777
|
-
const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
|
|
1778
|
-
for (const match of functionMatches) {
|
|
1779
|
-
const name = match[1];
|
|
1780
|
-
const isKeyword = [
|
|
1781
|
-
"for",
|
|
1782
|
-
"if",
|
|
1783
|
-
"else",
|
|
1784
|
-
"while",
|
|
1785
|
-
"do",
|
|
1786
|
-
"switch",
|
|
1787
|
-
"case",
|
|
1788
|
-
"break",
|
|
1789
|
-
"continue",
|
|
1790
|
-
"return",
|
|
1791
|
-
"throw",
|
|
1792
|
-
"try",
|
|
1793
|
-
"catch",
|
|
1794
|
-
"finally",
|
|
1795
|
-
"with",
|
|
1796
|
-
"yield",
|
|
1797
|
-
"await"
|
|
1798
|
-
].includes(name);
|
|
1799
|
-
if (isKeyword) {
|
|
1800
|
-
continue;
|
|
1801
|
-
}
|
|
1802
|
-
const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(
|
|
1803
|
-
name
|
|
1804
|
-
);
|
|
1805
|
-
if (isEntryPoint) {
|
|
1806
|
-
continue;
|
|
1807
|
-
}
|
|
1808
|
-
const isFactoryPattern = name.match(
|
|
1809
|
-
/(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/
|
|
1810
|
-
);
|
|
1811
|
-
const isEventHandler = name.match(/^on[A-Z]/);
|
|
1812
|
-
const isDescriptiveLong = name.length > 15;
|
|
1813
|
-
const isReactHook = name.match(/^use[A-Z]/);
|
|
1814
|
-
const isDescriptivePattern = name.match(
|
|
1815
|
-
/^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/
|
|
1816
|
-
) || name.match(
|
|
1817
|
-
/\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props|Data|Info|Details|State|Status|Response|Result)$/
|
|
1818
|
-
);
|
|
1819
|
-
const isHelperPattern = name.match(/^(to|from|with|without|for|as|into)\w+/) || // toMetadata, withLogger, forPath
|
|
1820
|
-
name.match(/^\w+(To|From|With|Without|For|As|Into)\w*$/);
|
|
1821
|
-
const isUtilityName = [
|
|
1822
|
-
"cn",
|
|
1823
|
-
"proxy",
|
|
1824
|
-
"sitemap",
|
|
1825
|
-
"robots",
|
|
1826
|
-
"gtag"
|
|
1827
|
-
].includes(name);
|
|
1828
|
-
const capitalCount = (name.match(/[A-Z]/g) || []).length;
|
|
1829
|
-
const isCompoundWord = capitalCount >= 3;
|
|
1830
|
-
const hasActionVerb = name.match(
|
|
1831
|
-
/^(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)/
|
|
1832
|
-
);
|
|
1833
|
-
if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord && !isHelperPattern && !isUtilityName && !isReactHook) {
|
|
1834
|
-
issues.push({
|
|
1835
|
-
file,
|
|
1836
|
-
line: lineNumber,
|
|
1837
|
-
type: "unclear",
|
|
1838
|
-
identifier: name,
|
|
1839
|
-
severity: "info",
|
|
1840
|
-
suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
|
|
1841
|
-
});
|
|
892
|
+
const shortNameMatch = line.match(/\b(const|let|var)\s+([a-zA-Z0-9]{2,3})\s*=/);
|
|
893
|
+
if (shortNameMatch) {
|
|
894
|
+
const name = shortNameMatch[2].toLowerCase();
|
|
895
|
+
const vagueNames = ["obj", "val", "tmp", "res", "ret", "data"];
|
|
896
|
+
if (vagueNames.includes(name)) {
|
|
897
|
+
issues.push({
|
|
898
|
+
file: filePath,
|
|
899
|
+
line: index + 1,
|
|
900
|
+
type: "poor-naming",
|
|
901
|
+
identifier: name,
|
|
902
|
+
severity: import_core6.Severity.Minor,
|
|
903
|
+
suggestion: `Avoid vague names like '${name}'`
|
|
904
|
+
});
|
|
905
|
+
}
|
|
1842
906
|
}
|
|
1843
|
-
}
|
|
907
|
+
});
|
|
908
|
+
} catch (err) {
|
|
909
|
+
void err;
|
|
1844
910
|
}
|
|
1845
|
-
}
|
|
911
|
+
}
|
|
1846
912
|
return issues;
|
|
1847
913
|
}
|
|
1848
914
|
|
|
915
|
+
// src/analyzers/naming-constants.ts
|
|
916
|
+
function detectNamingConventions(files, allIssues) {
|
|
917
|
+
const camelCaseCount = allIssues.filter(
|
|
918
|
+
(i) => i.type === "convention-mix"
|
|
919
|
+
).length;
|
|
920
|
+
const totalChecks = files.length * 10;
|
|
921
|
+
if (camelCaseCount / totalChecks > 0.3) {
|
|
922
|
+
return { dominantConvention: "mixed", conventionScore: 0.5 };
|
|
923
|
+
}
|
|
924
|
+
return { dominantConvention: "camelCase", conventionScore: 0.9 };
|
|
925
|
+
}
|
|
926
|
+
|
|
1849
927
|
// src/scoring.ts
|
|
1850
|
-
var
|
|
928
|
+
var import_core7 = require("@aiready/core");
|
|
1851
929
|
function calculateConsistencyScore2(issues, totalFilesAnalyzed, costConfig) {
|
|
1852
930
|
void costConfig;
|
|
1853
931
|
const criticalIssues = issues.filter((i) => i.severity === "critical").length;
|
|
@@ -1923,7 +1001,7 @@ function calculateConsistencyScore2(issues, totalFilesAnalyzed, costConfig) {
|
|
|
1923
1001
|
priority: "low"
|
|
1924
1002
|
});
|
|
1925
1003
|
}
|
|
1926
|
-
const productivityImpact = (0,
|
|
1004
|
+
const productivityImpact = (0, import_core7.calculateProductivityImpact)(issues);
|
|
1927
1005
|
return {
|
|
1928
1006
|
toolName: "consistency",
|
|
1929
1007
|
score,
|