@aiready/consistency 0.18.11 → 0.18.14
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 +12 -11
- package/dist/chunk-V2UPXL7L.mjs +842 -0
- package/dist/cli.js +4 -4
- package/dist/cli.mjs +1 -1
- package/dist/index.js +4 -4
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
- package/src/__tests__/analyzer.test.ts +2 -1
- package/src/analyzers/naming-python.ts +5 -5
- package/src/utils/config-loader.ts +7 -3
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/consistency@0.18.
|
|
3
|
+
> @aiready/consistency@0.18.13 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
|
-
[
|
|
13
|
-
[32mCJS[39m [1mdist/index.js [22m[32m34.59 KB[39m
|
|
14
|
-
[32mCJS[39m ⚡️ Build success in 132ms
|
|
12
|
+
[32mESM[39m [1mdist/chunk-V2UPXL7L.mjs [22m[32m25.18 KB[39m
|
|
15
13
|
[32mESM[39m [1mdist/cli.mjs [22m[32m8.83 KB[39m
|
|
16
14
|
[32mESM[39m [1mdist/index.mjs [22m[32m7.17 KB[39m
|
|
17
|
-
[32mESM[39m
|
|
18
|
-
[
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 52ms
|
|
16
|
+
[32mCJS[39m [1mdist/index.js [22m[32m34.67 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/cli.js [22m[32m36.07 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 52ms
|
|
19
19
|
DTS Build start
|
|
20
|
-
DTS ⚡️ Build success in
|
|
20
|
+
DTS ⚡️ Build success in 2525ms
|
|
21
21
|
DTS dist/cli.d.ts 20.00 B
|
|
22
22
|
DTS dist/index.d.ts 3.48 KB
|
|
23
23
|
DTS dist/cli.d.mts 20.00 B
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/consistency@0.18.
|
|
3
|
+
> @aiready/consistency@0.18.13 test /Users/pengcao/projects/aiready/packages/consistency
|
|
4
4
|
> vitest run
|
|
5
5
|
|
|
6
6
|
[?25l
|
|
7
7
|
[1m[46m RUN [49m[22m [36mv4.0.18 [39m[90m/Users/pengcao/projects/aiready/packages/consistency[39m
|
|
8
8
|
|
|
9
|
-
[32m✓[39m dist/__tests__/scoring.test.js [2m([22m[2m8 tests[22m[2m)[22m[32m
|
|
10
|
-
[32m✓[39m dist/__tests__/language-filter.test.js [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
11
|
-
[32m✓[39m src/__tests__/language-filter.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 2[2mms[22m[39m
|
|
12
|
-
[32m✓[39m src/__tests__/scoring.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 38[2mms[22m[39m
|
|
9
|
+
[32m✓[39m dist/__tests__/scoring.test.js [2m([22m[2m8 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
10
|
+
[32m✓[39m dist/__tests__/language-filter.test.js [2m([22m[2m3 tests[22m[2m)[22m[32m 3[2mms[22m[39m
|
|
13
11
|
[90mstdout[2m | src/__tests__/contract.test.ts
|
|
14
|
-
[22m[39m[ToolRegistry#0.
|
|
12
|
+
[22m[39m[ToolRegistry#0.3102922585477208] Registering tool: naming-consistency (consistency, naming, standards)
|
|
15
13
|
|
|
16
|
-
[32m✓[39m src/__tests__/contract.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m
|
|
17
|
-
[32m✓[39m src/__tests__/
|
|
18
|
-
[32m✓[39m
|
|
14
|
+
[32m✓[39m src/__tests__/contract.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 3[2mms[22m[39m
|
|
15
|
+
[32m✓[39m src/__tests__/scoring.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 111[2mms[22m[39m
|
|
16
|
+
[32m✓[39m src/__tests__/language-filter.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 3[2mms[22m[39m
|
|
17
|
+
[32m✓[39m src/__tests__/analyzer.test.ts [2m([22m[2m18 tests[22m[2m)[22m[33m 533[2mms[22m[39m
|
|
18
|
+
[33m[2m✓[22m[39m should analyze naming issues [33m 332[2mms[22m[39m
|
|
19
|
+
[32m✓[39m dist/__tests__/analyzer.test.js [2m([22m[2m18 tests[22m[2m)[22m[33m 579[2mms[22m[39m
|
|
19
20
|
|
|
20
21
|
[2m Test Files [22m [1m[32m7 passed[39m[22m[90m (7)[39m
|
|
21
22
|
[2m Tests [22m [1m[32m59 passed[39m[22m[90m (59)[39m
|
|
22
|
-
[2m Start at [22m
|
|
23
|
-
[2m Duration [22m
|
|
23
|
+
[2m Start at [22m 00:03:48
|
|
24
|
+
[2m Duration [22m 1.84s[2m (transform 918ms, setup 0ms, import 5.31s, tests 1.24s, environment 1ms)[22m
|
|
24
25
|
|
|
25
26
|
[?25h
|
|
@@ -0,0 +1,842 @@
|
|
|
1
|
+
// src/analyzers/naming-ast.ts
|
|
2
|
+
import { Severity as Severity2 } from "@aiready/core";
|
|
3
|
+
|
|
4
|
+
// src/utils/ast-parser.ts
|
|
5
|
+
import { parse } from "@typescript-eslint/typescript-estree";
|
|
6
|
+
import { readFileSync } from "fs";
|
|
7
|
+
function parseFile(filePath, content) {
|
|
8
|
+
try {
|
|
9
|
+
const code = content ?? readFileSync(filePath, "utf-8");
|
|
10
|
+
const isTypeScript = filePath.match(/\.tsx?$/);
|
|
11
|
+
return parse(code, {
|
|
12
|
+
jsx: filePath.match(/\.[jt]sx$/i) !== null,
|
|
13
|
+
loc: true,
|
|
14
|
+
range: true,
|
|
15
|
+
comment: false,
|
|
16
|
+
tokens: false,
|
|
17
|
+
// Relaxed parsing for JavaScript files
|
|
18
|
+
sourceType: "module",
|
|
19
|
+
ecmaVersion: "latest",
|
|
20
|
+
// Only use TypeScript parser features for .ts/.tsx files
|
|
21
|
+
filePath: isTypeScript ? filePath : void 0
|
|
22
|
+
});
|
|
23
|
+
} catch (error) {
|
|
24
|
+
void error;
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function traverseAST(node, visitor, parent = null) {
|
|
29
|
+
if (!node) return;
|
|
30
|
+
visitor.enter?.(node, parent);
|
|
31
|
+
for (const key of Object.keys(node)) {
|
|
32
|
+
const value = node[key];
|
|
33
|
+
if (Array.isArray(value)) {
|
|
34
|
+
for (const child of value) {
|
|
35
|
+
if (child && typeof child === "object" && "type" in child) {
|
|
36
|
+
traverseAST(child, visitor, node);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} else if (value && typeof value === "object" && "type" in value) {
|
|
40
|
+
traverseAST(value, visitor, node);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
visitor.leave?.(node, parent);
|
|
44
|
+
}
|
|
45
|
+
function isLoopStatement(node) {
|
|
46
|
+
return [
|
|
47
|
+
"ForStatement",
|
|
48
|
+
"ForInStatement",
|
|
49
|
+
"ForOfStatement",
|
|
50
|
+
"WhileStatement",
|
|
51
|
+
"DoWhileStatement"
|
|
52
|
+
].includes(node.type);
|
|
53
|
+
}
|
|
54
|
+
function getLineNumber(node) {
|
|
55
|
+
return node.loc?.start.line ?? 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/utils/context-detector.ts
|
|
59
|
+
import { Severity } from "@aiready/core";
|
|
60
|
+
function detectFileType(filePath, ast) {
|
|
61
|
+
void ast;
|
|
62
|
+
const path = filePath.toLowerCase();
|
|
63
|
+
if (path.match(/\.(test|spec)\.(ts|tsx|js|jsx)$/) || path.includes("__tests__")) {
|
|
64
|
+
return "test";
|
|
65
|
+
}
|
|
66
|
+
if (path.endsWith(".d.ts") || path.includes("types")) {
|
|
67
|
+
return "types";
|
|
68
|
+
}
|
|
69
|
+
if (path.match(/config|\.config\.|rc\.|setup/) || path.includes("configuration")) {
|
|
70
|
+
return "config";
|
|
71
|
+
}
|
|
72
|
+
return "production";
|
|
73
|
+
}
|
|
74
|
+
function detectCodeLayer(ast) {
|
|
75
|
+
let hasAPIIndicators = 0;
|
|
76
|
+
let hasBusinessIndicators = 0;
|
|
77
|
+
let hasDataIndicators = 0;
|
|
78
|
+
let hasUtilityIndicators = 0;
|
|
79
|
+
traverseAST(ast, {
|
|
80
|
+
enter: (node) => {
|
|
81
|
+
if (node.type === "ImportDeclaration") {
|
|
82
|
+
const source = node.source.value;
|
|
83
|
+
if (source.match(/express|fastify|koa|@nestjs|axios|fetch|http/i)) {
|
|
84
|
+
hasAPIIndicators++;
|
|
85
|
+
}
|
|
86
|
+
if (source.match(/database|prisma|typeorm|sequelize|mongoose|pg|mysql/i)) {
|
|
87
|
+
hasDataIndicators++;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (node.type === "FunctionDeclaration" && node.id) {
|
|
91
|
+
const name = node.id.name;
|
|
92
|
+
if (name.match(
|
|
93
|
+
/^(get|post|put|delete|patch|handle|api|route|controller)/i
|
|
94
|
+
)) {
|
|
95
|
+
hasAPIIndicators++;
|
|
96
|
+
}
|
|
97
|
+
if (name.match(/^(calculate|process|validate|transform|compute|analyze)/i)) {
|
|
98
|
+
hasBusinessIndicators++;
|
|
99
|
+
}
|
|
100
|
+
if (name.match(/^(find|create|update|delete|save|fetch|query|insert)/i)) {
|
|
101
|
+
hasDataIndicators++;
|
|
102
|
+
}
|
|
103
|
+
if (name.match(
|
|
104
|
+
/^(format|parse|convert|normalize|sanitize|encode|decode)/i
|
|
105
|
+
)) {
|
|
106
|
+
hasUtilityIndicators++;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") {
|
|
110
|
+
if (node.type === "ExportNamedDeclaration" && node.declaration) {
|
|
111
|
+
if (node.declaration.type === "FunctionDeclaration" && node.declaration.id) {
|
|
112
|
+
const name = node.declaration.id.name;
|
|
113
|
+
if (name.match(/handler|route|api|controller/i)) {
|
|
114
|
+
hasAPIIndicators += 2;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
const scores = {
|
|
122
|
+
api: hasAPIIndicators,
|
|
123
|
+
business: hasBusinessIndicators,
|
|
124
|
+
data: hasDataIndicators,
|
|
125
|
+
utility: hasUtilityIndicators
|
|
126
|
+
};
|
|
127
|
+
const maxScore = Math.max(...Object.values(scores));
|
|
128
|
+
if (maxScore === 0) {
|
|
129
|
+
return "unknown";
|
|
130
|
+
}
|
|
131
|
+
if (scores.api === maxScore) return "api";
|
|
132
|
+
if (scores.data === maxScore) return "data";
|
|
133
|
+
if (scores.business === maxScore) return "business";
|
|
134
|
+
if (scores.utility === maxScore) return "utility";
|
|
135
|
+
return "unknown";
|
|
136
|
+
}
|
|
137
|
+
function calculateComplexity(node) {
|
|
138
|
+
let complexity = 1;
|
|
139
|
+
traverseAST(node, {
|
|
140
|
+
enter: (childNode) => {
|
|
141
|
+
switch (childNode.type) {
|
|
142
|
+
case "IfStatement":
|
|
143
|
+
case "ConditionalExpression":
|
|
144
|
+
// ternary
|
|
145
|
+
case "SwitchCase":
|
|
146
|
+
case "ForStatement":
|
|
147
|
+
case "ForInStatement":
|
|
148
|
+
case "ForOfStatement":
|
|
149
|
+
case "WhileStatement":
|
|
150
|
+
case "DoWhileStatement":
|
|
151
|
+
case "CatchClause":
|
|
152
|
+
complexity++;
|
|
153
|
+
break;
|
|
154
|
+
case "LogicalExpression":
|
|
155
|
+
if (childNode.operator === "&&" || childNode.operator === "||") {
|
|
156
|
+
complexity++;
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
return complexity;
|
|
163
|
+
}
|
|
164
|
+
function buildCodeContext(filePath, ast) {
|
|
165
|
+
const fileType = detectFileType(filePath, ast);
|
|
166
|
+
const codeLayer = detectCodeLayer(ast);
|
|
167
|
+
let totalComplexity = 0;
|
|
168
|
+
let functionCount = 0;
|
|
169
|
+
traverseAST(ast, {
|
|
170
|
+
enter: (node) => {
|
|
171
|
+
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
|
|
172
|
+
totalComplexity += calculateComplexity(node);
|
|
173
|
+
functionCount++;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
const avgComplexity = functionCount > 0 ? totalComplexity / functionCount : 1;
|
|
178
|
+
return {
|
|
179
|
+
fileType,
|
|
180
|
+
codeLayer,
|
|
181
|
+
complexity: Math.round(avgComplexity),
|
|
182
|
+
isTestFile: fileType === "test",
|
|
183
|
+
isTypeDefinition: fileType === "types"
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function adjustSeverity(baseSeverity, context, issueType) {
|
|
187
|
+
const getEnum = (s) => {
|
|
188
|
+
if (s === Severity.Critical || s === "critical") return Severity.Critical;
|
|
189
|
+
if (s === Severity.Major || s === "major") return Severity.Major;
|
|
190
|
+
if (s === Severity.Minor || s === "minor") return Severity.Minor;
|
|
191
|
+
return Severity.Info;
|
|
192
|
+
};
|
|
193
|
+
let currentSev = getEnum(baseSeverity);
|
|
194
|
+
if (context.isTestFile) {
|
|
195
|
+
if (currentSev === Severity.Minor) currentSev = Severity.Info;
|
|
196
|
+
if (currentSev === Severity.Major) currentSev = Severity.Minor;
|
|
197
|
+
}
|
|
198
|
+
if (context.isTypeDefinition) {
|
|
199
|
+
if (currentSev === Severity.Minor) currentSev = Severity.Info;
|
|
200
|
+
}
|
|
201
|
+
if (context.codeLayer === "api") {
|
|
202
|
+
if (currentSev === Severity.Info && issueType === "unclear")
|
|
203
|
+
currentSev = Severity.Minor;
|
|
204
|
+
if (currentSev === Severity.Minor && issueType === "unclear")
|
|
205
|
+
currentSev = Severity.Major;
|
|
206
|
+
}
|
|
207
|
+
if (context.complexity > 10) {
|
|
208
|
+
if (currentSev === Severity.Info) currentSev = Severity.Minor;
|
|
209
|
+
}
|
|
210
|
+
if (context.codeLayer === "utility") {
|
|
211
|
+
if (currentSev === Severity.Minor && issueType === "abbreviation")
|
|
212
|
+
currentSev = Severity.Info;
|
|
213
|
+
}
|
|
214
|
+
return currentSev;
|
|
215
|
+
}
|
|
216
|
+
function isAcceptableInContext(name, context, options) {
|
|
217
|
+
if (options.isLoopVariable && ["i", "j", "k", "l", "n", "m"].includes(name)) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
if (context.isTestFile) {
|
|
221
|
+
if (["a", "b", "c", "x", "y", "z"].includes(name) && options.isParameter) {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (context.codeLayer === "utility" && ["x", "y", "z"].includes(name)) {
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
if (options.isDestructured) {
|
|
229
|
+
if (["s", "b", "f", "l"].includes(name)) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (options.isParameter && (options.complexity ?? context.complexity) < 3) {
|
|
234
|
+
if (name.length >= 2) {
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/analyzers/naming-ast.ts
|
|
242
|
+
async function analyzeNamingAST(filePaths) {
|
|
243
|
+
const allIssues = [];
|
|
244
|
+
for (const filePath of filePaths) {
|
|
245
|
+
try {
|
|
246
|
+
const ast = parseFile(filePath);
|
|
247
|
+
if (!ast) continue;
|
|
248
|
+
const context = buildCodeContext(filePath, ast);
|
|
249
|
+
const issues = analyzeIdentifiers(ast, filePath, context);
|
|
250
|
+
allIssues.push(...issues);
|
|
251
|
+
} catch (err) {
|
|
252
|
+
void err;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return allIssues;
|
|
256
|
+
}
|
|
257
|
+
function analyzeIdentifiers(ast, filePath, context) {
|
|
258
|
+
const issues = [];
|
|
259
|
+
const scopeTracker = new ScopeTracker();
|
|
260
|
+
traverseAST(ast, {
|
|
261
|
+
enter: (node) => {
|
|
262
|
+
if (node.type === "VariableDeclarator" && node.id.type === "Identifier") {
|
|
263
|
+
const isParameter = false;
|
|
264
|
+
const isLoopVariable = isLoopStatement(node.parent?.parent);
|
|
265
|
+
scopeTracker.declareVariable(
|
|
266
|
+
node.id.name,
|
|
267
|
+
node.id,
|
|
268
|
+
getLineNumber(node.id),
|
|
269
|
+
{ isParameter, isLoopVariable }
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
|
|
273
|
+
node.params.forEach((param) => {
|
|
274
|
+
if (param.type === "Identifier") {
|
|
275
|
+
scopeTracker.declareVariable(
|
|
276
|
+
param.name,
|
|
277
|
+
param,
|
|
278
|
+
getLineNumber(param),
|
|
279
|
+
{ isParameter: true }
|
|
280
|
+
);
|
|
281
|
+
} else if (param.type === "ObjectPattern") {
|
|
282
|
+
extractDestructuredIdentifiers(param, true, scopeTracker);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
if ((node.type === "ClassDeclaration" || node.type === "TSInterfaceDeclaration" || node.type === "TSTypeAliasDeclaration") && node.id) {
|
|
287
|
+
checkNamingConvention(
|
|
288
|
+
node.id.name,
|
|
289
|
+
"PascalCase",
|
|
290
|
+
node.id,
|
|
291
|
+
filePath,
|
|
292
|
+
issues,
|
|
293
|
+
context
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
for (const varInfo of scopeTracker.getVariables()) {
|
|
299
|
+
checkVariableNaming(varInfo, filePath, issues, context);
|
|
300
|
+
}
|
|
301
|
+
return issues;
|
|
302
|
+
}
|
|
303
|
+
function checkNamingConvention(name, convention, node, file, issues, context) {
|
|
304
|
+
let isValid = true;
|
|
305
|
+
if (convention === "PascalCase") {
|
|
306
|
+
isValid = /^[A-Z][a-zA-Z0-9]*$/.test(name);
|
|
307
|
+
} else if (convention === "camelCase") {
|
|
308
|
+
isValid = /^[a-z][a-zA-Z0-9]*$/.test(name);
|
|
309
|
+
} else if (convention === "UPPER_CASE") {
|
|
310
|
+
isValid = /^[A-Z][A-Z0-9_]*$/.test(name);
|
|
311
|
+
}
|
|
312
|
+
if (!isValid) {
|
|
313
|
+
const severity = adjustSeverity(Severity2.Info, context, "convention-mix");
|
|
314
|
+
issues.push({
|
|
315
|
+
file,
|
|
316
|
+
line: getLineNumber(node),
|
|
317
|
+
type: "convention-mix",
|
|
318
|
+
identifier: name,
|
|
319
|
+
severity,
|
|
320
|
+
suggestion: `Follow ${convention} for this identifier`
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function checkVariableNaming(varInfo, file, issues, context) {
|
|
325
|
+
const { name, node, line, options } = varInfo;
|
|
326
|
+
if (isAcceptableInContext(name, context, options)) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (name.length === 1 && !options.isLoopVariable) {
|
|
330
|
+
const severity = adjustSeverity(Severity2.Minor, context, "poor-naming");
|
|
331
|
+
issues.push({
|
|
332
|
+
file,
|
|
333
|
+
line,
|
|
334
|
+
type: "poor-naming",
|
|
335
|
+
identifier: name,
|
|
336
|
+
severity,
|
|
337
|
+
suggestion: "Use a more descriptive name than a single letter"
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
const vagueNames = [
|
|
341
|
+
"data",
|
|
342
|
+
"info",
|
|
343
|
+
"item",
|
|
344
|
+
"obj",
|
|
345
|
+
"val",
|
|
346
|
+
"tmp",
|
|
347
|
+
"temp",
|
|
348
|
+
"thing",
|
|
349
|
+
"stuff"
|
|
350
|
+
];
|
|
351
|
+
if (vagueNames.includes(name.toLowerCase())) {
|
|
352
|
+
const severity = adjustSeverity(Severity2.Minor, context, "poor-naming");
|
|
353
|
+
issues.push({
|
|
354
|
+
file,
|
|
355
|
+
line,
|
|
356
|
+
type: "poor-naming",
|
|
357
|
+
identifier: name,
|
|
358
|
+
severity,
|
|
359
|
+
suggestion: `Avoid vague names like '${name}'. What does this data represent?`
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
if (name.length > 1 && name.length <= 3 && !options.isLoopVariable && !isCommonAbbreviation(name)) {
|
|
363
|
+
const severity = adjustSeverity(Severity2.Info, context, "abbreviation");
|
|
364
|
+
issues.push({
|
|
365
|
+
file,
|
|
366
|
+
line,
|
|
367
|
+
type: "abbreviation",
|
|
368
|
+
identifier: name,
|
|
369
|
+
severity,
|
|
370
|
+
suggestion: "Avoid non-standard abbreviations"
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function isCommonAbbreviation(name) {
|
|
375
|
+
const common = [
|
|
376
|
+
"id",
|
|
377
|
+
"db",
|
|
378
|
+
"fs",
|
|
379
|
+
"os",
|
|
380
|
+
"ip",
|
|
381
|
+
"ui",
|
|
382
|
+
"ux",
|
|
383
|
+
"api",
|
|
384
|
+
"env",
|
|
385
|
+
"url"
|
|
386
|
+
];
|
|
387
|
+
return common.includes(name.toLowerCase());
|
|
388
|
+
}
|
|
389
|
+
var ScopeTracker = class {
|
|
390
|
+
constructor() {
|
|
391
|
+
this.variables = [];
|
|
392
|
+
}
|
|
393
|
+
declareVariable(name, node, line, options = {}) {
|
|
394
|
+
this.variables.push({ name, node, line, options });
|
|
395
|
+
}
|
|
396
|
+
getVariables() {
|
|
397
|
+
return this.variables;
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
function extractDestructuredIdentifiers(node, isParameter, scopeTracker) {
|
|
401
|
+
if (node.type === "ObjectPattern") {
|
|
402
|
+
node.properties.forEach((prop) => {
|
|
403
|
+
if (prop.type === "Property" && prop.value.type === "Identifier") {
|
|
404
|
+
scopeTracker.declareVariable(
|
|
405
|
+
prop.value.name,
|
|
406
|
+
prop.value,
|
|
407
|
+
getLineNumber(prop.value),
|
|
408
|
+
{
|
|
409
|
+
isParameter,
|
|
410
|
+
isDestructured: true
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
} else if (node.type === "ArrayPattern") {
|
|
416
|
+
for (const element of node.elements) {
|
|
417
|
+
if (element?.type === "Identifier") {
|
|
418
|
+
scopeTracker.declareVariable(
|
|
419
|
+
element.name,
|
|
420
|
+
element,
|
|
421
|
+
getLineNumber(element),
|
|
422
|
+
{
|
|
423
|
+
isParameter,
|
|
424
|
+
isDestructured: true
|
|
425
|
+
}
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/analyzers/patterns.ts
|
|
433
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
434
|
+
import { Severity as Severity3 } from "@aiready/core";
|
|
435
|
+
async function analyzePatterns(filePaths) {
|
|
436
|
+
const issues = [];
|
|
437
|
+
const contents = /* @__PURE__ */ new Map();
|
|
438
|
+
const tryCatchPattern = /try\s*\{/g;
|
|
439
|
+
const catchPattern = /catch\s*\(\s*(\w+)\s*\)/g;
|
|
440
|
+
const styleStats = {
|
|
441
|
+
tryCatch: 0,
|
|
442
|
+
thenCatch: 0,
|
|
443
|
+
asyncAwait: 0,
|
|
444
|
+
commonJs: 0,
|
|
445
|
+
esm: 0
|
|
446
|
+
};
|
|
447
|
+
for (const filePath of filePaths) {
|
|
448
|
+
try {
|
|
449
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
450
|
+
contents.set(filePath, content);
|
|
451
|
+
if (content.match(tryCatchPattern)) styleStats.tryCatch++;
|
|
452
|
+
if (content.match(/\.catch\s*\(/)) styleStats.thenCatch++;
|
|
453
|
+
if (content.match(/\bawait\b/)) styleStats.asyncAwait++;
|
|
454
|
+
if (content.match(/\brequire\s*\(/)) styleStats.commonJs++;
|
|
455
|
+
if (content.match(/\bimport\b.*\bfrom\b/)) styleStats.esm++;
|
|
456
|
+
} catch (err) {
|
|
457
|
+
void err;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
const totalFiles = filePaths.length;
|
|
461
|
+
if (styleStats.tryCatch > 0 && styleStats.thenCatch > 0) {
|
|
462
|
+
const dominant = styleStats.tryCatch >= styleStats.thenCatch ? "try-catch" : ".catch()";
|
|
463
|
+
const minority = dominant === "try-catch" ? ".catch()" : "try-catch";
|
|
464
|
+
issues.push({
|
|
465
|
+
files: filePaths.filter((f) => {
|
|
466
|
+
const c = contents.get(f) || "";
|
|
467
|
+
return minority === "try-catch" ? c.match(tryCatchPattern) : c.match(/\.catch\s*\(/);
|
|
468
|
+
}),
|
|
469
|
+
type: "error-handling",
|
|
470
|
+
description: `Mixed error handling styles: codebase primarily uses ${dominant}, but found ${minority} in some files.`,
|
|
471
|
+
examples: [dominant, minority],
|
|
472
|
+
severity: Severity3.Minor
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
if (styleStats.commonJs > 0 && styleStats.esm > 0) {
|
|
476
|
+
const minority = styleStats.esm >= styleStats.commonJs ? "CommonJS (require)" : "ESM (import)";
|
|
477
|
+
issues.push({
|
|
478
|
+
files: filePaths.filter((f) => {
|
|
479
|
+
const c = contents.get(f) || "";
|
|
480
|
+
return minority === "CommonJS (require)" ? c.match(/\brequire\s*\(/) : c.match(/\bimport\b/);
|
|
481
|
+
}),
|
|
482
|
+
type: "import-style",
|
|
483
|
+
description: `Mixed module systems: found both ESM and CommonJS.`,
|
|
484
|
+
examples: ['import X from "y"', 'const X = require("y")'],
|
|
485
|
+
severity: Severity3.Major
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
return issues;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/analyzer.ts
|
|
492
|
+
import {
|
|
493
|
+
scanFiles,
|
|
494
|
+
Severity as Severity5,
|
|
495
|
+
IssueType,
|
|
496
|
+
GLOBAL_SCAN_OPTIONS
|
|
497
|
+
} from "@aiready/core";
|
|
498
|
+
|
|
499
|
+
// src/analyzers/naming-python.ts
|
|
500
|
+
import { getParser, Severity as Severity4 } from "@aiready/core";
|
|
501
|
+
async function analyzePythonNaming(files) {
|
|
502
|
+
const issues = [];
|
|
503
|
+
const parser = getParser("dummy.py");
|
|
504
|
+
if (!parser) {
|
|
505
|
+
console.warn("Python parser not available");
|
|
506
|
+
return issues;
|
|
507
|
+
}
|
|
508
|
+
const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
|
|
509
|
+
for (const file of pythonFiles) {
|
|
510
|
+
try {
|
|
511
|
+
const fs = await import("fs");
|
|
512
|
+
const code = await fs.promises.readFile(file, "utf-8");
|
|
513
|
+
const result = parser.parse(code, file);
|
|
514
|
+
for (const exp of result.exports) {
|
|
515
|
+
const nameIssue = checkPythonNaming(
|
|
516
|
+
exp.name,
|
|
517
|
+
exp.type,
|
|
518
|
+
file,
|
|
519
|
+
exp.loc?.start.line || 0
|
|
520
|
+
);
|
|
521
|
+
if (nameIssue) {
|
|
522
|
+
issues.push(nameIssue);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
for (const imp of result.imports) {
|
|
526
|
+
for (const spec of imp.specifiers) {
|
|
527
|
+
if (spec !== "*" && spec !== "default") {
|
|
528
|
+
const nameIssue = checkPythonNaming(
|
|
529
|
+
spec,
|
|
530
|
+
"variable",
|
|
531
|
+
file,
|
|
532
|
+
imp.loc?.start.line || 0
|
|
533
|
+
);
|
|
534
|
+
if (nameIssue) {
|
|
535
|
+
issues.push(nameIssue);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
} catch (error) {
|
|
541
|
+
console.warn(`Skipping ${file} due to error:`, error);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return issues;
|
|
545
|
+
}
|
|
546
|
+
function checkPythonNaming(identifier, type, file, line) {
|
|
547
|
+
const parser = getParser("dummy.py");
|
|
548
|
+
const conventions = parser?.getNamingConventions();
|
|
549
|
+
if (!conventions) return null;
|
|
550
|
+
if (conventions.exceptions?.includes(identifier)) {
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
if (type === "class") {
|
|
554
|
+
if (!conventions.classPattern.test(identifier)) {
|
|
555
|
+
return {
|
|
556
|
+
type: "poor-naming",
|
|
557
|
+
identifier,
|
|
558
|
+
file,
|
|
559
|
+
line,
|
|
560
|
+
column: 0,
|
|
561
|
+
severity: Severity4.Major,
|
|
562
|
+
category: "naming",
|
|
563
|
+
suggestion: `Class names should use PascalCase (e.g., ${toPascalCase(identifier)})`
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
} else if (type === "function") {
|
|
567
|
+
if (!conventions.functionPattern.test(identifier)) {
|
|
568
|
+
if (/^[a-z][a-zA-Z0-9]*$/.test(identifier) && /[A-Z]/.test(identifier)) {
|
|
569
|
+
return {
|
|
570
|
+
type: "convention-mix",
|
|
571
|
+
identifier,
|
|
572
|
+
file,
|
|
573
|
+
line,
|
|
574
|
+
column: 0,
|
|
575
|
+
severity: Severity4.Major,
|
|
576
|
+
category: "naming",
|
|
577
|
+
suggestion: `Function names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
} else if (type === "const" || type === "variable") {
|
|
582
|
+
if (identifier === identifier.toUpperCase() && identifier.length > 1) {
|
|
583
|
+
if (!conventions.constantPattern.test(identifier)) {
|
|
584
|
+
return {
|
|
585
|
+
type: "poor-naming",
|
|
586
|
+
identifier,
|
|
587
|
+
file,
|
|
588
|
+
line,
|
|
589
|
+
column: 0,
|
|
590
|
+
severity: Severity4.Minor,
|
|
591
|
+
category: "naming",
|
|
592
|
+
suggestion: "Constants should use UPPER_CASE_WITH_UNDERSCORES"
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
} else {
|
|
596
|
+
if (!conventions.variablePattern.test(identifier)) {
|
|
597
|
+
if (/^[a-z][a-zA-Z0-9]*$/.test(identifier) && /[A-Z]/.test(identifier)) {
|
|
598
|
+
return {
|
|
599
|
+
type: "convention-mix",
|
|
600
|
+
identifier,
|
|
601
|
+
file,
|
|
602
|
+
line,
|
|
603
|
+
column: 0,
|
|
604
|
+
severity: Severity4.Major,
|
|
605
|
+
category: "naming",
|
|
606
|
+
suggestion: `Variable names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
function toSnakeCase(str) {
|
|
615
|
+
return str.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
|
|
616
|
+
}
|
|
617
|
+
function toPascalCase(str) {
|
|
618
|
+
return str.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// src/analyzer.ts
|
|
622
|
+
async function analyzeConsistency(options) {
|
|
623
|
+
const {
|
|
624
|
+
checkNaming = true,
|
|
625
|
+
checkPatterns = true,
|
|
626
|
+
checkArchitecture = false,
|
|
627
|
+
// Not implemented yet
|
|
628
|
+
minSeverity = Severity5.Info,
|
|
629
|
+
...scanOptions
|
|
630
|
+
} = options;
|
|
631
|
+
void checkArchitecture;
|
|
632
|
+
const filePaths = await scanFiles(scanOptions);
|
|
633
|
+
const tsJsFiles = filePaths.filter((f) => /\.(ts|tsx|js|jsx)$/i.test(f));
|
|
634
|
+
const pythonFiles = filePaths.filter((f) => /\.py$/i.test(f));
|
|
635
|
+
let namingIssues = [];
|
|
636
|
+
if (checkNaming) {
|
|
637
|
+
const tsJsNamingIssues = tsJsFiles.length > 0 ? await analyzeNamingAST(tsJsFiles) : [];
|
|
638
|
+
const pythonNamingIssues = pythonFiles.length > 0 ? await analyzePythonNaming(pythonFiles) : [];
|
|
639
|
+
namingIssues = [...tsJsNamingIssues, ...pythonNamingIssues];
|
|
640
|
+
}
|
|
641
|
+
const patternIssues = checkPatterns ? await analyzePatterns(filePaths) : [];
|
|
642
|
+
const results = [];
|
|
643
|
+
const fileIssuesMap = /* @__PURE__ */ new Map();
|
|
644
|
+
for (const issue of namingIssues) {
|
|
645
|
+
if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
const consistencyIssue = {
|
|
649
|
+
type: issue.type === "convention-mix" ? IssueType.NamingInconsistency : IssueType.NamingQuality,
|
|
650
|
+
category: "naming",
|
|
651
|
+
severity: getSeverityEnum(issue.severity),
|
|
652
|
+
message: `${issue.type}: ${issue.identifier}`,
|
|
653
|
+
location: {
|
|
654
|
+
file: issue.file,
|
|
655
|
+
line: issue.line,
|
|
656
|
+
column: issue.column
|
|
657
|
+
},
|
|
658
|
+
suggestion: issue.suggestion
|
|
659
|
+
};
|
|
660
|
+
if (!fileIssuesMap.has(issue.file)) {
|
|
661
|
+
fileIssuesMap.set(issue.file, []);
|
|
662
|
+
}
|
|
663
|
+
fileIssuesMap.get(issue.file).push(consistencyIssue);
|
|
664
|
+
}
|
|
665
|
+
for (const issue of patternIssues) {
|
|
666
|
+
if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
const consistencyIssue = {
|
|
670
|
+
type: IssueType.PatternInconsistency,
|
|
671
|
+
category: "patterns",
|
|
672
|
+
severity: getSeverityEnum(issue.severity),
|
|
673
|
+
message: issue.description,
|
|
674
|
+
location: {
|
|
675
|
+
file: issue.files[0] || "multiple files",
|
|
676
|
+
line: 1
|
|
677
|
+
},
|
|
678
|
+
examples: issue.examples,
|
|
679
|
+
suggestion: `Standardize ${issue.type} patterns across ${issue.files.length} files`
|
|
680
|
+
};
|
|
681
|
+
const firstFile = issue.files[0];
|
|
682
|
+
if (firstFile && !fileIssuesMap.has(firstFile)) {
|
|
683
|
+
fileIssuesMap.set(firstFile, []);
|
|
684
|
+
}
|
|
685
|
+
if (firstFile) {
|
|
686
|
+
fileIssuesMap.get(firstFile).push(consistencyIssue);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
for (const [fileName, issues] of fileIssuesMap) {
|
|
690
|
+
results.push({
|
|
691
|
+
fileName,
|
|
692
|
+
issues,
|
|
693
|
+
metrics: {
|
|
694
|
+
consistencyScore: calculateConsistencyScore(issues)
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
results.sort((fileResultA, fileResultB) => {
|
|
699
|
+
const maxSeverityA = Math.min(
|
|
700
|
+
...fileResultA.issues.map((i) => {
|
|
701
|
+
const val = getSeverityLevel(i.severity);
|
|
702
|
+
return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
|
|
703
|
+
})
|
|
704
|
+
);
|
|
705
|
+
const maxSeverityB = Math.min(
|
|
706
|
+
...fileResultB.issues.map((i) => {
|
|
707
|
+
const val = getSeverityLevel(i.severity);
|
|
708
|
+
return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
|
|
709
|
+
})
|
|
710
|
+
);
|
|
711
|
+
if (maxSeverityA !== maxSeverityB) {
|
|
712
|
+
return maxSeverityA - maxSeverityB;
|
|
713
|
+
}
|
|
714
|
+
return fileResultB.issues.length - fileResultA.issues.length;
|
|
715
|
+
});
|
|
716
|
+
const recommendations = generateRecommendations(namingIssues, patternIssues);
|
|
717
|
+
const namingCountFiltered = namingIssues.filter(
|
|
718
|
+
(i) => shouldIncludeSeverity(i.severity, minSeverity)
|
|
719
|
+
).length;
|
|
720
|
+
const patternCountFiltered = patternIssues.filter(
|
|
721
|
+
(i) => shouldIncludeSeverity(i.severity, minSeverity)
|
|
722
|
+
).length;
|
|
723
|
+
return {
|
|
724
|
+
summary: {
|
|
725
|
+
totalIssues: namingCountFiltered + patternCountFiltered,
|
|
726
|
+
namingIssues: namingCountFiltered,
|
|
727
|
+
patternIssues: patternCountFiltered,
|
|
728
|
+
architectureIssues: 0,
|
|
729
|
+
filesAnalyzed: filePaths.length,
|
|
730
|
+
config: Object.fromEntries(
|
|
731
|
+
Object.entries(options).filter(
|
|
732
|
+
([key]) => !GLOBAL_SCAN_OPTIONS.includes(key) || key === "rootDir"
|
|
733
|
+
)
|
|
734
|
+
)
|
|
735
|
+
},
|
|
736
|
+
results,
|
|
737
|
+
recommendations
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
function getSeverityLevel(s) {
|
|
741
|
+
if (s === Severity5.Critical || s === "critical") return 4;
|
|
742
|
+
if (s === Severity5.Major || s === "major") return 3;
|
|
743
|
+
if (s === Severity5.Minor || s === "minor") return 2;
|
|
744
|
+
if (s === Severity5.Info || s === "info") return 1;
|
|
745
|
+
return 0;
|
|
746
|
+
}
|
|
747
|
+
function getSeverityEnum(s) {
|
|
748
|
+
const val = getSeverityLevel(s);
|
|
749
|
+
switch (val) {
|
|
750
|
+
case 4:
|
|
751
|
+
return Severity5.Critical;
|
|
752
|
+
case 3:
|
|
753
|
+
return Severity5.Major;
|
|
754
|
+
case 2:
|
|
755
|
+
return Severity5.Minor;
|
|
756
|
+
case 1:
|
|
757
|
+
return Severity5.Info;
|
|
758
|
+
default:
|
|
759
|
+
return Severity5.Info;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function shouldIncludeSeverity(severity, minSeverity) {
|
|
763
|
+
return getSeverityLevel(severity) >= getSeverityLevel(minSeverity);
|
|
764
|
+
}
|
|
765
|
+
function calculateConsistencyScore(issues) {
|
|
766
|
+
let totalWeight = 0;
|
|
767
|
+
for (const issue of issues) {
|
|
768
|
+
const val = getSeverityLevel(issue.severity);
|
|
769
|
+
switch (val) {
|
|
770
|
+
case 4:
|
|
771
|
+
totalWeight += 10;
|
|
772
|
+
break;
|
|
773
|
+
case 3:
|
|
774
|
+
totalWeight += 5;
|
|
775
|
+
break;
|
|
776
|
+
case 2:
|
|
777
|
+
totalWeight += 2;
|
|
778
|
+
break;
|
|
779
|
+
case 1:
|
|
780
|
+
totalWeight += 1;
|
|
781
|
+
break;
|
|
782
|
+
default:
|
|
783
|
+
totalWeight += 1;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
return Math.max(0, 1 - totalWeight / 100);
|
|
787
|
+
}
|
|
788
|
+
function generateRecommendations(namingIssues, patternIssues) {
|
|
789
|
+
const recommendations = [];
|
|
790
|
+
if (namingIssues.length > 0) {
|
|
791
|
+
const conventionMixCount = namingIssues.filter(
|
|
792
|
+
(i) => i.type === "convention-mix"
|
|
793
|
+
).length;
|
|
794
|
+
if (conventionMixCount > 0) {
|
|
795
|
+
recommendations.push(
|
|
796
|
+
`Standardize naming conventions: Found ${conventionMixCount} snake_case variables in TypeScript/JavaScript (use camelCase)`
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
const poorNamingCount = namingIssues.filter(
|
|
800
|
+
(i) => i.type === "poor-naming"
|
|
801
|
+
).length;
|
|
802
|
+
if (poorNamingCount > 0) {
|
|
803
|
+
recommendations.push(
|
|
804
|
+
`Improve variable naming: Found ${poorNamingCount} single-letter or unclear variable names`
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
if (patternIssues.length > 0) {
|
|
809
|
+
const errorHandlingIssues = patternIssues.filter(
|
|
810
|
+
(i) => i.type === "error-handling"
|
|
811
|
+
);
|
|
812
|
+
if (errorHandlingIssues.length > 0) {
|
|
813
|
+
recommendations.push(
|
|
814
|
+
"Standardize error handling strategy across the codebase (prefer try-catch with typed errors)"
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
const asyncIssues = patternIssues.filter((i) => i.type === "async-style");
|
|
818
|
+
if (asyncIssues.length > 0) {
|
|
819
|
+
recommendations.push(
|
|
820
|
+
"Use async/await consistently instead of mixing with promise chains or callbacks"
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
const importIssues = patternIssues.filter((i) => i.type === "import-style");
|
|
824
|
+
if (importIssues.length > 0) {
|
|
825
|
+
recommendations.push(
|
|
826
|
+
"Use ES modules consistently across the project (avoid mixing with CommonJS)"
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
if (recommendations.length === 0) {
|
|
831
|
+
recommendations.push(
|
|
832
|
+
"No major consistency issues found! Your codebase follows good practices."
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
return recommendations;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
export {
|
|
839
|
+
analyzeNamingAST,
|
|
840
|
+
analyzePatterns,
|
|
841
|
+
analyzeConsistency
|
|
842
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -522,7 +522,7 @@ function checkPythonNaming(identifier, type, file, line) {
|
|
|
522
522
|
file,
|
|
523
523
|
line,
|
|
524
524
|
column: 0,
|
|
525
|
-
severity:
|
|
525
|
+
severity: import_core3.Severity.Major,
|
|
526
526
|
category: "naming",
|
|
527
527
|
suggestion: `Class names should use PascalCase (e.g., ${toPascalCase(identifier)})`
|
|
528
528
|
};
|
|
@@ -536,7 +536,7 @@ function checkPythonNaming(identifier, type, file, line) {
|
|
|
536
536
|
file,
|
|
537
537
|
line,
|
|
538
538
|
column: 0,
|
|
539
|
-
severity:
|
|
539
|
+
severity: import_core3.Severity.Major,
|
|
540
540
|
category: "naming",
|
|
541
541
|
suggestion: `Function names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`
|
|
542
542
|
};
|
|
@@ -551,7 +551,7 @@ function checkPythonNaming(identifier, type, file, line) {
|
|
|
551
551
|
file,
|
|
552
552
|
line,
|
|
553
553
|
column: 0,
|
|
554
|
-
severity:
|
|
554
|
+
severity: import_core3.Severity.Minor,
|
|
555
555
|
category: "naming",
|
|
556
556
|
suggestion: "Constants should use UPPER_CASE_WITH_UNDERSCORES"
|
|
557
557
|
};
|
|
@@ -565,7 +565,7 @@ function checkPythonNaming(identifier, type, file, line) {
|
|
|
565
565
|
file,
|
|
566
566
|
line,
|
|
567
567
|
column: 0,
|
|
568
|
-
severity:
|
|
568
|
+
severity: import_core3.Severity.Major,
|
|
569
569
|
category: "naming",
|
|
570
570
|
suggestion: `Variable names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`
|
|
571
571
|
};
|
package/dist/cli.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -540,7 +540,7 @@ function checkPythonNaming(identifier, type, file, line) {
|
|
|
540
540
|
file,
|
|
541
541
|
line,
|
|
542
542
|
column: 0,
|
|
543
|
-
severity:
|
|
543
|
+
severity: import_core3.Severity.Major,
|
|
544
544
|
category: "naming",
|
|
545
545
|
suggestion: `Class names should use PascalCase (e.g., ${toPascalCase(identifier)})`
|
|
546
546
|
};
|
|
@@ -554,7 +554,7 @@ function checkPythonNaming(identifier, type, file, line) {
|
|
|
554
554
|
file,
|
|
555
555
|
line,
|
|
556
556
|
column: 0,
|
|
557
|
-
severity:
|
|
557
|
+
severity: import_core3.Severity.Major,
|
|
558
558
|
category: "naming",
|
|
559
559
|
suggestion: `Function names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`
|
|
560
560
|
};
|
|
@@ -569,7 +569,7 @@ function checkPythonNaming(identifier, type, file, line) {
|
|
|
569
569
|
file,
|
|
570
570
|
line,
|
|
571
571
|
column: 0,
|
|
572
|
-
severity:
|
|
572
|
+
severity: import_core3.Severity.Minor,
|
|
573
573
|
category: "naming",
|
|
574
574
|
suggestion: "Constants should use UPPER_CASE_WITH_UNDERSCORES"
|
|
575
575
|
};
|
|
@@ -583,7 +583,7 @@ function checkPythonNaming(identifier, type, file, line) {
|
|
|
583
583
|
file,
|
|
584
584
|
line,
|
|
585
585
|
column: 0,
|
|
586
|
-
severity:
|
|
586
|
+
severity: import_core3.Severity.Major,
|
|
587
587
|
category: "naming",
|
|
588
588
|
suggestion: `Variable names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`
|
|
589
589
|
};
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/consistency",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.14",
|
|
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",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@typescript-eslint/typescript-estree": "^8.53.0",
|
|
44
44
|
"chalk": "^5.3.0",
|
|
45
45
|
"commander": "^14.0.0",
|
|
46
|
-
"@aiready/core": "0.21.
|
|
46
|
+
"@aiready/core": "0.21.14"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/node": "^24.0.0",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Severity } from '@aiready/core';
|
|
2
3
|
import { analyzeConsistency } from '../analyzer';
|
|
3
4
|
import { analyzePatterns } from '../analyzers/patterns';
|
|
4
5
|
|
|
@@ -19,7 +20,7 @@ describe('analyzeConsistency', () => {
|
|
|
19
20
|
it('should detect minimum severity filtering', async () => {
|
|
20
21
|
const report = await analyzeConsistency({
|
|
21
22
|
rootDir: './src',
|
|
22
|
-
minSeverity:
|
|
23
|
+
minSeverity: Severity.Major,
|
|
23
24
|
});
|
|
24
25
|
|
|
25
26
|
// All issues should be major or critical
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* https://peps.python.org/pep-0008/#naming-conventions
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { getParser } from '@aiready/core';
|
|
8
|
+
import { getParser, Severity } from '@aiready/core';
|
|
9
9
|
import type { NamingIssue } from '../types';
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -97,7 +97,7 @@ function checkPythonNaming(
|
|
|
97
97
|
file,
|
|
98
98
|
line,
|
|
99
99
|
column: 0,
|
|
100
|
-
severity:
|
|
100
|
+
severity: Severity.Major,
|
|
101
101
|
category: 'naming',
|
|
102
102
|
suggestion: `Class names should use PascalCase (e.g., ${toPascalCase(identifier)})`,
|
|
103
103
|
};
|
|
@@ -113,7 +113,7 @@ function checkPythonNaming(
|
|
|
113
113
|
file,
|
|
114
114
|
line,
|
|
115
115
|
column: 0,
|
|
116
|
-
severity:
|
|
116
|
+
severity: Severity.Major,
|
|
117
117
|
category: 'naming',
|
|
118
118
|
suggestion: `Function names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`,
|
|
119
119
|
};
|
|
@@ -130,7 +130,7 @@ function checkPythonNaming(
|
|
|
130
130
|
file,
|
|
131
131
|
line,
|
|
132
132
|
column: 0,
|
|
133
|
-
severity:
|
|
133
|
+
severity: Severity.Minor,
|
|
134
134
|
category: 'naming',
|
|
135
135
|
suggestion: 'Constants should use UPPER_CASE_WITH_UNDERSCORES',
|
|
136
136
|
};
|
|
@@ -149,7 +149,7 @@ function checkPythonNaming(
|
|
|
149
149
|
file,
|
|
150
150
|
line,
|
|
151
151
|
column: 0,
|
|
152
|
-
severity:
|
|
152
|
+
severity: Severity.Major,
|
|
153
153
|
category: 'naming',
|
|
154
154
|
suggestion: `Variable names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`,
|
|
155
155
|
};
|
|
@@ -31,10 +31,14 @@ export async function loadNamingConfig(files: string[]): Promise<NamingConfig> {
|
|
|
31
31
|
|
|
32
32
|
// Extract custom configuration
|
|
33
33
|
const customAbbreviations = new Set(
|
|
34
|
-
consistencyConfig?.acceptedAbbreviations || []
|
|
34
|
+
(consistencyConfig?.acceptedAbbreviations as string[]) || []
|
|
35
|
+
);
|
|
36
|
+
const customShortWords = new Set(
|
|
37
|
+
(consistencyConfig?.shortWords as string[]) || []
|
|
38
|
+
);
|
|
39
|
+
const disabledChecks = new Set(
|
|
40
|
+
(consistencyConfig?.disableChecks as string[]) || []
|
|
35
41
|
);
|
|
36
|
-
const customShortWords = new Set(consistencyConfig?.shortWords || []);
|
|
37
|
-
const disabledChecks = new Set(consistencyConfig?.disableChecks || []);
|
|
38
42
|
|
|
39
43
|
// Merge with defaults
|
|
40
44
|
const allAbbreviations = new Set([
|