Skip to content

Commit 59e3ae7

Browse files
committed
add refname evaluation in annotation
fix: func or class without signature have no link
1 parent 8f911bf commit 59e3ae7

File tree

3 files changed

+84
-47
lines changed

3 files changed

+84
-47
lines changed

nb_autodoc/annotation.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from nb_autodoc.utils import frozendict, interleave
2626

2727
if t.TYPE_CHECKING:
28-
from nb_autodoc.manager import _AnnContext
28+
from nb_autodoc.manager import ModuleManager, _AnnContext
2929
from nb_autodoc.typing import T_Definition
3030

3131

@@ -297,9 +297,11 @@ def __init__(
297297
*,
298298
globalns: dict[str, T_Definition] = frozendict(),
299299
add_link: t.Callable[[T_Definition], str] = _add_link_empty,
300+
eval_refname: t.Callable[[str], t.Optional[T_Definition]] = lambda x: None,
300301
) -> None:
301302
self.globalns = globalns
302303
self.add_link = add_link
304+
self.eval_refname = eval_refname
303305
self._builder: list[str] = []
304306

305307
def write(self, s: str) -> None:
@@ -336,8 +338,9 @@ def visit(self, annexpr: _annexpr) -> None:
336338

337339
def visit_Name(self, annexpr: Name) -> None:
338340
name = annexpr.name
339-
if name in self.globalns:
340-
self.write(self.add_link(self.globalns[name]))
341+
dobj = self.globalns.get(name) or self.eval_refname(name)
342+
if dobj:
343+
self.write(self.add_link(dobj))
341344
else:
342345
self.write(name)
343346

@@ -407,12 +410,14 @@ def __init__(
407410
context: _AnnContext,
408411
*,
409412
globalns: dict[str, T_Definition] | None = None,
413+
manager: ModuleManager,
410414
) -> None:
411415
norm = _get_typing_normalizer(context)
412416
self.ann: _T_annexpr = AnnotationTransformer(norm).visit(ast_expr)
413417
if globalns is None:
414418
globalns = {}
415419
self.globalns = globalns
420+
self.manager = manager
416421

417422
@property
418423
def is_typealias(self) -> bool:
@@ -427,9 +432,11 @@ def is_classvar(self) -> bool:
427432
return False
428433

429434
def get_doc_linkify(self, add_link: t.Callable[[T_Definition], str]) -> str:
430-
return AnnExprVisitor(globalns=self.globalns, add_link=add_link).render(
431-
self.ann
432-
)
435+
return AnnExprVisitor(
436+
globalns=self.globalns,
437+
add_link=add_link,
438+
eval_refname=self.manager.get_definition_dotted,
439+
).render(self.ann)
433440

434441
def __str__(self) -> str:
435442
return _type_str(self.ann)

nb_autodoc/builders/markdown.py

Lines changed: 70 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import inspect
33
import re
44
from contextlib import contextmanager
5-
from functools import singledispatch
5+
from functools import partial, singledispatch
66
from inspect import Parameter
77
from itertools import count
88
from textwrap import indent
@@ -35,7 +35,7 @@
3535
from .helpers import vuepress_slugify
3636

3737
_descr_role_re = re.compile(
38-
r"{(?P<name>\w+?)}`(?:(?P<text>[^{}]+?) <)?(?P<content>[\w\.]+)(?(text)>)`"
38+
r"{(?P<name>\w+?)}`(?:(?P<text>[^{}]+?) <)?(?P<content>[\w\.\+\-]+)(?(text)>)`"
3939
)
4040

4141

@@ -178,6 +178,7 @@ def __init__(
178178
self.add_heading_id = add_heading_id
179179
self.indent_size = config["markdown_indent_size"]
180180
self.builder = builder
181+
self.is_auto_args: bool = False
181182
self._builder: list[str] = []
182183
self._indent: int = 0
183184
self._level: int = 1
@@ -262,7 +263,7 @@ def _replace_descr(self, match: Match[str]) -> str:
262263
matchtext = match.group()
263264
manager = self.current_module.manager
264265
if name in ("version", "ver"):
265-
return get_version_badge(role["content"])
266+
return get_version_badge(content)
266267
elif name == "ref":
267268
if ":" in content:
268269
modulename, qualname = content.split(":")
@@ -290,7 +291,7 @@ def _resolve_args_from_sig(
290291
self,
291292
*,
292293
args: Optional[nodes.Args] = None,
293-
sig: FunctionSignature,
294+
sig: Optional[FunctionSignature] = None,
294295
bind_module: Module,
295296
) -> nodes.Args:
296297
doc_args_dict = None
@@ -299,37 +300,45 @@ def _resolve_args_from_sig(
299300
doc_args_dict = _args_to_dict(args)
300301
# turn signature into Args section
301302
new_args = nodes.Args(
302-
name="参数", args=[], vararg=None, kwonlyargs=[], kwarg=None # TODO: i18n
303+
name=args.name if args else "参数", # TODO: i18n
304+
args=[],
305+
vararg=None,
306+
kwonlyargs=[],
307+
kwarg=None,
303308
)
304-
for p in sig.parameters.values():
305-
doc_arg = None
306-
if doc_args_dict:
307-
doc_arg = doc_args_dict.get(p.name)
308-
annotation = None
309-
# doc overridden annotation or parameter annotation
310-
if doc_arg and doc_arg.annotation:
311-
annotation = bind_module.build_static_ann(
312-
ast.parse(doc_arg.annotation, mode="eval").body
313-
).get_doc_linkify(self.add_link)
314-
elif p.annotation is not Parameter.empty:
315-
annotation = p.annotation.get_doc_linkify(self.add_link)
316-
arg = nodes.ColonArg(p.name, annotation, [], "", "")
317-
if doc_arg:
318-
arg.descr = doc_arg.descr
319-
arg.long_descr = doc_arg.long_descr
320-
if p.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD):
321-
new_args.args.append(arg)
322-
elif p.kind is Parameter.VAR_POSITIONAL:
323-
new_args.vararg = arg
324-
elif p.kind is Parameter.KEYWORD_ONLY:
325-
new_args.kwonlyargs.append(arg)
326-
elif p.kind is Parameter.VAR_KEYWORD:
327-
new_args.kwarg = arg
309+
if sig:
310+
for p in sig.parameters.values():
311+
doc_arg = None
312+
if doc_args_dict:
313+
doc_arg = doc_args_dict.get(p.name)
314+
annotation = None
315+
# doc overridden annotation or parameter annotation
316+
if doc_arg and doc_arg.annotation:
317+
annotation = bind_module.build_static_ann(
318+
ast.parse(doc_arg.annotation, mode="eval").body
319+
).get_doc_linkify(self.add_link)
320+
elif p.annotation is not Parameter.empty:
321+
annotation = p.annotation.get_doc_linkify(self.add_link)
322+
arg = nodes.ColonArg(p.name, annotation, [], "", "")
323+
if doc_arg:
324+
arg.descr = doc_arg.descr
325+
arg.long_descr = doc_arg.long_descr
326+
if p.kind in (
327+
Parameter.POSITIONAL_ONLY,
328+
Parameter.POSITIONAL_OR_KEYWORD,
329+
):
330+
new_args.args.append(arg)
331+
elif p.kind is Parameter.VAR_POSITIONAL:
332+
new_args.vararg = arg
333+
elif p.kind is Parameter.KEYWORD_ONLY:
334+
new_args.kwonlyargs.append(arg)
335+
elif p.kind is Parameter.VAR_KEYWORD:
336+
new_args.kwarg = arg
328337
if doc_args_dict:
329338
# the docstring arg doesn't match signature
330339
# we think these arguments are keyword-only
331340
for name in doc_args_dict:
332-
if name in sig.parameters: # keep order
341+
if sig and name in sig.parameters: # keep order
333342
continue
334343
doc_arg = doc_args_dict[name]
335344
annotation = None
@@ -348,10 +357,12 @@ def _resolve_rets_from_sig(
348357
self,
349358
*,
350359
rets: Optional[nodes.Returns] = None,
351-
sig: FunctionSignature,
360+
sig: Optional[FunctionSignature] = None,
352361
bind_module: Module,
353362
) -> nodes.Returns:
354-
new_rets = nodes.Returns(name="返回", version=None) # TODO: i18n
363+
new_rets = nodes.Returns(
364+
name=rets.name if rets else "返回", version=None # TODO: i18n
365+
)
355366
new_rets.value = nodes.ColonArg(None, None, [], "", "")
356367
annotation = None
357368
if rets:
@@ -368,16 +379,14 @@ def _resolve_rets_from_sig(
368379
long_descr = long_descr.strip()
369380
new_rets.value.descr = descr
370381
new_rets.value.long_descr = long_descr
371-
if annotation is None and sig.return_annotation is not Parameter.empty:
382+
if annotation is None and sig and sig.return_annotation is not Parameter.empty:
372383
annotation = sig.return_annotation.get_doc_linkify(self.add_link)
373384
elif annotation is None:
374385
annotation = "untyped"
375386
new_rets.value.annotation = annotation
376387
return new_rets
377388

378389
def _resolve_doc_from_sig(self, dobj: Union[Function, Class]) -> None:
379-
if not dobj.signature:
380-
return None
381390
if not dobj.doctree:
382391
dobj.doctree = nodes.Docstring(
383392
roles=[], annotation=None, descr="", long_descr="", sections=[]
@@ -507,19 +516,23 @@ def visit_Function(self, dobj: Function) -> None:
507516
dobj.doctree.sections.insert(0, overloads) # type: ignore # mypy
508517
else:
509518
self._resolve_doc_from_sig(dobj)
519+
self.is_auto_args = bool(not dobj.signature)
510520
if dobj.doctree:
511521
self.newline()
512522
self.visit_Docstring(dobj.doctree)
523+
self.is_auto_args = False
513524

514525
def visit_Class(self, dobj: Class) -> None:
515526
self.title(dobj)
516527
if dobj.doctree:
517528
# remove before resolve
518529
_extract_inlinevalue(dobj.doctree)
519530
self._resolve_doc_from_sig(dobj)
531+
self.is_auto_args = bool(not dobj.signature)
520532
if dobj.doctree:
521533
self.newline()
522534
self.visit_Docstring(dobj.doctree)
535+
self.is_auto_args = False
523536
ctx = self.block() if isenumclass(dobj.pyobj) else self.heading()
524537
with ctx:
525538
for member in self.member_iterator.iter_class(dobj):
@@ -552,8 +565,17 @@ def visit_Docstring(
552565
self.visit(section)
553566

554567
def visit_ColonArg(
555-
self, dsobj: nodes.ColonArg, isvar: bool = False, iskw: bool = False
568+
self,
569+
dsobj: nodes.ColonArg,
570+
*,
571+
isvar: bool = False,
572+
iskw: bool = False,
573+
link_ann: bool = False,
556574
) -> None:
575+
if link_ann and dsobj.annotation:
576+
dsobj.annotation = self.current_module.build_static_ann(
577+
ast.parse(dsobj.annotation, mode="eval").body
578+
).get_doc_linkify(self.add_link)
557579
self.fill("- ")
558580
if dsobj.name:
559581
with self.delimit("`", "`"):
@@ -582,6 +604,7 @@ def visit_Text(self, dsobj: nodes.Text) -> None:
582604
self.write(dsobj.value)
583605

584606
def visit_Args(self, dsobj: nodes.Args) -> None:
607+
# NOTE: only function Args and Returns can have link (auto resolved)
585608
self.fill(f"- **{dsobj.name}**")
586609
rendered = False
587610
with self.block():
@@ -602,7 +625,10 @@ def visit_Args(self, dsobj: nodes.Args) -> None:
602625
self.newline()
603626
self.visit_ColonArg(dsobj.kwarg, iskw=True)
604627
if not rendered:
605-
self.newline("empty")
628+
if self.is_auto_args:
629+
self.newline("auto")
630+
else:
631+
self.newline("empty")
606632

607633
def visit_Overloads(self, dsobj: nodes.Overloads) -> None:
608634
self.fill("- **重载**") # TODO: i18n
@@ -633,9 +659,11 @@ def visit_Raises(self, dsobj: nodes.Raises) -> None:
633659
with self.block():
634660
for arg in dsobj.args:
635661
self.newline()
636-
self.visit_ColonArg(arg)
662+
self.visit_ColonArg(arg, link_ann=True)
637663

638-
def _visit_return_like(self, dsobj: Union[nodes.Returns, nodes.Yields]) -> None:
664+
def _visit_return_like(
665+
self, dsobj: Union[nodes.Returns, nodes.Yields], link_ann: bool = False
666+
) -> None:
639667
self.fill(f"- **{dsobj.name}**")
640668
if dsobj.version:
641669
self.write(" ")
@@ -645,9 +673,10 @@ def _visit_return_like(self, dsobj: Union[nodes.Returns, nodes.Yields]) -> None:
645673
self.newline(dsobj.value)
646674
else:
647675
self.newline()
648-
self.visit_ColonArg(dsobj.value)
676+
self.visit_ColonArg(dsobj.value, link_ann=link_ann)
649677

650-
visit_Returns = visit_Yields = _visit_return_like
678+
visit_Returns = _visit_return_like
679+
visit_Yields = partial(_visit_return_like, link_ann=True)
651680

652681
def visit_Require(self, dsobj: nodes.Require) -> None:
653682
self.fill(f"- **{dsobj.name}**")

nb_autodoc/manager.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ def build_static_ann(self, expr: ast.expr) -> Annotation:
400400
self.prime_analyzer.module.typing_names,
401401
),
402402
globalns=self.get_all_definitions(),
403+
manager=self.manager,
403404
)
404405

405406
def _transform_ast_signature(

0 commit comments

Comments
 (0)