@aiready/consistency 0.3.4 → 0.4.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 +81 -0
- package/PHASE4-RESULTS.md +122 -0
- package/README.md +55 -7
- package/dist/chunk-2BTBNG6X.mjs +814 -0
- package/dist/chunk-CZUJTDNH.mjs +848 -0
- package/dist/cli.js +168 -91
- package/dist/cli.mjs +1 -1
- package/dist/index.js +165 -88
- package/dist/index.mjs +1 -1
- package/package.json +12 -13
- package/src/__tests__/analyzer.test.ts +27 -0
- package/src/analyzers/naming.ts +226 -142
package/dist/cli.js
CHANGED
|
@@ -31,6 +31,7 @@ var import_core3 = require("@aiready/core");
|
|
|
31
31
|
|
|
32
32
|
// src/analyzers/naming.ts
|
|
33
33
|
var import_core = require("@aiready/core");
|
|
34
|
+
var import_path = require("path");
|
|
34
35
|
var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
|
|
35
36
|
// Full English words (1-3 letters)
|
|
36
37
|
"day",
|
|
@@ -88,6 +89,13 @@ var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
|
|
|
88
89
|
"tmp",
|
|
89
90
|
"ext",
|
|
90
91
|
"sep",
|
|
92
|
+
// Prepositions and conjunctions
|
|
93
|
+
"and",
|
|
94
|
+
"from",
|
|
95
|
+
"how",
|
|
96
|
+
"pad",
|
|
97
|
+
"bar",
|
|
98
|
+
"non",
|
|
91
99
|
// Additional full words commonly flagged
|
|
92
100
|
"tax",
|
|
93
101
|
"cat",
|
|
@@ -334,6 +342,17 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
334
342
|
"alb",
|
|
335
343
|
"nlb",
|
|
336
344
|
"aws",
|
|
345
|
+
"ses",
|
|
346
|
+
"gst",
|
|
347
|
+
"cdk",
|
|
348
|
+
"btn",
|
|
349
|
+
"buf",
|
|
350
|
+
"agg",
|
|
351
|
+
"ocr",
|
|
352
|
+
"ai",
|
|
353
|
+
"cf",
|
|
354
|
+
"cfn",
|
|
355
|
+
"ga",
|
|
337
356
|
// Metrics/Performance
|
|
338
357
|
"fcp",
|
|
339
358
|
"lcp",
|
|
@@ -345,12 +364,14 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
345
364
|
"qps",
|
|
346
365
|
"rps",
|
|
347
366
|
"tps",
|
|
367
|
+
"wpm",
|
|
348
368
|
// Testing & i18n
|
|
349
369
|
"po",
|
|
350
370
|
"e2e",
|
|
351
371
|
"a11y",
|
|
352
372
|
"i18n",
|
|
353
373
|
"l10n",
|
|
374
|
+
"spy",
|
|
354
375
|
// Domain-specific abbreviations (context-aware)
|
|
355
376
|
"sk",
|
|
356
377
|
"fy",
|
|
@@ -360,6 +381,16 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
360
381
|
"cta",
|
|
361
382
|
"roi",
|
|
362
383
|
"kpi",
|
|
384
|
+
"ttl",
|
|
385
|
+
"pct",
|
|
386
|
+
// Technical abbreviations
|
|
387
|
+
"mac",
|
|
388
|
+
"hex",
|
|
389
|
+
"esm",
|
|
390
|
+
"git",
|
|
391
|
+
"rec",
|
|
392
|
+
"loc",
|
|
393
|
+
"dup",
|
|
363
394
|
// Boolean helpers (these are intentional short names)
|
|
364
395
|
"is",
|
|
365
396
|
"has",
|
|
@@ -374,75 +405,113 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
374
405
|
]);
|
|
375
406
|
async function analyzeNaming(files) {
|
|
376
407
|
const issues = [];
|
|
408
|
+
const rootDir = files.length > 0 ? (0, import_path.dirname)(files[0]) : process.cwd();
|
|
409
|
+
const config = (0, import_core.loadConfig)(rootDir);
|
|
410
|
+
const consistencyConfig = config?.tools?.["consistency"];
|
|
411
|
+
const customAbbreviations = new Set(consistencyConfig?.acceptedAbbreviations || []);
|
|
412
|
+
const customShortWords = new Set(consistencyConfig?.shortWords || []);
|
|
413
|
+
const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
|
|
377
414
|
for (const file of files) {
|
|
378
415
|
const content = await (0, import_core.readFileContent)(file);
|
|
379
|
-
const fileIssues = analyzeFileNaming(file, content);
|
|
416
|
+
const fileIssues = analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks);
|
|
380
417
|
issues.push(...fileIssues);
|
|
381
418
|
}
|
|
382
419
|
return issues;
|
|
383
420
|
}
|
|
384
|
-
function analyzeFileNaming(file, content) {
|
|
421
|
+
function analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks) {
|
|
385
422
|
const issues = [];
|
|
386
423
|
const isTestFile = file.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/);
|
|
387
424
|
const lines = content.split("\n");
|
|
425
|
+
const allAbbreviations = /* @__PURE__ */ new Set([...ACCEPTABLE_ABBREVIATIONS, ...customAbbreviations]);
|
|
426
|
+
const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
|
|
427
|
+
const getContextWindow = (index, windowSize = 3) => {
|
|
428
|
+
const start = Math.max(0, index - windowSize);
|
|
429
|
+
const end = Math.min(lines.length, index + windowSize + 1);
|
|
430
|
+
return lines.slice(start, end).join("\n");
|
|
431
|
+
};
|
|
432
|
+
const isShortLivedVariable = (varName, declarationIndex) => {
|
|
433
|
+
const searchRange = 5;
|
|
434
|
+
const endIndex = Math.min(lines.length, declarationIndex + searchRange + 1);
|
|
435
|
+
let usageCount = 0;
|
|
436
|
+
for (let i = declarationIndex; i < endIndex; i++) {
|
|
437
|
+
const regex = new RegExp(`\\b${varName}\\b`, "g");
|
|
438
|
+
const matches = lines[i].match(regex);
|
|
439
|
+
if (matches) {
|
|
440
|
+
usageCount += matches.length;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return usageCount >= 2 && usageCount <= 3;
|
|
444
|
+
};
|
|
388
445
|
lines.forEach((line, index) => {
|
|
389
446
|
const lineNumber = index + 1;
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
447
|
+
const contextWindow = getContextWindow(index);
|
|
448
|
+
if (!disabledChecks.has("single-letter")) {
|
|
449
|
+
const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
|
|
450
|
+
for (const match of singleLetterMatches) {
|
|
451
|
+
const letter = match[1].toLowerCase();
|
|
452
|
+
const isInLoopContext = line.includes("for") || /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) || line.includes("=>") || // Arrow function
|
|
453
|
+
/\w+\s*=>\s*/.test(line);
|
|
454
|
+
const isI18nContext = line.includes("useTranslation") || line.includes("i18n.t") || /\bt\s*\(['"]/.test(line);
|
|
455
|
+
const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
456
|
+
/[a-z]\s*=>/.test(line) || // s => on same line
|
|
457
|
+
// Multi-line arrow function detection: look for pattern in context window
|
|
458
|
+
new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
|
|
459
|
+
new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && /=>/.test(contextWindow);
|
|
460
|
+
const isShortLived = isShortLivedVariable(letter, index);
|
|
461
|
+
if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
|
|
462
|
+
if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
issues.push({
|
|
466
|
+
file,
|
|
467
|
+
line: lineNumber,
|
|
468
|
+
type: "poor-naming",
|
|
469
|
+
identifier: match[1],
|
|
470
|
+
severity: "minor",
|
|
471
|
+
suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
|
|
472
|
+
});
|
|
401
473
|
}
|
|
402
|
-
issues.push({
|
|
403
|
-
file,
|
|
404
|
-
line: lineNumber,
|
|
405
|
-
type: "poor-naming",
|
|
406
|
-
identifier: match[1],
|
|
407
|
-
severity: "minor",
|
|
408
|
-
suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
|
|
409
|
-
});
|
|
410
474
|
}
|
|
411
475
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
421
|
-
const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
422
|
-
new RegExp(`\\b${abbrev}\\s*=>`).test(line);
|
|
423
|
-
if (isArrowFunctionParam) {
|
|
424
|
-
continue;
|
|
425
|
-
}
|
|
426
|
-
if (abbrev.length <= 2) {
|
|
427
|
-
const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
|
|
428
|
-
if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
|
|
476
|
+
if (!disabledChecks.has("abbreviation")) {
|
|
477
|
+
const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
|
|
478
|
+
for (const match of abbreviationMatches) {
|
|
479
|
+
const abbrev = match[1].toLowerCase();
|
|
480
|
+
if (allShortWords.has(abbrev)) {
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
if (allAbbreviations.has(abbrev)) {
|
|
429
484
|
continue;
|
|
430
485
|
}
|
|
431
|
-
const
|
|
432
|
-
|
|
486
|
+
const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
487
|
+
new RegExp(`\\b${abbrev}\\s*=>`).test(line) || // s => on same line
|
|
488
|
+
// Multi-line arrow function: check context window
|
|
489
|
+
new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
|
|
490
|
+
new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && new RegExp(`^\\s*${abbrev}\\s*=>`).test(line);
|
|
491
|
+
if (isArrowFunctionParam) {
|
|
433
492
|
continue;
|
|
434
493
|
}
|
|
494
|
+
if (abbrev.length <= 2) {
|
|
495
|
+
const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
|
|
496
|
+
if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
const isUserContext = /user|auth|account/i.test(line);
|
|
500
|
+
if (isUserContext && abbrev === "u") {
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
issues.push({
|
|
505
|
+
file,
|
|
506
|
+
line: lineNumber,
|
|
507
|
+
type: "abbreviation",
|
|
508
|
+
identifier: match[1],
|
|
509
|
+
severity: "info",
|
|
510
|
+
suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
|
|
511
|
+
});
|
|
435
512
|
}
|
|
436
|
-
issues.push({
|
|
437
|
-
file,
|
|
438
|
-
line: lineNumber,
|
|
439
|
-
type: "abbreviation",
|
|
440
|
-
identifier: match[1],
|
|
441
|
-
severity: "info",
|
|
442
|
-
suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
|
|
443
|
-
});
|
|
444
513
|
}
|
|
445
|
-
if (file.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
514
|
+
if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
446
515
|
const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
|
|
447
516
|
const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
|
|
448
517
|
if (snakeCaseVars) {
|
|
@@ -456,47 +525,55 @@ function analyzeFileNaming(file, content) {
|
|
|
456
525
|
});
|
|
457
526
|
}
|
|
458
527
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
528
|
+
if (!disabledChecks.has("unclear")) {
|
|
529
|
+
const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
|
|
530
|
+
for (const match of booleanMatches) {
|
|
531
|
+
const name = match[1];
|
|
532
|
+
if (!name.match(/^(is|has|should|can|will|did)/i)) {
|
|
533
|
+
issues.push({
|
|
534
|
+
file,
|
|
535
|
+
line: lineNumber,
|
|
536
|
+
type: "unclear",
|
|
537
|
+
identifier: name,
|
|
538
|
+
severity: "info",
|
|
539
|
+
suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
|
|
540
|
+
});
|
|
541
|
+
}
|
|
471
542
|
}
|
|
472
543
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
544
|
+
if (!disabledChecks.has("unclear")) {
|
|
545
|
+
const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
|
|
546
|
+
for (const match of functionMatches) {
|
|
547
|
+
const name = match[1];
|
|
548
|
+
const isKeyword = ["for", "if", "else", "while", "do", "switch", "case", "break", "continue", "return", "throw", "try", "catch", "finally", "with", "yield", "await"].includes(name);
|
|
549
|
+
if (isKeyword) {
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(name);
|
|
553
|
+
if (isEntryPoint) {
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator|Provider|Adapter|Mock)$/);
|
|
557
|
+
const isEventHandler = name.match(/^on[A-Z]/);
|
|
558
|
+
const isDescriptiveLong = name.length > 15;
|
|
559
|
+
const isReactHook = name.match(/^use[A-Z]/);
|
|
560
|
+
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)$/);
|
|
561
|
+
const isHelperPattern = name.match(/^(to|from|with|without|for|as|into)\w+/) || // toMetadata, withLogger, forPath
|
|
562
|
+
name.match(/^\w+(To|From|With|Without|For|As|Into)\w*$/);
|
|
563
|
+
const isUtilityName = ["cn", "proxy", "sitemap", "robots", "gtag"].includes(name);
|
|
564
|
+
const capitalCount = (name.match(/[A-Z]/g) || []).length;
|
|
565
|
+
const isCompoundWord = capitalCount >= 3;
|
|
566
|
+
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)/);
|
|
567
|
+
if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord && !isHelperPattern && !isUtilityName && !isReactHook) {
|
|
568
|
+
issues.push({
|
|
569
|
+
file,
|
|
570
|
+
line: lineNumber,
|
|
571
|
+
type: "unclear",
|
|
572
|
+
identifier: name,
|
|
573
|
+
severity: "info",
|
|
574
|
+
suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
|
|
575
|
+
});
|
|
576
|
+
}
|
|
500
577
|
}
|
|
501
578
|
}
|
|
502
579
|
});
|
|
@@ -796,7 +873,7 @@ function generateRecommendations(namingIssues, patternIssues) {
|
|
|
796
873
|
// src/cli.ts
|
|
797
874
|
var import_chalk = __toESM(require("chalk"));
|
|
798
875
|
var import_fs = require("fs");
|
|
799
|
-
var
|
|
876
|
+
var import_path2 = require("path");
|
|
800
877
|
var import_core4 = require("@aiready/core");
|
|
801
878
|
var program = new import_commander.Command();
|
|
802
879
|
program.name("aiready-consistency").description("Detect consistency issues in naming, patterns, and architecture").version("0.1.0").addHelpText("after", `
|
|
@@ -845,7 +922,7 @@ EXAMPLES:
|
|
|
845
922
|
`consistency-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
|
|
846
923
|
directory
|
|
847
924
|
);
|
|
848
|
-
const dir = (0,
|
|
925
|
+
const dir = (0, import_path2.dirname)(outputPath);
|
|
849
926
|
if (!(0, import_fs.existsSync)(dir)) {
|
|
850
927
|
(0, import_fs.mkdirSync)(dir, { recursive: true });
|
|
851
928
|
}
|
|
@@ -858,7 +935,7 @@ EXAMPLES:
|
|
|
858
935
|
`consistency-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.md`,
|
|
859
936
|
directory
|
|
860
937
|
);
|
|
861
|
-
const dir = (0,
|
|
938
|
+
const dir = (0, import_path2.dirname)(outputPath);
|
|
862
939
|
if (!(0, import_fs.existsSync)(dir)) {
|
|
863
940
|
(0, import_fs.mkdirSync)(dir, { recursive: true });
|
|
864
941
|
}
|