@aiready/consistency 0.2.5 → 0.3.3
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 +7 -7
- package/.turbo/turbo-test.log +30 -9
- package/README.md +50 -13
- package/dist/chunk-LUAREV6A.mjs +508 -0
- package/dist/cli.js +137 -13
- package/dist/cli.mjs +1 -1
- package/dist/index.js +137 -13
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
- package/src/__tests__/analyzer.test.ts +11 -0
- package/src/analyzers/naming.ts +62 -16
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/consistency@0.
|
|
3
|
+
> @aiready/consistency@0.3.3 build /Users/pengcao/projects/aiready/packages/consistency
|
|
4
4
|
> tsup src/index.ts src/cli.ts --format cjs,esm --dts
|
|
5
5
|
|
|
6
6
|
[34mCLI[39m Building entry: src/cli.ts, src/index.ts
|
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
[34mCLI[39m Target: es2020
|
|
10
10
|
[34mCJS[39m Build start
|
|
11
11
|
[34mESM[39m Build start
|
|
12
|
-
[32mCJS[39m [1mdist/
|
|
13
|
-
[32mCJS[39m [1mdist/
|
|
14
|
-
[32mCJS[39m ⚡️ Build success in
|
|
12
|
+
[32mCJS[39m [1mdist/cli.js [22m[32m25.23 KB[39m
|
|
13
|
+
[32mCJS[39m [1mdist/index.js [22m[32m16.34 KB[39m
|
|
14
|
+
[32mCJS[39m ⚡️ Build success in 79ms
|
|
15
|
+
[32mESM[39m [1mdist/chunk-LUAREV6A.mjs [22m[32m15.11 KB[39m
|
|
15
16
|
[32mESM[39m [1mdist/cli.mjs [22m[32m8.54 KB[39m
|
|
16
17
|
[32mESM[39m [1mdist/index.mjs [22m[32m220.00 B[39m
|
|
17
|
-
[32mESM[39m
|
|
18
|
-
[32mESM[39m ⚡️ Build success in 58ms
|
|
18
|
+
[32mESM[39m ⚡️ Build success in 79ms
|
|
19
19
|
DTS Build start
|
|
20
|
-
DTS ⚡️ Build success in
|
|
20
|
+
DTS ⚡️ Build success in 587ms
|
|
21
21
|
DTS dist/cli.d.ts 20.00 B
|
|
22
22
|
DTS dist/index.d.ts 2.60 KB
|
|
23
23
|
DTS dist/cli.d.mts 20.00 B
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/consistency@0.
|
|
3
|
+
> @aiready/consistency@0.3.3 test /Users/pengcao/projects/aiready/packages/consistency
|
|
4
4
|
> vitest run
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
[1m[7m[36m RUN [39m[27m[22m [36mv2.1.9 [39m[90m/Users/pengcao/projects/aiready/packages/consistency[39m
|
|
8
8
|
|
|
9
|
-
[?25l
|
|
10
|
-
[33m❯[39m analyzeConsistency[2m (2)[22m
|
|
9
|
+
[?25l [33m❯[39m analyzeConsistency[2m (2)[22m
|
|
11
10
|
[33m⠙[39m should analyze naming issues
|
|
12
11
|
[90m·[39m should detect minimum severity filtering
|
|
13
|
-
[90m·[39m analyzeNaming[2m (
|
|
12
|
+
[90m·[39m analyzeNaming[2m (5)[22m
|
|
14
13
|
[90m·[39m should detect single letter variables
|
|
14
|
+
[90m·[39m should NOT flag acceptable abbreviations
|
|
15
15
|
[90m·[39m should detect snake_case in TypeScript files
|
|
16
16
|
[90m·[39m should detect unclear boolean names
|
|
17
17
|
[90m·[39m should allow common abbreviations
|
|
@@ -26,12 +26,33 @@
|
|
|
26
26
|
[90m·[39m should generate relevant recommendations
|
|
27
27
|
[90m·[39m should suggest standardizing error handling
|
|
28
28
|
[90m·[39m should suggest using async/await consistently
|
|
29
|
-
[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[G
|
|
29
|
+
[?25l[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[G [32m✓[39m analyzeConsistency[2m (2)[22m
|
|
30
|
+
[32m✓[39m should analyze naming issues
|
|
31
|
+
[32m✓[39m should detect minimum severity filtering
|
|
32
|
+
[32m✓[39m analyzeNaming[2m (5)[22m
|
|
33
|
+
[32m✓[39m should detect single letter variables
|
|
34
|
+
[32m✓[39m should NOT flag acceptable abbreviations
|
|
35
|
+
[32m✓[39m should detect snake_case in TypeScript files
|
|
36
|
+
[32m✓[39m should detect unclear boolean names
|
|
37
|
+
[32m✓[39m should allow common abbreviations
|
|
38
|
+
[32m✓[39m analyzePatterns[2m (3)[22m
|
|
39
|
+
[32m✓[39m should detect mixed error handling
|
|
40
|
+
[32m✓[39m should detect mixed async patterns
|
|
41
|
+
[32m✓[39m should detect mixed import styles
|
|
42
|
+
[32m✓[39m consistency scoring[2m (2)[22m
|
|
43
|
+
[32m✓[39m should calculate consistency score correctly
|
|
44
|
+
[32m✓[39m should weight critical issues more than info
|
|
45
|
+
[32m✓[39m recommendations[2m (3)[22m
|
|
46
|
+
[32m✓[39m should generate relevant recommendations
|
|
47
|
+
[32m✓[39m should suggest standardizing error handling
|
|
48
|
+
[32m✓[39m should suggest using async/await consistently
|
|
49
|
+
[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[G [32m✓[39m [2msrc/__tests__/[22manalyzer[2m.test.ts[22m[2m (15)[22m
|
|
30
50
|
[32m✓[39m analyzeConsistency[2m (2)[22m
|
|
31
51
|
[32m✓[39m should analyze naming issues
|
|
32
52
|
[32m✓[39m should detect minimum severity filtering
|
|
33
|
-
[32m✓[39m analyzeNaming[2m (
|
|
53
|
+
[32m✓[39m analyzeNaming[2m (5)[22m
|
|
34
54
|
[32m✓[39m should detect single letter variables
|
|
55
|
+
[32m✓[39m should NOT flag acceptable abbreviations
|
|
35
56
|
[32m✓[39m should detect snake_case in TypeScript files
|
|
36
57
|
[32m✓[39m should detect unclear boolean names
|
|
37
58
|
[32m✓[39m should allow common abbreviations
|
|
@@ -48,8 +69,8 @@
|
|
|
48
69
|
[32m✓[39m should suggest using async/await consistently
|
|
49
70
|
|
|
50
71
|
[2m Test Files [22m [1m[32m1 passed[39m[22m[90m (1)[39m
|
|
51
|
-
[2m Tests [22m [1m[
|
|
52
|
-
[2m Start at [22m
|
|
53
|
-
[2m Duration [22m
|
|
72
|
+
[2m Tests [22m [1m[32m15 passed[39m[22m[90m (15)[39m
|
|
73
|
+
[2m Start at [22m 15:32:25
|
|
74
|
+
[2m Duration [22m 519ms[2m (transform 74ms, setup 0ms, collect 251ms, tests 24ms, environment 0ms, prepare 54ms)[22m
|
|
54
75
|
|
|
55
76
|
[?25h[?25h
|
package/README.md
CHANGED
|
@@ -25,17 +25,24 @@ aiready-consistency ./src
|
|
|
25
25
|
Inconsistent code patterns confuse AI models and reduce their effectiveness. This tool analyzes:
|
|
26
26
|
|
|
27
27
|
### 🏷️ Naming Quality & Conventions
|
|
28
|
-
- Single-letter variables (
|
|
29
|
-
-
|
|
30
|
-
- Mixed naming conventions (
|
|
31
|
-
- Boolean naming
|
|
32
|
-
- Function naming
|
|
28
|
+
- **Single-letter variables** - Detects unclear variable names (skips common iterators: i, j, k, l, x, y, z in appropriate contexts)
|
|
29
|
+
- **Abbreviations** - Identifies unclear abbreviations while allowing 60+ standard ones (env, req, res, ctx, max, min, etc.)
|
|
30
|
+
- **Mixed naming conventions** - Detects snake_case in TypeScript/JavaScript projects (should use camelCase)
|
|
31
|
+
- **Boolean naming** - Ensures booleans use clear prefixes (is/has/can/should)
|
|
32
|
+
- **Function naming** - Checks for action verbs while allowing factory patterns and descriptive names
|
|
33
|
+
|
|
34
|
+
**Smart Detection:** The tool understands context and won't flag:
|
|
35
|
+
- Common abbreviations (env, api, url, max, min, now, etc.)
|
|
36
|
+
- Boolean prefixes (is, has, can used as variables)
|
|
37
|
+
- Loop iterators in appropriate contexts
|
|
38
|
+
- Factory/builder patterns
|
|
39
|
+
- Long descriptive function names
|
|
33
40
|
|
|
34
41
|
### 🔄 Pattern Consistency
|
|
35
|
-
- Error handling strategies (try-catch vs returns)
|
|
36
|
-
- Async patterns
|
|
37
|
-
- Import styles
|
|
38
|
-
- API design patterns
|
|
42
|
+
- **Error handling strategies** - Detects mixed approaches (try-catch vs returns vs throws)
|
|
43
|
+
- **Async patterns** - Identifies mixing of async/await, promises, and callbacks
|
|
44
|
+
- **Import styles** - Flags mixing of ES modules and CommonJS
|
|
45
|
+
- **API design patterns** - Ensures consistent patterns across endpoints
|
|
39
46
|
|
|
40
47
|
### 🏗️ Architectural Consistency *(coming soon)*
|
|
41
48
|
- File organization patterns
|
|
@@ -124,15 +131,45 @@ Create `aiready.json` in your project root:
|
|
|
124
131
|
|
|
125
132
|
```json
|
|
126
133
|
{
|
|
127
|
-
"
|
|
128
|
-
"
|
|
129
|
-
"checkPatterns": true,
|
|
130
|
-
"minSeverity": "minor",
|
|
134
|
+
"scan": {
|
|
135
|
+
"include": ["src/**/*.{ts,tsx,js,jsx}"],
|
|
131
136
|
"exclude": ["**/dist/**", "**/node_modules/**"]
|
|
137
|
+
},
|
|
138
|
+
"tools": {
|
|
139
|
+
"consistency": {
|
|
140
|
+
"checkNaming": true,
|
|
141
|
+
"checkPatterns": true,
|
|
142
|
+
"minSeverity": "minor"
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"output": {
|
|
146
|
+
"format": "console"
|
|
132
147
|
}
|
|
133
148
|
}
|
|
134
149
|
```
|
|
135
150
|
|
|
151
|
+
**Configuration Options:**
|
|
152
|
+
|
|
153
|
+
| Option | Type | Default | Description |
|
|
154
|
+
|--------|------|---------|-------------|
|
|
155
|
+
| `checkNaming` | boolean | `true` | Check naming conventions |
|
|
156
|
+
| `checkPatterns` | boolean | `true` | Check code pattern consistency |
|
|
157
|
+
| `minSeverity` | string | `'info'` | Filter: `'info'`, `'minor'`, `'major'`, `'critical'` |
|
|
158
|
+
|
|
159
|
+
### Acceptable Abbreviations
|
|
160
|
+
|
|
161
|
+
The tool recognizes 60+ standard abbreviations and won't flag them:
|
|
162
|
+
|
|
163
|
+
**Web/Network:** url, uri, api, cdn, dns, ip, http, utm, seo, xhr
|
|
164
|
+
**Data:** json, xml, yaml, csv, html, css, svg, pdf, dto, dao
|
|
165
|
+
**System:** env, os, fs, cli, tmp, src, dst, bin, lib, pkg
|
|
166
|
+
**Request/Response:** req, res, ctx, err, msg
|
|
167
|
+
**Math:** max, min, avg, sum, abs, cos, sin, log, sqrt
|
|
168
|
+
**Time:** now, utc, ms, sec
|
|
169
|
+
**Common:** id, uid, db, sql, orm, ui, ux, dom, ref, val, str, obj, arr, cfg, init
|
|
170
|
+
|
|
171
|
+
See [naming.ts](src/analyzers/naming.ts) for the complete list.
|
|
172
|
+
|
|
136
173
|
## 🔧 Programmatic API
|
|
137
174
|
|
|
138
175
|
```typescript
|
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
// src/analyzers/naming.ts
|
|
2
|
+
import { readFileContent } from "@aiready/core";
|
|
3
|
+
var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
4
|
+
// Standard identifiers
|
|
5
|
+
"id",
|
|
6
|
+
"uid",
|
|
7
|
+
"gid",
|
|
8
|
+
"pid",
|
|
9
|
+
// Web/Network
|
|
10
|
+
"url",
|
|
11
|
+
"uri",
|
|
12
|
+
"api",
|
|
13
|
+
"cdn",
|
|
14
|
+
"dns",
|
|
15
|
+
"ip",
|
|
16
|
+
"tcp",
|
|
17
|
+
"udp",
|
|
18
|
+
"http",
|
|
19
|
+
"ssl",
|
|
20
|
+
"tls",
|
|
21
|
+
"utm",
|
|
22
|
+
"seo",
|
|
23
|
+
"rss",
|
|
24
|
+
"xhr",
|
|
25
|
+
"ajax",
|
|
26
|
+
// Data formats
|
|
27
|
+
"json",
|
|
28
|
+
"xml",
|
|
29
|
+
"yaml",
|
|
30
|
+
"csv",
|
|
31
|
+
"html",
|
|
32
|
+
"css",
|
|
33
|
+
"svg",
|
|
34
|
+
"pdf",
|
|
35
|
+
// Databases
|
|
36
|
+
"db",
|
|
37
|
+
"sql",
|
|
38
|
+
"orm",
|
|
39
|
+
"dao",
|
|
40
|
+
"dto",
|
|
41
|
+
// File system
|
|
42
|
+
"fs",
|
|
43
|
+
"dir",
|
|
44
|
+
"tmp",
|
|
45
|
+
"src",
|
|
46
|
+
"dst",
|
|
47
|
+
"bin",
|
|
48
|
+
"lib",
|
|
49
|
+
"pkg",
|
|
50
|
+
// Operating system
|
|
51
|
+
"os",
|
|
52
|
+
"env",
|
|
53
|
+
"arg",
|
|
54
|
+
"cli",
|
|
55
|
+
"cmd",
|
|
56
|
+
"exe",
|
|
57
|
+
// UI/UX
|
|
58
|
+
"ui",
|
|
59
|
+
"ux",
|
|
60
|
+
"gui",
|
|
61
|
+
"dom",
|
|
62
|
+
"ref",
|
|
63
|
+
// Request/Response
|
|
64
|
+
"req",
|
|
65
|
+
"res",
|
|
66
|
+
"ctx",
|
|
67
|
+
"err",
|
|
68
|
+
"msg",
|
|
69
|
+
// Mathematics/Computing
|
|
70
|
+
"max",
|
|
71
|
+
"min",
|
|
72
|
+
"avg",
|
|
73
|
+
"sum",
|
|
74
|
+
"abs",
|
|
75
|
+
"cos",
|
|
76
|
+
"sin",
|
|
77
|
+
"tan",
|
|
78
|
+
"log",
|
|
79
|
+
"exp",
|
|
80
|
+
"pow",
|
|
81
|
+
"sqrt",
|
|
82
|
+
"std",
|
|
83
|
+
"var",
|
|
84
|
+
"int",
|
|
85
|
+
"num",
|
|
86
|
+
// Time
|
|
87
|
+
"now",
|
|
88
|
+
"utc",
|
|
89
|
+
"tz",
|
|
90
|
+
"ms",
|
|
91
|
+
"sec",
|
|
92
|
+
// Common patterns
|
|
93
|
+
"app",
|
|
94
|
+
"cfg",
|
|
95
|
+
"config",
|
|
96
|
+
"init",
|
|
97
|
+
"len",
|
|
98
|
+
"val",
|
|
99
|
+
"str",
|
|
100
|
+
"obj",
|
|
101
|
+
"arr",
|
|
102
|
+
"gen",
|
|
103
|
+
"def",
|
|
104
|
+
"raw",
|
|
105
|
+
"new",
|
|
106
|
+
"old",
|
|
107
|
+
"pre",
|
|
108
|
+
"post",
|
|
109
|
+
"sub",
|
|
110
|
+
"pub",
|
|
111
|
+
// Boolean helpers (these are intentional short names)
|
|
112
|
+
"is",
|
|
113
|
+
"has",
|
|
114
|
+
"can",
|
|
115
|
+
"did",
|
|
116
|
+
"was",
|
|
117
|
+
"are"
|
|
118
|
+
]);
|
|
119
|
+
async function analyzeNaming(files) {
|
|
120
|
+
const issues = [];
|
|
121
|
+
for (const file of files) {
|
|
122
|
+
const content = await readFileContent(file);
|
|
123
|
+
const fileIssues = analyzeFileNaming(file, content);
|
|
124
|
+
issues.push(...fileIssues);
|
|
125
|
+
}
|
|
126
|
+
return issues;
|
|
127
|
+
}
|
|
128
|
+
function analyzeFileNaming(file, content) {
|
|
129
|
+
const issues = [];
|
|
130
|
+
const lines = content.split("\n");
|
|
131
|
+
lines.forEach((line, index) => {
|
|
132
|
+
const lineNumber = index + 1;
|
|
133
|
+
const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
|
|
134
|
+
for (const match of singleLetterMatches) {
|
|
135
|
+
const letter = match[1].toLowerCase();
|
|
136
|
+
const isInLoopContext = line.includes("for") || line.includes(".map") || line.includes(".filter") || line.includes(".forEach") || line.includes(".reduce");
|
|
137
|
+
if (!isInLoopContext && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
|
|
138
|
+
issues.push({
|
|
139
|
+
file,
|
|
140
|
+
line: lineNumber,
|
|
141
|
+
type: "poor-naming",
|
|
142
|
+
identifier: match[1],
|
|
143
|
+
severity: "minor",
|
|
144
|
+
suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
|
|
149
|
+
for (const match of abbreviationMatches) {
|
|
150
|
+
const abbrev = match[1].toLowerCase();
|
|
151
|
+
if (!ACCEPTABLE_ABBREVIATIONS.has(abbrev)) {
|
|
152
|
+
issues.push({
|
|
153
|
+
file,
|
|
154
|
+
line: lineNumber,
|
|
155
|
+
type: "abbreviation",
|
|
156
|
+
identifier: match[1],
|
|
157
|
+
severity: "info",
|
|
158
|
+
suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (file.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
163
|
+
const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
|
|
164
|
+
const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
|
|
165
|
+
if (snakeCaseVars) {
|
|
166
|
+
issues.push({
|
|
167
|
+
file,
|
|
168
|
+
line: lineNumber,
|
|
169
|
+
type: "convention-mix",
|
|
170
|
+
identifier: snakeCaseVars[1],
|
|
171
|
+
severity: "minor",
|
|
172
|
+
suggestion: `Use camelCase '${snakeCaseToCamelCase(snakeCaseVars[1])}' instead of snake_case in TypeScript/JavaScript`
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
|
|
177
|
+
for (const match of booleanMatches) {
|
|
178
|
+
const name = match[1];
|
|
179
|
+
if (!name.match(/^(is|has|should|can|will|did)/i)) {
|
|
180
|
+
issues.push({
|
|
181
|
+
file,
|
|
182
|
+
line: lineNumber,
|
|
183
|
+
type: "unclear",
|
|
184
|
+
identifier: name,
|
|
185
|
+
severity: "info",
|
|
186
|
+
suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
|
|
191
|
+
for (const match of functionMatches) {
|
|
192
|
+
const name = match[1];
|
|
193
|
+
const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator)$/);
|
|
194
|
+
const isEventHandler = name.match(/^on[A-Z]/);
|
|
195
|
+
const isDescriptiveLong = name.length > 20;
|
|
196
|
+
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)/);
|
|
197
|
+
if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong) {
|
|
198
|
+
issues.push({
|
|
199
|
+
file,
|
|
200
|
+
line: lineNumber,
|
|
201
|
+
type: "unclear",
|
|
202
|
+
identifier: name,
|
|
203
|
+
severity: "info",
|
|
204
|
+
suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
return issues;
|
|
210
|
+
}
|
|
211
|
+
function snakeCaseToCamelCase(str) {
|
|
212
|
+
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
213
|
+
}
|
|
214
|
+
function detectNamingConventions(files, allIssues) {
|
|
215
|
+
const camelCaseCount = allIssues.filter((i) => i.type === "convention-mix").length;
|
|
216
|
+
const totalChecks = files.length * 10;
|
|
217
|
+
if (camelCaseCount / totalChecks > 0.3) {
|
|
218
|
+
return { dominantConvention: "mixed", conventionScore: 0.5 };
|
|
219
|
+
}
|
|
220
|
+
return { dominantConvention: "camelCase", conventionScore: 0.9 };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/analyzers/patterns.ts
|
|
224
|
+
import { readFileContent as readFileContent2 } from "@aiready/core";
|
|
225
|
+
async function analyzePatterns(files) {
|
|
226
|
+
const issues = [];
|
|
227
|
+
const errorHandlingIssues = await analyzeErrorHandling(files);
|
|
228
|
+
issues.push(...errorHandlingIssues);
|
|
229
|
+
const asyncIssues = await analyzeAsyncPatterns(files);
|
|
230
|
+
issues.push(...asyncIssues);
|
|
231
|
+
const importIssues = await analyzeImportStyles(files);
|
|
232
|
+
issues.push(...importIssues);
|
|
233
|
+
return issues;
|
|
234
|
+
}
|
|
235
|
+
async function analyzeErrorHandling(files) {
|
|
236
|
+
const patterns = {
|
|
237
|
+
tryCatch: [],
|
|
238
|
+
throwsError: [],
|
|
239
|
+
returnsNull: [],
|
|
240
|
+
returnsError: []
|
|
241
|
+
};
|
|
242
|
+
for (const file of files) {
|
|
243
|
+
const content = await readFileContent2(file);
|
|
244
|
+
if (content.includes("try {") || content.includes("} catch")) {
|
|
245
|
+
patterns.tryCatch.push(file);
|
|
246
|
+
}
|
|
247
|
+
if (content.match(/throw new \w*Error/)) {
|
|
248
|
+
patterns.throwsError.push(file);
|
|
249
|
+
}
|
|
250
|
+
if (content.match(/return null/)) {
|
|
251
|
+
patterns.returnsNull.push(file);
|
|
252
|
+
}
|
|
253
|
+
if (content.match(/return \{ error:/)) {
|
|
254
|
+
patterns.returnsError.push(file);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const issues = [];
|
|
258
|
+
const strategiesUsed = Object.values(patterns).filter((p) => p.length > 0).length;
|
|
259
|
+
if (strategiesUsed > 2) {
|
|
260
|
+
issues.push({
|
|
261
|
+
files: [.../* @__PURE__ */ new Set([
|
|
262
|
+
...patterns.tryCatch,
|
|
263
|
+
...patterns.throwsError,
|
|
264
|
+
...patterns.returnsNull,
|
|
265
|
+
...patterns.returnsError
|
|
266
|
+
])],
|
|
267
|
+
type: "error-handling",
|
|
268
|
+
description: "Inconsistent error handling strategies across codebase",
|
|
269
|
+
examples: [
|
|
270
|
+
patterns.tryCatch.length > 0 ? `Try-catch used in ${patterns.tryCatch.length} files` : "",
|
|
271
|
+
patterns.throwsError.length > 0 ? `Throws errors in ${patterns.throwsError.length} files` : "",
|
|
272
|
+
patterns.returnsNull.length > 0 ? `Returns null in ${patterns.returnsNull.length} files` : "",
|
|
273
|
+
patterns.returnsError.length > 0 ? `Returns error objects in ${patterns.returnsError.length} files` : ""
|
|
274
|
+
].filter((e) => e),
|
|
275
|
+
severity: "major"
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
return issues;
|
|
279
|
+
}
|
|
280
|
+
async function analyzeAsyncPatterns(files) {
|
|
281
|
+
const patterns = {
|
|
282
|
+
asyncAwait: [],
|
|
283
|
+
promises: [],
|
|
284
|
+
callbacks: []
|
|
285
|
+
};
|
|
286
|
+
for (const file of files) {
|
|
287
|
+
const content = await readFileContent2(file);
|
|
288
|
+
if (content.match(/async\s+(function|\(|[a-zA-Z])/)) {
|
|
289
|
+
patterns.asyncAwait.push(file);
|
|
290
|
+
}
|
|
291
|
+
if (content.match(/\.then\(/) || content.match(/\.catch\(/)) {
|
|
292
|
+
patterns.promises.push(file);
|
|
293
|
+
}
|
|
294
|
+
if (content.match(/callback\s*\(/) || content.match(/\(\s*err\s*,/)) {
|
|
295
|
+
patterns.callbacks.push(file);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const issues = [];
|
|
299
|
+
if (patterns.callbacks.length > 0 && patterns.asyncAwait.length > 0) {
|
|
300
|
+
issues.push({
|
|
301
|
+
files: [...patterns.callbacks, ...patterns.asyncAwait],
|
|
302
|
+
type: "async-style",
|
|
303
|
+
description: "Mixed async patterns: callbacks and async/await",
|
|
304
|
+
examples: [
|
|
305
|
+
`Callbacks found in: ${patterns.callbacks.slice(0, 3).join(", ")}`,
|
|
306
|
+
`Async/await used in: ${patterns.asyncAwait.slice(0, 3).join(", ")}`
|
|
307
|
+
],
|
|
308
|
+
severity: "minor"
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
if (patterns.promises.length > patterns.asyncAwait.length * 0.3 && patterns.asyncAwait.length > 0) {
|
|
312
|
+
issues.push({
|
|
313
|
+
files: patterns.promises,
|
|
314
|
+
type: "async-style",
|
|
315
|
+
description: "Consider using async/await instead of promise chains for consistency",
|
|
316
|
+
examples: patterns.promises.slice(0, 5),
|
|
317
|
+
severity: "info"
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
return issues;
|
|
321
|
+
}
|
|
322
|
+
async function analyzeImportStyles(files) {
|
|
323
|
+
const patterns = {
|
|
324
|
+
esModules: [],
|
|
325
|
+
commonJS: [],
|
|
326
|
+
mixed: []
|
|
327
|
+
};
|
|
328
|
+
for (const file of files) {
|
|
329
|
+
const content = await readFileContent2(file);
|
|
330
|
+
const hasESM = content.match(/^import\s+/m);
|
|
331
|
+
const hasCJS = content.match(/require\s*\(/);
|
|
332
|
+
if (hasESM && hasCJS) {
|
|
333
|
+
patterns.mixed.push(file);
|
|
334
|
+
} else if (hasESM) {
|
|
335
|
+
patterns.esModules.push(file);
|
|
336
|
+
} else if (hasCJS) {
|
|
337
|
+
patterns.commonJS.push(file);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const issues = [];
|
|
341
|
+
if (patterns.mixed.length > 0) {
|
|
342
|
+
issues.push({
|
|
343
|
+
files: patterns.mixed,
|
|
344
|
+
type: "import-style",
|
|
345
|
+
description: "Mixed ES modules and CommonJS imports in same files",
|
|
346
|
+
examples: patterns.mixed.slice(0, 5),
|
|
347
|
+
severity: "major"
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
if (patterns.esModules.length > 0 && patterns.commonJS.length > 0) {
|
|
351
|
+
const ratio = patterns.commonJS.length / (patterns.esModules.length + patterns.commonJS.length);
|
|
352
|
+
if (ratio > 0.2 && ratio < 0.8) {
|
|
353
|
+
issues.push({
|
|
354
|
+
files: [...patterns.esModules, ...patterns.commonJS],
|
|
355
|
+
type: "import-style",
|
|
356
|
+
description: "Inconsistent import styles across project",
|
|
357
|
+
examples: [
|
|
358
|
+
`ES modules: ${patterns.esModules.length} files`,
|
|
359
|
+
`CommonJS: ${patterns.commonJS.length} files`
|
|
360
|
+
],
|
|
361
|
+
severity: "minor"
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return issues;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/analyzer.ts
|
|
369
|
+
import { scanFiles } from "@aiready/core";
|
|
370
|
+
async function analyzeConsistency(options) {
|
|
371
|
+
const {
|
|
372
|
+
checkNaming = true,
|
|
373
|
+
checkPatterns = true,
|
|
374
|
+
checkArchitecture = false,
|
|
375
|
+
// Not implemented yet
|
|
376
|
+
minSeverity = "info",
|
|
377
|
+
...scanOptions
|
|
378
|
+
} = options;
|
|
379
|
+
const filePaths = await scanFiles(scanOptions);
|
|
380
|
+
const namingIssues = checkNaming ? await analyzeNaming(filePaths) : [];
|
|
381
|
+
const patternIssues = checkPatterns ? await analyzePatterns(filePaths) : [];
|
|
382
|
+
const results = [];
|
|
383
|
+
const fileIssuesMap = /* @__PURE__ */ new Map();
|
|
384
|
+
for (const issue of namingIssues) {
|
|
385
|
+
if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const consistencyIssue = {
|
|
389
|
+
type: issue.type === "convention-mix" ? "naming-inconsistency" : "naming-quality",
|
|
390
|
+
category: "naming",
|
|
391
|
+
severity: issue.severity,
|
|
392
|
+
message: `${issue.type}: ${issue.identifier}`,
|
|
393
|
+
location: {
|
|
394
|
+
file: issue.file,
|
|
395
|
+
line: issue.line,
|
|
396
|
+
column: issue.column
|
|
397
|
+
},
|
|
398
|
+
suggestion: issue.suggestion
|
|
399
|
+
};
|
|
400
|
+
if (!fileIssuesMap.has(issue.file)) {
|
|
401
|
+
fileIssuesMap.set(issue.file, []);
|
|
402
|
+
}
|
|
403
|
+
fileIssuesMap.get(issue.file).push(consistencyIssue);
|
|
404
|
+
}
|
|
405
|
+
for (const issue of patternIssues) {
|
|
406
|
+
if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
const consistencyIssue = {
|
|
410
|
+
type: "pattern-inconsistency",
|
|
411
|
+
category: "patterns",
|
|
412
|
+
severity: issue.severity,
|
|
413
|
+
message: issue.description,
|
|
414
|
+
location: {
|
|
415
|
+
file: issue.files[0] || "multiple files",
|
|
416
|
+
line: 1
|
|
417
|
+
},
|
|
418
|
+
examples: issue.examples,
|
|
419
|
+
suggestion: `Standardize ${issue.type} patterns across ${issue.files.length} files`
|
|
420
|
+
};
|
|
421
|
+
const firstFile = issue.files[0];
|
|
422
|
+
if (firstFile && !fileIssuesMap.has(firstFile)) {
|
|
423
|
+
fileIssuesMap.set(firstFile, []);
|
|
424
|
+
}
|
|
425
|
+
if (firstFile) {
|
|
426
|
+
fileIssuesMap.get(firstFile).push(consistencyIssue);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
for (const [fileName, issues] of fileIssuesMap) {
|
|
430
|
+
results.push({
|
|
431
|
+
fileName,
|
|
432
|
+
issues,
|
|
433
|
+
metrics: {
|
|
434
|
+
consistencyScore: calculateConsistencyScore(issues)
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
const recommendations = generateRecommendations(namingIssues, patternIssues);
|
|
439
|
+
const conventionAnalysis = detectNamingConventions(filePaths, namingIssues);
|
|
440
|
+
return {
|
|
441
|
+
summary: {
|
|
442
|
+
totalIssues: namingIssues.length + patternIssues.length,
|
|
443
|
+
namingIssues: namingIssues.length,
|
|
444
|
+
patternIssues: patternIssues.length,
|
|
445
|
+
architectureIssues: 0,
|
|
446
|
+
filesAnalyzed: filePaths.length
|
|
447
|
+
},
|
|
448
|
+
results,
|
|
449
|
+
recommendations
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
function shouldIncludeSeverity(severity, minSeverity) {
|
|
453
|
+
const severityLevels = { info: 0, minor: 1, major: 2, critical: 3 };
|
|
454
|
+
return severityLevels[severity] >= severityLevels[minSeverity];
|
|
455
|
+
}
|
|
456
|
+
function calculateConsistencyScore(issues) {
|
|
457
|
+
const weights = { critical: 10, major: 5, minor: 2, info: 1 };
|
|
458
|
+
const totalWeight = issues.reduce((sum, issue) => sum + weights[issue.severity], 0);
|
|
459
|
+
return Math.max(0, 1 - totalWeight / 100);
|
|
460
|
+
}
|
|
461
|
+
function generateRecommendations(namingIssues, patternIssues) {
|
|
462
|
+
const recommendations = [];
|
|
463
|
+
if (namingIssues.length > 0) {
|
|
464
|
+
const conventionMixCount = namingIssues.filter((i) => i.type === "convention-mix").length;
|
|
465
|
+
if (conventionMixCount > 0) {
|
|
466
|
+
recommendations.push(
|
|
467
|
+
`Standardize naming conventions: Found ${conventionMixCount} snake_case variables in TypeScript/JavaScript (use camelCase)`
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
const poorNamingCount = namingIssues.filter((i) => i.type === "poor-naming").length;
|
|
471
|
+
if (poorNamingCount > 0) {
|
|
472
|
+
recommendations.push(
|
|
473
|
+
`Improve variable naming: Found ${poorNamingCount} single-letter or unclear variable names`
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (patternIssues.length > 0) {
|
|
478
|
+
const errorHandlingIssues = patternIssues.filter((i) => i.type === "error-handling");
|
|
479
|
+
if (errorHandlingIssues.length > 0) {
|
|
480
|
+
recommendations.push(
|
|
481
|
+
"Standardize error handling strategy across the codebase (prefer try-catch with typed errors)"
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
const asyncIssues = patternIssues.filter((i) => i.type === "async-style");
|
|
485
|
+
if (asyncIssues.length > 0) {
|
|
486
|
+
recommendations.push(
|
|
487
|
+
"Use async/await consistently instead of mixing with promise chains or callbacks"
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
const importIssues = patternIssues.filter((i) => i.type === "import-style");
|
|
491
|
+
if (importIssues.length > 0) {
|
|
492
|
+
recommendations.push(
|
|
493
|
+
"Use ES modules consistently across the project (avoid mixing with CommonJS)"
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (recommendations.length === 0) {
|
|
498
|
+
recommendations.push("No major consistency issues found! Your codebase follows good practices.");
|
|
499
|
+
}
|
|
500
|
+
return recommendations;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export {
|
|
504
|
+
analyzeNaming,
|
|
505
|
+
detectNamingConventions,
|
|
506
|
+
analyzePatterns,
|
|
507
|
+
analyzeConsistency
|
|
508
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -31,6 +31,122 @@ var import_core3 = require("@aiready/core");
|
|
|
31
31
|
|
|
32
32
|
// src/analyzers/naming.ts
|
|
33
33
|
var import_core = require("@aiready/core");
|
|
34
|
+
var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
35
|
+
// Standard identifiers
|
|
36
|
+
"id",
|
|
37
|
+
"uid",
|
|
38
|
+
"gid",
|
|
39
|
+
"pid",
|
|
40
|
+
// Web/Network
|
|
41
|
+
"url",
|
|
42
|
+
"uri",
|
|
43
|
+
"api",
|
|
44
|
+
"cdn",
|
|
45
|
+
"dns",
|
|
46
|
+
"ip",
|
|
47
|
+
"tcp",
|
|
48
|
+
"udp",
|
|
49
|
+
"http",
|
|
50
|
+
"ssl",
|
|
51
|
+
"tls",
|
|
52
|
+
"utm",
|
|
53
|
+
"seo",
|
|
54
|
+
"rss",
|
|
55
|
+
"xhr",
|
|
56
|
+
"ajax",
|
|
57
|
+
// Data formats
|
|
58
|
+
"json",
|
|
59
|
+
"xml",
|
|
60
|
+
"yaml",
|
|
61
|
+
"csv",
|
|
62
|
+
"html",
|
|
63
|
+
"css",
|
|
64
|
+
"svg",
|
|
65
|
+
"pdf",
|
|
66
|
+
// Databases
|
|
67
|
+
"db",
|
|
68
|
+
"sql",
|
|
69
|
+
"orm",
|
|
70
|
+
"dao",
|
|
71
|
+
"dto",
|
|
72
|
+
// File system
|
|
73
|
+
"fs",
|
|
74
|
+
"dir",
|
|
75
|
+
"tmp",
|
|
76
|
+
"src",
|
|
77
|
+
"dst",
|
|
78
|
+
"bin",
|
|
79
|
+
"lib",
|
|
80
|
+
"pkg",
|
|
81
|
+
// Operating system
|
|
82
|
+
"os",
|
|
83
|
+
"env",
|
|
84
|
+
"arg",
|
|
85
|
+
"cli",
|
|
86
|
+
"cmd",
|
|
87
|
+
"exe",
|
|
88
|
+
// UI/UX
|
|
89
|
+
"ui",
|
|
90
|
+
"ux",
|
|
91
|
+
"gui",
|
|
92
|
+
"dom",
|
|
93
|
+
"ref",
|
|
94
|
+
// Request/Response
|
|
95
|
+
"req",
|
|
96
|
+
"res",
|
|
97
|
+
"ctx",
|
|
98
|
+
"err",
|
|
99
|
+
"msg",
|
|
100
|
+
// Mathematics/Computing
|
|
101
|
+
"max",
|
|
102
|
+
"min",
|
|
103
|
+
"avg",
|
|
104
|
+
"sum",
|
|
105
|
+
"abs",
|
|
106
|
+
"cos",
|
|
107
|
+
"sin",
|
|
108
|
+
"tan",
|
|
109
|
+
"log",
|
|
110
|
+
"exp",
|
|
111
|
+
"pow",
|
|
112
|
+
"sqrt",
|
|
113
|
+
"std",
|
|
114
|
+
"var",
|
|
115
|
+
"int",
|
|
116
|
+
"num",
|
|
117
|
+
// Time
|
|
118
|
+
"now",
|
|
119
|
+
"utc",
|
|
120
|
+
"tz",
|
|
121
|
+
"ms",
|
|
122
|
+
"sec",
|
|
123
|
+
// Common patterns
|
|
124
|
+
"app",
|
|
125
|
+
"cfg",
|
|
126
|
+
"config",
|
|
127
|
+
"init",
|
|
128
|
+
"len",
|
|
129
|
+
"val",
|
|
130
|
+
"str",
|
|
131
|
+
"obj",
|
|
132
|
+
"arr",
|
|
133
|
+
"gen",
|
|
134
|
+
"def",
|
|
135
|
+
"raw",
|
|
136
|
+
"new",
|
|
137
|
+
"old",
|
|
138
|
+
"pre",
|
|
139
|
+
"post",
|
|
140
|
+
"sub",
|
|
141
|
+
"pub",
|
|
142
|
+
// Boolean helpers (these are intentional short names)
|
|
143
|
+
"is",
|
|
144
|
+
"has",
|
|
145
|
+
"can",
|
|
146
|
+
"did",
|
|
147
|
+
"was",
|
|
148
|
+
"are"
|
|
149
|
+
]);
|
|
34
150
|
async function analyzeNaming(files) {
|
|
35
151
|
const issues = [];
|
|
36
152
|
for (const file of files) {
|
|
@@ -47,26 +163,30 @@ function analyzeFileNaming(file, content) {
|
|
|
47
163
|
const lineNumber = index + 1;
|
|
48
164
|
const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
|
|
49
165
|
for (const match of singleLetterMatches) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
166
|
+
const letter = match[1].toLowerCase();
|
|
167
|
+
const isInLoopContext = line.includes("for") || line.includes(".map") || line.includes(".filter") || line.includes(".forEach") || line.includes(".reduce");
|
|
168
|
+
if (!isInLoopContext && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
|
|
169
|
+
issues.push({
|
|
170
|
+
file,
|
|
171
|
+
line: lineNumber,
|
|
172
|
+
type: "poor-naming",
|
|
173
|
+
identifier: match[1],
|
|
174
|
+
severity: "minor",
|
|
175
|
+
suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
|
|
176
|
+
});
|
|
177
|
+
}
|
|
58
178
|
}
|
|
59
179
|
const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
|
|
60
180
|
for (const match of abbreviationMatches) {
|
|
61
|
-
const abbrev = match[1];
|
|
62
|
-
if (!
|
|
181
|
+
const abbrev = match[1].toLowerCase();
|
|
182
|
+
if (!ACCEPTABLE_ABBREVIATIONS.has(abbrev)) {
|
|
63
183
|
issues.push({
|
|
64
184
|
file,
|
|
65
185
|
line: lineNumber,
|
|
66
186
|
type: "abbreviation",
|
|
67
|
-
identifier:
|
|
187
|
+
identifier: match[1],
|
|
68
188
|
severity: "info",
|
|
69
|
-
suggestion: `Consider using full word instead of abbreviation '${
|
|
189
|
+
suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
|
|
70
190
|
});
|
|
71
191
|
}
|
|
72
192
|
}
|
|
@@ -101,7 +221,11 @@ function analyzeFileNaming(file, content) {
|
|
|
101
221
|
const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
|
|
102
222
|
for (const match of functionMatches) {
|
|
103
223
|
const name = match[1];
|
|
104
|
-
|
|
224
|
+
const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator)$/);
|
|
225
|
+
const isEventHandler = name.match(/^on[A-Z]/);
|
|
226
|
+
const isDescriptiveLong = name.length > 20;
|
|
227
|
+
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)/);
|
|
228
|
+
if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong) {
|
|
105
229
|
issues.push({
|
|
106
230
|
file,
|
|
107
231
|
line: lineNumber,
|
package/dist/cli.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -32,6 +32,122 @@ var import_core3 = require("@aiready/core");
|
|
|
32
32
|
|
|
33
33
|
// src/analyzers/naming.ts
|
|
34
34
|
var import_core = require("@aiready/core");
|
|
35
|
+
var ACCEPTABLE_ABBREVIATIONS = /* @__PURE__ */ new Set([
|
|
36
|
+
// Standard identifiers
|
|
37
|
+
"id",
|
|
38
|
+
"uid",
|
|
39
|
+
"gid",
|
|
40
|
+
"pid",
|
|
41
|
+
// Web/Network
|
|
42
|
+
"url",
|
|
43
|
+
"uri",
|
|
44
|
+
"api",
|
|
45
|
+
"cdn",
|
|
46
|
+
"dns",
|
|
47
|
+
"ip",
|
|
48
|
+
"tcp",
|
|
49
|
+
"udp",
|
|
50
|
+
"http",
|
|
51
|
+
"ssl",
|
|
52
|
+
"tls",
|
|
53
|
+
"utm",
|
|
54
|
+
"seo",
|
|
55
|
+
"rss",
|
|
56
|
+
"xhr",
|
|
57
|
+
"ajax",
|
|
58
|
+
// Data formats
|
|
59
|
+
"json",
|
|
60
|
+
"xml",
|
|
61
|
+
"yaml",
|
|
62
|
+
"csv",
|
|
63
|
+
"html",
|
|
64
|
+
"css",
|
|
65
|
+
"svg",
|
|
66
|
+
"pdf",
|
|
67
|
+
// Databases
|
|
68
|
+
"db",
|
|
69
|
+
"sql",
|
|
70
|
+
"orm",
|
|
71
|
+
"dao",
|
|
72
|
+
"dto",
|
|
73
|
+
// File system
|
|
74
|
+
"fs",
|
|
75
|
+
"dir",
|
|
76
|
+
"tmp",
|
|
77
|
+
"src",
|
|
78
|
+
"dst",
|
|
79
|
+
"bin",
|
|
80
|
+
"lib",
|
|
81
|
+
"pkg",
|
|
82
|
+
// Operating system
|
|
83
|
+
"os",
|
|
84
|
+
"env",
|
|
85
|
+
"arg",
|
|
86
|
+
"cli",
|
|
87
|
+
"cmd",
|
|
88
|
+
"exe",
|
|
89
|
+
// UI/UX
|
|
90
|
+
"ui",
|
|
91
|
+
"ux",
|
|
92
|
+
"gui",
|
|
93
|
+
"dom",
|
|
94
|
+
"ref",
|
|
95
|
+
// Request/Response
|
|
96
|
+
"req",
|
|
97
|
+
"res",
|
|
98
|
+
"ctx",
|
|
99
|
+
"err",
|
|
100
|
+
"msg",
|
|
101
|
+
// Mathematics/Computing
|
|
102
|
+
"max",
|
|
103
|
+
"min",
|
|
104
|
+
"avg",
|
|
105
|
+
"sum",
|
|
106
|
+
"abs",
|
|
107
|
+
"cos",
|
|
108
|
+
"sin",
|
|
109
|
+
"tan",
|
|
110
|
+
"log",
|
|
111
|
+
"exp",
|
|
112
|
+
"pow",
|
|
113
|
+
"sqrt",
|
|
114
|
+
"std",
|
|
115
|
+
"var",
|
|
116
|
+
"int",
|
|
117
|
+
"num",
|
|
118
|
+
// Time
|
|
119
|
+
"now",
|
|
120
|
+
"utc",
|
|
121
|
+
"tz",
|
|
122
|
+
"ms",
|
|
123
|
+
"sec",
|
|
124
|
+
// Common patterns
|
|
125
|
+
"app",
|
|
126
|
+
"cfg",
|
|
127
|
+
"config",
|
|
128
|
+
"init",
|
|
129
|
+
"len",
|
|
130
|
+
"val",
|
|
131
|
+
"str",
|
|
132
|
+
"obj",
|
|
133
|
+
"arr",
|
|
134
|
+
"gen",
|
|
135
|
+
"def",
|
|
136
|
+
"raw",
|
|
137
|
+
"new",
|
|
138
|
+
"old",
|
|
139
|
+
"pre",
|
|
140
|
+
"post",
|
|
141
|
+
"sub",
|
|
142
|
+
"pub",
|
|
143
|
+
// Boolean helpers (these are intentional short names)
|
|
144
|
+
"is",
|
|
145
|
+
"has",
|
|
146
|
+
"can",
|
|
147
|
+
"did",
|
|
148
|
+
"was",
|
|
149
|
+
"are"
|
|
150
|
+
]);
|
|
35
151
|
async function analyzeNaming(files) {
|
|
36
152
|
const issues = [];
|
|
37
153
|
for (const file of files) {
|
|
@@ -48,26 +164,30 @@ function analyzeFileNaming(file, content) {
|
|
|
48
164
|
const lineNumber = index + 1;
|
|
49
165
|
const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
|
|
50
166
|
for (const match of singleLetterMatches) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
167
|
+
const letter = match[1].toLowerCase();
|
|
168
|
+
const isInLoopContext = line.includes("for") || line.includes(".map") || line.includes(".filter") || line.includes(".forEach") || line.includes(".reduce");
|
|
169
|
+
if (!isInLoopContext && !["x", "y", "z", "i", "j", "k", "l", "n", "m"].includes(letter)) {
|
|
170
|
+
issues.push({
|
|
171
|
+
file,
|
|
172
|
+
line: lineNumber,
|
|
173
|
+
type: "poor-naming",
|
|
174
|
+
identifier: match[1],
|
|
175
|
+
severity: "minor",
|
|
176
|
+
suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
|
|
177
|
+
});
|
|
178
|
+
}
|
|
59
179
|
}
|
|
60
180
|
const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
|
|
61
181
|
for (const match of abbreviationMatches) {
|
|
62
|
-
const abbrev = match[1];
|
|
63
|
-
if (!
|
|
182
|
+
const abbrev = match[1].toLowerCase();
|
|
183
|
+
if (!ACCEPTABLE_ABBREVIATIONS.has(abbrev)) {
|
|
64
184
|
issues.push({
|
|
65
185
|
file,
|
|
66
186
|
line: lineNumber,
|
|
67
187
|
type: "abbreviation",
|
|
68
|
-
identifier:
|
|
188
|
+
identifier: match[1],
|
|
69
189
|
severity: "info",
|
|
70
|
-
suggestion: `Consider using full word instead of abbreviation '${
|
|
190
|
+
suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
|
|
71
191
|
});
|
|
72
192
|
}
|
|
73
193
|
}
|
|
@@ -102,7 +222,11 @@ function analyzeFileNaming(file, content) {
|
|
|
102
222
|
const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
|
|
103
223
|
for (const match of functionMatches) {
|
|
104
224
|
const name = match[1];
|
|
105
|
-
|
|
225
|
+
const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator)$/);
|
|
226
|
+
const isEventHandler = name.match(/^on[A-Z]/);
|
|
227
|
+
const isDescriptiveLong = name.length > 20;
|
|
228
|
+
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)/);
|
|
229
|
+
if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong) {
|
|
106
230
|
issues.push({
|
|
107
231
|
file,
|
|
108
232
|
line: lineNumber,
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/consistency",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.3",
|
|
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",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"chalk": "^5.3.0",
|
|
43
43
|
"commander": "^12.1.0",
|
|
44
|
-
"@aiready/core": "0.
|
|
44
|
+
"@aiready/core": "0.7.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/node": "^22.10.5",
|
|
@@ -44,6 +44,17 @@ const result = x + y;
|
|
|
44
44
|
expect(true).toBe(true);
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
+
it('should NOT flag acceptable abbreviations', () => {
|
|
48
|
+
// These should all be acceptable and NOT flagged
|
|
49
|
+
const acceptableAbbreviations = [
|
|
50
|
+
'env', 'req', 'res', 'ctx', 'err', 'api', 'url', 'id',
|
|
51
|
+
'max', 'min', 'now', 'utm', 'has', 'is', 'can',
|
|
52
|
+
'db', 'fs', 'os', 'ui', 'tmp', 'src', 'dst'
|
|
53
|
+
];
|
|
54
|
+
// These abbreviations should not trigger warnings
|
|
55
|
+
expect(acceptableAbbreviations.length).toBeGreaterThan(0);
|
|
56
|
+
});
|
|
57
|
+
|
|
47
58
|
it('should detect snake_case in TypeScript files', () => {
|
|
48
59
|
const testCode = `
|
|
49
60
|
const user_name = 'John';
|
package/src/analyzers/naming.ts
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
import { readFileContent } from '@aiready/core';
|
|
2
2
|
import type { NamingIssue } from '../types';
|
|
3
3
|
|
|
4
|
+
// Comprehensive list of acceptable abbreviations and acronyms
|
|
5
|
+
const ACCEPTABLE_ABBREVIATIONS = new Set([
|
|
6
|
+
// Standard identifiers
|
|
7
|
+
'id', 'uid', 'gid', 'pid',
|
|
8
|
+
// Web/Network
|
|
9
|
+
'url', 'uri', 'api', 'cdn', 'dns', 'ip', 'tcp', 'udp', 'http', 'ssl', 'tls',
|
|
10
|
+
'utm', 'seo', 'rss', 'xhr', 'ajax',
|
|
11
|
+
// Data formats
|
|
12
|
+
'json', 'xml', 'yaml', 'csv', 'html', 'css', 'svg', 'pdf',
|
|
13
|
+
// Databases
|
|
14
|
+
'db', 'sql', 'orm', 'dao', 'dto',
|
|
15
|
+
// File system
|
|
16
|
+
'fs', 'dir', 'tmp', 'src', 'dst', 'bin', 'lib', 'pkg',
|
|
17
|
+
// Operating system
|
|
18
|
+
'os', 'env', 'arg', 'cli', 'cmd', 'exe',
|
|
19
|
+
// UI/UX
|
|
20
|
+
'ui', 'ux', 'gui', 'dom', 'ref',
|
|
21
|
+
// Request/Response
|
|
22
|
+
'req', 'res', 'ctx', 'err', 'msg',
|
|
23
|
+
// Mathematics/Computing
|
|
24
|
+
'max', 'min', 'avg', 'sum', 'abs', 'cos', 'sin', 'tan', 'log', 'exp',
|
|
25
|
+
'pow', 'sqrt', 'std', 'var', 'int', 'num',
|
|
26
|
+
// Time
|
|
27
|
+
'now', 'utc', 'tz', 'ms', 'sec',
|
|
28
|
+
// Common patterns
|
|
29
|
+
'app', 'cfg', 'config', 'init', 'len', 'val', 'str', 'obj', 'arr',
|
|
30
|
+
'gen', 'def', 'raw', 'new', 'old', 'pre', 'post', 'sub', 'pub',
|
|
31
|
+
// Boolean helpers (these are intentional short names)
|
|
32
|
+
'is', 'has', 'can', 'did', 'was', 'are'
|
|
33
|
+
]);
|
|
34
|
+
|
|
4
35
|
/**
|
|
5
36
|
* Analyzes naming conventions and quality
|
|
6
37
|
*/
|
|
@@ -26,32 +57,39 @@ function analyzeFileNaming(file: string, content: string): NamingIssue[] {
|
|
|
26
57
|
lines.forEach((line, index) => {
|
|
27
58
|
const lineNumber = index + 1;
|
|
28
59
|
|
|
29
|
-
// Check for single letter variables (except i, j, k in loops)
|
|
60
|
+
// Check for single letter variables (except i, j, k, l in loops/common contexts)
|
|
30
61
|
const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
|
|
31
62
|
for (const match of singleLetterMatches) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
63
|
+
const letter = match[1].toLowerCase();
|
|
64
|
+
// Skip if it's in a loop context or common iterator
|
|
65
|
+
const isInLoopContext = line.includes('for') || line.includes('.map') ||
|
|
66
|
+
line.includes('.filter') || line.includes('.forEach') ||
|
|
67
|
+
line.includes('.reduce');
|
|
68
|
+
if (!isInLoopContext && !['x', 'y', 'z', 'i', 'j', 'k', 'l', 'n', 'm'].includes(letter)) {
|
|
69
|
+
issues.push({
|
|
70
|
+
file,
|
|
71
|
+
line: lineNumber,
|
|
72
|
+
type: 'poor-naming',
|
|
73
|
+
identifier: match[1],
|
|
74
|
+
severity: 'minor',
|
|
75
|
+
suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
|
|
76
|
+
});
|
|
77
|
+
}
|
|
40
78
|
}
|
|
41
79
|
|
|
42
80
|
// Check for overly abbreviated variables
|
|
43
81
|
const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
|
|
44
82
|
for (const match of abbreviationMatches) {
|
|
45
|
-
const abbrev = match[1];
|
|
46
|
-
// Skip
|
|
47
|
-
if (!
|
|
83
|
+
const abbrev = match[1].toLowerCase();
|
|
84
|
+
// Skip acceptable abbreviations
|
|
85
|
+
if (!ACCEPTABLE_ABBREVIATIONS.has(abbrev)) {
|
|
48
86
|
issues.push({
|
|
49
87
|
file,
|
|
50
88
|
line: lineNumber,
|
|
51
89
|
type: 'abbreviation',
|
|
52
|
-
identifier:
|
|
90
|
+
identifier: match[1],
|
|
53
91
|
severity: 'info',
|
|
54
|
-
suggestion: `Consider using full word instead of abbreviation '${
|
|
92
|
+
suggestion: `Consider using full word instead of abbreviation '${match[1]}'`
|
|
55
93
|
});
|
|
56
94
|
}
|
|
57
95
|
}
|
|
@@ -93,8 +131,16 @@ function analyzeFileNaming(file: string, content: string): NamingIssue[] {
|
|
|
93
131
|
const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
|
|
94
132
|
for (const match of functionMatches) {
|
|
95
133
|
const name = match[1];
|
|
96
|
-
// Functions should typically start with verbs
|
|
97
|
-
|
|
134
|
+
// Functions should typically start with verbs, but allow:
|
|
135
|
+
// 1. Factory/builder patterns (ends with Factory, Builder, etc.)
|
|
136
|
+
// 2. Descriptive compound names that explain what they return
|
|
137
|
+
// 3. Event handlers (onClick, onSubmit, etc.)
|
|
138
|
+
const isFactoryPattern = name.match(/(Factory|Builder|Creator|Generator)$/);
|
|
139
|
+
const isEventHandler = name.match(/^on[A-Z]/);
|
|
140
|
+
const isDescriptiveLong = name.length > 20; // Long names are usually descriptive enough
|
|
141
|
+
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)/);
|
|
142
|
+
|
|
143
|
+
if (!hasActionVerb && !isFactoryPattern && !isEventHandler && !isDescriptiveLong) {
|
|
98
144
|
issues.push({
|
|
99
145
|
file,
|
|
100
146
|
line: lineNumber,
|