diff --git a/vibeguard/analyzers/languages/typescript.py b/vibeguard/analyzers/languages/typescript.py new file mode 100644 index 0000000..6507b18 --- /dev/null +++ b/vibeguard/analyzers/languages/typescript.py @@ -0,0 +1,105 @@ +"""TypeScript analyzer for VibeGuard.""" + +import re +from pathlib import Path +from typing import Any + +from vibeguard.analyzers.languages.javascript import JavaScriptAnalyzer + + +class TypeScriptAnalyzer(JavaScriptAnalyzer): + """Analyzer for TypeScript code.""" + + LANGUAGE_NAME = "typescript" + FILE_EXTENSIONS = [".ts", ".tsx"] + + def analyze(self, content: str, path: Path) -> list[dict[str, Any]]: + """Analyze TypeScript content for anti-patterns.""" + issues: list[dict[str, Any]] = [] + + issues.extend(super().analyze(content, path)) + + lines = content.split("\n") + + for i, line in enumerate(lines, 1): + issues.extend(self._check_any_type(line, i, path)) + issues.extend(self._check_missing_type_annotation(line, i, path)) + issues.extend(self._check_interfaceNaming(line, i, path)) + issues.extend(self._check_enum_usage(line, i, path)) + + return issues + + def _check_any_type( + self, line: str, line_num: int, path: Path + ) -> list[dict[str, Any]]: + """Check for 'any' type usage.""" + issues: list[dict[str, Any]] = [] + + if re.search(r":\s*any\b", line) and "any" not in line.lower(): + issues.append( + { + "pattern": "ANY_TYPE_USAGE", + "severity": "warning", + "file": str(path), + "line": line_num, + "message": "'any' type detected - defeats TypeScript's type safety", + "suggestion": "Use specific types or 'unknown' with proper type narrowing", + "language": self.LANGUAGE_NAME, + } + ) + + return issues + + def _check_missing_type_annotation( + self, line: str, line_num: int, path: Path + ) -> list[dict[str, Any]]: + """Check for missing type annotations in typed contexts.""" + issues: list[dict[str, Any]] = [] + + if re.search(r"const\s+\w+\s*=", line) and "function" not in line: + if not re.search(r":\s*\w+", line) and "=" in line: + pass + + return issues + + def _check_interfaceNaming( + self, line: str, line_num: int, path: Path + ) -> list[dict[str, Any]]: + """Check for interface naming conventions.""" + issues: list[dict[str, Any]] = [] + + if re.match(r"^\s*interface\s+[a-z]", line): + issues.append( + { + "pattern": "INTERFACE_NAMING", + "severity": "info", + "file": str(path), + "line": line_num, + "message": "Interface name should start with capital letter", + "suggestion": "Rename interface to use PascalCase", + "language": self.LANGUAGE_NAME, + } + ) + + return issues + + def _check_enum_usage( + self, line: str, line_num: int, path: Path + ) -> list[dict[str, Any]]: + """Check for enum usage vs const objects.""" + issues: list[dict[str, Any]] = [] + + if re.match(r"^\s*enum\s+\w+", line): + issues.append( + { + "pattern": "ENUM_USAGE", + "severity": "info", + "file": str(path), + "line": line_num, + "message": "Enum detected - consider using const objects for better tree-shaking", + "suggestion": "Consider using 'const' objects with 'as const' assertion", + "language": self.LANGUAGE_NAME, + } + ) + + return issues