Skip to content

Commit 2b35f7c

Browse files
authored
Add documentation entries for type aliases (#156)
Add a TypeAlias ir entry, repurpose Attribute renderer to also render type aliases, add a type alias directive and xref role.
1 parent fb88024 commit 2b35f7c

File tree

13 files changed

+196
-33
lines changed

13 files changed

+196
-33
lines changed

sphinx_js/directives.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,20 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
371371
return handle_typeparams_for_signature(self, sig, signode, keep_callsig=False)
372372

373373

374+
class JSTypeAlias(JSObject):
375+
doc_field_types = [
376+
JSGroupedField(
377+
"typeparam",
378+
label="Type parameters",
379+
names=("typeparam",),
380+
can_collapse=True,
381+
)
382+
]
383+
384+
def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
385+
return handle_typeparams_for_signature(self, sig, signode, keep_callsig=False)
386+
387+
374388
class JSClass(JSConstructor):
375389
def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
376390
return handle_typeparams_for_signature(self, sig, signode, keep_callsig=True)
@@ -438,6 +452,9 @@ def add_directives(app: Sphinx) -> None:
438452
JavaScriptDomain.object_types["interface"] = ObjType(_("interface"), "interface")
439453
app.add_directive_to_domain("js", "interface", JSInterface)
440454
app.add_role_to_domain("js", "interface", JSXRefRole())
455+
JavaScriptDomain.object_types["typealias"] = ObjType(_("type alias"), "typealias")
456+
app.add_directive_to_domain("js", "typealias", JSTypeAlias)
457+
app.add_role_to_domain("js", "typealias", JSXRefRole())
441458
app.add_node(
442459
desc_js_type_parameter_list,
443460
html=(visit_desc_js_type_parameter_list, depart_desc_js_type_parameter_list),

sphinx_js/ir.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ class Module:
203203
functions: list["Function"] = Factory(list)
204204
classes: list["Class"] = Factory(list)
205205
interfaces: list["Interface"] = Factory(list)
206+
type_aliases: list["TypeAlias"] = Factory(list)
206207

207208

208209
@define(slots=False)
@@ -327,7 +328,14 @@ class Class(TopLevel, _MembersAndSupers):
327328
kind: Literal["class"] = "class"
328329

329330

330-
TopLevelUnion = Class | Interface | Function | Attribute
331+
@define
332+
class TypeAlias(TopLevel):
333+
type: Type
334+
type_params: list[TypeParam] = Factory(list)
335+
kind: Literal["typeAlias"] = "typeAlias"
336+
337+
338+
TopLevelUnion = Class | Interface | Function | Attribute | TypeAlias
331339

332340
# Now make a serializer/deserializer.
333341
# TODO: Add tests to make sure that serialization and deserialization are a

sphinx_js/js/convertTopLevel.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -421,8 +421,6 @@ export class Converter {
421421
object.kindOf(
422422
ReflectionKind.Module |
423423
ReflectionKind.Namespace |
424-
// TODO: document TypeAliases
425-
ReflectionKind.TypeAlias |
426424
// TODO: document enums
427425
ReflectionKind.Enum |
428426
ReflectionKind.EnumMember |
@@ -436,7 +434,7 @@ export class Converter {
436434
// be too?
437435
return [undefined, (object as DeclarationReflection).children];
438436
}
439-
const kind = ReflectionKind.singularString(object.kind);
437+
const kind = ReflectionKind[object.kind];
440438
const convertFunc = `convert${kind}` as keyof this;
441439
if (!this[convertFunc]) {
442440
throw new Error(`No known converter for kind ${kind}`);
@@ -875,4 +873,14 @@ export class Converter {
875873
description: renderCommentSummary(typeParam.comment),
876874
};
877875
}
876+
877+
convertTypeAlias(ty: DeclarationReflection): ConvertResult {
878+
const ir: TopLevelIR = {
879+
...this.topLevelProperties(ty),
880+
kind: "typeAlias",
881+
type: this.convertType(ty.type!),
882+
type_params: this.typeParamsToIR(ty.typeParameters),
883+
};
884+
return [ir, ty.children];
885+
}
878886
}

sphinx_js/js/ir.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,10 @@ export type Class = TopLevel &
146146
kind: "class";
147147
};
148148

149-
export type TopLevelIR = Attribute | IRFunction | Class | Interface;
149+
export type TypeAlias = TopLevel & {
150+
kind: "typeAlias";
151+
type: Type;
152+
type_params: TypeParam[];
153+
};
154+
155+
export type TopLevelIR = Attribute | IRFunction | Class | Interface | TypeAlias;

sphinx_js/renderers.py

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
Return,
3737
TopLevel,
3838
Type,
39+
TypeAlias,
3940
TypeParam,
4041
TypeXRef,
4142
TypeXRefInternal,
@@ -337,7 +338,7 @@ def rst_nodes(self) -> list[Node]:
337338
def rst_for(self, obj: TopLevel) -> str:
338339
renderer_class: type
339340
match obj:
340-
case Attribute(_):
341+
case Attribute(_) | TypeAlias(_):
341342
renderer_class = AutoAttributeRenderer
342343
case Function(_):
343344
renderer_class = AutoFunctionRenderer
@@ -374,7 +375,7 @@ def rst(
374375
result = "\n".join(lines) + "\n"
375376
return result
376377

377-
def _type_params(self, obj: Function | Class | Interface) -> str:
378+
def _type_params(self, obj: Function | Class | TypeAlias | Interface) -> str:
378379
if not obj.type_params:
379380
return ""
380381
return "<{}>".format(", ".join(tp.name for tp in obj.type_params))
@@ -654,19 +655,34 @@ class AutoAttributeRenderer(JsRenderer):
654655
_template = "attribute.rst"
655656
_renderer_type = "attribute"
656657

657-
def _template_vars(self, name: str, obj: Attribute) -> dict[str, Any]: # type: ignore[override]
658+
def _template_vars(self, name: str, obj: Attribute | TypeAlias) -> dict[str, Any]: # type: ignore[override]
659+
is_optional = False
660+
if isinstance(obj, Attribute):
661+
is_optional = obj.is_optional
662+
type_params = ""
663+
is_type_alias = isinstance(obj, TypeAlias)
664+
fields: Iterator[tuple[list[str], str]] = iter([])
665+
if isinstance(obj, TypeAlias):
666+
type_params = self._type_params(obj)
667+
fields = self._fields(obj)
658668
return dict(
659669
name=name,
670+
is_type_alias=is_type_alias,
671+
type_params=type_params,
672+
fields=fields,
660673
description=render_description(obj.description),
661674
deprecated=obj.deprecated,
662-
is_optional=obj.is_optional,
675+
is_optional=is_optional,
663676
see_also=obj.see_alsos,
664677
examples=[render_description(ex) for ex in obj.examples],
665678
type=self.render_type(obj.type),
666679
content="\n".join(self._content),
667680
)
668681

669682

683+
_SECTION_ORDER = ["type_aliases", "attributes", "functions", "interfaces", "classes"]
684+
685+
670686
class AutoModuleRenderer(JsRenderer):
671687
def _parse_path(self, arg: str) -> None:
672688
# content, arguments, options, app: all need to be accessible to
@@ -692,10 +708,8 @@ def rst( # type:ignore[override]
692708
) -> str:
693709
rst: list[Sequence[str]] = []
694710
rst.append([f".. js:module:: {''.join(partial_path)}"])
695-
rst.append(self.rst_for_group(obj.attributes))
696-
rst.append(self.rst_for_group(obj.functions))
697-
rst.append(self.rst_for_group(obj.classes))
698-
rst.append(self.rst_for_group(obj.interfaces))
711+
for group_name in _SECTION_ORDER:
712+
rst.append(self.rst_for_group(getattr(obj, group_name)))
699713
return "\n\n".join(["\n\n".join(r) for r in rst])
700714

701715

@@ -715,20 +729,15 @@ def get_object(self) -> Module:
715729

716730
def rst_nodes(self) -> list[Node]:
717731
module = self.get_object()
718-
pairs: list[tuple[str, Iterable[TopLevel]]] = [
719-
("attributes", module.attributes),
720-
("functions", module.functions),
721-
("classes", module.classes),
722-
("interfaces", module.interfaces),
723-
]
724732
pkgname = "".join(self._partial_path)
725733

726734
result: list[Node] = []
727-
for group_name, group_objects in pairs:
728-
n = nodes.container()
735+
for group_name in _SECTION_ORDER:
736+
group_objects = getattr(module, group_name)
729737
if not group_objects:
730738
continue
731-
n += self.format_heading(group_name.title() + ":")
739+
n = nodes.container()
740+
n += self.format_heading(group_name.replace("_", " ").title() + ":")
732741
table_items = self.get_summary_table(pkgname, group_objects)
733742
n += self.format_table(table_items)
734743
n["classes"] += ["jssummarytable", group_name]

sphinx_js/templates/attribute.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
{% import 'common.rst' as common %}
22

3+
{% if is_type_alias -%}
4+
.. js:typealias:: {{ name }}{{ type_params }}
5+
{%- else -%}
36
.. js:attribute:: {{ name }}{{ '?' if is_optional else '' }}
7+
{%- endif %}
8+
49

510
{{ common.deprecated(deprecated)|indent(3) }}
611

sphinx_js/typedoc.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ def _create_modules(self, ir_objects: Sequence[ir.TopLevel]) -> Iterable[ir.Modu
165165
"interface": "interfaces",
166166
"function": "functions",
167167
"attribute": "attributes",
168+
"typeAlias": "type_aliases",
168169
}
169170
for obj, path, kind in self._get_toplevel_objects(ir_objects):
170171
pathparts = path.split("/")
@@ -182,4 +183,5 @@ def _create_modules(self, ir_objects: Sequence[ir.TopLevel]) -> Iterable[ir.Modu
182183
mod.functions = sorted(mod.functions, key=attrgetter("name"))
183184
mod.classes = sorted(mod.classes, key=attrgetter("name"))
184185
mod.interfaces = sorted(mod.interfaces, key=attrgetter("name"))
186+
mod.type_aliases = sorted(mod.type_aliases, key=attrgetter("name"))
185187
return modules.values()

tests/test_build_ts/source/module.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ export interface I {}
5757
*/
5858
export let interfaceInstance: I = {};
5959

60+
/**
61+
* A super special type alias
62+
* @typeParam T The whatsit
63+
*/
64+
export type TestTypeAlias<T extends A> = { a: T };
65+
export type TestTypeAlias2 = { a: number };
66+
67+
export let t: TestTypeAlias<A>;
68+
export let t2: TestTypeAlias2;
69+
6070
/**
6171
* A function with a type parameter!
6272
*

tests/test_build_ts/test_build_ts.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,19 @@ def test_automodule(self):
249249
"automodule",
250250
dedent(
251251
"""\
252+
module.TestTypeAlias<T>
253+
254+
type: { a: T; }
255+
256+
A super special type alias
257+
258+
Type parameters:
259+
**T** -- The whatsit (extends "A")
260+
261+
module.TestTypeAlias2
262+
263+
type: { a: number; }
264+
252265
module.a
253266
254267
type: 7
@@ -273,6 +286,14 @@ def test_automodule(self):
273286
274287
Another thing.
275288
289+
module.t
290+
291+
type: "TestTypeAlias"<"A">
292+
293+
module.t2
294+
295+
type: "TestTypeAlias2"
296+
276297
module.zInstance
277298
278299
type: "Z"<"A">
@@ -309,6 +330,12 @@ def test_automodule(self):
309330
Returns:
310331
number
311332
333+
interface module.I
334+
335+
Documentation for the interface I
336+
337+
*exported from* "module"
338+
312339
class module.A()
313340
314341
This is a summary. This is more info.
@@ -347,12 +374,6 @@ class module.Z<T>(a, b)
347374
type: T
348375
349376
Z.z()
350-
351-
interface module.I
352-
353-
Documentation for the interface I
354-
355-
*exported from* "module"
356377
"""
357378
),
358379
)
@@ -434,7 +455,7 @@ def test_autosummary(self):
434455
soup = BeautifulSoup(self._file_contents("autosummary"), "html.parser")
435456
attrs = soup.find(class_="attributes")
436457
rows = list(attrs.find_all("tr"))
437-
assert len(rows) == 5
458+
assert len(rows) == 7
438459

439460
href = rows[0].find("a")
440461
assert href.get_text() == "a"
@@ -471,3 +492,7 @@ def test_autosummary(self):
471492
classes.find(class_="summary").get_text()
472493
== "Documentation for the interface I"
473494
)
495+
496+
classes = soup.find(class_="type_aliases")
497+
assert classes
498+
assert classes.find(class_="summary").get_text() == "A super special type alias"

0 commit comments

Comments
 (0)