@aiready/consistency 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +24 -0
- package/CONTRIBUTING.md +153 -0
- package/LICENSE +21 -0
- package/README.md +170 -0
- package/dist/chunk-BDDMOIU2.mjs +385 -0
- package/dist/chunk-CF4LU7KE.mjs +384 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +626 -0
- package/dist/cli.mjs +224 -0
- package/dist/index.d.mts +77 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +415 -0
- package/dist/index.mjs +12 -0
- package/package.json +63 -0
- package/src/__tests__/analyzer.test.ts +127 -0
- package/src/analyzer.ts +182 -0
- package/src/analyzers/naming.ts +134 -0
- package/src/analyzers/patterns.ts +192 -0
- package/src/cli.ts +254 -0
- package/src/index.ts +11 -0
- package/src/types.ts +62 -0
- package/tsconfig.json +9 -0
package/src/analyzer.ts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { scanFiles } from '@aiready/core';
|
|
2
|
+
import type { AnalysisResult, Issue } from '@aiready/core';
|
|
3
|
+
import type { ConsistencyOptions, ConsistencyReport, ConsistencyIssue } from './types';
|
|
4
|
+
import { analyzeNaming, detectNamingConventions } from './analyzers/naming';
|
|
5
|
+
import { analyzePatterns } from './analyzers/patterns';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Main consistency analyzer that orchestrates all analysis types
|
|
9
|
+
*/
|
|
10
|
+
export async function analyzeConsistency(
|
|
11
|
+
options: ConsistencyOptions
|
|
12
|
+
): Promise<ConsistencyReport> {
|
|
13
|
+
const {
|
|
14
|
+
checkNaming = true,
|
|
15
|
+
checkPatterns = true,
|
|
16
|
+
checkArchitecture = false, // Not implemented yet
|
|
17
|
+
minSeverity = 'info',
|
|
18
|
+
...scanOptions
|
|
19
|
+
} = options;
|
|
20
|
+
|
|
21
|
+
// Scan files
|
|
22
|
+
const filePaths = await scanFiles(scanOptions);
|
|
23
|
+
|
|
24
|
+
// Collect issues by category
|
|
25
|
+
const namingIssues = checkNaming ? await analyzeNaming(filePaths) : [];
|
|
26
|
+
const patternIssues = checkPatterns ? await analyzePatterns(filePaths) : [];
|
|
27
|
+
|
|
28
|
+
// Convert to AnalysisResult format
|
|
29
|
+
const results: AnalysisResult[] = [];
|
|
30
|
+
const fileIssuesMap = new Map<string, ConsistencyIssue[]>();
|
|
31
|
+
|
|
32
|
+
// Process naming issues
|
|
33
|
+
for (const issue of namingIssues) {
|
|
34
|
+
if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const consistencyIssue: ConsistencyIssue = {
|
|
39
|
+
type: issue.type === 'convention-mix' ? 'naming-inconsistency' : 'naming-quality',
|
|
40
|
+
category: 'naming',
|
|
41
|
+
severity: issue.severity,
|
|
42
|
+
message: `${issue.type}: ${issue.identifier}`,
|
|
43
|
+
location: {
|
|
44
|
+
file: issue.file,
|
|
45
|
+
line: issue.line,
|
|
46
|
+
column: issue.column
|
|
47
|
+
},
|
|
48
|
+
suggestion: issue.suggestion
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if (!fileIssuesMap.has(issue.file)) {
|
|
52
|
+
fileIssuesMap.set(issue.file, []);
|
|
53
|
+
}
|
|
54
|
+
fileIssuesMap.get(issue.file)!.push(consistencyIssue);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Process pattern issues
|
|
58
|
+
for (const issue of patternIssues) {
|
|
59
|
+
if (!shouldIncludeSeverity(issue.severity, minSeverity)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const consistencyIssue: ConsistencyIssue = {
|
|
64
|
+
type: 'pattern-inconsistency',
|
|
65
|
+
category: 'patterns',
|
|
66
|
+
severity: issue.severity,
|
|
67
|
+
message: issue.description,
|
|
68
|
+
location: {
|
|
69
|
+
file: issue.files[0] || 'multiple files',
|
|
70
|
+
line: 1
|
|
71
|
+
},
|
|
72
|
+
examples: issue.examples,
|
|
73
|
+
suggestion: `Standardize ${issue.type} patterns across ${issue.files.length} files`
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Add to first file in the pattern
|
|
77
|
+
const firstFile = issue.files[0];
|
|
78
|
+
if (firstFile && !fileIssuesMap.has(firstFile)) {
|
|
79
|
+
fileIssuesMap.set(firstFile, []);
|
|
80
|
+
}
|
|
81
|
+
if (firstFile) {
|
|
82
|
+
fileIssuesMap.get(firstFile)!.push(consistencyIssue);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Convert to AnalysisResult format
|
|
87
|
+
for (const [fileName, issues] of fileIssuesMap) {
|
|
88
|
+
results.push({
|
|
89
|
+
fileName,
|
|
90
|
+
issues: issues as Issue[],
|
|
91
|
+
metrics: {
|
|
92
|
+
consistencyScore: calculateConsistencyScore(issues)
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Generate recommendations
|
|
98
|
+
const recommendations = generateRecommendations(namingIssues, patternIssues);
|
|
99
|
+
|
|
100
|
+
// Detect naming conventions
|
|
101
|
+
const conventionAnalysis = detectNamingConventions(filePaths, namingIssues);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
summary: {
|
|
105
|
+
totalIssues: namingIssues.length + patternIssues.length,
|
|
106
|
+
namingIssues: namingIssues.length,
|
|
107
|
+
patternIssues: patternIssues.length,
|
|
108
|
+
architectureIssues: 0,
|
|
109
|
+
filesAnalyzed: filePaths.length
|
|
110
|
+
},
|
|
111
|
+
results,
|
|
112
|
+
recommendations
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function shouldIncludeSeverity(
|
|
117
|
+
severity: 'critical' | 'major' | 'minor' | 'info',
|
|
118
|
+
minSeverity: 'critical' | 'major' | 'minor' | 'info'
|
|
119
|
+
): boolean {
|
|
120
|
+
const severityLevels = { info: 0, minor: 1, major: 2, critical: 3 };
|
|
121
|
+
return severityLevels[severity] >= severityLevels[minSeverity];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function calculateConsistencyScore(issues: ConsistencyIssue[]): number {
|
|
125
|
+
// Higher score = more consistent (fewer issues)
|
|
126
|
+
const weights = { critical: 10, major: 5, minor: 2, info: 1 };
|
|
127
|
+
const totalWeight = issues.reduce((sum, issue) => sum + weights[issue.severity], 0);
|
|
128
|
+
// Score from 0-1, where 1 is perfect
|
|
129
|
+
return Math.max(0, 1 - totalWeight / 100);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function generateRecommendations(
|
|
133
|
+
namingIssues: any[],
|
|
134
|
+
patternIssues: any[]
|
|
135
|
+
): string[] {
|
|
136
|
+
const recommendations: string[] = [];
|
|
137
|
+
|
|
138
|
+
if (namingIssues.length > 0) {
|
|
139
|
+
const conventionMixCount = namingIssues.filter(i => i.type === 'convention-mix').length;
|
|
140
|
+
if (conventionMixCount > 0) {
|
|
141
|
+
recommendations.push(
|
|
142
|
+
`Standardize naming conventions: Found ${conventionMixCount} snake_case variables in TypeScript/JavaScript (use camelCase)`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const poorNamingCount = namingIssues.filter(i => i.type === 'poor-naming').length;
|
|
147
|
+
if (poorNamingCount > 0) {
|
|
148
|
+
recommendations.push(
|
|
149
|
+
`Improve variable naming: Found ${poorNamingCount} single-letter or unclear variable names`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (patternIssues.length > 0) {
|
|
155
|
+
const errorHandlingIssues = patternIssues.filter(i => i.type === 'error-handling');
|
|
156
|
+
if (errorHandlingIssues.length > 0) {
|
|
157
|
+
recommendations.push(
|
|
158
|
+
'Standardize error handling strategy across the codebase (prefer try-catch with typed errors)'
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const asyncIssues = patternIssues.filter(i => i.type === 'async-style');
|
|
163
|
+
if (asyncIssues.length > 0) {
|
|
164
|
+
recommendations.push(
|
|
165
|
+
'Use async/await consistently instead of mixing with promise chains or callbacks'
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const importIssues = patternIssues.filter(i => i.type === 'import-style');
|
|
170
|
+
if (importIssues.length > 0) {
|
|
171
|
+
recommendations.push(
|
|
172
|
+
'Use ES modules consistently across the project (avoid mixing with CommonJS)'
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (recommendations.length === 0) {
|
|
178
|
+
recommendations.push('No major consistency issues found! Your codebase follows good practices.');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return recommendations;
|
|
182
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { readFileContent } from '@aiready/core';
|
|
2
|
+
import type { NamingIssue } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Analyzes naming conventions and quality
|
|
6
|
+
*/
|
|
7
|
+
export async function analyzeNaming(files: string[]): Promise<NamingIssue[]> {
|
|
8
|
+
const issues: NamingIssue[] = [];
|
|
9
|
+
|
|
10
|
+
for (const file of files) {
|
|
11
|
+
const content = await readFileContent(file);
|
|
12
|
+
const fileIssues = analyzeFileNaming(file, content);
|
|
13
|
+
issues.push(...fileIssues);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return issues;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function analyzeFileNaming(file: string, content: string): NamingIssue[] {
|
|
20
|
+
const issues: NamingIssue[] = [];
|
|
21
|
+
|
|
22
|
+
// Split into lines for line number tracking
|
|
23
|
+
const lines = content.split('\n');
|
|
24
|
+
|
|
25
|
+
// Check for naming patterns
|
|
26
|
+
lines.forEach((line, index) => {
|
|
27
|
+
const lineNumber = index + 1;
|
|
28
|
+
|
|
29
|
+
// Check for single letter variables (except i, j, k in loops)
|
|
30
|
+
const singleLetterMatches = line.matchAll(/\b(?:const|let|var)\s+([a-hm-z])\s*=/gi);
|
|
31
|
+
for (const match of singleLetterMatches) {
|
|
32
|
+
issues.push({
|
|
33
|
+
file,
|
|
34
|
+
line: lineNumber,
|
|
35
|
+
type: 'poor-naming',
|
|
36
|
+
identifier: match[1],
|
|
37
|
+
severity: 'minor',
|
|
38
|
+
suggestion: `Use descriptive variable name instead of single letter '${match[1]}'`
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check for overly abbreviated variables
|
|
43
|
+
const abbreviationMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z]{1,3})(?=[A-Z]|_|\s*=)/g);
|
|
44
|
+
for (const match of abbreviationMatches) {
|
|
45
|
+
const abbrev = match[1];
|
|
46
|
+
// Skip common acceptable abbreviations
|
|
47
|
+
if (!['id', 'url', 'api', 'db', 'fs', 'os', 'ui'].includes(abbrev.toLowerCase())) {
|
|
48
|
+
issues.push({
|
|
49
|
+
file,
|
|
50
|
+
line: lineNumber,
|
|
51
|
+
type: 'abbreviation',
|
|
52
|
+
identifier: abbrev,
|
|
53
|
+
severity: 'info',
|
|
54
|
+
suggestion: `Consider using full word instead of abbreviation '${abbrev}'`
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check for snake_case vs camelCase mixing in TypeScript/JavaScript
|
|
60
|
+
if (file.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
61
|
+
const camelCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*=/);
|
|
62
|
+
const snakeCaseVars = line.match(/\b(?:const|let|var)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\s*=/);
|
|
63
|
+
|
|
64
|
+
if (snakeCaseVars) {
|
|
65
|
+
issues.push({
|
|
66
|
+
file,
|
|
67
|
+
line: lineNumber,
|
|
68
|
+
type: 'convention-mix',
|
|
69
|
+
identifier: snakeCaseVars[1],
|
|
70
|
+
severity: 'minor',
|
|
71
|
+
suggestion: `Use camelCase '${snakeCaseToCamelCase(snakeCaseVars[1])}' instead of snake_case in TypeScript/JavaScript`
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check for unclear boolean names (should start with is/has/should/can)
|
|
77
|
+
const booleanMatches = line.matchAll(/\b(?:const|let|var)\s+([a-z][a-zA-Z0-9]*)\s*:\s*boolean/gi);
|
|
78
|
+
for (const match of booleanMatches) {
|
|
79
|
+
const name = match[1];
|
|
80
|
+
if (!name.match(/^(is|has|should|can|will|did)/i)) {
|
|
81
|
+
issues.push({
|
|
82
|
+
file,
|
|
83
|
+
line: lineNumber,
|
|
84
|
+
type: 'unclear',
|
|
85
|
+
identifier: name,
|
|
86
|
+
severity: 'info',
|
|
87
|
+
suggestion: `Boolean variable '${name}' should start with is/has/should/can for clarity`
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check for function names that don't indicate action
|
|
93
|
+
const functionMatches = line.matchAll(/function\s+([a-z][a-zA-Z0-9]*)/g);
|
|
94
|
+
for (const match of functionMatches) {
|
|
95
|
+
const name = match[1];
|
|
96
|
+
// Functions should typically start with verbs
|
|
97
|
+
if (!name.match(/^(get|set|is|has|can|should|create|update|delete|fetch|load|save|process|handle|validate|check|find|search|filter|map|reduce)/)) {
|
|
98
|
+
issues.push({
|
|
99
|
+
file,
|
|
100
|
+
line: lineNumber,
|
|
101
|
+
type: 'unclear',
|
|
102
|
+
identifier: name,
|
|
103
|
+
severity: 'info',
|
|
104
|
+
suggestion: `Function '${name}' should start with an action verb (get, set, create, etc.)`
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return issues;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function snakeCaseToCamelCase(str: string): string {
|
|
114
|
+
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Detects naming convention patterns across the codebase
|
|
119
|
+
*/
|
|
120
|
+
export function detectNamingConventions(files: string[], allIssues: NamingIssue[]): {
|
|
121
|
+
dominantConvention: 'camelCase' | 'snake_case' | 'PascalCase' | 'mixed';
|
|
122
|
+
conventionScore: number;
|
|
123
|
+
} {
|
|
124
|
+
// Count conventions
|
|
125
|
+
const camelCaseCount = allIssues.filter(i => i.type === 'convention-mix').length;
|
|
126
|
+
const totalChecks = files.length * 10; // Rough estimate
|
|
127
|
+
|
|
128
|
+
if (camelCaseCount / totalChecks > 0.3) {
|
|
129
|
+
return { dominantConvention: 'mixed', conventionScore: 0.5 };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// For TypeScript/JavaScript, default to camelCase
|
|
133
|
+
return { dominantConvention: 'camelCase', conventionScore: 0.9 };
|
|
134
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { readFileContent } from '@aiready/core';
|
|
2
|
+
import type { PatternIssue } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Analyzes code pattern consistency
|
|
6
|
+
*/
|
|
7
|
+
export async function analyzePatterns(files: string[]): Promise<PatternIssue[]> {
|
|
8
|
+
const issues: PatternIssue[] = [];
|
|
9
|
+
|
|
10
|
+
// Analyze error handling patterns
|
|
11
|
+
const errorHandlingIssues = await analyzeErrorHandling(files);
|
|
12
|
+
issues.push(...errorHandlingIssues);
|
|
13
|
+
|
|
14
|
+
// Analyze async/await patterns
|
|
15
|
+
const asyncIssues = await analyzeAsyncPatterns(files);
|
|
16
|
+
issues.push(...asyncIssues);
|
|
17
|
+
|
|
18
|
+
// Analyze import styles
|
|
19
|
+
const importIssues = await analyzeImportStyles(files);
|
|
20
|
+
issues.push(...importIssues);
|
|
21
|
+
|
|
22
|
+
return issues;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function analyzeErrorHandling(files: string[]): Promise<PatternIssue[]> {
|
|
26
|
+
const patterns = {
|
|
27
|
+
tryCatch: [] as string[],
|
|
28
|
+
throwsError: [] as string[],
|
|
29
|
+
returnsNull: [] as string[],
|
|
30
|
+
returnsError: [] as string[]
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
for (const file of files) {
|
|
34
|
+
const content = await readFileContent(file);
|
|
35
|
+
|
|
36
|
+
if (content.includes('try {') || content.includes('} catch')) {
|
|
37
|
+
patterns.tryCatch.push(file);
|
|
38
|
+
}
|
|
39
|
+
if (content.match(/throw new \w*Error/)) {
|
|
40
|
+
patterns.throwsError.push(file);
|
|
41
|
+
}
|
|
42
|
+
if (content.match(/return null/)) {
|
|
43
|
+
patterns.returnsNull.push(file);
|
|
44
|
+
}
|
|
45
|
+
if (content.match(/return \{ error:/)) {
|
|
46
|
+
patterns.returnsError.push(file);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const issues: PatternIssue[] = [];
|
|
51
|
+
|
|
52
|
+
// Check for mixed error handling strategies
|
|
53
|
+
const strategiesUsed = Object.values(patterns).filter(p => p.length > 0).length;
|
|
54
|
+
if (strategiesUsed > 2) {
|
|
55
|
+
issues.push({
|
|
56
|
+
files: [...new Set([
|
|
57
|
+
...patterns.tryCatch,
|
|
58
|
+
...patterns.throwsError,
|
|
59
|
+
...patterns.returnsNull,
|
|
60
|
+
...patterns.returnsError
|
|
61
|
+
])],
|
|
62
|
+
type: 'error-handling',
|
|
63
|
+
description: 'Inconsistent error handling strategies across codebase',
|
|
64
|
+
examples: [
|
|
65
|
+
patterns.tryCatch.length > 0 ? `Try-catch used in ${patterns.tryCatch.length} files` : '',
|
|
66
|
+
patterns.throwsError.length > 0 ? `Throws errors in ${patterns.throwsError.length} files` : '',
|
|
67
|
+
patterns.returnsNull.length > 0 ? `Returns null in ${patterns.returnsNull.length} files` : '',
|
|
68
|
+
patterns.returnsError.length > 0 ? `Returns error objects in ${patterns.returnsError.length} files` : ''
|
|
69
|
+
].filter(e => e),
|
|
70
|
+
severity: 'major'
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return issues;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function analyzeAsyncPatterns(files: string[]): Promise<PatternIssue[]> {
|
|
78
|
+
const patterns = {
|
|
79
|
+
asyncAwait: [] as string[],
|
|
80
|
+
promises: [] as string[],
|
|
81
|
+
callbacks: [] as string[]
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
for (const file of files) {
|
|
85
|
+
const content = await readFileContent(file);
|
|
86
|
+
|
|
87
|
+
if (content.match(/async\s+(function|\(|[a-zA-Z])/)) {
|
|
88
|
+
patterns.asyncAwait.push(file);
|
|
89
|
+
}
|
|
90
|
+
if (content.match(/\.then\(/) || content.match(/\.catch\(/)) {
|
|
91
|
+
patterns.promises.push(file);
|
|
92
|
+
}
|
|
93
|
+
if (content.match(/callback\s*\(/) || content.match(/\(\s*err\s*,/)) {
|
|
94
|
+
patterns.callbacks.push(file);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const issues: PatternIssue[] = [];
|
|
99
|
+
|
|
100
|
+
// Modern codebases should prefer async/await
|
|
101
|
+
if (patterns.callbacks.length > 0 && patterns.asyncAwait.length > 0) {
|
|
102
|
+
issues.push({
|
|
103
|
+
files: [...patterns.callbacks, ...patterns.asyncAwait],
|
|
104
|
+
type: 'async-style',
|
|
105
|
+
description: 'Mixed async patterns: callbacks and async/await',
|
|
106
|
+
examples: [
|
|
107
|
+
`Callbacks found in: ${patterns.callbacks.slice(0, 3).join(', ')}`,
|
|
108
|
+
`Async/await used in: ${patterns.asyncAwait.slice(0, 3).join(', ')}`
|
|
109
|
+
],
|
|
110
|
+
severity: 'minor'
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Mixing .then() chains with async/await
|
|
115
|
+
if (patterns.promises.length > patterns.asyncAwait.length * 0.3 && patterns.asyncAwait.length > 0) {
|
|
116
|
+
issues.push({
|
|
117
|
+
files: patterns.promises,
|
|
118
|
+
type: 'async-style',
|
|
119
|
+
description: 'Consider using async/await instead of promise chains for consistency',
|
|
120
|
+
examples: patterns.promises.slice(0, 5),
|
|
121
|
+
severity: 'info'
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return issues;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function analyzeImportStyles(files: string[]): Promise<PatternIssue[]> {
|
|
129
|
+
const patterns = {
|
|
130
|
+
esModules: [] as string[],
|
|
131
|
+
commonJS: [] as string[],
|
|
132
|
+
mixed: [] as string[]
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
for (const file of files) {
|
|
136
|
+
const content = await readFileContent(file);
|
|
137
|
+
const hasESM = content.match(/^import\s+/m);
|
|
138
|
+
const hasCJS = content.match(/require\s*\(/);
|
|
139
|
+
|
|
140
|
+
if (hasESM && hasCJS) {
|
|
141
|
+
patterns.mixed.push(file);
|
|
142
|
+
} else if (hasESM) {
|
|
143
|
+
patterns.esModules.push(file);
|
|
144
|
+
} else if (hasCJS) {
|
|
145
|
+
patterns.commonJS.push(file);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const issues: PatternIssue[] = [];
|
|
150
|
+
|
|
151
|
+
// Check for mixed import styles in same file
|
|
152
|
+
if (patterns.mixed.length > 0) {
|
|
153
|
+
issues.push({
|
|
154
|
+
files: patterns.mixed,
|
|
155
|
+
type: 'import-style',
|
|
156
|
+
description: 'Mixed ES modules and CommonJS imports in same files',
|
|
157
|
+
examples: patterns.mixed.slice(0, 5),
|
|
158
|
+
severity: 'major'
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Check for inconsistent styles across project
|
|
163
|
+
if (patterns.esModules.length > 0 && patterns.commonJS.length > 0) {
|
|
164
|
+
const ratio = patterns.commonJS.length / (patterns.esModules.length + patterns.commonJS.length);
|
|
165
|
+
if (ratio > 0.2 && ratio < 0.8) {
|
|
166
|
+
issues.push({
|
|
167
|
+
files: [...patterns.esModules, ...patterns.commonJS],
|
|
168
|
+
type: 'import-style',
|
|
169
|
+
description: 'Inconsistent import styles across project',
|
|
170
|
+
examples: [
|
|
171
|
+
`ES modules: ${patterns.esModules.length} files`,
|
|
172
|
+
`CommonJS: ${patterns.commonJS.length} files`
|
|
173
|
+
],
|
|
174
|
+
severity: 'minor'
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return issues;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Analyzes API design consistency
|
|
184
|
+
*/
|
|
185
|
+
export async function analyzeAPIDesign(files: string[]): Promise<PatternIssue[]> {
|
|
186
|
+
// This would analyze:
|
|
187
|
+
// - Function parameter order consistency
|
|
188
|
+
// - Return type patterns
|
|
189
|
+
// - Options object vs individual parameters
|
|
190
|
+
// For now, return empty array
|
|
191
|
+
return [];
|
|
192
|
+
}
|