From 44a98961b2811c9be117fa9a5508e1b8908d3d60 Mon Sep 17 00:00:00 2001
From: Stephen Finucane
Date: Mon, 6 Oct 2025 00:10:35 +0100
Subject: [PATCH] Use hyphenated version directives (#13714)
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
---
doc/usage/restructuredtext/directives.rst | 52 +++++++++++++------
sphinx/addnodes.py | 4 +-
sphinx/application.py | 2 +-
sphinx/builders/changes.py | 10 +++-
sphinx/domains/changeset.py | 17 ++++--
tests/roots/test-changes/base.rst | 33 ++++++++----
tests/roots/test-changes/c-api.rst | 4 +-
tests/roots/test-changes/library/utils.rst | 6 +--
tests/roots/test-intl/versionchange.txt | 16 +++---
.../test-intl/xx/LC_MESSAGES/versionchange.po | 18 +++++--
tests/roots/test-root/markup.txt | 29 +++++++----
.../test_builders/test_build_html_5_output.py | 36 +++++++++++--
tests/test_intl/test_intl.py | 10 ++--
13 files changed, 172 insertions(+), 65 deletions(-)
diff --git a/doc/usage/restructuredtext/directives.rst b/doc/usage/restructuredtext/directives.rst
index 95eee926e1..4b1a2042df 100644
--- a/doc/usage/restructuredtext/directives.rst
+++ b/doc/usage/restructuredtext/directives.rst
@@ -537,7 +537,8 @@ Describing changes between versions
pair: changes; in version
pair: removed; in version
-.. rst:directive:: .. versionadded:: version [brief explanation]
+.. rst:directive:: .. version-added:: version [brief explanation]
+ .. versionadded:: version [brief explanation]
This directive documents the version of the project
which added the described feature.
@@ -551,56 +552,75 @@ Describing changes between versions
There must be no blank line between the directive head and the explanation;
this is to make these blocks visually continuous in the markup.
+ .. version-changed:: 8.3
+ The :rst:dir:`versionadded` directive was renamed to :rst:dir:`version-added`.
+ The previous name is retained as an alias.
+
Example::
- .. versionadded:: 2.5
+ .. version-added:: 2.5
The *spam* parameter.
- .. versionadded:: 2.5
+ .. version-added:: 2.5
The *spam* parameter.
-.. rst:directive:: .. versionchanged:: version [brief explanation]
+.. rst:directive:: .. version-changed:: version [brief explanation]
+ .. versionchanged:: version [brief explanation]
- Similar to :rst:dir:`versionadded`, but describes when and what changed in
+ Similar to :rst:dir:`version-added`, but describes when and what changed in
the named feature in some way (new parameters, changed side effects, etc.).
+ .. version-changed:: 8.3
+ The :rst:dir:`versionchanged` directive was renamed to :rst:dir:`version-changed`.
+ The previous name is retained as an alias.
+
Example::
- .. versionchanged:: 2.8
+ .. version-changed:: 2.8
The *spam* parameter is now of type *boson*.
- .. versionchanged:: 2.8
+ .. version-changed:: 2.8
The *spam* parameter is now of type *boson*.
-.. rst:directive:: .. deprecated:: version [brief explanation]
+.. rst:directive:: .. version-deprecated:: version [brief explanation]
+ .. deprecated:: version [brief explanation]
- Similar to :rst:dir:`versionadded`, but describes when the feature was
+ Similar to :rst:dir:`version-added`, but describes when the feature was
deprecated.
A *brief* explanation can also be given,
for example to tell the reader what to use instead.
+ .. version-changed:: 8.3
+ The :rst:dir:`deprecated` directive was renamed to :rst:dir:`version-deprecated`.
+ The previous name is retained as an alias
+
Example::
- .. deprecated:: 3.1
+ .. version-deprecated:: 3.1
Use :py:func:`spam` instead.
- .. deprecated:: 3.1
+ .. version-deprecated:: 3.1
Use :py:func:`!spam` instead.
-.. rst:directive:: .. versionremoved:: version [brief explanation]
+.. rst:directive:: .. version-removed:: version [brief explanation]
+ .. versionremoved:: version [brief explanation]
- Similar to :rst:dir:`versionadded`, but describes when the feature was removed.
+ Similar to :rst:dir:`version-added`, but describes when the feature was removed.
An explanation may be provided to tell the reader what to use instead,
or why the feature was removed.
- .. versionadded:: 7.3
+ .. version-added:: 7.3
+
+ .. version-changed:: 8.3
+ The :rst:dir:`versionremoved` directive was renamed to :rst:dir:`version-removed`.
+ The previous name is retained as an alias.
Example::
- .. versionremoved:: 4.0
+ .. version-removed:: 4.0
The :py:func:`spam` function is more flexible, and should be used instead.
- .. versionremoved:: 4.0
+ .. version-removed:: 4.0
The :py:func:`!spam` function is more flexible, and should be used instead.
diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py
index 3cf63b6b05..4ce85dabcf 100644
--- a/sphinx/addnodes.py
+++ b/sphinx/addnodes.py
@@ -402,8 +402,8 @@ class desc_sig_literal_char(desc_sig_element, _sig_element=True):
class versionmodified(nodes.Admonition, nodes.TextElement):
"""Node for version change entries.
- Currently used for "versionadded", "versionchanged", "deprecated"
- and "versionremoved" directives.
+ Currently used for "version-added", "version-changed", "version-deprecated"
+ and "version-removed" directives, along with their aliases.
"""
diff --git a/sphinx/application.py b/sphinx/application.py
index c4fcc93f9f..9cd3d856bd 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -1121,7 +1121,7 @@ class Sphinx:
.. versionchanged:: 0.6
Docutils 0.5-style directive classes are now supported.
- .. deprecated:: 1.8
+ .. versionchanged:: 1.8
Docutils 0.4-style (function based) directives support is deprecated.
.. versionchanged:: 1.8
Add *override* keyword.
diff --git a/sphinx/builders/changes.py b/sphinx/builders/changes.py
index 99d46fa048..6d38a6acbb 100644
--- a/sphinx/builders/changes.py
+++ b/sphinx/builders/changes.py
@@ -23,7 +23,7 @@ logger = logging.getLogger(__name__)
class ChangesBuilder(Builder):
- """Write a summary with all versionadded/changed/deprecated/removed directives."""
+ """Write a summary with all version-related directives."""
name = 'changes'
epilog = __('The overview file is in %(outdir)s.')
@@ -43,9 +43,13 @@ class ChangesBuilder(Builder):
return str(self.outdir)
typemap = {
+ 'version-added': 'added',
'versionadded': 'added',
+ 'version-changed': 'changed',
'versionchanged': 'changed',
+ 'version-deprecated': 'deprecated',
'deprecated': 'deprecated',
+ 'version-removed': 'removed',
'versionremoved': 'removed',
}
@@ -112,9 +116,13 @@ class ChangesBuilder(Builder):
f.write(self.templates.render('changes/versionchanges.html', ctx))
hltext = [
+ f'.. version-added:: {version}',
f'.. versionadded:: {version}',
+ f'.. version-changed:: {version}',
f'.. versionchanged:: {version}',
+ f'.. version-deprecated:: {version}',
f'.. deprecated:: {version}',
+ f'.. version-removed:: {version}',
f'.. versionremoved:: {version}',
]
diff --git a/sphinx/domains/changeset.py b/sphinx/domains/changeset.py
index e2657ad63e..4349595f9d 100644
--- a/sphinx/domains/changeset.py
+++ b/sphinx/domains/changeset.py
@@ -21,6 +21,12 @@ if TYPE_CHECKING:
from sphinx.environment import BuildEnvironment
from sphinx.util.typing import ExtensionMetadata, OptionSpec
+name_aliases = {
+ 'version-added': 'versionadded',
+ 'version-changed': 'versionchanged',
+ 'version-deprecated': 'deprecated',
+ 'version-removed': 'versionremoved',
+}
versionlabels = {
'versionadded': _('Added in version %s'),
@@ -56,12 +62,13 @@ class VersionChange(SphinxDirective):
option_spec: ClassVar[OptionSpec] = {}
def run(self) -> list[Node]:
+ name = name_aliases.get(self.name, self.name)
node = addnodes.versionmodified()
node.document = self.state.document
self.set_source_info(node)
- node['type'] = self.name
+ node['type'] = name
node['version'] = self.arguments[0]
- text = versionlabels[self.name] % self.arguments[0]
+ text = versionlabels[name] % self.arguments[0]
if len(self.arguments) == 2:
inodes, messages = self.parse_inline(
self.arguments[1], lineno=self.lineno + 1
@@ -73,7 +80,7 @@ class VersionChange(SphinxDirective):
messages = []
if self.content:
node += self.parse_content_to_nodes()
- classes = ['versionmodified', versionlabel_classes[self.name]]
+ classes = ['versionmodified', versionlabel_classes[name]]
if len(node) > 0 and isinstance(node[0], nodes.paragraph):
# the contents start with a paragraph
if node[0].rawsource:
@@ -168,9 +175,13 @@ class ChangeSetDomain(Domain):
def setup(app: Sphinx) -> ExtensionMetadata:
app.add_domain(ChangeSetDomain)
+ app.add_directive('version-deprecated', VersionChange)
app.add_directive('deprecated', VersionChange)
+ app.add_directive('version-added', VersionChange)
app.add_directive('versionadded', VersionChange)
+ app.add_directive('version-changed', VersionChange)
app.add_directive('versionchanged', VersionChange)
+ app.add_directive('version-removed', VersionChange)
app.add_directive('versionremoved', VersionChange)
return {
diff --git a/tests/roots/test-changes/base.rst b/tests/roots/test-changes/base.rst
index 81d90e66ef..a6a83e4a28 100644
--- a/tests/roots/test-changes/base.rst
+++ b/tests/roots/test-changes/base.rst
@@ -1,23 +1,38 @@
Version markup
--------------
-.. versionadded:: 0.6
+.. version-added:: 0.6
Some funny **stuff**.
-.. versionchanged:: 0.6
+.. version-changed:: 0.6
Even more funny stuff.
-.. deprecated:: 0.6
+.. version-deprecated:: 0.6
Boring stuff.
-.. versionremoved:: 0.6
+.. version-removed:: 0.6
Goodbye boring stuff.
-.. versionadded:: 1.2
+.. version-added:: 1.2
- First paragraph of versionadded.
+ First paragraph of version-added.
-.. versionchanged:: 1.2
- First paragraph of versionchanged.
+.. version-changed:: 1.2
+ First paragraph of version-changed.
- Second paragraph of versionchanged.
+ Second paragraph of version-changed.
+
+.. version-deprecated:: 1.3
+ First paragraph of version-deprecated.
+
+.. version-added:: 0.6
+ Deprecated alias
+
+.. version-changed:: 0.6
+ Deprecated alias
+
+.. versionremoved:: 0.6
+ Deprecated alias
+
+.. deprecated:: 0.6
+ Deprecated alias
diff --git a/tests/roots/test-changes/c-api.rst b/tests/roots/test-changes/c-api.rst
index f0ad413cd2..6ded4afebd 100644
--- a/tests/roots/test-changes/c-api.rst
+++ b/tests/roots/test-changes/c-api.rst
@@ -8,7 +8,7 @@ Memory
Allocate *n* bytes of memory.
- .. versionchanged:: 0.6
+ .. version-changed:: 0.6
Can now be replaced with a different allocator.
@@ -17,7 +17,7 @@ System
Access to the system allocator.
-.. versionadded:: 0.6
+.. version-added:: 0.6
.. c:function:: void* Test_SysMalloc(size_t n)
diff --git a/tests/roots/test-changes/library/utils.rst b/tests/roots/test-changes/library/utils.rst
index 86446995b2..5667571559 100644
--- a/tests/roots/test-changes/library/utils.rst
+++ b/tests/roots/test-changes/library/utils.rst
@@ -16,10 +16,10 @@ Classes
Class for handling paths.
- .. versionadded:: 0.5
+ .. version-added:: 0.5
- Innovative new way to handle paths.
+ Innovative new way to handle paths.
- .. deprecated:: 0.6
+ .. version-deprecated:: 0.6
So, that was a bad idea it turns out.
diff --git a/tests/roots/test-intl/versionchange.txt b/tests/roots/test-intl/versionchange.txt
index 764534246c..69b9ada185 100644
--- a/tests/roots/test-intl/versionchange.txt
+++ b/tests/roots/test-intl/versionchange.txt
@@ -3,16 +3,16 @@
i18n with versionchange
============================
-.. deprecated:: 1.0
- This is the *first* paragraph of deprecated.
+.. version-deprecated:: 1.0
+ This is the *first* paragraph of version-deprecated.
- This is the *second* paragraph of deprecated.
+ This is the *second* paragraph of version-deprecated.
-.. versionadded:: 1.0
- This is the *first* paragraph of versionadded.
+.. version-added:: 1.0
+ This is the *first* paragraph of version-added.
-.. versionchanged:: 1.0
+.. version-changed:: 1.0
- This is the *first* paragraph of versionchanged.
+ This is the *first* paragraph of version-changed.
-.. versionremoved:: 1.0 This is the *first* paragraph of versionremoved.
+.. version-removed:: 1.0 This is the *first* paragraph of version-removed.
diff --git a/tests/roots/test-intl/xx/LC_MESSAGES/versionchange.po b/tests/roots/test-intl/xx/LC_MESSAGES/versionchange.po
index b1d786580f..8f1b88bcac 100644
--- a/tests/roots/test-intl/xx/LC_MESSAGES/versionchange.po
+++ b/tests/roots/test-intl/xx/LC_MESSAGES/versionchange.po
@@ -19,12 +19,24 @@ msgstr ""
msgid "i18n with versionchange"
msgstr "I18N WITH VERSIONCHANGE"
+msgid "This is the *first* paragraph of version-deprecated."
+msgstr "THIS IS THE *FIRST* PARAGRAPH OF VERSION-DEPRECATED."
+
+msgid "This is the *second* paragraph of version-deprecated."
+msgstr "THIS IS THE *SECOND* PARAGRAPH OF VERSION-DEPRECATED."
+
+msgid "This is the *first* paragraph of version-added."
+msgstr "THIS IS THE *FIRST* PARAGRAPH OF VERSION-ADDED."
+
+msgid "This is the *first* paragraph of version-changed."
+msgstr "THIS IS THE *FIRST* PARAGRAPH OF VERSION-CHANGED."
+
+msgid "This is the *first* paragraph of version-removed."
+msgstr "THIS IS THE *FIRST* PARAGRAPH OF VERSION-REMOVED."
+
msgid "This is the *first* paragraph of deprecated."
msgstr "THIS IS THE *FIRST* PARAGRAPH OF DEPRECATED."
-msgid "This is the *second* paragraph of deprecated."
-msgstr "THIS IS THE *SECOND* PARAGRAPH OF DEPRECATED."
-
msgid "This is the *first* paragraph of versionadded."
msgstr "THIS IS THE *FIRST* PARAGRAPH OF VERSIONADDED."
diff --git a/tests/roots/test-root/markup.txt b/tests/roots/test-root/markup.txt
index 0a7b6cb2c9..8f5e026a25 100644
--- a/tests/roots/test-root/markup.txt
+++ b/tests/roots/test-root/markup.txt
@@ -292,27 +292,38 @@ Figures
Version markup
--------------
-.. versionadded:: 0.6
+.. version-added:: 0.6
Some funny **stuff**.
-.. versionchanged:: 0.6
+.. version-changed:: 0.6
Even more funny stuff.
-.. deprecated:: 0.6
+.. version-deprecated:: 0.6
Boring stuff.
-.. versionremoved:: 0.6
+.. version-removed:: 0.6
Goodbye boring stuff.
-.. versionadded:: 1.2
+.. version-added:: 1.2
- First paragraph of versionadded.
+ First paragraph of version-added.
-.. versionchanged:: 1.2
- First paragraph of versionchanged.
+.. version-changed:: 1.2
+ First paragraph of version-changed.
- Second paragraph of versionchanged.
+ Second paragraph of version-changed.
+.. version-added:: 0.6
+ Deprecated alias for version-added.
+
+.. version-changed:: 0.6
+ Deprecated alias for version-changed.
+
+.. deprecated:: 0.6
+ Deprecated alias for version-deprecated.
+
+.. versionremoved:: 0.6
+ Deprecated alias for version-removed.
Code blocks
-----------
diff --git a/tests/test_builders/test_build_html_5_output.py b/tests/test_builders/test_build_html_5_output.py
index db9dd8a749..6798482f62 100644
--- a/tests/test_builders/test_build_html_5_output.py
+++ b/tests/test_builders/test_build_html_5_output.py
@@ -205,23 +205,53 @@ def tail_check(check: str) -> Callable[[Iterable[Element]], Literal[True]]:
(
'markup.html',
".//div[@class='versionadded']/p/span",
- tail_check('First paragraph of versionadded'),
+ tail_check('First paragraph of version-added'),
+ ),
+ (
+ 'markup.html',
+ ".//div[@class='versionadded']/p/span",
+ tail_check('Deprecated alias for version-added'),
),
(
'markup.html',
".//div[@class='versionchanged']/p/span",
- tail_check('First paragraph of versionchanged'),
+ tail_check('First paragraph of version-changed'),
),
(
'markup.html',
".//div[@class='versionchanged']/p",
- 'Second paragraph of versionchanged',
+ 'Second paragraph of version-changed',
+ ),
+ (
+ 'markup.html',
+ ".//div[@class='versionchanged']/p/span",
+ tail_check('Deprecated alias for version-changed'),
+ ),
+ (
+ 'markup.html',
+ ".//div[@class='deprecated']/p/span",
+ 'Deprecated since version 0.6: ',
+ ),
+ (
+ 'markup.html',
+ ".//div[@class='deprecated']/p/span",
+ tail_check('Boring stuff.'),
+ ),
+ (
+ 'markup.html',
+ ".//div[@class='deprecated']/p/span",
+ tail_check('Deprecated alias for version-deprecated'),
),
(
'markup.html',
".//div[@class='versionremoved']/p/span",
'Removed in version 0.6: ',
),
+ (
+ 'markup.html',
+ ".//div[@class='versionremoved']/p/span",
+ tail_check('Deprecated alias for version-removed'),
+ ),
# footnote reference
('markup.html', ".//a[@class='footnote-reference brackets']", r'1'),
# created by reference lookup
diff --git a/tests/test_intl/test_intl.py b/tests/test_intl/test_intl.py
index d43c029d4d..2375c1a668 100644
--- a/tests/test_intl/test_intl.py
+++ b/tests/test_intl/test_intl.py
@@ -1101,29 +1101,29 @@ def test_html_versionchanges(app):
expect1 = (
"""Deprecated since version 1.0: """
- """THIS IS THE FIRST PARAGRAPH OF DEPRECATED.
\n"""
- """THIS IS THE SECOND PARAGRAPH OF DEPRECATED.
\n"""
+ """THIS IS THE FIRST PARAGRAPH OF VERSION-DEPRECATED.
\n"""
+ """THIS IS THE SECOND PARAGRAPH OF VERSION-DEPRECATED.
\n"""
)
matched_content = get_content(result, 'deprecated')
assert matched_content == expect1
expect2 = (
"""Added in version 1.0: """
- """THIS IS THE FIRST PARAGRAPH OF VERSIONADDED.
\n"""
+ """THIS IS THE FIRST PARAGRAPH OF VERSION-ADDED.\n"""
)
matched_content = get_content(result, 'versionadded')
assert matched_content == expect2
expect3 = (
"""Changed in version 1.0: """
- """THIS IS THE FIRST PARAGRAPH OF VERSIONCHANGED.
\n"""
+ """THIS IS THE FIRST PARAGRAPH OF VERSION-CHANGED.\n"""
)
matched_content = get_content(result, 'versionchanged')
assert matched_content == expect3
expect4 = (
"""Removed in version 1.0: """
- """THIS IS THE FIRST PARAGRAPH OF VERSIONREMOVED.
\n"""
+ """THIS IS THE FIRST PARAGRAPH OF VERSION-REMOVED.\n"""
)
matched_content = get_content(result, 'versionremoved')
assert matched_content == expect4