@aiready/consistency 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  analyzeConsistency: () => analyzeConsistency,
24
24
  analyzeNaming: () => analyzeNaming,
25
+ analyzeNamingAST: () => analyzeNamingAST,
25
26
  analyzePatterns: () => analyzePatterns,
26
27
  detectNamingConventions: () => detectNamingConventions
27
28
  });
@@ -30,11 +31,436 @@ module.exports = __toCommonJS(index_exports);
30
31
  // src/analyzer.ts
31
32
  var import_core3 = require("@aiready/core");
32
33
 
33
- // src/analyzers/naming.ts
34
+ // src/analyzers/naming-ast.ts
34
35
  var import_core = require("@aiready/core");
35
36
  var import_path = require("path");
37
+
38
+ // src/utils/ast-parser.ts
39
+ var import_typescript_estree = require("@typescript-eslint/typescript-estree");
40
+ var import_fs = require("fs");
41
+ function parseFile(filePath, content) {
42
+ try {
43
+ const code = content ?? (0, import_fs.readFileSync)(filePath, "utf-8");
44
+ const isTypeScript = filePath.match(/\.tsx?$/);
45
+ return (0, import_typescript_estree.parse)(code, {
46
+ jsx: filePath.match(/\.[jt]sx$/i) !== null,
47
+ loc: true,
48
+ range: true,
49
+ comment: false,
50
+ tokens: false,
51
+ // Relaxed parsing for JavaScript files
52
+ sourceType: "module",
53
+ ecmaVersion: "latest",
54
+ // Only use TypeScript parser features for .ts/.tsx files
55
+ filePath: isTypeScript ? filePath : void 0
56
+ });
57
+ } catch (error) {
58
+ console.warn(`Failed to parse ${filePath}:`, error instanceof Error ? error.message : error);
59
+ return null;
60
+ }
61
+ }
62
+ function traverseAST(node, visitor, parent = null) {
63
+ if (!node) return;
64
+ visitor.enter?.(node, parent);
65
+ for (const key of Object.keys(node)) {
66
+ const value = node[key];
67
+ if (Array.isArray(value)) {
68
+ for (const child of value) {
69
+ if (child && typeof child === "object" && "type" in child) {
70
+ traverseAST(child, visitor, node);
71
+ }
72
+ }
73
+ } else if (value && typeof value === "object" && "type" in value) {
74
+ traverseAST(value, visitor, node);
75
+ }
76
+ }
77
+ visitor.leave?.(node, parent);
78
+ }
79
+ function isLoopStatement(node) {
80
+ return [
81
+ "ForStatement",
82
+ "ForInStatement",
83
+ "ForOfStatement",
84
+ "WhileStatement",
85
+ "DoWhileStatement"
86
+ ].includes(node.type);
87
+ }
88
+ function getFunctionName(node) {
89
+ switch (node.type) {
90
+ case "FunctionDeclaration":
91
+ return node.id?.name ?? null;
92
+ case "FunctionExpression":
93
+ return node.id?.name ?? null;
94
+ case "ArrowFunctionExpression":
95
+ return null;
96
+ // Arrow functions don't have names directly
97
+ case "MethodDefinition":
98
+ if (node.key.type === "Identifier") {
99
+ return node.key.name;
100
+ }
101
+ return null;
102
+ default:
103
+ return null;
104
+ }
105
+ }
106
+ function getLineNumber(node) {
107
+ return node.loc?.start.line ?? 0;
108
+ }
109
+ function isCoverageContext(node, ancestors) {
110
+ const coveragePatterns = /coverage|summary|metrics|pct|percent|statements|branches|functions|lines/i;
111
+ if (node.type === "Identifier" && coveragePatterns.test(node.name)) {
112
+ return true;
113
+ }
114
+ for (const ancestor of ancestors.slice(-3)) {
115
+ if (ancestor.type === "MemberExpression") {
116
+ const memberExpr = ancestor;
117
+ if (memberExpr.object.type === "Identifier" && coveragePatterns.test(memberExpr.object.name)) {
118
+ return true;
119
+ }
120
+ }
121
+ if (ancestor.type === "ObjectPattern" || ancestor.type === "ObjectExpression") {
122
+ const parent = ancestors[ancestors.indexOf(ancestor) - 1];
123
+ if (parent?.type === "VariableDeclarator") {
124
+ const varDecl = parent;
125
+ if (varDecl.id.type === "Identifier" && coveragePatterns.test(varDecl.id.name)) {
126
+ return true;
127
+ }
128
+ }
129
+ }
130
+ }
131
+ return false;
132
+ }
133
+
134
+ // src/utils/scope-tracker.ts
135
+ var ScopeTracker = class {
136
+ constructor(rootNode) {
137
+ this.allScopes = [];
138
+ this.rootScope = {
139
+ type: "global",
140
+ node: rootNode,
141
+ parent: null,
142
+ children: [],
143
+ variables: /* @__PURE__ */ new Map()
144
+ };
145
+ this.currentScope = this.rootScope;
146
+ this.allScopes.push(this.rootScope);
147
+ }
148
+ /**
149
+ * Enter a new scope
150
+ */
151
+ enterScope(type, node) {
152
+ const newScope = {
153
+ type,
154
+ node,
155
+ parent: this.currentScope,
156
+ children: [],
157
+ variables: /* @__PURE__ */ new Map()
158
+ };
159
+ this.currentScope.children.push(newScope);
160
+ this.currentScope = newScope;
161
+ this.allScopes.push(newScope);
162
+ }
163
+ /**
164
+ * Exit current scope and return to parent
165
+ */
166
+ exitScope() {
167
+ if (this.currentScope.parent) {
168
+ this.currentScope = this.currentScope.parent;
169
+ }
170
+ }
171
+ /**
172
+ * Declare a variable in the current scope
173
+ */
174
+ declareVariable(name, node, line, options = {}) {
175
+ const varInfo = {
176
+ name,
177
+ node,
178
+ declarationLine: line,
179
+ references: [],
180
+ type: options.type,
181
+ isParameter: options.isParameter ?? false,
182
+ isDestructured: options.isDestructured ?? false,
183
+ isLoopVariable: options.isLoopVariable ?? false
184
+ };
185
+ this.currentScope.variables.set(name, varInfo);
186
+ }
187
+ /**
188
+ * Add a reference to a variable
189
+ */
190
+ addReference(name, node) {
191
+ const varInfo = this.findVariable(name);
192
+ if (varInfo) {
193
+ varInfo.references.push(node);
194
+ }
195
+ }
196
+ /**
197
+ * Find a variable in current or parent scopes
198
+ */
199
+ findVariable(name) {
200
+ let scope = this.currentScope;
201
+ while (scope) {
202
+ const varInfo = scope.variables.get(name);
203
+ if (varInfo) {
204
+ return varInfo;
205
+ }
206
+ scope = scope.parent;
207
+ }
208
+ return null;
209
+ }
210
+ /**
211
+ * Get all variables in current scope (not including parent scopes)
212
+ */
213
+ getCurrentScopeVariables() {
214
+ return Array.from(this.currentScope.variables.values());
215
+ }
216
+ /**
217
+ * Get all variables across all scopes
218
+ */
219
+ getAllVariables() {
220
+ const allVars = [];
221
+ for (const scope of this.allScopes) {
222
+ allVars.push(...Array.from(scope.variables.values()));
223
+ }
224
+ return allVars;
225
+ }
226
+ /**
227
+ * Calculate actual usage count (references minus declaration)
228
+ */
229
+ getUsageCount(varInfo) {
230
+ return varInfo.references.length;
231
+ }
232
+ /**
233
+ * Check if a variable is short-lived (used within N lines)
234
+ */
235
+ isShortLived(varInfo, maxLines = 5) {
236
+ if (varInfo.references.length === 0) {
237
+ return false;
238
+ }
239
+ const declarationLine = varInfo.declarationLine;
240
+ const maxUsageLine = Math.max(
241
+ ...varInfo.references.map((ref) => ref.loc?.start.line ?? declarationLine)
242
+ );
243
+ return maxUsageLine - declarationLine <= maxLines;
244
+ }
245
+ /**
246
+ * Check if a variable is used in a limited scope (e.g., only in one callback)
247
+ */
248
+ isLocallyScoped(varInfo) {
249
+ if (varInfo.references.length === 0) return false;
250
+ const lines = varInfo.references.map((ref) => ref.loc?.start.line ?? 0);
251
+ const minLine = Math.min(...lines);
252
+ const maxLine = Math.max(...lines);
253
+ return maxLine - minLine <= 3;
254
+ }
255
+ /**
256
+ * Get current scope type
257
+ */
258
+ getCurrentScopeType() {
259
+ return this.currentScope.type;
260
+ }
261
+ /**
262
+ * Check if currently in a loop scope
263
+ */
264
+ isInLoop() {
265
+ let scope = this.currentScope;
266
+ while (scope) {
267
+ if (scope.type === "loop") {
268
+ return true;
269
+ }
270
+ scope = scope.parent;
271
+ }
272
+ return false;
273
+ }
274
+ /**
275
+ * Check if currently in a function scope
276
+ */
277
+ isInFunction() {
278
+ let scope = this.currentScope;
279
+ while (scope) {
280
+ if (scope.type === "function") {
281
+ return true;
282
+ }
283
+ scope = scope.parent;
284
+ }
285
+ return false;
286
+ }
287
+ /**
288
+ * Get the root scope
289
+ */
290
+ getRootScope() {
291
+ return this.rootScope;
292
+ }
293
+ };
294
+
295
+ // src/utils/context-detector.ts
296
+ function detectFileType(filePath, ast) {
297
+ const path = filePath.toLowerCase();
298
+ if (path.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/) || path.includes("__tests__")) {
299
+ return "test";
300
+ }
301
+ if (path.endsWith(".d.ts") || path.includes("types")) {
302
+ return "types";
303
+ }
304
+ if (path.match(/config|\.config\.|rc\.|setup/) || path.includes("configuration")) {
305
+ return "config";
306
+ }
307
+ return "production";
308
+ }
309
+ function detectCodeLayer(ast) {
310
+ let hasAPIIndicators = 0;
311
+ let hasBusinessIndicators = 0;
312
+ let hasDataIndicators = 0;
313
+ let hasUtilityIndicators = 0;
314
+ traverseAST(ast, {
315
+ enter: (node) => {
316
+ if (node.type === "ImportDeclaration") {
317
+ const source = node.source.value;
318
+ if (source.match(/express|fastify|koa|@nestjs|axios|fetch|http/i)) {
319
+ hasAPIIndicators++;
320
+ }
321
+ if (source.match(/database|prisma|typeorm|sequelize|mongoose|pg|mysql/i)) {
322
+ hasDataIndicators++;
323
+ }
324
+ }
325
+ if (node.type === "FunctionDeclaration" && node.id) {
326
+ const name = node.id.name;
327
+ if (name.match(/^(get|post|put|delete|patch|handle|api|route|controller)/i)) {
328
+ hasAPIIndicators++;
329
+ }
330
+ if (name.match(/^(calculate|process|validate|transform|compute|analyze)/i)) {
331
+ hasBusinessIndicators++;
332
+ }
333
+ if (name.match(/^(find|create|update|delete|save|fetch|query|insert)/i)) {
334
+ hasDataIndicators++;
335
+ }
336
+ if (name.match(/^(format|parse|convert|normalize|sanitize|encode|decode)/i)) {
337
+ hasUtilityIndicators++;
338
+ }
339
+ }
340
+ if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") {
341
+ if (node.type === "ExportNamedDeclaration" && node.declaration) {
342
+ if (node.declaration.type === "FunctionDeclaration" && node.declaration.id) {
343
+ const name = node.declaration.id.name;
344
+ if (name.match(/handler|route|api|controller/i)) {
345
+ hasAPIIndicators += 2;
346
+ }
347
+ }
348
+ }
349
+ }
350
+ }
351
+ });
352
+ const scores = {
353
+ api: hasAPIIndicators,
354
+ business: hasBusinessIndicators,
355
+ data: hasDataIndicators,
356
+ utility: hasUtilityIndicators
357
+ };
358
+ const maxScore = Math.max(...Object.values(scores));
359
+ if (maxScore === 0) {
360
+ return "unknown";
361
+ }
362
+ if (scores.api === maxScore) return "api";
363
+ if (scores.data === maxScore) return "data";
364
+ if (scores.business === maxScore) return "business";
365
+ if (scores.utility === maxScore) return "utility";
366
+ return "unknown";
367
+ }
368
+ function calculateComplexity(node) {
369
+ let complexity = 1;
370
+ traverseAST(node, {
371
+ enter: (childNode) => {
372
+ switch (childNode.type) {
373
+ case "IfStatement":
374
+ case "ConditionalExpression":
375
+ // ternary
376
+ case "SwitchCase":
377
+ case "ForStatement":
378
+ case "ForInStatement":
379
+ case "ForOfStatement":
380
+ case "WhileStatement":
381
+ case "DoWhileStatement":
382
+ case "CatchClause":
383
+ complexity++;
384
+ break;
385
+ case "LogicalExpression":
386
+ if (childNode.operator === "&&" || childNode.operator === "||") {
387
+ complexity++;
388
+ }
389
+ break;
390
+ }
391
+ }
392
+ });
393
+ return complexity;
394
+ }
395
+ function buildCodeContext(filePath, ast) {
396
+ const fileType = detectFileType(filePath, ast);
397
+ const codeLayer = detectCodeLayer(ast);
398
+ let totalComplexity = 0;
399
+ let functionCount = 0;
400
+ traverseAST(ast, {
401
+ enter: (node) => {
402
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
403
+ totalComplexity += calculateComplexity(node);
404
+ functionCount++;
405
+ }
406
+ }
407
+ });
408
+ const avgComplexity = functionCount > 0 ? totalComplexity / functionCount : 1;
409
+ return {
410
+ fileType,
411
+ codeLayer,
412
+ complexity: Math.round(avgComplexity),
413
+ isTestFile: fileType === "test",
414
+ isTypeDefinition: fileType === "types"
415
+ };
416
+ }
417
+ function adjustSeverity(baseSeverity, context, issueType) {
418
+ if (context.isTestFile) {
419
+ if (baseSeverity === "minor") return "info";
420
+ if (baseSeverity === "major") return "minor";
421
+ }
422
+ if (context.isTypeDefinition) {
423
+ if (baseSeverity === "minor") return "info";
424
+ }
425
+ if (context.codeLayer === "api") {
426
+ if (baseSeverity === "info" && issueType === "unclear") return "minor";
427
+ if (baseSeverity === "minor" && issueType === "unclear") return "major";
428
+ }
429
+ if (context.complexity > 10) {
430
+ if (baseSeverity === "info") return "minor";
431
+ }
432
+ if (context.codeLayer === "utility") {
433
+ if (baseSeverity === "minor" && issueType === "abbreviation") return "info";
434
+ }
435
+ return baseSeverity;
436
+ }
437
+ function isAcceptableInContext(name, context, options) {
438
+ if (options.isLoopVariable && ["i", "j", "k", "l", "n", "m"].includes(name)) {
439
+ return true;
440
+ }
441
+ if (context.isTestFile) {
442
+ if (["a", "b", "c", "x", "y", "z"].includes(name) && options.isParameter) {
443
+ return true;
444
+ }
445
+ }
446
+ if (context.codeLayer === "utility" && ["x", "y", "z"].includes(name)) {
447
+ return true;
448
+ }
449
+ if (options.isDestructured) {
450
+ if (["s", "b", "f", "l"].includes(name)) {
451
+ return true;
452
+ }
453
+ }
454
+ if (options.isParameter && (options.complexity ?? context.complexity) < 3) {
455
+ if (name.length >= 2) {
456
+ return true;
457
+ }
458
+ }
459
+ return false;
460
+ }
461
+
462
+ // src/analyzers/naming-ast.ts
36
463
  var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
37
- // Full English words (1-3 letters)
38
464
  "day",
39
465
  "key",
40
466
  "net",
@@ -90,14 +516,12 @@ var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
90
516
  "tmp",
91
517
  "ext",
92
518
  "sep",
93
- // Prepositions and conjunctions
94
519
  "and",
95
520
  "from",
96
521
  "how",
97
522
  "pad",
98
523
  "bar",
99
524
  "non",
100
- // Additional full words commonly flagged
101
525
  "tax",
102
526
  "cat",
103
527
  "dog",
@@ -177,18 +601,15 @@ var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
177
601
  "won"
178
602
  ]);
179
603
  var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
180
- // Standard identifiers
181
604
  "id",
182
605
  "uid",
183
606
  "gid",
184
607
  "pid",
185
- // Loop counters and iterators
186
608
  "i",
187
609
  "j",
188
610
  "k",
189
611
  "n",
190
612
  "m",
191
- // Web/Network
192
613
  "url",
193
614
  "uri",
194
615
  "api",
@@ -208,7 +629,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
208
629
  "cors",
209
630
  "ws",
210
631
  "wss",
211
- // Data formats
212
632
  "json",
213
633
  "xml",
214
634
  "yaml",
@@ -217,7 +637,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
217
637
  "css",
218
638
  "svg",
219
639
  "pdf",
220
- // File types & extensions
221
640
  "img",
222
641
  "txt",
223
642
  "doc",
@@ -229,7 +648,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
229
648
  "jpg",
230
649
  "png",
231
650
  "gif",
232
- // Databases
233
651
  "db",
234
652
  "sql",
235
653
  "orm",
@@ -238,7 +656,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
238
656
  "ddb",
239
657
  "rds",
240
658
  "nosql",
241
- // File system
242
659
  "fs",
243
660
  "dir",
244
661
  "tmp",
@@ -247,7 +664,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
247
664
  "bin",
248
665
  "lib",
249
666
  "pkg",
250
- // Operating system
251
667
  "os",
252
668
  "env",
253
669
  "arg",
@@ -256,20 +672,17 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
256
672
  "exe",
257
673
  "cwd",
258
674
  "pwd",
259
- // UI/UX
260
675
  "ui",
261
676
  "ux",
262
677
  "gui",
263
678
  "dom",
264
679
  "ref",
265
- // Request/Response
266
680
  "req",
267
681
  "res",
268
682
  "ctx",
269
683
  "err",
270
684
  "msg",
271
685
  "auth",
272
- // Mathematics/Computing
273
686
  "max",
274
687
  "min",
275
688
  "avg",
@@ -287,7 +700,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
287
700
  "int",
288
701
  "num",
289
702
  "idx",
290
- // Time
291
703
  "now",
292
704
  "utc",
293
705
  "tz",
@@ -297,7 +709,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
297
709
  "min",
298
710
  "yr",
299
711
  "mo",
300
- // Common patterns
301
712
  "app",
302
713
  "cfg",
303
714
  "config",
@@ -316,7 +727,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
316
727
  "post",
317
728
  "sub",
318
729
  "pub",
319
- // Programming/Framework specific
320
730
  "ts",
321
731
  "js",
322
732
  "jsx",
@@ -330,7 +740,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
330
740
  "mod",
331
741
  "opts",
332
742
  "dev",
333
- // Cloud/Infrastructure
334
743
  "s3",
335
744
  "ec2",
336
745
  "sqs",
@@ -354,7 +763,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
354
763
  "cf",
355
764
  "cfn",
356
765
  "ga",
357
- // Metrics/Performance
358
766
  "fcp",
359
767
  "lcp",
360
768
  "cls",
@@ -366,14 +774,12 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
366
774
  "rps",
367
775
  "tps",
368
776
  "wpm",
369
- // Testing & i18n
370
777
  "po",
371
778
  "e2e",
372
779
  "a11y",
373
780
  "i18n",
374
781
  "l10n",
375
782
  "spy",
376
- // Domain-specific abbreviations (context-aware)
377
783
  "sk",
378
784
  "fy",
379
785
  "faq",
@@ -384,7 +790,6 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
384
790
  "kpi",
385
791
  "ttl",
386
792
  "pct",
387
- // Technical abbreviations
388
793
  "mac",
389
794
  "hex",
390
795
  "esm",
@@ -392,19 +797,27 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
392
797
  "rec",
393
798
  "loc",
394
799
  "dup",
395
- // Boolean helpers (these are intentional short names)
396
800
  "is",
397
801
  "has",
398
802
  "can",
399
803
  "did",
400
804
  "was",
401
805
  "are",
402
- // Date/Time context (when in date contexts)
403
806
  "d",
404
807
  "t",
405
- "dt"
808
+ "dt",
809
+ "s",
810
+ "b",
811
+ "f",
812
+ "l",
813
+ // Coverage metrics
814
+ "vid",
815
+ "pic",
816
+ "img",
817
+ "doc",
818
+ "msg"
406
819
  ]);
407
- async function analyzeNaming(files) {
820
+ async function analyzeNamingAST(files) {
408
821
  const issues = [];
409
822
  const rootDir = files.length > 0 ? (0, import_path.dirname)(files[0]) : process.cwd();
410
823
  const config = (0, import_core.loadConfig)(rootDir);
@@ -412,184 +825,221 @@ async function analyzeNaming(files) {
412
825
  const customAbbreviations = new Set(consistencyConfig?.acceptedAbbreviations || []);
413
826
  const customShortWords = new Set(consistencyConfig?.shortWords || []);
414
827
  const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
828
+ const allAbbreviations = /* @__PURE__ */ new Set([...ACCEPTABLE_ABBREVIATIONS, ...customAbbreviations]);
829
+ const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
415
830
  for (const file of files) {
416
- const content = await (0, import_core.readFileContent)(file);
417
- const fileIssues = analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks);
418
- issues.push(...fileIssues);
831
+ try {
832
+ const ast = parseFile(file);
833
+ if (!ast) continue;
834
+ const fileIssues = analyzeFileNamingAST(
835
+ file,
836
+ ast,
837
+ allAbbreviations,
838
+ allShortWords,
839
+ disabledChecks
840
+ );
841
+ issues.push(...fileIssues);
842
+ } catch (error) {
843
+ console.warn(`Skipping ${file} due to parse error:`, error);
844
+ }
419
845
  }
420
846
  return issues;
421
847
  }
422
- function analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks) {
848
+ function analyzeFileNamingAST(file, ast, allAbbreviations, allShortWords, disabledChecks) {
423
849
  const issues = [];
424
- const isTestFile = file.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/);
425
- const lines = content.split("\n");
426
- const allAbbreviations = /* @__PURE__ */ new Set([...ACCEPTABLE_ABBREVIATIONS, ...customAbbreviations]);
427
- const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
428
- const getContextWindow = (index, windowSize = 3) => {
429
- const start = Math.max(0, index - windowSize);
430
- const end = Math.min(lines.length, index + windowSize + 1);
431
- return lines.slice(start, end).join("\n");
432
- };
433
- const isShortLivedVariable = (varName, declarationIndex) => {
434
- const searchRange = 5;
435
- const endIndex = Math.min(lines.length, declarationIndex + searchRange + 1);
436
- let usageCount = 0;
437
- for (let i = declarationIndex; i < endIndex; i++) {
438
- const regex = new RegExp(`\\b${varName}\\b`, "g");
439
- const matches = lines[i].match(regex);
440
- if (matches) {
441
- usageCount += matches.length;
442
- }
443
- }
444
- return usageCount >= 2 && usageCount <= 3;
445
- };
446
- lines.forEach((line, index) => {
447
- const lineNumber = index + 1;
448
- const contextWindow = getContextWindow(index);
449
- if (!disabledChecks.has("single-letter")) {
450
- const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
451
- for (const match of singleLetterMatches) {
452
- const letter = match[1].toLowerCase();
453
- const isInLoopContext = line.includes("for") || /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) || line.includes("=>") || // Arrow function
454
- /\w+\s*=>\s*/.test(line);
455
- const isI18nContext = line.includes("useTranslation") || line.includes("i18n.t") || /\bt\s*\(['"]/.test(line);
456
- const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
457
- /[a-z]\s*=>/.test(line) || // s => on same line
458
- // Multi-line arrow function detection: look for pattern in context window
459
- new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
460
- new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && /=>/.test(contextWindow);
461
- const isShortLived = isShortLivedVariable(letter, index);
462
- if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
463
- if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
464
- continue;
850
+ const scopeTracker = new ScopeTracker(ast);
851
+ const context = buildCodeContext(file, ast);
852
+ const ancestors = [];
853
+ traverseAST(ast, {
854
+ enter: (node, parent) => {
855
+ ancestors.push(node);
856
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
857
+ scopeTracker.enterScope("function", node);
858
+ if ("params" in node) {
859
+ for (const param of node.params) {
860
+ if (param.type === "Identifier") {
861
+ scopeTracker.declareVariable(param.name, param, getLineNumber(param), {
862
+ isParameter: true
863
+ });
864
+ } else if (param.type === "ObjectPattern" || param.type === "ArrayPattern") {
865
+ extractIdentifiersFromPattern(param, scopeTracker, true);
866
+ }
465
867
  }
466
- issues.push({
467
- file,
468
- line: lineNumber,
469
- type: "poor-naming",
470
- identifier: match[1],
471
- severity: "minor",
472
- suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
473
- });
474
868
  }
869
+ } else if (node.type === "BlockStatement") {
870
+ scopeTracker.enterScope("block", node);
871
+ } else if (isLoopStatement(node)) {
872
+ scopeTracker.enterScope("loop", node);
873
+ } else if (node.type === "ClassDeclaration") {
874
+ scopeTracker.enterScope("class", node);
475
875
  }
476
- }
477
- if (!disabledChecks.has("abbreviation")) {
478
- const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
479
- for (const match of abbreviationMatches) {
480
- const abbrev = match[1].toLowerCase();
481
- if (allShortWords.has(abbrev)) {
482
- continue;
483
- }
484
- if (allAbbreviations.has(abbrev)) {
485
- continue;
486
- }
487
- const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
488
- new RegExp(`\\b${abbrev}\\s*=>`).test(line) || // s => on same line
489
- // Multi-line arrow function: check context window
490
- new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
491
- new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && new RegExp(`^\\s*${abbrev}\\s*=>`).test(line);
492
- if (isArrowFunctionParam) {
493
- continue;
876
+ if (node.type === "VariableDeclarator") {
877
+ if (node.id.type === "Identifier") {
878
+ const isInCoverage = isCoverageContext(node, ancestors);
879
+ scopeTracker.declareVariable(
880
+ node.id.name,
881
+ node.id,
882
+ getLineNumber(node.id),
883
+ {
884
+ type: "typeAnnotation" in node.id ? node.id.typeAnnotation : null,
885
+ isDestructured: false,
886
+ isLoopVariable: scopeTracker.getCurrentScopeType() === "loop"
887
+ }
888
+ );
889
+ } else if (node.id.type === "ObjectPattern" || node.id.type === "ArrayPattern") {
890
+ extractIdentifiersFromPattern(node.id, scopeTracker, false, ancestors);
494
891
  }
495
- if (abbrev.length <= 2) {
496
- const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
497
- if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
498
- continue;
499
- }
500
- const isUserContext = /user|auth|account/i.test(line);
501
- if (isUserContext && abbrev === "u") {
502
- continue;
892
+ }
893
+ if (node.type === "Identifier" && parent) {
894
+ if (parent.type !== "VariableDeclarator" || parent.id !== node) {
895
+ if (parent.type !== "FunctionDeclaration" || parent.id !== node) {
896
+ scopeTracker.addReference(node.name, node);
503
897
  }
504
898
  }
505
- issues.push({
506
- file,
507
- line: lineNumber,
508
- type: "abbreviation",
509
- identifier: match[1],
510
- severity: "info",
511
- suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
512
- });
899
+ }
900
+ },
901
+ leave: (node) => {
902
+ ancestors.pop();
903
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "BlockStatement" || isLoopStatement(node) || node.type === "ClassDeclaration") {
904
+ scopeTracker.exitScope();
513
905
  }
514
906
  }
515
- if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
516
- const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
517
- const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
518
- if (snakeCaseVars) {
907
+ });
908
+ const allVariables = scopeTracker.getAllVariables();
909
+ for (const varInfo of allVariables) {
910
+ const name = varInfo.name;
911
+ const line = varInfo.declarationLine;
912
+ if (disabledChecks.has("single-letter") && name.length === 1) continue;
913
+ if (disabledChecks.has("abbreviation") && name.length <= 3) continue;
914
+ const isInCoverage = ["s", "b", "f", "l"].includes(name) && varInfo.isDestructured;
915
+ if (isInCoverage) continue;
916
+ const functionComplexity = varInfo.node.type === "Identifier" && "parent" in varInfo.node ? calculateComplexity(varInfo.node) : context.complexity;
917
+ if (isAcceptableInContext(name, context, {
918
+ isLoopVariable: varInfo.isLoopVariable || allAbbreviations.has(name),
919
+ isParameter: varInfo.isParameter,
920
+ isDestructured: varInfo.isDestructured,
921
+ complexity: functionComplexity
922
+ })) {
923
+ continue;
924
+ }
925
+ if (name.length === 1 && !allAbbreviations.has(name) && !allShortWords.has(name)) {
926
+ const isShortLived = scopeTracker.isShortLived(varInfo, 5);
927
+ if (!isShortLived) {
519
928
  issues.push({
520
929
  file,
521
- line: lineNumber,
522
- type: "convention-mix",
523
- identifier: snakeCaseVars[1],
524
- severity: "minor",
525
- suggestion: `Use camelCase '${snakeCaseToCamelCase(snakeCaseVars[1])}' instead of snake_case in TypeScript/JavaScript`
930
+ line,
931
+ type: "poor-naming",
932
+ identifier: name,
933
+ severity: adjustSeverity("minor", context, "poor-naming"),
934
+ suggestion: `Use descriptive variable name instead of single letter '${name}'`
526
935
  });
527
936
  }
937
+ continue;
528
938
  }
529
- if (!disabledChecks.has("unclear")) {
530
- const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
531
- for (const match of booleanMatches) {
532
- const name = match[1];
533
- if (!name.match(/^(is|has|should|can|will|did)/i)) {
939
+ if (name.length >= 2 && name.length <= 3) {
940
+ if (!allShortWords.has(name.toLowerCase()) && !allAbbreviations.has(name.toLowerCase())) {
941
+ const isShortLived = scopeTracker.isShortLived(varInfo, 5);
942
+ if (!isShortLived) {
534
943
  issues.push({
535
944
  file,
536
- line: lineNumber,
537
- type: "unclear",
945
+ line,
946
+ type: "abbreviation",
538
947
  identifier: name,
539
- severity: "info",
540
- suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
948
+ severity: adjustSeverity("info", context, "abbreviation"),
949
+ suggestion: `Consider using full word instead of abbreviation '${name}'`
541
950
  });
542
951
  }
543
952
  }
953
+ continue;
544
954
  }
545
- if (!disabledChecks.has("unclear")) {
546
- const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
547
- for (const match of functionMatches) {
548
- const name = match[1];
549
- const isKeyword = ["for", "if", "else", "while", "do", "switch", "case", "break", "continue", "return", "throw", "try", "catch", "finally", "with", "yield", "await"].includes(name);
550
- if (isKeyword) {
551
- continue;
552
- }
553
- const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(name);
554
- if (isEntryPoint) {
555
- continue;
556
- }
557
- const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/);
558
- const isEventHandler = name.match(/^on[A-Z]/);
559
- const isDescriptiveLong = name.length > 15;
560
- const isReactHook = name.match(/^use[A-Z]/);
561
- const isDescriptivePattern = name.match(/^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/) || name.match(/\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props|Data|Info|Details|State|Status|Response|Result)$/);
562
- const isHelperPattern = name.match(/^(to|from|with|without|for|as|into)\w+/) || // toMetadata, withLogger, forPath
563
- name.match(/^\w+(To|From|With|Without|For|As|Into)\w*$/);
564
- const isUtilityName = ["cn", "proxy", "sitemap", "robots", "gtag"].includes(name);
565
- const capitalCount = (name.match(/[A-Z]/g) || []).length;
566
- const isCompoundWord = capitalCount >= 3;
567
- const hasActionVerb = name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce|make|do|run|start|stop|build|parse|format|render|calculate|compute|generate|transform|convert|normalize|sanitize|encode|decode|compress|extract|merge|split|join|sort|compare|test|verify|ensure|apply|execute|invoke|call|emit|dispatch|trigger|listen|subscribe|unsubscribe|add|remove|clear|reset|toggle|enable|disable|open|close|connect|disconnect|send|receive|read|write|import|export|register|unregister|mount|unmount|track|store|persist|upsert|derive|classify|combine|discover|activate|require|assert|expect|mask|escape|sign|put|list|complete|page|safe|mock|pick|pluralize|text)/);
568
- if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord && !isHelperPattern && !isUtilityName && !isReactHook) {
569
- issues.push({
570
- file,
571
- line: lineNumber,
572
- type: "unclear",
573
- identifier: name,
574
- severity: "info",
575
- suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
576
- });
577
- }
955
+ if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
956
+ if (name.includes("_") && !name.startsWith("_") && name.toLowerCase() === name) {
957
+ const camelCase = name.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
958
+ issues.push({
959
+ file,
960
+ line,
961
+ type: "convention-mix",
962
+ identifier: name,
963
+ severity: adjustSeverity("minor", context, "convention-mix"),
964
+ suggestion: `Use camelCase '${camelCase}' instead of snake_case in TypeScript/JavaScript`
965
+ });
578
966
  }
579
967
  }
580
- });
968
+ }
969
+ if (!disabledChecks.has("unclear")) {
970
+ traverseAST(ast, {
971
+ enter: (node) => {
972
+ if (node.type === "FunctionDeclaration" || node.type === "MethodDefinition") {
973
+ const name = getFunctionName(node);
974
+ if (!name) return;
975
+ const line = getLineNumber(node);
976
+ if (["main", "init", "setup", "bootstrap"].includes(name)) return;
977
+ const hasActionVerb = name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce|make|do|run|start|stop|build|parse|format|render|calculate|compute|generate|transform|convert|normalize|sanitize|encode|decode|compress|extract|merge|split|join|sort|compare|test|verify|ensure|apply|execute|invoke|call|emit|dispatch|trigger|listen|subscribe|unsubscribe|add|remove|clear|reset|toggle|enable|disable|open|close|connect|disconnect|send|receive|read|write|import|export|register|unregister|mount|unmount|track|store|persist|upsert|derive|classify|combine|discover|activate|require|assert|expect|mask|escape|sign|put|list|complete|page|safe|mock|pick|pluralize|text)/);
978
+ const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/);
979
+ const isEventHandler = name.match(/^on[A-Z]/);
980
+ const isDescriptiveLong = name.length > 15;
981
+ const isReactHook = name.match(/^use[A-Z]/);
982
+ const isHelperPattern = name.match(/^(to|from|with|without|for|as|into)\w+/);
983
+ const isUtilityName = ["cn", "proxy", "sitemap", "robots", "gtag"].includes(name);
984
+ const isDescriptivePattern = name.match(/^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/) || name.match(/\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props|Data|Info|Details|State|Status|Response|Result)$/);
985
+ const capitalCount = (name.match(/[A-Z]/g) || []).length;
986
+ const isCompoundWord = capitalCount >= 3;
987
+ if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isReactHook && !isHelperPattern && !isUtilityName && !isDescriptivePattern && !isCompoundWord) {
988
+ issues.push({
989
+ file,
990
+ line,
991
+ type: "unclear",
992
+ identifier: name,
993
+ severity: adjustSeverity("info", context, "unclear"),
994
+ suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
995
+ });
996
+ }
997
+ }
998
+ }
999
+ });
1000
+ }
581
1001
  return issues;
582
1002
  }
583
- function snakeCaseToCamelCase(str) {
584
- return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
585
- }
586
- function detectNamingConventions(files, allIssues) {
587
- const camelCaseCount = allIssues.filter((i) => i.type === "convention-mix").length;
588
- const totalChecks = files.length * 10;
589
- if (camelCaseCount / totalChecks > 0.3) {
590
- return { dominantConvention: "mixed", conventionScore: 0.5 };
1003
+ function extractIdentifiersFromPattern(pattern, scopeTracker, isParameter, ancestors) {
1004
+ if (pattern.type === "ObjectPattern") {
1005
+ for (const prop of pattern.properties) {
1006
+ if (prop.type === "Property" && prop.value.type === "Identifier") {
1007
+ scopeTracker.declareVariable(
1008
+ prop.value.name,
1009
+ prop.value,
1010
+ getLineNumber(prop.value),
1011
+ {
1012
+ isParameter,
1013
+ isDestructured: true
1014
+ }
1015
+ );
1016
+ } else if (prop.type === "RestElement" && prop.argument.type === "Identifier") {
1017
+ scopeTracker.declareVariable(
1018
+ prop.argument.name,
1019
+ prop.argument,
1020
+ getLineNumber(prop.argument),
1021
+ {
1022
+ isParameter,
1023
+ isDestructured: true
1024
+ }
1025
+ );
1026
+ }
1027
+ }
1028
+ } else if (pattern.type === "ArrayPattern") {
1029
+ for (const element of pattern.elements) {
1030
+ if (element && element.type === "Identifier") {
1031
+ scopeTracker.declareVariable(
1032
+ element.name,
1033
+ element,
1034
+ getLineNumber(element),
1035
+ {
1036
+ isParameter,
1037
+ isDestructured: true
1038
+ }
1039
+ );
1040
+ }
1041
+ }
591
1042
  }
592
- return { dominantConvention: "camelCase", conventionScore: 0.9 };
593
1043
  }
594
1044
 
595
1045
  // src/analyzers/patterns.ts
@@ -748,7 +1198,7 @@ async function analyzeConsistency(options) {
748
1198
  ...scanOptions
749
1199
  } = options;
750
1200
  const filePaths = await (0, import_core3.scanFiles)(scanOptions);
751
- const namingIssues = checkNaming ? await analyzeNaming(filePaths) : [];
1201
+ const namingIssues = checkNaming ? await analyzeNamingAST(filePaths) : [];
752
1202
  const patternIssues = checkPatterns ? await analyzePatterns(filePaths) : [];
753
1203
  const results = [];
754
1204
  const fileIssuesMap = /* @__PURE__ */ new Map();
@@ -807,7 +1257,6 @@ async function analyzeConsistency(options) {
807
1257
  });
808
1258
  }
809
1259
  const recommendations = generateRecommendations(namingIssues, patternIssues);
810
- const conventionAnalysis = detectNamingConventions(filePaths, namingIssues);
811
1260
  return {
812
1261
  summary: {
813
1262
  totalIssues: namingIssues.length + patternIssues.length,
@@ -870,10 +1319,588 @@ function generateRecommendations(namingIssues, patternIssues) {
870
1319
  }
871
1320
  return recommendations;
872
1321
  }
1322
+
1323
+ // src/analyzers/naming.ts
1324
+ var import_core4 = require("@aiready/core");
1325
+ var import_path2 = require("path");
1326
+ var COMMON_SHORT_WORDS2 = /* @__PURE__ */ new Set([
1327
+ // Full English words (1-3 letters)
1328
+ "day",
1329
+ "key",
1330
+ "net",
1331
+ "to",
1332
+ "go",
1333
+ "for",
1334
+ "not",
1335
+ "new",
1336
+ "old",
1337
+ "top",
1338
+ "end",
1339
+ "run",
1340
+ "try",
1341
+ "use",
1342
+ "get",
1343
+ "set",
1344
+ "add",
1345
+ "put",
1346
+ "map",
1347
+ "log",
1348
+ "row",
1349
+ "col",
1350
+ "tab",
1351
+ "box",
1352
+ "div",
1353
+ "nav",
1354
+ "tag",
1355
+ "any",
1356
+ "all",
1357
+ "one",
1358
+ "two",
1359
+ "out",
1360
+ "off",
1361
+ "on",
1362
+ "yes",
1363
+ "no",
1364
+ "now",
1365
+ "max",
1366
+ "min",
1367
+ "sum",
1368
+ "avg",
1369
+ "ref",
1370
+ "src",
1371
+ "dst",
1372
+ "raw",
1373
+ "def",
1374
+ "sub",
1375
+ "pub",
1376
+ "pre",
1377
+ "mid",
1378
+ "alt",
1379
+ "opt",
1380
+ "tmp",
1381
+ "ext",
1382
+ "sep",
1383
+ // Prepositions and conjunctions
1384
+ "and",
1385
+ "from",
1386
+ "how",
1387
+ "pad",
1388
+ "bar",
1389
+ "non",
1390
+ // Additional full words commonly flagged
1391
+ "tax",
1392
+ "cat",
1393
+ "dog",
1394
+ "car",
1395
+ "bus",
1396
+ "web",
1397
+ "app",
1398
+ "war",
1399
+ "law",
1400
+ "pay",
1401
+ "buy",
1402
+ "win",
1403
+ "cut",
1404
+ "hit",
1405
+ "hot",
1406
+ "pop",
1407
+ "job",
1408
+ "age",
1409
+ "act",
1410
+ "let",
1411
+ "lot",
1412
+ "bad",
1413
+ "big",
1414
+ "far",
1415
+ "few",
1416
+ "own",
1417
+ "per",
1418
+ "red",
1419
+ "low",
1420
+ "see",
1421
+ "six",
1422
+ "ten",
1423
+ "way",
1424
+ "who",
1425
+ "why",
1426
+ "yet",
1427
+ "via",
1428
+ "due",
1429
+ "fee",
1430
+ "fun",
1431
+ "gas",
1432
+ "gay",
1433
+ "god",
1434
+ "gun",
1435
+ "guy",
1436
+ "ice",
1437
+ "ill",
1438
+ "kid",
1439
+ "mad",
1440
+ "man",
1441
+ "mix",
1442
+ "mom",
1443
+ "mrs",
1444
+ "nor",
1445
+ "odd",
1446
+ "oil",
1447
+ "pan",
1448
+ "pet",
1449
+ "pit",
1450
+ "pot",
1451
+ "pow",
1452
+ "pro",
1453
+ "raw",
1454
+ "rep",
1455
+ "rid",
1456
+ "sad",
1457
+ "sea",
1458
+ "sit",
1459
+ "sky",
1460
+ "son",
1461
+ "tea",
1462
+ "tie",
1463
+ "tip",
1464
+ "van",
1465
+ "war",
1466
+ "win",
1467
+ "won"
1468
+ ]);
1469
+ var ACCEPTABLE_ABBREVIATIONS2 = /* @__PURE__ */ new Set([
1470
+ // Standard identifiers
1471
+ "id",
1472
+ "uid",
1473
+ "gid",
1474
+ "pid",
1475
+ // Loop counters and iterators
1476
+ "i",
1477
+ "j",
1478
+ "k",
1479
+ "n",
1480
+ "m",
1481
+ // Web/Network
1482
+ "url",
1483
+ "uri",
1484
+ "api",
1485
+ "cdn",
1486
+ "dns",
1487
+ "ip",
1488
+ "tcp",
1489
+ "udp",
1490
+ "http",
1491
+ "ssl",
1492
+ "tls",
1493
+ "utm",
1494
+ "seo",
1495
+ "rss",
1496
+ "xhr",
1497
+ "ajax",
1498
+ "cors",
1499
+ "ws",
1500
+ "wss",
1501
+ // Data formats
1502
+ "json",
1503
+ "xml",
1504
+ "yaml",
1505
+ "csv",
1506
+ "html",
1507
+ "css",
1508
+ "svg",
1509
+ "pdf",
1510
+ // File types & extensions
1511
+ "img",
1512
+ "txt",
1513
+ "doc",
1514
+ "docx",
1515
+ "xlsx",
1516
+ "ppt",
1517
+ "md",
1518
+ "rst",
1519
+ "jpg",
1520
+ "png",
1521
+ "gif",
1522
+ // Databases
1523
+ "db",
1524
+ "sql",
1525
+ "orm",
1526
+ "dao",
1527
+ "dto",
1528
+ "ddb",
1529
+ "rds",
1530
+ "nosql",
1531
+ // File system
1532
+ "fs",
1533
+ "dir",
1534
+ "tmp",
1535
+ "src",
1536
+ "dst",
1537
+ "bin",
1538
+ "lib",
1539
+ "pkg",
1540
+ // Operating system
1541
+ "os",
1542
+ "env",
1543
+ "arg",
1544
+ "cli",
1545
+ "cmd",
1546
+ "exe",
1547
+ "cwd",
1548
+ "pwd",
1549
+ // UI/UX
1550
+ "ui",
1551
+ "ux",
1552
+ "gui",
1553
+ "dom",
1554
+ "ref",
1555
+ // Request/Response
1556
+ "req",
1557
+ "res",
1558
+ "ctx",
1559
+ "err",
1560
+ "msg",
1561
+ "auth",
1562
+ // Mathematics/Computing
1563
+ "max",
1564
+ "min",
1565
+ "avg",
1566
+ "sum",
1567
+ "abs",
1568
+ "cos",
1569
+ "sin",
1570
+ "tan",
1571
+ "log",
1572
+ "exp",
1573
+ "pow",
1574
+ "sqrt",
1575
+ "std",
1576
+ "var",
1577
+ "int",
1578
+ "num",
1579
+ "idx",
1580
+ // Time
1581
+ "now",
1582
+ "utc",
1583
+ "tz",
1584
+ "ms",
1585
+ "sec",
1586
+ "hr",
1587
+ "min",
1588
+ "yr",
1589
+ "mo",
1590
+ // Common patterns
1591
+ "app",
1592
+ "cfg",
1593
+ "config",
1594
+ "init",
1595
+ "len",
1596
+ "val",
1597
+ "str",
1598
+ "obj",
1599
+ "arr",
1600
+ "gen",
1601
+ "def",
1602
+ "raw",
1603
+ "new",
1604
+ "old",
1605
+ "pre",
1606
+ "post",
1607
+ "sub",
1608
+ "pub",
1609
+ // Programming/Framework specific
1610
+ "ts",
1611
+ "js",
1612
+ "jsx",
1613
+ "tsx",
1614
+ "py",
1615
+ "rb",
1616
+ "vue",
1617
+ "re",
1618
+ "fn",
1619
+ "fns",
1620
+ "mod",
1621
+ "opts",
1622
+ "dev",
1623
+ // Cloud/Infrastructure
1624
+ "s3",
1625
+ "ec2",
1626
+ "sqs",
1627
+ "sns",
1628
+ "vpc",
1629
+ "ami",
1630
+ "iam",
1631
+ "acl",
1632
+ "elb",
1633
+ "alb",
1634
+ "nlb",
1635
+ "aws",
1636
+ "ses",
1637
+ "gst",
1638
+ "cdk",
1639
+ "btn",
1640
+ "buf",
1641
+ "agg",
1642
+ "ocr",
1643
+ "ai",
1644
+ "cf",
1645
+ "cfn",
1646
+ "ga",
1647
+ // Metrics/Performance
1648
+ "fcp",
1649
+ "lcp",
1650
+ "cls",
1651
+ "ttfb",
1652
+ "tti",
1653
+ "fid",
1654
+ "fps",
1655
+ "qps",
1656
+ "rps",
1657
+ "tps",
1658
+ "wpm",
1659
+ // Testing & i18n
1660
+ "po",
1661
+ "e2e",
1662
+ "a11y",
1663
+ "i18n",
1664
+ "l10n",
1665
+ "spy",
1666
+ // Domain-specific abbreviations (context-aware)
1667
+ "sk",
1668
+ "fy",
1669
+ "faq",
1670
+ "og",
1671
+ "seo",
1672
+ "cta",
1673
+ "roi",
1674
+ "kpi",
1675
+ "ttl",
1676
+ "pct",
1677
+ // Technical abbreviations
1678
+ "mac",
1679
+ "hex",
1680
+ "esm",
1681
+ "git",
1682
+ "rec",
1683
+ "loc",
1684
+ "dup",
1685
+ // Boolean helpers (these are intentional short names)
1686
+ "is",
1687
+ "has",
1688
+ "can",
1689
+ "did",
1690
+ "was",
1691
+ "are",
1692
+ // Date/Time context (when in date contexts)
1693
+ "d",
1694
+ "t",
1695
+ "dt",
1696
+ // Coverage metrics (industry standard: statements/branches/functions/lines)
1697
+ "s",
1698
+ "b",
1699
+ "f",
1700
+ "l",
1701
+ // Common media/content abbreviations
1702
+ "vid",
1703
+ "pic",
1704
+ "img",
1705
+ "doc",
1706
+ "msg"
1707
+ ]);
1708
+ async function analyzeNaming(files) {
1709
+ const issues = [];
1710
+ const rootDir = files.length > 0 ? (0, import_path2.dirname)(files[0]) : process.cwd();
1711
+ const config = (0, import_core4.loadConfig)(rootDir);
1712
+ const consistencyConfig = config?.tools?.["consistency"];
1713
+ const customAbbreviations = new Set(consistencyConfig?.acceptedAbbreviations || []);
1714
+ const customShortWords = new Set(consistencyConfig?.shortWords || []);
1715
+ const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
1716
+ for (const file of files) {
1717
+ const content = await (0, import_core4.readFileContent)(file);
1718
+ const fileIssues = analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks);
1719
+ issues.push(...fileIssues);
1720
+ }
1721
+ return issues;
1722
+ }
1723
+ function analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks) {
1724
+ const issues = [];
1725
+ const isTestFile = file.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/);
1726
+ const lines = content.split("\n");
1727
+ const allAbbreviations = /* @__PURE__ */ new Set([...ACCEPTABLE_ABBREVIATIONS2, ...customAbbreviations]);
1728
+ const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS2, ...customShortWords]);
1729
+ const getContextWindow = (index, windowSize = 3) => {
1730
+ const start = Math.max(0, index - windowSize);
1731
+ const end = Math.min(lines.length, index + windowSize + 1);
1732
+ return lines.slice(start, end).join("\n");
1733
+ };
1734
+ const isShortLivedVariable = (varName, declarationIndex) => {
1735
+ const searchRange = 5;
1736
+ const endIndex = Math.min(lines.length, declarationIndex + searchRange + 1);
1737
+ let usageCount = 0;
1738
+ for (let i = declarationIndex; i < endIndex; i++) {
1739
+ const regex = new RegExp(`\\b${varName}\\b`, "g");
1740
+ const matches = lines[i].match(regex);
1741
+ if (matches) {
1742
+ usageCount += matches.length;
1743
+ }
1744
+ }
1745
+ return usageCount >= 2 && usageCount <= 3;
1746
+ };
1747
+ lines.forEach((line, index) => {
1748
+ const lineNumber = index + 1;
1749
+ const contextWindow = getContextWindow(index);
1750
+ if (!disabledChecks.has("single-letter")) {
1751
+ const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
1752
+ for (const match of singleLetterMatches) {
1753
+ const letter = match[1].toLowerCase();
1754
+ const isCoverageContext2 = /coverage|summary|metrics|pct|percent/i.test(line) || /\.(?:statements|branches|functions|lines)\.pct/i.test(line);
1755
+ if (isCoverageContext2 && ["s", "b", "f", "l"].includes(letter)) {
1756
+ continue;
1757
+ }
1758
+ const isInLoopContext = line.includes("for") || /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) || line.includes("=>") || // Arrow function
1759
+ /\w+\s*=>\s*/.test(line);
1760
+ const isI18nContext = line.includes("useTranslation") || line.includes("i18n.t") || /\bt\s*\(['"]/.test(line);
1761
+ const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
1762
+ /[a-z]\s*=>/.test(line) || // s => on same line
1763
+ // Multi-line arrow function detection: look for pattern in context window
1764
+ new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
1765
+ new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && /=>/.test(contextWindow);
1766
+ const isShortLived = isShortLivedVariable(letter, index);
1767
+ if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
1768
+ if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
1769
+ continue;
1770
+ }
1771
+ issues.push({
1772
+ file,
1773
+ line: lineNumber,
1774
+ type: "poor-naming",
1775
+ identifier: match[1],
1776
+ severity: "minor",
1777
+ suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
1778
+ });
1779
+ }
1780
+ }
1781
+ }
1782
+ if (!disabledChecks.has("abbreviation")) {
1783
+ const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
1784
+ for (const match of abbreviationMatches) {
1785
+ const abbrev = match[1].toLowerCase();
1786
+ if (allShortWords.has(abbrev)) {
1787
+ continue;
1788
+ }
1789
+ if (allAbbreviations.has(abbrev)) {
1790
+ continue;
1791
+ }
1792
+ const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
1793
+ new RegExp(`\\b${abbrev}\\s*=>`).test(line) || // s => on same line
1794
+ // Multi-line arrow function: check context window
1795
+ new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
1796
+ new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && new RegExp(`^\\s*${abbrev}\\s*=>`).test(line);
1797
+ if (isArrowFunctionParam) {
1798
+ continue;
1799
+ }
1800
+ if (abbrev.length <= 2) {
1801
+ const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
1802
+ if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
1803
+ continue;
1804
+ }
1805
+ const isUserContext = /user|auth|account/i.test(line);
1806
+ if (isUserContext && abbrev === "u") {
1807
+ continue;
1808
+ }
1809
+ }
1810
+ issues.push({
1811
+ file,
1812
+ line: lineNumber,
1813
+ type: "abbreviation",
1814
+ identifier: match[1],
1815
+ severity: "info",
1816
+ suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
1817
+ });
1818
+ }
1819
+ }
1820
+ if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
1821
+ const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
1822
+ const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
1823
+ if (snakeCaseVars) {
1824
+ issues.push({
1825
+ file,
1826
+ line: lineNumber,
1827
+ type: "convention-mix",
1828
+ identifier: snakeCaseVars[1],
1829
+ severity: "minor",
1830
+ suggestion: `Use camelCase '${snakeCaseToCamelCase(snakeCaseVars[1])}' instead of snake_case in TypeScript/JavaScript`
1831
+ });
1832
+ }
1833
+ }
1834
+ if (!disabledChecks.has("unclear")) {
1835
+ const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
1836
+ for (const match of booleanMatches) {
1837
+ const name = match[1];
1838
+ if (!name.match(/^(is|has|should|can|will|did)/i)) {
1839
+ issues.push({
1840
+ file,
1841
+ line: lineNumber,
1842
+ type: "unclear",
1843
+ identifier: name,
1844
+ severity: "info",
1845
+ suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
1846
+ });
1847
+ }
1848
+ }
1849
+ }
1850
+ if (!disabledChecks.has("unclear")) {
1851
+ const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
1852
+ for (const match of functionMatches) {
1853
+ const name = match[1];
1854
+ const isKeyword = ["for", "if", "else", "while", "do", "switch", "case", "break", "continue", "return", "throw", "try", "catch", "finally", "with", "yield", "await"].includes(name);
1855
+ if (isKeyword) {
1856
+ continue;
1857
+ }
1858
+ const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(name);
1859
+ if (isEntryPoint) {
1860
+ continue;
1861
+ }
1862
+ const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/);
1863
+ const isEventHandler = name.match(/^on[A-Z]/);
1864
+ const isDescriptiveLong = name.length > 15;
1865
+ const isReactHook = name.match(/^use[A-Z]/);
1866
+ const isDescriptivePattern = name.match(/^(default|total|count|sum|avg|max|min|initial|current|previous|next)\w+/) || name.match(/\w+(Count|Total|Sum|Average|List|Map|Set|Config|Settings|Options|Props|Data|Info|Details|State|Status|Response|Result)$/);
1867
+ const isHelperPattern = name.match(/^(to|from|with|without|for|as|into)\w+/) || // toMetadata, withLogger, forPath
1868
+ name.match(/^\w+(To|From|With|Without|For|As|Into)\w*$/);
1869
+ const isUtilityName = ["cn", "proxy", "sitemap", "robots", "gtag"].includes(name);
1870
+ const capitalCount = (name.match(/[A-Z]/g) || []).length;
1871
+ const isCompoundWord = capitalCount >= 3;
1872
+ const hasActionVerb = name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce|make|do|run|start|stop|build|parse|format|render|calculate|compute|generate|transform|convert|normalize|sanitize|encode|decode|compress|extract|merge|split|join|sort|compare|test|verify|ensure|apply|execute|invoke|call|emit|dispatch|trigger|listen|subscribe|unsubscribe|add|remove|clear|reset|toggle|enable|disable|open|close|connect|disconnect|send|receive|read|write|import|export|register|unregister|mount|unmount|track|store|persist|upsert|derive|classify|combine|discover|activate|require|assert|expect|mask|escape|sign|put|list|complete|page|safe|mock|pick|pluralize|text)/);
1873
+ if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord && !isHelperPattern && !isUtilityName && !isReactHook) {
1874
+ issues.push({
1875
+ file,
1876
+ line: lineNumber,
1877
+ type: "unclear",
1878
+ identifier: name,
1879
+ severity: "info",
1880
+ suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
1881
+ });
1882
+ }
1883
+ }
1884
+ }
1885
+ });
1886
+ return issues;
1887
+ }
1888
+ function snakeCaseToCamelCase(str) {
1889
+ return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
1890
+ }
1891
+ function detectNamingConventions(files, allIssues) {
1892
+ const camelCaseCount = allIssues.filter((i) => i.type === "convention-mix").length;
1893
+ const totalChecks = files.length * 10;
1894
+ if (camelCaseCount / totalChecks > 0.3) {
1895
+ return { dominantConvention: "mixed", conventionScore: 0.5 };
1896
+ }
1897
+ return { dominantConvention: "camelCase", conventionScore: 0.9 };
1898
+ }
873
1899
  // Annotate the CommonJS export names for ESM import in node:
874
1900
  0 && (module.exports = {
875
1901
  analyzeConsistency,
876
1902
  analyzeNaming,
1903
+ analyzeNamingAST,
877
1904
  analyzePatterns,
878
1905
  detectNamingConventions
879
1906
  });