diff --git a/sphinx_js/__init__.py b/sphinx_js/__init__.py index 51310fd4..095a9727 100644 --- a/sphinx_js/__init__.py +++ b/sphinx_js/__init__.py @@ -5,15 +5,12 @@ from typing import Any from sphinx.application import Sphinx +from sphinx.domains.javascript import JavaScriptDomain from sphinx.errors import SphinxError from .directives import ( JSFunction, - auto_attribute_directive_bound_to_app, - auto_class_directive_bound_to_app, - auto_function_directive_bound_to_app, - auto_module_directive_bound_to_app, - auto_summary_directive_bound_to_app, + add_directives, sphinx_js_type_role, ) from .jsdoc import Analyzer as JsAnalyzer @@ -94,7 +91,6 @@ def fix_staticfunction_objtype() -> None: """Override js:function directive with one that understands static and async prefixes """ - from sphinx.domains.javascript import JavaScriptDomain JavaScriptDomain.directives["function"] = JSFunction @@ -153,21 +149,7 @@ def setup(app: Sphinx) -> None: # is RSTs. app.connect("builder-inited", analyze) - app.add_directive_to_domain( - "js", "autofunction", auto_function_directive_bound_to_app(app) - ) - app.add_directive_to_domain( - "js", "autoclass", auto_class_directive_bound_to_app(app) - ) - app.add_directive_to_domain( - "js", "autoattribute", auto_attribute_directive_bound_to_app(app) - ) - app.add_directive_to_domain( - "js", "automodule", auto_module_directive_bound_to_app(app) - ) - app.add_directive_to_domain( - "js", "autosummary", auto_summary_directive_bound_to_app(app) - ) + add_directives(app) # TODO: We could add a js:module with app.add_directive_to_domain(). diff --git a/sphinx_js/directives.py b/sphinx_js/directives.py index 62b158e9..d4177900 100644 --- a/sphinx_js/directives.py +++ b/sphinx_js/directives.py @@ -9,6 +9,7 @@ """ import re from collections.abc import Iterable +from functools import cache from os.path import join, relpath from typing import Any @@ -20,7 +21,9 @@ from docutils.utils import new_document from sphinx import addnodes from sphinx.application import Sphinx -from sphinx.domains.javascript import JSCallable +from sphinx.domains import ObjType +from sphinx.domains.javascript import JavaScriptDomain, JSCallable, JSObject +from sphinx.locale import _ from .renderers import ( AutoAttributeRenderer, @@ -178,6 +181,39 @@ def get_display_prefix( return result +class JSInterface(JSCallable): + """Like a callable but with a different prefix.""" + + allow_nesting = True + + def get_display_prefix(self) -> list[Node]: + return [ + addnodes.desc_sig_keyword("interface", "interface"), + addnodes.desc_sig_space(), + ] + + +@cache +def patch_js_interface() -> None: + orig_get_index_text = JSObject.get_index_text + + def patched_get_index_text( + self: JSObject, objectname: str, name_obj: tuple[str, str] + ) -> str: + name, obj = name_obj + if self.objtype == "interface": + return _("%s() (interface)") % name + return orig_get_index_text(self, objectname, name_obj) + + JSObject.get_index_text = patched_get_index_text # type:ignore[method-assign] + + +def add_js_interface(app: Sphinx) -> None: + patch_js_interface() + JavaScriptDomain.object_types["interface"] = ObjType(_("interface"), "interface") + app.add_directive_to_domain("js", "interface", JSInterface) + + def auto_module_directive_bound_to_app(app: Sphinx) -> type[Directive]: class AutoModuleDirective(JsDirectiveWithChildren): """TODO: words here""" @@ -198,3 +234,22 @@ def run(self) -> list[Node]: return self._run(AutoSummaryRenderer, app) return JsDocSummary + + +def add_directives(app: Sphinx) -> None: + app.add_directive_to_domain( + "js", "autofunction", auto_function_directive_bound_to_app(app) + ) + app.add_directive_to_domain( + "js", "autoclass", auto_class_directive_bound_to_app(app) + ) + app.add_directive_to_domain( + "js", "autoattribute", auto_attribute_directive_bound_to_app(app) + ) + app.add_directive_to_domain( + "js", "automodule", auto_module_directive_bound_to_app(app) + ) + app.add_directive_to_domain( + "js", "autosummary", auto_summary_directive_bound_to_app(app) + ) + add_js_interface(app) diff --git a/sphinx_js/renderers.py b/sphinx_js/renderers.py index f1cefd70..8bf497f8 100644 --- a/sphinx_js/renderers.py +++ b/sphinx_js/renderers.py @@ -591,9 +591,7 @@ def _template_vars(self, name: str, obj: Class | Interface) -> dict[str, Any]: interfaces=[self.render_type(x) for x in obj.interfaces] if isinstance(obj, Class) else [], - is_interface=isinstance( - obj, Interface - ), # TODO: Make interfaces not look so much like classes. This will require taking complete control of templating from Sphinx. + is_interface=isinstance(obj, Interface), supers=[self.render_type(x) for x in obj.supers], constructor_comment=render_description(constructor.description), content="\n".join(self._content), diff --git a/sphinx_js/templates/class.rst b/sphinx_js/templates/class.rst index 3269d9e0..ea3ef49b 100644 --- a/sphinx_js/templates/class.rst +++ b/sphinx_js/templates/class.rst @@ -1,6 +1,10 @@ {% import 'common.rst' as common %} +{% if is_interface -%} +.. js:interface:: {{ name }}{{ params }} +{%- else -%} .. js:class:: {{ name }}{{ params }} +{%- endif %} {{ common.deprecated(deprecated)|indent(3) }} @@ -12,10 +16,6 @@ *abstract* {%- endif %} - {% if is_interface -%} - *interface* - {%- endif %} - {{ common.exported_from(exported_from)|indent(3) }} {% if supers -%} diff --git a/tests/test_build_ts/source/module.ts b/tests/test_build_ts/source/module.ts index ccc046fc..3b49ab04 100644 --- a/tests/test_build_ts/source/module.ts +++ b/tests/test_build_ts/source/module.ts @@ -38,6 +38,6 @@ export class Z { export const q = { a: "z29", b: 76 }; /** - * Interface documentation + * Documentation for the interface I */ export interface I {} diff --git a/tests/test_build_ts/test_build_ts.py b/tests/test_build_ts/test_build_ts.py index 0b97e98a..bb200f5f 100644 --- a/tests/test_build_ts/test_build_ts.py +++ b/tests/test_build_ts/test_build_ts.py @@ -93,9 +93,7 @@ def test_optional_members(self): question marks sticking out of them.""" self._file_contents_eq( "autoclass_interface_optionals", - "class OptionalThings()\n" - "\n" - " *interface*\n" + "interface OptionalThings()\n" "\n" ' *exported from* "class"\n' "\n" @@ -316,11 +314,9 @@ class module.Z(a, b) Z.z() - class module.I() - - Interface documentation + interface module.I() - *interface* + Documentation for the interface I *exported from* "module" """ @@ -422,4 +418,7 @@ def test_autosummary(self): assert classes.find(class_="summary").get_text() == "This is a summary." classes = soup.find(class_="interfaces") - assert classes.find(class_="summary").get_text() == "Interface documentation" + assert ( + classes.find(class_="summary").get_text() + == "Documentation for the interface I" + )