diff --git a/sphinx/domains/changeset.py b/sphinx/domains/changeset.py index 4349595f9df..03d76d82253 100644 --- a/sphinx/domains/changeset.py +++ b/sphinx/domains/changeset.py @@ -70,11 +70,23 @@ def run(self) -> list[Node]: node['version'] = self.arguments[0] text = versionlabels[name] % self.arguments[0] if len(self.arguments) == 2: + # The argument may be on the same line as the directive, or on the + # following line (docutils treats the first non-blank indented line + # as the second argument when optional_arguments > 0 and there's no + # blank line). Check the directive's own line to tell them apart. + directive_line = self.block_text.partition('\n')[0] + arg_first_line = self.arguments[1].partition('\n')[0] + if arg_first_line in directive_line: + arg_lineno = self.lineno + else: + arg_lineno = self.lineno + 1 inodes, messages = self.parse_inline( - self.arguments[1], lineno=self.lineno + 1 + self.arguments[1], lineno=arg_lineno ) para = nodes.paragraph(self.arguments[1], '', *inodes, translatable=False) - self.set_source_info(para) + para.source, para.line = self.state_machine.get_source_and_line( + arg_lineno + ) node.append(para) else: messages = [] @@ -89,9 +101,10 @@ def run(self) -> list[Node]: content.source = node[0].source content.line = node[0].line content += node[0].children - node[0].replace_self( - nodes.paragraph('', '', content, translatable=False) - ) + new_para = nodes.paragraph('', '', content, translatable=False) + new_para.source = node[0].source + new_para.line = node[0].line + node[0].replace_self(new_para) para = node[0] para.insert(0, nodes.inline('', '%s: ' % text, classes=classes)) diff --git a/tests/roots/test-root/markup.txt b/tests/roots/test-root/markup.txt index 8f5e026a25f..4fb46970259 100644 --- a/tests/roots/test-root/markup.txt +++ b/tests/roots/test-root/markup.txt @@ -295,6 +295,11 @@ Version markup .. version-added:: 0.6 Some funny **stuff**. +.. version-added:: 0.6.1 + Some more funny **stuff** + that took much more text + to describe. + .. version-changed:: 0.6 Even more funny stuff. @@ -308,15 +313,26 @@ Version markup First paragraph of version-added. +.. version-added:: 1.2.1 + + First paragraph of version-added. + With multiple lines. + .. version-changed:: 1.2 First paragraph of version-changed. Second paragraph of version-changed. -.. version-added:: 0.6 +.. version-changed:: 3.14 This was the pi release. + +.. version-changed:: 3.1416 This was the pi release. + It was great because pi is trancendental + and has a lot of digits. + +.. versionadded:: 0.6 Deprecated alias for version-added. -.. version-changed:: 0.6 +.. versionchanged:: 0.6 Deprecated alias for version-changed. .. deprecated:: 0.6 diff --git a/tests/test_builders/test_build_html_5_output.py b/tests/test_builders/test_build_html_5_output.py index b712d1415a3..e5a7fec74b4 100644 --- a/tests/test_builders/test_build_html_5_output.py +++ b/tests/test_builders/test_build_html_5_output.py @@ -8,6 +8,7 @@ import pytest from docutils import nodes +from sphinx import addnodes from tests.test_builders.xpath_util import check_xpath if TYPE_CHECKING: @@ -565,3 +566,34 @@ def insert_invalid_rubric_heading_level( assert 'WARNING: unsupported rubric heading level: 7' in warnings assert '' not in content assert '

INSERTED RUBRIC

' in content + + +@pytest.mark.parametrize( + ('node_type', 'version', 'lineno'), + [ + # argument-style content (no blank line after directive) + ('versionadded', '0.6', 296), + ('versionadded', '0.6.1', 299), + # body content (blank line after directive) + ('versionadded', '1.2', 314), + ('versionadded', '1.2.1', 318), + # inline content (text on same line as directive) + ('versionchanged', '3.14', 326), + ('versionchanged', '3.1416', 328), + ], +) +@pytest.mark.sphinx('dummy', testroot='root') +def test_versionadded_paragraph_source_info( + app: SphinxTestApp, + node_type: str, + version: str, + lineno: int, +) -> None: + app.build() + doctree = app.env.get_doctree('markup') + for node in doctree.findall(addnodes.versionmodified): + if node['type'] == node_type and node['version'] == version: + assert node[0].line == lineno + break + else: + pytest.fail(f'{node_type} {version} node not found')