diff --git a/requirements_dev.txt b/requirements_dev.txt index ee574901..39460775 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -5,3 +5,4 @@ pytest==7.2.0 recommonmark==0.7.1 nox twine==4.0.2 +beautifulsoup4 diff --git a/sphinx_js/renderers.py b/sphinx_js/renderers.py index 6b592968..c6b4e73e 100644 --- a/sphinx_js/renderers.py +++ b/sphinx_js/renderers.py @@ -26,6 +26,7 @@ Type, TypeParam, TypeXRef, + TypeXRefInternal, ) from .jsdoc import Analyzer as JsAnalyzer from .parsers import PathVisitor @@ -207,11 +208,15 @@ def _formal_params(self, obj: Function | Class) -> str: return "({})".format(", ".join(formals)) - def format_type(self, type: Type, escape: bool = False) -> str: + def format_type(self, type: Type, escape: bool = False, bold: bool = True) -> str: if not type: return "" if isinstance(type, str): - return self.format_type([type]) + if bold: + type = "**%s**" % type + if escape: + type = rst.escape(type) + return type it = iter(type) def strs() -> Iterator[str]: @@ -236,17 +241,23 @@ def strs() -> Iterator[str]: return "".join(res) def render_xref(self, s: TypeXRef, escape: bool = False) -> str: + if isinstance(s, TypeXRefInternal): + name = rst.escape(s.name) + result = f":js:class:`{name}`" + else: + result = s.name if escape: - return rst.escape(s.name) - return s.name + result = rst.escape(result) + return result def _return_formatter(self, return_: Return) -> tuple[list[str], str]: """Derive heads and tail from ``@returns`` blocks.""" - tail = "" + tail = [] if return_.type: - tail += "**%s** -- " % self.format_type(return_.type, escape=True) - tail += return_.description - return ["returns"], tail + tail.append(self.format_type(return_.type, escape=False)) + if return_.description: + tail.append(return_.description) + return ["returns"], " -- ".join(tail) def _type_param_formatter(self, tparam: TypeParam) -> tuple[list[str], str] | None: v = tparam.name @@ -280,7 +291,7 @@ def _exception_formatter(self, exception: Exc) -> tuple[list[str], str]: """Derive heads and tail from ``@throws`` blocks.""" heads = ["throws"] if exception.type: - heads.append(self.format_type(exception.type)) + heads.append(self.format_type(exception.type, bold=False)) tail = exception.description return heads, tail diff --git a/tests/test_build_js/test_build_js.py b/tests/test_build_js/test_build_js.py index 0b9e85ba..7f424214 100644 --- a/tests/test_build_js/test_build_js.py +++ b/tests/test_build_js/test_build_js.py @@ -43,14 +43,14 @@ def test_autofunction_typedef(self): """Make sure @typedef uses can be documented with autofunction.""" self._file_contents_eq( "autofunction_typedef", - "TypeDefinition()\n\n Arguments:\n * **width** (*Number*) -- width in pixels\n", + "TypeDefinition()\n\n Arguments:\n * **width** (**Number**) -- width in pixels\n", ) def test_autofunction_callback(self): """Make sure @callback uses can be documented with autofunction.""" self._file_contents_eq( "autofunction_callback", - "requestCallback(responseCode)\n\n Some global callback\n\n Arguments:\n * **responseCode** (*number*) --\n", + "requestCallback(responseCode)\n\n Some global callback\n\n Arguments:\n * **responseCode** (**number**) --\n", ) def test_autofunction_example(self): @@ -71,10 +71,10 @@ def test_autofunction_destructured_params(self): "autofunction_destructured_params", "destructuredParams(p1, p2)\n\n" " Arguments:\n" - " * **p1** (*number*) --\n\n" - " * **p2** (*Object*) --\n\n" - " * **p2.foo** (*string*) --\n\n" - " * **p2.bar** (*string*) --\n", + " * **p1** (**number**) --\n\n" + " * **p2** (**Object**) --\n\n" + " * **p2.foo** (**string**) --\n\n" + " * **p2.bar** (**string**) --\n", ) def test_autofunction_defaults_in_doclet(self): @@ -84,9 +84,9 @@ def test_autofunction_defaults_in_doclet(self): "autofunction_defaults_doclet", 'defaultsDocumentedInDoclet(func=() => 5, str="a string with \\" quote", strNum="42", strBool="true", num=5, nil=null)\n\n' " Arguments:\n" - " * **func** (*function*) --\n\n" - " * **strNum** (*string*) --\n\n" - " * **strBool** (*string*) --\n", + " * **func** (**function**) --\n\n" + " * **strNum** (**string**) --\n\n" + " * **strBool** (**string**) --\n", ) def test_autofunction_defaults_in_code(self): @@ -361,7 +361,7 @@ def test_restructuredtext_injection(self): "injection(a_, b)\n\n" " Arguments:\n" " * **a_** -- Snorf\n\n" - " * **b** (>>type_<<) -- >>Borf_<<\n\n" + " * **b** (**type_**) -- >>Borf_<<\n\n" " Returns:\n" " **rtype_** -- >>Dorf_<<\n", ) @@ -377,7 +377,7 @@ def test_union_types(self): switched from " | " as the union separator back to "|". """ - assert "* **fnodeA** (*Node|Fnode*) --" in self._file_contents("union") + assert "* **fnodeA** (**Node|Fnode**) --" in self._file_contents("union") def test_field_list_unwrapping(self): """Ensure the tails of field lists have line breaks and leading @@ -414,7 +414,7 @@ def test_field_list_unwrapping(self): FIELDS = """ Arguments: - * **node** (*Node*) -- Something of a single type + * **node** (**Node**) -- Something of a single type Throws: **PartyError|FartyError** -- Something with multiple types and a diff --git a/tests/test_build_ts/source/class.ts b/tests/test_build_ts/source/class.ts index de9365c6..0a7962ce 100644 --- a/tests/test_build_ts/source/class.ts +++ b/tests/test_build_ts/source/class.ts @@ -53,3 +53,16 @@ export interface OptionalThings { foop?(): void; boop?: boolean; } + +/** + * Words words words + * @param a An optional thing + * @returns The result + */ +export function blah(a: OptionalThings) : ConstructorlessClass { + return 0 as ConstructorlessClass; +} + +export function thunk(b : typeof blah) { + +} diff --git a/tests/test_build_ts/source/docs/xrefs.rst b/tests/test_build_ts/source/docs/xrefs.rst new file mode 100644 index 00000000..cfca799d --- /dev/null +++ b/tests/test_build_ts/source/docs/xrefs.rst @@ -0,0 +1,7 @@ +.. js:autofunction:: blah + + blah + +.. js:autofunction:: thunk + + Xrefs in the function type of the argument diff --git a/tests/test_build_ts/test_build_ts.py b/tests/test_build_ts/test_build_ts.py index e29fd28f..ca6c6dc1 100644 --- a/tests/test_build_ts/test_build_ts.py +++ b/tests/test_build_ts/test_build_ts.py @@ -1,3 +1,4 @@ +from bs4 import BeautifulSoup from conftest import TYPEDOC_VERSION from tests.testing import SphinxBuildTestCase @@ -121,3 +122,28 @@ def test_implements_links(self): assert 'href="index.html#class.Interface"' in self._file_contents( "autoclass_class_with_interface_and_supers" ) + + def test_xrefs(self): + soup = BeautifulSoup(self._file_contents("xrefs"), "html.parser") + + def get_links(id): + return soup.find(id=id).parent.find_all("a") + + links = get_links("blah") + href = links[1] + assert href.attrs["class"] == ["reference", "internal"] + assert href.attrs["href"] == "autoclass_interface_optionals.html#OptionalThings" + assert href.attrs["title"] == "OptionalThings" + assert next(href.children).name == "code" + assert href.get_text() == "OptionalThings()" + + href = links[2] + assert href.attrs["class"] == ["reference", "internal"] + assert ( + href.attrs["href"] == "autoclass_constructorless.html#ConstructorlessClass" + ) + assert href.get_text() == "ConstructorlessClass()" + + thunk_links = get_links("thunk") + assert thunk_links[1].get_text() == "OptionalThings()" + assert thunk_links[2].get_text() == "ConstructorlessClass()"