From f6bd9a04b3399b412793c8bb4c39ac466d191582 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Sat, 31 Jan 2026 17:11:28 +0000 Subject: [PATCH] Add JS, Go, and Rust detectors --- src/docgen/detectors/javascript.py | 124 +++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/docgen/detectors/javascript.py diff --git a/src/docgen/detectors/javascript.py b/src/docgen/detectors/javascript.py new file mode 100644 index 0000000..2caf895 --- /dev/null +++ b/src/docgen/detectors/javascript.py @@ -0,0 +1,124 @@ +"""JavaScript endpoint detector for Express and Fastify.""" + +import re +from pathlib import Path +from typing import Optional +from docgen.models import Endpoint, HTTPMethod +from docgen.detectors.base import BaseDetector + + +class JavaScriptDetector(BaseDetector): + """Detector for JavaScript web frameworks.""" + + extensions = [".js", ".ts", ".jsx", ".tsx"] + framework_name = "javascript" + + EXPRESS_PATTERN = re.compile( + r'(app|router)\.(get|post|put|patch|delete|options|head)\s*\(\s*["\']([^"\']+)["\']', + re.MULTILINE, + ) + + EXPRESS_APP_ROUTE_PATTERN = re.compile( + r'(app|router)\.(route)\s*\(\s*["\']([^"\']+)["\'][\s\S]*?\.((?:get|post|put|patch|delete|options|head))\s*\(', + re.MULTILINE, + ) + + FASTIFY_PATTERN = re.compile( + r'(?:fastify|instance)\s*\.\s*(get|post|put|patch|delete|options|head)\s*\(\s*["\']([^"\']+)["\']', + re.MULTILINE, + ) + + FASTIFY_DECORATOR_PATTERN = re.compile( + r'@?(?:get|post|put|patch|delete|options|head)\s*\(\s*["\']([^"\']+)["\']', + re.MULTILINE, + ) + + METHOD_MAP = { + "get": HTTPMethod.GET, + "post": HTTPMethod.POST, + "put": HTTPMethod.PUT, + "patch": HTTPMethod.PATCH, + "delete": HTTPMethod.DELETE, + "options": HTTPMethod.OPTIONS, + "head": HTTPMethod.HEAD, + } + + def detect_endpoints(self, file_path: Path) -> list[Endpoint]: + """Detect endpoints in a JavaScript/TypeScript file.""" + content = file_path.read_text() + endpoints = [] + + framework = self._detect_framework(content) + + if framework == "fastify": + endpoints.extend(self._detect_fastify(content, file_path)) + else: + endpoints.extend(self._detect_express(content, file_path)) + + return endpoints + + def _detect_framework(self, content: str) -> Optional[str]: + """Auto-detect the JavaScript framework.""" + if "from '@fastify/" in content or "from 'fastify'" in content or "import fastify" in content: + return "fastify" + if 'require("fastify")' in content or "require('fastify')" in content: + return "fastify" + if "from 'express'" in content or "import express" in content: + return "express" + return None + + def _detect_express(self, content: str, file_path: Path) -> list[Endpoint]: + """Detect Express endpoints.""" + endpoints = [] + + for match in self.EXPRESS_PATTERN.finditer(content): + router, method, path = match.groups() + endpoint = Endpoint( + path=path, + method=self.METHOD_MAP.get(method.lower(), HTTPMethod.GET), + summary=f"{method.upper()} {path}", + file_path=str(file_path), + line_number=content[:match.start()].count("\n") + 1, + ) + endpoints.append(endpoint) + + for match in self.EXPRESS_APP_ROUTE_PATTERN.finditer(content): + router, path, sub_method = match.groups() + endpoint = Endpoint( + path=path, + method=self.METHOD_MAP.get(sub_method.lower(), HTTPMethod.GET), + summary=f"{sub_method.upper()} {path}", + file_path=str(file_path), + line_number=content[:match.start()].count("\n") + 1, + ) + endpoints.append(endpoint) + + return endpoints + + def _detect_fastify(self, content: str, file_path: Path) -> list[Endpoint]: + """Detect Fastify endpoints.""" + endpoints = [] + + for match in self.FASTIFY_PATTERN.finditer(content): + method, path = match.groups() + endpoint = Endpoint( + path=path, + method=self.METHOD_MAP.get(method.lower(), HTTPMethod.GET), + summary=f"{method.upper()} {path}", + file_path=str(file_path), + line_number=content[:match.start()].count("\n") + 1, + ) + endpoints.append(endpoint) + + for match in self.FASTIFY_DECORATOR_PATTERN.finditer(content): + path = match.group(1) + endpoint = Endpoint( + path=path, + method=HTTPMethod.GET, + summary=f"GET {path}", + file_path=str(file_path), + line_number=content[:match.start()].count("\n") + 1, + ) + endpoints.append(endpoint) + + return endpoints