From f87e05fa85e4baf938d9391a02e1e53ee06a04fc Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Mon, 2 Oct 2023 10:04:55 -0700 Subject: [PATCH] This moves the call to post_convert to immediately following `to_ir` This uses a decorator to call post_convert after each call to `to_ir` so that it can't be forgotten. In order to allow subclasses to restrict the type of `to_ir`, we use a decorator (I originally tried a similar approach as with `type.render_name` but it doesn't work with the subclass type restrictions). Typing the decorator took a bit of trial and error, but the key thing is to say that the decorator doesn't change the type of the function. --- sphinx_js/analyzer_utils.py | 2 -- sphinx_js/typedoc.py | 38 ++++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/sphinx_js/analyzer_utils.py b/sphinx_js/analyzer_utils.py index 0143ccee..1c3036c4 100644 --- a/sphinx_js/analyzer_utils.py +++ b/sphinx_js/analyzer_utils.py @@ -28,8 +28,6 @@ def search_node_modules(cmdname: str, cmdpath: str, dir: str | Path) -> str: # search for local install for base in parent_dirs: typedoc = base / "node_modules" / cmdpath - print(base, typedoc) - if typedoc.is_file(): return str(typedoc.resolve()) diff --git a/sphinx_js/typedoc.py b/sphinx_js/typedoc.py index 4e81851c..a4e877de 100644 --- a/sphinx_js/typedoc.py +++ b/sphinx_js/typedoc.py @@ -7,7 +7,7 @@ import typing from collections.abc import Iterable, Iterator, Sequence from errno import ENOENT -from functools import cache +from functools import cache, wraps from inspect import isclass from json import load from operator import attrgetter @@ -28,6 +28,31 @@ MIN_TYPEDOC_VERSION = (0, 25, 0) +T = typing.TypeVar("T") +P = typing.ParamSpec("P") + + +def post_convert(f: typing.Callable[P, T]) -> typing.Callable[P, T]: + """Wrap to_ir with a call to post_convert if it returned a result. + + I treid to be more specific about the type of the decorator but it caused + mypy to change the type of the decorated function. This signature ensures + that the inferred type of the decorated method is identical to the type of + the original method and then uses a couple of carefully placed casts. + """ + + @wraps(f) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: + result = f(*args, **kwargs) + converted, *_ = typing.cast(tuple[ir.TopLevel | None, ...], result) + if converted: + self, converter = typing.cast(tuple[Node | Signature, Converter], args) + converter._post_convert(converter, self, converted) + return result + + return wrapper + + @cache def typedoc_version_info(typedoc: str) -> tuple[tuple[int, ...], tuple[int, ...]]: result = subprocess.run( @@ -494,6 +519,7 @@ def _top_level_properties(self) -> TopLevelPropertiesDict: exported_from=ir.Pathname(self.filepath), ) + @post_convert def to_ir( self, converter: Converter ) -> tuple[ir.TopLevel | None, Sequence["Node"]]: @@ -528,6 +554,7 @@ def comment(self) -> Comment: return self.setSignature.comment return self.comment_ + @post_convert def to_ir(self, converter: Converter) -> tuple[ir.Attribute, Sequence["Node"]]: if self.getSignature: # There's no signature to speak of for a getter: only a return type. @@ -578,6 +605,7 @@ def comment(self) -> Comment: def _path_segments(self, base_dir: str) -> list[str]: return [self.name] + @post_convert def to_ir( self, converter: Converter ) -> tuple[ir.Function | None, Sequence["Node"]]: @@ -675,6 +703,7 @@ def _constructor_and_members( class Class(ClassOrInterface): kindString: Literal["Class"] + @post_convert def to_ir(self, converter: Converter) -> tuple[ir.Class | None, Sequence["Node"]]: constructor, members = self._constructor_and_members(converter) result = ir.Class( @@ -694,6 +723,7 @@ def to_ir(self, converter: Converter) -> tuple[ir.Class | None, Sequence["Node"] class Interface(ClassOrInterface): kindString: Literal["Interface"] + @post_convert def to_ir(self, converter: Converter) -> tuple[ir.Interface, Sequence["Node"]]: _, members = self._constructor_and_members(converter) result = ir.Interface( @@ -718,6 +748,7 @@ def children_with_ids(self) -> Iterator["IndexType"]: if isinstance(self.type, ReflectionType): yield self.type.declaration + @post_convert def to_ir( self, converter: Converter ) -> tuple[ir.Attribute | ir.Function | None, Sequence["Node"]]: @@ -793,6 +824,7 @@ def render(self, converter: Converter) -> Iterator[str | ir.TypeXRef]: yield "; " yield "}" + @post_convert def to_ir( self, converter: Converter ) -> tuple[ir.Function | None, Sequence["Node"]]: @@ -1024,9 +1056,13 @@ def inner(param: Param) -> Iterator[str | ir.TypeXRef]: else: yield ir.TypeXRefIntrinsic("void") + # Don't wrap this in @post_convert since it'll always be covered by the + # owner of the signature. def to_ir( self, converter: Converter ) -> tuple[ir.Function | None, Sequence["Node"]]: + # TODO: The following shouldn't happen in to_ir since it mutates the + # Node SYMBOL_PREFIX = "[Symbol\u2024" if self.name.startswith("[") and not self.name.startswith(SYMBOL_PREFIX): # a symbol.