Add analyzers module (base, factory, Python, JavaScript)
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled

This commit is contained in:
2026-02-03 06:57:52 +00:00
parent 80b52fe77d
commit a2903f86b6

View File

@@ -0,0 +1,187 @@
"""Python analyzer for VibeGuard."""
import ast
from pathlib import Path
from typing import Any
from vibeguard.analyzers.base import BaseAnalyzer
class PythonAnalyzer(BaseAnalyzer):
"""Analyzer for Python code using AST."""
LANGUAGE_NAME = "python"
FILE_EXTENSIONS = [".py", ".pyi"]
def parse_file(self, path: Path) -> ast.AST | None:
"""Parse a Python file and return the AST."""
try:
with open(path, "r", encoding="utf-8") as f:
content = f.read()
return ast.parse(content, filename=str(path))
except SyntaxError:
return None
except Exception:
return None
def analyze(self, tree: ast.AST, path: Path) -> list[dict[str, Any]]:
"""Analyze Python AST for anti-patterns."""
issues: list[dict[str, Any]] = []
for node in ast.walk(tree):
if isinstance(node, ast.Assign):
issues.extend(self._check_magic_strings(node, path))
issues.extend(self._check_hardcoded_values(node, path))
if isinstance(node, ast.FunctionDef):
issues.extend(self._check_missing_return_type(node, path))
issues.extend(self._check_missing_docstring(node, path))
if isinstance(node, ast.ExceptHandler):
issues.extend(self._check_bare_except(node, path))
if isinstance(node, ast.Compare):
issues.extend(self._check_type_check_patterns(node, path))
return issues
def _check_magic_strings(
self, node: ast.Assign, path: Path
) -> list[dict[str, Any]]:
"""Check for magic string assignments."""
issues: list[dict[str, Any]] = []
for target in node.targets:
if isinstance(target, ast.Name) and target.id.isupper():
if isinstance(node.value, ast.Constant) and isinstance(
node.value.value, str
):
if len(node.value.value) > 20:
issues.append(
{
"pattern": "MAGIC_STRING",
"severity": "warning",
"file": str(path),
"line": node.lineno,
"message": "Long magic string detected - consider using constants",
"suggestion": "Extract to a named constant at module level",
"language": self.LANGUAGE_NAME,
}
)
return issues
def _check_hardcoded_values(
self, node: ast.Assign, path: Path
) -> list[dict[str, Any]]:
"""Check for hardcoded numeric values."""
issues: list[dict[str, Any]] = []
for target in node.targets:
if isinstance(target, ast.Name):
if isinstance(node.value, ast.Constant):
value = node.value.value
if isinstance(value, (int, float)) and abs(value) > 1000:
issues.append(
{
"pattern": "HARDCODED_VALUE",
"severity": "warning",
"file": str(path),
"line": node.lineno,
"message": f"Hardcoded numeric value {value} detected",
"suggestion": "Extract to a named constant with descriptive name",
"language": self.LANGUAGE_NAME,
}
)
return issues
def _check_missing_return_type(
self, node: ast.FunctionDef, path: Path
) -> list[dict[str, Any]]:
"""Check for missing return type annotations."""
issues: list[dict[str, Any]] = []
if node.returns is None and len(node.body) > 0:
issues.append(
{
"pattern": "MISSING_RETURN_TYPE",
"severity": "info",
"file": str(path),
"line": node.lineno,
"message": f"Function '{node.name}' is missing return type annotation",
"suggestion": "Add return type annotation for better type safety",
"language": self.LANGUAGE_NAME,
}
)
return issues
def _check_missing_docstring(
self, node: ast.FunctionDef, path: Path
) -> list[dict[str, Any]]:
"""Check for missing function docstrings."""
issues: list[dict[str, Any]] = []
has_docstring = (
ast.get_docstring(node, clean=False) is not None
if node.body
else False
)
if not has_docstring and len(node.body) > 1:
issues.append(
{
"pattern": "MISSING_DOCSTRING",
"severity": "info",
"file": str(path),
"line": node.lineno,
"message": f"Function '{node.name}' is missing docstring",
"suggestion": "Add a docstring explaining the function's purpose and parameters",
"language": self.LANGUAGE_NAME,
}
)
return issues
def _check_bare_except(
self, node: ast.ExceptHandler, path: Path
) -> list[dict[str, Any]]:
"""Check for bare except clauses."""
issues: list[dict[str, Any]] = []
if node.type is None:
issues.append(
{
"pattern": "BARE_EXCEPT",
"severity": "error",
"file": str(path),
"line": node.lineno if hasattr(node, "lineno") else 0,
"message": "Bare except clause detected - catches all exceptions",
"suggestion": "Catch specific exceptions or use 'except Exception'",
"language": self.LANGUAGE_NAME,
}
)
return issues
def _check_type_check_patterns(
self, node: ast.Compare, path: Path
) -> list[dict[str, Any]]:
"""Check for type checking anti-patterns."""
issues: list[dict[str, Any]] = []
for comparator in node.comparators:
if isinstance(comparator, ast.Call):
if isinstance(comparator.func, ast.Name):
if comparator.func.id == "type":
issues.append(
{
"pattern": "TYPE_CHECK_PATTERN",
"severity": "warning",
"file": str(path),
"line": node.lineno,
"message": "Using type() for type checking - use isinstance() instead",
"suggestion": "Replace type() == with isinstance() for proper inheritance support",
"language": self.LANGUAGE_NAME,
}
)
return issues