Add TypeScript, Go, and Rust analyzers
This commit is contained in:
202
vibeguard/analyzers/languages/go.py
Normal file
202
vibeguard/analyzers/languages/go.py
Normal file
@@ -0,0 +1,202 @@
|
||||
"""Go analyzer for VibeGuard."""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from vibeguard.analyzers.base import BaseAnalyzer
|
||||
|
||||
|
||||
class GoAnalyzer(BaseAnalyzer):
|
||||
"""Analyzer for Go code."""
|
||||
|
||||
LANGUAGE_NAME = "go"
|
||||
FILE_EXTENSIONS = [".go"]
|
||||
|
||||
def parse_file(self, path: Path) -> str | None:
|
||||
"""Parse a Go file and return the content."""
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def analyze(self, content: str, path: Path) -> list[dict[str, Any]]:
|
||||
"""Analyze Go content for anti-patterns."""
|
||||
issues: list[dict[str, Any]] = []
|
||||
|
||||
lines = content.split("\n")
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
issues.extend(self._check_error_handling(line, i, path))
|
||||
issues.extend(self._check_context_usage(line, i, path))
|
||||
issues.extend(self._check_goroutine_leak(line, i, path))
|
||||
issues.extend(self._check_magic_strings(line, i, path))
|
||||
issues.extend(self._check_hardcoded_values(line, i, path))
|
||||
|
||||
issues.extend(self._check_error_wrapping(content, path))
|
||||
issues.extend(self._check_naked_return(path, content))
|
||||
|
||||
return issues
|
||||
|
||||
def _check_error_handling(
|
||||
self, line: str, line_num: int, path: Path
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Check for improper error handling."""
|
||||
issues: list[dict[str, Any]] = []
|
||||
|
||||
if re.search(r"if\s+err\s*!=\s*nil\s*{[^}]*}\s*$", line):
|
||||
pass
|
||||
|
||||
if re.search(r"_\s*:=\s*", line) and "err" not in line:
|
||||
issues.append(
|
||||
{
|
||||
"pattern": "IGNORED_ERROR",
|
||||
"severity": "error",
|
||||
"file": str(path),
|
||||
"line": line_num,
|
||||
"message": "Error being ignored (assigned to blank identifier)",
|
||||
"suggestion": "Handle the error properly instead of ignoring it",
|
||||
"language": self.LANGUAGE_NAME,
|
||||
}
|
||||
)
|
||||
|
||||
return issues
|
||||
|
||||
def _check_context_usage(
|
||||
self, line: str, line_num: int, path: Path
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Check for context.Background() misuse."""
|
||||
issues: list[dict[str, Any]] = []
|
||||
|
||||
if re.search(r"context\.Background\(\)", line):
|
||||
issues.append(
|
||||
{
|
||||
"pattern": "CONTEXT_BACKGROUND",
|
||||
"severity": "warning",
|
||||
"file": str(path),
|
||||
"line": line_num,
|
||||
"message": "context.Background() should not be passed to functions",
|
||||
"suggestion": "Use context.TODO() or pass request-scoped context",
|
||||
"language": self.LANGUAGE_NAME,
|
||||
}
|
||||
)
|
||||
|
||||
return issues
|
||||
|
||||
def _check_goroutine_leak(
|
||||
self, line: str, line_num: int, path: Path
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Check for potential goroutine leaks."""
|
||||
issues: list[dict[str, Any]] = []
|
||||
|
||||
if re.search(r"go\s+\w+\(", line):
|
||||
issues.append(
|
||||
{
|
||||
"pattern": "POTENTIAL_GOROUTINE_LEAK",
|
||||
"severity": "warning",
|
||||
"file": str(path),
|
||||
"line": line_num,
|
||||
"message": "Goroutine started - ensure it won't leak",
|
||||
"suggestion": "Use wait groups or context to control goroutine lifetime",
|
||||
"language": self.LANGUAGE_NAME,
|
||||
}
|
||||
)
|
||||
|
||||
return issues
|
||||
|
||||
def _check_magic_strings(
|
||||
self, line: str, line_num: int, path: Path
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Check for magic strings."""
|
||||
issues: list[dict[str, Any]] = []
|
||||
|
||||
if re.search(r'"[^"]{30,}"', line):
|
||||
issues.append(
|
||||
{
|
||||
"pattern": "MAGIC_STRING",
|
||||
"severity": "warning",
|
||||
"file": str(path),
|
||||
"line": line_num,
|
||||
"message": "Long magic string detected - consider using constants",
|
||||
"suggestion": "Extract to a named constant",
|
||||
"language": self.LANGUAGE_NAME,
|
||||
}
|
||||
)
|
||||
|
||||
return issues
|
||||
|
||||
def _check_hardcoded_values(
|
||||
self, line: str, line_num: int, path: Path
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Check for hardcoded values."""
|
||||
issues: list[dict[str, Any]] = []
|
||||
|
||||
if re.search(r"[^0-9a-zA-Z](?:1[0-9]{3}|[2-9][0-9]{3,})(?![0-9])", line):
|
||||
issues.append(
|
||||
{
|
||||
"pattern": "HARDCODED_VALUE",
|
||||
"severity": "warning",
|
||||
"file": str(path),
|
||||
"line": line_num,
|
||||
"message": "Large hardcoded numeric value detected",
|
||||
"suggestion": "Extract to a named constant with descriptive name",
|
||||
"language": self.LANGUAGE_NAME,
|
||||
}
|
||||
)
|
||||
|
||||
return issues
|
||||
|
||||
def _check_error_wrapping(self, content: str, path: Path) -> list[dict[str, Any]]:
|
||||
"""Check for proper error wrapping with %w."""
|
||||
issues: list[dict[str, Any]] = []
|
||||
|
||||
pattern = re.compile(r"fmt\.Errorf\([^)]*%[svx]", re.MULTILINE)
|
||||
matches = pattern.findall(content)
|
||||
for match in matches:
|
||||
if "%w" not in match:
|
||||
issues.append(
|
||||
{
|
||||
"pattern": "ERROR_WRAPPING",
|
||||
"severity": "warning",
|
||||
"file": str(path),
|
||||
"line": 0,
|
||||
"message": "Error formatting without %w - error won't be wrapped",
|
||||
"suggestion": "Use %w verb to wrap errors for proper error chains",
|
||||
"language": self.LANGUAGE_NAME,
|
||||
}
|
||||
)
|
||||
|
||||
return issues
|
||||
|
||||
def _check_naked_return(self, path: Path, content: str) -> list[dict[str, Any]]:
|
||||
"""Check for naked returns in functions with multiple returns."""
|
||||
issues: list[dict[str, Any]] = []
|
||||
|
||||
lines = content.split("\n")
|
||||
in_func = False
|
||||
has_multiple_returns = False
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if re.match(r"func\s+\([^)]*\)\s*\([^)]*\)\s*{", line):
|
||||
in_func = True
|
||||
elif in_func and re.match(r"\s*return\s*$", line):
|
||||
if has_multiple_returns:
|
||||
issues.append(
|
||||
{
|
||||
"pattern": "NAKED_RETURN",
|
||||
"severity": "warning",
|
||||
"file": str(path),
|
||||
"line": i + 1,
|
||||
"message": "Naked return in function with multiple return values",
|
||||
"suggestion": "Use named return values or explicit returns",
|
||||
"language": self.LANGUAGE_NAME,
|
||||
}
|
||||
)
|
||||
elif in_func and re.search(r"\breturn\s+\w+", line):
|
||||
has_multiple_returns = True
|
||||
elif in_func and line.strip() == "}":
|
||||
in_func = False
|
||||
has_multiple_returns = False
|
||||
|
||||
return issues
|
||||
Reference in New Issue
Block a user