Skip to content

Commit

Permalink
Add documentation entries for type aliases (#156)
Browse files Browse the repository at this point in the history
Add a TypeAlias ir entry, repurpose Attribute renderer to also render type
aliases, add a type alias directive and xref role.
  • Loading branch information
hoodmane authored May 6, 2024
1 parent fb88024 commit 2b35f7c
Show file tree
Hide file tree
Showing 13 changed files with 196 additions and 33 deletions.
17 changes: 17 additions & 0 deletions sphinx_js/directives.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,20 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
return handle_typeparams_for_signature(self, sig, signode, keep_callsig=False)


class JSTypeAlias(JSObject):
doc_field_types = [
JSGroupedField(
"typeparam",
label="Type parameters",
names=("typeparam",),
can_collapse=True,
)
]

def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
return handle_typeparams_for_signature(self, sig, signode, keep_callsig=False)


class JSClass(JSConstructor):
def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
return handle_typeparams_for_signature(self, sig, signode, keep_callsig=True)
Expand Down Expand Up @@ -438,6 +452,9 @@ def add_directives(app: Sphinx) -> None:
JavaScriptDomain.object_types["interface"] = ObjType(_("interface"), "interface")
app.add_directive_to_domain("js", "interface", JSInterface)
app.add_role_to_domain("js", "interface", JSXRefRole())
JavaScriptDomain.object_types["typealias"] = ObjType(_("type alias"), "typealias")
app.add_directive_to_domain("js", "typealias", JSTypeAlias)
app.add_role_to_domain("js", "typealias", JSXRefRole())
app.add_node(
desc_js_type_parameter_list,
html=(visit_desc_js_type_parameter_list, depart_desc_js_type_parameter_list),
Expand Down
10 changes: 9 additions & 1 deletion sphinx_js/ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ class Module:
functions: list["Function"] = Factory(list)
classes: list["Class"] = Factory(list)
interfaces: list["Interface"] = Factory(list)
type_aliases: list["TypeAlias"] = Factory(list)


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


TopLevelUnion = Class | Interface | Function | Attribute
@define
class TypeAlias(TopLevel):
type: Type
type_params: list[TypeParam] = Factory(list)
kind: Literal["typeAlias"] = "typeAlias"


TopLevelUnion = Class | Interface | Function | Attribute | TypeAlias

# Now make a serializer/deserializer.
# TODO: Add tests to make sure that serialization and deserialization are a
Expand Down
14 changes: 11 additions & 3 deletions sphinx_js/js/convertTopLevel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,8 +421,6 @@ export class Converter {
object.kindOf(
ReflectionKind.Module |
ReflectionKind.Namespace |
// TODO: document TypeAliases
ReflectionKind.TypeAlias |
// TODO: document enums
ReflectionKind.Enum |
ReflectionKind.EnumMember |
Expand All @@ -436,7 +434,7 @@ export class Converter {
// be too?
return [undefined, (object as DeclarationReflection).children];
}
const kind = ReflectionKind.singularString(object.kind);
const kind = ReflectionKind[object.kind];
const convertFunc = `convert${kind}` as keyof this;
if (!this[convertFunc]) {
throw new Error(`No known converter for kind ${kind}`);
Expand Down Expand Up @@ -875,4 +873,14 @@ export class Converter {
description: renderCommentSummary(typeParam.comment),
};
}

convertTypeAlias(ty: DeclarationReflection): ConvertResult {
const ir: TopLevelIR = {
...this.topLevelProperties(ty),
kind: "typeAlias",
type: this.convertType(ty.type!),
type_params: this.typeParamsToIR(ty.typeParameters),
};
return [ir, ty.children];
}
}
8 changes: 7 additions & 1 deletion sphinx_js/js/ir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,10 @@ export type Class = TopLevel &
kind: "class";
};

export type TopLevelIR = Attribute | IRFunction | Class | Interface;
export type TypeAlias = TopLevel & {
kind: "typeAlias";
type: Type;
type_params: TypeParam[];
};

export type TopLevelIR = Attribute | IRFunction | Class | Interface | TypeAlias;
43 changes: 26 additions & 17 deletions sphinx_js/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
Return,
TopLevel,
Type,
TypeAlias,
TypeParam,
TypeXRef,
TypeXRefInternal,
Expand Down Expand Up @@ -337,7 +338,7 @@ def rst_nodes(self) -> list[Node]:
def rst_for(self, obj: TopLevel) -> str:
renderer_class: type
match obj:
case Attribute(_):
case Attribute(_) | TypeAlias(_):
renderer_class = AutoAttributeRenderer
case Function(_):
renderer_class = AutoFunctionRenderer
Expand Down Expand Up @@ -374,7 +375,7 @@ def rst(
result = "\n".join(lines) + "\n"
return result

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

def _template_vars(self, name: str, obj: Attribute) -> dict[str, Any]: # type: ignore[override]
def _template_vars(self, name: str, obj: Attribute | TypeAlias) -> dict[str, Any]: # type: ignore[override]
is_optional = False
if isinstance(obj, Attribute):
is_optional = obj.is_optional
type_params = ""
is_type_alias = isinstance(obj, TypeAlias)
fields: Iterator[tuple[list[str], str]] = iter([])
if isinstance(obj, TypeAlias):
type_params = self._type_params(obj)
fields = self._fields(obj)
return dict(
name=name,
is_type_alias=is_type_alias,
type_params=type_params,
fields=fields,
description=render_description(obj.description),
deprecated=obj.deprecated,
is_optional=obj.is_optional,
is_optional=is_optional,
see_also=obj.see_alsos,
examples=[render_description(ex) for ex in obj.examples],
type=self.render_type(obj.type),
content="\n".join(self._content),
)


_SECTION_ORDER = ["type_aliases", "attributes", "functions", "interfaces", "classes"]


class AutoModuleRenderer(JsRenderer):
def _parse_path(self, arg: str) -> None:
# content, arguments, options, app: all need to be accessible to
Expand All @@ -692,10 +708,8 @@ def rst( # type:ignore[override]
) -> str:
rst: list[Sequence[str]] = []
rst.append([f".. js:module:: {''.join(partial_path)}"])
rst.append(self.rst_for_group(obj.attributes))
rst.append(self.rst_for_group(obj.functions))
rst.append(self.rst_for_group(obj.classes))
rst.append(self.rst_for_group(obj.interfaces))
for group_name in _SECTION_ORDER:
rst.append(self.rst_for_group(getattr(obj, group_name)))
return "\n\n".join(["\n\n".join(r) for r in rst])


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

def rst_nodes(self) -> list[Node]:
module = self.get_object()
pairs: list[tuple[str, Iterable[TopLevel]]] = [
("attributes", module.attributes),
("functions", module.functions),
("classes", module.classes),
("interfaces", module.interfaces),
]
pkgname = "".join(self._partial_path)

result: list[Node] = []
for group_name, group_objects in pairs:
n = nodes.container()
for group_name in _SECTION_ORDER:
group_objects = getattr(module, group_name)
if not group_objects:
continue
n += self.format_heading(group_name.title() + ":")
n = nodes.container()
n += self.format_heading(group_name.replace("_", " ").title() + ":")
table_items = self.get_summary_table(pkgname, group_objects)
n += self.format_table(table_items)
n["classes"] += ["jssummarytable", group_name]
Expand Down
5 changes: 5 additions & 0 deletions sphinx_js/templates/attribute.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{% import 'common.rst' as common %}

{% if is_type_alias -%}
.. js:typealias:: {{ name }}{{ type_params }}
{%- else -%}
.. js:attribute:: {{ name }}{{ '?' if is_optional else '' }}
{%- endif %}


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

Expand Down
2 changes: 2 additions & 0 deletions sphinx_js/typedoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ def _create_modules(self, ir_objects: Sequence[ir.TopLevel]) -> Iterable[ir.Modu
"interface": "interfaces",
"function": "functions",
"attribute": "attributes",
"typeAlias": "type_aliases",
}
for obj, path, kind in self._get_toplevel_objects(ir_objects):
pathparts = path.split("/")
Expand All @@ -182,4 +183,5 @@ def _create_modules(self, ir_objects: Sequence[ir.TopLevel]) -> Iterable[ir.Modu
mod.functions = sorted(mod.functions, key=attrgetter("name"))
mod.classes = sorted(mod.classes, key=attrgetter("name"))
mod.interfaces = sorted(mod.interfaces, key=attrgetter("name"))
mod.type_aliases = sorted(mod.type_aliases, key=attrgetter("name"))
return modules.values()
10 changes: 10 additions & 0 deletions tests/test_build_ts/source/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ export interface I {}
*/
export let interfaceInstance: I = {};

/**
* A super special type alias
* @typeParam T The whatsit
*/
export type TestTypeAlias<T extends A> = { a: T };
export type TestTypeAlias2 = { a: number };

export let t: TestTypeAlias<A>;
export let t2: TestTypeAlias2;

/**
* A function with a type parameter!
*
Expand Down
39 changes: 32 additions & 7 deletions tests/test_build_ts/test_build_ts.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,19 @@ def test_automodule(self):
"automodule",
dedent(
"""\
module.TestTypeAlias<T>
type: { a: T; }
A super special type alias
Type parameters:
**T** -- The whatsit (extends "A")
module.TestTypeAlias2
type: { a: number; }
module.a
type: 7
Expand All @@ -273,6 +286,14 @@ def test_automodule(self):
Another thing.
module.t
type: "TestTypeAlias"<"A">
module.t2
type: "TestTypeAlias2"
module.zInstance
type: "Z"<"A">
Expand Down Expand Up @@ -309,6 +330,12 @@ def test_automodule(self):
Returns:
number
interface module.I
Documentation for the interface I
*exported from* "module"
class module.A()
This is a summary. This is more info.
Expand Down Expand Up @@ -347,12 +374,6 @@ class module.Z<T>(a, b)
type: T
Z.z()
interface module.I
Documentation for the interface I
*exported from* "module"
"""
),
)
Expand Down Expand Up @@ -434,7 +455,7 @@ def test_autosummary(self):
soup = BeautifulSoup(self._file_contents("autosummary"), "html.parser")
attrs = soup.find(class_="attributes")
rows = list(attrs.find_all("tr"))
assert len(rows) == 5
assert len(rows) == 7

href = rows[0].find("a")
assert href.get_text() == "a"
Expand Down Expand Up @@ -471,3 +492,7 @@ def test_autosummary(self):
classes.find(class_="summary").get_text()
== "Documentation for the interface I"
)

classes = soup.find(class_="type_aliases")
assert classes
assert classes.find(class_="summary").get_text() == "A super special type alias"
Loading

0 comments on commit 2b35f7c

Please sign in to comment.