Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add documentation entries for type aliases #156

Merged
merged 4 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading