Skip to content

Commit 4e9bb36

Browse files
authored
Render internal xrefs (#56)
This is the second half of #51. It adds support to the renderer to generate internal xrefs. Which wasn't that much work. In a followup it would be nice to add external xrefs.
1 parent e5b780e commit 4e9bb36

File tree

6 files changed

+79
-21
lines changed

6 files changed

+79
-21
lines changed

requirements_dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ pytest==7.2.0
55
recommonmark==0.7.1
66
nox
77
twine==4.0.2
8+
beautifulsoup4

sphinx_js/renderers.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
Type,
2727
TypeParam,
2828
TypeXRef,
29+
TypeXRefInternal,
2930
)
3031
from .jsdoc import Analyzer as JsAnalyzer
3132
from .parsers import PathVisitor
@@ -207,11 +208,15 @@ def _formal_params(self, obj: Function | Class) -> str:
207208

208209
return "({})".format(", ".join(formals))
209210

210-
def format_type(self, type: Type, escape: bool = False) -> str:
211+
def format_type(self, type: Type, escape: bool = False, bold: bool = True) -> str:
211212
if not type:
212213
return ""
213214
if isinstance(type, str):
214-
return self.format_type([type])
215+
if bold:
216+
type = "**%s**" % type
217+
if escape:
218+
type = rst.escape(type)
219+
return type
215220
it = iter(type)
216221

217222
def strs() -> Iterator[str]:
@@ -236,17 +241,23 @@ def strs() -> Iterator[str]:
236241
return "".join(res)
237242

238243
def render_xref(self, s: TypeXRef, escape: bool = False) -> str:
244+
if isinstance(s, TypeXRefInternal):
245+
name = rst.escape(s.name)
246+
result = f":js:class:`{name}`"
247+
else:
248+
result = s.name
239249
if escape:
240-
return rst.escape(s.name)
241-
return s.name
250+
result = rst.escape(result)
251+
return result
242252

243253
def _return_formatter(self, return_: Return) -> tuple[list[str], str]:
244254
"""Derive heads and tail from ``@returns`` blocks."""
245-
tail = ""
255+
tail = []
246256
if return_.type:
247-
tail += "**%s** -- " % self.format_type(return_.type, escape=True)
248-
tail += return_.description
249-
return ["returns"], tail
257+
tail.append(self.format_type(return_.type, escape=False))
258+
if return_.description:
259+
tail.append(return_.description)
260+
return ["returns"], " -- ".join(tail)
250261

251262
def _type_param_formatter(self, tparam: TypeParam) -> tuple[list[str], str] | None:
252263
v = tparam.name
@@ -280,7 +291,7 @@ def _exception_formatter(self, exception: Exc) -> tuple[list[str], str]:
280291
"""Derive heads and tail from ``@throws`` blocks."""
281292
heads = ["throws"]
282293
if exception.type:
283-
heads.append(self.format_type(exception.type))
294+
heads.append(self.format_type(exception.type, bold=False))
284295
tail = exception.description
285296
return heads, tail
286297

tests/test_build_js/test_build_js.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ def test_autofunction_typedef(self):
4343
"""Make sure @typedef uses can be documented with autofunction."""
4444
self._file_contents_eq(
4545
"autofunction_typedef",
46-
"TypeDefinition()\n\n Arguments:\n * **width** (*Number*) -- width in pixels\n",
46+
"TypeDefinition()\n\n Arguments:\n * **width** (**Number**) -- width in pixels\n",
4747
)
4848

4949
def test_autofunction_callback(self):
5050
"""Make sure @callback uses can be documented with autofunction."""
5151
self._file_contents_eq(
5252
"autofunction_callback",
53-
"requestCallback(responseCode)\n\n Some global callback\n\n Arguments:\n * **responseCode** (*number*) --\n",
53+
"requestCallback(responseCode)\n\n Some global callback\n\n Arguments:\n * **responseCode** (**number**) --\n",
5454
)
5555

5656
def test_autofunction_example(self):
@@ -71,10 +71,10 @@ def test_autofunction_destructured_params(self):
7171
"autofunction_destructured_params",
7272
"destructuredParams(p1, p2)\n\n"
7373
" Arguments:\n"
74-
" * **p1** (*number*) --\n\n"
75-
" * **p2** (*Object*) --\n\n"
76-
" * **p2.foo** (*string*) --\n\n"
77-
" * **p2.bar** (*string*) --\n",
74+
" * **p1** (**number**) --\n\n"
75+
" * **p2** (**Object**) --\n\n"
76+
" * **p2.foo** (**string**) --\n\n"
77+
" * **p2.bar** (**string**) --\n",
7878
)
7979

8080
def test_autofunction_defaults_in_doclet(self):
@@ -84,9 +84,9 @@ def test_autofunction_defaults_in_doclet(self):
8484
"autofunction_defaults_doclet",
8585
'defaultsDocumentedInDoclet(func=() => 5, str="a string with \\" quote", strNum="42", strBool="true", num=5, nil=null)\n\n'
8686
" Arguments:\n"
87-
" * **func** (*function*) --\n\n"
88-
" * **strNum** (*string*) --\n\n"
89-
" * **strBool** (*string*) --\n",
87+
" * **func** (**function**) --\n\n"
88+
" * **strNum** (**string**) --\n\n"
89+
" * **strBool** (**string**) --\n",
9090
)
9191

9292
def test_autofunction_defaults_in_code(self):
@@ -361,7 +361,7 @@ def test_restructuredtext_injection(self):
361361
"injection(a_, b)\n\n"
362362
" Arguments:\n"
363363
" * **a_** -- Snorf\n\n"
364-
" * **b** (>>type_<<) -- >>Borf_<<\n\n"
364+
" * **b** (**type_**) -- >>Borf_<<\n\n"
365365
" Returns:\n"
366366
" **rtype_** -- >>Dorf_<<\n",
367367
)
@@ -377,7 +377,7 @@ def test_union_types(self):
377377
switched from " | " as the union separator back to "|".
378378
379379
"""
380-
assert "* **fnodeA** (*Node|Fnode*) --" in self._file_contents("union")
380+
assert "* **fnodeA** (**Node|Fnode**) --" in self._file_contents("union")
381381

382382
def test_field_list_unwrapping(self):
383383
"""Ensure the tails of field lists have line breaks and leading
@@ -414,7 +414,7 @@ def test_field_list_unwrapping(self):
414414
FIELDS = """
415415
416416
Arguments:
417-
* **node** (*Node*) -- Something of a single type
417+
* **node** (**Node**) -- Something of a single type
418418
419419
Throws:
420420
**PartyError|FartyError** -- Something with multiple types and a

tests/test_build_ts/source/class.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,16 @@ export interface OptionalThings {
5353
foop?(): void;
5454
boop?: boolean;
5555
}
56+
57+
/**
58+
* Words words words
59+
* @param a An optional thing
60+
* @returns The result
61+
*/
62+
export function blah(a: OptionalThings) : ConstructorlessClass {
63+
return 0 as ConstructorlessClass;
64+
}
65+
66+
export function thunk(b : typeof blah) {
67+
68+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.. js:autofunction:: blah
2+
3+
blah
4+
5+
.. js:autofunction:: thunk
6+
7+
Xrefs in the function type of the argument

tests/test_build_ts/test_build_ts.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from bs4 import BeautifulSoup
12
from conftest import TYPEDOC_VERSION
23

34
from tests.testing import SphinxBuildTestCase
@@ -121,3 +122,28 @@ def test_implements_links(self):
121122
assert 'href="index.html#class.Interface"' in self._file_contents(
122123
"autoclass_class_with_interface_and_supers"
123124
)
125+
126+
def test_xrefs(self):
127+
soup = BeautifulSoup(self._file_contents("xrefs"), "html.parser")
128+
129+
def get_links(id):
130+
return soup.find(id=id).parent.find_all("a")
131+
132+
links = get_links("blah")
133+
href = links[1]
134+
assert href.attrs["class"] == ["reference", "internal"]
135+
assert href.attrs["href"] == "autoclass_interface_optionals.html#OptionalThings"
136+
assert href.attrs["title"] == "OptionalThings"
137+
assert next(href.children).name == "code"
138+
assert href.get_text() == "OptionalThings()"
139+
140+
href = links[2]
141+
assert href.attrs["class"] == ["reference", "internal"]
142+
assert (
143+
href.attrs["href"] == "autoclass_constructorless.html#ConstructorlessClass"
144+
)
145+
assert href.get_text() == "ConstructorlessClass()"
146+
147+
thunk_links = get_links("thunk")
148+
assert thunk_links[1].get_text() == "OptionalThings()"
149+
assert thunk_links[2].get_text() == "ConstructorlessClass()"

0 commit comments

Comments
 (0)