Created
January 11, 2025 09:42
-
-
Save oriapp/f956dc7633760ebed7a238b3be6e58ab to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import argparse | |
import ast | |
import json | |
import os | |
from termcolor import colored | |
from typing import List, Dict | |
# Define colors for different priorities | |
COLORS = { | |
'high': 'red', | |
'medium': 'yellow', | |
'low': 'cyan', | |
} | |
def parse_arguments(): | |
"""Parse command-line arguments.""" | |
parser = argparse.ArgumentParser(description="Check code quality and cleanliness.") | |
parser.add_argument("file", type=str, nargs="?", help="Path to the Python file to check.") | |
parser.add_argument( | |
"--ignore-complete-sentence", | |
action="store_true", | |
help="Ignore the rule that comments should be complete sentences.", | |
) | |
parser.add_argument( | |
"--generate-config", | |
action="store_true", | |
help="Generate a default configuration file.", | |
) | |
parser.add_argument( | |
"--config", | |
type=str, | |
default=None, | |
help="Path to the configuration file to use.", | |
) | |
return parser.parse_args() | |
def generate_default_config(file_path: str): | |
"""Generate a default configuration file.""" | |
default_config = { | |
"check_inline_comments": True, | |
"check_docstrings": True, | |
"check_compile_time_errors": True, | |
"ignore_complete_sentence": False, | |
"severity": { | |
"inline_comment": "low", | |
"docstring": "medium", | |
"compile_error": "high" | |
}, | |
"check_method_docstrings": True, | |
"check_class_docstrings": True, | |
"check_function_docstrings": True, | |
"check_init_docstrings": False, | |
} | |
# Ensure the directory exists for the config file | |
config_dir = os.path.dirname(file_path) | |
if config_dir and not os.path.exists(config_dir): | |
os.makedirs(config_dir, exist_ok=True) | |
# Write the default config to the specified file | |
with open(file_path, 'w') as f: | |
json.dump(default_config, f, indent=4) | |
print(colored(f"Default configuration file generated at {file_path}.", 'green')) | |
def check_inline_comments(content: str, config: Dict) -> List[str]: | |
"""Check if inline comments follow proper formatting.""" | |
issues = [] | |
if not config.get("check_inline_comments", True): | |
return issues | |
lines = content.splitlines() | |
for i, line in enumerate(lines): | |
stripped_line = line.strip() | |
if '#' in stripped_line: | |
comment_index = stripped_line.index('#') | |
comment_text = stripped_line[comment_index + 1:].strip() | |
if comment_text: # Only check non-empty comments | |
if not comment_text[0].isupper(): | |
issues.append( | |
colored(f"{i+1}: Inline comment should start with a capital letter.", | |
COLORS[config['severity']['inline_comment']]) | |
) | |
if not config["ignore_complete_sentence"] and not comment_text.endswith('.'): | |
issues.append( | |
colored(f"{i+1}: Inline comment should be a complete sentence.", | |
COLORS[config['severity']['inline_comment']]) | |
) | |
return issues | |
def check_compile_time_errors(file_path: str) -> List[str]: | |
"""Check for compile-time errors.""" | |
issues = [] | |
try: | |
with open(file_path, "r") as file: | |
content = file.read() | |
ast.parse(content) # Try parsing the file for syntax errors | |
except SyntaxError as e: | |
issues.append( | |
colored(f"Compile-time error in {file_path}, line {e.lineno}: {e.msg}", COLORS['high']) | |
) | |
except Exception as e: | |
issues.append( | |
colored(f"Unexpected error during parsing: {e}", COLORS['high']) | |
) | |
return issues | |
def check_docstrings(content: str, config: Dict) -> List[str]: | |
"""Check if all methods, classes, and functions have proper docstrings.""" | |
issues = [] | |
if not config.get("check_docstrings", True): | |
return issues | |
tree = ast.parse(content) | |
for node in ast.walk(tree): | |
# Check functions and methods for docstrings | |
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): | |
if not node.body or (not isinstance(node.body[0], ast.Expr) or not isinstance(node.body[0].value, ast.Str)): | |
if isinstance(node, ast.FunctionDef) and node.name == "__init__" and not config.get("check_init_docstrings", False): | |
continue | |
issues.append(colored(f"Missing docstring for {node.__class__.__name__} '{node.name}' at line {node.lineno}.", COLORS['medium'])) | |
return issues | |
def load_config(config_path: str) -> Dict: | |
"""Load configuration from a JSON file.""" | |
if os.path.exists(config_path): | |
with open(config_path, 'r') as f: | |
return json.load(f) | |
return {} | |
def main(): | |
args = parse_arguments() | |
if args.generate_config: | |
# Generate default configuration file if flag is set | |
config_file_path = "config.json" # Default path for the config file | |
generate_default_config(config_file_path) | |
return | |
# Ensure that file argument is passed if not generating config | |
if not args.file: | |
print(colored("Error: You must specify a Python file to check.", 'red')) | |
return | |
config = {} | |
if args.config: | |
config = load_config(args.config) | |
else: | |
print(colored("No configuration file specified. Using default settings.", 'yellow')) | |
# Read the file content | |
try: | |
with open(args.file, "r") as file: | |
content = file.read() | |
except FileNotFoundError: | |
print(colored(f"File not found: {args.file}", COLORS['high'])) | |
return | |
# Perform checks | |
issues = [] | |
issues.extend(check_compile_time_errors(args.file)) | |
issues.extend(check_inline_comments(content, config)) | |
issues.extend(check_docstrings(content, config)) | |
# Output results | |
if issues: | |
print("\n".join(issues)) | |
else: | |
print(colored("No issues found.", 'green')) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment