@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/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,168 @@ 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 = [
|
|
407
|
+
"id",
|
|
408
|
+
"db",
|
|
409
|
+
"fs",
|
|
410
|
+
"os",
|
|
411
|
+
"ip",
|
|
412
|
+
"ui",
|
|
413
|
+
"ux",
|
|
414
|
+
"api",
|
|
415
|
+
"env",
|
|
416
|
+
"url"
|
|
417
|
+
];
|
|
418
|
+
return common.includes(name.toLowerCase());
|
|
419
|
+
}
|
|
420
|
+
var ScopeTracker = class {
|
|
421
|
+
constructor() {
|
|
422
|
+
this.variables = [];
|
|
423
|
+
}
|
|
424
|
+
declareVariable(name, node, line, options = {}) {
|
|
425
|
+
this.variables.push({ name, node, line, options });
|
|
426
|
+
}
|
|
427
|
+
getVariables() {
|
|
428
|
+
return this.variables;
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
function extractDestructuredIdentifiers(node, isParameter, scopeTracker) {
|
|
432
|
+
if (node.type === "ObjectPattern") {
|
|
433
|
+
node.properties.forEach((prop) => {
|
|
1093
434
|
if (prop.type === "Property" && prop.value.type === "Identifier") {
|
|
1094
435
|
scopeTracker.declareVariable(
|
|
1095
436
|
prop.value.name,
|
|
@@ -1100,21 +441,11 @@ function extractIdentifiersFromPattern(pattern, scopeTracker, isParameter, ances
|
|
|
1100
441
|
isDestructured: true
|
|
1101
442
|
}
|
|
1102
443
|
);
|
|
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
444
|
}
|
|
1114
|
-
}
|
|
1115
|
-
} else if (
|
|
1116
|
-
for (const element of
|
|
1117
|
-
if (element
|
|
445
|
+
});
|
|
446
|
+
} else if (node.type === "ArrayPattern") {
|
|
447
|
+
for (const element of node.elements) {
|
|
448
|
+
if (element?.type === "Identifier") {
|
|
1118
449
|
scopeTracker.declareVariable(
|
|
1119
450
|
element.name,
|
|
1120
451
|
element,
|
|
@@ -1130,10 +461,10 @@ function extractIdentifiersFromPattern(pattern, scopeTracker, isParameter, ances
|
|
|
1130
461
|
}
|
|
1131
462
|
|
|
1132
463
|
// src/analyzers/naming-python.ts
|
|
1133
|
-
var
|
|
464
|
+
var import_core3 = require("@aiready/core");
|
|
1134
465
|
async function analyzePythonNaming(files) {
|
|
1135
466
|
const issues = [];
|
|
1136
|
-
const parser = (0,
|
|
467
|
+
const parser = (0, import_core3.getParser)("dummy.py");
|
|
1137
468
|
if (!parser) {
|
|
1138
469
|
console.warn("Python parser not available");
|
|
1139
470
|
return issues;
|
|
@@ -1177,7 +508,7 @@ async function analyzePythonNaming(files) {
|
|
|
1177
508
|
return issues;
|
|
1178
509
|
}
|
|
1179
510
|
function checkPythonNaming(identifier, type, file, line) {
|
|
1180
|
-
const parser = (0,
|
|
511
|
+
const parser = (0, import_core3.getParser)("dummy.py");
|
|
1181
512
|
const conventions = parser?.getNamingConventions();
|
|
1182
513
|
if (!conventions) return null;
|
|
1183
514
|
if (conventions.exceptions?.includes(identifier)) {
|
|
@@ -1252,162 +583,63 @@ function toPascalCase(str) {
|
|
|
1252
583
|
}
|
|
1253
584
|
|
|
1254
585
|
// src/analyzers/patterns.ts
|
|
1255
|
-
var
|
|
1256
|
-
|
|
586
|
+
var import_fs2 = require("fs");
|
|
587
|
+
var import_core4 = require("@aiready/core");
|
|
588
|
+
async function analyzePatterns(filePaths) {
|
|
1257
589
|
const issues = [];
|
|
1258
|
-
const
|
|
1259
|
-
|
|
1260
|
-
const
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
const patterns = {
|
|
1268
|
-
tryCatch: [],
|
|
1269
|
-
throwsError: [],
|
|
1270
|
-
returnsNull: [],
|
|
1271
|
-
returnsError: []
|
|
590
|
+
const contents = /* @__PURE__ */ new Map();
|
|
591
|
+
const tryCatchPattern = /try\s*\{/g;
|
|
592
|
+
const catchPattern = /catch\s*\(\s*(\w+)\s*\)/g;
|
|
593
|
+
const styleStats = {
|
|
594
|
+
tryCatch: 0,
|
|
595
|
+
thenCatch: 0,
|
|
596
|
+
asyncAwait: 0,
|
|
597
|
+
commonJs: 0,
|
|
598
|
+
esm: 0
|
|
1272
599
|
};
|
|
1273
|
-
for (const
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
if (content.match(/return \{ error:/)) {
|
|
1285
|
-
patterns.returnsError.push(file);
|
|
600
|
+
for (const filePath of filePaths) {
|
|
601
|
+
try {
|
|
602
|
+
const content = (0, import_fs2.readFileSync)(filePath, "utf-8");
|
|
603
|
+
contents.set(filePath, content);
|
|
604
|
+
if (content.match(tryCatchPattern)) styleStats.tryCatch++;
|
|
605
|
+
if (content.match(/\.catch\s*\(/)) styleStats.thenCatch++;
|
|
606
|
+
if (content.match(/\bawait\b/)) styleStats.asyncAwait++;
|
|
607
|
+
if (content.match(/\brequire\s*\(/)) styleStats.commonJs++;
|
|
608
|
+
if (content.match(/\bimport\b.*\bfrom\b/)) styleStats.esm++;
|
|
609
|
+
} catch (err) {
|
|
610
|
+
void err;
|
|
1286
611
|
}
|
|
1287
612
|
}
|
|
1288
|
-
const
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
if (strategiesUsed > 2) {
|
|
613
|
+
const totalFiles = filePaths.length;
|
|
614
|
+
if (styleStats.tryCatch > 0 && styleStats.thenCatch > 0) {
|
|
615
|
+
const dominant = styleStats.tryCatch >= styleStats.thenCatch ? "try-catch" : ".catch()";
|
|
616
|
+
const minority = dominant === "try-catch" ? ".catch()" : "try-catch";
|
|
1293
617
|
issues.push({
|
|
1294
|
-
files:
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
...patterns.returnsNull,
|
|
1299
|
-
...patterns.returnsError
|
|
1300
|
-
])
|
|
1301
|
-
],
|
|
618
|
+
files: filePaths.filter((f) => {
|
|
619
|
+
const c = contents.get(f) || "";
|
|
620
|
+
return minority === "try-catch" ? c.match(tryCatchPattern) : c.match(/\.catch\s*\(/);
|
|
621
|
+
}),
|
|
1302
622
|
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"
|
|
623
|
+
description: `Mixed error handling styles: codebase primarily uses ${dominant}, but found ${minority} in some files.`,
|
|
624
|
+
examples: [dominant, minority],
|
|
625
|
+
severity: import_core4.Severity.Minor
|
|
1311
626
|
});
|
|
1312
627
|
}
|
|
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) {
|
|
628
|
+
if (styleStats.commonJs > 0 && styleStats.esm > 0) {
|
|
629
|
+
const minority = styleStats.esm >= styleStats.commonJs ? "CommonJS (require)" : "ESM (import)";
|
|
1347
630
|
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,
|
|
631
|
+
files: filePaths.filter((f) => {
|
|
632
|
+
const c = contents.get(f) || "";
|
|
633
|
+
return minority === "CommonJS (require)" ? c.match(/\brequire\s*\(/) : c.match(/\bimport\b/);
|
|
634
|
+
}),
|
|
1379
635
|
type: "import-style",
|
|
1380
|
-
description:
|
|
1381
|
-
examples:
|
|
1382
|
-
severity:
|
|
636
|
+
description: `Mixed module systems: found both ESM and CommonJS.`,
|
|
637
|
+
examples: ['import X from "y"', 'const X = require("y")'],
|
|
638
|
+
severity: import_core4.Severity.Major
|
|
1383
639
|
});
|
|
1384
640
|
}
|
|
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
641
|
return issues;
|
|
1401
642
|
}
|
|
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
643
|
|
|
1412
644
|
// src/analyzer.ts
|
|
1413
645
|
async function analyzeConsistency(options) {
|
|
@@ -1416,11 +648,11 @@ async function analyzeConsistency(options) {
|
|
|
1416
648
|
checkPatterns = true,
|
|
1417
649
|
checkArchitecture = false,
|
|
1418
650
|
// Not implemented yet
|
|
1419
|
-
minSeverity =
|
|
651
|
+
minSeverity = import_core5.Severity.Info,
|
|
1420
652
|
...scanOptions
|
|
1421
653
|
} = options;
|
|
1422
654
|
void checkArchitecture;
|
|
1423
|
-
const filePaths = await (0,
|
|
655
|
+
const filePaths = await (0, import_core5.scanFiles)(scanOptions);
|
|
1424
656
|
const tsJsFiles = filePaths.filter((f) => /\.(ts|tsx|js|jsx)$/i.test(f));
|
|
1425
657
|
const pythonFiles = filePaths.filter((f) => /\.py$/i.test(f));
|
|
1426
658
|
let namingIssues = [];
|
|
@@ -1437,9 +669,9 @@ async function analyzeConsistency(options) {
|
|
|
1437
669
|
continue;
|
|
1438
670
|
}
|
|
1439
671
|
const consistencyIssue = {
|
|
1440
|
-
type: issue.type === "convention-mix" ?
|
|
672
|
+
type: issue.type === "convention-mix" ? import_core5.IssueType.NamingInconsistency : import_core5.IssueType.NamingQuality,
|
|
1441
673
|
category: "naming",
|
|
1442
|
-
severity: issue.severity,
|
|
674
|
+
severity: getSeverityEnum(issue.severity),
|
|
1443
675
|
message: `${issue.type}: ${issue.identifier}`,
|
|
1444
676
|
location: {
|
|
1445
677
|
file: issue.file,
|
|
@@ -1458,9 +690,9 @@ async function analyzeConsistency(options) {
|
|
|
1458
690
|
continue;
|
|
1459
691
|
}
|
|
1460
692
|
const consistencyIssue = {
|
|
1461
|
-
type:
|
|
693
|
+
type: import_core5.IssueType.PatternInconsistency,
|
|
1462
694
|
category: "patterns",
|
|
1463
|
-
severity: issue.severity,
|
|
695
|
+
severity: getSeverityEnum(issue.severity),
|
|
1464
696
|
message: issue.description,
|
|
1465
697
|
location: {
|
|
1466
698
|
file: issue.files[0] || "multiple files",
|
|
@@ -1487,16 +719,17 @@ async function analyzeConsistency(options) {
|
|
|
1487
719
|
});
|
|
1488
720
|
}
|
|
1489
721
|
results.sort((fileResultA, fileResultB) => {
|
|
1490
|
-
const severityOrder = { critical: 0, major: 1, minor: 2, info: 3 };
|
|
1491
722
|
const maxSeverityA = Math.min(
|
|
1492
|
-
...fileResultA.issues.map(
|
|
1493
|
-
|
|
1494
|
-
|
|
723
|
+
...fileResultA.issues.map((i) => {
|
|
724
|
+
const val = getSeverityLevel(i.severity);
|
|
725
|
+
return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
|
|
726
|
+
})
|
|
1495
727
|
);
|
|
1496
728
|
const maxSeverityB = Math.min(
|
|
1497
|
-
...fileResultB.issues.map(
|
|
1498
|
-
|
|
1499
|
-
|
|
729
|
+
...fileResultB.issues.map((i) => {
|
|
730
|
+
const val = getSeverityLevel(i.severity);
|
|
731
|
+
return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
|
|
732
|
+
})
|
|
1500
733
|
);
|
|
1501
734
|
if (maxSeverityA !== maxSeverityB) {
|
|
1502
735
|
return maxSeverityA - maxSeverityB;
|
|
@@ -1522,16 +755,52 @@ async function analyzeConsistency(options) {
|
|
|
1522
755
|
recommendations
|
|
1523
756
|
};
|
|
1524
757
|
}
|
|
758
|
+
function getSeverityLevel(s) {
|
|
759
|
+
if (s === import_core5.Severity.Critical || s === "critical") return 4;
|
|
760
|
+
if (s === import_core5.Severity.Major || s === "major") return 3;
|
|
761
|
+
if (s === import_core5.Severity.Minor || s === "minor") return 2;
|
|
762
|
+
if (s === import_core5.Severity.Info || s === "info") return 1;
|
|
763
|
+
return 0;
|
|
764
|
+
}
|
|
765
|
+
function getSeverityEnum(s) {
|
|
766
|
+
const val = getSeverityLevel(s);
|
|
767
|
+
switch (val) {
|
|
768
|
+
case 4:
|
|
769
|
+
return import_core5.Severity.Critical;
|
|
770
|
+
case 3:
|
|
771
|
+
return import_core5.Severity.Major;
|
|
772
|
+
case 2:
|
|
773
|
+
return import_core5.Severity.Minor;
|
|
774
|
+
case 1:
|
|
775
|
+
return import_core5.Severity.Info;
|
|
776
|
+
default:
|
|
777
|
+
return import_core5.Severity.Info;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
1525
780
|
function shouldIncludeSeverity(severity, minSeverity) {
|
|
1526
|
-
|
|
1527
|
-
return severityLevels[severity] >= severityLevels[minSeverity];
|
|
781
|
+
return getSeverityLevel(severity) >= getSeverityLevel(minSeverity);
|
|
1528
782
|
}
|
|
1529
783
|
function calculateConsistencyScore(issues) {
|
|
1530
|
-
|
|
1531
|
-
const
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
784
|
+
let totalWeight = 0;
|
|
785
|
+
for (const issue of issues) {
|
|
786
|
+
const val = getSeverityLevel(issue.severity);
|
|
787
|
+
switch (val) {
|
|
788
|
+
case 4:
|
|
789
|
+
totalWeight += 10;
|
|
790
|
+
break;
|
|
791
|
+
case 3:
|
|
792
|
+
totalWeight += 5;
|
|
793
|
+
break;
|
|
794
|
+
case 2:
|
|
795
|
+
totalWeight += 2;
|
|
796
|
+
break;
|
|
797
|
+
case 1:
|
|
798
|
+
totalWeight += 1;
|
|
799
|
+
break;
|
|
800
|
+
default:
|
|
801
|
+
totalWeight += 1;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
1535
804
|
return Math.max(0, 1 - totalWeight / 100);
|
|
1536
805
|
}
|
|
1537
806
|
function generateRecommendations(namingIssues, patternIssues) {
|
|
@@ -1586,9 +855,9 @@ function generateRecommendations(namingIssues, patternIssues) {
|
|
|
1586
855
|
|
|
1587
856
|
// src/cli.ts
|
|
1588
857
|
var import_chalk = __toESM(require("chalk"));
|
|
1589
|
-
var
|
|
1590
|
-
var
|
|
1591
|
-
var
|
|
858
|
+
var import_fs3 = require("fs");
|
|
859
|
+
var import_path = require("path");
|
|
860
|
+
var import_core6 = require("@aiready/core");
|
|
1592
861
|
var program = new import_commander.Command();
|
|
1593
862
|
program.name("aiready-consistency").description(
|
|
1594
863
|
"Detect consistency patterns in naming, code structure, and architecture"
|
|
@@ -1627,7 +896,7 @@ EXAMPLES:
|
|
|
1627
896
|
).option("--output-file <path>", "Output file path (for json/markdown)").action(async (directory, options) => {
|
|
1628
897
|
console.log(import_chalk.default.blue("\u{1F50D} Analyzing consistency...\n"));
|
|
1629
898
|
const startTime = Date.now();
|
|
1630
|
-
const config = await (0,
|
|
899
|
+
const config = await (0, import_core6.loadConfig)(directory);
|
|
1631
900
|
const defaults = {
|
|
1632
901
|
checkNaming: true,
|
|
1633
902
|
checkPatterns: true,
|
|
@@ -1636,7 +905,7 @@ EXAMPLES:
|
|
|
1636
905
|
include: void 0,
|
|
1637
906
|
exclude: void 0
|
|
1638
907
|
};
|
|
1639
|
-
const mergedConfig = (0,
|
|
908
|
+
const mergedConfig = (0, import_core6.mergeConfigWithDefaults)(config, defaults);
|
|
1640
909
|
const finalOptions = {
|
|
1641
910
|
rootDir: directory,
|
|
1642
911
|
checkNaming: options.naming !== false && mergedConfig.checkNaming,
|
|
@@ -1650,29 +919,29 @@ EXAMPLES:
|
|
|
1650
919
|
const elapsedTime = ((Date.now() - startTime) / 1e3).toFixed(2);
|
|
1651
920
|
if (options.output === "json") {
|
|
1652
921
|
const output = JSON.stringify(report, null, 2);
|
|
1653
|
-
const outputPath = (0,
|
|
922
|
+
const outputPath = (0, import_core6.resolveOutputPath)(
|
|
1654
923
|
options.outputFile,
|
|
1655
924
|
`consistency-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
|
|
1656
925
|
directory
|
|
1657
926
|
);
|
|
1658
|
-
const dir = (0,
|
|
1659
|
-
if (!(0,
|
|
1660
|
-
(0,
|
|
927
|
+
const dir = (0, import_path.dirname)(outputPath);
|
|
928
|
+
if (!(0, import_fs3.existsSync)(dir)) {
|
|
929
|
+
(0, import_fs3.mkdirSync)(dir, { recursive: true });
|
|
1661
930
|
}
|
|
1662
|
-
(0,
|
|
931
|
+
(0, import_fs3.writeFileSync)(outputPath, output);
|
|
1663
932
|
console.log(import_chalk.default.green(`\u2713 Report saved to ${outputPath}`));
|
|
1664
933
|
} else if (options.output === "markdown") {
|
|
1665
934
|
const markdown = generateMarkdownReport(report, elapsedTime);
|
|
1666
|
-
const outputPath = (0,
|
|
935
|
+
const outputPath = (0, import_core6.resolveOutputPath)(
|
|
1667
936
|
options.outputFile,
|
|
1668
937
|
`consistency-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.md`,
|
|
1669
938
|
directory
|
|
1670
939
|
);
|
|
1671
|
-
const dir = (0,
|
|
1672
|
-
if (!(0,
|
|
1673
|
-
(0,
|
|
940
|
+
const dir = (0, import_path.dirname)(outputPath);
|
|
941
|
+
if (!(0, import_fs3.existsSync)(dir)) {
|
|
942
|
+
(0, import_fs3.mkdirSync)(dir, { recursive: true });
|
|
1674
943
|
}
|
|
1675
|
-
(0,
|
|
944
|
+
(0, import_fs3.writeFileSync)(outputPath, markdown);
|
|
1676
945
|
console.log(import_chalk.default.green(`\u2713 Report saved to ${outputPath}`));
|
|
1677
946
|
} else {
|
|
1678
947
|
displayConsoleReport(report, elapsedTime);
|