diff --git a/CHANGES b/CHANGES index da8580fe67a..2a3ebbf5cbc 100644 --- a/CHANGES +++ b/CHANGES @@ -36,6 +36,8 @@ Features added text * #9176: i18n: Emit a debug message if message catalog file not found under :confval:`locale_dirs` +* #6525: linkcheck: Add :confval:`linkcheck_warn_redirects` to emit a warning + when the hyperlink is redirected * #9097: Optimize the paralell build * #9131: Add :confval:`nitpick_ignore_regex` to ignore nitpicky warnings using regular expressions diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index 0b2bd4e3eab..c5397e6c87f 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -2642,6 +2642,14 @@ Options for the linkcheck builder .. versionadded:: 3.4 +.. confval:: linkcheck_warn_redirects + + If true, emit a warning when redirection detected on checking hyperlinks. + It's useful to detect unexpected redirects under :option:`the warn-is-error + mode `. Default is ``False``. + + .. versionadded:: 4.1 + Options for the XML builder --------------------------- diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index a46b80c081b..568687a1084 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -272,8 +272,12 @@ def process_result(self, result: CheckResult) -> None: except KeyError: text, color = ('with unknown code', purple) linkstat['text'] = text - logger.info(color('redirect ') + result.uri + - color(' - ' + text + ' to ' + result.message)) + if self.config.linkcheck_warn_redirects: + logger.warning('redirect ' + result.uri + ' - ' + text + ' to ' + + result.message, location=(filename, result.lineno)) + else: + logger.info(color('redirect ') + result.uri + + color(' - ' + text + ' to ' + result.message)) self.write_entry('redirected ' + text, result.docname, filename, result.lineno, result.uri + ' to ' + result.message) else: @@ -657,6 +661,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: # commonly used for dynamic pages app.add_config_value('linkcheck_anchors_ignore', ["^!"], None) app.add_config_value('linkcheck_rate_limit_timeout', 300.0, None) + app.add_config_value('linkcheck_warn_redirects', False, None) return { 'version': 'builtin', diff --git a/tests/test_build_linkcheck.py b/tests/test_build_linkcheck.py index fd7a5482abd..2314006e5b5 100644 --- a/tests/test_build_linkcheck.py +++ b/tests/test_build_linkcheck.py @@ -23,6 +23,7 @@ import requests from sphinx.builders.linkcheck import HyperlinkAvailabilityCheckWorker, RateLimit +from sphinx.testing.util import strip_escseq from sphinx.util.console import strip_colors from .utils import CERT_FILE, http_server, https_server @@ -250,7 +251,7 @@ def log_date_time_string(self): @pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True) -def test_follows_redirects_on_HEAD(app, capsys): +def test_follows_redirects_on_HEAD(app, capsys, warning): with http_server(make_redirect_handler(support_head=True)): app.build() stdout, stderr = capsys.readouterr() @@ -265,10 +266,11 @@ def test_follows_redirects_on_HEAD(app, capsys): 127.0.0.1 - - [] "HEAD /?redirected=1 HTTP/1.1" 204 - """ ) + assert warning.getvalue() == '' @pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True) -def test_follows_redirects_on_GET(app, capsys): +def test_follows_redirects_on_GET(app, capsys, warning): with http_server(make_redirect_handler(support_head=False)): app.build() stdout, stderr = capsys.readouterr() @@ -284,6 +286,16 @@ def test_follows_redirects_on_GET(app, capsys): 127.0.0.1 - - [] "GET /?redirected=1 HTTP/1.1" 204 - """ ) + assert warning.getvalue() == '' + + +@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True, + confoverrides={'linkcheck_warn_redirects': True}) +def test_linkcheck_warn_redirects(app, warning): + with http_server(make_redirect_handler(support_head=False)): + app.build() + assert ("index.rst.rst:1: WARNING: redirect http://localhost:7777/ - with Found to " + "http://localhost:7777/?redirected=1\n" in strip_escseq(warning.getvalue())) class OKHandler(http.server.BaseHTTPRequestHandler):