407 lines
16 KiB
Python
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()}
|