@aiready/consistency 0.21.9 → 0.21.12

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.
Files changed (132) hide show
  1. package/.turbo/turbo-build.log +23 -24
  2. package/dist/index.d.mts +3 -3
  3. package/dist/index.d.ts +3 -3
  4. package/dist/index.js +4 -4
  5. package/dist/index.mjs +3 -3
  6. package/package.json +2 -2
  7. package/src/__tests__/provider.test.ts +6 -34
  8. package/src/index.ts +3 -3
  9. package/src/provider.ts +2 -2
  10. package/dist/__tests__/analyzer.test.d.ts +0 -2
  11. package/dist/__tests__/analyzer.test.d.ts.map +0 -1
  12. package/dist/__tests__/analyzer.test.js +0 -157
  13. package/dist/__tests__/analyzer.test.js.map +0 -1
  14. package/dist/__tests__/language-filter.test.d.ts +0 -2
  15. package/dist/__tests__/language-filter.test.d.ts.map +0 -1
  16. package/dist/__tests__/language-filter.test.js +0 -46
  17. package/dist/__tests__/language-filter.test.js.map +0 -1
  18. package/dist/__tests__/scoring.test.d.ts +0 -2
  19. package/dist/__tests__/scoring.test.d.ts.map +0 -1
  20. package/dist/__tests__/scoring.test.js +0 -118
  21. package/dist/__tests__/scoring.test.js.map +0 -1
  22. package/dist/analyzer.d.ts +0 -7
  23. package/dist/analyzer.d.ts.map +0 -1
  24. package/dist/analyzer.js +0 -160
  25. package/dist/analyzer.js.map +0 -1
  26. package/dist/analyzers/naming-ast.d.ts +0 -7
  27. package/dist/analyzers/naming-ast.d.ts.map +0 -1
  28. package/dist/analyzers/naming-ast.js +0 -253
  29. package/dist/analyzers/naming-ast.js.map +0 -1
  30. package/dist/analyzers/naming-constants.d.ts +0 -21
  31. package/dist/analyzers/naming-constants.d.ts.map +0 -1
  32. package/dist/analyzers/naming-constants.js +0 -96
  33. package/dist/analyzers/naming-constants.js.map +0 -1
  34. package/dist/analyzers/naming-python.d.ts +0 -16
  35. package/dist/analyzers/naming-python.d.ts.map +0 -1
  36. package/dist/analyzers/naming-python.js +0 -165
  37. package/dist/analyzers/naming-python.js.map +0 -1
  38. package/dist/analyzers/naming.d.ts +0 -6
  39. package/dist/analyzers/naming.d.ts.map +0 -1
  40. package/dist/analyzers/naming.js +0 -234
  41. package/dist/analyzers/naming.js.map +0 -1
  42. package/dist/analyzers/patterns.d.ts +0 -10
  43. package/dist/analyzers/patterns.d.ts.map +0 -1
  44. package/dist/analyzers/patterns.js +0 -197
  45. package/dist/analyzers/patterns.js.map +0 -1
  46. package/dist/chunk-2BTBNG6X.mjs +0 -814
  47. package/dist/chunk-3ZB6FFRL.mjs +0 -661
  48. package/dist/chunk-5UFRGXSB.mjs +0 -783
  49. package/dist/chunk-66M3TIO7.mjs +0 -837
  50. package/dist/chunk-6BM5MV3S.mjs +0 -719
  51. package/dist/chunk-6H3JHDP7.mjs +0 -832
  52. package/dist/chunk-7PHHJOGC.mjs +0 -1374
  53. package/dist/chunk-AASFXGUR.mjs +0 -1622
  54. package/dist/chunk-AR7DIZLP.mjs +0 -827
  55. package/dist/chunk-BDDMOIU2.mjs +0 -385
  56. package/dist/chunk-BMILMNKJ.mjs +0 -1633
  57. package/dist/chunk-BYY6MD5T.mjs +0 -729
  58. package/dist/chunk-CA4Q5JBK.mjs +0 -1143
  59. package/dist/chunk-CF4LU7KE.mjs +0 -384
  60. package/dist/chunk-CJINEUIH.mjs +0 -1369
  61. package/dist/chunk-CLWNLHDB.mjs +0 -909
  62. package/dist/chunk-CZUJTDNH.mjs +0 -848
  63. package/dist/chunk-DNGW3WQK.mjs +0 -810
  64. package/dist/chunk-DSI3TEO2.mjs +0 -662
  65. package/dist/chunk-EIQ5K6OO.mjs +0 -1579
  66. package/dist/chunk-FEJODRK5.mjs +0 -783
  67. package/dist/chunk-FK56AZ43.mjs +0 -817
  68. package/dist/chunk-H6S7WKSQ.mjs +0 -729
  69. package/dist/chunk-HAOJLJNB.mjs +0 -1290
  70. package/dist/chunk-HJCP36VW.mjs +0 -821
  71. package/dist/chunk-HPG7P6PD.mjs +0 -1372
  72. package/dist/chunk-IVRBV7SE.mjs +0 -1295
  73. package/dist/chunk-IXBC6GVT.mjs +0 -832
  74. package/dist/chunk-J5IFYDVU.mjs +0 -1579
  75. package/dist/chunk-KWQVBF7K.mjs +0 -831
  76. package/dist/chunk-LD3CHHU2.mjs +0 -1297
  77. package/dist/chunk-LMOXGPCM.mjs +0 -722
  78. package/dist/chunk-LSXZH6X6.mjs +0 -810
  79. package/dist/chunk-LUAREV6A.mjs +0 -508
  80. package/dist/chunk-MAPVFXBP.mjs +0 -708
  81. package/dist/chunk-MM2PLUCH.mjs +0 -1376
  82. package/dist/chunk-NPWCJZUG.mjs +0 -708
  83. package/dist/chunk-ON73WHHU.mjs +0 -1310
  84. package/dist/chunk-P6NVKUBB.mjs +0 -831
  85. package/dist/chunk-Q3KTWDSL.mjs +0 -808
  86. package/dist/chunk-Q5XMWG33.mjs +0 -661
  87. package/dist/chunk-QOIPVP6P.mjs +0 -1607
  88. package/dist/chunk-RMEQWG52.mjs +0 -1633
  89. package/dist/chunk-S6BZVTWN.mjs +0 -731
  90. package/dist/chunk-TE6JYZD3.mjs +0 -810
  91. package/dist/chunk-TLVLM3M5.mjs +0 -771
  92. package/dist/chunk-TXHPUU7A.mjs +0 -863
  93. package/dist/chunk-UMBBTNQN.mjs +0 -787
  94. package/dist/chunk-V2UPXL7L.mjs +0 -842
  95. package/dist/chunk-VODCPPET.mjs +0 -1292
  96. package/dist/chunk-W6UGMKRV.mjs +0 -1310
  97. package/dist/chunk-WGH4TGZ3.mjs +0 -1288
  98. package/dist/chunk-WTBDNCEN.mjs +0 -1352
  99. package/dist/chunk-XVW5DKJQ.mjs +0 -1619
  100. package/dist/chunk-YCDCIOJN.mjs +0 -842
  101. package/dist/chunk-YEHXYHGY.mjs +0 -1497
  102. package/dist/chunk-YHHXE2JX.mjs +0 -912
  103. package/dist/chunk-ZB6UK276.mjs +0 -662
  104. package/dist/chunk-ZG3KFSD3.mjs +0 -1142
  105. package/dist/cli.d.ts.map +0 -1
  106. package/dist/cli.js.map +0 -1
  107. package/dist/index.d.ts.map +0 -1
  108. package/dist/index.js.map +0 -1
  109. package/dist/scoring.d.ts +0 -12
  110. package/dist/scoring.d.ts.map +0 -1
  111. package/dist/scoring.js +0 -110
  112. package/dist/scoring.js.map +0 -1
  113. package/dist/types.d.ts +0 -53
  114. package/dist/types.d.ts.map +0 -1
  115. package/dist/types.js +0 -2
  116. package/dist/types.js.map +0 -1
  117. package/dist/utils/ast-parser.d.ts +0 -46
  118. package/dist/utils/ast-parser.d.ts.map +0 -1
  119. package/dist/utils/ast-parser.js +0 -157
  120. package/dist/utils/ast-parser.js.map +0 -1
  121. package/dist/utils/config-loader.d.ts +0 -19
  122. package/dist/utils/config-loader.d.ts.map +0 -1
  123. package/dist/utils/config-loader.js +0 -31
  124. package/dist/utils/config-loader.js.map +0 -1
  125. package/dist/utils/context-detector.d.ts +0 -40
  126. package/dist/utils/context-detector.d.ts.map +0 -1
  127. package/dist/utils/context-detector.js +0 -225
  128. package/dist/utils/context-detector.js.map +0 -1
  129. package/dist/utils/scope-tracker.d.ts +0 -87
  130. package/dist/utils/scope-tracker.d.ts.map +0 -1
  131. package/dist/utils/scope-tracker.js +0 -161
  132. package/dist/utils/scope-tracker.js.map +0 -1
@@ -1,837 +0,0 @@
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 { scanFiles, Severity as Severity4, IssueType, GLOBAL_SCAN_OPTIONS } from "@aiready/core";
493
-
494
- // src/analyzers/naming-python.ts
495
- import { getParser } from "@aiready/core";
496
- async function analyzePythonNaming(files) {
497
- const issues = [];
498
- const parser = getParser("dummy.py");
499
- if (!parser) {
500
- console.warn("Python parser not available");
501
- return issues;
502
- }
503
- const pythonFiles = files.filter((f) => f.toLowerCase().endsWith(".py"));
504
- for (const file of pythonFiles) {
505
- try {
506
- const fs = await import("fs");
507
- const code = await fs.promises.readFile(file, "utf-8");
508
- const result = parser.parse(code, file);
509
- for (const exp of result.exports) {
510
- const nameIssue = checkPythonNaming(
511
- exp.name,
512
- exp.type,
513
- file,
514
- exp.loc?.start.line || 0
515
- );
516
- if (nameIssue) {
517
- issues.push(nameIssue);
518
- }
519
- }
520
- for (const imp of result.imports) {
521
- for (const spec of imp.specifiers) {
522
- if (spec !== "*" && spec !== "default") {
523
- const nameIssue = checkPythonNaming(
524
- spec,
525
- "variable",
526
- file,
527
- imp.loc?.start.line || 0
528
- );
529
- if (nameIssue) {
530
- issues.push(nameIssue);
531
- }
532
- }
533
- }
534
- }
535
- } catch (error) {
536
- console.warn(`Skipping ${file} due to error:`, error);
537
- }
538
- }
539
- return issues;
540
- }
541
- function checkPythonNaming(identifier, type, file, line) {
542
- const parser = getParser("dummy.py");
543
- const conventions = parser?.getNamingConventions();
544
- if (!conventions) return null;
545
- if (conventions.exceptions?.includes(identifier)) {
546
- return null;
547
- }
548
- if (type === "class") {
549
- if (!conventions.classPattern.test(identifier)) {
550
- return {
551
- type: "poor-naming",
552
- identifier,
553
- file,
554
- line,
555
- column: 0,
556
- severity: "major",
557
- category: "naming",
558
- suggestion: `Class names should use PascalCase (e.g., ${toPascalCase(identifier)})`
559
- };
560
- }
561
- } else if (type === "function") {
562
- if (!conventions.functionPattern.test(identifier)) {
563
- if (/^[a-z][a-zA-Z0-9]*$/.test(identifier) && /[A-Z]/.test(identifier)) {
564
- return {
565
- type: "convention-mix",
566
- identifier,
567
- file,
568
- line,
569
- column: 0,
570
- severity: "major",
571
- category: "naming",
572
- suggestion: `Function names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`
573
- };
574
- }
575
- }
576
- } else if (type === "const" || type === "variable") {
577
- if (identifier === identifier.toUpperCase() && identifier.length > 1) {
578
- if (!conventions.constantPattern.test(identifier)) {
579
- return {
580
- type: "poor-naming",
581
- identifier,
582
- file,
583
- line,
584
- column: 0,
585
- severity: "minor",
586
- category: "naming",
587
- suggestion: "Constants should use UPPER_CASE_WITH_UNDERSCORES"
588
- };
589
- }
590
- } else {
591
- if (!conventions.variablePattern.test(identifier)) {
592
- if (/^[a-z][a-zA-Z0-9]*$/.test(identifier) && /[A-Z]/.test(identifier)) {
593
- return {
594
- type: "convention-mix",
595
- identifier,
596
- file,
597
- line,
598
- column: 0,
599
- severity: "major",
600
- category: "naming",
601
- suggestion: `Variable names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`
602
- };
603
- }
604
- }
605
- }
606
- }
607
- return null;
608
- }
609
- function toSnakeCase(str) {
610
- return str.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
611
- }
612
- function toPascalCase(str) {
613
- return str.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
614
- }
615
-
616
- // src/analyzer.ts
617
- async function analyzeConsistency(options) {
618
- const {
619
- checkNaming = true,
620
- checkPatterns = true,
621
- checkArchitecture = false,
622
- // Not implemented yet
623
- minSeverity = Severity4.Info,
624
- ...scanOptions
625
- } = options;
626
- void checkArchitecture;
627
- const filePaths = await scanFiles(scanOptions);
628
- const tsJsFiles = filePaths.filter((f) => /\.(ts|tsx|js|jsx)$/i.test(f));
629
- const pythonFiles = filePaths.filter((f) => /\.py$/i.test(f));
630
- let namingIssues = [];
631
- if (checkNaming) {
632
- const tsJsNamingIssues = tsJsFiles.length > 0 ? await analyzeNamingAST(tsJsFiles) : [];
633
- const pythonNamingIssues = pythonFiles.length > 0 ? await analyzePythonNaming(pythonFiles) : [];
634
- namingIssues = [...tsJsNamingIssues, ...pythonNamingIssues];
635
- }
636
- const patternIssues = checkPatterns ? await analyzePatterns(filePaths) : [];
637
- const results = [];
638
- const fileIssuesMap = /* @__PURE__ */ new Map();
639
- for (const issue of namingIssues) {
640
- if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
641
- continue;
642
- }
643
- const consistencyIssue = {
644
- type: issue.type === "convention-mix" ? IssueType.NamingInconsistency : IssueType.NamingQuality,
645
- category: "naming",
646
- severity: getSeverityEnum(issue.severity),
647
- message: `${issue.type}: ${issue.identifier}`,
648
- location: {
649
- file: issue.file,
650
- line: issue.line,
651
- column: issue.column
652
- },
653
- suggestion: issue.suggestion
654
- };
655
- if (!fileIssuesMap.has(issue.file)) {
656
- fileIssuesMap.set(issue.file, []);
657
- }
658
- fileIssuesMap.get(issue.file).push(consistencyIssue);
659
- }
660
- for (const issue of patternIssues) {
661
- if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
662
- continue;
663
- }
664
- const consistencyIssue = {
665
- type: IssueType.PatternInconsistency,
666
- category: "patterns",
667
- severity: getSeverityEnum(issue.severity),
668
- message: issue.description,
669
- location: {
670
- file: issue.files[0] || "multiple files",
671
- line: 1
672
- },
673
- examples: issue.examples,
674
- suggestion: `Standardize ${issue.type} patterns across ${issue.files.length} files`
675
- };
676
- const firstFile = issue.files[0];
677
- if (firstFile && !fileIssuesMap.has(firstFile)) {
678
- fileIssuesMap.set(firstFile, []);
679
- }
680
- if (firstFile) {
681
- fileIssuesMap.get(firstFile).push(consistencyIssue);
682
- }
683
- }
684
- for (const [fileName, issues] of fileIssuesMap) {
685
- results.push({
686
- fileName,
687
- issues,
688
- metrics: {
689
- consistencyScore: calculateConsistencyScore(issues)
690
- }
691
- });
692
- }
693
- results.sort((fileResultA, fileResultB) => {
694
- const maxSeverityA = Math.min(
695
- ...fileResultA.issues.map((i) => {
696
- const val = getSeverityLevel(i.severity);
697
- return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
698
- })
699
- );
700
- const maxSeverityB = Math.min(
701
- ...fileResultB.issues.map((i) => {
702
- const val = getSeverityLevel(i.severity);
703
- return val === 4 ? 0 : val === 3 ? 1 : val === 2 ? 2 : 3;
704
- })
705
- );
706
- if (maxSeverityA !== maxSeverityB) {
707
- return maxSeverityA - maxSeverityB;
708
- }
709
- return fileResultB.issues.length - fileResultA.issues.length;
710
- });
711
- const recommendations = generateRecommendations(namingIssues, patternIssues);
712
- const namingCountFiltered = namingIssues.filter(
713
- (i) => shouldIncludeSeverity(i.severity, minSeverity)
714
- ).length;
715
- const patternCountFiltered = patternIssues.filter(
716
- (i) => shouldIncludeSeverity(i.severity, minSeverity)
717
- ).length;
718
- return {
719
- summary: {
720
- totalIssues: namingCountFiltered + patternCountFiltered,
721
- namingIssues: namingCountFiltered,
722
- patternIssues: patternCountFiltered,
723
- architectureIssues: 0,
724
- filesAnalyzed: filePaths.length,
725
- config: Object.fromEntries(
726
- Object.entries(options).filter(
727
- ([key]) => !GLOBAL_SCAN_OPTIONS.includes(key) || key === "rootDir"
728
- )
729
- )
730
- },
731
- results,
732
- recommendations
733
- };
734
- }
735
- function getSeverityLevel(s) {
736
- if (s === Severity4.Critical || s === "critical") return 4;
737
- if (s === Severity4.Major || s === "major") return 3;
738
- if (s === Severity4.Minor || s === "minor") return 2;
739
- if (s === Severity4.Info || s === "info") return 1;
740
- return 0;
741
- }
742
- function getSeverityEnum(s) {
743
- const val = getSeverityLevel(s);
744
- switch (val) {
745
- case 4:
746
- return Severity4.Critical;
747
- case 3:
748
- return Severity4.Major;
749
- case 2:
750
- return Severity4.Minor;
751
- case 1:
752
- return Severity4.Info;
753
- default:
754
- return Severity4.Info;
755
- }
756
- }
757
- function shouldIncludeSeverity(severity, minSeverity) {
758
- return getSeverityLevel(severity) >= getSeverityLevel(minSeverity);
759
- }
760
- function calculateConsistencyScore(issues) {
761
- let totalWeight = 0;
762
- for (const issue of issues) {
763
- const val = getSeverityLevel(issue.severity);
764
- switch (val) {
765
- case 4:
766
- totalWeight += 10;
767
- break;
768
- case 3:
769
- totalWeight += 5;
770
- break;
771
- case 2:
772
- totalWeight += 2;
773
- break;
774
- case 1:
775
- totalWeight += 1;
776
- break;
777
- default:
778
- totalWeight += 1;
779
- }
780
- }
781
- return Math.max(0, 1 - totalWeight / 100);
782
- }
783
- function generateRecommendations(namingIssues, patternIssues) {
784
- const recommendations = [];
785
- if (namingIssues.length > 0) {
786
- const conventionMixCount = namingIssues.filter(
787
- (i) => i.type === "convention-mix"
788
- ).length;
789
- if (conventionMixCount > 0) {
790
- recommendations.push(
791
- `Standardize naming conventions: Found ${conventionMixCount} snake_case variables in TypeScript/JavaScript (use camelCase)`
792
- );
793
- }
794
- const poorNamingCount = namingIssues.filter(
795
- (i) => i.type === "poor-naming"
796
- ).length;
797
- if (poorNamingCount > 0) {
798
- recommendations.push(
799
- `Improve variable naming: Found ${poorNamingCount} single-letter or unclear variable names`
800
- );
801
- }
802
- }
803
- if (patternIssues.length > 0) {
804
- const errorHandlingIssues = patternIssues.filter(
805
- (i) => i.type === "error-handling"
806
- );
807
- if (errorHandlingIssues.length > 0) {
808
- recommendations.push(
809
- "Standardize error handling strategy across the codebase (prefer try-catch with typed errors)"
810
- );
811
- }
812
- const asyncIssues = patternIssues.filter((i) => i.type === "async-style");
813
- if (asyncIssues.length > 0) {
814
- recommendations.push(
815
- "Use async/await consistently instead of mixing with promise chains or callbacks"
816
- );
817
- }
818
- const importIssues = patternIssues.filter((i) => i.type === "import-style");
819
- if (importIssues.length > 0) {
820
- recommendations.push(
821
- "Use ES modules consistently across the project (avoid mixing with CommonJS)"
822
- );
823
- }
824
- }
825
- if (recommendations.length === 0) {
826
- recommendations.push(
827
- "No major consistency issues found! Your codebase follows good practices."
828
- );
829
- }
830
- return recommendations;
831
- }
832
-
833
- export {
834
- analyzeNamingAST,
835
- analyzePatterns,
836
- analyzeConsistency
837
- };