字段说明测试

This commit is contained in:
2026-06-05 15:42:29 +08:00
parent 77479a40a1
commit 021fc8d5e3
4 changed files with 188 additions and 64 deletions

View File

@@ -233,6 +233,64 @@ def _param_required(param: FormalParameter) -> bool:
return not _has_ann(param, "Nullable")
JAVADOC_PARAM_RE = re.compile(
r"@param\s+(\w+)\s+(.*?)(?=\n\s*\*\s*@|\n\s*\*/|\Z)",
re.DOTALL,
)
def _clean_javadoc_text(text: str) -> str:
"""清理 JavaDoc 行前缀与多余空白。"""
cleaned = re.sub(r"\s*\*\s?", " ", text)
return re.sub(r"\s+", " ", cleaned).strip()
def _parse_javadoc_params(javadoc: str) -> Dict[str, str]:
"""从 JavaDoc 块解析 @param 名称 -> 说明。"""
if not javadoc:
return {}
result: Dict[str, str] = {}
for match in JAVADOC_PARAM_RE.finditer(javadoc):
name = match.group(1)
desc = _clean_javadoc_text(match.group(2))
if desc:
result[name] = desc
return result
def _extract_javadoc_before_line(source: str, target_line: int) -> str:
"""
提取目标行之前紧邻的 JavaDoc 块。
target_line 为 1-indexed与方法声明行号一致
"""
if not source or target_line <= 1:
return ""
lines = source.splitlines()
idx = target_line - 2
while idx >= 0 and not lines[idx].strip():
idx -= 1
while idx >= 0 and lines[idx].strip().startswith("@"):
idx -= 1
if idx < 0 or not lines[idx].strip().endswith("*/"):
return ""
end_idx = idx
while idx >= 0 and not lines[idx].strip().startswith("/**"):
idx -= 1
if idx < 0:
return ""
return "\n".join(lines[idx : end_idx + 1])
def _lookup_param_description(
javadoc_params: Dict[str, str], param: FormalParameter, resolved_name: str
) -> Optional[str]:
"""按注解名或形参名匹配 JavaDoc @param 说明。"""
for key in (resolved_name, param.name):
if key and key in javadoc_params:
return javadoc_params[key]
return None
class ControllerAstParser:
"""
基于 javalang 的 Controller 解析器。
@@ -247,6 +305,7 @@ class ControllerAstParser:
self.repo_root = repo_root
self.source_dir = source_dir
self._dto_cache: Dict[str, List[ApiParameter]] = {}
self._current_source = ""
def parse_file_content(self, source: str, repo_relative_path: str) -> List[ApiEndpoint]:
"""
@@ -257,6 +316,7 @@ class ControllerAstParser:
:return: 端点列表
"""
endpoints: List[ApiEndpoint] = []
self._current_source = source
try:
tree = javalang.parse.parse(source)
except (javalang.parser.JavaSyntaxError, RecursionError) as exc:
@@ -300,9 +360,16 @@ class ControllerAstParser:
continue
method_path = _ann_string(ann, "value", "path")
javadoc_params: Dict[str, str] = {}
if getattr(method, "position", None) and method.position:
javadoc = _extract_javadoc_before_line(
self._current_source, method.position.line
)
javadoc_params = _parse_javadoc_params(javadoc)
params = []
for p in method.parameters or []:
params.extend(self._extract_param(p))
params.extend(self._extract_param(p, javadoc_params))
return ApiEndpoint(
http_method=_http_method(ann_name, ann),
@@ -314,10 +381,13 @@ class ControllerAstParser:
)
return None
def _extract_param(self, param: FormalParameter) -> List[ApiParameter]:
def _extract_param(
self, param: FormalParameter, javadoc_params: Optional[Dict[str, str]] = None
) -> List[ApiParameter]:
"""提取方法参数,@RequestBody 展开 DTO 字段;忽略框架注入参数。"""
type_name = _type_to_str(param.type)
name = _param_name(param)
javadoc_params = javadoc_params or {}
if _is_framework_param(type_name, name):
return []
@@ -325,12 +395,14 @@ class ControllerAstParser:
if _has_ann(param, "RequestBody"):
return self._expand_dto(type_name, "body")
description = _lookup_param_description(javadoc_params, param, name)
return [
ApiParameter(
name=name,
type=type_name,
required=_param_required(param),
source=_param_source(param),
description=description,
)
]
@@ -347,7 +419,8 @@ class ControllerAstParser:
return result
try:
tree = javalang.parse.parse(dto_file.read_text(encoding="utf-8", errors="ignore"))
dto_source = dto_file.read_text(encoding="utf-8", errors="ignore")
tree = javalang.parse.parse(dto_source)
except (javalang.parser.JavaSyntaxError, OSError):
result = [ApiParameter(name=simple, type=type_name, required=True, source=source)]
self._dto_cache[simple] = result
@@ -360,6 +433,14 @@ class ControllerAstParser:
for field in type_decl.fields or []:
if "static" in (field.modifiers or []):
continue
field_javadoc = ""
if getattr(field, "position", None) and field.position:
field_javadoc = _extract_javadoc_before_line(
dto_source, field.position.line
)
field_desc = _clean_javadoc_text(
field_javadoc.replace("/**", "").replace("*/", "").strip()
) or None
for decl in field.declarators:
fields.append(
ApiParameter(
@@ -367,6 +448,7 @@ class ControllerAstParser:
type=_type_to_str(field.type),
required=not _has_ann(field, "Nullable"),
source=source,
description=field_desc,
)
)