@aiready/consistency 0.6.17 → 0.7.1

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.
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Python Naming Analyzer - PEP 8 Compliant
3
+ *
4
+ * Analyzes Python code for PEP 8 naming convention violations
5
+ * https://peps.python.org/pep-0008/#naming-conventions
6
+ */
7
+
8
+ import { getParser, Language } from '@aiready/core';
9
+ import type { NamingIssue } from '../types';
10
+
11
+ /**
12
+ * Analyze Python files for PEP 8 naming violations
13
+ */
14
+ export async function analyzePythonNaming(files: string[]): Promise<NamingIssue[]> {
15
+ const issues: NamingIssue[] = [];
16
+ const parser = getParser('dummy.py'); // Get Python parser instance
17
+
18
+ if (!parser) {
19
+ console.warn('Python parser not available');
20
+ return issues;
21
+ }
22
+
23
+ // Filter to only Python files
24
+ const pythonFiles = files.filter(f => f.toLowerCase().endsWith('.py'));
25
+
26
+ for (const file of pythonFiles) {
27
+ try {
28
+ const fs = await import('fs');
29
+ const code = await fs.promises.readFile(file, 'utf-8');
30
+ const result = parser.parse(code, file);
31
+
32
+ // Analyze each export for naming violations
33
+ for (const exp of result.exports) {
34
+ const nameIssue = checkPythonNaming(exp.name, exp.type, file, exp.loc?.start.line || 0);
35
+ if (nameIssue) {
36
+ issues.push(nameIssue);
37
+ }
38
+ }
39
+
40
+ // Analyze imports for naming issues (optional, less critical)
41
+ for (const imp of result.imports) {
42
+ for (const spec of imp.specifiers) {
43
+ if (spec !== '*' && spec !== 'default') {
44
+ const nameIssue = checkPythonNaming(spec, 'variable', file, imp.loc?.start.line || 0);
45
+ if (nameIssue) {
46
+ issues.push(nameIssue);
47
+ }
48
+ }
49
+ }
50
+ }
51
+ } catch (error) {
52
+ console.warn(`Skipping ${file} due to error:`, error);
53
+ }
54
+ }
55
+
56
+ return issues;
57
+ }
58
+
59
+ /**
60
+ * Check a Python identifier against PEP 8 conventions
61
+ */
62
+ function checkPythonNaming(
63
+ identifier: string,
64
+ type: string,
65
+ file: string,
66
+ line: number
67
+ ): NamingIssue | null {
68
+ // Get naming conventions from parser
69
+ const parser = getParser('dummy.py');
70
+ const conventions = parser?.getNamingConventions();
71
+ if (!conventions) return null;
72
+
73
+ // Skip special methods and exceptions
74
+ if (conventions.exceptions?.includes(identifier)) {
75
+ return null;
76
+ }
77
+
78
+ // Check based on type
79
+ if (type === 'class') {
80
+ // Classes should be PascalCase
81
+ if (!conventions.classPattern.test(identifier)) {
82
+ return {
83
+ type: 'poor-naming',
84
+ identifier,
85
+ file,
86
+ line,
87
+ column: 0,
88
+ severity: 'major',
89
+ category: 'naming',
90
+ suggestion: `Class names should use PascalCase (e.g., ${toPascalCase(identifier)})`,
91
+ };
92
+ }
93
+ } else if (type === 'function') {
94
+ // Functions should be snake_case
95
+ if (!conventions.functionPattern.test(identifier)) {
96
+ // Check if it's incorrectly using camelCase
97
+ if (/^[a-z][a-zA-Z0-9]*$/.test(identifier) && /[A-Z]/.test(identifier)) {
98
+ return {
99
+ type: 'convention-mix',
100
+ identifier,
101
+ file,
102
+ line,
103
+ column: 0,
104
+ severity: 'major',
105
+ category: 'naming',
106
+ suggestion: `Function names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`,
107
+ };
108
+ }
109
+ }
110
+ } else if (type === 'const' || type === 'variable') {
111
+ // Check if it looks like a constant (all uppercase)
112
+ if (identifier === identifier.toUpperCase() && identifier.length > 1) {
113
+ // Constants should be UPPER_CASE_WITH_UNDERSCORES
114
+ if (!conventions.constantPattern.test(identifier)) {
115
+ return {
116
+ type: 'poor-naming',
117
+ identifier,
118
+ file,
119
+ line,
120
+ column: 0,
121
+ severity: 'minor',
122
+ category: 'naming',
123
+ suggestion: 'Constants should use UPPER_CASE_WITH_UNDERSCORES',
124
+ };
125
+ }
126
+ } else {
127
+ // Regular variables should be snake_case
128
+ if (!conventions.variablePattern.test(identifier)) {
129
+ // Check if it's using camelCase (common mistake from JS/TS developers)
130
+ if (/^[a-z][a-zA-Z0-9]*$/.test(identifier) && /[A-Z]/.test(identifier)) {
131
+ return {
132
+ type: 'convention-mix',
133
+ identifier,
134
+ file,
135
+ line,
136
+ column: 0,
137
+ severity: 'major',
138
+ category: 'naming',
139
+ suggestion: `Variable names should use snake_case, not camelCase (e.g., ${toSnakeCase(identifier)})`,
140
+ };
141
+ }
142
+ }
143
+ }
144
+ }
145
+
146
+ return null;
147
+ }
148
+
149
+ /**
150
+ * Convert camelCase to snake_case
151
+ */
152
+ function toSnakeCase(str: string): string {
153
+ return str
154
+ .replace(/([A-Z])/g, '_$1')
155
+ .toLowerCase()
156
+ .replace(/^_/, '');
157
+ }
158
+
159
+ /**
160
+ * Convert snake_case to PascalCase
161
+ */
162
+ function toPascalCase(str: string): string {
163
+ return str
164
+ .split('_')
165
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
166
+ .join('');
167
+ }
168
+
169
+ /**
170
+ * Detect common Python anti-patterns in naming
171
+ */
172
+ export function detectPythonNamingAntiPatterns(files: string[]): NamingIssue[] {
173
+ const issues: NamingIssue[] = [];
174
+
175
+ // Anti-pattern 1: Using camelCase in Python (common for JS/TS developers)
176
+ // Anti-pattern 2: Using PascalCase for functions
177
+ // Anti-pattern 3: Not using leading underscore for private methods
178
+ // Anti-pattern 4: Using single letter names outside of comprehensions
179
+
180
+ // These will be implemented as we refine the analyzer
181
+
182
+ return issues;
183
+ }