@aiready/consistency 0.3.4 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +24 -0
- package/.turbo/turbo-test.log +81 -0
- package/README.md +55 -7
- package/dist/chunk-2BTBNG6X.mjs +814 -0
- package/dist/cli.js +134 -91
- package/dist/cli.mjs +1 -1
- package/dist/index.js +131 -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 +208 -137
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",
|
|
@@ -374,75 +375,113 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
374
375
|
]);
|
|
375
376
|
async function analyzeNaming(files) {
|
|
376
377
|
const issues = [];
|
|
378
|
+
const rootDir = files.length > 0 ? (0, import_path.dirname)(files[0]) : process.cwd();
|
|
379
|
+
const config = (0, import_core.loadConfig)(rootDir);
|
|
380
|
+
const consistencyConfig = config?.tools?.["consistency"];
|
|
381
|
+
const customAbbreviations = new Set(consistencyConfig?.acceptedAbbreviations || []);
|
|
382
|
+
const customShortWords = new Set(consistencyConfig?.shortWords || []);
|
|
383
|
+
const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
|
|
377
384
|
for (const file of files) {
|
|
378
385
|
const content = await (0, import_core.readFileContent)(file);
|
|
379
|
-
const fileIssues = analyzeFileNaming(file, content);
|
|
386
|
+
const fileIssues = analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks);
|
|
380
387
|
issues.push(...fileIssues);
|
|
381
388
|
}
|
|
382
389
|
return issues;
|
|
383
390
|
}
|
|
384
|
-
function analyzeFileNaming(file, content) {
|
|
391
|
+
function analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks) {
|
|
385
392
|
const issues = [];
|
|
386
393
|
const isTestFile = file.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/);
|
|
387
394
|
const lines = content.split("\n");
|
|
395
|
+
const allAbbreviations = /* @__PURE__ */ new Set([...ACCEPTABLE_ABBREVIATIONS, ...customAbbreviations]);
|
|
396
|
+
const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
|
|
397
|
+
const getContextWindow = (index, windowSize = 3) => {
|
|
398
|
+
const start = Math.max(0, index - windowSize);
|
|
399
|
+
const end = Math.min(lines.length, index + windowSize + 1);
|
|
400
|
+
return lines.slice(start, end).join("\n");
|
|
401
|
+
};
|
|
402
|
+
const isShortLivedVariable = (varName, declarationIndex) => {
|
|
403
|
+
const searchRange = 5;
|
|
404
|
+
const endIndex = Math.min(lines.length, declarationIndex + searchRange + 1);
|
|
405
|
+
let usageCount = 0;
|
|
406
|
+
for (let i = declarationIndex; i < endIndex; i++) {
|
|
407
|
+
const regex = new RegExp(`\\b${varName}\\b`, "g");
|
|
408
|
+
const matches = lines[i].match(regex);
|
|
409
|
+
if (matches) {
|
|
410
|
+
usageCount += matches.length;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return usageCount >= 2 && usageCount <= 3;
|
|
414
|
+
};
|
|
388
415
|
lines.forEach((line, index) => {
|
|
389
416
|
const lineNumber = index + 1;
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
417
|
+
const contextWindow = getContextWindow(index);
|
|
418
|
+
if (!disabledChecks.has("single-letter")) {
|
|
419
|
+
const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
|
|
420
|
+
for (const match of singleLetterMatches) {
|
|
421
|
+
const letter = match[1].toLowerCase();
|
|
422
|
+
const isInLoopContext = line.includes("for") || /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) || line.includes("=>") || // Arrow function
|
|
423
|
+
/\w+\s*=>\s*/.test(line);
|
|
424
|
+
const isI18nContext = line.includes("useTranslation") || line.includes("i18n.t") || /\bt\s*\(['"]/.test(line);
|
|
425
|
+
const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
426
|
+
/[a-z]\s*=>/.test(line) || // s => on same line
|
|
427
|
+
// Multi-line arrow function detection: look for pattern in context window
|
|
428
|
+
new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
|
|
429
|
+
new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && /=>/.test(contextWindow);
|
|
430
|
+
const isShortLived = isShortLivedVariable(letter, index);
|
|
431
|
+
if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
|
|
432
|
+
if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
issues.push({
|
|
436
|
+
file,
|
|
437
|
+
line: lineNumber,
|
|
438
|
+
type: "poor-naming",
|
|
439
|
+
identifier: match[1],
|
|
440
|
+
severity: "minor",
|
|
441
|
+
suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
|
|
442
|
+
});
|
|
401
443
|
}
|
|
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
444
|
}
|
|
411
445
|
}
|
|
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)) {
|
|
446
|
+
if (!disabledChecks.has("abbreviation")) {
|
|
447
|
+
const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
|
|
448
|
+
for (const match of abbreviationMatches) {
|
|
449
|
+
const abbrev = match[1].toLowerCase();
|
|
450
|
+
if (allShortWords.has(abbrev)) {
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
if (allAbbreviations.has(abbrev)) {
|
|
429
454
|
continue;
|
|
430
455
|
}
|
|
431
|
-
const
|
|
432
|
-
|
|
456
|
+
const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
457
|
+
new RegExp(`\\b${abbrev}\\s*=>`).test(line) || // s => on same line
|
|
458
|
+
// Multi-line arrow function: check context window
|
|
459
|
+
new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
|
|
460
|
+
new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && new RegExp(`^\\s*${abbrev}\\s*=>`).test(line);
|
|
461
|
+
if (isArrowFunctionParam) {
|
|
433
462
|
continue;
|
|
434
463
|
}
|
|
464
|
+
if (abbrev.length <= 2) {
|
|
465
|
+
const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
|
|
466
|
+
if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
const isUserContext = /user|auth|account/i.test(line);
|
|
470
|
+
if (isUserContext && abbrev === "u") {
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
issues.push({
|
|
475
|
+
file,
|
|
476
|
+
line: lineNumber,
|
|
477
|
+
type: "abbreviation",
|
|
478
|
+
identifier: match[1],
|
|
479
|
+
severity: "info",
|
|
480
|
+
suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
|
|
481
|
+
});
|
|
435
482
|
}
|
|
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
483
|
}
|
|
445
|
-
if (file.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
484
|
+
if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
446
485
|
const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
|
|
447
486
|
const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
|
|
448
487
|
if (snakeCaseVars) {
|
|
@@ -456,47 +495,51 @@ function analyzeFileNaming(file, content) {
|
|
|
456
495
|
});
|
|
457
496
|
}
|
|
458
497
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
498
|
+
if (!disabledChecks.has("unclear")) {
|
|
499
|
+
const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
|
|
500
|
+
for (const match of booleanMatches) {
|
|
501
|
+
const name = match[1];
|
|
502
|
+
if (!name.match(/^(is|has|should|can|will|did)/i)) {
|
|
503
|
+
issues.push({
|
|
504
|
+
file,
|
|
505
|
+
line: lineNumber,
|
|
506
|
+
type: "unclear",
|
|
507
|
+
identifier: name,
|
|
508
|
+
severity: "info",
|
|
509
|
+
suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
|
|
510
|
+
});
|
|
511
|
+
}
|
|
471
512
|
}
|
|
472
513
|
}
|
|
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
|
-
|
|
514
|
+
if (!disabledChecks.has("unclear")) {
|
|
515
|
+
const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
|
|
516
|
+
for (const match of functionMatches) {
|
|
517
|
+
const name = match[1];
|
|
518
|
+
const isKeyword = ["for", "if", "else", "while", "do", "switch", "case", "break", "continue", "return", "throw", "try", "catch", "finally", "with", "yield", "await"].includes(name);
|
|
519
|
+
if (isKeyword) {
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(name);
|
|
523
|
+
if (isEntryPoint) {
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator)$/);
|
|
527
|
+
const isEventHandler = name.match(/^on[A-Z]/);
|
|
528
|
+
const isDescriptiveLong = name.length > 15;
|
|
529
|
+
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)$/);
|
|
530
|
+
const capitalCount = (name.match(/[A-Z]/g) || []).length;
|
|
531
|
+
const isCompoundWord = capitalCount >= 3;
|
|
532
|
+
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)/);
|
|
533
|
+
if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord) {
|
|
534
|
+
issues.push({
|
|
535
|
+
file,
|
|
536
|
+
line: lineNumber,
|
|
537
|
+
type: "unclear",
|
|
538
|
+
identifier: name,
|
|
539
|
+
severity: "info",
|
|
540
|
+
suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
|
|
541
|
+
});
|
|
542
|
+
}
|
|
500
543
|
}
|
|
501
544
|
}
|
|
502
545
|
});
|
|
@@ -796,7 +839,7 @@ function generateRecommendations(namingIssues, patternIssues) {
|
|
|
796
839
|
// src/cli.ts
|
|
797
840
|
var import_chalk = __toESM(require("chalk"));
|
|
798
841
|
var import_fs = require("fs");
|
|
799
|
-
var
|
|
842
|
+
var import_path2 = require("path");
|
|
800
843
|
var import_core4 = require("@aiready/core");
|
|
801
844
|
var program = new import_commander.Command();
|
|
802
845
|
program.name("aiready-consistency").description("Detect consistency issues in naming, patterns, and architecture").version("0.1.0").addHelpText("after", `
|
|
@@ -845,7 +888,7 @@ EXAMPLES:
|
|
|
845
888
|
`consistency-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`,
|
|
846
889
|
directory
|
|
847
890
|
);
|
|
848
|
-
const dir = (0,
|
|
891
|
+
const dir = (0, import_path2.dirname)(outputPath);
|
|
849
892
|
if (!(0, import_fs.existsSync)(dir)) {
|
|
850
893
|
(0, import_fs.mkdirSync)(dir, { recursive: true });
|
|
851
894
|
}
|
|
@@ -858,7 +901,7 @@ EXAMPLES:
|
|
|
858
901
|
`consistency-report-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.md`,
|
|
859
902
|
directory
|
|
860
903
|
);
|
|
861
|
-
const dir = (0,
|
|
904
|
+
const dir = (0, import_path2.dirname)(outputPath);
|
|
862
905
|
if (!(0, import_fs.existsSync)(dir)) {
|
|
863
906
|
(0, import_fs.mkdirSync)(dir, { recursive: true });
|
|
864
907
|
}
|
package/dist/cli.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ var import_core3 = require("@aiready/core");
|
|
|
32
32
|
|
|
33
33
|
// src/analyzers/naming.ts
|
|
34
34
|
var import_core = require("@aiready/core");
|
|
35
|
+
var import_path = require("path");
|
|
35
36
|
var COMMON_SHORT_WORDS = /* @__PURE__ */ new Set([
|
|
36
37
|
// Full English words (1-3 letters)
|
|
37
38
|
"day",
|
|
@@ -375,75 +376,113 @@ var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
|
375
376
|
]);
|
|
376
377
|
async function analyzeNaming(files) {
|
|
377
378
|
const issues = [];
|
|
379
|
+
const rootDir = files.length > 0 ? (0, import_path.dirname)(files[0]) : process.cwd();
|
|
380
|
+
const config = (0, import_core.loadConfig)(rootDir);
|
|
381
|
+
const consistencyConfig = config?.tools?.["consistency"];
|
|
382
|
+
const customAbbreviations = new Set(consistencyConfig?.acceptedAbbreviations || []);
|
|
383
|
+
const customShortWords = new Set(consistencyConfig?.shortWords || []);
|
|
384
|
+
const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
|
|
378
385
|
for (const file of files) {
|
|
379
386
|
const content = await (0, import_core.readFileContent)(file);
|
|
380
|
-
const fileIssues = analyzeFileNaming(file, content);
|
|
387
|
+
const fileIssues = analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks);
|
|
381
388
|
issues.push(...fileIssues);
|
|
382
389
|
}
|
|
383
390
|
return issues;
|
|
384
391
|
}
|
|
385
|
-
function analyzeFileNaming(file, content) {
|
|
392
|
+
function analyzeFileNaming(file, content, customAbbreviations, customShortWords, disabledChecks) {
|
|
386
393
|
const issues = [];
|
|
387
394
|
const isTestFile = file.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/);
|
|
388
395
|
const lines = content.split("\n");
|
|
396
|
+
const allAbbreviations = /* @__PURE__ */ new Set([...ACCEPTABLE_ABBREVIATIONS, ...customAbbreviations]);
|
|
397
|
+
const allShortWords = /* @__PURE__ */ new Set([...COMMON_SHORT_WORDS, ...customShortWords]);
|
|
398
|
+
const getContextWindow = (index, windowSize = 3) => {
|
|
399
|
+
const start = Math.max(0, index - windowSize);
|
|
400
|
+
const end = Math.min(lines.length, index + windowSize + 1);
|
|
401
|
+
return lines.slice(start, end).join("\n");
|
|
402
|
+
};
|
|
403
|
+
const isShortLivedVariable = (varName, declarationIndex) => {
|
|
404
|
+
const searchRange = 5;
|
|
405
|
+
const endIndex = Math.min(lines.length, declarationIndex + searchRange + 1);
|
|
406
|
+
let usageCount = 0;
|
|
407
|
+
for (let i = declarationIndex; i < endIndex; i++) {
|
|
408
|
+
const regex = new RegExp(`\\b${varName}\\b`, "g");
|
|
409
|
+
const matches = lines[i].match(regex);
|
|
410
|
+
if (matches) {
|
|
411
|
+
usageCount += matches.length;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return usageCount >= 2 && usageCount <= 3;
|
|
415
|
+
};
|
|
389
416
|
lines.forEach((line, index) => {
|
|
390
417
|
const lineNumber = index + 1;
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
418
|
+
const contextWindow = getContextWindow(index);
|
|
419
|
+
if (!disabledChecks.has("single-letter")) {
|
|
420
|
+
const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
|
|
421
|
+
for (const match of singleLetterMatches) {
|
|
422
|
+
const letter = match[1].toLowerCase();
|
|
423
|
+
const isInLoopContext = line.includes("for") || /\.(map|filter|forEach|reduce|find|some|every)\s*\(/.test(line) || line.includes("=>") || // Arrow function
|
|
424
|
+
/\w+\s*=>\s*/.test(line);
|
|
425
|
+
const isI18nContext = line.includes("useTranslation") || line.includes("i18n.t") || /\bt\s*\(['"]/.test(line);
|
|
426
|
+
const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
427
|
+
/[a-z]\s*=>/.test(line) || // s => on same line
|
|
428
|
+
// Multi-line arrow function detection: look for pattern in context window
|
|
429
|
+
new RegExp(`\\b${letter}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
|
|
430
|
+
new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && /=>/.test(contextWindow);
|
|
431
|
+
const isShortLived = isShortLivedVariable(letter, index);
|
|
432
|
+
if (!isInLoopContext && !isI18nContext && !isArrowFunctionParam && !isShortLived && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
|
|
433
|
+
if (isTestFile && ["a", "b", "c", "d", "e", "f", "s"].includes(letter)) {
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
issues.push({
|
|
437
|
+
file,
|
|
438
|
+
line: lineNumber,
|
|
439
|
+
type: "poor-naming",
|
|
440
|
+
identifier: match[1],
|
|
441
|
+
severity: "minor",
|
|
442
|
+
suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
|
|
443
|
+
});
|
|
402
444
|
}
|
|
403
|
-
issues.push({
|
|
404
|
-
file,
|
|
405
|
-
line: lineNumber,
|
|
406
|
-
type: "poor-naming",
|
|
407
|
-
identifier: match[1],
|
|
408
|
-
severity: "minor",
|
|
409
|
-
suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
|
|
410
|
-
});
|
|
411
445
|
}
|
|
412
446
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
419
|
-
if (ACCEPTABLE_ABBREVIATIONS.has(abbrev)) {
|
|
420
|
-
continue;
|
|
421
|
-
}
|
|
422
|
-
const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
423
|
-
new RegExp(`\\b${abbrev}\\s*=>`).test(line);
|
|
424
|
-
if (isArrowFunctionParam) {
|
|
425
|
-
continue;
|
|
426
|
-
}
|
|
427
|
-
if (abbrev.length <= 2) {
|
|
428
|
-
const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
|
|
429
|
-
if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
|
|
447
|
+
if (!disabledChecks.has("abbreviation")) {
|
|
448
|
+
const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
|
|
449
|
+
for (const match of abbreviationMatches) {
|
|
450
|
+
const abbrev = match[1].toLowerCase();
|
|
451
|
+
if (allShortWords.has(abbrev)) {
|
|
430
452
|
continue;
|
|
431
453
|
}
|
|
432
|
-
|
|
433
|
-
if (isUserContext && abbrev === "u") {
|
|
454
|
+
if (allAbbreviations.has(abbrev)) {
|
|
434
455
|
continue;
|
|
435
456
|
}
|
|
457
|
+
const isArrowFunctionParam = /\(\s*[a-z]\s*(?:,\s*[a-z]\s*)*\)\s*=>/.test(line) || // (s) => or (a, b) =>
|
|
458
|
+
new RegExp(`\\b${abbrev}\\s*=>`).test(line) || // s => on same line
|
|
459
|
+
// Multi-line arrow function: check context window
|
|
460
|
+
new RegExp(`\\b${abbrev}\\s*\\)\\s*$`).test(line) && /=>/.test(contextWindow) || // (s)\n =>
|
|
461
|
+
new RegExp(`\\.(?:map|filter|forEach|reduce|find|some|every)\\s*\\(\\s*$`).test(lines[index - 1] || "") && new RegExp(`^\\s*${abbrev}\\s*=>`).test(line);
|
|
462
|
+
if (isArrowFunctionParam) {
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
if (abbrev.length <= 2) {
|
|
466
|
+
const isDateTimeContext = /date|time|day|hour|minute|second|timestamp/i.test(line);
|
|
467
|
+
if (isDateTimeContext && ["d", "t", "dt"].includes(abbrev)) {
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
const isUserContext = /user|auth|account/i.test(line);
|
|
471
|
+
if (isUserContext && abbrev === "u") {
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
issues.push({
|
|
476
|
+
file,
|
|
477
|
+
line: lineNumber,
|
|
478
|
+
type: "abbreviation",
|
|
479
|
+
identifier: match[1],
|
|
480
|
+
severity: "info",
|
|
481
|
+
suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
|
|
482
|
+
});
|
|
436
483
|
}
|
|
437
|
-
issues.push({
|
|
438
|
-
file,
|
|
439
|
-
line: lineNumber,
|
|
440
|
-
type: "abbreviation",
|
|
441
|
-
identifier: match[1],
|
|
442
|
-
severity: "info",
|
|
443
|
-
suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
|
|
444
|
-
});
|
|
445
484
|
}
|
|
446
|
-
if (file.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
485
|
+
if (!disabledChecks.has("convention-mix") && file.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
447
486
|
const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
|
|
448
487
|
const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
|
|
449
488
|
if (snakeCaseVars) {
|
|
@@ -457,47 +496,51 @@ function analyzeFileNaming(file, content) {
|
|
|
457
496
|
});
|
|
458
497
|
}
|
|
459
498
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
499
|
+
if (!disabledChecks.has("unclear")) {
|
|
500
|
+
const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
|
|
501
|
+
for (const match of booleanMatches) {
|
|
502
|
+
const name = match[1];
|
|
503
|
+
if (!name.match(/^(is|has|should|can|will|did)/i)) {
|
|
504
|
+
issues.push({
|
|
505
|
+
file,
|
|
506
|
+
line: lineNumber,
|
|
507
|
+
type: "unclear",
|
|
508
|
+
identifier: name,
|
|
509
|
+
severity: "info",
|
|
510
|
+
suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
|
|
511
|
+
});
|
|
512
|
+
}
|
|
472
513
|
}
|
|
473
514
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
515
|
+
if (!disabledChecks.has("unclear")) {
|
|
516
|
+
const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
|
|
517
|
+
for (const match of functionMatches) {
|
|
518
|
+
const name = match[1];
|
|
519
|
+
const isKeyword = ["for", "if", "else", "while", "do", "switch", "case", "break", "continue", "return", "throw", "try", "catch", "finally", "with", "yield", "await"].includes(name);
|
|
520
|
+
if (isKeyword) {
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
const isEntryPoint = ["main", "init", "setup", "bootstrap"].includes(name);
|
|
524
|
+
if (isEntryPoint) {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator)$/);
|
|
528
|
+
const isEventHandler = name.match(/^on[A-Z]/);
|
|
529
|
+
const isDescriptiveLong = name.length > 15;
|
|
530
|
+
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)$/);
|
|
531
|
+
const capitalCount = (name.match(/[A-Z]/g) || []).length;
|
|
532
|
+
const isCompoundWord = capitalCount >= 3;
|
|
533
|
+
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)/);
|
|
534
|
+
if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong && !isDescriptivePattern && !isCompoundWord) {
|
|
535
|
+
issues.push({
|
|
536
|
+
file,
|
|
537
|
+
line: lineNumber,
|
|
538
|
+
type: "unclear",
|
|
539
|
+
identifier: name,
|
|
540
|
+
severity: "info",
|
|
541
|
+
suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
|
|
542
|
+
});
|
|
543
|
+
}
|
|
501
544
|
}
|
|
502
545
|
}
|
|
503
546
|
});
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/consistency",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
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,9 @@
|
|
|
48
39
|
},
|
|
49
40
|
"homepage": "https://github.com/caopengau/aiready-consistency",
|
|
50
41
|
"dependencies": {
|
|
51
|
-
"@aiready/core": "workspace:*",
|
|
52
42
|
"chalk": "^5.3.0",
|
|
53
|
-
"commander": "^12.1.0"
|
|
43
|
+
"commander": "^12.1.0",
|
|
44
|
+
"@aiready/core": "0.7.1"
|
|
54
45
|
},
|
|
55
46
|
"devDependencies": {
|
|
56
47
|
"@types/node": "^22.10.5",
|
|
@@ -60,5 +51,13 @@
|
|
|
60
51
|
},
|
|
61
52
|
"engines": {
|
|
62
53
|
"node": ">=18.0.0"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsup src/index.ts src/cli.ts --format cjs,esm --dts",
|
|
57
|
+
"dev": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --watch",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"lint": "eslint src",
|
|
60
|
+
"clean": "rm -rf dist",
|
|
61
|
+
"release": "pnpm build && pnpm publish --no-git-checks"
|
|
63
62
|
}
|
|
64
|
-
}
|
|
63
|
+
}
|