@aiready/consistency 0.21.8 → 0.21.11

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.
Files changed (136) hide show
  1. package/.turbo/turbo-build.log +23 -24
  2. package/.turbo/turbo-format-check.log +1 -1
  3. package/.turbo/turbo-lint.log +1 -1
  4. package/.turbo/turbo-test.log +18 -19
  5. package/.turbo/turbo-type-check.log +1 -1
  6. package/dist/index.d.mts +3 -3
  7. package/dist/index.d.ts +3 -3
  8. package/dist/index.js +4 -4
  9. package/dist/index.mjs +3 -3
  10. package/package.json +2 -2
  11. package/src/__tests__/provider.test.ts +6 -34
  12. package/src/index.ts +3 -3
  13. package/src/provider.ts +2 -2
  14. package/dist/__tests__/analyzer.test.d.ts +0 -2
  15. package/dist/__tests__/analyzer.test.d.ts.map +0 -1
  16. package/dist/__tests__/analyzer.test.js +0 -157
  17. package/dist/__tests__/analyzer.test.js.map +0 -1
  18. package/dist/__tests__/language-filter.test.d.ts +0 -2
  19. package/dist/__tests__/language-filter.test.d.ts.map +0 -1
  20. package/dist/__tests__/language-filter.test.js +0 -46
  21. package/dist/__tests__/language-filter.test.js.map +0 -1
  22. package/dist/__tests__/scoring.test.d.ts +0 -2
  23. package/dist/__tests__/scoring.test.d.ts.map +0 -1
  24. package/dist/__tests__/scoring.test.js +0 -118
  25. package/dist/__tests__/scoring.test.js.map +0 -1
  26. package/dist/analyzer.d.ts +0 -7
  27. package/dist/analyzer.d.ts.map +0 -1
  28. package/dist/analyzer.js +0 -160
  29. package/dist/analyzer.js.map +0 -1
  30. package/dist/analyzers/naming-ast.d.ts +0 -7
  31. package/dist/analyzers/naming-ast.d.ts.map +0 -1
  32. package/dist/analyzers/naming-ast.js +0 -253
  33. package/dist/analyzers/naming-ast.js.map +0 -1
  34. package/dist/analyzers/naming-constants.d.ts +0 -21
  35. package/dist/analyzers/naming-constants.d.ts.map +0 -1
  36. package/dist/analyzers/naming-constants.js +0 -96
  37. package/dist/analyzers/naming-constants.js.map +0 -1
  38. package/dist/analyzers/naming-python.d.ts +0 -16
  39. package/dist/analyzers/naming-python.d.ts.map +0 -1
  40. package/dist/analyzers/naming-python.js +0 -165
  41. package/dist/analyzers/naming-python.js.map +0 -1
  42. package/dist/analyzers/naming.d.ts +0 -6
  43. package/dist/analyzers/naming.d.ts.map +0 -1
  44. package/dist/analyzers/naming.js +0 -234
  45. package/dist/analyzers/naming.js.map +0 -1
  46. package/dist/analyzers/patterns.d.ts +0 -10
  47. package/dist/analyzers/patterns.d.ts.map +0 -1
  48. package/dist/analyzers/patterns.js +0 -197
  49. package/dist/analyzers/patterns.js.map +0 -1
  50. package/dist/chunk-2BTBNG6X.mjs +0 -814
  51. package/dist/chunk-3ZB6FFRL.mjs +0 -661
  52. package/dist/chunk-5UFRGXSB.mjs +0 -783
  53. package/dist/chunk-66M3TIO7.mjs +0 -837
  54. package/dist/chunk-6BM5MV3S.mjs +0 -719
  55. package/dist/chunk-6H3JHDP7.mjs +0 -832
  56. package/dist/chunk-7PHHJOGC.mjs +0 -1374
  57. package/dist/chunk-AASFXGUR.mjs +0 -1622
  58. package/dist/chunk-AR7DIZLP.mjs +0 -827
  59. package/dist/chunk-BDDMOIU2.mjs +0 -385
  60. package/dist/chunk-BMILMNKJ.mjs +0 -1633
  61. package/dist/chunk-BYY6MD5T.mjs +0 -729
  62. package/dist/chunk-CA4Q5JBK.mjs +0 -1143
  63. package/dist/chunk-CF4LU7KE.mjs +0 -384
  64. package/dist/chunk-CJINEUIH.mjs +0 -1369
  65. package/dist/chunk-CLWNLHDB.mjs +0 -909
  66. package/dist/chunk-CZUJTDNH.mjs +0 -848
  67. package/dist/chunk-DNGW3WQK.mjs +0 -810
  68. package/dist/chunk-DSI3TEO2.mjs +0 -662
  69. package/dist/chunk-EIQ5K6OO.mjs +0 -1579
  70. package/dist/chunk-FEJODRK5.mjs +0 -783
  71. package/dist/chunk-FK56AZ43.mjs +0 -817
  72. package/dist/chunk-H6S7WKSQ.mjs +0 -729
  73. package/dist/chunk-HAOJLJNB.mjs +0 -1290
  74. package/dist/chunk-HJCP36VW.mjs +0 -821
  75. package/dist/chunk-HPG7P6PD.mjs +0 -1372
  76. package/dist/chunk-IVRBV7SE.mjs +0 -1295
  77. package/dist/chunk-IXBC6GVT.mjs +0 -832
  78. package/dist/chunk-J5IFYDVU.mjs +0 -1579
  79. package/dist/chunk-KWQVBF7K.mjs +0 -831
  80. package/dist/chunk-LD3CHHU2.mjs +0 -1297
  81. package/dist/chunk-LMOXGPCM.mjs +0 -722
  82. package/dist/chunk-LSXZH6X6.mjs +0 -810
  83. package/dist/chunk-LUAREV6A.mjs +0 -508
  84. package/dist/chunk-MAPVFXBP.mjs +0 -708
  85. package/dist/chunk-MM2PLUCH.mjs +0 -1376
  86. package/dist/chunk-NPWCJZUG.mjs +0 -708
  87. package/dist/chunk-ON73WHHU.mjs +0 -1310
  88. package/dist/chunk-P6NVKUBB.mjs +0 -831
  89. package/dist/chunk-Q3KTWDSL.mjs +0 -808
  90. package/dist/chunk-Q5XMWG33.mjs +0 -661
  91. package/dist/chunk-QOIPVP6P.mjs +0 -1607
  92. package/dist/chunk-RMEQWG52.mjs +0 -1633
  93. package/dist/chunk-S6BZVTWN.mjs +0 -731
  94. package/dist/chunk-TE6JYZD3.mjs +0 -810
  95. package/dist/chunk-TLVLM3M5.mjs +0 -771
  96. package/dist/chunk-TXHPUU7A.mjs +0 -863
  97. package/dist/chunk-UMBBTNQN.mjs +0 -787
  98. package/dist/chunk-V2UPXL7L.mjs +0 -842
  99. package/dist/chunk-VODCPPET.mjs +0 -1292
  100. package/dist/chunk-W6UGMKRV.mjs +0 -1310
  101. package/dist/chunk-WGH4TGZ3.mjs +0 -1288
  102. package/dist/chunk-WTBDNCEN.mjs +0 -1352
  103. package/dist/chunk-XVW5DKJQ.mjs +0 -1619
  104. package/dist/chunk-YCDCIOJN.mjs +0 -842
  105. package/dist/chunk-YEHXYHGY.mjs +0 -1497
  106. package/dist/chunk-YHHXE2JX.mjs +0 -912
  107. package/dist/chunk-ZB6UK276.mjs +0 -662
  108. package/dist/chunk-ZG3KFSD3.mjs +0 -1142
  109. package/dist/cli.d.ts.map +0 -1
  110. package/dist/cli.js.map +0 -1
  111. package/dist/index.d.ts.map +0 -1
  112. package/dist/index.js.map +0 -1
  113. package/dist/scoring.d.ts +0 -12
  114. package/dist/scoring.d.ts.map +0 -1
  115. package/dist/scoring.js +0 -110
  116. package/dist/scoring.js.map +0 -1
  117. package/dist/types.d.ts +0 -53
  118. package/dist/types.d.ts.map +0 -1
  119. package/dist/types.js +0 -2
  120. package/dist/types.js.map +0 -1
  121. package/dist/utils/ast-parser.d.ts +0 -46
  122. package/dist/utils/ast-parser.d.ts.map +0 -1
  123. package/dist/utils/ast-parser.js +0 -157
  124. package/dist/utils/ast-parser.js.map +0 -1
  125. package/dist/utils/config-loader.d.ts +0 -19
  126. package/dist/utils/config-loader.d.ts.map +0 -1
  127. package/dist/utils/config-loader.js +0 -31
  128. package/dist/utils/config-loader.js.map +0 -1
  129. package/dist/utils/context-detector.d.ts +0 -40
  130. package/dist/utils/context-detector.d.ts.map +0 -1
  131. package/dist/utils/context-detector.js +0 -225
  132. package/dist/utils/context-detector.js.map +0 -1
  133. package/dist/utils/scope-tracker.d.ts +0 -87
  134. package/dist/utils/scope-tracker.d.ts.map +0 -1
  135. package/dist/utils/scope-tracker.js +0 -161
  136. package/dist/utils/scope-tracker.js.map +0 -1
@@ -1,863 +0,0 @@
1
- // src/analyzers/naming.ts
2
- import { readFileContent, loadConfig } from "@aiready/core";
3
- import { dirname } from "path";
4
- var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
5
- // Full English words (1-3 letters)
6
- "day",
7
- "key",
8
- "net",
9
- "to",
10
- "go",
11
- "for",
12
- "not",
13
- "new",
14
- "old",
15
- "top",
16
- "end",
17
- "run",
18
- "try",
19
- "use",
20
- "get",
21
- "set",
22
- "add",
23
- "put",
24
- "map",
25
- "log",
26
- "row",
27
- "col",
28
- "tab",
29
- "box",
30
- "div",
31
- "nav",
32
- "tag",
33
- "any",
34
- "all",
35
- "one",
36
- "two",
37
- "out",
38
- "off",
39
- "on",
40
- "yes",
41
- "no",
42
- "now",
43
- "max",
44
- "min",
45
- "sum",
46
- "avg",
47
- "ref",
48
- "src",
49
- "dst",
50
- "raw",
51
- "def",
52
- "sub",
53
- "pub",
54
- "pre",
55
- "mid",
56
- "alt",
57
- "opt",
58
- "tmp",
59
- "ext",
60
- "sep",
61
- // Prepositions and conjunctions
62
- "and",
63
- "from",
64
- "how",
65
- "pad",
66
- "bar",
67
- "non",
68
- // Additional full words commonly flagged
69
- "tax",
70
- "cat",
71
- "dog",
72
- "car",
73
- "bus",
74
- "web",
75
- "app",
76
- "war",
77
- "law",
78
- "pay",
79
- "buy",
80
- "win",
81
- "cut",
82
- "hit",
83
- "hot",
84
- "pop",
85
- "job",
86
- "age",
87
- "act",
88
- "let",
89
- "lot",
90
- "bad",
91
- "big",
92
- "far",
93
- "few",
94
- "own",
95
- "per",
96
- "red",
97
- "low",
98
- "see",
99
- "six",
100
- "ten",
101
- "way",
102
- "who",
103
- "why",
104
- "yet",
105
- "via",
106
- "due",
107
- "fee",
108
- "fun",
109
- "gas",
110
- "gay",
111
- "god",
112
- "gun",
113
- "guy",
114
- "ice",
115
- "ill",
116
- "kid",
117
- "mad",
118
- "man",
119
- "mix",
120
- "mom",
121
- "mrs",
122
- "nor",
123
- "odd",
124
- "oil",
125
- "pan",
126
- "pet",
127
- "pit",
128
- "pot",
129
- "pow",
130
- "pro",
131
- "raw",
132
- "rep",
133
- "rid",
134
- "sad",
135
- "sea",
136
- "sit",
137
- "sky",
138
- "son",
139
- "tea",
140
- "tie",
141
- "tip",
142
- "van",
143
- "war",
144
- "win",
145
- "won"
146
- ]);
147
- var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
148
- // Standard identifiers
149
- "id",
150
- "uid",
151
- "gid",
152
- "pid",
153
- // Loop counters and iterators
154
- "i",
155
- "j",
156
- "k",
157
- "n",
158
- "m",
159
- // Web/Network
160
- "url",
161
- "uri",
162
- "api",
163
- "cdn",
164
- "dns",
165
- "ip",
166
- "tcp",
167
- "udp",
168
- "http",
169
- "ssl",
170
- "tls",
171
- "utm",
172
- "seo",
173
- "rss",
174
- "xhr",
175
- "ajax",
176
- "cors",
177
- "ws",
178
- "wss",
179
- // Data formats
180
- "json",
181
- "xml",
182
- "yaml",
183
- "csv",
184
- "html",
185
- "css",
186
- "svg",
187
- "pdf",
188
- // File types & extensions
189
- "img",
190
- "txt",
191
- "doc",
192
- "docx",
193
- "xlsx",
194
- "ppt",
195
- "md",
196
- "rst",
197
- "jpg",
198
- "png",
199
- "gif",
200
- // Databases
201
- "db",
202
- "sql",
203
- "orm",
204
- "dao",
205
- "dto",
206
- "ddb",
207
- "rds",
208
- "nosql",
209
- // File system
210
- "fs",
211
- "dir",
212
- "tmp",
213
- "src",
214
- "dst",
215
- "bin",
216
- "lib",
217
- "pkg",
218
- // Operating system
219
- "os",
220
- "env",
221
- "arg",
222
- "cli",
223
- "cmd",
224
- "exe",
225
- "cwd",
226
- "pwd",
227
- // UI/UX
228
- "ui",
229
- "ux",
230
- "gui",
231
- "dom",
232
- "ref",
233
- // Request/Response
234
- "req",
235
- "res",
236
- "ctx",
237
- "err",
238
- "msg",
239
- "auth",
240
- // Mathematics/Computing
241
- "max",
242
- "min",
243
- "avg",
244
- "sum",
245
- "abs",
246
- "cos",
247
- "sin",
248
- "tan",
249
- "log",
250
- "exp",
251
- "pow",
252
- "sqrt",
253
- "std",
254
- "var",
255
- "int",
256
- "num",
257
- "idx",
258
- // Time
259
- "now",
260
- "utc",
261
- "tz",
262
- "ms",
263
- "sec",
264
- "hr",
265
- "min",
266
- "yr",
267
- "mo",
268
- // Common patterns
269
- "app",
270
- "cfg",
271
- "config",
272
- "init",
273
- "len",
274
- "val",
275
- "str",
276
- "obj",
277
- "arr",
278
- "gen",
279
- "def",
280
- "raw",
281
- "new",
282
- "old",
283
- "pre",
284
- "post",
285
- "sub",
286
- "pub",
287
- // Programming/Framework specific
288
- "ts",
289
- "js",
290
- "jsx",
291
- "tsx",
292
- "py",
293
- "rb",
294
- "vue",
295
- "re",
296
- "fn",
297
- "fns",
298
- "mod",
299
- "opts",
300
- "dev",
301
- // Cloud/Infrastructure
302
- "s3",
303
- "ec2",
304
- "sqs",
305
- "sns",
306
- "vpc",
307
- "ami",
308
- "iam",
309
- "acl",
310
- "elb",
311
- "alb",
312
- "nlb",
313
- "aws",
314
- "ses",
315
- "gst",
316
- "cdk",
317
- "btn",
318
- "buf",
319
- "agg",
320
- "ocr",
321
- "ai",
322
- "cf",
323
- "cfn",
324
- "ga",
325
- // Metrics/Performance
326
- "fcp",
327
- "lcp",
328
- "cls",
329
- "ttfb",
330
- "tti",
331
- "fid",
332
- "fps",
333
- "qps",
334
- "rps",
335
- "tps",
336
- "wpm",
337
- // Testing & i18n
338
- "po",
339
- "e2e",
340
- "a11y",
341
- "i18n",
342
- "l10n",
343
- "spy",
344
- // Domain-specific abbreviations (context-aware)
345
- "sk",
346
- "fy",
347
- "faq",
348
- "og",
349
- "seo",
350
- "cta",
351
- "roi",
352
- "kpi",
353
- "ttl",
354
- "pct",
355
- // Technical abbreviations
356
- "mac",
357
- "hex",
358
- "esm",
359
- "git",
360
- "rec",
361
- "loc",
362
- "dup",
363
- // Boolean helpers (these are intentional short names)
364
- "is",
365
- "has",
366
- "can",
367
- "did",
368
- "was",
369
- "are",
370
- // Date/Time context (when in date contexts)
371
- "d",
372
- "t",
373
- "dt",
374
- // Coverage metrics (industry standard: statements/branches/functions/lines)
375
- "s",
376
- "b",
377
- "f",
378
- "l",
379
- // Common media/content abbreviations
380
- "vid",
381
- "pic",
382
- "img",
383
- "doc",
384
- "msg"
385
- ]);
386
- async function analyzeNaming(files) {
387
- const issues = [];
388
- const rootDir = files.length > 0 ? dirname(files[0]) : process.cwd();
389
- const config = loadConfig(rootDir);
390
- const consistencyConfig = config?.tools?.["consistency"];
391
- const customAbbreviations = new Set(consistencyConfig?.acceptedAbbreviations || []);
392
- const customShortWords = new Set(consistencyConfig?.shortWords || []);
393
- const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
394
- for (const file of files) {
395
- const content = await readFileContent(file);
396
- const fileIssues = analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks);
397
- issues.push(...fileIssues);
398
- }
399
- return issues;
400
- }
401
- function analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks) {
402
- const issues = [];
403
- const isTestFile = file.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/);
404
- const lines = content.split("\n");
405
- const allAbbreviations = /* @__PURE__ */ new Set([...ACCEPTABLE_ABBREVIATIONS, ...customAbbreviations]);
406
- const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
407
- const getContextWindow = (index, windowSize = 3) => {
408
- const start = Math.max(0, index - windowSize);
409
- const end = Math.min(lines.length, index + windowSize + 1);
410
- return lines.slice(start, end).join("\n");
411
- };
412
- const isShortLivedVariable = (varName, declarationIndex) => {
413
- const searchRange = 5;
414
- const endIndex = Math.min(lines.length, declarationIndex + searchRange + 1);
415
- let usageCount = 0;
416
- for (let i = declarationIndex; i < endIndex; i++) {
417
- const regex = new RegExp(`\\b${varName}\\b`, "g");
418
- const matches = lines[i].match(regex);
419
- if (matches) {
420
- usageCount += matches.length;
421
- }
422
- }
423
- return usageCount >= 2 && usageCount <= 3;
424
- };
425
- lines.forEach((line, index) => {
426
- const lineNumber = index + 1;
427
- const contextWindow = getContextWindow(index);
428
- if (!disabledChecks.has("single-letter")) {
429
- const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
430
- for (const match of singleLetterMatches) {
431
- const letter = match[1].toLowerCase();
432
- const isCoverageContext = /coverage|summary|metrics|pct|percent/i.test(line) || /\.(?:statements|branches|functions|lines)\.pct/i.test(line);
433
- if (isCoverageContext && ["s", "b", "f", "l"].includes(letter)) {
434
- continue;
435
- }
436
- const isInLoopContext = line.includes("for") || /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) || line.includes("=>") || // Arrow function
437
- /\w+\s*=>\s*/.test(line);
438
- const isI18nContext = line.includes("useTranslation") || line.includes("i18n.t") || /\bt\s*\(['"]/.test(line);
439
- const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
440
- /[a-z]\s*=>/.test(line) || // s => on same line
441
- // Multi-line arrow function detection: look for pattern in context window
442
- new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
443
- new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && /=>/.test(contextWindow);
444
- const isShortLived = isShortLivedVariable(letter, index);
445
- if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
446
- if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
447
- continue;
448
- }
449
- issues.push({
450
- file,
451
- line: lineNumber,
452
- type: "poor-naming",
453
- identifier: match[1],
454
- severity: "minor",
455
- suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
456
- });
457
- }
458
- }
459
- }
460
- if (!disabledChecks.has("abbreviation")) {
461
- const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
462
- for (const match of abbreviationMatches) {
463
- const abbrev = match[1].toLowerCase();
464
- if (allShortWords.has(abbrev)) {
465
- continue;
466
- }
467
- if (allAbbreviations.has(abbrev)) {
468
- continue;
469
- }
470
- const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
471
- new RegExp(`\\b${abbrev}\\s*=>`).test(line) || // s => on same line
472
- // Multi-line arrow function: check context window
473
- new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
474
- new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && new RegExp(`^\\s*${abbrev}\\s*=>`).test(line);
475
- if (isArrowFunctionParam) {
476
- continue;
477
- }
478
- if (abbrev.length <= 2) {
479
- const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
480
- if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
481
- continue;
482
- }
483
- const isUserContext = /user|auth|account/i.test(line);
484
- if (isUserContext && abbrev === "u") {
485
- continue;
486
- }
487
- }
488
- issues.push({
489
- file,
490
- line: lineNumber,
491
- type: "abbreviation",
492
- identifier: match[1],
493
- severity: "info",
494
- suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
495
- });
496
- }
497
- }
498
- if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
499
- const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
500
- const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
501
- if (snakeCaseVars) {
502
- issues.push({
503
- file,
504
- line: lineNumber,
505
- type: "convention-mix",
506
- identifier: snakeCaseVars[1],
507
- severity: "minor",
508
- suggestion: `Use camelCase '${snakeCaseToCamelCase(snakeCaseVars[1])}' instead of snake_case in TypeScript/JavaScript`
509
- });
510
- }
511
- }
512
- if (!disabledChecks.has("unclear")) {
513
- const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
514
- for (const match of booleanMatches) {
515
- const name = match[1];
516
- if (!name.match(/^(is|has|should|can|will|did)/i)) {
517
- issues.push({
518
- file,
519
- line: lineNumber,
520
- type: "unclear",
521
- identifier: name,
522
- severity: "info",
523
- suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
524
- });
525
- }
526
- }
527
- }
528
- if (!disabledChecks.has("unclear")) {
529
- const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
530
- for (const match of functionMatches) {
531
- const name = match[1];
532
- const isKeyword = ["for", "if", "else", "while", "do", "switch", "case", "break", "continue", "return", "throw", "try", "catch", "finally", "with", "yield", "await"].includes(name);
533
- if (isKeyword) {
534
- continue;
535
- }
536
- const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(name);
537
- if (isEntryPoint) {
538
- continue;
539
- }
540
- const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/);
541
- const isEventHandler = name.match(/^on[A-Z]/);
542
- const isDescriptiveLong = name.length > 15;
543
- const isReactHook = name.match(/^use[A-Z]/);
544
- 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)$/);
545
- const isHelperPattern = name.match(/^(to|from|with|without|for|as|into)\w+/) || // toMetadata, withLogger, forPath
546
- name.match(/^\w+(To|From|With|Without|For|As|Into)\w*$/);
547
- const isUtilityName = ["cn", "proxy", "sitemap", "robots", "gtag"].includes(name);
548
- const capitalCount = (name.match(/[A-Z]/g) || []).length;
549
- const isCompoundWord = capitalCount >= 3;
550
- 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)/);
551
- if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord && !isHelperPattern && !isUtilityName && !isReactHook) {
552
- issues.push({
553
- file,
554
- line: lineNumber,
555
- type: "unclear",
556
- identifier: name,
557
- severity: "info",
558
- suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
559
- });
560
- }
561
- }
562
- }
563
- });
564
- return issues;
565
- }
566
- function snakeCaseToCamelCase(str) {
567
- return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
568
- }
569
- function detectNamingConventions(files, allIssues) {
570
- const camelCaseCount = allIssues.filter((i) => i.type === "convention-mix").length;
571
- const totalChecks = files.length * 10;
572
- if (camelCaseCount / totalChecks > 0.3) {
573
- return { dominantConvention: "mixed", conventionScore: 0.5 };
574
- }
575
- return { dominantConvention: "camelCase", conventionScore: 0.9 };
576
- }
577
-
578
- // src/analyzers/patterns.ts
579
- import { readFileContent as readFileContent2 } from "@aiready/core";
580
- async function analyzePatterns(files) {
581
- const issues = [];
582
- const errorHandlingIssues = await analyzeErrorHandling(files);
583
- issues.push(...errorHandlingIssues);
584
- const asyncIssues = await analyzeAsyncPatterns(files);
585
- issues.push(...asyncIssues);
586
- const importIssues = await analyzeImportStyles(files);
587
- issues.push(...importIssues);
588
- return issues;
589
- }
590
- async function analyzeErrorHandling(files) {
591
- const patterns = {
592
- tryCatch: [],
593
- throwsError: [],
594
- returnsNull: [],
595
- returnsError: []
596
- };
597
- for (const file of files) {
598
- const content = await readFileContent2(file);
599
- if (content.includes("try {") || content.includes("} catch")) {
600
- patterns.tryCatch.push(file);
601
- }
602
- if (content.match(/throw new \w*Error/)) {
603
- patterns.throwsError.push(file);
604
- }
605
- if (content.match(/return null/)) {
606
- patterns.returnsNull.push(file);
607
- }
608
- if (content.match(/return \{ error:/)) {
609
- patterns.returnsError.push(file);
610
- }
611
- }
612
- const issues = [];
613
- const strategiesUsed = Object.values(patterns).filter((p) => p.length > 0).length;
614
- if (strategiesUsed > 2) {
615
- issues.push({
616
- files: [.../* @__PURE__ */ new Set([
617
- ...patterns.tryCatch,
618
- ...patterns.throwsError,
619
- ...patterns.returnsNull,
620
- ...patterns.returnsError
621
- ])],
622
- type: "error-handling",
623
- description: "Inconsistent error handling strategies across codebase",
624
- examples: [
625
- patterns.tryCatch.length > 0 ? `Try-catch used in ${patterns.tryCatch.length} files` : "",
626
- patterns.throwsError.length > 0 ? `Throws errors in ${patterns.throwsError.length} files` : "",
627
- patterns.returnsNull.length > 0 ? `Returns null in ${patterns.returnsNull.length} files` : "",
628
- patterns.returnsError.length > 0 ? `Returns error objects in ${patterns.returnsError.length} files` : ""
629
- ].filter((e) => e),
630
- severity: "major"
631
- });
632
- }
633
- return issues;
634
- }
635
- async function analyzeAsyncPatterns(files) {
636
- const patterns = {
637
- asyncAwait: [],
638
- promises: [],
639
- callbacks: []
640
- };
641
- for (const file of files) {
642
- const content = await readFileContent2(file);
643
- if (content.match(/async\s+(function|\(|[a-zA-Z])/)) {
644
- patterns.asyncAwait.push(file);
645
- }
646
- if (content.match(/\.then\(/) || content.match(/\.catch\(/)) {
647
- patterns.promises.push(file);
648
- }
649
- if (content.match(/callback\s*\(/) || content.match(/\(\s*err\s*,/)) {
650
- patterns.callbacks.push(file);
651
- }
652
- }
653
- const issues = [];
654
- if (patterns.callbacks.length > 0 && patterns.asyncAwait.length > 0) {
655
- issues.push({
656
- files: [...patterns.callbacks, ...patterns.asyncAwait],
657
- type: "async-style",
658
- description: "Mixed async patterns: callbacks and async/await",
659
- examples: [
660
- `Callbacks found in: ${patterns.callbacks.slice(0, 3).join(", ")}`,
661
- `Async/await used in: ${patterns.asyncAwait.slice(0, 3).join(", ")}`
662
- ],
663
- severity: "minor"
664
- });
665
- }
666
- if (patterns.promises.length > patterns.asyncAwait.length * 0.3 && patterns.asyncAwait.length > 0) {
667
- issues.push({
668
- files: patterns.promises,
669
- type: "async-style",
670
- description: "Consider using async/await instead of promise chains for consistency",
671
- examples: patterns.promises.slice(0, 5),
672
- severity: "info"
673
- });
674
- }
675
- return issues;
676
- }
677
- async function analyzeImportStyles(files) {
678
- const patterns = {
679
- esModules: [],
680
- commonJS: [],
681
- mixed: []
682
- };
683
- for (const file of files) {
684
- const content = await readFileContent2(file);
685
- const hasESM = content.match(/^import\s+/m);
686
- const hasCJS = content.match(/require\s*\(/);
687
- if (hasESM && hasCJS) {
688
- patterns.mixed.push(file);
689
- } else if (hasESM) {
690
- patterns.esModules.push(file);
691
- } else if (hasCJS) {
692
- patterns.commonJS.push(file);
693
- }
694
- }
695
- const issues = [];
696
- if (patterns.mixed.length > 0) {
697
- issues.push({
698
- files: patterns.mixed,
699
- type: "import-style",
700
- description: "Mixed ES modules and CommonJS imports in same files",
701
- examples: patterns.mixed.slice(0, 5),
702
- severity: "major"
703
- });
704
- }
705
- if (patterns.esModules.length > 0 && patterns.commonJS.length > 0) {
706
- const ratio = patterns.commonJS.length / (patterns.esModules.length + patterns.commonJS.length);
707
- if (ratio > 0.2 && ratio < 0.8) {
708
- issues.push({
709
- files: [...patterns.esModules, ...patterns.commonJS],
710
- type: "import-style",
711
- description: "Inconsistent import styles across project",
712
- examples: [
713
- `ES modules: ${patterns.esModules.length} files`,
714
- `CommonJS: ${patterns.commonJS.length} files`
715
- ],
716
- severity: "minor"
717
- });
718
- }
719
- }
720
- return issues;
721
- }
722
-
723
- // src/analyzer.ts
724
- import { scanFiles } from "@aiready/core";
725
- async function analyzeConsistency(options) {
726
- const {
727
- checkNaming = true,
728
- checkPatterns = true,
729
- checkArchitecture = false,
730
- // Not implemented yet
731
- minSeverity = "info",
732
- ...scanOptions
733
- } = options;
734
- const filePaths = await scanFiles(scanOptions);
735
- const namingIssues = checkNaming ? await analyzeNaming(filePaths) : [];
736
- const patternIssues = checkPatterns ? await analyzePatterns(filePaths) : [];
737
- const results = [];
738
- const fileIssuesMap = /* @__PURE__ */ new Map();
739
- for (const issue of namingIssues) {
740
- if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
741
- continue;
742
- }
743
- const consistencyIssue = {
744
- type: issue.type === "convention-mix" ? "naming-inconsistency" : "naming-quality",
745
- category: "naming",
746
- severity: issue.severity,
747
- message: `${issue.type}: ${issue.identifier}`,
748
- location: {
749
- file: issue.file,
750
- line: issue.line,
751
- column: issue.column
752
- },
753
- suggestion: issue.suggestion
754
- };
755
- if (!fileIssuesMap.has(issue.file)) {
756
- fileIssuesMap.set(issue.file, []);
757
- }
758
- fileIssuesMap.get(issue.file).push(consistencyIssue);
759
- }
760
- for (const issue of patternIssues) {
761
- if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
762
- continue;
763
- }
764
- const consistencyIssue = {
765
- type: "pattern-inconsistency",
766
- category: "patterns",
767
- severity: issue.severity,
768
- message: issue.description,
769
- location: {
770
- file: issue.files[0] || "multiple files",
771
- line: 1
772
- },
773
- examples: issue.examples,
774
- suggestion: `Standardize ${issue.type} patterns across ${issue.files.length} files`
775
- };
776
- const firstFile = issue.files[0];
777
- if (firstFile && !fileIssuesMap.has(firstFile)) {
778
- fileIssuesMap.set(firstFile, []);
779
- }
780
- if (firstFile) {
781
- fileIssuesMap.get(firstFile).push(consistencyIssue);
782
- }
783
- }
784
- for (const [fileName, issues] of fileIssuesMap) {
785
- results.push({
786
- fileName,
787
- issues,
788
- metrics: {
789
- consistencyScore: calculateConsistencyScore(issues)
790
- }
791
- });
792
- }
793
- const recommendations = generateRecommendations(namingIssues, patternIssues);
794
- const conventionAnalysis = detectNamingConventions(filePaths, namingIssues);
795
- return {
796
- summary: {
797
- totalIssues: namingIssues.length + patternIssues.length,
798
- namingIssues: namingIssues.length,
799
- patternIssues: patternIssues.length,
800
- architectureIssues: 0,
801
- filesAnalyzed: filePaths.length
802
- },
803
- results,
804
- recommendations
805
- };
806
- }
807
- function shouldIncludeSeverity(severity, minSeverity) {
808
- const severityLevels = { info: 0, minor: 1, major: 2, critical: 3 };
809
- return severityLevels[severity] >= severityLevels[minSeverity];
810
- }
811
- function calculateConsistencyScore(issues) {
812
- const weights = { critical: 10, major: 5, minor: 2, info: 1 };
813
- const totalWeight = issues.reduce((sum, issue) => sum + weights[issue.severity], 0);
814
- return Math.max(0, 1 - totalWeight / 100);
815
- }
816
- function generateRecommendations(namingIssues, patternIssues) {
817
- const recommendations = [];
818
- if (namingIssues.length > 0) {
819
- const conventionMixCount = namingIssues.filter((i) => i.type === "convention-mix").length;
820
- if (conventionMixCount > 0) {
821
- recommendations.push(
822
- `Standardize naming conventions: Found ${conventionMixCount} snake_case variables in TypeScript/JavaScript (use camelCase)`
823
- );
824
- }
825
- const poorNamingCount = namingIssues.filter((i) => i.type === "poor-naming").length;
826
- if (poorNamingCount > 0) {
827
- recommendations.push(
828
- `Improve variable naming: Found ${poorNamingCount} single-letter or unclear variable names`
829
- );
830
- }
831
- }
832
- if (patternIssues.length > 0) {
833
- const errorHandlingIssues = patternIssues.filter((i) => i.type === "error-handling");
834
- if (errorHandlingIssues.length > 0) {
835
- recommendations.push(
836
- "Standardize error handling strategy across the codebase (prefer try-catch with typed errors)"
837
- );
838
- }
839
- const asyncIssues = patternIssues.filter((i) => i.type === "async-style");
840
- if (asyncIssues.length > 0) {
841
- recommendations.push(
842
- "Use async/await consistently instead of mixing with promise chains or callbacks"
843
- );
844
- }
845
- const importIssues = patternIssues.filter((i) => i.type === "import-style");
846
- if (importIssues.length > 0) {
847
- recommendations.push(
848
- "Use ES modules consistently across the project (avoid mixing with CommonJS)"
849
- );
850
- }
851
- }
852
- if (recommendations.length === 0) {
853
- recommendations.push("No major consistency issues found! Your codebase follows good practices.");
854
- }
855
- return recommendations;
856
- }
857
-
858
- export {
859
- analyzeNaming,
860
- detectNamingConventions,
861
- analyzePatterns,
862
- analyzeConsistency
863
- };