@aiready/consistency 0.5.0 → 0.6.1
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 +24 -0
- package/.turbo/turbo-test.log +123 -0
- package/dist/chunk-HAOJLJNB.mjs +1290 -0
- package/dist/chunk-IVRBV7SE.mjs +1295 -0
- package/dist/chunk-LD3CHHU2.mjs +1297 -0
- package/dist/chunk-VODCPPET.mjs +1292 -0
- package/dist/chunk-WGH4TGZ3.mjs +1288 -0
- package/dist/cli.js +624 -189
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +1196 -182
- package/dist/index.mjs +581 -4
- package/package.json +14 -13
- package/src/analyzer.ts +4 -4
- package/src/analyzers/naming-ast.ts +378 -0
- package/src/index.ts +2 -1
- package/src/utils/ast-parser.ts +181 -0
- package/src/utils/context-detector.ts +278 -0
- package/src/utils/scope-tracker.ts +221 -0
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,589 @@
|
|
|
1
1
|
import {
|
|
2
2
|
analyzeConsistency,
|
|
3
|
-
|
|
4
|
-
analyzePatterns
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
analyzeNamingAST,
|
|
4
|
+
analyzePatterns
|
|
5
|
+
} from "./chunk-LD3CHHU2.mjs";
|
|
6
|
+
|
|
7
|
+
// src/analyzers/naming.ts
|
|
8
|
+
import { readFileContent, loadConfig } from "@aiready/core";
|
|
9
|
+
import { dirname } from "path";
|
|
10
|
+
var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
|
|
11
|
+
// Full English words (1-3 letters)
|
|
12
|
+
"day",
|
|
13
|
+
"key",
|
|
14
|
+
"net",
|
|
15
|
+
"to",
|
|
16
|
+
"go",
|
|
17
|
+
"for",
|
|
18
|
+
"not",
|
|
19
|
+
"new",
|
|
20
|
+
"old",
|
|
21
|
+
"top",
|
|
22
|
+
"end",
|
|
23
|
+
"run",
|
|
24
|
+
"try",
|
|
25
|
+
"use",
|
|
26
|
+
"get",
|
|
27
|
+
"set",
|
|
28
|
+
"add",
|
|
29
|
+
"put",
|
|
30
|
+
"map",
|
|
31
|
+
"log",
|
|
32
|
+
"row",
|
|
33
|
+
"col",
|
|
34
|
+
"tab",
|
|
35
|
+
"box",
|
|
36
|
+
"div",
|
|
37
|
+
"nav",
|
|
38
|
+
"tag",
|
|
39
|
+
"any",
|
|
40
|
+
"all",
|
|
41
|
+
"one",
|
|
42
|
+
"two",
|
|
43
|
+
"out",
|
|
44
|
+
"off",
|
|
45
|
+
"on",
|
|
46
|
+
"yes",
|
|
47
|
+
"no",
|
|
48
|
+
"now",
|
|
49
|
+
"max",
|
|
50
|
+
"min",
|
|
51
|
+
"sum",
|
|
52
|
+
"avg",
|
|
53
|
+
"ref",
|
|
54
|
+
"src",
|
|
55
|
+
"dst",
|
|
56
|
+
"raw",
|
|
57
|
+
"def",
|
|
58
|
+
"sub",
|
|
59
|
+
"pub",
|
|
60
|
+
"pre",
|
|
61
|
+
"mid",
|
|
62
|
+
"alt",
|
|
63
|
+
"opt",
|
|
64
|
+
"tmp",
|
|
65
|
+
"ext",
|
|
66
|
+
"sep",
|
|
67
|
+
// Prepositions and conjunctions
|
|
68
|
+
"and",
|
|
69
|
+
"from",
|
|
70
|
+
"how",
|
|
71
|
+
"pad",
|
|
72
|
+
"bar",
|
|
73
|
+
"non",
|
|
74
|
+
// Additional full words commonly flagged
|
|
75
|
+
"tax",
|
|
76
|
+
"cat",
|
|
77
|
+
"dog",
|
|
78
|
+
"car",
|
|
79
|
+
"bus",
|
|
80
|
+
"web",
|
|
81
|
+
"app",
|
|
82
|
+
"war",
|
|
83
|
+
"law",
|
|
84
|
+
"pay",
|
|
85
|
+
"buy",
|
|
86
|
+
"win",
|
|
87
|
+
"cut",
|
|
88
|
+
"hit",
|
|
89
|
+
"hot",
|
|
90
|
+
"pop",
|
|
91
|
+
"job",
|
|
92
|
+
"age",
|
|
93
|
+
"act",
|
|
94
|
+
"let",
|
|
95
|
+
"lot",
|
|
96
|
+
"bad",
|
|
97
|
+
"big",
|
|
98
|
+
"far",
|
|
99
|
+
"few",
|
|
100
|
+
"own",
|
|
101
|
+
"per",
|
|
102
|
+
"red",
|
|
103
|
+
"low",
|
|
104
|
+
"see",
|
|
105
|
+
"six",
|
|
106
|
+
"ten",
|
|
107
|
+
"way",
|
|
108
|
+
"who",
|
|
109
|
+
"why",
|
|
110
|
+
"yet",
|
|
111
|
+
"via",
|
|
112
|
+
"due",
|
|
113
|
+
"fee",
|
|
114
|
+
"fun",
|
|
115
|
+
"gas",
|
|
116
|
+
"gay",
|
|
117
|
+
"god",
|
|
118
|
+
"gun",
|
|
119
|
+
"guy",
|
|
120
|
+
"ice",
|
|
121
|
+
"ill",
|
|
122
|
+
"kid",
|
|
123
|
+
"mad",
|
|
124
|
+
"man",
|
|
125
|
+
"mix",
|
|
126
|
+
"mom",
|
|
127
|
+
"mrs",
|
|
128
|
+
"nor",
|
|
129
|
+
"odd",
|
|
130
|
+
"oil",
|
|
131
|
+
"pan",
|
|
132
|
+
"pet",
|
|
133
|
+
"pit",
|
|
134
|
+
"pot",
|
|
135
|
+
"pow",
|
|
136
|
+
"pro",
|
|
137
|
+
"raw",
|
|
138
|
+
"rep",
|
|
139
|
+
"rid",
|
|
140
|
+
"sad",
|
|
141
|
+
"sea",
|
|
142
|
+
"sit",
|
|
143
|
+
"sky",
|
|
144
|
+
"son",
|
|
145
|
+
"tea",
|
|
146
|
+
"tie",
|
|
147
|
+
"tip",
|
|
148
|
+
"van",
|
|
149
|
+
"war",
|
|
150
|
+
"win",
|
|
151
|
+
"won"
|
|
152
|
+
]);
|
|
153
|
+
var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
154
|
+
// Standard identifiers
|
|
155
|
+
"id",
|
|
156
|
+
"uid",
|
|
157
|
+
"gid",
|
|
158
|
+
"pid",
|
|
159
|
+
// Loop counters and iterators
|
|
160
|
+
"i",
|
|
161
|
+
"j",
|
|
162
|
+
"k",
|
|
163
|
+
"n",
|
|
164
|
+
"m",
|
|
165
|
+
// Web/Network
|
|
166
|
+
"url",
|
|
167
|
+
"uri",
|
|
168
|
+
"api",
|
|
169
|
+
"cdn",
|
|
170
|
+
"dns",
|
|
171
|
+
"ip",
|
|
172
|
+
"tcp",
|
|
173
|
+
"udp",
|
|
174
|
+
"http",
|
|
175
|
+
"ssl",
|
|
176
|
+
"tls",
|
|
177
|
+
"utm",
|
|
178
|
+
"seo",
|
|
179
|
+
"rss",
|
|
180
|
+
"xhr",
|
|
181
|
+
"ajax",
|
|
182
|
+
"cors",
|
|
183
|
+
"ws",
|
|
184
|
+
"wss",
|
|
185
|
+
// Data formats
|
|
186
|
+
"json",
|
|
187
|
+
"xml",
|
|
188
|
+
"yaml",
|
|
189
|
+
"csv",
|
|
190
|
+
"html",
|
|
191
|
+
"css",
|
|
192
|
+
"svg",
|
|
193
|
+
"pdf",
|
|
194
|
+
// File types & extensions
|
|
195
|
+
"img",
|
|
196
|
+
"txt",
|
|
197
|
+
"doc",
|
|
198
|
+
"docx",
|
|
199
|
+
"xlsx",
|
|
200
|
+
"ppt",
|
|
201
|
+
"md",
|
|
202
|
+
"rst",
|
|
203
|
+
"jpg",
|
|
204
|
+
"png",
|
|
205
|
+
"gif",
|
|
206
|
+
// Databases
|
|
207
|
+
"db",
|
|
208
|
+
"sql",
|
|
209
|
+
"orm",
|
|
210
|
+
"dao",
|
|
211
|
+
"dto",
|
|
212
|
+
"ddb",
|
|
213
|
+
"rds",
|
|
214
|
+
"nosql",
|
|
215
|
+
// File system
|
|
216
|
+
"fs",
|
|
217
|
+
"dir",
|
|
218
|
+
"tmp",
|
|
219
|
+
"src",
|
|
220
|
+
"dst",
|
|
221
|
+
"bin",
|
|
222
|
+
"lib",
|
|
223
|
+
"pkg",
|
|
224
|
+
// Operating system
|
|
225
|
+
"os",
|
|
226
|
+
"env",
|
|
227
|
+
"arg",
|
|
228
|
+
"cli",
|
|
229
|
+
"cmd",
|
|
230
|
+
"exe",
|
|
231
|
+
"cwd",
|
|
232
|
+
"pwd",
|
|
233
|
+
// UI/UX
|
|
234
|
+
"ui",
|
|
235
|
+
"ux",
|
|
236
|
+
"gui",
|
|
237
|
+
"dom",
|
|
238
|
+
"ref",
|
|
239
|
+
// Request/Response
|
|
240
|
+
"req",
|
|
241
|
+
"res",
|
|
242
|
+
"ctx",
|
|
243
|
+
"err",
|
|
244
|
+
"msg",
|
|
245
|
+
"auth",
|
|
246
|
+
// Mathematics/Computing
|
|
247
|
+
"max",
|
|
248
|
+
"min",
|
|
249
|
+
"avg",
|
|
250
|
+
"sum",
|
|
251
|
+
"abs",
|
|
252
|
+
"cos",
|
|
253
|
+
"sin",
|
|
254
|
+
"tan",
|
|
255
|
+
"log",
|
|
256
|
+
"exp",
|
|
257
|
+
"pow",
|
|
258
|
+
"sqrt",
|
|
259
|
+
"std",
|
|
260
|
+
"var",
|
|
261
|
+
"int",
|
|
262
|
+
"num",
|
|
263
|
+
"idx",
|
|
264
|
+
// Time
|
|
265
|
+
"now",
|
|
266
|
+
"utc",
|
|
267
|
+
"tz",
|
|
268
|
+
"ms",
|
|
269
|
+
"sec",
|
|
270
|
+
"hr",
|
|
271
|
+
"min",
|
|
272
|
+
"yr",
|
|
273
|
+
"mo",
|
|
274
|
+
// Common patterns
|
|
275
|
+
"app",
|
|
276
|
+
"cfg",
|
|
277
|
+
"config",
|
|
278
|
+
"init",
|
|
279
|
+
"len",
|
|
280
|
+
"val",
|
|
281
|
+
"str",
|
|
282
|
+
"obj",
|
|
283
|
+
"arr",
|
|
284
|
+
"gen",
|
|
285
|
+
"def",
|
|
286
|
+
"raw",
|
|
287
|
+
"new",
|
|
288
|
+
"old",
|
|
289
|
+
"pre",
|
|
290
|
+
"post",
|
|
291
|
+
"sub",
|
|
292
|
+
"pub",
|
|
293
|
+
// Programming/Framework specific
|
|
294
|
+
"ts",
|
|
295
|
+
"js",
|
|
296
|
+
"jsx",
|
|
297
|
+
"tsx",
|
|
298
|
+
"py",
|
|
299
|
+
"rb",
|
|
300
|
+
"vue",
|
|
301
|
+
"re",
|
|
302
|
+
"fn",
|
|
303
|
+
"fns",
|
|
304
|
+
"mod",
|
|
305
|
+
"opts",
|
|
306
|
+
"dev",
|
|
307
|
+
// Cloud/Infrastructure
|
|
308
|
+
"s3",
|
|
309
|
+
"ec2",
|
|
310
|
+
"sqs",
|
|
311
|
+
"sns",
|
|
312
|
+
"vpc",
|
|
313
|
+
"ami",
|
|
314
|
+
"iam",
|
|
315
|
+
"acl",
|
|
316
|
+
"elb",
|
|
317
|
+
"alb",
|
|
318
|
+
"nlb",
|
|
319
|
+
"aws",
|
|
320
|
+
"ses",
|
|
321
|
+
"gst",
|
|
322
|
+
"cdk",
|
|
323
|
+
"btn",
|
|
324
|
+
"buf",
|
|
325
|
+
"agg",
|
|
326
|
+
"ocr",
|
|
327
|
+
"ai",
|
|
328
|
+
"cf",
|
|
329
|
+
"cfn",
|
|
330
|
+
"ga",
|
|
331
|
+
// Metrics/Performance
|
|
332
|
+
"fcp",
|
|
333
|
+
"lcp",
|
|
334
|
+
"cls",
|
|
335
|
+
"ttfb",
|
|
336
|
+
"tti",
|
|
337
|
+
"fid",
|
|
338
|
+
"fps",
|
|
339
|
+
"qps",
|
|
340
|
+
"rps",
|
|
341
|
+
"tps",
|
|
342
|
+
"wpm",
|
|
343
|
+
// Testing & i18n
|
|
344
|
+
"po",
|
|
345
|
+
"e2e",
|
|
346
|
+
"a11y",
|
|
347
|
+
"i18n",
|
|
348
|
+
"l10n",
|
|
349
|
+
"spy",
|
|
350
|
+
// Domain-specific abbreviations (context-aware)
|
|
351
|
+
"sk",
|
|
352
|
+
"fy",
|
|
353
|
+
"faq",
|
|
354
|
+
"og",
|
|
355
|
+
"seo",
|
|
356
|
+
"cta",
|
|
357
|
+
"roi",
|
|
358
|
+
"kpi",
|
|
359
|
+
"ttl",
|
|
360
|
+
"pct",
|
|
361
|
+
// Technical abbreviations
|
|
362
|
+
"mac",
|
|
363
|
+
"hex",
|
|
364
|
+
"esm",
|
|
365
|
+
"git",
|
|
366
|
+
"rec",
|
|
367
|
+
"loc",
|
|
368
|
+
"dup",
|
|
369
|
+
// Boolean helpers (these are intentional short names)
|
|
370
|
+
"is",
|
|
371
|
+
"has",
|
|
372
|
+
"can",
|
|
373
|
+
"did",
|
|
374
|
+
"was",
|
|
375
|
+
"are",
|
|
376
|
+
// Date/Time context (when in date contexts)
|
|
377
|
+
"d",
|
|
378
|
+
"t",
|
|
379
|
+
"dt",
|
|
380
|
+
// Coverage metrics (industry standard: statements/branches/functions/lines)
|
|
381
|
+
"s",
|
|
382
|
+
"b",
|
|
383
|
+
"f",
|
|
384
|
+
"l",
|
|
385
|
+
// Common media/content abbreviations
|
|
386
|
+
"vid",
|
|
387
|
+
"pic",
|
|
388
|
+
"img",
|
|
389
|
+
"doc",
|
|
390
|
+
"msg"
|
|
391
|
+
]);
|
|
392
|
+
async function analyzeNaming(files) {
|
|
393
|
+
const issues = [];
|
|
394
|
+
const rootDir = files.length > 0 ? dirname(files[0]) : process.cwd();
|
|
395
|
+
const config = loadConfig(rootDir);
|
|
396
|
+
const consistencyConfig = config?.tools?.["consistency"];
|
|
397
|
+
const customAbbreviations = new Set(consistencyConfig?.acceptedAbbreviations || []);
|
|
398
|
+
const customShortWords = new Set(consistencyConfig?.shortWords || []);
|
|
399
|
+
const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
|
|
400
|
+
for (const file of files) {
|
|
401
|
+
const content = await readFileContent(file);
|
|
402
|
+
const fileIssues = analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks);
|
|
403
|
+
issues.push(...fileIssues);
|
|
404
|
+
}
|
|
405
|
+
return issues;
|
|
406
|
+
}
|
|
407
|
+
function analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks) {
|
|
408
|
+
const issues = [];
|
|
409
|
+
const isTestFile = file.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/);
|
|
410
|
+
const lines = content.split("\n");
|
|
411
|
+
const allAbbreviations = /* @__PURE__ */ new Set([...ACCEPTABLE_ABBREVIATIONS, ...customAbbreviations]);
|
|
412
|
+
const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
|
|
413
|
+
const getContextWindow = (index, windowSize = 3) => {
|
|
414
|
+
const start = Math.max(0, index - windowSize);
|
|
415
|
+
const end = Math.min(lines.length, index + windowSize + 1);
|
|
416
|
+
return lines.slice(start, end).join("\n");
|
|
417
|
+
};
|
|
418
|
+
const isShortLivedVariable = (varName, declarationIndex) => {
|
|
419
|
+
const searchRange = 5;
|
|
420
|
+
const endIndex = Math.min(lines.length, declarationIndex + searchRange + 1);
|
|
421
|
+
let usageCount = 0;
|
|
422
|
+
for (let i = declarationIndex; i < endIndex; i++) {
|
|
423
|
+
const regex = new RegExp(`\\b${varName}\\b`, "g");
|
|
424
|
+
const matches = lines[i].match(regex);
|
|
425
|
+
if (matches) {
|
|
426
|
+
usageCount += matches.length;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return usageCount >= 2 && usageCount <= 3;
|
|
430
|
+
};
|
|
431
|
+
lines.forEach((line, index) => {
|
|
432
|
+
const lineNumber = index + 1;
|
|
433
|
+
const contextWindow = getContextWindow(index);
|
|
434
|
+
if (!disabledChecks.has("single-letter")) {
|
|
435
|
+
const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
|
|
436
|
+
for (const match of singleLetterMatches) {
|
|
437
|
+
const letter = match[1].toLowerCase();
|
|
438
|
+
const isCoverageContext = /coverage|summary|metrics|pct|percent/i.test(line) || /\.(?:statements|branches|functions|lines)\.pct/i.test(line);
|
|
439
|
+
if (isCoverageContext && ["s", "b", "f", "l"].includes(letter)) {
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
const isInLoopContext = line.includes("for") || /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) || line.includes("=>") || // Arrow function
|
|
443
|
+
/\w+\s*=>\s*/.test(line);
|
|
444
|
+
const isI18nContext = line.includes("useTranslation") || line.includes("i18n.t") || /\bt\s*\(['"]/.test(line);
|
|
445
|
+
const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
446
|
+
/[a-z]\s*=>/.test(line) || // s => on same line
|
|
447
|
+
// Multi-line arrow function detection: look for pattern in context window
|
|
448
|
+
new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
|
|
449
|
+
new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && /=>/.test(contextWindow);
|
|
450
|
+
const isShortLived = isShortLivedVariable(letter, index);
|
|
451
|
+
if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
|
|
452
|
+
if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
issues.push({
|
|
456
|
+
file,
|
|
457
|
+
line: lineNumber,
|
|
458
|
+
type: "poor-naming",
|
|
459
|
+
identifier: match[1],
|
|
460
|
+
severity: "minor",
|
|
461
|
+
suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (!disabledChecks.has("abbreviation")) {
|
|
467
|
+
const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
|
|
468
|
+
for (const match of abbreviationMatches) {
|
|
469
|
+
const abbrev = match[1].toLowerCase();
|
|
470
|
+
if (allShortWords.has(abbrev)) {
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
if (allAbbreviations.has(abbrev)) {
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
477
|
+
new RegExp(`\\b${abbrev}\\s*=>`).test(line) || // s => on same line
|
|
478
|
+
// Multi-line arrow function: check context window
|
|
479
|
+
new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
|
|
480
|
+
new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && new RegExp(`^\\s*${abbrev}\\s*=>`).test(line);
|
|
481
|
+
if (isArrowFunctionParam) {
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (abbrev.length <= 2) {
|
|
485
|
+
const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
|
|
486
|
+
if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
const isUserContext = /user|auth|account/i.test(line);
|
|
490
|
+
if (isUserContext && abbrev === "u") {
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
issues.push({
|
|
495
|
+
file,
|
|
496
|
+
line: lineNumber,
|
|
497
|
+
type: "abbreviation",
|
|
498
|
+
identifier: match[1],
|
|
499
|
+
severity: "info",
|
|
500
|
+
suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
505
|
+
const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
|
|
506
|
+
const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
|
|
507
|
+
if (snakeCaseVars) {
|
|
508
|
+
issues.push({
|
|
509
|
+
file,
|
|
510
|
+
line: lineNumber,
|
|
511
|
+
type: "convention-mix",
|
|
512
|
+
identifier: snakeCaseVars[1],
|
|
513
|
+
severity: "minor",
|
|
514
|
+
suggestion: `Use camelCase '${snakeCaseToCamelCase(snakeCaseVars[1])}' instead of snake_case in TypeScript/JavaScript`
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (!disabledChecks.has("unclear")) {
|
|
519
|
+
const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
|
|
520
|
+
for (const match of booleanMatches) {
|
|
521
|
+
const name = match[1];
|
|
522
|
+
if (!name.match(/^(is|has|should|can|will|did)/i)) {
|
|
523
|
+
issues.push({
|
|
524
|
+
file,
|
|
525
|
+
line: lineNumber,
|
|
526
|
+
type: "unclear",
|
|
527
|
+
identifier: name,
|
|
528
|
+
severity: "info",
|
|
529
|
+
suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (!disabledChecks.has("unclear")) {
|
|
535
|
+
const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
|
|
536
|
+
for (const match of functionMatches) {
|
|
537
|
+
const name = match[1];
|
|
538
|
+
const isKeyword = ["for", "if", "else", "while", "do", "switch", "case", "break", "continue", "return", "throw", "try", "catch", "finally", "with", "yield", "await"].includes(name);
|
|
539
|
+
if (isKeyword) {
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(name);
|
|
543
|
+
if (isEntryPoint) {
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/);
|
|
547
|
+
const isEventHandler = name.match(/^on[A-Z]/);
|
|
548
|
+
const isDescriptiveLong = name.length > 15;
|
|
549
|
+
const isReactHook = name.match(/^use[A-Z]/);
|
|
550
|
+
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)$/);
|
|
551
|
+
const isHelperPattern = name.match(/^(to|from|with|without|for|as|into)\w+/) || // toMetadata, withLogger, forPath
|
|
552
|
+
name.match(/^\w+(To|From|With|Without|For|As|Into)\w*$/);
|
|
553
|
+
const isUtilityName = ["cn", "proxy", "sitemap", "robots", "gtag"].includes(name);
|
|
554
|
+
const capitalCount = (name.match(/[A-Z]/g) || []).length;
|
|
555
|
+
const isCompoundWord = capitalCount >= 3;
|
|
556
|
+
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)/);
|
|
557
|
+
if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord && !isHelperPattern && !isUtilityName && !isReactHook) {
|
|
558
|
+
issues.push({
|
|
559
|
+
file,
|
|
560
|
+
line: lineNumber,
|
|
561
|
+
type: "unclear",
|
|
562
|
+
identifier: name,
|
|
563
|
+
severity: "info",
|
|
564
|
+
suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
return issues;
|
|
571
|
+
}
|
|
572
|
+
function snakeCaseToCamelCase(str) {
|
|
573
|
+
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
574
|
+
}
|
|
575
|
+
function detectNamingConventions(files, allIssues) {
|
|
576
|
+
const camelCaseCount = allIssues.filter((i) => i.type === "convention-mix").length;
|
|
577
|
+
const totalChecks = files.length * 10;
|
|
578
|
+
if (camelCaseCount / totalChecks > 0.3) {
|
|
579
|
+
return { dominantConvention: "mixed", conventionScore: 0.5 };
|
|
580
|
+
}
|
|
581
|
+
return { dominantConvention: "camelCase", conventionScore: 0.9 };
|
|
582
|
+
}
|
|
7
583
|
export {
|
|
8
584
|
analyzeConsistency,
|
|
9
585
|
analyzeNaming,
|
|
586
|
+
analyzeNamingAST,
|
|
10
587
|
analyzePatterns,
|
|
11
588
|
detectNamingConventions
|
|
12
589
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/consistency",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Detects consistency issues in naming, patterns, and architecture that confuse AI models",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -15,15 +15,6 @@
|
|
|
15
15
|
"import": "./dist/index.mjs"
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
|
-
"scripts": {
|
|
19
|
-
"build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts",
|
|
20
|
-
"dev": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --watch",
|
|
21
|
-
"test": "vitest run",
|
|
22
|
-
"lint": "eslint src",
|
|
23
|
-
"clean": "rm -rf dist",
|
|
24
|
-
"prepublishOnly": "pnpm build",
|
|
25
|
-
"release": "pnpm build && pnpm publish --no-git-checks"
|
|
26
|
-
},
|
|
27
18
|
"keywords": [
|
|
28
19
|
"aiready",
|
|
29
20
|
"consistency",
|
|
@@ -48,9 +39,11 @@
|
|
|
48
39
|
},
|
|
49
40
|
"homepage": "https://github.com/caopengau/aiready-consistency",
|
|
50
41
|
"dependencies": {
|
|
51
|
-
"@
|
|
42
|
+
"@typescript-eslint/types": "^8.53.0",
|
|
43
|
+
"@typescript-eslint/typescript-estree": "^8.53.0",
|
|
52
44
|
"chalk": "^5.3.0",
|
|
53
|
-
"commander": "^12.1.0"
|
|
45
|
+
"commander": "^12.1.0",
|
|
46
|
+
"@aiready/core": "0.7.1"
|
|
54
47
|
},
|
|
55
48
|
"devDependencies": {
|
|
56
49
|
"@types/node": "^22.10.5",
|
|
@@ -60,5 +53,13 @@
|
|
|
60
53
|
},
|
|
61
54
|
"engines": {
|
|
62
55
|
"node": ">=18.0.0"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts",
|
|
59
|
+
"dev": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --watch",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"lint": "eslint src",
|
|
62
|
+
"clean": "rm -rf dist",
|
|
63
|
+
"release": "pnpm build && pnpm publish --no-git-checks"
|
|
63
64
|
}
|
|
64
|
-
}
|
|
65
|
+
}
|
package/src/analyzer.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { scanFiles } from '@aiready/core';
|
|
2
2
|
import type { AnalysisResult, Issue } from '@aiready/core';
|
|
3
3
|
import type { ConsistencyOptions, ConsistencyReport, ConsistencyIssue } from './types';
|
|
4
|
-
import {
|
|
4
|
+
import { analyzeNamingAST } from './analyzers/naming-ast';
|
|
5
5
|
import { analyzePatterns } from './analyzers/patterns';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -22,7 +22,7 @@ export async function analyzeConsistency(
|
|
|
22
22
|
const filePaths = await scanFiles(scanOptions);
|
|
23
23
|
|
|
24
24
|
// Collect issues by category
|
|
25
|
-
const namingIssues = checkNaming ? await
|
|
25
|
+
const namingIssues = checkNaming ? await analyzeNamingAST(filePaths) : [];
|
|
26
26
|
const patternIssues = checkPatterns ? await analyzePatterns(filePaths) : [];
|
|
27
27
|
|
|
28
28
|
// Convert to AnalysisResult format
|
|
@@ -97,8 +97,8 @@ export async function analyzeConsistency(
|
|
|
97
97
|
// Generate recommendations
|
|
98
98
|
const recommendations = generateRecommendations(namingIssues, patternIssues);
|
|
99
99
|
|
|
100
|
-
// Detect naming conventions
|
|
101
|
-
const conventionAnalysis = detectNamingConventions(filePaths, namingIssues);
|
|
100
|
+
// Detect naming conventions (TODO: re-implement for AST version)
|
|
101
|
+
// const conventionAnalysis = detectNamingConventions(filePaths, namingIssues);
|
|
102
102
|
|
|
103
103
|
return {
|
|
104
104
|
summary: {
|