From 7cbe1a17875240ee79b301aeeff3ed0e53c7476c Mon Sep 17 00:00:00 2001 From: Jiya Date: Thu, 5 Nov 2026 12:38:59 +0530 Subject: [PATCH] Fix moved links in additional formats --- scripts/check_external_links.py | 39 ++++++++++++++++++++++++------ tests/test_check_external_links.py | 27 +++++++++++++++++++++ 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/scripts/check_external_links.py b/scripts/check_external_links.py index 5e2f385..44bf918 100644 --- a/scripts/check_external_links.py +++ b/scripts/check_external_links.py @@ -336,27 +336,52 @@ def report_needs_issue(report: dict) -> bool: def fix_moved(report: dict, root: Path = ROOT) -> tuple[int, list[Path]]: - """Rewrite each permanently moved link to the address its redirect ended on, in the files that cite it (#1255). - Only `[text](URL)` and `` forms are rewritten, a target that looks like a login or error page is left alone, - and ignored URLs never reach the report. Returns (links rewritten, files changed); run `make build-explainers` - after changing an explainer.""" + """Rewrite moved links in Markdown and HTML formats.""" changed: dict[Path, str] = {} count = 0 + for item in report.get("moved", []): old, new = item["url"], item["detail"] + if LOGIN_OR_ERROR.search(urllib.parse.urlsplit(new).path): continue + + escaped_old = re.escape(old) + for name in item["files"]: path = Path(name) if Path(name).is_absolute() else root / name - text = changed.get(path) or path.read_text(encoding="utf-8") - fixed = text.replace(f"]({old})", f"]({new})").replace(f"<{old}>", f"<{new}>") + text = changed.get(path) + if text is None: + text = path.read_text(encoding="utf-8") + + fixed = re.sub( + rf"(\]\(\s*){escaped_old}(?=\s*(?:\"[^\"]*\"|'[^']*'|\([^)]*\))?\s*\))", + lambda m: m.group(1) + new, + text, + ) + + fixed = re.sub( + rf"(?m)^(\s*\[[^\]]+\]:\s*?(?:\s+.*)?$)", + lambda m: m.group(1) + new, + fixed, + ) + + fixed = re.sub( + rf"""(?i)(\bhref\s*=\s*)(["']){escaped_old}\2""", + lambda m: m.group(1) + m.group(2) + new + m.group(2), + fixed, + ) + + fixed = fixed.replace(f"<{old}>", f"<{new}>") + if fixed != text: changed[path] = fixed count += 1 + for path, text in changed.items(): path.write_text(text, encoding="utf-8") - return count, sorted(changed) + return count, sorted(changed) def _positive_int(value: str) -> int: number = int(value) diff --git a/tests/test_check_external_links.py b/tests/test_check_external_links.py index 8a53a78..28f159b 100644 --- a/tests/test_check_external_links.py +++ b/tests/test_check_external_links.py @@ -412,3 +412,30 @@ def test_main_fix_moved_rewrites_after_reporting(tmp_path, monkeypatch, capsys): out = capsys.readouterr().out assert "1 moved" in out and "Rewrote 1 moved link(s) in 1 file(s)" in out assert doc.read_text(encoding="utf-8") == "[a](https://new.example.org/a)\n" + +def test_fix_moved_rewrites_additional_link_formats(tmp_path): + doc = tmp_path / "doc.md" + doc.write_text( + '[Google](https://old.example.org/a "Search")\n' + '[google]: https://old.example.org/a\n' + 'Google\n', + encoding="utf-8", + ) + + report = { + "moved": [{ + "url": "https://old.example.org/a", + "detail": "https://new.example.org/a", + "files": ["doc.md"], + }] + } + + count, files = script.fix_moved(report, root=tmp_path) + + assert count == 1 + assert files == [doc] + assert doc.read_text(encoding="utf-8") == ( + '[Google](https://new.example.org/a "Search")\n' + '[google]: https://new.example.org/a\n' + 'Google\n' + )