字段说明测试
This commit is contained in:
@@ -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,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user