@aiready/consistency 0.16.2 → 0.16.5
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 +9 -9
- 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-IXBC6GVT.mjs +832 -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 +288 -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 +336 -1241
- package/dist/index.mjs +63 -229
- package/package.json +2 -2
- package/src/__tests__/contract.test.ts +18 -2
- package/src/analyzer.ts +63 -28
- package/src/analyzers/naming-ast.ts +204 -328
- package/src/analyzers/naming.ts +58 -362
- package/src/analyzers/patterns.ts +57 -224
- package/src/types.ts +16 -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,168 @@ 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 = [
|
|
420
|
+
"id",
|
|
421
|
+
"db",
|
|
422
|
+
"fs",
|
|
423
|
+
"os",
|
|
424
|
+
"ip",
|
|
425
|
+
"ui",
|
|
426
|
+
"ux",
|
|
427
|
+
"api",
|
|
428
|
+
"env",
|
|
429
|
+
"url"
|
|
430
|
+
];
|
|
431
|
+
return common.includes(name.toLowerCase());
|
|
432
|
+
}
|
|
433
|
+
var ScopeTracker = class {
|
|
434
|
+
constructor() {
|
|
435
|
+
this.variables = [];
|
|
436
|
+
}
|
|
437
|
+
declareVariable(name, node, line, options = {}) {
|
|
438
|
+
this.variables.push({ name, node, line, options });
|
|
439
|
+
}
|
|
440
|
+
getVariables() {
|
|
441
|
+
return this.variables;
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
function extractDestructuredIdentifiers(node, isParameter, scopeTracker) {
|
|
445
|
+
if (node.type === "ObjectPattern") {
|
|
446
|
+
node.properties.forEach((prop) => {
|
|
1119
447
|
if (prop.type === "Property" && prop.value.type === "Identifier") {
|
|
1120
448
|
scopeTracker.declareVariable(
|
|
1121
449
|
prop.value.name,
|
|
@@ -1126,21 +454,11 @@ function extractIdentifiersFromPattern(pattern, scopeTracker, isParameter, ances
|
|
|
1126
454
|
isDestructured: true
|
|
1127
455
|
}
|
|
1128
456
|
);
|
|
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
457
|
}
|
|
1140
|
-
}
|
|
1141
|
-
} else if (
|
|
1142
|
-
for (const element of
|
|
1143
|
-
if (element
|
|
458
|
+
});
|
|
459
|
+
} else if (node.type === "ArrayPattern") {
|
|
460
|
+
for (const element of node.elements) {
|
|
461
|
+
if (element?.type === "Identifier") {
|
|
1144
462
|
scopeTracker.declareVariable(
|
|
1145
463
|
element.name,
|
|
1146
464
|
element,
|
|
@@ -1156,10 +474,10 @@ function extractIdentifiersFromPattern(pattern, scopeTracker, isParameter, ances
|
|
|
1156
474
|
}
|
|
1157
475
|
|
|
1158
476
|
// src/analyzers/naming-python.ts
|
|
1159
|
-
var
|
|
477
|
+
var import_core3 = require("@aiready/core");
|
|
1160
478
|
async function analyzePythonNaming(files) {
|
|
1161
479
|
const issues = [];
|
|
1162
|
-
const parser = (0,
|
|
480
|
+
const parser = (0, import_core3.getParser)("dummy.py");
|
|
1163
481
|
if (!parser) {
|
|
1164
482
|
console.warn("Python parser not available");
|
|
1165
483
|
return issues;
|
|
@@ -1203,7 +521,7 @@ async function analyzePythonNaming(files) {
|
|
|
1203
521
|
return issues;
|
|
1204
522
|
}
|
|
1205
523
|
function checkPythonNaming(identifier, type, file, line) {
|
|
1206
|
-
const parser = (0,
|
|
524
|
+
const parser = (0, import_core3.getParser)("dummy.py");
|
|
1207
525
|
const conventions = parser?.getNamingConventions();
|
|
1208
526
|
if (!conventions) return null;
|
|
1209
527
|
if (conventions.exceptions?.includes(identifier)) {
|
|
@@ -1278,162 +596,63 @@ function toPascalCase(str) {
|
|
|
1278
596
|
}
|
|
1279
597
|
|
|
1280
598
|
// src/analyzers/patterns.ts
|
|
1281
|
-
var
|
|
1282
|
-
|
|
599
|
+
var import_fs2 = require("fs");
|
|
600
|
+
var import_core4 = require("@aiready/core");
|
|
601
|
+
async function analyzePatterns(filePaths) {
|
|
1283
602
|
const issues = [];
|
|
1284
|
-
const
|
|
1285
|
-
|
|
1286
|
-
const
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
const patterns = {
|
|
1294
|
-
tryCatch: [],
|
|
1295
|
-
throwsError: [],
|
|
1296
|
-
returnsNull: [],
|
|
1297
|
-
returnsError: []
|
|
603
|
+
const contents = /* @__PURE__ */ new Map();
|
|
604
|
+
const tryCatchPattern = /try\s*\{/g;
|
|
605
|
+
const catchPattern = /catch\s*\(\s*(\w+)\s*\)/g;
|
|
606
|
+
const styleStats = {
|
|
607
|
+
tryCatch: 0,
|
|
608
|
+
thenCatch: 0,
|
|
609
|
+
asyncAwait: 0,
|
|
610
|
+
commonJs: 0,
|
|
611
|
+
esm: 0
|
|
1298
612
|
};
|
|
1299
|
-
for (const
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
if (content.match(/return \{ error:/)) {
|
|
1311
|
-
patterns.returnsError.push(file);
|
|
613
|
+
for (const filePath of filePaths) {
|
|
614
|
+
try {
|
|
615
|
+
const content = (0, import_fs2.readFileSync)(filePath, "utf-8");
|
|
616
|
+
contents.set(filePath, content);
|
|
617
|
+
if (content.match(tryCatchPattern)) styleStats.tryCatch++;
|
|
618
|
+
if (content.match(/\.catch\s*\(/)) styleStats.thenCatch++;
|
|
619
|
+
if (content.match(/\bawait\b/)) styleStats.asyncAwait++;
|
|
620
|
+
if (content.match(/\brequire\s*\(/)) styleStats.commonJs++;
|
|
621
|
+
if (content.match(/\bimport\b.*\bfrom\b/)) styleStats.esm++;
|
|
622
|
+
} catch (err) {
|
|
623
|
+
void err;
|
|
1312
624
|
}
|
|
1313
625
|
}
|
|
1314
|
-
const
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
if (strategiesUsed > 2) {
|
|
626
|
+
const totalFiles = filePaths.length;
|
|
627
|
+
if (styleStats.tryCatch > 0 && styleStats.thenCatch > 0) {
|
|
628
|
+
const dominant = styleStats.tryCatch >= styleStats.thenCatch ? "try-catch" : ".catch()";
|
|
629
|
+
const minority = dominant === "try-catch" ? ".catch()" : "try-catch";
|
|
1319
630
|
issues.push({
|
|
1320
|
-
files:
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
...patterns.returnsNull,
|
|
1325
|
-
...patterns.returnsError
|
|
1326
|
-
])
|
|
1327
|
-
],
|
|
631
|
+
files: filePaths.filter((f) => {
|
|
632
|
+
const c = contents.get(f) || "";
|
|
633
|
+
return minority === "try-catch" ? c.match(tryCatchPattern) : c.match(/\.catch\s*\(/);
|
|
634
|
+
}),
|
|
1328
635
|
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"
|
|
636
|
+
description: `Mixed error handling styles: codebase primarily uses ${dominant}, but found ${minority} in some files.`,
|
|
637
|
+
examples: [dominant, minority],
|
|
638
|
+
severity: import_core4.Severity.Minor
|
|
1337
639
|
});
|
|
1338
640
|
}
|
|
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) {
|
|
641
|
+
if (styleStats.commonJs > 0 && styleStats.esm > 0) {
|
|
642
|
+
const minority = styleStats.esm >= styleStats.commonJs ? "CommonJS (require)" : "ESM (import)";
|
|
1373
643
|
issues.push({
|
|
1374
|
-
files:
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
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) {
|
|
1403
|
-
issues.push({
|
|
1404
|
-
files: patterns.mixed,
|
|
644
|
+
files: filePaths.filter((f) => {
|
|
645
|
+
const c = contents.get(f) || "";
|
|
646
|
+
return minority === "CommonJS (require)" ? c.match(/\brequire\s*\(/) : c.match(/\bimport\b/);
|
|
647
|
+
}),
|
|
1405
648
|
type: "import-style",
|
|
1406
|
-
description:
|
|
1407
|
-
examples:
|
|
1408
|
-
severity:
|
|
649
|
+
description: `Mixed module systems: found both ESM and CommonJS.`,
|
|
650
|
+
examples: ['import X from "y"', 'const X = require("y")'],
|
|
651
|
+
severity: import_core4.Severity.Major
|
|
1409
652
|
});
|
|
1410
653
|
}
|
|
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
654
|
return issues;
|
|
1427
655
|
}
|
|
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
656
|
|
|
1438
657
|
// src/analyzer.ts
|
|
1439
658
|
async function analyzeConsistency(options) {
|
|
@@ -1442,11 +661,11 @@ async function analyzeConsistency(options) {
|
|
|
1442
661
|
checkPatterns = true,
|
|
1443
662
|
checkArchitecture = false,
|
|
1444
663
|
// Not implemented yet
|
|
1445
|
-
minSeverity =
|
|
664
|
+
minSeverity = import_core5.Severity.Info,
|
|
1446
665
|
...scanOptions
|
|
1447
666
|
} = options;
|
|
1448
667
|
void checkArchitecture;
|
|
1449
|
-
const filePaths = await (0,
|
|
668
|
+
const filePaths = await (0, import_core5.scanFiles)(scanOptions);
|
|
1450
669
|
const tsJsFiles = filePaths.filter((f) => /\.(ts|tsx|js|jsx)$/i.test(f));
|
|
1451
670
|
const pythonFiles = filePaths.filter((f) => /\.py$/i.test(f));
|
|
1452
671
|
let namingIssues = [];
|
|
@@ -1463,9 +682,9 @@ async function analyzeConsistency(options) {
|
|
|
1463
682
|
continue;
|
|
1464
683
|
}
|
|
1465
684
|
const consistencyIssue = {
|
|
1466
|
-
type: issue.type === "convention-mix" ?
|
|
685
|
+
type: issue.type === "convention-mix" ? import_core5.IssueType.NamingInconsistency : import_core5.IssueType.NamingQuality,
|
|
1467
686
|
category: "naming",
|
|
1468
|
-
severity: issue.severity,
|
|
687
|
+
severity: getSeverityEnum(issue.severity),
|
|
1469
688
|
message: `${issue.type}: ${issue.identifier}`,
|
|
1470
689
|
location: {
|
|
1471
690
|
file: issue.file,
|
|
@@ -1484,9 +703,9 @@ async function analyzeConsistency(options) {
|
|
|
1484
703
|
continue;
|
|
1485
704
|
}
|
|
1486
705
|
const consistencyIssue = {
|
|
1487
|
-
type:
|
|
706
|
+
type: import_core5.IssueType.PatternInconsistency,
|
|
1488
707
|
category: "patterns",
|
|
1489
|
-
severity: issue.severity,
|
|
708
|
+
severity: getSeverityEnum(issue.severity),
|
|
1490
709
|
message: issue.description,
|
|
1491
710
|
location: {
|
|
1492
711
|
file: issue.files[0] || "multiple files",
|
|
@@ -1513,16 +732,17 @@ async function analyzeConsistency(options) {
|
|
|
1513
732
|
});
|
|
1514
733
|
}
|
|
1515
734
|
results.sort((fileResultA, fileResultB) => {
|
|
1516
|
-
const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
|
|
1517
735
|
const maxSeverityA = Math.min(
|
|
1518
|
-
...fileResultA.issues.map(
|
|
1519
|
-
|
|
1520
|
-
|
|
736
|
+
...fileResultA.issues.map((i) => {
|
|
737
|
+
const val = getSeverityLevel(i.severity);
|
|
738
|
+
return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
|
|
739
|
+
})
|
|
1521
740
|
);
|
|
1522
741
|
const maxSeverityB = Math.min(
|
|
1523
|
-
...fileResultB.issues.map(
|
|
1524
|
-
|
|
1525
|
-
|
|
742
|
+
...fileResultB.issues.map((i) => {
|
|
743
|
+
const val = getSeverityLevel(i.severity);
|
|
744
|
+
return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
|
|
745
|
+
})
|
|
1526
746
|
);
|
|
1527
747
|
if (maxSeverityA !== maxSeverityB) {
|
|
1528
748
|
return maxSeverityA - maxSeverityB;
|
|
@@ -1548,16 +768,52 @@ async function analyzeConsistency(options) {
|
|
|
1548
768
|
recommendations
|
|
1549
769
|
};
|
|
1550
770
|
}
|
|
771
|
+
function getSeverityLevel(s) {
|
|
772
|
+
if (s === import_core5.Severity.Critical || s === "critical") return 4;
|
|
773
|
+
if (s === import_core5.Severity.Major || s === "major") return 3;
|
|
774
|
+
if (s === import_core5.Severity.Minor || s === "minor") return 2;
|
|
775
|
+
if (s === import_core5.Severity.Info || s === "info") return 1;
|
|
776
|
+
return 0;
|
|
777
|
+
}
|
|
778
|
+
function getSeverityEnum(s) {
|
|
779
|
+
const val = getSeverityLevel(s);
|
|
780
|
+
switch (val) {
|
|
781
|
+
case 4:
|
|
782
|
+
return import_core5.Severity.Critical;
|
|
783
|
+
case 3:
|
|
784
|
+
return import_core5.Severity.Major;
|
|
785
|
+
case 2:
|
|
786
|
+
return import_core5.Severity.Minor;
|
|
787
|
+
case 1:
|
|
788
|
+
return import_core5.Severity.Info;
|
|
789
|
+
default:
|
|
790
|
+
return import_core5.Severity.Info;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
1551
793
|
function shouldIncludeSeverity(severity, minSeverity) {
|
|
1552
|
-
|
|
1553
|
-
return severityLevels[severity] >= severityLevels[minSeverity];
|
|
794
|
+
return getSeverityLevel(severity) >= getSeverityLevel(minSeverity);
|
|
1554
795
|
}
|
|
1555
796
|
function calculateConsistencyScore(issues) {
|
|
1556
|
-
|
|
1557
|
-
const
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
797
|
+
let totalWeight = 0;
|
|
798
|
+
for (const issue of issues) {
|
|
799
|
+
const val = getSeverityLevel(issue.severity);
|
|
800
|
+
switch (val) {
|
|
801
|
+
case 4:
|
|
802
|
+
totalWeight += 10;
|
|
803
|
+
break;
|
|
804
|
+
case 3:
|
|
805
|
+
totalWeight += 5;
|
|
806
|
+
break;
|
|
807
|
+
case 2:
|
|
808
|
+
totalWeight += 2;
|
|
809
|
+
break;
|
|
810
|
+
case 1:
|
|
811
|
+
totalWeight += 1;
|
|
812
|
+
break;
|
|
813
|
+
default:
|
|
814
|
+
totalWeight += 1;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
1561
817
|
return Math.max(0, 1 - totalWeight / 100);
|
|
1562
818
|
}
|
|
1563
819
|
function generateRecommendations(namingIssues, patternIssues) {
|
|
@@ -1611,243 +867,82 @@ function generateRecommendations(namingIssues, patternIssues) {
|
|
|
1611
867
|
}
|
|
1612
868
|
|
|
1613
869
|
// 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) {
|
|
870
|
+
var import_fs3 = require("fs");
|
|
871
|
+
var import_core6 = require("@aiready/core");
|
|
872
|
+
async function analyzeNaming(filePaths) {
|
|
1632
873
|
const issues = [];
|
|
1633
|
-
const
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
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
|
-
}
|
|
874
|
+
for (const filePath of filePaths) {
|
|
875
|
+
try {
|
|
876
|
+
const content = (0, import_fs3.readFileSync)(filePath, "utf-8");
|
|
877
|
+
const lines = content.split("\n");
|
|
878
|
+
lines.forEach((line, index) => {
|
|
879
|
+
const singleLetterMatch = line.match(
|
|
880
|
+
/\b(const|let|var)\s+([a-hj-km-np-zA-Z])\s*=/
|
|
881
|
+
);
|
|
882
|
+
if (singleLetterMatch) {
|
|
1686
883
|
issues.push({
|
|
1687
|
-
file,
|
|
1688
|
-
line:
|
|
884
|
+
file: filePath,
|
|
885
|
+
line: index + 1,
|
|
1689
886
|
type: "poor-naming",
|
|
1690
|
-
identifier:
|
|
1691
|
-
severity:
|
|
1692
|
-
suggestion:
|
|
887
|
+
identifier: singleLetterMatch[2],
|
|
888
|
+
severity: import_core6.Severity.Minor,
|
|
889
|
+
suggestion: "Use a more descriptive name than a single letter"
|
|
1693
890
|
});
|
|
1694
891
|
}
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
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;
|
|
892
|
+
if (filePath.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
893
|
+
const snakeCaseMatch = line.match(
|
|
894
|
+
/\b(const|let|var|function)\s+([a-z]+_[a-z0-9_]+)\b/
|
|
895
|
+
);
|
|
896
|
+
if (snakeCaseMatch) {
|
|
897
|
+
issues.push({
|
|
898
|
+
file: filePath,
|
|
899
|
+
line: index + 1,
|
|
900
|
+
type: "convention-mix",
|
|
901
|
+
identifier: snakeCaseMatch[2],
|
|
902
|
+
severity: import_core6.Severity.Info,
|
|
903
|
+
suggestion: "Use camelCase instead of snake_case in TypeScript/JavaScript"
|
|
904
|
+
});
|
|
1727
905
|
}
|
|
1728
906
|
}
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
line: lineNumber,
|
|
1732
|
-
type: "abbreviation",
|
|
1733
|
-
identifier: match[1],
|
|
1734
|
-
severity: "info",
|
|
1735
|
-
suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
|
|
1736
|
-
});
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
1740
|
-
const camelCaseVars = line.match(
|
|
1741
|
-
/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/
|
|
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)$/
|
|
907
|
+
const shortNameMatch = line.match(
|
|
908
|
+
/\b(const|let|var)\s+([a-zA-Z0-9]{2,3})\s*=/
|
|
1810
909
|
);
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
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
|
-
});
|
|
910
|
+
if (shortNameMatch) {
|
|
911
|
+
const name = shortNameMatch[2].toLowerCase();
|
|
912
|
+
const vagueNames = ["obj", "val", "tmp", "res", "ret", "data"];
|
|
913
|
+
if (vagueNames.includes(name)) {
|
|
914
|
+
issues.push({
|
|
915
|
+
file: filePath,
|
|
916
|
+
line: index + 1,
|
|
917
|
+
type: "poor-naming",
|
|
918
|
+
identifier: name,
|
|
919
|
+
severity: import_core6.Severity.Minor,
|
|
920
|
+
suggestion: `Avoid vague names like '${name}'`
|
|
921
|
+
});
|
|
922
|
+
}
|
|
1842
923
|
}
|
|
1843
|
-
}
|
|
924
|
+
});
|
|
925
|
+
} catch (err) {
|
|
926
|
+
void err;
|
|
1844
927
|
}
|
|
1845
|
-
}
|
|
928
|
+
}
|
|
1846
929
|
return issues;
|
|
1847
930
|
}
|
|
1848
931
|
|
|
932
|
+
// src/analyzers/naming-constants.ts
|
|
933
|
+
function detectNamingConventions(files, allIssues) {
|
|
934
|
+
const camelCaseCount = allIssues.filter(
|
|
935
|
+
(i) => i.type === "convention-mix"
|
|
936
|
+
).length;
|
|
937
|
+
const totalChecks = files.length * 10;
|
|
938
|
+
if (camelCaseCount / totalChecks > 0.3) {
|
|
939
|
+
return { dominantConvention: "mixed", conventionScore: 0.5 };
|
|
940
|
+
}
|
|
941
|
+
return { dominantConvention: "camelCase", conventionScore: 0.9 };
|
|
942
|
+
}
|
|
943
|
+
|
|
1849
944
|
// src/scoring.ts
|
|
1850
|
-
var
|
|
945
|
+
var import_core7 = require("@aiready/core");
|
|
1851
946
|
function calculateConsistencyScore2(issues, totalFilesAnalyzed, costConfig) {
|
|
1852
947
|
void costConfig;
|
|
1853
948
|
const criticalIssues = issues.filter((i) => i.severity === "critical").length;
|
|
@@ -1923,7 +1018,7 @@ function calculateConsistencyScore2(issues, totalFilesAnalyzed, costConfig) {
|
|
|
1923
1018
|
priority: "low"
|
|
1924
1019
|
});
|
|
1925
1020
|
}
|
|
1926
|
-
const productivityImpact = (0,
|
|
1021
|
+
const productivityImpact = (0, import_core7.calculateProductivityImpact)(issues);
|
|
1927
1022
|
return {
|
|
1928
1023
|
toolName: "consistency",
|
|
1929
1024
|
score,
|