Add analyzers module (base, factory, Python, JavaScript)
This commit is contained in:
206
vibeguard/analyzers/languages/javascript.py
Normal file
206
vibeguard/analyzers/languages/javascript.py
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
"""JavaScript analyzer for VibeGuard."""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from vibeguard.analyzers.base import BaseAnalyzer
|
||||||
|
|
||||||
|
|
||||||
|
class JavaScriptAnalyzer(BaseAnalyzer):
|
||||||
|
"""Analyzer for JavaScript code."""
|
||||||
|
|
||||||
|
LANGUAGE_NAME = "javascript"
|
||||||
|
FILE_EXTENSIONS = [".js", ".jsx"]
|
||||||
|
|
||||||
|
MAGIC_STRING_PATTERN = re.compile(r'["\']{3,}[^"\']{20,}["\']{3,}')
|
||||||
|
HARD_CODED_NUMBER = re.compile(r"[^0-9a-zA-Z](?:1[0-9]{3}|[2-9][0-9]{3,})(?![0-9])")
|
||||||
|
|
||||||
|
def parse_file(self, path: Path) -> str | None:
|
||||||
|
"""Parse a JavaScript 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 JavaScript content for anti-patterns."""
|
||||||
|
issues: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
lines = content.split("\n")
|
||||||
|
|
||||||
|
for i, line in enumerate(lines, 1):
|
||||||
|
issues.extend(self._check_console_log(line, i, path))
|
||||||
|
issues.extend(self._check_var_usage(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_inconsistent_error_handling(line, i, path))
|
||||||
|
issues.extend(self._check_async_await_without_await(line, i, path))
|
||||||
|
|
||||||
|
issues.extend(self._check_missing_await(content, path))
|
||||||
|
issues.extend(self._check_promise_wrapper(path, content))
|
||||||
|
|
||||||
|
return issues
|
||||||
|
|
||||||
|
def _check_console_log(
|
||||||
|
self, line: str, line_num: int, path: Path
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
"""Check for console.log statements."""
|
||||||
|
issues: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
if "console.log" in line and "console" not in line.lower():
|
||||||
|
issues.append(
|
||||||
|
{
|
||||||
|
"pattern": "CONSOLE_LOG",
|
||||||
|
"severity": "warning",
|
||||||
|
"file": str(path),
|
||||||
|
"line": line_num,
|
||||||
|
"message": "Console.log statement detected - remove or use proper logging",
|
||||||
|
"suggestion": "Use a logging library instead of console.log",
|
||||||
|
"language": self.LANGUAGE_NAME,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return issues
|
||||||
|
|
||||||
|
def _check_var_usage(
|
||||||
|
self, line: str, line_num: int, path: Path
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
"""Check for var keyword usage."""
|
||||||
|
issues: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
if re.search(r"\bvar\s+", line):
|
||||||
|
issues.append(
|
||||||
|
{
|
||||||
|
"pattern": "VAR_KEYWORD",
|
||||||
|
"severity": "warning",
|
||||||
|
"file": str(path),
|
||||||
|
"line": line_num,
|
||||||
|
"message": "'var' keyword detected - use 'const' or 'let' instead",
|
||||||
|
"suggestion": "Replace 'var' with 'const' (preferred) or 'let' for block scoping",
|
||||||
|
"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 self.MAGIC_STRING_PATTERN.search(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 at module level",
|
||||||
|
"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 numeric values."""
|
||||||
|
issues: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
if self.HARD_CODED_NUMBER.search(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_inconsistent_error_handling(
|
||||||
|
self, line: str, line_num: int, path: Path
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
"""Check for inconsistent error handling patterns."""
|
||||||
|
issues: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
if "catch (e)" in line or "catch(err)" in line:
|
||||||
|
issues.append(
|
||||||
|
{
|
||||||
|
"pattern": "INCONSISTENT_ERROR_HANDLING",
|
||||||
|
"severity": "warning",
|
||||||
|
"file": str(path),
|
||||||
|
"line": line_num,
|
||||||
|
"message": "Generic error variable name - use more descriptive names",
|
||||||
|
"suggestion": "Use specific error names like 'error', 'err', or 'exception'",
|
||||||
|
"language": self.LANGUAGE_NAME,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return issues
|
||||||
|
|
||||||
|
def _check_async_await_without_await(
|
||||||
|
self, line: str, line_num: int, path: Path
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
"""Check for async function without await usage."""
|
||||||
|
issues: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
if re.search(r"async\s+function.*await", line):
|
||||||
|
pass
|
||||||
|
elif re.search(r"const\s+\w+\s*=\s*async", line):
|
||||||
|
pass
|
||||||
|
elif "await" in line and line.strip().startswith("await"):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return issues
|
||||||
|
|
||||||
|
def _check_missing_await(self, content: str, path: Path) -> list[dict[str, Any]]:
|
||||||
|
"""Check for potential missing await keywords."""
|
||||||
|
issues: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
async_pattern = re.compile(r"async\s+\([^)]*\)\s*=>(?!\s*await)")
|
||||||
|
matches = async_pattern.findall(content)
|
||||||
|
for match in matches:
|
||||||
|
issues.append(
|
||||||
|
{
|
||||||
|
"pattern": "POTENTIAL_MISSING_AWAIT",
|
||||||
|
"severity": "info",
|
||||||
|
"file": str(path),
|
||||||
|
"line": 0,
|
||||||
|
"message": "Arrow function with async but no await in body",
|
||||||
|
"suggestion": "Check if async keyword is needed or if await is missing",
|
||||||
|
"language": self.LANGUAGE_NAME,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return issues
|
||||||
|
|
||||||
|
def _check_promise_wrapper(self, path: Path, content: str) -> list[dict[str, Any]]:
|
||||||
|
"""Check for unnecessary Promise wrappers."""
|
||||||
|
issues: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
pattern = re.compile(r"new\s+Promise\s*\(\s*\(\s*resolve\s*\)\s*=>\s*{[^}]*resolve\([^)]+\)")
|
||||||
|
matches = pattern.findall(content)
|
||||||
|
for match in matches:
|
||||||
|
issues.append(
|
||||||
|
{
|
||||||
|
"pattern": "UNNECESSARY_PROMISE_WRAPPER",
|
||||||
|
"severity": "warning",
|
||||||
|
"file": str(path),
|
||||||
|
"line": 0,
|
||||||
|
"message": "Unnecessary Promise wrapper detected",
|
||||||
|
"suggestion": "Return the value directly instead of wrapping in Promise",
|
||||||
|
"language": self.LANGUAGE_NAME,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return issues
|
||||||
Reference in New Issue
Block a user