From f98307a6dc34281159b0a936c3b078f5ef851bb9 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Sun, 24 Sep 2023 19:11:26 -0700 Subject: [PATCH] Add async prefix to async functions --- sphinx_js/__init__.py | 36 ++++--------------------- sphinx_js/directives.py | 28 +++++++++++++------ sphinx_js/ir.py | 1 + sphinx_js/jsdoc.py | 1 + sphinx_js/renderers.py | 2 ++ sphinx_js/templates/function.rst | 10 ++++--- sphinx_js/typedoc.py | 5 +++- tests/test_build_ts/source/class.ts | 2 ++ tests/test_build_ts/test_build_ts.py | 13 +++++++++ tests/test_jsdoc_analysis/test_jsdoc.py | 1 + tests/test_renderers.py | 8 +++++- 11 files changed, 62 insertions(+), 45 deletions(-) diff --git a/sphinx_js/__init__.py b/sphinx_js/__init__.py index 11922628..0d7ee9c5 100644 --- a/sphinx_js/__init__.py +++ b/sphinx_js/__init__.py @@ -6,7 +6,7 @@ from sphinx.errors import SphinxError from .directives import ( - JSStaticFunction, + JSFunction, auto_attribute_directive_bound_to_app, auto_class_directive_bound_to_app, auto_function_directive_bound_to_app, @@ -86,37 +86,12 @@ class JSGroupedField(JSXrefMixin, GroupedField): # Cache this to guarantee it only runs once. @cache def fix_staticfunction_objtype() -> None: - """Add support for staticfunction objtype - - This adds a new staticfunction objtype to javascript domain class attribute. - Can't do this with ``app.add_object_type()`` because that adds it to the - std domain. - - This also monkeypatches ``JSObject.get_index_text`` to have the right name - for static functions. - + """Override js:function directive with one that understands static and async + prefixes """ - from sphinx.domains import ObjType - from sphinx.domains.javascript import JavaScriptDomain, JSObject - from sphinx.locale import _ - - if "staticfunction" in JavaScriptDomain.object_types: - return - JavaScriptDomain.object_types["staticfunction"] = ObjType( - _("static function"), "func" - ) - - orig_get_index_text = JSObject.get_index_text - - def get_index_text(self: Any, objectname: str, name_obj: Any) -> Any: - name, obj = name_obj - if self.objtype == "staticfunction": - if not obj: - return _("%s() (built-in static function)") % name - return _("%s() (%s static method)") % (name, obj) - return orig_get_index_text(self, objectname, name_obj) + from sphinx.domains.javascript import JavaScriptDomain - JSObject.get_index_text = get_index_text # type:ignore[assignment] + JavaScriptDomain.directives["function"] = JSFunction @cache @@ -149,7 +124,6 @@ def setup(app: Sphinx) -> None: # is RSTs. app.connect("builder-inited", analyze) - app.add_directive_to_domain("js", "staticfunction", JSStaticFunction) app.add_directive_to_domain( "js", "autofunction", auto_function_directive_bound_to_app(app) ) diff --git a/sphinx_js/directives.py b/sphinx_js/directives.py index afdd067d..91202c81 100644 --- a/sphinx_js/directives.py +++ b/sphinx_js/directives.py @@ -120,11 +120,23 @@ def _members_to_exclude(arg: str | None) -> set[str]: return set(a.strip() for a in (arg or "").split(",")) -class JSStaticFunction(JSCallable): - """Like a callable but with a different prefix.""" - - def get_display_prefix(self) -> list[Any]: - return [ - addnodes.desc_sig_keyword("static", "static"), - addnodes.desc_sig_space(), - ] +class JSFunction(JSCallable): + option_spec = { + **JSCallable.option_spec, + "static": flag, + "async": flag, + } + + def get_display_prefix( + self, + ) -> list[Any]: + result = [] + for name in ["static", "async"]: + if name in self.options: + result.extend( + [ + addnodes.desc_sig_keyword(name, name), + addnodes.desc_sig_space(), + ] + ) + return result diff --git a/sphinx_js/ir.py b/sphinx_js/ir.py index 78f481a8..4b25718a 100644 --- a/sphinx_js/ir.py +++ b/sphinx_js/ir.py @@ -236,6 +236,7 @@ class Attribute(TopLevel, _Member): class Function(TopLevel, _Member): """A function or a method of a class""" + is_async: bool params: list[Param] exceptions: list[Exc] returns: list[Return] diff --git a/sphinx_js/jsdoc.py b/sphinx_js/jsdoc.py index f78d401f..9defa446 100644 --- a/sphinx_js/jsdoc.py +++ b/sphinx_js/jsdoc.py @@ -205,6 +205,7 @@ def _doclet_as_function(self, doclet: Doclet, full_path: list[str]) -> Function: is_abstract=False, is_optional=False, is_static=is_static(doclet), + is_async=False, is_private=is_private(doclet), exceptions=exceptions_to_ir(doclet.get("exceptions", [])), returns=returns_to_ir(doclet.get("returns", [])), diff --git a/sphinx_js/renderers.py b/sphinx_js/renderers.py index fa543c4f..c8481c62 100644 --- a/sphinx_js/renderers.py +++ b/sphinx_js/renderers.py @@ -393,6 +393,7 @@ def _template_vars(self, name: str, obj: Function) -> dict[str, Any]: # type: i deprecated=deprecated, is_optional=obj.is_optional, is_static=obj.is_static, + is_async=obj.is_async, see_also=obj.see_alsos, content="\n".join(self._content), ) @@ -425,6 +426,7 @@ def _template_vars(self, name: str, obj: Class | Interface) -> dict[str, Any]: is_abstract=False, is_optional=False, is_static=False, + is_async=False, is_private=False, type_params=obj.type_params, params=[], diff --git a/sphinx_js/templates/function.rst b/sphinx_js/templates/function.rst index 4a136c76..456a35f3 100644 --- a/sphinx_js/templates/function.rst +++ b/sphinx_js/templates/function.rst @@ -1,10 +1,12 @@ {% import 'common.rst' as common %} -{% if is_static %} -.. js:staticfunction:: {{ name }}{{ '?' if is_optional else '' }}{{ params }} -{% else %} .. js:function:: {{ name }}{{ '?' if is_optional else '' }}{{ params }} -{% endif %} + {% if is_static -%} + :static: + {% endif %} + {%- if is_async -%} + :async: + {% endif %} {{ common.deprecated(deprecated)|indent(3) }} diff --git a/sphinx_js/typedoc.py b/sphinx_js/typedoc.py index 86a7ef72..93a6cebc 100644 --- a/sphinx_js/typedoc.py +++ b/sphinx_js/typedoc.py @@ -875,7 +875,9 @@ def to_ir( self._fix_type_suffix() params = self._destructure_params() - + # Would be nice if we could statically determine that the function was + # defined with `async` keyword but this is probably good enough + is_async = isinstance(self.type, ReferenceType) and self.type.name == "Promise" # This is the real meat of a function, method, or constructor. # # Constructors' .name attrs end up being like 'new Foo'. They @@ -894,6 +896,7 @@ def to_ir( returns=self.return_type(converter) if self.kindString != "Constructor signature" else [], + is_async=is_async, **self.parent_member_properties, **self._top_level_properties(), ) diff --git a/tests/test_build_ts/source/class.ts b/tests/test_build_ts/source/class.ts index a2088559..8228a0ae 100644 --- a/tests/test_build_ts/source/class.ts +++ b/tests/test_build_ts/source/class.ts @@ -88,3 +88,5 @@ export function deprecatedFunction() {} * ``` */ export function exampleFunction() {} + +export async function asyncFunction() {} diff --git a/tests/test_build_ts/test_build_ts.py b/tests/test_build_ts/test_build_ts.py index 5e91c1cd..37cbd5b7 100644 --- a/tests/test_build_ts/test_build_ts.py +++ b/tests/test_build_ts/test_build_ts.py @@ -138,6 +138,19 @@ def test_example(self): ), ) + def test_async(self): + self._file_contents_eq( + "async_function", + dedent( + """\ + async asyncFunction() + + Returns: + Promise + """ + ), + ) + class HtmlBuilderTests(SphinxBuildTestCase): """Tests which require an HTML build of our Sphinx tree, for checking diff --git a/tests/test_jsdoc_analysis/test_jsdoc.py b/tests/test_jsdoc_analysis/test_jsdoc.py index a3371241..a7db2301 100644 --- a/tests/test_jsdoc_analysis/test_jsdoc.py +++ b/tests/test_jsdoc_analysis/test_jsdoc.py @@ -66,6 +66,7 @@ def test_top_level_and_function(self): is_abstract=False, is_optional=False, is_static=False, + is_async=False, params=[ Param( name="bar", diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 68b259d9..0efffd72 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -69,6 +69,7 @@ def make_function(**args): is_abstract=False, is_optional=False, is_static=False, + is_async=False, is_private=False, name="", path=[], @@ -117,7 +118,12 @@ def test_func_render_flags(function_render): # TODO: look into this. assert function_render(is_abstract=True) == DEFAULT_RESULT assert function_render(is_optional=True) == ".. js:function:: blah?()\n" - assert function_render(is_static=True) == ".. js:staticfunction:: blah()\n" + assert function_render(is_static=True) == ".. js:function:: blah()\n :static:\n" + assert function_render(is_async=True) == ".. js:function:: blah()\n :async:\n" + assert ( + function_render(is_async=True, is_static=True) + == ".. js:function:: blah()\n :static:\n :async:\n" + ) assert function_render(is_private=True) == DEFAULT_RESULT