From 3647633225103d202112d6cbc5c6ae0608f46425 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Sat, 26 Aug 2023 19:23:20 +0200 Subject: [PATCH] Add support for typedoc 0.23 --- .github/workflows/ci.yml | 2 +- noxfile.py | 2 +- sphinx_js/typedoc.py | 69 ++++++++++++++++--- tests/conftest.py | 8 +++ tests/test_build_ts/test_build_ts.py | 9 ++- .../test_typedoc_analysis.py | 9 ++- 6 files changed, 82 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9dfdc81a..8aaf6410 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,7 @@ jobs: fail-fast: false matrix: python-version: ['3.10', '3.11'] - typedoc-version: ['0.20', '0.21', '0.22'] + typedoc-version: ['0.20', '0.21', '0.22', '0.23'] experimental: [false] name: Python ${{ matrix.python-version}} + typedoc ${{ matrix.typedoc-version }} diff --git a/noxfile.py b/noxfile.py index df486051..2934fcdc 100644 --- a/noxfile.py +++ b/noxfile.py @@ -12,7 +12,7 @@ def tests(session: Session) -> None: @nox.session(python=["3.10", "3.11"]) -@nox.parametrize("typedoc", ["0.20", "0.21", "0.22"]) +@nox.parametrize("typedoc", ["0.20", "0.21", "0.22", "0.23"]) def test_typedoc(session: Session, typedoc: str) -> None: session.install("-r", "requirements_dev.txt") diff --git a/sphinx_js/typedoc.py b/sphinx_js/typedoc.py index cb15985f..431e1a10 100644 --- a/sphinx_js/typedoc.py +++ b/sphinx_js/typedoc.py @@ -120,8 +120,20 @@ def _populate_index_inner( children.append(node.children) if isinstance(node, Accessor): - children.append(node.getSignature) - children.append(node.setSignature) + if node.getSignature: + if isinstance(node.getSignature, list): + sig = node.getSignature[0] + else: + sig = node.getSignature + node.getSignature = sig + children.append([sig]) + if node.setSignature: + if isinstance(node.setSignature, list): + sig = node.setSignature[0] + else: + sig = node.setSignature + node.setSignature = sig + children.append([sig]) if isinstance(node, Callable): children.append(node.signatures) @@ -129,9 +141,11 @@ def _populate_index_inner( if isinstance(node, Signature): children.append(node.parameters) children.append(node.typeParameter) + children.append(node.typeParameters) if isinstance(node, ClassOrInterface): children.append(node.typeParameter) + children.append(node.typeParameters) for child in (c for l in children for c in l): self._populate_index_inner( @@ -253,10 +267,31 @@ class Source(BaseModel): line: int +class Summary(BaseModel): + kind: Literal["text"] + text: str + + +class Tag(BaseModel): + tag: str + content: list[Summary] + + class Comment(BaseModel): returns: str = "" shortText: str | None text: str | None + summary: list[Summary] | None + blockTags: list[Tag] = [] + + def get_returns(self) -> str: + result = self.returns.strip() + if result: + return result + for tag in self.blockTags: + if tag.tag == "@returns": + return tag.content[0].text.strip() + return "" class Flags(BaseModel): @@ -359,17 +394,17 @@ def _path_segments(self, base_dir: str) -> list[str]: class Accessor(NodeBase): kindString: Literal["Accessor"] - getSignature: list["Signature"] = [] - setSignature: list["Signature"] = [] + getSignature: "list[Signature] | Signature" = [] + setSignature: "list[Signature] | Signature" = [] def to_ir(self, converter: Converter) -> tuple[ir.Attribute, Sequence["Node"]]: if self.getSignature: # There's no signature to speak of for a getter: only a return type. - type = self.getSignature[0].type + type = self.getSignature.type # type: ignore[union-attr] else: # ES6 says setters have exactly 1 param. I'm not sure if they # can have multiple signatures, though. - type = self.setSignature[0].parameters[0].type + type = self.setSignature.parameters[0].type # type: ignore[union-attr] res = ir.Attribute( type=type.render_name(converter), **self.member_properties(), @@ -410,6 +445,7 @@ class ClassOrInterface(NodeBase): implementedTypes: list["TypeD"] = [] children: Sequence["ClassChild"] = [] typeParameter: list["TypeParameter"] = [] + typeParameters: list["TypeParameter"] = [] def _related_types( self, @@ -466,7 +502,9 @@ def _constructor_and_members( # This really, really should happen exactly once per class. # Type parameter cannot appear on constructor declaration so copy # it down from the class. - child.signatures[0].typeParameter = self.typeParameter + child.signatures[0].typeParameter = ( + self.typeParameter or self.typeParameters + ) constructor = child.to_ir(converter)[0] continue result = child.to_ir(converter)[0] @@ -486,7 +524,9 @@ def to_ir(self, converter: Converter) -> tuple[ir.Class | None, Sequence["Node"] supers=self._related_types(converter, kind="extendedTypes"), is_abstract=self.flags.isAbstract, interfaces=self._related_types(converter, kind="implementedTypes"), - type_params=[x.to_ir(converter) for x in self.typeParameter], + type_params=[ + x.to_ir(converter) for x in (self.typeParameter or self.typeParameters) + ], **self._top_level_properties(), ) return result, self.children @@ -569,7 +609,10 @@ class OtherNode(NodeBase): def make_description(comment: Comment) -> str: """Construct a single comment string from a fancy object.""" - ret = "\n\n".join(text for text in [comment.shortText, comment.text] if text) + if comment.summary: + ret = comment.summary[0].text + else: + ret = "\n\n".join(text for text in [comment.shortText, comment.text] if text) return ret.strip() @@ -625,6 +668,7 @@ class Signature(TopLevelProperties): name: str typeParameter: list[TypeParameter] = [] + typeParameters: list[TypeParameter] = [] parameters: list["Param"] = [] sources: list[Source] = [] type: "TypeD" # This is the return type! @@ -644,10 +688,11 @@ def return_type(self, converter: Converter) -> list[ir.Return]: if self.type.type == "intrinsic" and self.type.name == "void": # Returns nothing return [] + return [ ir.Return( type=self.type.render_name(converter), - description=self.comment.returns.strip(), + description=self.comment.get_returns(), ) ] @@ -669,7 +714,9 @@ def to_ir( exceptions=[], # Though perhaps technically true, it looks weird to the user # (and in the template) if constructors have a return value: - type_params=[x.to_ir(converter) for x in self.typeParameter], + type_params=[ + x.to_ir(converter) for x in (self.typeParameter or self.typeParameters) + ], returns=self.return_type(converter) if self.kindString != "Constructor signature" else [], diff --git a/tests/conftest.py b/tests/conftest.py index 0c45c08a..748f5690 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,8 @@ import sys from pathlib import Path +sys.path.append(str(Path(__file__).parent)) + import pytest from sphinx.testing.path import path @@ -14,6 +16,12 @@ else: raise RuntimeError("Couldn't find node_modules") +from sphinx_js.analyzer_utils import search_node_modules +from sphinx_js.typedoc import typedoc_version_info + +TYPEDOC = search_node_modules("typedoc", "typedoc/bin/typedoc", "") +TYPEDOC_VERSION = typedoc_version_info(TYPEDOC)[0] + pytest_plugins = "sphinx.testing.fixtures" diff --git a/tests/test_build_ts/test_build_ts.py b/tests/test_build_ts/test_build_ts.py index 37f77ca4..e29fd28f 100644 --- a/tests/test_build_ts/test_build_ts.py +++ b/tests/test_build_ts/test_build_ts.py @@ -1,3 +1,5 @@ +from conftest import TYPEDOC_VERSION + from tests.testing import SphinxBuildTestCase @@ -40,14 +42,17 @@ def test_abstract_extends_and_implements(self): These are all TypeScript-specific features. """ + CLASS_COMMENT = ( + " A definition of a class\n\n" if TYPEDOC_VERSION >= (0, 23, 0) else "" + ) + # The quotes around ClassDefinition() must be some weird decision in # Sphinx's text output. I don't care if they go away in a future # version of Sphinx. It doesn't affect the HTML output. self._file_contents_eq( "autoclass_class_with_interface_and_supers", "class ClassWithSupersAndInterfacesAndAbstract()\n" - "\n" - " *abstract*\n" + "\n" + CLASS_COMMENT + " *abstract*\n" "\n" ' *exported from* "class"\n' "\n" diff --git a/tests/test_typedoc_analysis/test_typedoc_analysis.py b/tests/test_typedoc_analysis/test_typedoc_analysis.py index a4a6b0df..3216a6d7 100644 --- a/tests/test_typedoc_analysis/test_typedoc_analysis.py +++ b/tests/test_typedoc_analysis/test_typedoc_analysis.py @@ -2,10 +2,11 @@ from unittest import TestCase import pytest +from conftest import TYPEDOC_VERSION from sphinx_js.ir import Attribute, Class, Function, Param, Pathname, Return, TypeParam from sphinx_js.renderers import AutoClassRenderer, AutoFunctionRenderer -from sphinx_js.typedoc import Comment, Converter, parse +from sphinx_js.typedoc import Comment, Converter, Summary, parse from tests.testing import NO_MATCH, TypeDocAnalyzerTestCase, TypeDocTestCase, dict_where @@ -105,7 +106,11 @@ class PathSegmentsTests(TypeDocTestCase): def commented_object(self, comment, **kwargs): """Return the object from ``json`` having the given comment short-text.""" - return dict_where(self.json, comment=Comment(shortText=comment), **kwargs) + if TYPEDOC_VERSION >= (0, 23, 0): + comment = Comment(summary=[Summary(kind="text", text=comment)]) + else: + comment = Comment(shortText=comment) + return dict_where(self.json, comment=comment, **kwargs) def commented_object_path(self, comment, **kwargs): """Return the path segments of the object with the given comment."""