From 1953d47ed21b2ff5392a89a7968e357e0103810a Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 26 Apr 2020 22:30:49 +0900 Subject: [PATCH] Close #7143: py domain: Add :final: option to py:*: directives --- CHANGES | 2 ++ doc/usage/restructuredtext/domains.rst | 25 +++++++++++++++ sphinx/domains/python.py | 13 +++++++- tests/test_domain_py.py | 44 +++++++++++++++++++++++++- 4 files changed, 82 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 30783aa6871..4d186626f92 100644 --- a/CHANGES +++ b/CHANGES @@ -59,6 +59,8 @@ Features added * #7542: html theme: Make admonition/topic/sidebar scrollable * C and C++: allow semicolon in the end of declarations. * C++, parse parameterized noexcept specifiers. +* #7143: py domain: Add ``:final:`` option to :rst:dir:`py:class:`, + :rst:dir:`py:exception:` and :rst:dir:`py:method:` directives Bugs fixed ---------- diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst index 7a89e94e206..9559acf2200 100644 --- a/doc/usage/restructuredtext/domains.rst +++ b/doc/usage/restructuredtext/domains.rst @@ -212,6 +212,15 @@ The following directives are provided for module and class contents: Describes an exception class. The signature can, but need not include parentheses with constructor arguments. + .. rubric:: options + + .. rst:directive:option:: final + :type: no value + + Indicate the class is a final class. + + .. versionadded:: 3.1 + .. rst:directive:: .. py:class:: name .. py:class:: name(parameters) @@ -235,6 +244,15 @@ The following directives are provided for module and class contents: The first way is the preferred one. + .. rubric:: options + + .. rst:directive:option:: final + :type: no value + + Indicate the class is a final class. + + .. versionadded:: 3.1 + .. rst:directive:: .. py:attribute:: name Describes an object data attribute. The description should include @@ -283,6 +301,13 @@ The following directives are provided for module and class contents: .. versionadded:: 2.1 + .. rst:directive:option:: final + :type: no value + + Indicate the class is a final method. + + .. versionadded:: 3.1 + .. rst:directive:option:: property :type: no value diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 52b670ad4af..99339ccbd1a 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -641,10 +641,18 @@ class PyClasslike(PyObject): Description of a class-like object (classes, interfaces, exceptions). """ + option_spec = PyObject.option_spec.copy() + option_spec.update({ + 'final': directives.flag, + }) + allow_nesting = True def get_signature_prefix(self, sig: str) -> str: - return self.objtype + ' ' + if 'final' in self.options: + return 'final %s ' % self.objtype + else: + return '%s ' % self.objtype def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: if self.objtype == 'class': @@ -749,6 +757,7 @@ class PyMethod(PyObject): 'abstractmethod': directives.flag, 'async': directives.flag, 'classmethod': directives.flag, + 'final': directives.flag, 'property': directives.flag, 'staticmethod': directives.flag, }) @@ -761,6 +770,8 @@ def needs_arglist(self) -> bool: def get_signature_prefix(self, sig: str) -> str: prefix = [] + if 'final' in self.options: + prefix.append('final') if 'abstractmethod' in self.options: prefix.append('abstract') if 'async' in self.options: diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index d9d61db1429..5a1d73cfe66 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -499,6 +499,34 @@ def test_pyfunction(app): assert domain.objects['example.func2'] == ('index', 'example.func2', 'function') +def test_pyclass_options(app): + text = (".. py:class:: Class1\n" + ".. py:class:: Class2\n" + " :final:\n") + domain = app.env.get_domain('py') + doctree = restructuredtext.parse(app, text) + assert_node(doctree, (addnodes.index, + [desc, ([desc_signature, ([desc_annotation, "class "], + [desc_name, "Class1"])], + [desc_content, ()])], + addnodes.index, + [desc, ([desc_signature, ([desc_annotation, "final class "], + [desc_name, "Class2"])], + [desc_content, ()])])) + + # class + assert_node(doctree[0], addnodes.index, + entries=[('single', 'Class1 (built-in class)', 'Class1', '', None)]) + assert 'Class1' in domain.objects + assert domain.objects['Class1'] == ('index', 'Class1', 'class') + + # :final: + assert_node(doctree[2], addnodes.index, + entries=[('single', 'Class2 (built-in class)', 'Class2', '', None)]) + assert 'Class2' in domain.objects + assert domain.objects['Class2'] == ('index', 'Class2', 'class') + + def test_pymethod_options(app): text = (".. py:class:: Class\n" "\n" @@ -512,7 +540,9 @@ def test_pymethod_options(app): " .. py:method:: meth5\n" " :property:\n" " .. py:method:: meth6\n" - " :abstractmethod:\n") + " :abstractmethod:\n" + " .. py:method:: meth7\n" + " :final:\n") domain = app.env.get_domain('py') doctree = restructuredtext.parse(app, text) assert_node(doctree, (addnodes.index, @@ -529,6 +559,8 @@ def test_pymethod_options(app): addnodes.index, desc, addnodes.index, + desc, + addnodes.index, desc)])])) # method @@ -589,6 +621,16 @@ def test_pymethod_options(app): assert 'Class.meth6' in domain.objects assert domain.objects['Class.meth6'] == ('index', 'Class.meth6', 'method') + # :final: + assert_node(doctree[1][1][12], addnodes.index, + entries=[('single', 'meth7() (Class method)', 'Class.meth7', '', None)]) + assert_node(doctree[1][1][13], ([desc_signature, ([desc_annotation, "final "], + [desc_name, "meth7"], + [desc_parameterlist, ()])], + [desc_content, ()])) + assert 'Class.meth7' in domain.objects + assert domain.objects['Class.meth7'] == ('index', 'Class.meth7', 'method') + def test_pyclassmethod(app): text = (".. py:class:: Class\n"