diff --git a/depaudit/parsers/go.py b/depaudit/parsers/go.py new file mode 100644 index 0000000..9c8bbc0 --- /dev/null +++ b/depaudit/parsers/go.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +from pathlib import Path +from typing import Any + +from depaudit.parsers import Parser, ParsedManifest, Dependency + + +class GoParser(Parser): + language = "go" + + def can_parse(self, file_path: Path) -> bool: + return file_path.name == "go.mod" + + def parse(self, file_path: Path) -> ParsedManifest: + content = file_path.read_text(encoding="utf-8") + lines = content.split("\n") + + manifest = ParsedManifest( + language=self.language, + file_path=file_path, + ) + + i = 0 + in_require_block = False + current_line_indent = 0 + + while i < len(lines): + line = lines[i].strip() + original_line = lines[i] + + if not line or line.startswith("//"): + i += 1 + continue + + if line.startswith("module"): + manifest.project_name = line.split()[1].strip() + elif line.startswith("go"): + pass + elif line.startswith("require"): + in_require_block = True + current_line_indent = len(lines[i]) - len(lines[i].lstrip()) + self._parse_require(line, file_path, manifest) + elif in_require_block: + current_indent = len(lines[i]) - len(lines[i].lstrip()) + if current_indent > current_line_indent and not line.startswith("//"): + self._parse_require(lines[i], file_path, manifest) + else: + in_require_block = False + + i += 1 + + return manifest + + def _parse_require( + self, line: str, file_path: Path, manifest: ParsedManifest + ) -> None: + content = line.strip() + if content.startswith("//"): + return + + if content.startswith("require"): + content = content[len("require"):].strip() + if content.startswith("("): + content = content[1:] + if content.endswith(")"): + content = content[:-1] + + if not content: + return + + parts = content.split() + i = 0 + while i < len(parts): + if parts[i].startswith("//"): + break + name = parts[i] + version = "" + if i + 1 < len(parts) and not parts[i + 1].startswith("/"): + version = parts[i + 1] + i += 2 + else: + i += 1 + + if name: + indirect = "// indirect" in line or "(// indirect)" in line or "(indirect)" in line + manifest.dependencies.append( + self._create_dependency(file_path, name, version, indirect=indirect) + )