Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| Check and fix naming conventions across the codebase. | |
| This script analyzes Python files for naming convention compliance: | |
| - Files: snake_case | |
| - Classes: PascalCase | |
| - Functions/methods: snake_case | |
| - Constants: UPPER_CASE | |
| - Variables: snake_case | |
| """ | |
| import ast | |
| import os | |
| import re | |
| from pathlib import Path | |
| from typing import Dict, List, Tuple, Set | |
| from dataclasses import dataclass | |
| class NamingIssue: | |
| """Represents a naming convention issue""" | |
| file_path: str | |
| line_number: int | |
| issue_type: str | |
| current_name: str | |
| suggested_name: str | |
| description: str | |
| class NamingConventionChecker: | |
| """Checks and reports naming convention issues""" | |
| def __init__(self): | |
| self.issues: List[NamingIssue] = [] | |
| def is_snake_case(self, name: str) -> bool: | |
| """Check if name follows snake_case convention""" | |
| # Allow leading underscores for private functions/variables | |
| return re.match(r'^_*[a-z][a-z0-9_]*$', name) is not None | |
| def is_pascal_case(self, name: str) -> bool: | |
| """Check if name follows PascalCase convention""" | |
| return re.match(r'^[A-Z][a-zA-Z0-9]*$', name) is not None | |
| def is_upper_case(self, name: str) -> bool: | |
| """Check if name follows UPPER_CASE convention""" | |
| return re.match(r'^[A-Z][A-Z0-9_]*$', name) is not None | |
| def to_snake_case(self, name: str) -> str: | |
| """Convert name to snake_case""" | |
| # Handle PascalCase to snake_case | |
| s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) | |
| return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() | |
| def to_pascal_case(self, name: str) -> str: | |
| """Convert name to PascalCase""" | |
| return ''.join(word.capitalize() for word in name.split('_')) | |
| def to_upper_case(self, name: str) -> str: | |
| """Convert name to UPPER_CASE""" | |
| return name.upper() | |
| def analyze_file(self, file_path: str) -> List[NamingIssue]: | |
| """Analyze a Python file for naming convention issues""" | |
| issues = [] | |
| try: | |
| with open(file_path, 'r', encoding='utf-8') as f: | |
| content = f.read() | |
| tree = ast.parse(content) | |
| except (SyntaxError, UnicodeDecodeError) as e: | |
| return issues | |
| for node in ast.walk(tree): | |
| # Check class names (should be PascalCase) | |
| if isinstance(node, ast.ClassDef): | |
| if not self.is_pascal_case(node.name): | |
| issues.append(NamingIssue( | |
| file_path=file_path, | |
| line_number=node.lineno, | |
| issue_type="class_name", | |
| current_name=node.name, | |
| suggested_name=self.to_pascal_case(node.name), | |
| description=f"Class '{node.name}' should use PascalCase" | |
| )) | |
| # Check function/method names (should be snake_case) | |
| elif isinstance(node, ast.FunctionDef) or isinstance(node, ast.AsyncFunctionDef): | |
| # Skip special methods like __init__, __str__, etc. | |
| if not (node.name.startswith('__') and node.name.endswith('__')): | |
| if not self.is_snake_case(node.name): | |
| issues.append(NamingIssue( | |
| file_path=file_path, | |
| line_number=node.lineno, | |
| issue_type="function_name", | |
| current_name=node.name, | |
| suggested_name=self.to_snake_case(node.name), | |
| description=f"Function '{node.name}' should use snake_case" | |
| )) | |
| # Check variable assignments for constants (UPPER_CASE) | |
| elif isinstance(node, ast.Assign): | |
| for target in node.targets: | |
| if isinstance(target, ast.Name): | |
| name = target.id | |
| # Check if it looks like a constant (all caps or starts with caps) | |
| if (name.isupper() or (len(name) > 1 and name[0].isupper())) and '_' in name: | |
| if not self.is_upper_case(name): | |
| issues.append(NamingIssue( | |
| file_path=file_path, | |
| line_number=node.lineno, | |
| issue_type="constant_name", | |
| current_name=name, | |
| suggested_name=self.to_upper_case(name), | |
| description=f"Constant '{name}' should use UPPER_CASE" | |
| )) | |
| return issues | |
| def check_file_names(self, directory: str) -> List[NamingIssue]: | |
| """Check if Python file names follow snake_case convention""" | |
| issues = [] | |
| for root, dirs, files in os.walk(directory): | |
| # Skip certain directories | |
| dirs[:] = [d for d in dirs if d not in ['__pycache__', '.git', 'venv', 'env', '.pytest_cache', 'backups', 'atlas_env', 'node_modules', '.vscode']] | |
| for file in files: | |
| if file.endswith('.py') and file != '__init__.py': | |
| file_path = os.path.join(root, file) | |
| file_name = file[:-3] # Remove .py extension | |
| if not self.is_snake_case(file_name): | |
| suggested_name = self.to_snake_case(file_name) + '.py' | |
| issues.append(NamingIssue( | |
| file_path=file_path, | |
| line_number=0, | |
| issue_type="file_name", | |
| current_name=file, | |
| suggested_name=suggested_name, | |
| description=f"File '{file}' should use snake_case naming" | |
| )) | |
| return issues | |
| def analyze_directory(self, directory: str) -> List[NamingIssue]: | |
| """Analyze all Python files in a directory for naming issues""" | |
| all_issues = [] | |
| # Check file names | |
| all_issues.extend(self.check_file_names(directory)) | |
| # Check code content | |
| for root, dirs, files in os.walk(directory): | |
| # Skip certain directories | |
| dirs[:] = [d for d in dirs if d not in ['__pycache__', '.git', 'venv', 'env', '.pytest_cache', 'backups', 'atlas_env', 'node_modules', '.vscode']] | |
| for file in files: | |
| if file.endswith('.py'): | |
| file_path = os.path.join(root, file) | |
| file_issues = self.analyze_file(file_path) | |
| all_issues.extend(file_issues) | |
| return all_issues | |
| def main(): | |
| """Main function to check naming conventions across the codebase""" | |
| print("π Checking naming conventions...") | |
| checker = NamingConventionChecker() | |
| project_root = Path(__file__).parent.parent.parent | |
| # Directories to check | |
| directories_to_check = [ | |
| str(project_root), # Root level | |
| str(project_root / 'analytics'), | |
| str(project_root / 'tests'), | |
| str(project_root / 'scripts'), | |
| str(project_root / 'archive') | |
| ] | |
| all_issues = [] | |
| for directory in directories_to_check: | |
| if os.path.exists(directory): | |
| print(f"\nπ Checking directory: {directory}") | |
| issues = checker.analyze_directory(directory) | |
| all_issues.extend(issues) | |
| print(f" Found {len(issues)} naming issues") | |
| # Group issues by type | |
| issues_by_type = {} | |
| for issue in all_issues: | |
| if issue.issue_type not in issues_by_type: | |
| issues_by_type[issue.issue_type] = [] | |
| issues_by_type[issue.issue_type].append(issue) | |
| # Report results | |
| print(f"\nπ Naming Convention Analysis Results:") | |
| print(f"Total issues found: {len(all_issues)}") | |
| for issue_type, issues in issues_by_type.items(): | |
| print(f"\n{issue_type.replace('_', ' ').title()} Issues ({len(issues)}):") | |
| for issue in issues[:10]: # Show first 10 of each type | |
| rel_path = os.path.relpath(issue.file_path, project_root) | |
| if issue.line_number > 0: | |
| print(f" π {rel_path}:{issue.line_number} - {issue.current_name} β {issue.suggested_name}") | |
| else: | |
| print(f" π {rel_path} - {issue.current_name} β {issue.suggested_name}") | |
| if len(issues) > 10: | |
| print(f" ... and {len(issues) - 10} more") | |
| # Provide recommendations | |
| print(f"\nπ‘ Recommendations:") | |
| if issues_by_type.get('file_name'): | |
| print(" β’ Rename files to use snake_case (e.g., MyFile.py β my_file.py)") | |
| if issues_by_type.get('class_name'): | |
| print(" β’ Rename classes to use PascalCase (e.g., my_class β MyClass)") | |
| if issues_by_type.get('function_name'): | |
| print(" β’ Rename functions to use snake_case (e.g., myFunction β my_function)") | |
| if issues_by_type.get('constant_name'): | |
| print(" β’ Rename constants to use UPPER_CASE (e.g., myConstant β MY_CONSTANT)") | |
| if not all_issues: | |
| print("β All naming conventions are properly followed!") | |
| if __name__ == "__main__": | |
| main() |