Skip to main content
Glama

CodeAnalysis MCP Server

by 0xjcf
style_checker.py20.3 kB
#!/usr/bin/env python3 """Style Guide Checker This script checks code files against the project's style guide and reports violations. Maturity: beta Why: - Automated style checking ensures consistent code quality - Helps developers identify style issues before code review - Provides a quick way to check compliance with project standards - Can be integrated into CI/CD pipelines for enforcement """ import argparse import json import os import re import subprocess import sys from pathlib import Path from concurrent.futures import ThreadPoolExecutor class StyleChecker: """Checks code files against style guidelines.""" def __init__(self, verbose=False): self.verbose = verbose self.violations = [] def check_file(self, file_path): """Check a single file for style violations.""" file_path = Path(file_path) if not file_path.exists(): print(f"Error: File {file_path} does not exist") return [] file_violations = [] # Determine file type and apply appropriate checks if file_path.suffix in ['.js', '.jsx', '.ts', '.tsx']: file_violations.extend(self._check_js_ts_file(file_path)) elif file_path.suffix == '.py': file_violations.extend(self._check_python_file(file_path)) elif file_path.suffix in ['.pine', '.pinescript']: file_violations.extend(self._check_pine_script_file(file_path)) else: if self.verbose: print(f"Skipping unsupported file type: {file_path}") return [] return file_violations def _check_js_ts_file(self, file_path): """Check JavaScript/TypeScript file for style violations.""" violations = [] # Check if ESLint is available try: result = subprocess.run( ['npx', 'eslint', '--format', 'json', str(file_path)], capture_output=True, text=True, check=False ) # Parse ESLint output if result.stdout: try: eslint_results = json.loads(result.stdout) for file_result in eslint_results: for message in file_result.get('messages', []): violations.append({ 'file': str(file_path), 'line': message.get('line', 0), 'column': message.get('column', 0), 'rule': message.get('ruleId', 'unknown'), 'message': message.get('message', ''), 'severity': 'error' if message.get('severity') == 2 else 'warning' }) except json.JSONDecodeError: print(f"Error parsing ESLint output for {file_path}") except FileNotFoundError: # Fall back to basic checks if ESLint is not available violations.extend(self._basic_js_ts_checks(file_path)) return violations def _basic_js_ts_checks(self, file_path): """Perform basic style checks for JS/TS files without ESLint.""" violations = [] try: with open(file_path, 'r', encoding='utf-8') as f: lines = f.readlines() for i, line in enumerate(lines, 1): # Check line length if len(line.rstrip('\n')) > 100: violations.append({ 'file': str(file_path), 'line': i, 'column': 101, 'rule': 'max-len', 'message': 'Line exceeds maximum length of 100', 'severity': 'warning' }) # Check for trailing whitespace if line.rstrip('\n') != line.rstrip(): violations.append({ 'file': str(file_path), 'line': i, 'column': len(line.rstrip('\n')), 'rule': 'no-trailing-whitespace', 'message': 'Line contains trailing whitespace', 'severity': 'warning' }) # Check for console.log statements if 'console.log(' in line: violations.append({ 'file': str(file_path), 'line': i, 'column': line.find('console.log('), 'rule': 'no-console', 'message': 'Unexpected console statement', 'severity': 'warning' }) except Exception as e: print(f"Error checking {file_path}: {e}") return violations def _check_python_file(self, file_path): """Check Python file for style violations.""" violations = [] # Check if pylint is available try: result = subprocess.run( ['pylint', '--output-format=json', str(file_path)], capture_output=True, text=True, check=False ) # Parse pylint output if result.stdout: try: pylint_results = json.loads(result.stdout) for message in pylint_results: violations.append({ 'file': str(file_path), 'line': message.get('line', 0), 'column': message.get('column', 0), 'rule': message.get('symbol', 'unknown'), 'message': message.get('message', ''), 'severity': 'error' if message.get('type') in ['error', 'fatal'] else 'warning' }) except json.JSONDecodeError: print(f"Error parsing pylint output for {file_path}") except FileNotFoundError: # Fall back to basic checks if pylint is not available violations.extend(self._basic_python_checks(file_path)) return violations def _basic_python_checks(self, file_path): """Perform basic style checks for Python files without pylint.""" violations = [] try: with open(file_path, 'r', encoding='utf-8') as f: lines = f.readlines() for i, line in enumerate(lines, 1): # Check line length if len(line.rstrip('\n')) > 100: violations.append({ 'file': str(file_path), 'line': i, 'column': 101, 'rule': 'line-too-long', 'message': 'Line too long (>100 characters)', 'severity': 'warning' }) # Check for trailing whitespace if line.rstrip('\n') != line.rstrip(): violations.append({ 'file': str(file_path), 'line': i, 'column': len(line.rstrip('\n')), 'rule': 'trailing-whitespace', 'message': 'Line contains trailing whitespace', 'severity': 'warning' }) # Check for print statements if re.search(r'^\s*print\(', line): violations.append({ 'file': str(file_path), 'line': i, 'column': line.find('print('), 'rule': 'print-statement', 'message': 'Use logging instead of print statements', 'severity': 'warning' }) except Exception as e: print(f"Error checking {file_path}: {e}") return violations def _check_pine_script_file(self, file_path): """Check Pine Script file for style violations.""" violations = [] try: with open(file_path, 'r', encoding='utf-8') as f: lines = f.readlines() for i, line in enumerate(lines, 1): # Check line length if len(line.rstrip('\n')) > 100: violations.append({ 'file': str(file_path), 'line': i, 'column': 101, 'rule': 'line-too-long', 'message': 'Line too long (>100 characters)', 'severity': 'warning' }) # Check for trailing whitespace if line.rstrip('\n') != line.rstrip(): violations.append({ 'file': str(file_path), 'line': i, 'column': len(line.rstrip('\n')), 'rule': 'trailing-whitespace', 'message': 'Line contains trailing whitespace', 'severity': 'warning' }) # Check for missing var keyword for variables that don't need recalculation if re.search(r'^\s*(float|int|bool|string|color)\s+\w+\s*=\s*[^=]', line): if not re.search(r'^\s*var\s+(float|int|bool|string|color)', line): violations.append({ 'file': str(file_path), 'line': i, 'column': 0, 'rule': 'missing-var-keyword', 'message': 'Consider using "var" for variables that don\'t need recalculation on every bar', 'severity': 'warning' }) except Exception as e: print(f"Error checking {file_path}: {e}") return violations def check_directory(self, directory_path, exclude_patterns=None): """Check all files in a directory for style violations.""" if exclude_patterns is None: exclude_patterns = ['node_modules', 'dist', 'build', '.git'] directory_path = Path(directory_path) if not directory_path.is_dir(): print(f"Error: {directory_path} is not a directory") return all_files = [] # Collect all files to check for root, dirs, files in os.walk(directory_path): # Skip excluded directories dirs[:] = [d for d in dirs if not any(pattern in str(Path(root) / d) for pattern in exclude_patterns)] for file in files: file_path = Path(root) / file if file_path.suffix in ['.js', '.jsx', '.ts', '.tsx', '.py', '.pine', '.pinescript']: all_files.append(file_path) if self.verbose: print(f"Found {len(all_files)} files to check") # Check files in parallel with ThreadPoolExecutor() as executor: results = list(executor.map(self.check_file, all_files)) # Flatten results for file_violations in results: self.violations.extend(file_violations) def report_violations(self, output_format='text', output_file=None): """Report style violations in the specified format.""" if not self.violations: print("No style violations found!") return if output_format == 'text': self._report_text(output_file) elif output_format == 'json': self._report_json(output_file) elif output_format == 'html': self._report_html(output_file) else: print(f"Unsupported output format: {output_format}") def _report_text(self, output_file=None): """Report violations in text format.""" # Sort violations by file and line number sorted_violations = sorted( self.violations, key=lambda v: (v['file'], v['line'], v['column']) ) # Group violations by file violations_by_file = {} for violation in sorted_violations: file_path = violation['file'] if file_path not in violations_by_file: violations_by_file[file_path] = [] violations_by_file[file_path].append(violation) # Generate report report = [] report.append(f"Found {len(self.violations)} style violations in {len(violations_by_file)} files\n") for file_path, file_violations in violations_by_file.items(): report.append(f"File: {file_path}") report.append("-" * 80) for violation in file_violations: severity = violation['severity'].upper() rule = violation['rule'] line = violation['line'] column = violation['column'] message = violation['message'] report.append(f"{severity} {rule} at line {line}, column {column}: {message}") report.append("") report_text = "\n".join(report) if output_file: with open(output_file, 'w') as f: f.write(report_text) print(f"Report written to {output_file}") else: print(report_text) def _report_json(self, output_file=None): """Report violations in JSON format.""" report = { 'summary': { 'total_violations': len(self.violations), 'files_with_violations': len(set(v['file'] for v in self.violations)), 'errors': len([v for v in self.violations if v['severity'] == 'error']), 'warnings': len([v for v in self.violations if v['severity'] == 'warning']) }, 'violations': self.violations } report_json = json.dumps(report, indent=2) if output_file: with open(output_file, 'w') as f: f.write(report_json) print(f"Report written to {output_file}") else: print(report_json) def _report_html(self, output_file=None): """Report violations in HTML format.""" if not output_file: output_file = 'style_report.html' # Sort violations by file and line number sorted_violations = sorted( self.violations, key=lambda v: (v['file'], v['line'], v['column']) ) # Group violations by file violations_by_file = {} for violation in sorted_violations: file_path = violation['file'] if file_path not in violations_by_file: violations_by_file[file_path] = [] violations_by_file[file_path].append(violation) # Generate HTML report html = [] html.append('<!DOCTYPE html>') html.append('<html lang="en">') html.append('<head>') html.append(' <meta charset="UTF-8">') html.append(' <meta name="viewport" content="width=device-width, initial-scale=1.0">') html.append(' <title>Style Guide Checker Report</title>') html.append(' <style>') html.append(' body { font-family: Arial, sans-serif; margin: 20px; }') html.append(' h1 { color: #333; }') html.append(' .summary { margin-bottom: 20px; }') html.append(' .file { margin-bottom: 30px; }') html.append(' .file-header { background-color: #f0f0f0; padding: 10px; border-radius: 5px; }') html.append(' .violations { margin-left: 20px; }') html.append(' .violation { margin: 10px 0; padding: 5px; border-left: 3px solid #ccc; }') html.append(' .error { border-left-color: #d9534f; }') html.append(' .warning { border-left-color: #f0ad4e; }') html.append(' .severity { font-weight: bold; }') html.append(' .error .severity { color: #d9534f; }') html.append(' .warning .severity { color: #f0ad4e; }') html.append(' </style>') html.append('</head>') html.append('<body>') html.append(' <h1>Style Guide Checker Report</h1>') # Summary html.append(' <div class="summary">') html.append(f' <p>Found <strong>{len(self.violations)}</strong> style violations in <strong>{len(violations_by_file)}</strong> files.</p>') html.append(f' <p>Errors: <strong>{len([v for v in self.violations if v["severity"] == "error"])}</strong>, Warnings: <strong>{len([v for v in self.violations if v["severity"] == "warning"])}</strong></p>') html.append(' </div>') # Violations by file for file_path, file_violations in violations_by_file.items(): html.append(' <div class="file">') html.append(f' <div class="file-header"><strong>File:</strong> {file_path}</div>') html.append(' <div class="violations">') for violation in file_violations: severity = violation['severity'] rule = violation['rule'] line = violation['line'] column = violation['column'] message = violation['message'] html.append(f' <div class="violation {severity}">') html.append(f' <span class="severity">{severity.upper()}</span> <strong>{rule}</strong> at line {line}, column {column}') html.append(f' <div>{message}</div>') html.append(' </div>') html.append(' </div>') html.append(' </div>') html.append('</body>') html.append('</html>') with open(output_file, 'w') as f: f.write('\n'.join(html)) print(f"HTML report written to {output_file}") def main(): parser = argparse.ArgumentParser(description="Check code files against style guidelines") parser.add_argument("path", help="File or directory to check") parser.add_argument("--format", choices=["text", "json", "html"], default="text", help="Output format (default: text)") parser.add_argument("--output", help="Output file (default: stdout for text/json, style_report.html for html)") parser.add_argument("--exclude", nargs="+", default=["node_modules", "dist", "build", ".git"], help="Patterns to exclude (default: node_modules dist build .git)") parser.add_argument("--verbose", action="store_true", help="Enable verbose output") args = parser.parse_args() checker = StyleChecker(verbose=args.verbose) path = Path(args.path) if path.is_file(): checker.check_file(path) elif path.is_dir(): checker.check_directory(path, exclude_patterns=args.exclude) else: print(f"Error: {path} is not a valid file or directory") sys.exit(1) checker.report_violations(output_format=args.format, output_file=args.output) if __name__ == "__main__": main()

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/0xjcf/MCP_CodeAnalysis'

If you have feedback or need assistance with the MCP directory API, please join our Discord server