@aiready/consistency 0.16.2 → 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/cli.js
CHANGED
|
@@ -27,7 +27,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
var import_commander = require("commander");
|
|
28
28
|
|
|
29
29
|
// src/analyzer.ts
|
|
30
|
-
var
|
|
30
|
+
var import_core5 = require("@aiready/core");
|
|
31
|
+
|
|
32
|
+
// src/analyzers/naming-ast.ts
|
|
33
|
+
var import_core2 = require("@aiready/core");
|
|
31
34
|
|
|
32
35
|
// src/utils/ast-parser.ts
|
|
33
36
|
var import_typescript_estree = require("@typescript-eslint/typescript-estree");
|
|
@@ -79,214 +82,12 @@ function isLoopStatement(node) {
|
|
|
79
82
|
"DoWhileStatement"
|
|
80
83
|
].includes(node.type);
|
|
81
84
|
}
|
|
82
|
-
function getFunctionName(node) {
|
|
83
|
-
switch (node.type) {
|
|
84
|
-
case "FunctionDeclaration":
|
|
85
|
-
return node.id?.name ?? null;
|
|
86
|
-
case "FunctionExpression":
|
|
87
|
-
return node.id?.name ?? null;
|
|
88
|
-
case "ArrowFunctionExpression":
|
|
89
|
-
return null;
|
|
90
|
-
// Arrow functions don't have names directly
|
|
91
|
-
case "MethodDefinition":
|
|
92
|
-
if (node.key.type === "Identifier") {
|
|
93
|
-
return node.key.name;
|
|
94
|
-
}
|
|
95
|
-
return null;
|
|
96
|
-
default:
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
85
|
function getLineNumber(node) {
|
|
101
86
|
return node.loc?.start.line ?? 0;
|
|
102
87
|
}
|
|
103
|
-
function isCoverageContext(node, ancestors) {
|
|
104
|
-
const coveragePatterns = /coverage|summary|metrics|pct|percent|statements|branches|functions|lines/i;
|
|
105
|
-
if (node.type === "Identifier" && coveragePatterns.test(node.name)) {
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
for (const ancestor of ancestors.slice(-3)) {
|
|
109
|
-
if (ancestor.type === "MemberExpression") {
|
|
110
|
-
const memberExpr = ancestor;
|
|
111
|
-
if (memberExpr.object.type === "Identifier" && coveragePatterns.test(memberExpr.object.name)) {
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
if (ancestor.type === "ObjectPattern" || ancestor.type === "ObjectExpression") {
|
|
116
|
-
const parent = ancestors[ancestors.indexOf(ancestor) - 1];
|
|
117
|
-
if (parent?.type === "VariableDeclarator") {
|
|
118
|
-
const varDecl = parent;
|
|
119
|
-
if (varDecl.id.type === "Identifier" && coveragePatterns.test(varDecl.id.name)) {
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// src/utils/scope-tracker.ts
|
|
129
|
-
var ScopeTracker = class {
|
|
130
|
-
constructor(rootNode) {
|
|
131
|
-
this.allScopes = [];
|
|
132
|
-
this.rootScope = {
|
|
133
|
-
type: "global",
|
|
134
|
-
node: rootNode,
|
|
135
|
-
parent: null,
|
|
136
|
-
children: [],
|
|
137
|
-
variables: /* @__PURE__ */ new Map()
|
|
138
|
-
};
|
|
139
|
-
this.currentScope = this.rootScope;
|
|
140
|
-
this.allScopes.push(this.rootScope);
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Enter a new scope
|
|
144
|
-
*/
|
|
145
|
-
enterScope(type, node) {
|
|
146
|
-
const newScope = {
|
|
147
|
-
type,
|
|
148
|
-
node,
|
|
149
|
-
parent: this.currentScope,
|
|
150
|
-
children: [],
|
|
151
|
-
variables: /* @__PURE__ */ new Map()
|
|
152
|
-
};
|
|
153
|
-
this.currentScope.children.push(newScope);
|
|
154
|
-
this.currentScope = newScope;
|
|
155
|
-
this.allScopes.push(newScope);
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Exit current scope and return to parent
|
|
159
|
-
*/
|
|
160
|
-
exitScope() {
|
|
161
|
-
if (this.currentScope.parent) {
|
|
162
|
-
this.currentScope = this.currentScope.parent;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Declare a variable in the current scope
|
|
167
|
-
*/
|
|
168
|
-
declareVariable(name, node, line, options = {}) {
|
|
169
|
-
const varInfo = {
|
|
170
|
-
name,
|
|
171
|
-
node,
|
|
172
|
-
declarationLine: line,
|
|
173
|
-
references: [],
|
|
174
|
-
type: options.type,
|
|
175
|
-
isParameter: options.isParameter ?? false,
|
|
176
|
-
isDestructured: options.isDestructured ?? false,
|
|
177
|
-
isLoopVariable: options.isLoopVariable ?? false
|
|
178
|
-
};
|
|
179
|
-
this.currentScope.variables.set(name, varInfo);
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Add a reference to a variable
|
|
183
|
-
*/
|
|
184
|
-
addReference(name, node) {
|
|
185
|
-
const varInfo = this.findVariable(name);
|
|
186
|
-
if (varInfo) {
|
|
187
|
-
varInfo.references.push(node);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Find a variable in current or parent scopes
|
|
192
|
-
*/
|
|
193
|
-
findVariable(name) {
|
|
194
|
-
let scope = this.currentScope;
|
|
195
|
-
while (scope) {
|
|
196
|
-
const varInfo = scope.variables.get(name);
|
|
197
|
-
if (varInfo) {
|
|
198
|
-
return varInfo;
|
|
199
|
-
}
|
|
200
|
-
scope = scope.parent;
|
|
201
|
-
}
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Get all variables in current scope (not including parent scopes)
|
|
206
|
-
*/
|
|
207
|
-
getCurrentScopeVariables() {
|
|
208
|
-
return Array.from(this.currentScope.variables.values());
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Get all variables across all scopes
|
|
212
|
-
*/
|
|
213
|
-
getAllVariables() {
|
|
214
|
-
const allVars = [];
|
|
215
|
-
for (const scope of this.allScopes) {
|
|
216
|
-
allVars.push(...Array.from(scope.variables.values()));
|
|
217
|
-
}
|
|
218
|
-
return allVars;
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* Calculate actual usage count (references minus declaration)
|
|
222
|
-
*/
|
|
223
|
-
getUsageCount(varInfo) {
|
|
224
|
-
return varInfo.references.length;
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Check if a variable is short-lived (used within N lines)
|
|
228
|
-
*/
|
|
229
|
-
isShortLived(varInfo, maxLines = 5) {
|
|
230
|
-
if (varInfo.references.length === 0) {
|
|
231
|
-
return false;
|
|
232
|
-
}
|
|
233
|
-
const declarationLine = varInfo.declarationLine;
|
|
234
|
-
const maxUsageLine = Math.max(
|
|
235
|
-
...varInfo.references.map((ref) => ref.loc?.start.line ?? declarationLine)
|
|
236
|
-
);
|
|
237
|
-
return maxUsageLine - declarationLine <= maxLines;
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* Check if a variable is used in a limited scope (e.g., only in one callback)
|
|
241
|
-
*/
|
|
242
|
-
isLocallyScoped(varInfo) {
|
|
243
|
-
if (varInfo.references.length === 0) return false;
|
|
244
|
-
const lines = varInfo.references.map((ref) => ref.loc?.start.line ?? 0);
|
|
245
|
-
const minLine = Math.min(...lines);
|
|
246
|
-
const maxLine = Math.max(...lines);
|
|
247
|
-
return maxLine - minLine <= 3;
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Get current scope type
|
|
251
|
-
*/
|
|
252
|
-
getCurrentScopeType() {
|
|
253
|
-
return this.currentScope.type;
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Check if currently in a loop scope
|
|
257
|
-
*/
|
|
258
|
-
isInLoop() {
|
|
259
|
-
let scope = this.currentScope;
|
|
260
|
-
while (scope) {
|
|
261
|
-
if (scope.type === "loop") {
|
|
262
|
-
return true;
|
|
263
|
-
}
|
|
264
|
-
scope = scope.parent;
|
|
265
|
-
}
|
|
266
|
-
return false;
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Check if currently in a function scope
|
|
270
|
-
*/
|
|
271
|
-
isInFunction() {
|
|
272
|
-
let scope = this.currentScope;
|
|
273
|
-
while (scope) {
|
|
274
|
-
if (scope.type === "function") {
|
|
275
|
-
return true;
|
|
276
|
-
}
|
|
277
|
-
scope = scope.parent;
|
|
278
|
-
}
|
|
279
|
-
return false;
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Get the root scope
|
|
283
|
-
*/
|
|
284
|
-
getRootScope() {
|
|
285
|
-
return this.rootScope;
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
88
|
|
|
289
89
|
// src/utils/context-detector.ts
|
|
90
|
+
var import_core = require("@aiready/core");
|
|
290
91
|
function detectFileType(filePath, ast) {
|
|
291
92
|
void ast;
|
|
292
93
|
const path = filePath.toLowerCase();
|
|
@@ -414,24 +215,34 @@ function buildCodeContext(filePath, ast) {
|
|
|
414
215
|
};
|
|
415
216
|
}
|
|
416
217
|
function adjustSeverity(baseSeverity, context, issueType) {
|
|
218
|
+
const getEnum = (s) => {
|
|
219
|
+
if (s === import_core.Severity.Critical || s === "critical") return import_core.Severity.Critical;
|
|
220
|
+
if (s === import_core.Severity.Major || s === "major") return import_core.Severity.Major;
|
|
221
|
+
if (s === import_core.Severity.Minor || s === "minor") return import_core.Severity.Minor;
|
|
222
|
+
return import_core.Severity.Info;
|
|
223
|
+
};
|
|
224
|
+
let currentSev = getEnum(baseSeverity);
|
|
417
225
|
if (context.isTestFile) {
|
|
418
|
-
if (
|
|
419
|
-
if (
|
|
226
|
+
if (currentSev === import_core.Severity.Minor) currentSev = import_core.Severity.Info;
|
|
227
|
+
if (currentSev === import_core.Severity.Major) currentSev = import_core.Severity.Minor;
|
|
420
228
|
}
|
|
421
229
|
if (context.isTypeDefinition) {
|
|
422
|
-
if (
|
|
230
|
+
if (currentSev === import_core.Severity.Minor) currentSev = import_core.Severity.Info;
|
|
423
231
|
}
|
|
424
232
|
if (context.codeLayer === "api") {
|
|
425
|
-
if (
|
|
426
|
-
|
|
233
|
+
if (currentSev === import_core.Severity.Info && issueType === "unclear")
|
|
234
|
+
currentSev = import_core.Severity.Minor;
|
|
235
|
+
if (currentSev === import_core.Severity.Minor && issueType === "unclear")
|
|
236
|
+
currentSev = import_core.Severity.Major;
|
|
427
237
|
}
|
|
428
238
|
if (context.complexity > 10) {
|
|
429
|
-
if (
|
|
239
|
+
if (currentSev === import_core.Severity.Info) currentSev = import_core.Severity.Minor;
|
|
430
240
|
}
|
|
431
241
|
if (context.codeLayer === "utility") {
|
|
432
|
-
if (
|
|
242
|
+
if (currentSev === import_core.Severity.Minor && issueType === "abbreviation")
|
|
243
|
+
currentSev = import_core.Severity.Info;
|
|
433
244
|
}
|
|
434
|
-
return
|
|
245
|
+
return currentSev;
|
|
435
246
|
}
|
|
436
247
|
function isAcceptableInContext(name, context, options) {
|
|
437
248
|
if (options.isLoopVariable && ["i", "j", "k", "l", "n", "m"].includes(name)) {
|
|
@@ -458,638 +269,157 @@ function isAcceptableInContext(name, context, options) {
|
|
|
458
269
|
return false;
|
|
459
270
|
}
|
|
460
271
|
|
|
461
|
-
// src/utils/config-loader.ts
|
|
462
|
-
var import_core = require("@aiready/core");
|
|
463
|
-
var import_path = require("path");
|
|
464
|
-
|
|
465
|
-
// src/analyzers/naming-constants.ts
|
|
466
|
-
var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
|
|
467
|
-
// Full English words (1-3 letters)
|
|
468
|
-
"day",
|
|
469
|
-
"key",
|
|
470
|
-
"net",
|
|
471
|
-
"to",
|
|
472
|
-
"go",
|
|
473
|
-
"for",
|
|
474
|
-
"not",
|
|
475
|
-
"new",
|
|
476
|
-
"old",
|
|
477
|
-
"top",
|
|
478
|
-
"end",
|
|
479
|
-
"run",
|
|
480
|
-
"try",
|
|
481
|
-
"use",
|
|
482
|
-
"get",
|
|
483
|
-
"set",
|
|
484
|
-
"add",
|
|
485
|
-
"put",
|
|
486
|
-
"map",
|
|
487
|
-
"log",
|
|
488
|
-
"row",
|
|
489
|
-
"col",
|
|
490
|
-
"tab",
|
|
491
|
-
"box",
|
|
492
|
-
"div",
|
|
493
|
-
"nav",
|
|
494
|
-
"tag",
|
|
495
|
-
"any",
|
|
496
|
-
"all",
|
|
497
|
-
"one",
|
|
498
|
-
"two",
|
|
499
|
-
"out",
|
|
500
|
-
"off",
|
|
501
|
-
"on",
|
|
502
|
-
"yes",
|
|
503
|
-
"no",
|
|
504
|
-
"now",
|
|
505
|
-
"max",
|
|
506
|
-
"min",
|
|
507
|
-
"sum",
|
|
508
|
-
"avg",
|
|
509
|
-
"ref",
|
|
510
|
-
"src",
|
|
511
|
-
"dst",
|
|
512
|
-
"raw",
|
|
513
|
-
"def",
|
|
514
|
-
"sub",
|
|
515
|
-
"pub",
|
|
516
|
-
"pre",
|
|
517
|
-
"mid",
|
|
518
|
-
"alt",
|
|
519
|
-
"opt",
|
|
520
|
-
"tmp",
|
|
521
|
-
"ext",
|
|
522
|
-
"sep",
|
|
523
|
-
// Prepositions and conjunctions
|
|
524
|
-
"and",
|
|
525
|
-
"from",
|
|
526
|
-
"how",
|
|
527
|
-
"pad",
|
|
528
|
-
"bar",
|
|
529
|
-
"non",
|
|
530
|
-
// Additional full words commonly flagged
|
|
531
|
-
"tax",
|
|
532
|
-
"cat",
|
|
533
|
-
"dog",
|
|
534
|
-
"car",
|
|
535
|
-
"bus",
|
|
536
|
-
"web",
|
|
537
|
-
"app",
|
|
538
|
-
"war",
|
|
539
|
-
"law",
|
|
540
|
-
"pay",
|
|
541
|
-
"buy",
|
|
542
|
-
"win",
|
|
543
|
-
"cut",
|
|
544
|
-
"hit",
|
|
545
|
-
"hot",
|
|
546
|
-
"pop",
|
|
547
|
-
"job",
|
|
548
|
-
"age",
|
|
549
|
-
"act",
|
|
550
|
-
"let",
|
|
551
|
-
"lot",
|
|
552
|
-
"bad",
|
|
553
|
-
"big",
|
|
554
|
-
"far",
|
|
555
|
-
"few",
|
|
556
|
-
"own",
|
|
557
|
-
"per",
|
|
558
|
-
"red",
|
|
559
|
-
"low",
|
|
560
|
-
"see",
|
|
561
|
-
"six",
|
|
562
|
-
"ten",
|
|
563
|
-
"way",
|
|
564
|
-
"who",
|
|
565
|
-
"why",
|
|
566
|
-
"yet",
|
|
567
|
-
"via",
|
|
568
|
-
"due",
|
|
569
|
-
"fee",
|
|
570
|
-
"fun",
|
|
571
|
-
"gas",
|
|
572
|
-
"gay",
|
|
573
|
-
"god",
|
|
574
|
-
"gun",
|
|
575
|
-
"guy",
|
|
576
|
-
"ice",
|
|
577
|
-
"ill",
|
|
578
|
-
"kid",
|
|
579
|
-
"mad",
|
|
580
|
-
"man",
|
|
581
|
-
"mix",
|
|
582
|
-
"mom",
|
|
583
|
-
"mrs",
|
|
584
|
-
"nor",
|
|
585
|
-
"odd",
|
|
586
|
-
"oil",
|
|
587
|
-
"pan",
|
|
588
|
-
"pet",
|
|
589
|
-
"pit",
|
|
590
|
-
"pot",
|
|
591
|
-
"pow",
|
|
592
|
-
"pro",
|
|
593
|
-
"raw",
|
|
594
|
-
"rep",
|
|
595
|
-
"rid",
|
|
596
|
-
"sad",
|
|
597
|
-
"sea",
|
|
598
|
-
"sit",
|
|
599
|
-
"sky",
|
|
600
|
-
"son",
|
|
601
|
-
"tea",
|
|
602
|
-
"tie",
|
|
603
|
-
"tip",
|
|
604
|
-
"van",
|
|
605
|
-
"war",
|
|
606
|
-
"win",
|
|
607
|
-
"won"
|
|
608
|
-
]);
|
|
609
|
-
var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
610
|
-
// Standard identifiers
|
|
611
|
-
"id",
|
|
612
|
-
"uid",
|
|
613
|
-
"gid",
|
|
614
|
-
"pid",
|
|
615
|
-
// Loop counters and iterators
|
|
616
|
-
"i",
|
|
617
|
-
"j",
|
|
618
|
-
"k",
|
|
619
|
-
"n",
|
|
620
|
-
"m",
|
|
621
|
-
// Web/Network
|
|
622
|
-
"url",
|
|
623
|
-
"uri",
|
|
624
|
-
"api",
|
|
625
|
-
"cdn",
|
|
626
|
-
"dns",
|
|
627
|
-
"ip",
|
|
628
|
-
"tcp",
|
|
629
|
-
"udp",
|
|
630
|
-
"http",
|
|
631
|
-
"ssl",
|
|
632
|
-
"tls",
|
|
633
|
-
"utm",
|
|
634
|
-
"seo",
|
|
635
|
-
"rss",
|
|
636
|
-
"xhr",
|
|
637
|
-
"ajax",
|
|
638
|
-
"cors",
|
|
639
|
-
"ws",
|
|
640
|
-
"wss",
|
|
641
|
-
// Data formats
|
|
642
|
-
"json",
|
|
643
|
-
"xml",
|
|
644
|
-
"yaml",
|
|
645
|
-
"csv",
|
|
646
|
-
"html",
|
|
647
|
-
"css",
|
|
648
|
-
"svg",
|
|
649
|
-
"pdf",
|
|
650
|
-
// File types & extensions
|
|
651
|
-
"img",
|
|
652
|
-
"txt",
|
|
653
|
-
"doc",
|
|
654
|
-
"docx",
|
|
655
|
-
"xlsx",
|
|
656
|
-
"ppt",
|
|
657
|
-
"md",
|
|
658
|
-
"rst",
|
|
659
|
-
"jpg",
|
|
660
|
-
"png",
|
|
661
|
-
"gif",
|
|
662
|
-
// Databases
|
|
663
|
-
"db",
|
|
664
|
-
"sql",
|
|
665
|
-
"orm",
|
|
666
|
-
"dao",
|
|
667
|
-
"dto",
|
|
668
|
-
"ddb",
|
|
669
|
-
"rds",
|
|
670
|
-
"nosql",
|
|
671
|
-
// File system
|
|
672
|
-
"fs",
|
|
673
|
-
"dir",
|
|
674
|
-
"tmp",
|
|
675
|
-
"src",
|
|
676
|
-
"dst",
|
|
677
|
-
"bin",
|
|
678
|
-
"lib",
|
|
679
|
-
"pkg",
|
|
680
|
-
// Operating system
|
|
681
|
-
"os",
|
|
682
|
-
"env",
|
|
683
|
-
"arg",
|
|
684
|
-
"cli",
|
|
685
|
-
"cmd",
|
|
686
|
-
"exe",
|
|
687
|
-
"cwd",
|
|
688
|
-
"pwd",
|
|
689
|
-
// UI/UX
|
|
690
|
-
"ui",
|
|
691
|
-
"ux",
|
|
692
|
-
"gui",
|
|
693
|
-
"dom",
|
|
694
|
-
"ref",
|
|
695
|
-
// Request/Response
|
|
696
|
-
"req",
|
|
697
|
-
"res",
|
|
698
|
-
"ctx",
|
|
699
|
-
"err",
|
|
700
|
-
"msg",
|
|
701
|
-
"auth",
|
|
702
|
-
// Mathematics/Computing
|
|
703
|
-
"max",
|
|
704
|
-
"min",
|
|
705
|
-
"avg",
|
|
706
|
-
"sum",
|
|
707
|
-
"abs",
|
|
708
|
-
"cos",
|
|
709
|
-
"sin",
|
|
710
|
-
"tan",
|
|
711
|
-
"log",
|
|
712
|
-
"exp",
|
|
713
|
-
"pow",
|
|
714
|
-
"sqrt",
|
|
715
|
-
"std",
|
|
716
|
-
"var",
|
|
717
|
-
"int",
|
|
718
|
-
"num",
|
|
719
|
-
"idx",
|
|
720
|
-
// Time
|
|
721
|
-
"now",
|
|
722
|
-
"utc",
|
|
723
|
-
"tz",
|
|
724
|
-
"ms",
|
|
725
|
-
"sec",
|
|
726
|
-
"hr",
|
|
727
|
-
"min",
|
|
728
|
-
"yr",
|
|
729
|
-
"mo",
|
|
730
|
-
// Common patterns
|
|
731
|
-
"app",
|
|
732
|
-
"cfg",
|
|
733
|
-
"config",
|
|
734
|
-
"init",
|
|
735
|
-
"len",
|
|
736
|
-
"val",
|
|
737
|
-
"str",
|
|
738
|
-
"obj",
|
|
739
|
-
"arr",
|
|
740
|
-
"gen",
|
|
741
|
-
"def",
|
|
742
|
-
"raw",
|
|
743
|
-
"new",
|
|
744
|
-
"old",
|
|
745
|
-
"pre",
|
|
746
|
-
"post",
|
|
747
|
-
"sub",
|
|
748
|
-
"pub",
|
|
749
|
-
// Programming/Framework specific
|
|
750
|
-
"ts",
|
|
751
|
-
"js",
|
|
752
|
-
"jsx",
|
|
753
|
-
"tsx",
|
|
754
|
-
"py",
|
|
755
|
-
"rb",
|
|
756
|
-
"vue",
|
|
757
|
-
"re",
|
|
758
|
-
"fn",
|
|
759
|
-
"fns",
|
|
760
|
-
"mod",
|
|
761
|
-
"opts",
|
|
762
|
-
"dev",
|
|
763
|
-
// Cloud/Infrastructure
|
|
764
|
-
"s3",
|
|
765
|
-
"ec2",
|
|
766
|
-
"sqs",
|
|
767
|
-
"sns",
|
|
768
|
-
"vpc",
|
|
769
|
-
"ami",
|
|
770
|
-
"iam",
|
|
771
|
-
"acl",
|
|
772
|
-
"elb",
|
|
773
|
-
"alb",
|
|
774
|
-
"nlb",
|
|
775
|
-
"aws",
|
|
776
|
-
"ses",
|
|
777
|
-
"gst",
|
|
778
|
-
"cdk",
|
|
779
|
-
"btn",
|
|
780
|
-
"buf",
|
|
781
|
-
"agg",
|
|
782
|
-
"ocr",
|
|
783
|
-
"ai",
|
|
784
|
-
"cf",
|
|
785
|
-
"cfn",
|
|
786
|
-
"ga",
|
|
787
|
-
// Metrics/Performance
|
|
788
|
-
"fcp",
|
|
789
|
-
"lcp",
|
|
790
|
-
"cls",
|
|
791
|
-
"ttfb",
|
|
792
|
-
"tti",
|
|
793
|
-
"fid",
|
|
794
|
-
"fps",
|
|
795
|
-
"qps",
|
|
796
|
-
"rps",
|
|
797
|
-
"tps",
|
|
798
|
-
"wpm",
|
|
799
|
-
// Testing & i18n
|
|
800
|
-
"po",
|
|
801
|
-
"e2e",
|
|
802
|
-
"a11y",
|
|
803
|
-
"i18n",
|
|
804
|
-
"l10n",
|
|
805
|
-
"spy",
|
|
806
|
-
// Domain-specific abbreviations (context-aware)
|
|
807
|
-
"sk",
|
|
808
|
-
"fy",
|
|
809
|
-
"faq",
|
|
810
|
-
"og",
|
|
811
|
-
"seo",
|
|
812
|
-
"cta",
|
|
813
|
-
"roi",
|
|
814
|
-
"kpi",
|
|
815
|
-
"ttl",
|
|
816
|
-
"pct",
|
|
817
|
-
// Technical abbreviations
|
|
818
|
-
"mac",
|
|
819
|
-
"hex",
|
|
820
|
-
"esm",
|
|
821
|
-
"git",
|
|
822
|
-
"rec",
|
|
823
|
-
"loc",
|
|
824
|
-
"dup",
|
|
825
|
-
// Boolean helpers (these are intentional short names)
|
|
826
|
-
"is",
|
|
827
|
-
"has",
|
|
828
|
-
"can",
|
|
829
|
-
"did",
|
|
830
|
-
"was",
|
|
831
|
-
"are",
|
|
832
|
-
// Date/Time context (when in date contexts)
|
|
833
|
-
"d",
|
|
834
|
-
"t",
|
|
835
|
-
"dt",
|
|
836
|
-
// Coverage metrics (industry standard: statements/branches/functions/lines)
|
|
837
|
-
"s",
|
|
838
|
-
"b",
|
|
839
|
-
"f",
|
|
840
|
-
"l",
|
|
841
|
-
// Common media/content abbreviations
|
|
842
|
-
"vid",
|
|
843
|
-
"pic",
|
|
844
|
-
"img",
|
|
845
|
-
"doc",
|
|
846
|
-
"msg"
|
|
847
|
-
]);
|
|
848
|
-
|
|
849
|
-
// src/utils/config-loader.ts
|
|
850
|
-
async function loadNamingConfig(files) {
|
|
851
|
-
const rootDir = files.length > 0 ? (0, import_path.dirname)(files[0]) : process.cwd();
|
|
852
|
-
const config = await (0, import_core.loadConfig)(rootDir);
|
|
853
|
-
const consistencyConfig = config?.tools?.["consistency"];
|
|
854
|
-
const customAbbreviations = new Set(
|
|
855
|
-
consistencyConfig?.acceptedAbbreviations || []
|
|
856
|
-
);
|
|
857
|
-
const customShortWords = new Set(consistencyConfig?.shortWords || []);
|
|
858
|
-
const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
|
|
859
|
-
const allAbbreviations = /* @__PURE__ */ new Set([
|
|
860
|
-
...ACCEPTABLE_ABBREVIATIONS,
|
|
861
|
-
...customAbbreviations
|
|
862
|
-
]);
|
|
863
|
-
const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
|
|
864
|
-
return {
|
|
865
|
-
customAbbreviations,
|
|
866
|
-
customShortWords,
|
|
867
|
-
disabledChecks,
|
|
868
|
-
allAbbreviations,
|
|
869
|
-
allShortWords
|
|
870
|
-
};
|
|
871
|
-
}
|
|
872
|
-
|
|
873
272
|
// src/analyzers/naming-ast.ts
|
|
874
|
-
async function analyzeNamingAST(
|
|
875
|
-
const
|
|
876
|
-
const
|
|
877
|
-
const supportedFiles = files.filter(
|
|
878
|
-
(file) => /\.(js|jsx|ts|tsx)$/i.test(file)
|
|
879
|
-
);
|
|
880
|
-
for (const file of supportedFiles) {
|
|
273
|
+
async function analyzeNamingAST(filePaths) {
|
|
274
|
+
const allIssues = [];
|
|
275
|
+
for (const filePath of filePaths) {
|
|
881
276
|
try {
|
|
882
|
-
const ast = parseFile(
|
|
277
|
+
const ast = parseFile(filePath);
|
|
883
278
|
if (!ast) continue;
|
|
884
|
-
const
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
disabledChecks
|
|
890
|
-
);
|
|
891
|
-
issues.push(...fileIssues);
|
|
892
|
-
} catch (error) {
|
|
893
|
-
console.warn(`Skipping ${file} due to parse error:`, error);
|
|
279
|
+
const context = buildCodeContext(filePath, ast);
|
|
280
|
+
const issues = analyzeIdentifiers(ast, filePath, context);
|
|
281
|
+
allIssues.push(...issues);
|
|
282
|
+
} catch (err) {
|
|
283
|
+
void err;
|
|
894
284
|
}
|
|
895
285
|
}
|
|
896
|
-
return
|
|
286
|
+
return allIssues;
|
|
897
287
|
}
|
|
898
|
-
function
|
|
288
|
+
function analyzeIdentifiers(ast, filePath, context) {
|
|
899
289
|
const issues = [];
|
|
900
|
-
const scopeTracker = new ScopeTracker(
|
|
901
|
-
const context = buildCodeContext(file, ast);
|
|
902
|
-
const ancestors = [];
|
|
290
|
+
const scopeTracker = new ScopeTracker();
|
|
903
291
|
traverseAST(ast, {
|
|
904
|
-
enter: (node
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
getLineNumber(param),
|
|
915
|
-
{
|
|
916
|
-
isParameter: true
|
|
917
|
-
}
|
|
918
|
-
);
|
|
919
|
-
} else if (param.type === "ObjectPattern" || param.type === "ArrayPattern") {
|
|
920
|
-
extractIdentifiersFromPattern(param, scopeTracker, true);
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
} else if (node.type === "BlockStatement") {
|
|
925
|
-
scopeTracker.enterScope("block", node);
|
|
926
|
-
} else if (isLoopStatement(node)) {
|
|
927
|
-
scopeTracker.enterScope("loop", node);
|
|
928
|
-
} else if (node.type === "ClassDeclaration") {
|
|
929
|
-
scopeTracker.enterScope("class", node);
|
|
930
|
-
}
|
|
931
|
-
if (node.type === "VariableDeclarator") {
|
|
932
|
-
if (node.id.type === "Identifier") {
|
|
933
|
-
void isCoverageContext(node, ancestors);
|
|
934
|
-
scopeTracker.declareVariable(
|
|
935
|
-
node.id.name,
|
|
936
|
-
node.id,
|
|
937
|
-
getLineNumber(node.id),
|
|
938
|
-
{
|
|
939
|
-
type: "typeAnnotation" in node.id ? node.id.typeAnnotation : null,
|
|
940
|
-
isDestructured: false,
|
|
941
|
-
isLoopVariable: scopeTracker.getCurrentScopeType() === "loop"
|
|
942
|
-
}
|
|
943
|
-
);
|
|
944
|
-
} else if (node.id.type === "ObjectPattern" || node.id.type === "ArrayPattern") {
|
|
945
|
-
extractIdentifiersFromPattern(
|
|
946
|
-
node.id,
|
|
947
|
-
scopeTracker,
|
|
948
|
-
false,
|
|
949
|
-
ancestors
|
|
950
|
-
);
|
|
951
|
-
}
|
|
292
|
+
enter: (node) => {
|
|
293
|
+
if (node.type === "VariableDeclarator" && node.id.type === "Identifier") {
|
|
294
|
+
const isParameter = false;
|
|
295
|
+
const isLoopVariable = isLoopStatement(node.parent?.parent);
|
|
296
|
+
scopeTracker.declareVariable(
|
|
297
|
+
node.id.name,
|
|
298
|
+
node.id,
|
|
299
|
+
getLineNumber(node.id),
|
|
300
|
+
{ isParameter, isLoopVariable }
|
|
301
|
+
);
|
|
952
302
|
}
|
|
953
|
-
if (node.type === "
|
|
954
|
-
|
|
955
|
-
if (
|
|
956
|
-
scopeTracker.
|
|
303
|
+
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
|
|
304
|
+
node.params.forEach((param) => {
|
|
305
|
+
if (param.type === "Identifier") {
|
|
306
|
+
scopeTracker.declareVariable(
|
|
307
|
+
param.name,
|
|
308
|
+
param,
|
|
309
|
+
getLineNumber(param),
|
|
310
|
+
{ isParameter: true }
|
|
311
|
+
);
|
|
312
|
+
} else if (param.type === "ObjectPattern") {
|
|
313
|
+
extractDestructuredIdentifiers(param, true, scopeTracker);
|
|
957
314
|
}
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
},
|
|
961
|
-
leave: (node) => {
|
|
962
|
-
ancestors.pop();
|
|
963
|
-
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "BlockStatement" || isLoopStatement(node) || node.type === "ClassDeclaration") {
|
|
964
|
-
scopeTracker.exitScope();
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
});
|
|
968
|
-
const allVariables = scopeTracker.getAllVariables();
|
|
969
|
-
for (const varInfo of allVariables) {
|
|
970
|
-
const name = varInfo.name;
|
|
971
|
-
const line = varInfo.declarationLine;
|
|
972
|
-
if (disabledChecks.has("single-letter") && name.length === 1) continue;
|
|
973
|
-
if (disabledChecks.has("abbreviation") && name.length <= 3) continue;
|
|
974
|
-
const isInCoverage = ["s", "b", "f", "l"].includes(name) && varInfo.isDestructured;
|
|
975
|
-
if (isInCoverage) continue;
|
|
976
|
-
const functionComplexity = varInfo.node.type === "Identifier" && "parent" in varInfo.node ? calculateComplexity(varInfo.node) : context.complexity;
|
|
977
|
-
if (isAcceptableInContext(name, context, {
|
|
978
|
-
isLoopVariable: varInfo.isLoopVariable || allAbbreviations.has(name),
|
|
979
|
-
isParameter: varInfo.isParameter,
|
|
980
|
-
isDestructured: varInfo.isDestructured,
|
|
981
|
-
complexity: functionComplexity
|
|
982
|
-
})) {
|
|
983
|
-
continue;
|
|
984
|
-
}
|
|
985
|
-
if (name.length === 1 && !allAbbreviations.has(name) && !allShortWords.has(name)) {
|
|
986
|
-
const isShortLived = scopeTracker.isShortLived(varInfo, 5);
|
|
987
|
-
if (!isShortLived) {
|
|
988
|
-
issues.push({
|
|
989
|
-
file,
|
|
990
|
-
line,
|
|
991
|
-
type: "poor-naming",
|
|
992
|
-
identifier: name,
|
|
993
|
-
severity: adjustSeverity("minor", context, "poor-naming"),
|
|
994
|
-
suggestion: `Use descriptive variable name instead of single letter '${name}'`
|
|
995
315
|
});
|
|
996
316
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
issues
|
|
1004
|
-
|
|
1005
|
-
line,
|
|
1006
|
-
type: "abbreviation",
|
|
1007
|
-
identifier: name,
|
|
1008
|
-
severity: adjustSeverity("info", context, "abbreviation"),
|
|
1009
|
-
suggestion: `Consider using full word instead of abbreviation '${name}'`
|
|
1010
|
-
});
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
continue;
|
|
1014
|
-
}
|
|
1015
|
-
if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
1016
|
-
if (name.includes("_") && !name.startsWith("_") && name.toLowerCase() === name) {
|
|
1017
|
-
const camelCase = name.replace(
|
|
1018
|
-
/_([a-z])/g,
|
|
1019
|
-
(_, letter) => letter.toUpperCase()
|
|
317
|
+
if ((node.type === "ClassDeclaration" || node.type === "TSInterfaceDeclaration" || node.type === "TSTypeAliasDeclaration") && node.id) {
|
|
318
|
+
checkNamingConvention(
|
|
319
|
+
node.id.name,
|
|
320
|
+
"PascalCase",
|
|
321
|
+
node.id,
|
|
322
|
+
filePath,
|
|
323
|
+
issues,
|
|
324
|
+
context
|
|
1020
325
|
);
|
|
1021
|
-
issues.push({
|
|
1022
|
-
file,
|
|
1023
|
-
line,
|
|
1024
|
-
type: "convention-mix",
|
|
1025
|
-
identifier: name,
|
|
1026
|
-
severity: adjustSeverity("minor", context, "convention-mix"),
|
|
1027
|
-
suggestion: `Use camelCase '${camelCase}' instead of snake_case in TypeScript/JavaScript`
|
|
1028
|
-
});
|
|
1029
326
|
}
|
|
1030
327
|
}
|
|
328
|
+
});
|
|
329
|
+
for (const varInfo of scopeTracker.getVariables()) {
|
|
330
|
+
checkVariableNaming(varInfo, filePath, issues, context);
|
|
1031
331
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
"proxy",
|
|
1053
|
-
"sitemap",
|
|
1054
|
-
"robots",
|
|
1055
|
-
"gtag"
|
|
1056
|
-
].includes(name);
|
|
1057
|
-
const isLanguageKeyword = [
|
|
1058
|
-
"constructor",
|
|
1059
|
-
"toString",
|
|
1060
|
-
"valueOf",
|
|
1061
|
-
"toJSON"
|
|
1062
|
-
].includes(name);
|
|
1063
|
-
const isFrameworkPattern = name.match(
|
|
1064
|
-
/^(goto|fill|click|select|submit|wait|expect)\w*/
|
|
1065
|
-
);
|
|
1066
|
-
const isDescriptivePattern = name.match(
|
|
1067
|
-
/^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/
|
|
1068
|
-
) || name.match(
|
|
1069
|
-
/\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props|Data|Info|Details|State|Status|Response|Result)$/
|
|
1070
|
-
);
|
|
1071
|
-
const capitalCount = (name.match(/[A-Z]/g) || []).length;
|
|
1072
|
-
const isCompoundWord = capitalCount >= 3;
|
|
1073
|
-
if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isReactHook && !isHelperPattern && !isUtilityName && !isDescriptivePattern && !isCompoundWord && !isLanguageKeyword && !isFrameworkPattern) {
|
|
1074
|
-
issues.push({
|
|
1075
|
-
file,
|
|
1076
|
-
line,
|
|
1077
|
-
type: "unclear",
|
|
1078
|
-
identifier: name,
|
|
1079
|
-
severity: adjustSeverity("info", context, "unclear"),
|
|
1080
|
-
suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
|
|
1081
|
-
});
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
332
|
+
return issues;
|
|
333
|
+
}
|
|
334
|
+
function checkNamingConvention(name, convention, node, file, issues, context) {
|
|
335
|
+
let isValid = true;
|
|
336
|
+
if (convention === "PascalCase") {
|
|
337
|
+
isValid = /^[A-Z][a-zA-Z0-9]*$/.test(name);
|
|
338
|
+
} else if (convention === "camelCase") {
|
|
339
|
+
isValid = /^[a-z][a-zA-Z0-9]*$/.test(name);
|
|
340
|
+
} else if (convention === "UPPER_CASE") {
|
|
341
|
+
isValid = /^[A-Z][A-Z0-9_]*$/.test(name);
|
|
342
|
+
}
|
|
343
|
+
if (!isValid) {
|
|
344
|
+
const severity = adjustSeverity(import_core2.Severity.Info, context, "convention-mix");
|
|
345
|
+
issues.push({
|
|
346
|
+
file,
|
|
347
|
+
line: getLineNumber(node),
|
|
348
|
+
type: "convention-mix",
|
|
349
|
+
identifier: name,
|
|
350
|
+
severity,
|
|
351
|
+
suggestion: `Follow ${convention} for this identifier`
|
|
1085
352
|
});
|
|
1086
353
|
}
|
|
1087
|
-
return issues;
|
|
1088
354
|
}
|
|
1089
|
-
function
|
|
1090
|
-
|
|
1091
|
-
if (
|
|
1092
|
-
|
|
355
|
+
function checkVariableNaming(varInfo, file, issues, context) {
|
|
356
|
+
const { name, node, line, options } = varInfo;
|
|
357
|
+
if (isAcceptableInContext(name, context, options)) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (name.length === 1 && !options.isLoopVariable) {
|
|
361
|
+
const severity = adjustSeverity(import_core2.Severity.Minor, context, "poor-naming");
|
|
362
|
+
issues.push({
|
|
363
|
+
file,
|
|
364
|
+
line,
|
|
365
|
+
type: "poor-naming",
|
|
366
|
+
identifier: name,
|
|
367
|
+
severity,
|
|
368
|
+
suggestion: "Use a more descriptive name than a single letter"
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
const vagueNames = [
|
|
372
|
+
"data",
|
|
373
|
+
"info",
|
|
374
|
+
"item",
|
|
375
|
+
"obj",
|
|
376
|
+
"val",
|
|
377
|
+
"tmp",
|
|
378
|
+
"temp",
|
|
379
|
+
"thing",
|
|
380
|
+
"stuff"
|
|
381
|
+
];
|
|
382
|
+
if (vagueNames.includes(name.toLowerCase())) {
|
|
383
|
+
const severity = adjustSeverity(import_core2.Severity.Minor, context, "poor-naming");
|
|
384
|
+
issues.push({
|
|
385
|
+
file,
|
|
386
|
+
line,
|
|
387
|
+
type: "poor-naming",
|
|
388
|
+
identifier: name,
|
|
389
|
+
severity,
|
|
390
|
+
suggestion: `Avoid vague names like '${name}'. What does this data represent?`
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
if (name.length > 1 && name.length <= 3 && !options.isLoopVariable && !isCommonAbbreviation(name)) {
|
|
394
|
+
const severity = adjustSeverity(import_core2.Severity.Info, context, "abbreviation");
|
|
395
|
+
issues.push({
|
|
396
|
+
file,
|
|
397
|
+
line,
|
|
398
|
+
type: "abbreviation",
|
|
399
|
+
identifier: name,
|
|
400
|
+
severity,
|
|
401
|
+
suggestion: "Avoid non-standard abbreviations"
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function isCommonAbbreviation(name) {
|
|
406
|
+
const common = ["id", "db", "fs", "os", "ip", "ui", "ux", "api", "env", "url"];
|
|
407
|
+
return common.includes(name.toLowerCase());
|
|
408
|
+
}
|
|
409
|
+
var ScopeTracker = class {
|
|
410
|
+
constructor() {
|
|
411
|
+
this.variables = [];
|
|
412
|
+
}
|
|
413
|
+
declareVariable(name, node, line, options = {}) {
|
|
414
|
+
this.variables.push({ name, node, line, options });
|
|
415
|
+
}
|
|
416
|
+
getVariables() {
|
|
417
|
+
return this.variables;
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
function extractDestructuredIdentifiers(node, isParameter, scopeTracker) {
|
|
421
|
+
if (node.type === "ObjectPattern") {
|
|
422
|
+
node.properties.forEach((prop) => {
|
|
1093
423
|
if (prop.type === "Property" && prop.value.type === "Identifier") {
|
|
1094
424
|
scopeTracker.declareVariable(
|
|
1095
425
|
prop.value.name,
|
|
@@ -1100,21 +430,11 @@ function extractIdentifiersFromPattern(pattern, scopeTracker, isParameter, ances
|
|
|
1100
430
|
isDestructured: true
|
|
1101
431
|
}
|
|
1102
432
|
);
|
|
1103
|
-
} else if (prop.type === "RestElement" && prop.argument.type === "Identifier") {
|
|
1104
|
-
scopeTracker.declareVariable(
|
|
1105
|
-
prop.argument.name,
|
|
1106
|
-
prop.argument,
|
|
1107
|
-
getLineNumber(prop.argument),
|
|
1108
|
-
{
|
|
1109
|
-
isParameter,
|
|
1110
|
-
isDestructured: true
|
|
1111
|
-
}
|
|
1112
|
-
);
|
|
1113
433
|
}
|
|
1114
|
-
}
|
|
1115
|
-
} else if (
|
|
1116
|
-
for (const element of
|
|
1117
|
-
if (element
|
|
434
|
+
});
|
|
435
|
+
} else if (node.type === "ArrayPattern") {
|
|
436
|
+
for (const element of node.elements) {
|
|
437
|
+
if (element?.type === "Identifier") {
|
|
1118
438
|
scopeTracker.declareVariable(
|
|
1119
439
|
element.name,
|
|
1120
440
|
element,
|
|
@@ -1130,10 +450,10 @@ function extractIdentifiersFromPattern(pattern, scopeTracker, isParameter, ances
|
|
|
1130
450
|
}
|
|
1131
451
|
|
|
1132
452
|
// src/analyzers/naming-python.ts
|
|
1133
|
-
var
|
|
453
|
+
var import_core3 = require("@aiready/core");
|
|
1134
454
|
async function analyzePythonNaming(files) {
|
|
1135
455
|
const issues = [];
|
|
1136
|
-
const parser = (0,
|
|
456
|
+
const parser = (0, import_core3.getParser)("dummy.py");
|
|
1137
457
|
if (!parser) {
|
|
1138
458
|
console.warn("Python parser not available");
|
|
1139
459
|
return issues;
|
|
@@ -1177,7 +497,7 @@ async function analyzePythonNaming(files) {
|
|
|
1177
497
|
return issues;
|
|
1178
498
|
}
|
|
1179
499
|
function checkPythonNaming(identifier, type, file, line) {
|
|
1180
|
-
const parser = (0,
|
|
500
|
+
const parser = (0, import_core3.getParser)("dummy.py");
|
|
1181
501
|
const conventions = parser?.getNamingConventions();
|
|
1182
502
|
if (!conventions) return null;
|
|
1183
503
|
if (conventions.exceptions?.includes(identifier)) {
|
|
@@ -1252,162 +572,63 @@ function toPascalCase(str) {
|
|
|
1252
572
|
}
|
|
1253
573
|
|
|
1254
574
|
// src/analyzers/patterns.ts
|
|
1255
|
-
var
|
|
1256
|
-
|
|
575
|
+
var import_fs2 = require("fs");
|
|
576
|
+
var import_core4 = require("@aiready/core");
|
|
577
|
+
async function analyzePatterns(filePaths) {
|
|
1257
578
|
const issues = [];
|
|
1258
|
-
const
|
|
1259
|
-
|
|
1260
|
-
const
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
const patterns = {
|
|
1268
|
-
tryCatch: [],
|
|
1269
|
-
throwsError: [],
|
|
1270
|
-
returnsNull: [],
|
|
1271
|
-
returnsError: []
|
|
579
|
+
const contents = /* @__PURE__ */ new Map();
|
|
580
|
+
const tryCatchPattern = /try\s*\{/g;
|
|
581
|
+
const catchPattern = /catch\s*\(\s*(\w+)\s*\)/g;
|
|
582
|
+
const styleStats = {
|
|
583
|
+
tryCatch: 0,
|
|
584
|
+
thenCatch: 0,
|
|
585
|
+
asyncAwait: 0,
|
|
586
|
+
commonJs: 0,
|
|
587
|
+
esm: 0
|
|
1272
588
|
};
|
|
1273
|
-
for (const
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
if (content.match(/return \{ error:/)) {
|
|
1285
|
-
patterns.returnsError.push(file);
|
|
589
|
+
for (const filePath of filePaths) {
|
|
590
|
+
try {
|
|
591
|
+
const content = (0, import_fs2.readFileSync)(filePath, "utf-8");
|
|
592
|
+
contents.set(filePath, content);
|
|
593
|
+
if (content.match(tryCatchPattern)) styleStats.tryCatch++;
|
|
594
|
+
if (content.match(/\.catch\s*\(/)) styleStats.thenCatch++;
|
|
595
|
+
if (content.match(/\bawait\b/)) styleStats.asyncAwait++;
|
|
596
|
+
if (content.match(/\brequire\s*\(/)) styleStats.commonJs++;
|
|
597
|
+
if (content.match(/\bimport\b.*\bfrom\b/)) styleStats.esm++;
|
|
598
|
+
} catch (err) {
|
|
599
|
+
void err;
|
|
1286
600
|
}
|
|
1287
601
|
}
|
|
1288
|
-
const
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
if (strategiesUsed > 2) {
|
|
602
|
+
const totalFiles = filePaths.length;
|
|
603
|
+
if (styleStats.tryCatch > 0 && styleStats.thenCatch > 0) {
|
|
604
|
+
const dominant = styleStats.tryCatch >= styleStats.thenCatch ? "try-catch" : ".catch()";
|
|
605
|
+
const minority = dominant === "try-catch" ? ".catch()" : "try-catch";
|
|
1293
606
|
issues.push({
|
|
1294
|
-
files:
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
...patterns.returnsNull,
|
|
1299
|
-
...patterns.returnsError
|
|
1300
|
-
])
|
|
1301
|
-
],
|
|
607
|
+
files: filePaths.filter((f) => {
|
|
608
|
+
const c = contents.get(f) || "";
|
|
609
|
+
return minority === "try-catch" ? c.match(tryCatchPattern) : c.match(/\.catch\s*\(/);
|
|
610
|
+
}),
|
|
1302
611
|
type: "error-handling",
|
|
1303
|
-
description:
|
|
1304
|
-
examples: [
|
|
1305
|
-
|
|
1306
|
-
patterns.throwsError.length > 0 ? `Throws errors in ${patterns.throwsError.length} files` : "",
|
|
1307
|
-
patterns.returnsNull.length > 0 ? `Returns null in ${patterns.returnsNull.length} files` : "",
|
|
1308
|
-
patterns.returnsError.length > 0 ? `Returns error objects in ${patterns.returnsError.length} files` : ""
|
|
1309
|
-
].filter((e) => e),
|
|
1310
|
-
severity: "major"
|
|
612
|
+
description: `Mixed error handling styles: codebase primarily uses ${dominant}, but found ${minority} in some files.`,
|
|
613
|
+
examples: [dominant, minority],
|
|
614
|
+
severity: import_core4.Severity.Minor
|
|
1311
615
|
});
|
|
1312
616
|
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
async function analyzeAsyncPatterns(files) {
|
|
1316
|
-
const patterns = {
|
|
1317
|
-
asyncAwait: [],
|
|
1318
|
-
promises: [],
|
|
1319
|
-
callbacks: []
|
|
1320
|
-
};
|
|
1321
|
-
for (const file of files) {
|
|
1322
|
-
const content = await (0, import_core3.readFileContent)(file);
|
|
1323
|
-
if (content.match(/async\s+(function|\(|[a-zA-Z])/)) {
|
|
1324
|
-
patterns.asyncAwait.push(file);
|
|
1325
|
-
}
|
|
1326
|
-
if (content.match(/\.then\(/) || content.match(/\.catch\(/)) {
|
|
1327
|
-
patterns.promises.push(file);
|
|
1328
|
-
}
|
|
1329
|
-
if (content.match(/callback\s*\(/) || content.match(/\(\s*err\s*,/)) {
|
|
1330
|
-
patterns.callbacks.push(file);
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
const issues = [];
|
|
1334
|
-
if (patterns.callbacks.length > 0 && patterns.asyncAwait.length > 0) {
|
|
1335
|
-
issues.push({
|
|
1336
|
-
files: [...patterns.callbacks, ...patterns.asyncAwait],
|
|
1337
|
-
type: "async-style",
|
|
1338
|
-
description: "Mixed async patterns: callbacks and async/await",
|
|
1339
|
-
examples: [
|
|
1340
|
-
`Callbacks found in: ${patterns.callbacks.slice(0, 3).join(", ")}`,
|
|
1341
|
-
`Async/await used in: ${patterns.asyncAwait.slice(0, 3).join(", ")}`
|
|
1342
|
-
],
|
|
1343
|
-
severity: "minor"
|
|
1344
|
-
});
|
|
1345
|
-
}
|
|
1346
|
-
if (patterns.promises.length > patterns.asyncAwait.length * 0.3 && patterns.asyncAwait.length > 0) {
|
|
617
|
+
if (styleStats.commonJs > 0 && styleStats.esm > 0) {
|
|
618
|
+
const minority = styleStats.esm >= styleStats.commonJs ? "CommonJS (require)" : "ESM (import)";
|
|
1347
619
|
issues.push({
|
|
1348
|
-
files:
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
severity: "info"
|
|
1353
|
-
});
|
|
1354
|
-
}
|
|
1355
|
-
return issues;
|
|
1356
|
-
}
|
|
1357
|
-
async function analyzeImportStyles(files) {
|
|
1358
|
-
const patterns = {
|
|
1359
|
-
esModules: [],
|
|
1360
|
-
commonJS: [],
|
|
1361
|
-
mixed: []
|
|
1362
|
-
};
|
|
1363
|
-
for (const file of files) {
|
|
1364
|
-
const content = await (0, import_core3.readFileContent)(file);
|
|
1365
|
-
const hasESM = content.match(/^import\s+/m);
|
|
1366
|
-
const hasCJS = hasActualRequireCalls(content);
|
|
1367
|
-
if (hasESM && hasCJS) {
|
|
1368
|
-
patterns.mixed.push(file);
|
|
1369
|
-
} else if (hasESM) {
|
|
1370
|
-
patterns.esModules.push(file);
|
|
1371
|
-
} else if (hasCJS) {
|
|
1372
|
-
patterns.commonJS.push(file);
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
const issues = [];
|
|
1376
|
-
if (patterns.mixed.length > 0) {
|
|
1377
|
-
issues.push({
|
|
1378
|
-
files: patterns.mixed,
|
|
620
|
+
files: filePaths.filter((f) => {
|
|
621
|
+
const c = contents.get(f) || "";
|
|
622
|
+
return minority === "CommonJS (require)" ? c.match(/\brequire\s*\(/) : c.match(/\bimport\b/);
|
|
623
|
+
}),
|
|
1379
624
|
type: "import-style",
|
|
1380
|
-
description:
|
|
1381
|
-
examples:
|
|
1382
|
-
severity:
|
|
625
|
+
description: `Mixed module systems: found both ESM and CommonJS.`,
|
|
626
|
+
examples: ['import X from "y"', 'const X = require("y")'],
|
|
627
|
+
severity: import_core4.Severity.Major
|
|
1383
628
|
});
|
|
1384
629
|
}
|
|
1385
|
-
if (patterns.esModules.length > 0 && patterns.commonJS.length > 0) {
|
|
1386
|
-
const ratio = patterns.commonJS.length / (patterns.esModules.length + patterns.commonJS.length);
|
|
1387
|
-
if (ratio > 0.2 && ratio < 0.8) {
|
|
1388
|
-
issues.push({
|
|
1389
|
-
files: [...patterns.esModules, ...patterns.commonJS],
|
|
1390
|
-
type: "import-style",
|
|
1391
|
-
description: "Inconsistent import styles across project",
|
|
1392
|
-
examples: [
|
|
1393
|
-
`ES modules: ${patterns.esModules.length} files`,
|
|
1394
|
-
`CommonJS: ${patterns.commonJS.length} files`
|
|
1395
|
-
],
|
|
1396
|
-
severity: "minor"
|
|
1397
|
-
});
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
630
|
return issues;
|
|
1401
631
|
}
|
|
1402
|
-
function hasActualRequireCalls(content) {
|
|
1403
|
-
let cleaned = content.replace(/\/\/.*$/gm, "");
|
|
1404
|
-
cleaned = cleaned.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
1405
|
-
cleaned = cleaned.replace(/"[^"\n]*"/g, '""');
|
|
1406
|
-
cleaned = cleaned.replace(/'[^'\n]*'/g, "''");
|
|
1407
|
-
cleaned = cleaned.replace(/`[^`]*`/g, "``");
|
|
1408
|
-
cleaned = cleaned.replace(/\/[^/\n]*require[^/\n]*\/[gimsuvy]*/g, "");
|
|
1409
|
-
return /require\s*\(/.test(cleaned);
|
|
1410
|
-
}
|
|
1411
632
|
|
|
1412
633
|
// src/analyzer.ts
|
|
1413
634
|
async function analyzeConsistency(options) {
|
|
@@ -1416,11 +637,11 @@ async function analyzeConsistency(options) {
|
|
|
1416
637
|
checkPatterns = true,
|
|
1417
638
|
checkArchitecture = false,
|
|
1418
639
|
// Not implemented yet
|
|
1419
|
-
minSeverity =
|
|
640
|
+
minSeverity = import_core5.Severity.Info,
|
|
1420
641
|
...scanOptions
|
|
1421
642
|
} = options;
|
|
1422
643
|
void checkArchitecture;
|
|
1423
|
-
const filePaths = await (0,
|
|
644
|
+
const filePaths = await (0, import_core5.scanFiles)(scanOptions);
|
|
1424
645
|
const tsJsFiles = filePaths.filter((f) => /\.(ts|tsx|js|jsx)$/i.test(f));
|
|
1425
646
|
const pythonFiles = filePaths.filter((f) => /\.py$/i.test(f));
|
|
1426
647
|
let namingIssues = [];
|
|
@@ -1437,9 +658,9 @@ async function analyzeConsistency(options) {
|
|
|
1437
658
|
continue;
|
|
1438
659
|
}
|
|
1439
660
|
const consistencyIssue = {
|
|
1440
|
-
type: issue.type === "convention-mix" ?
|
|
661
|
+
type: issue.type === "convention-mix" ? import_core5.IssueType.NamingInconsistency : import_core5.IssueType.NamingQuality,
|
|
1441
662
|
category: "naming",
|
|
1442
|
-
severity: issue.severity,
|
|
663
|
+
severity: getSeverityEnum(issue.severity),
|
|
1443
664
|
message: `${issue.type}: ${issue.identifier}`,
|
|
1444
665
|
location: {
|
|
1445
666
|
file: issue.file,
|
|
@@ -1458,9 +679,9 @@ async function analyzeConsistency(options) {
|
|
|
1458
679
|
continue;
|
|
1459
680
|
}
|
|
1460
681
|
const consistencyIssue = {
|
|
1461
|
-
type:
|
|
682
|
+
type: import_core5.IssueType.PatternInconsistency,
|
|
1462
683
|
category: "patterns",
|
|
1463
|
-
severity: issue.severity,
|
|
684
|
+
severity: getSeverityEnum(issue.severity),
|
|
1464
685
|
message: issue.description,
|
|
1465
686
|
location: {
|
|
1466
687
|
file: issue.files[0] || "multiple files",
|
|
@@ -1487,16 +708,17 @@ async function analyzeConsistency(options) {
|
|
|
1487
708
|
});
|
|
1488
709
|
}
|
|
1489
710
|
results.sort((fileResultA, fileResultB) => {
|
|
1490
|
-
const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
|
|
1491
711
|
const maxSeverityA = Math.min(
|
|
1492
|
-
...fileResultA.issues.map(
|
|
1493
|
-
|
|
1494
|
-
|
|
712
|
+
...fileResultA.issues.map((i) => {
|
|
713
|
+
const val = getSeverityLevel(i.severity);
|
|
714
|
+
return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
|
|
715
|
+
})
|
|
1495
716
|
);
|
|
1496
717
|
const maxSeverityB = Math.min(
|
|
1497
|
-
...fileResultB.issues.map(
|
|
1498
|
-
|
|
1499
|
-
|
|
718
|
+
...fileResultB.issues.map((i) => {
|
|
719
|
+
const val = getSeverityLevel(i.severity);
|
|
720
|
+
return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
|
|
721
|
+
})
|
|
1500
722
|
);
|
|
1501
723
|
if (maxSeverityA !== maxSeverityB) {
|
|
1502
724
|
return maxSeverityA - maxSeverityB;
|
|
@@ -1522,16 +744,52 @@ async function analyzeConsistency(options) {
|
|
|
1522
744
|
recommendations
|
|
1523
745
|
};
|
|
1524
746
|
}
|
|
747
|
+
function getSeverityLevel(s) {
|
|
748
|
+
if (s === import_core5.Severity.Critical || s === "critical") return 4;
|
|
749
|
+
if (s === import_core5.Severity.Major || s === "major") return 3;
|
|
750
|
+
if (s === import_core5.Severity.Minor || s === "minor") return 2;
|
|
751
|
+
if (s === import_core5.Severity.Info || s === "info") return 1;
|
|
752
|
+
return 0;
|
|
753
|
+
}
|
|
754
|
+
function getSeverityEnum(s) {
|
|
755
|
+
const val = getSeverityLevel(s);
|
|
756
|
+
switch (val) {
|
|
757
|
+
case 4:
|
|
758
|
+
return import_core5.Severity.Critical;
|
|
759
|
+
case 3:
|
|
760
|
+
return import_core5.Severity.Major;
|
|
761
|
+
case 2:
|
|
762
|
+
return import_core5.Severity.Minor;
|
|
763
|
+
case 1:
|
|
764
|
+
return import_core5.Severity.Info;
|
|
765
|
+
default:
|
|
766
|
+
return import_core5.Severity.Info;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
1525
769
|
function shouldIncludeSeverity(severity, minSeverity) {
|
|
1526
|
-
|
|
1527
|
-
return severityLevels[severity] >= severityLevels[minSeverity];
|
|
770
|
+
return getSeverityLevel(severity) >= getSeverityLevel(minSeverity);
|
|
1528
771
|
}
|
|
1529
772
|
function calculateConsistencyScore(issues) {
|
|
1530
|
-
|
|
1531
|
-
const
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
773
|
+
let totalWeight = 0;
|
|
774
|
+
for (const issue of issues) {
|
|
775
|
+
const val = getSeverityLevel(issue.severity);
|
|
776
|
+
switch (val) {
|
|
777
|
+
case 4:
|
|
778
|
+
totalWeight += 10;
|
|
779
|
+
break;
|
|
780
|
+
case 3:
|
|
781
|
+
totalWeight += 5;
|
|
782
|
+
break;
|
|
783
|
+
case 2:
|
|
784
|
+
totalWeight += 2;
|
|
785
|
+
break;
|
|
786
|
+
case 1:
|
|
787
|
+
totalWeight += 1;
|
|
788
|
+
break;
|
|
789
|
+
default:
|
|
790
|
+
totalWeight += 1;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
1535
793
|
return Math.max(0, 1 - totalWeight / 100);
|
|
1536
794
|
}
|
|
1537
795
|
function generateRecommendations(namingIssues, patternIssues) {
|
|
@@ -1586,9 +844,9 @@ function generateRecommendations(namingIssues, patternIssues) {
|
|
|
1586
844
|
|
|
1587
845
|
// src/cli.ts
|
|
1588
846
|
var import_chalk = __toESM(require("chalk"));
|
|
1589
|
-
var
|
|
1590
|
-
var
|
|
1591
|
-
var
|
|
847
|
+
var import_fs3 = require("fs");
|
|
848
|
+
var import_path = require("path");
|
|
849
|
+
var import_core6 = require("@aiready/core");
|
|
1592
850
|
var program = new import_commander.Command();
|
|
1593
851
|
program.name("aiready-consistency").description(
|
|
1594
852
|
"Detect consistency patterns in naming, code structure, and architecture"
|
|
@@ -1627,7 +885,7 @@ EXAMPLES:
|
|
|
1627
885
|
).option("--output-file <path>", "Output file path (for json/markdown)").action(async (directory, options) => {
|
|
1628
886
|
console.log(import_chalk.default.blue("\u{1F50D} Analyzing consistency...\n"));
|
|
1629
887
|
const startTime = Date.now();
|
|
1630
|
-
const config = await (0,
|
|
888
|
+
const config = await (0, import_core6.loadConfig)(directory);
|
|
1631
889
|
const defaults = {
|
|
1632
890
|
checkNaming: true,
|
|
1633
891
|
checkPatterns: true,
|
|
@@ -1636,7 +894,7 @@ EXAMPLES:
|
|
|
1636
894
|
include: void 0,
|
|
1637
895
|
exclude: void 0
|
|
1638
896
|
};
|
|
1639
|
-
const mergedConfig = (0,
|
|
897
|
+
const mergedConfig = (0, import_core6.mergeConfigWithDefaults)(config, defaults);
|
|
1640
898
|
const finalOptions = {
|
|
1641
899
|
rootDir: directory,
|
|
1642
900
|
checkNaming: options.naming !== false && mergedConfig.checkNaming,
|
|
@@ -1650,29 +908,29 @@ EXAMPLES:
|
|
|
1650
908
|
const elapsedTime = ((Date.now() - startTime) / 1e3).toFixed(2);
|
|
1651
909
|
if (options.output === "json") {
|
|
1652
910
|
const output = JSON.stringify(report, null, 2);
|
|
1653
|
-
const outputPath = (0,
|
|
911
|
+
const outputPath = (0, import_core6.resolveOutputPath)(
|
|
1654
912
|
options.outputFile,
|
|
1655
913
|
`consistency-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
|
|
1656
914
|
directory
|
|
1657
915
|
);
|
|
1658
|
-
const dir = (0,
|
|
1659
|
-
if (!(0,
|
|
1660
|
-
(0,
|
|
916
|
+
const dir = (0, import_path.dirname)(outputPath);
|
|
917
|
+
if (!(0, import_fs3.existsSync)(dir)) {
|
|
918
|
+
(0, import_fs3.mkdirSync)(dir, { recursive: true });
|
|
1661
919
|
}
|
|
1662
|
-
(0,
|
|
920
|
+
(0, import_fs3.writeFileSync)(outputPath, output);
|
|
1663
921
|
console.log(import_chalk.default.green(`\u2713 Report saved to ${outputPath}`));
|
|
1664
922
|
} else if (options.output === "markdown") {
|
|
1665
923
|
const markdown = generateMarkdownReport(report, elapsedTime);
|
|
1666
|
-
const outputPath = (0,
|
|
924
|
+
const outputPath = (0, import_core6.resolveOutputPath)(
|
|
1667
925
|
options.outputFile,
|
|
1668
926
|
`consistency-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.md`,
|
|
1669
927
|
directory
|
|
1670
928
|
);
|
|
1671
|
-
const dir = (0,
|
|
1672
|
-
if (!(0,
|
|
1673
|
-
(0,
|
|
929
|
+
const dir = (0, import_path.dirname)(outputPath);
|
|
930
|
+
if (!(0, import_fs3.existsSync)(dir)) {
|
|
931
|
+
(0, import_fs3.mkdirSync)(dir, { recursive: true });
|
|
1674
932
|
}
|
|
1675
|
-
(0,
|
|
933
|
+
(0, import_fs3.writeFileSync)(outputPath, markdown);
|
|
1676
934
|
console.log(import_chalk.default.green(`\u2713 Report saved to ${outputPath}`));
|
|
1677
935
|
} else {
|
|
1678
936
|
displayConsoleReport(report, elapsedTime);
|