@aiready/consistency 0.16.2 → 0.16.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 +10 -10
- package/.turbo/turbo-test.log +12 -10
- package/dist/chunk-AASFXGUR.mjs +1622 -0
- package/dist/chunk-AR7DIZLP.mjs +827 -0
- package/dist/chunk-BMILMNKJ.mjs +1633 -0
- package/dist/chunk-HJCP36VW.mjs +821 -0
- package/dist/chunk-QOIPVP6P.mjs +1607 -0
- package/dist/chunk-RMEQWG52.mjs +1633 -0
- package/dist/chunk-XVW5DKJQ.mjs +1619 -0
- package/dist/cli.js +277 -1019
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +320 -1242
- package/dist/index.mjs +58 -230
- package/package.json +2 -2
- package/src/__tests__/contract.test.ts +18 -2
- package/src/analyzer.ts +49 -28
- package/src/analyzers/naming-ast.ts +188 -328
- package/src/analyzers/naming.ts +52 -365
- package/src/analyzers/patterns.ts +51 -228
- package/src/types.ts +10 -10
- package/src/utils/context-detector.ts +23 -10
|
@@ -1,251 +1,74 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { Severity } from '@aiready/core';
|
|
2
3
|
import type { PatternIssue } from '../types';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
+
* Detect inconsistent code patterns across files
|
|
6
7
|
*/
|
|
7
|
-
export async function analyzePatterns(
|
|
8
|
-
files: string[]
|
|
9
|
-
): Promise<PatternIssue[]> {
|
|
8
|
+
export async function analyzePatterns(filePaths: string[]): Promise<PatternIssue[]> {
|
|
10
9
|
const issues: PatternIssue[] = [];
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return issues;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function analyzeErrorHandling(files: string[]): Promise<PatternIssue[]> {
|
|
28
|
-
const patterns = {
|
|
29
|
-
tryCatch: [] as string[],
|
|
30
|
-
throwsError: [] as string[],
|
|
31
|
-
returnsNull: [] as string[],
|
|
32
|
-
returnsError: [] as string[],
|
|
10
|
+
const contents = new Map<string, string>();
|
|
11
|
+
|
|
12
|
+
// 1. Error handling style
|
|
13
|
+
const tryCatchPattern = /try\s*\{/g;
|
|
14
|
+
const catchPattern = /catch\s*\(\s*(\w+)\s*\)/g;
|
|
15
|
+
|
|
16
|
+
const styleStats = {
|
|
17
|
+
tryCatch: 0,
|
|
18
|
+
thenCatch: 0,
|
|
19
|
+
asyncAwait: 0,
|
|
20
|
+
commonJs: 0,
|
|
21
|
+
esm: 0,
|
|
33
22
|
};
|
|
34
23
|
|
|
35
|
-
for (const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
patterns.returnsError.push(file);
|
|
24
|
+
for (const filePath of filePaths) {
|
|
25
|
+
try {
|
|
26
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
27
|
+
contents.set(filePath, content);
|
|
28
|
+
|
|
29
|
+
if (content.match(tryCatchPattern)) styleStats.tryCatch++;
|
|
30
|
+
if (content.match(/\.catch\s*\(/)) styleStats.thenCatch++;
|
|
31
|
+
if (content.match(/\bawait\b/)) styleStats.asyncAwait++;
|
|
32
|
+
|
|
33
|
+
if (content.match(/\brequire\s*\(/)) styleStats.commonJs++;
|
|
34
|
+
if (content.match(/\bimport\b.*\bfrom\b/)) styleStats.esm++;
|
|
35
|
+
} catch (err) {
|
|
36
|
+
void err;
|
|
49
37
|
}
|
|
50
38
|
}
|
|
51
39
|
|
|
52
|
-
const
|
|
40
|
+
const totalFiles = filePaths.length;
|
|
53
41
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
42
|
+
// Report inconsistencies if there's a significant mix
|
|
43
|
+
if (styleStats.tryCatch > 0 && styleStats.thenCatch > 0) {
|
|
44
|
+
const dominant = styleStats.tryCatch >= styleStats.thenCatch ? 'try-catch' : '.catch()';
|
|
45
|
+
const minority = dominant === 'try-catch' ? '.catch()' : 'try-catch';
|
|
46
|
+
|
|
59
47
|
issues.push({
|
|
60
|
-
files:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
...patterns.returnsNull,
|
|
65
|
-
...patterns.returnsError,
|
|
66
|
-
]),
|
|
67
|
-
],
|
|
48
|
+
files: filePaths.filter(f => {
|
|
49
|
+
const c = contents.get(f) || '';
|
|
50
|
+
return minority === 'try-catch' ? c.match(tryCatchPattern) : c.match(/\.catch\s*\(/);
|
|
51
|
+
}),
|
|
68
52
|
type: 'error-handling',
|
|
69
|
-
description:
|
|
70
|
-
examples: [
|
|
71
|
-
|
|
72
|
-
? `Try-catch used in ${patterns.tryCatch.length} files`
|
|
73
|
-
: '',
|
|
74
|
-
patterns.throwsError.length > 0
|
|
75
|
-
? `Throws errors in ${patterns.throwsError.length} files`
|
|
76
|
-
: '',
|
|
77
|
-
patterns.returnsNull.length > 0
|
|
78
|
-
? `Returns null in ${patterns.returnsNull.length} files`
|
|
79
|
-
: '',
|
|
80
|
-
patterns.returnsError.length > 0
|
|
81
|
-
? `Returns error objects in ${patterns.returnsError.length} files`
|
|
82
|
-
: '',
|
|
83
|
-
].filter((e) => e),
|
|
84
|
-
severity: 'major',
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return issues;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async function analyzeAsyncPatterns(files: string[]): Promise<PatternIssue[]> {
|
|
92
|
-
const patterns = {
|
|
93
|
-
asyncAwait: [] as string[],
|
|
94
|
-
promises: [] as string[],
|
|
95
|
-
callbacks: [] as string[],
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
for (const file of files) {
|
|
99
|
-
const content = await readFileContent(file);
|
|
100
|
-
|
|
101
|
-
if (content.match(/async\s+(function|\(|[a-zA-Z])/)) {
|
|
102
|
-
patterns.asyncAwait.push(file);
|
|
103
|
-
}
|
|
104
|
-
if (content.match(/\.then\(/) || content.match(/\.catch\(/)) {
|
|
105
|
-
patterns.promises.push(file);
|
|
106
|
-
}
|
|
107
|
-
if (content.match(/callback\s*\(/) || content.match(/\(\s*err\s*,/)) {
|
|
108
|
-
patterns.callbacks.push(file);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const issues: PatternIssue[] = [];
|
|
113
|
-
|
|
114
|
-
// Modern codebases should prefer async/await
|
|
115
|
-
if (patterns.callbacks.length > 0 && patterns.asyncAwait.length > 0) {
|
|
116
|
-
issues.push({
|
|
117
|
-
files: [...patterns.callbacks, ...patterns.asyncAwait],
|
|
118
|
-
type: 'async-style',
|
|
119
|
-
description: 'Mixed async patterns: callbacks and async/await',
|
|
120
|
-
examples: [
|
|
121
|
-
`Callbacks found in: ${patterns.callbacks.slice(0, 3).join(', ')}`,
|
|
122
|
-
`Async/await used in: ${patterns.asyncAwait.slice(0, 3).join(', ')}`,
|
|
123
|
-
],
|
|
124
|
-
severity: 'minor',
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Mixing .then() chains with async/await
|
|
129
|
-
if (
|
|
130
|
-
patterns.promises.length > patterns.asyncAwait.length * 0.3 &&
|
|
131
|
-
patterns.asyncAwait.length > 0
|
|
132
|
-
) {
|
|
133
|
-
issues.push({
|
|
134
|
-
files: patterns.promises,
|
|
135
|
-
type: 'async-style',
|
|
136
|
-
description:
|
|
137
|
-
'Consider using async/await instead of promise chains for consistency',
|
|
138
|
-
examples: patterns.promises.slice(0, 5),
|
|
139
|
-
severity: 'info',
|
|
53
|
+
description: `Mixed error handling styles: codebase primarily uses ${dominant}, but found ${minority} in some files.`,
|
|
54
|
+
examples: [dominant, minority],
|
|
55
|
+
severity: Severity.Minor,
|
|
140
56
|
});
|
|
141
57
|
}
|
|
142
58
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
async function analyzeImportStyles(files: string[]): Promise<PatternIssue[]> {
|
|
147
|
-
const patterns = {
|
|
148
|
-
esModules: [] as string[],
|
|
149
|
-
commonJS: [] as string[],
|
|
150
|
-
mixed: [] as string[],
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
for (const file of files) {
|
|
154
|
-
const content = await readFileContent(file);
|
|
155
|
-
const hasESM = content.match(/^import\s+/m);
|
|
156
|
-
|
|
157
|
-
// Check for actual CommonJS require() calls, excluding:
|
|
158
|
-
// - String literals: "require('...') or 'require('...')
|
|
159
|
-
// - Regex patterns: /require\(/
|
|
160
|
-
// - Comments: // require( or /* require( */
|
|
161
|
-
const hasCJS = hasActualRequireCalls(content);
|
|
162
|
-
|
|
163
|
-
if (hasESM && hasCJS) {
|
|
164
|
-
patterns.mixed.push(file);
|
|
165
|
-
} else if (hasESM) {
|
|
166
|
-
patterns.esModules.push(file);
|
|
167
|
-
} else if (hasCJS) {
|
|
168
|
-
patterns.commonJS.push(file);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const issues: PatternIssue[] = [];
|
|
173
|
-
|
|
174
|
-
// Check for mixed import styles in same file
|
|
175
|
-
if (patterns.mixed.length > 0) {
|
|
59
|
+
if (styleStats.commonJs > 0 && styleStats.esm > 0) {
|
|
60
|
+
const minority = styleStats.esm >= styleStats.commonJs ? 'CommonJS (require)' : 'ESM (import)';
|
|
176
61
|
issues.push({
|
|
177
|
-
files:
|
|
62
|
+
files: filePaths.filter(f => {
|
|
63
|
+
const c = contents.get(f) || '';
|
|
64
|
+
return minority === 'CommonJS (require)' ? c.match(/\brequire\s*\(/) : c.match(/\bimport\b/);
|
|
65
|
+
}),
|
|
178
66
|
type: 'import-style',
|
|
179
|
-
description:
|
|
180
|
-
examples:
|
|
181
|
-
severity:
|
|
67
|
+
description: `Mixed module systems: found both ESM and CommonJS.`,
|
|
68
|
+
examples: ['import X from "y"', 'const X = require("y")'],
|
|
69
|
+
severity: Severity.Major,
|
|
182
70
|
});
|
|
183
71
|
}
|
|
184
72
|
|
|
185
|
-
// Check for inconsistent styles across project
|
|
186
|
-
if (patterns.esModules.length > 0 && patterns.commonJS.length > 0) {
|
|
187
|
-
const ratio =
|
|
188
|
-
patterns.commonJS.length /
|
|
189
|
-
(patterns.esModules.length + patterns.commonJS.length);
|
|
190
|
-
if (ratio > 0.2 && ratio < 0.8) {
|
|
191
|
-
issues.push({
|
|
192
|
-
files: [...patterns.esModules, ...patterns.commonJS],
|
|
193
|
-
type: 'import-style',
|
|
194
|
-
description: 'Inconsistent import styles across project',
|
|
195
|
-
examples: [
|
|
196
|
-
`ES modules: ${patterns.esModules.length} files`,
|
|
197
|
-
`CommonJS: ${patterns.commonJS.length} files`,
|
|
198
|
-
],
|
|
199
|
-
severity: 'minor',
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
73
|
return issues;
|
|
205
74
|
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Detects actual require() calls, excluding false positives
|
|
209
|
-
* Filters out require() in:
|
|
210
|
-
* - String literals (single/double/template quotes)
|
|
211
|
-
* - Regex patterns
|
|
212
|
-
* - Single-line comments (//)
|
|
213
|
-
* - Multi-line comments
|
|
214
|
-
*/
|
|
215
|
-
function hasActualRequireCalls(content: string): boolean {
|
|
216
|
-
// Simple heuristic: remove obvious false positives
|
|
217
|
-
// 1. Remove single-line comments
|
|
218
|
-
let cleaned = content.replace(/\/\/.*$/gm, '');
|
|
219
|
-
|
|
220
|
-
// 2. Remove multi-line comments (non-greedy)
|
|
221
|
-
cleaned = cleaned.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
222
|
-
|
|
223
|
-
// 3. Remove string literals - use simpler regex to avoid backtracking
|
|
224
|
-
// Match strings but don't try to be perfect, just remove obvious ones
|
|
225
|
-
cleaned = cleaned.replace(/"[^"\n]*"/g, '""');
|
|
226
|
-
cleaned = cleaned.replace(/'[^'\n]*'/g, "''");
|
|
227
|
-
cleaned = cleaned.replace(/`[^`]*`/g, '``');
|
|
228
|
-
|
|
229
|
-
// 4. Simple regex detection: if we see /require in the line, likely a regex pattern
|
|
230
|
-
// Remove lines that look like regex patterns with require
|
|
231
|
-
cleaned = cleaned.replace(/\/[^/\n]*require[^/\n]*\/[gimsuvy]*/g, '');
|
|
232
|
-
|
|
233
|
-
// Now check for require( in the cleaned content
|
|
234
|
-
return /require\s*\(/.test(cleaned);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Analyzes API design consistency
|
|
239
|
-
*/
|
|
240
|
-
export async function analyzeAPIDesign(
|
|
241
|
-
files: string[]
|
|
242
|
-
): Promise<PatternIssue[]> {
|
|
243
|
-
// This would analyze:
|
|
244
|
-
// - Function parameter order consistency
|
|
245
|
-
// - Return type patterns
|
|
246
|
-
// - Options object vs individual parameters
|
|
247
|
-
// For now, return empty array
|
|
248
|
-
// Parameter currently unused; reference to avoid lint warnings
|
|
249
|
-
void files;
|
|
250
|
-
return [];
|
|
251
|
-
}
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ScanOptions, AnalysisResult, Issue } from '@aiready/core';
|
|
1
|
+
import type { ScanOptions, AnalysisResult, Issue, Severity, IssueType } from '@aiready/core';
|
|
2
2
|
|
|
3
3
|
export interface ConsistencyOptions extends ScanOptions {
|
|
4
4
|
/** Check naming conventions and quality */
|
|
@@ -8,15 +8,15 @@ export interface ConsistencyOptions extends ScanOptions {
|
|
|
8
8
|
/** Check architectural consistency */
|
|
9
9
|
checkArchitecture?: boolean;
|
|
10
10
|
/** Minimum severity to report */
|
|
11
|
-
minSeverity?:
|
|
11
|
+
minSeverity?: Severity;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export interface ConsistencyIssue extends Issue {
|
|
15
15
|
type:
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
16
|
+
| IssueType.NamingInconsistency
|
|
17
|
+
| IssueType.NamingQuality
|
|
18
|
+
| IssueType.PatternInconsistency
|
|
19
|
+
| IssueType.ArchitectureInconsistency;
|
|
20
20
|
category: 'naming' | 'patterns' | 'architecture';
|
|
21
21
|
/** Examples of the inconsistency found */
|
|
22
22
|
examples?: string[];
|
|
@@ -28,10 +28,10 @@ export interface NamingIssue {
|
|
|
28
28
|
file: string;
|
|
29
29
|
line: number;
|
|
30
30
|
column?: number;
|
|
31
|
-
type:
|
|
31
|
+
type: string;
|
|
32
32
|
identifier: string;
|
|
33
33
|
suggestion?: string;
|
|
34
|
-
severity:
|
|
34
|
+
severity: Severity;
|
|
35
35
|
category?: 'naming';
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -40,14 +40,14 @@ export interface PatternIssue {
|
|
|
40
40
|
type: 'error-handling' | 'async-style' | 'import-style' | 'api-design';
|
|
41
41
|
description: string;
|
|
42
42
|
examples: string[];
|
|
43
|
-
severity:
|
|
43
|
+
severity: Severity;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export interface ArchitectureIssue {
|
|
47
47
|
type: 'file-organization' | 'module-structure' | 'export-style';
|
|
48
48
|
description: string;
|
|
49
49
|
affectedPaths: string[];
|
|
50
|
-
severity:
|
|
50
|
+
severity: Severity;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
export interface ConsistencyReport {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { TSESTree } from '@typescript-eslint/typescript-estree';
|
|
2
2
|
import { traverseAST } from './ast-parser';
|
|
3
|
+
import { Severity } from '@aiready/core';
|
|
3
4
|
|
|
4
5
|
export type FileType = 'test' | 'production' | 'config' | 'types';
|
|
5
6
|
export type CodeLayer = 'api' | 'business' | 'data' | 'utility' | 'unknown';
|
|
@@ -227,38 +228,50 @@ export function buildCodeContext(
|
|
|
227
228
|
* Get context-adjusted severity based on code context
|
|
228
229
|
*/
|
|
229
230
|
export function adjustSeverity(
|
|
230
|
-
baseSeverity:
|
|
231
|
+
baseSeverity: Severity | string,
|
|
231
232
|
context: CodeContext,
|
|
232
233
|
issueType: string
|
|
233
|
-
):
|
|
234
|
+
): Severity {
|
|
235
|
+
const getEnum = (s: any): Severity => {
|
|
236
|
+
if (s === Severity.Critical || s === 'critical') return Severity.Critical;
|
|
237
|
+
if (s === Severity.Major || s === 'major') return Severity.Major;
|
|
238
|
+
if (s === Severity.Minor || s === 'minor') return Severity.Minor;
|
|
239
|
+
return Severity.Info;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
let currentSev = getEnum(baseSeverity);
|
|
243
|
+
|
|
234
244
|
// Test files: Be more lenient
|
|
235
245
|
if (context.isTestFile) {
|
|
236
|
-
if (
|
|
237
|
-
if (
|
|
246
|
+
if (currentSev === Severity.Minor) currentSev = Severity.Info;
|
|
247
|
+
if (currentSev === Severity.Major) currentSev = Severity.Minor;
|
|
238
248
|
}
|
|
239
249
|
|
|
240
250
|
// Type definition files: Be more lenient (often use short generic names)
|
|
241
251
|
if (context.isTypeDefinition) {
|
|
242
|
-
if (
|
|
252
|
+
if (currentSev === Severity.Minor) currentSev = Severity.Info;
|
|
243
253
|
}
|
|
244
254
|
|
|
245
255
|
// API layer: Be stricter (public interface)
|
|
246
256
|
if (context.codeLayer === 'api') {
|
|
247
|
-
if (
|
|
248
|
-
|
|
257
|
+
if (currentSev === Severity.Info && issueType === 'unclear')
|
|
258
|
+
currentSev = Severity.Minor;
|
|
259
|
+
if (currentSev === Severity.Minor && issueType === 'unclear')
|
|
260
|
+
currentSev = Severity.Major;
|
|
249
261
|
}
|
|
250
262
|
|
|
251
263
|
// High complexity: Be stricter (need clearer names)
|
|
252
264
|
if (context.complexity > 10) {
|
|
253
|
-
if (
|
|
265
|
+
if (currentSev === Severity.Info) currentSev = Severity.Minor;
|
|
254
266
|
}
|
|
255
267
|
|
|
256
268
|
// Utility/helper layer: Allow shorter names
|
|
257
269
|
if (context.codeLayer === 'utility') {
|
|
258
|
-
if (
|
|
270
|
+
if (currentSev === Severity.Minor && issueType === 'abbreviation')
|
|
271
|
+
currentSev = Severity.Info;
|
|
259
272
|
}
|
|
260
273
|
|
|
261
|
-
return
|
|
274
|
+
return currentSev;
|
|
262
275
|
}
|
|
263
276
|
|
|
264
277
|
/**
|