Atlas / scripts /utilities /check_naming_conventions.py
findEthics
Complete codebase cleanup and project structure validation
f0b765c
Raw
History Blame Contribute Delete
9.59 kB
#!/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
@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()