Files
vibeguard/vibeguard/patterns/manager.py
2026-02-03 07:13:55 +00:00

407 lines
16 KiB
Python

"""Pattern manager for VibeGuard."""
from typing import Any
from vibeguard.patterns.definitions import Pattern, Severity
class PatternManager:
"""Manages anti-pattern definitions for VibeGuard."""
def __init__(self) -> None:
self.patterns: dict[str, Pattern] = {}
def load_all_patterns(self) -> None:
"""Load all pattern definitions."""
self._load_python_patterns()
self._load_javascript_patterns()
self._load_typescript_patterns()
self._load_go_patterns()
self._load_rust_patterns()
self._load_general_patterns()
def _load_python_patterns(self) -> None:
"""Load Python-specific patterns."""
python_patterns = [
Pattern(
id="PY001",
name="Magic String",
description="Long string literal without explanation",
severity=Severity.WARNING,
languages=["python"],
message="Magic string detected",
suggestion="Extract to a named constant",
category="maintainability",
),
Pattern(
id="PY002",
name="Hardcoded Value",
description="Hardcoded numeric value that should be a constant",
severity=Severity.WARNING,
languages=["python"],
message="Hardcoded numeric value detected",
suggestion="Extract to a named constant",
category="maintainability",
),
Pattern(
id="PY003",
name="Missing Return Type",
description="Function without return type annotation",
severity=Severity.INFO,
languages=["python"],
message="Function is missing return type annotation",
suggestion="Add return type annotation for type safety",
category="type-safety",
),
Pattern(
id="PY004",
name="Missing Docstring",
description="Function without docstring",
severity=Severity.INFO,
languages=["python"],
message="Function is missing docstring",
suggestion="Add a docstring explaining the function's purpose",
category="documentation",
),
Pattern(
id="PY005",
name="Bare Except",
description="Catching all exceptions without specific type",
severity=Severity.ERROR,
languages=["python"],
message="Bare except clause detected",
suggestion="Catch specific exceptions or use 'except Exception'",
category="error-handling",
),
Pattern(
id="PY006",
name="Type Check Pattern",
description="Using type() instead of isinstance()",
severity=Severity.WARNING,
languages=["python"],
message="Using type() for type checking",
suggestion="Use isinstance() for proper inheritance support",
category="type-safety",
),
]
for pattern in python_patterns:
self.patterns[pattern.id] = pattern
def _load_javascript_patterns(self) -> None:
"""Load JavaScript-specific patterns."""
js_patterns = [
Pattern(
id="JS001",
name="Console Log",
description="Console.log statement left in code",
severity=Severity.WARNING,
languages=["javascript"],
message="Console.log statement detected",
suggestion="Use a logging library or remove debug statements",
category="debugging",
),
Pattern(
id="JS002",
name="Var Keyword",
description="Using var instead of const/let",
severity=Severity.WARNING,
languages=["javascript"],
message="'var' keyword detected",
suggestion="Use 'const' or 'let' for proper block scoping",
category="best-practices",
),
Pattern(
id="JS003",
name="Magic String",
description="Long string literal without explanation",
severity=Severity.WARNING,
languages=["javascript"],
message="Magic string detected",
suggestion="Extract to a named constant",
category="maintainability",
),
Pattern(
id="JS004",
name="Hardcoded Value",
description="Hardcoded numeric value",
severity=Severity.WARNING,
languages=["javascript"],
message="Hardcoded numeric value detected",
suggestion="Extract to a named constant",
category="maintainability",
),
Pattern(
id="JS005",
name="Inconsistent Error Handling",
description="Generic error variable name",
severity=Severity.INFO,
languages=["javascript"],
message="Generic error variable name",
suggestion="Use more descriptive error variable names",
category="readability",
),
Pattern(
id="JS006",
name="Unnecessary Promise Wrapper",
description="Unnecessary Promise constructor wrapper",
severity=Severity.WARNING,
languages=["javascript"],
message="Unnecessary Promise wrapper detected",
suggestion="Return the value directly instead of wrapping",
category="performance",
),
]
for pattern in js_patterns:
self.patterns[pattern.id] = pattern
def _load_typescript_patterns(self) -> None:
"""Load TypeScript-specific patterns."""
ts_patterns = [
Pattern(
id="TS001",
name="Any Type Usage",
description="Using 'any' type defeats TypeScript's purpose",
severity=Severity.WARNING,
languages=["typescript"],
message="'any' type detected",
suggestion="Use specific types or 'unknown' with type narrowing",
category="type-safety",
),
Pattern(
id="TS002",
name="Interface Naming",
description="Interface name should use PascalCase",
severity=Severity.INFO,
languages=["typescript"],
message="Interface name should start with capital letter",
suggestion="Rename interface to use PascalCase",
category="naming-conventions",
),
Pattern(
id="TS003",
name="Enum Usage",
description="Enum vs const object consideration",
severity=Severity.INFO,
languages=["typescript"],
message="Enum detected",
suggestion="Consider using const objects with 'as const' for better tree-shaking",
category="best-practices",
),
]
for pattern in ts_patterns:
self.patterns[pattern.id] = pattern
def _load_go_patterns(self) -> None:
"""Load Go-specific patterns."""
go_patterns = [
Pattern(
id="GO001",
name="Ignored Error",
description="Error being ignored with blank identifier",
severity=Severity.ERROR,
languages=["go"],
message="Error being ignored",
suggestion="Handle the error properly instead of ignoring it",
category="error-handling",
),
Pattern(
id="GO002",
name="Context Background",
description="Using context.Background() in production code",
severity=Severity.WARNING,
languages=["go"],
message="context.Background() should not be passed to functions",
suggestion="Use context.TODO() or request-scoped context",
category="best-practices",
),
Pattern(
id="GO003",
name="Potential Goroutine Leak",
description="Goroutine without proper lifetime management",
severity=Severity.WARNING,
languages=["go"],
message="Goroutine started - ensure it won't leak",
suggestion="Use wait groups or context to control goroutine lifetime",
category="concurrency",
),
Pattern(
id="GO004",
name="Magic String",
description="Long string literal without explanation",
severity=Severity.WARNING,
languages=["go"],
message="Magic string detected",
suggestion="Extract to a named constant",
category="maintainability",
),
Pattern(
id="GO005",
name="Error Wrapping",
description="Error not wrapped with %w verb",
severity=Severity.WARNING,
languages=["go"],
message="Error formatting without %w",
suggestion="Use %w verb to wrap errors for proper error chains",
category="error-handling",
),
Pattern(
id="GO006",
name="Naked Return",
description="Naked return in function with multiple return values",
severity=Severity.WARNING,
languages=["go"],
message="Naked return in function with multiple return values",
suggestion="Use named return values or explicit returns",
category="readability",
),
]
for pattern in go_patterns:
self.patterns[pattern.id] = pattern
def _load_rust_patterns(self) -> None:
"""Load Rust-specific patterns."""
rust_patterns = [
Pattern(
id="RS001",
name="Unnecessary Clone",
description="Calling clone() on a Copy type",
severity=Severity.WARNING,
languages=["rust"],
message="Unnecessary .clone() - type implements Copy trait",
suggestion="Remove .clone() as the type will be automatically copied",
category="performance",
),
Pattern(
id="RS002",
name="Allow Unused",
description="Allowing unused code instead of removing it",
severity=Severity.INFO,
languages=["rust"],
message="#[allow(unused)] detected",
suggestion="Remove unused code instead of suppressing warnings",
category="best-practices",
),
Pattern(
id="RS003",
name="Expect Panic",
description="Using expect() which can panic",
severity=Severity.WARNING,
languages=["rust"],
message=".expect() can panic",
suggestion="Use ? operator or proper error handling instead",
category="error-handling",
),
Pattern(
id="RS004",
name="Magic String",
description="Long string literal without explanation",
severity=Severity.WARNING,
languages=["rust"],
message="Magic string detected",
suggestion="Extract to a named constant",
category="maintainability",
),
Pattern(
id="RS005",
name="Unnecessary Boxing",
description="Unnecessary Box::new() usage",
severity=Severity.INFO,
languages=["rust"],
message="Unnecessary Box::new()",
suggestion="Only use Box for recursive types, trait objects, or large stacks",
category="performance",
),
Pattern(
id="RS006",
name="Into Iterator Consumption",
description="into_iter() consumes the collection",
severity=Severity.INFO,
languages=["rust"],
message="into_iter() consumes the collection",
suggestion="Use .iter() for borrowing, .iter_mut() for mutable borrowing",
category="best-practices",
),
]
for pattern in rust_patterns:
self.patterns[pattern.id] = pattern
def _load_general_patterns(self) -> None:
"""Load general patterns applicable to all languages."""
general_patterns = [
Pattern(
id="GEN001",
name="Empty Catch Block",
description="Empty catch/except block that ignores errors",
severity=Severity.ERROR,
languages=["python", "javascript", "typescript", "go"],
message="Empty catch block - errors are being silently ignored",
suggestion="Handle the error or log it",
category="error-handling",
),
Pattern(
id="GEN002",
name="Debug Code",
description="Debug code left in production",
severity=Severity.WARNING,
languages=["python", "javascript", "typescript", "go", "rust"],
message="Debug code detected",
suggestion="Remove or disable debug code in production",
category="debugging",
),
Pattern(
id="GEN003",
name="Inconsistent Naming",
description="Inconsistent naming convention in code",
severity=Severity.INFO,
languages=["python", "javascript", "typescript", "go", "rust"],
message="Inconsistent naming convention detected",
suggestion="Use consistent naming (camelCase, snake_case, PascalCase)",
category="naming-conventions",
),
Pattern(
id="GEN004",
name="Deep Nesting",
description="Code with excessive nesting depth",
severity=Severity.WARNING,
languages=["python", "javascript", "typescript", "go", "rust"],
message="Excessive nesting depth detected",
suggestion="Consider refactoring with early returns or extracted functions",
category="readability",
),
]
for pattern in general_patterns:
self.patterns[pattern.id] = pattern
def get_pattern(self, pattern_id: str) -> Pattern | None:
"""Get a pattern by its ID."""
return self.patterns.get(pattern_id)
def get_patterns_by_language(self, language: str) -> list[Pattern]:
"""Get all patterns for a specific language."""
return [
p for p in self.patterns.values() if language in p.languages or "all" in p.languages
]
def get_patterns_by_severity(self, severity: Severity) -> list[Pattern]:
"""Get all patterns with a specific severity."""
return [p for p in self.patterns.values() if p.severity == severity]
def to_dict(self) -> dict[str, Any]:
"""Convert all patterns to a dictionary."""
return {pid: {
"id": pid,
"name": p.name,
"description": p.description,
"severity": p.severity.value,
"languages": p.languages,
"message": p.message,
"suggestion": p.suggestion,
"category": p.category,
} for pid, p in self.patterns.items()}