Files
vibeguard/vibeguard/analyzers/languages/go.py
7000pctAUTO 034bb54949
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled
Add TypeScript, Go, and Rust analyzers
2026-02-03 06:59:00 +00:00

203 lines
7.2 KiB
Python

"""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