From 132f08b55fd6f305d0a67d0ee3b15275f0c5d139 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Sun, 24 Sep 2023 18:35:58 -0700 Subject: [PATCH] Add support for examples to typescript (#64) This modified the examples in a few ways: 1. One admonition per example. This is clearly necessary if the examples are allowed to have text and not just code pens. 2. Don't insert a codepen in the jinja template. Instead, a DescriptionCode gets a code pen and a DescriptionText or raw string does not. --- sphinx_js/ir.py | 2 +- sphinx_js/jsdoc.py | 6 +- sphinx_js/renderers.py | 8 +-- sphinx_js/templates/common.rst | 12 ++-- sphinx_js/typedoc.py | 4 +- tests/test_build_ts/source/class.ts | 10 +++ tests/test_build_ts/source/docs/example.rst | 1 + tests/test_build_ts/test_build_ts.py | 18 ++++++ tests/test_jsdoc_analysis/test_jsdoc.py | 22 ++++--- tests/test_renderers.py | 68 ++++++++++++++++----- 10 files changed, 110 insertions(+), 41 deletions(-) create mode 100644 tests/test_build_ts/source/docs/example.rst diff --git a/sphinx_js/ir.py b/sphinx_js/ir.py index c4513afe..78f481a8 100644 --- a/sphinx_js/ir.py +++ b/sphinx_js/ir.py @@ -208,7 +208,7 @@ class TopLevel: #: Explanation of the deprecation (which implies True) or True or False deprecated: Description | bool #: List of preformatted textual examples - examples: list[str] + examples: Sequence[Description] #: List of paths to also refer the reader to see_alsos: list[str] #: Explicitly documented sub-properties of the object, a la jsdoc's diff --git a/sphinx_js/jsdoc.py b/sphinx_js/jsdoc.py index 9100b0a8..f78d401f 100644 --- a/sphinx_js/jsdoc.py +++ b/sphinx_js/jsdoc.py @@ -27,6 +27,7 @@ NO_DEFAULT, Attribute, Class, + DescriptionCode, Exc, Function, Param, @@ -378,7 +379,10 @@ def top_level_properties( # class, so it gets filled out elsewhere. line=doclet["meta"]["lineno"], deprecated=doclet.get("deprecated", False), - examples=doclet.get("examples", []), + examples=[ + [DescriptionCode("```js\n" + x + "\n```")] + for x in doclet.get("examples", []) + ], see_alsos=doclet.get("see", []), properties=properties_to_ir(doclet.get("properties", [])), ) diff --git a/sphinx_js/renderers.py b/sphinx_js/renderers.py index ca0e9c44..fa543c4f 100644 --- a/sphinx_js/renderers.py +++ b/sphinx_js/renderers.py @@ -242,7 +242,7 @@ def render_description(self, description: Description) -> str: start = f".. code-block:: {code_type}\n\n" codeblock = textwrap.indent(mid, " " * 4) end = "\n\n" - content.append(start + codeblock + end) + content.append("\n" + start + codeblock + end) # A code pen continue @@ -389,7 +389,7 @@ def _template_vars(self, name: str, obj: Function) -> dict[str, Any]: # type: i params=self._formal_params(obj), fields=self._fields(obj), description=self.render_description(obj.description), - examples=obj.examples, + examples=[self.render_description(x) for x in obj.examples], deprecated=deprecated, is_optional=obj.is_optional, is_static=obj.is_static, @@ -437,7 +437,7 @@ def _template_vars(self, name: str, obj: Class | Interface) -> dict[str, Any]: name=name, params=self._formal_params(constructor), fields=self._fields(constructor), - examples=constructor.examples, + examples=[self.render_description(ex) for ex in constructor.examples], deprecated=constructor.deprecated, see_also=constructor.see_alsos, exported_from=obj.exported_from, @@ -552,7 +552,7 @@ def _template_vars(self, name: str, obj: Attribute) -> dict[str, Any]: # type: deprecated=obj.deprecated, is_optional=obj.is_optional, see_also=obj.see_alsos, - examples=obj.examples, + examples=[self.render_description(ex) for ex in obj.examples], type=self.render_type(obj.type), content="\n".join(self._content), ) diff --git a/sphinx_js/templates/common.rst b/sphinx_js/templates/common.rst index 5402e18b..34086492 100644 --- a/sphinx_js/templates/common.rst +++ b/sphinx_js/templates/common.rst @@ -8,16 +8,12 @@ {% endmacro %} {% macro examples(items) %} -{% if items -%} -.. admonition:: Example {%- if items|length > 1 -%} s {%- endif %} - - {% for example in items -%} - .. code-block:: js +{% for example in items %} - {{ example|indent(6) }} +.. admonition:: Example - {% endfor %} -{%- endif %} + {{ example|indent(3) }} +{% endfor %} {% endmacro %} {% macro see_also(items) %} diff --git a/sphinx_js/typedoc.py b/sphinx_js/typedoc.py index df34c6ac..86a7ef72 100644 --- a/sphinx_js/typedoc.py +++ b/sphinx_js/typedoc.py @@ -376,7 +376,7 @@ class TopLevelPropertiesDict(TypedDict): description: Sequence[ir.DescriptionItem] line: int | None deprecated: Sequence[ir.DescriptionItem] | bool - examples: list[str] + examples: Sequence[ir.Description] see_alsos: list[str] properties: list[ir.Attribute] exported_from: ir.Pathname | None @@ -403,7 +403,7 @@ def _top_level_properties(self) -> TopLevelPropertiesDict: line=self.sources[0].line if self.sources else None, # These properties aren't supported by TypeDoc: deprecated=deprecated, - examples=[], + examples=self.comment.get_tag_list("example"), see_alsos=[], properties=[], exported_from=ir.Pathname(make_filepath_segments(self.filename)), diff --git a/tests/test_build_ts/source/class.ts b/tests/test_build_ts/source/class.ts index 13e90f03..a2088559 100644 --- a/tests/test_build_ts/source/class.ts +++ b/tests/test_build_ts/source/class.ts @@ -78,3 +78,13 @@ export function selfReferential(b: typeof selfReferential) {} * @deprecated since v20! */ export function deprecatedFunction() {} + + +/** + * @example This is an example. + * @example This is another example. + * ```py + * Something python + * ``` + */ +export function exampleFunction() {} diff --git a/tests/test_build_ts/source/docs/example.rst b/tests/test_build_ts/source/docs/example.rst new file mode 100644 index 00000000..6ae2106b --- /dev/null +++ b/tests/test_build_ts/source/docs/example.rst @@ -0,0 +1 @@ +.. js:autofunction:: exampleFunction diff --git a/tests/test_build_ts/test_build_ts.py b/tests/test_build_ts/test_build_ts.py index 97d32900..5e91c1cd 100644 --- a/tests/test_build_ts/test_build_ts.py +++ b/tests/test_build_ts/test_build_ts.py @@ -120,6 +120,24 @@ def test_deprecated(self): ), ) + def test_example(self): + self._file_contents_eq( + "example", + dedent( + """\ + exampleFunction() + + Example: + + This is an example. + + Example: This is another example. + + Something python + """ + ), + ) + class HtmlBuilderTests(SphinxBuildTestCase): """Tests which require an HTML build of our Sphinx tree, for checking diff --git a/tests/test_jsdoc_analysis/test_jsdoc.py b/tests/test_jsdoc_analysis/test_jsdoc.py index 68ae88a5..a3371241 100644 --- a/tests/test_jsdoc_analysis/test_jsdoc.py +++ b/tests/test_jsdoc_analysis/test_jsdoc.py @@ -1,4 +1,12 @@ -from sphinx_js.ir import Attribute, Exc, Function, Param, Pathname, Return +from sphinx_js.ir import ( + Attribute, + DescriptionCode, + Exc, + Function, + Param, + Pathname, + Return, +) from sphinx_js.jsdoc import full_path_segments from tests.testing import JsDocTestCase @@ -105,10 +113,7 @@ def test_class(self): 15, # jsdoc 3.6.3 ) # We ignore examples and other fields from the class doclet so far. This could change someday. - assert cls.examples in ( - ["Example in class"], # jsdoc, 4.0.0 - ["Example in constructor"], # jsdoc 3.6.3 - ) + assert cls.examples == [[DescriptionCode(code="```js\nExample in class\n```")]] # Members: getter, private_method = cls.members # default constructor not included here @@ -131,10 +136,9 @@ def test_class(self): ) # Same path as class. This might differ in different languages. assert constructor.filename == "class.js" assert constructor.description == "Constructor doc." - assert constructor.examples in ( - ["Example in class"], # jsdoc 4.0.0 - ["Example in constructor"], # jsdoc 3.6.3 - ) + assert constructor.examples == [ + [DescriptionCode(code="```js\nExample in class\n```")] + ] assert constructor.params == [ Param( name="ho", diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 9d6fda79..68b259d9 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -39,6 +39,7 @@ def test_render_description(): Code 2 has ``double ticks around it``. Code 3 has a :sphinx:role:`before it`. + .. code-block:: js A JS code pen! @@ -279,19 +280,6 @@ def test_func_render_callouts(function_render): Deprecated: v0.24 """, ) - assert function_render(examples=["ex1", "ex2"]) == DEFAULT_RESULT + setindent( - """ - .. admonition:: Examples - - .. code-block:: js - - ex1 - - .. code-block:: js - - ex2 - """, - ) assert function_render(see_alsos=["see", "this too"]) == DEFAULT_RESULT + setindent( """ .. seealso:: @@ -325,12 +313,60 @@ def test_all(function_render): .. admonition:: Example - .. code-block:: js - - ex1 + ex1 .. seealso:: - :any:`see` """ ) + + +def test_examples(function_render): + assert function_render(examples=["ex1", "ex2"]) == DEFAULT_RESULT + setindent( + """ + .. admonition:: Example + + ex1 + + .. admonition:: Example + + ex2 + """, + ) + + assert function_render( + examples=[[DescriptionText(text="This is another example.\n")]] + ) == DEFAULT_RESULT + setindent( + """ + .. admonition:: Example + + This is another example. + """ + ) + + assert function_render( + examples=[ + [DescriptionCode(code="```ts\nThis is an example.\n```")], + [ + DescriptionText(text="This is another example.\n"), + DescriptionCode(code="```py\nSomething python\n```"), + ], + ] + ) == DEFAULT_RESULT + setindent( + """ + .. admonition:: Example + + .. code-block:: ts + + This is an example. + + .. admonition:: Example + + This is another example. + + .. code-block:: py + + Something python + """ + )