diff --git a/CHANGES b/CHANGES index e808cdfde..4c6e4e38d 100644 --- a/CHANGES +++ b/CHANGES @@ -38,12 +38,21 @@ Deprecated Features added -------------- +* #8095: napoleon: Add :confval:`napoleon_preprocess_types` to enable the type + preprocessor for numpy style docstrings +* #8114: C and C++, parse function attributes after parameters and qualifiers. + Bugs fixed ---------- * #8074: napoleon: Crashes during processing C-ext module +* #8088: napoleon: "Inline literal start-string without end-string" warning in + Numpy style Parameters section * #8084: autodoc: KeyError is raised on documenting an attribute of the broken class +* #8091: autodoc: AttributeError is raised on documenting an attribute on Python + 3.5.2 +* C++, fix parsing of template template paramters, broken by the fix of #7944 Testing -------- diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 65786b5de..d8ccc2e3d 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -32,7 +32,7 @@ from sphinx.transforms import SphinxTransform from sphinx.transforms.post_transforms import ReferencesResolver from sphinx.util import logging from sphinx.util.cfamily import ( - NoOldIdError, ASTBaseBase, ASTBaseParenExprList, + NoOldIdError, ASTBaseBase, ASTAttribute, ASTBaseParenExprList, verify_description_mode, StringifyTransform, BaseParser, DefinitionError, UnsupportedMultiCharacterCharLiteral, identifier_re, anon_identifier_re, integer_literal_re, octal_literal_re, @@ -652,8 +652,9 @@ class ASTFunctionParameter(ASTBase): class ASTParameters(ASTBase): - def __init__(self, args: List[ASTFunctionParameter]) -> None: + def __init__(self, args: List[ASTFunctionParameter], attrs: List[ASTAttribute]) -> None: self.args = args + self.attrs = attrs @property def function_params(self) -> List[ASTFunctionParameter]: @@ -669,6 +670,9 @@ class ASTParameters(ASTBase): first = False res.append(str(a)) res.append(')') + for attr in self.attrs: + res.append(' ') + res.append(transform(attr)) return ''.join(res) def describe_signature(self, signode: TextElement, mode: str, @@ -683,6 +687,9 @@ class ASTParameters(ASTBase): arg.describe_signature(param, 'markType', env, symbol=symbol) paramlist += param signode += paramlist + for attr in self.attrs: + signode += nodes.Text(' ') + attr.describe_signature(signode) class ASTDeclSpecsSimple(ASTBaseBase): @@ -2572,7 +2579,15 @@ class DefinitionParser(BaseParser): self.fail( 'Expecting "," or ")" in parameters, ' 'got "%s".' % self.current_char) - return ASTParameters(args) + + attrs = [] + while True: + attr = self._parse_attribute() + if attr is None: + break + attrs.append(attr) + + return ASTParameters(args, attrs) def _parse_decl_specs_simple(self, outer: str, typed: bool) -> ASTDeclSpecsSimple: """Just parse the simple ones.""" diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 46708b846..92d578427 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -1879,7 +1879,8 @@ class ASTNoexceptSpec(ASTBase): class ASTParametersQualifiers(ASTBase): def __init__(self, args: List[ASTFunctionParameter], volatile: bool, const: bool, refQual: str, exceptionSpec: ASTNoexceptSpec, trailingReturn: "ASTType", - override: bool, final: bool, initializer: str) -> None: + override: bool, final: bool, attrs: List[ASTAttribute], + initializer: str) -> None: self.args = args self.volatile = volatile self.const = const @@ -1888,6 +1889,7 @@ class ASTParametersQualifiers(ASTBase): self.trailingReturn = trailingReturn self.override = override self.final = final + self.attrs = attrs self.initializer = initializer @property @@ -1947,6 +1949,9 @@ class ASTParametersQualifiers(ASTBase): res.append(' final') if self.override: res.append(' override') + for attr in self.attrs: + res.append(' ') + res.append(transform(attr)) if self.initializer: res.append(' = ') res.append(self.initializer) @@ -1988,6 +1993,9 @@ class ASTParametersQualifiers(ASTBase): _add_anno(signode, 'final') if self.override: _add_anno(signode, 'override') + for attr in self.attrs: + signode += nodes.Text(' ') + attr.describe_signature(signode) if self.initializer: _add_text(signode, '= ' + str(self.initializer)) @@ -5709,6 +5717,13 @@ class DefinitionParser(BaseParser): override = self.skip_word_and_ws( 'override') # they can be permuted + attrs = [] + while True: + attr = self._parse_attribute() + if attr is None: + break + attrs.append(attr) + self.skip_ws() initializer = None if self.skip_string('='): @@ -5725,7 +5740,7 @@ class DefinitionParser(BaseParser): return ASTParametersQualifiers( args, volatile, const, refQual, exceptionSpec, trailingReturn, - override, final, initializer) + override, final, attrs, initializer) def _parse_decl_specs_simple(self, outer: str, typed: bool) -> ASTDeclSpecsSimple: """Just parse the simple ones.""" @@ -6251,6 +6266,7 @@ class DefinitionParser(BaseParser): # ========================================================================== def _parse_template_paramter(self) -> ASTTemplateParam: + self.skip_ws() if self.skip_word('template'): # declare a tenplate template parameter nestedParams = self._parse_template_parameter_list() diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 47f6bcb25..f3820f715 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1613,6 +1613,9 @@ class DataDocumenter(ModuleLevelDocumenter): except KeyError: # a broken class found (refs: https://github.com/sphinx-doc/sphinx/issues/8084) annotations = {} + except AttributeError: + # AttributeError is raised on 3.5.2 (fixed by 3.5.3) + annotations = {} if self.objpath[-1] in annotations: objrepr = stringify_typehint(annotations.get(self.objpath[-1])) @@ -1986,6 +1989,9 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): except KeyError: # a broken class found (refs: https://github.com/sphinx-doc/sphinx/issues/8084) annotations = {} + except AttributeError: + # AttributeError is raised on 3.5.2 (fixed by 3.5.3) + annotations = {} if self.objpath[-1] in annotations: objrepr = stringify_typehint(annotations.get(self.objpath[-1])) diff --git a/sphinx/ext/napoleon/__init__.py b/sphinx/ext/napoleon/__init__.py index 6cab63c9f..e62ef7152 100644 --- a/sphinx/ext/napoleon/__init__.py +++ b/sphinx/ext/napoleon/__init__.py @@ -41,6 +41,7 @@ class Config: napoleon_use_param = True napoleon_use_rtype = True napoleon_use_keyword = True + napoleon_preprocess_types = False napoleon_type_aliases = None napoleon_custom_sections = None @@ -237,9 +238,12 @@ class Config: :returns: *bool* -- True if successful, False otherwise + napoleon_preprocess_types : :obj:`bool` (Defaults to False) + Enable the type preprocessor for numpy style docstrings. + napoleon_type_aliases : :obj:`dict` (Defaults to None) Add a mapping of strings to string, translating types in numpy - style docstrings. + style docstrings. Only works if ``napoleon_preprocess_types = True``. napoleon_custom_sections : :obj:`list` (Defaults to None) Add a list of custom sections to include, expanding the list of parsed sections. @@ -268,6 +272,7 @@ class Config: 'napoleon_use_param': (True, 'env'), 'napoleon_use_rtype': (True, 'env'), 'napoleon_use_keyword': (True, 'env'), + 'napoleon_preprocess_types': (False, 'env'), 'napoleon_type_aliases': (None, 'env'), 'napoleon_custom_sections': (None, 'env') } diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index 29799cb06..4397d0e41 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -1104,11 +1104,12 @@ class NumpyDocstring(GoogleDocstring): _name, _type = line, '' _name, _type = _name.strip(), _type.strip() _name = self._escape_args_and_kwargs(_name) - _type = _convert_numpy_type_spec( - _type, - location=self._get_location(), - translations=self._config.napoleon_type_aliases or {}, - ) + if self._config.napoleon_preprocess_types: + _type = _convert_numpy_type_spec( + _type, + location=self._get_location(), + translations=self._config.napoleon_type_aliases or {}, + ) if prefer_type and not _type: _type, _name = _name, _type diff --git a/sphinx/util/cfamily.py b/sphinx/util/cfamily.py index a67b2a0ad..0edea128c 100644 --- a/sphinx/util/cfamily.py +++ b/sphinx/util/cfamily.py @@ -391,7 +391,7 @@ class BaseParser: % startPos) return self.definition[startPos:self.pos] - def _parse_attribute(self) -> ASTAttribute: + def _parse_attribute(self) -> Optional[ASTAttribute]: self.skip_ws() # try C++11 style startPos = self.pos diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py index 71bf251e9..b6f72287e 100644 --- a/tests/test_domain_c.py +++ b/tests/test_domain_c.py @@ -497,17 +497,16 @@ def test_attributes(): parse('member', 'paren_attr({]}) int f') # position: decl specs - check('function', 'static inline __attribute__(()) void f()', - {1: 'f'}, + check('function', 'static inline __attribute__(()) void f()', {1: 'f'}, output='__attribute__(()) static inline void f()') - check('function', '[[attr1]] [[attr2]] void f()', - {1: 'f'}, - output='[[attr1]] [[attr2]] void f()') + check('function', '[[attr1]] [[attr2]] void f()', {1: 'f'}) # position: declarator check('member', 'int *[[attr]] i', {1: 'i'}) check('member', 'int *const [[attr]] volatile i', {1: 'i'}, output='int *[[attr]] volatile const i') check('member', 'int *[[attr]] *i', {1: 'i'}) + # position: parameters + check('function', 'void f() [[attr1]] [[attr2]]', {1: 'f'}) # issue michaeljones/breathe#500 check('function', 'LIGHTGBM_C_EXPORT int LGBM_BoosterFree(int handle)', diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 118227cd7..558d69911 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -764,6 +764,7 @@ def test_templates(): check('class', "template typename> {key}A", {2: "II0E0E1A"}) check('class', "template typename ...T> {key}A", {2: "II0EDpE1A"}) check('class', "template typename...> {key}A", {2: "II0EDpE1A"}) + check('class', "template typename...> {key}A", {2: "I0I0EDpE1A"}) check('class', "template {key}A", {2: "I_iE1A"}) check('class', "template {key}A", {2: "I_iE1A"}) @@ -937,15 +938,15 @@ def test_attributes(): check('function', 'static inline __attribute__(()) void f()', {1: 'f', 2: '1fv'}, output='__attribute__(()) static inline void f()') - check('function', '[[attr1]] [[attr2]] void f()', - {1: 'f', 2: '1fv'}, - output='[[attr1]] [[attr2]] void f()') + check('function', '[[attr1]] [[attr2]] void f()', {1: 'f', 2: '1fv'}) # position: declarator check('member', 'int *[[attr]] i', {1: 'i__iP', 2: '1i'}) check('member', 'int *const [[attr]] volatile i', {1: 'i__iPVC', 2: '1i'}, output='int *[[attr]] volatile const i') check('member', 'int &[[attr]] i', {1: 'i__iR', 2: '1i'}) check('member', 'int *[[attr]] *i', {1: 'i__iPP', 2: '1i'}) + # position: parameters and qualifiers + check('function', 'void f() [[attr1]] [[attr2]]', {1: 'f', 2: '1fv'}) def test_xref_parsing(): diff --git a/tests/test_ext_napoleon_docstring.py b/tests/test_ext_napoleon_docstring.py index 7eb908058..bbc075edd 100644 --- a/tests/test_ext_napoleon_docstring.py +++ b/tests/test_ext_napoleon_docstring.py @@ -66,19 +66,19 @@ Sample namedtuple subclass Quick description of attr1 - :type: :class:`Arbitrary type` + :type: Arbitrary type .. attribute:: attr2 Quick description of attr2 - :type: :class:`Another arbitrary type` + :type: Another arbitrary type .. attribute:: attr3 Adds a newline after the type - :type: :class:`Type` + :type: Type """ self.assertEqual(expected, actual) @@ -1311,12 +1311,34 @@ class NumpyDocstringTest(BaseDocstringTest): config = Config( napoleon_use_param=False, napoleon_use_rtype=False, - napoleon_use_keyword=False) + napoleon_use_keyword=False, + napoleon_preprocess_types=True) for docstring, expected in self.docstrings: actual = str(NumpyDocstring(dedent(docstring), config)) expected = dedent(expected) self.assertEqual(expected, actual) + def test_type_preprocessor(self): + docstring = dedent(""" + Single line summary + + Parameters + ---------- + arg1:str + Extended + description of arg1 + """) + + config = Config(napoleon_preprocess_types=False, napoleon_use_param=False) + actual = str(NumpyDocstring(docstring, config)) + expected = dedent(""" + Single line summary + + :Parameters: **arg1** (*str*) -- Extended + description of arg1 + """) + self.assertEqual(expected, actual) + def test_parameters_with_class_reference(self): docstring = """\ Parameters @@ -1352,7 +1374,7 @@ x1, x2 : array_like config = Config(napoleon_use_param=False) actual = str(NumpyDocstring(docstring, config)) expected = """\ -:Parameters: **x1, x2** (:class:`array_like`) -- Input arrays, description of ``x1``, ``x2``. +:Parameters: **x1, x2** (*array_like*) -- Input arrays, description of ``x1``, ``x2``. """ self.assertEqual(expected, actual) @@ -1360,9 +1382,9 @@ x1, x2 : array_like actual = str(NumpyDocstring(dedent(docstring), config)) expected = """\ :param x1: Input arrays, description of ``x1``, ``x2``. -:type x1: :class:`array_like` +:type x1: array_like :param x2: Input arrays, description of ``x1``, ``x2``. -:type x2: :class:`array_like` +:type x2: array_like """ self.assertEqual(expected, actual) @@ -1377,7 +1399,7 @@ param1 : MyClass instance config = Config(napoleon_use_param=False) actual = str(NumpyDocstring(docstring, config)) expected = """\ -:Parameters: **param1** (:class:`MyClass instance`) +:Parameters: **param1** (*MyClass instance*) """ self.assertEqual(expected, actual) @@ -1385,7 +1407,7 @@ param1 : MyClass instance actual = str(NumpyDocstring(dedent(docstring), config)) expected = """\ :param param1: -:type param1: :class:`MyClass instance` +:type param1: MyClass instance """ self.assertEqual(expected, actual) @@ -1474,7 +1496,7 @@ arg_ : type expected = """ :ivar arg_: some description -:vartype arg_: :class:`type` +:vartype arg_: type """ config = Config(napoleon_use_ivar=True) @@ -1494,7 +1516,7 @@ arg_ : type expected = """ :ivar arg\\_: some description -:vartype arg\\_: :class:`type` +:vartype arg\\_: type """ config = Config(napoleon_use_ivar=True) @@ -1862,59 +1884,59 @@ definition_after_normal_text : int expected = """One line summary. :param no_list: -:type no_list: :class:`int` +:type no_list: int :param one_bullet_empty: * -:type one_bullet_empty: :class:`int` +:type one_bullet_empty: int :param one_bullet_single_line: - first line -:type one_bullet_single_line: :class:`int` +:type one_bullet_single_line: int :param one_bullet_two_lines: + first line continued -:type one_bullet_two_lines: :class:`int` +:type one_bullet_two_lines: int :param two_bullets_single_line: - first line - second line -:type two_bullets_single_line: :class:`int` +:type two_bullets_single_line: int :param two_bullets_two_lines: * first line continued * second line continued -:type two_bullets_two_lines: :class:`int` +:type two_bullets_two_lines: int :param one_enumeration_single_line: 1. first line -:type one_enumeration_single_line: :class:`int` +:type one_enumeration_single_line: int :param one_enumeration_two_lines: 1) first line continued -:type one_enumeration_two_lines: :class:`int` +:type one_enumeration_two_lines: int :param two_enumerations_one_line: (iii) first line (iv) second line -:type two_enumerations_one_line: :class:`int` +:type two_enumerations_one_line: int :param two_enumerations_two_lines: a. first line continued b. second line continued -:type two_enumerations_two_lines: :class:`int` +:type two_enumerations_two_lines: int :param one_definition_one_line: item 1 first line -:type one_definition_one_line: :class:`int` +:type one_definition_one_line: int :param one_definition_two_lines: item 1 first line continued -:type one_definition_two_lines: :class:`int` +:type one_definition_two_lines: int :param two_definitions_one_line: item 1 first line item 2 second line -:type two_definitions_one_line: :class:`int` +:type two_definitions_one_line: int :param two_definitions_two_lines: item 1 first line @@ -1922,14 +1944,14 @@ definition_after_normal_text : int item 2 second line continued -:type two_definitions_two_lines: :class:`int` +:type two_definitions_two_lines: int :param one_definition_blank_line: item 1 first line extra first line -:type one_definition_blank_line: :class:`int` +:type one_definition_blank_line: int :param two_definitions_blank_lines: item 1 @@ -1942,12 +1964,12 @@ definition_after_normal_text : int second line extra second line -:type two_definitions_blank_lines: :class:`int` +:type two_definitions_blank_lines: int :param definition_after_normal_text: text line item 1 first line -:type definition_after_normal_text: :class:`int` +:type definition_after_normal_text: int """ config = Config(napoleon_use_param=True) actual = str(NumpyDocstring(docstring, config)) @@ -2041,7 +2063,7 @@ definition_after_normal_text : int item 1 first line """ - config = Config(napoleon_use_param=False) + config = Config(napoleon_use_param=False, napoleon_preprocess_types=True) actual = str(NumpyDocstring(docstring, config)) self.assertEqual(expected, actual) @@ -2222,6 +2244,7 @@ definition_after_normal_text : int config = Config( napoleon_use_param=True, napoleon_use_rtype=True, + napoleon_preprocess_types=True, napoleon_type_aliases=translations, ) actual = str(NumpyDocstring(docstring, config))