From 8b71632b52fa9aa574e3051b7dec42081a18cb80 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 5 Aug 2023 23:24:42 +0100 Subject: [PATCH 01/83] PEP 703, 720: Remove blank ``Resolution`` headers --- pep-0703.rst | 1 - pep-0720.rst | 1 - 2 files changed, 2 deletions(-) diff --git a/pep-0703.rst b/pep-0703.rst index be2a8e99415..92080992bfa 100644 --- a/pep-0703.rst +++ b/pep-0703.rst @@ -10,7 +10,6 @@ Created: 09-Jan-2023 Python-Version: 3.13 Post-History: `09-Jan-2023 `__, `04-May-2023 `__ -Resolution: Abstract diff --git a/pep-0720.rst b/pep-0720.rst index 87c00ae7c97..14e0708c7b9 100644 --- a/pep-0720.rst +++ b/pep-0720.rst @@ -7,7 +7,6 @@ Type: Informational Content-Type: text/x-rst Created: 01-Jul-2023 Python-Version: 3.12 -Resolution: Abstract From 6a01ddabd170b98d0ce6b37bc737140009181b0b Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 5 Aug 2023 23:24:54 +0100 Subject: [PATCH 02/83] PEP 499: Shorten title --- pep-0499.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0499.txt b/pep-0499.txt index 9cbf6f8c3f0..e48a24767a0 100644 --- a/pep-0499.txt +++ b/pep-0499.txt @@ -1,5 +1,5 @@ PEP: 499 -Title: ``python -m foo`` should bind ``sys.modules['foo']`` in addition to ``sys.modules['__main__']`` +Title: ``python -m foo`` should also bind ``'foo'`` in ``sys.modules`` Version: $Revision$ Last-Modified: $Date$ Author: Cameron Simpson , Chris Angelico , Joseph Jevnik From 3a6be9131ad6fae71c59f52fe251521a9045b88b Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 5 Aug 2023 23:25:05 +0100 Subject: [PATCH 03/83] Add ``pep-lint.py`` --- .github/CODEOWNERS | 1 + .github/workflows/lint.yml | 14 + pep-lint.py | 431 ++++++++++++++++++++++++++ pep_sphinx_extensions/generate_rss.py | 3 - 4 files changed, 446 insertions(+), 3 deletions(-) create mode 100755 pep-lint.py diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9cd7f698fb8..23b5b64568f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -21,6 +21,7 @@ contents.rst @AA-Turner .codespell/ @CAM-Gerlach @hugovk .codespellrc @CAM-Gerlach @hugovk .pre-commit-config.yaml @CAM-Gerlach @hugovk +pep-lint.py @AA-Turner @CAM-Gerlach @hugovk # Git infrastructure .gitattributes @CAM-Gerlach diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f5afc074072..d964c8141e8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -35,3 +35,17 @@ jobs: uses: pre-commit/action@v3.0.0 with: extra_args: --all-files --hook-stage manual codespell || true + + pep-lint: + name: Run pep-lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3 + uses: actions/setup-python@v4 + with: + python-version: "3" + + - name: Run pep-lint + uses: python pep-lint.py diff --git a/pep-lint.py b/pep-lint.py new file mode 100755 index 00000000000..ffe2ae8cf72 --- /dev/null +++ b/pep-lint.py @@ -0,0 +1,431 @@ +#!/usr/bin/env python3 + +# This file is placed in the public domain or under the +# CC0-1.0-Universal license, whichever is more permissive. + +import datetime +import re +import sys +from pathlib import Path + +# get the directory with the PEP sources +PEP_ROOT = Path(__file__).resolve().parent + +# See PEP 12 for the order +# Note we retain "BDFL-Delegate" +ALL_HEADERS = ( + "PEP", + "Title", + "Version", + "Last-Modified", + "Author", + "Sponsor", + "BDFL-Delegate", "PEP-Delegate", + "Discussions-To", + "Status", + "Type", + "Topic", + "Content-Type", + "Requires", + "Created", + "Python-Version", + "Post-History", + "Replaces", + "Superseded-By", + "Resolution", +) +REQUIRED_HEADERS = frozenset({"PEP", "Title", "Author", "Status", "Type", "Created"}) + +# See PEP 1 for the full list +ALL_STATUSES = frozenset({ + "Accepted", + "Active", + "April Fool!", + "Deferred", + "Draft", + "Final", + "Provisional", + "Rejected", + "Superseded", + "Withdrawn", +}) + +# PEPs that are allowed to link directly to PEPs +SKIP_DIRECT_PEP_LINK_CHECK = frozenset({"0009", "0287", "0676", "0684", "8001"}) + +HEADER_PATTERN = re.compile(r"^([a-z\-]+):(?: |$)", re.IGNORECASE) +EMAIL_PATTERN = re.compile( + r""" + ([^\W\d_]|[. ])+ # Name; any sequence of unicode letters, full stops, or spaces + ( < # Start of optional email part: ' <' + [a-z0-9!#$%&'*+\-/=?^_{|}~.]+ # Local part; ASCII letters, digits, and legal special characters + (@| at ) # Local and domain parts separator + (\w+\.)+[a-z0-9-]+ # Domain, with at least two segments + >)? # End of optional email part: '>' + """, + re.IGNORECASE | re.VERBOSE +) +DISCOURSE_THREAD_PATTERN = re.compile(r'([\w\-]+/)?\d+') +DISCOURSE_POST_PATTERN = re.compile(r'([\w\-]+/)?\d+(/\d+)?') +MAILMAN_2_PATTERN = re.compile(r'[\w\-]+/\d{4}-[A-Za-z]+/[A-Za-z0-9]+\.html') +MAILMAN_3_THREAD_PATTERN = re.compile(r'[\w\-]+@python\.org/thread/[A-Za-z0-9]+/?(#[A-Za-z0-9]+)?') +MAILMAN_3_MESSAGE_PATTERN = re.compile(r'[\w\-]+@python\.org/message/[A-Za-z0-9]+/?(#[A-Za-z0-9]+)?') + + +def check(): + """The main entry-point.""" + failed = 0 + for iterator in (PEP_ROOT.glob("pep-????.txt"), PEP_ROOT.glob("pep-????.rst")): + for file in iterator: + content = file.read_text(encoding='utf-8') + lines = content.splitlines() + failed += _output_error(file, lines, check_pep(file, lines)) + for file in PEP_ROOT.glob("docs/*.rst"): + content = file.read_text(encoding='utf-8') + lines = content.splitlines() + for line_num, line in enumerate(lines, start=1): + failed += _output_error(file, lines, check_direct_links(line_num, line)) + if failed > 0: + print(f"pep-lint failed: {failed} errors", file=sys.stderr) + return 1 + return 0 + + +def check_pep(filename, lines): + yield from check_headers(lines) + for line_num, line in enumerate(lines, start=1): + if filename.stem.removeprefix("pep-") in SKIP_DIRECT_PEP_LINK_CHECK: + continue + yield from check_direct_links(line_num, line.lstrip()) + + +def check_headers(lines): + yield from _validate_pep_number(next(iter(lines), "")) + + found_headers = {} + line_num = 0 + for line_num, line in enumerate(lines, start=1): + if line.strip() == "": + headers_end_line_num = line_num + break + if match := HEADER_PATTERN.match(line): + header = match[1] + if header in ALL_HEADERS: + if header not in found_headers: + found_headers[match[1]] = line_num + else: + yield line_num, f"Must not have duplicate header: {header} " + else: + yield line_num, f"Must not have invalid header: {header}" + else: + headers_end_line_num = line_num + + yield from _validate_required_headers(found_headers) + + shifted_line_nums = list(found_headers.values())[1:] + for i, (header, line_num) in enumerate(found_headers.items()): + start = line_num - 1 + end = headers_end_line_num - 1 + if i < len(found_headers) - 1: + end = shifted_line_nums[i] - 1 + remainder = "\n".join(lines[start:end]).removeprefix(f"{header}:") + if remainder != "": + if remainder[0] not in {" ", "\n"}: + yield line_num, f"Headers must have a space after the colon: {header}" + remainder = remainder.lstrip() + + if header == "Title": + yield from _validate_title(line_num, remainder) + elif header == "Author": + yield from _validate_author(line_num, remainder) + elif header == "Sponsor": + yield from _validate_sponsor(line_num, remainder) + elif header in {"BDFL-Delegate", "PEP-Delegate"}: + yield from _validate_delegate(line_num, remainder) + elif header == "Discussions-To": + yield from _validate_discussions_to(line_num, remainder) + elif header == "Status": + yield from _validate_status(line_num, remainder) + elif header == "Type": + yield from _validate_type(line_num, remainder) + elif header == "Topic": + yield from _validate_topic(line_num, remainder) + elif header == "Content-Type": + yield from _validate_content_type(line_num, remainder) + elif header in {"Requires", "Replaces", "Superseded-By"}: + yield from _validate_pep_references(line_num, remainder) + elif header == "Created": + yield from _validate_created(line_num, remainder) + elif header == "Python-Version": + yield from _validate_python_version(line_num, remainder) + elif header == "Post-History": + yield from _validate_post_history(line_num, remainder) + elif header == "Resolution": + yield from _validate_resolution(line_num, remainder) + + +def check_direct_links(line_num, line): + """Check that PEPs and RFCs aren't linked directly""" + + line = line.lstrip().lower() + if "dev/peps/pep-" in line or "peps.python.org/pep-" in line: + yield line_num, "Use the :pep:`NNN` role to refer to PEPs" + if "rfc-editor.org/rfc/" in line or "ietf.org/doc/html/rfc" in line: + yield line_num, "Use the :rfc:`NNN` role to refer to RFCs" + + +def _output_error(filename, lines, errors): + err_count = 0 + for line_num, msg in errors: + print(f"{filename}:{line_num}: |", file=sys.stderr) + print(f"{filename}:{line_num}: {msg}", file=sys.stderr) + print(f"{filename}:{line_num}: from: {lines[line_num - 1]!r}", file=sys.stderr) + print(f"{filename}:{line_num}: |", file=sys.stderr) + err_count += 1 + return err_count + + +########################### +# PEP Header Validators # +########################### + + +def _validate_required_headers(found_headers): + """PEPs must have all required headers, in the PEP 12 order""" + + if missing := REQUIRED_HEADERS.difference(found_headers): + for missing_header in sorted(missing, key=ALL_HEADERS.index): + yield 0, f"Must have required header: {missing_header}" + + ordered_headers = sorted(found_headers, key=ALL_HEADERS.index) + if list(found_headers) != ordered_headers: + order_str = ", ".join(ordered_headers) + yield 0, "Headers must be in PEP 12 order. Correct order: " + order_str + + +def _validate_pep_number(line): + """'PEP' header must be a number 1-9999""" + + if not line.startswith("PEP: "): + yield 1, "PEP must begin with the 'PEP:' header" + return + + pep_number = line.removeprefix("PEP: ").strip() + yield from _pep_num(1, pep_number, "'PEP:' header") + + +def _validate_title(line_num, line): + """'Title' must be 1-79 characters""" + + if len(line.strip()) == 0: + yield line_num, "PEP must have a title" + elif len(line.strip()) > 79: + yield line_num, "PEP title must be less than 80 characters" + + +def _validate_author(line_num, body): + """'Author' must be list of 'Name , …'""" + + lines = body.split("\n") + for offset, line in enumerate(lines): + if offset > 1 and line[:9].isspace(): + yield line_num + offset, f"Author line must not be over-indented" + if offset < len(lines) - 1: + if not line.endswith(","): + yield line_num + offset, "Author continuation lines must end with a comma" + yield from _email(line_num + offset, line, "Author") + + +def _validate_sponsor(line_num, line): + """'Sponsor' must have format 'Name '""" + + yield from _email(line_num, line, "Sponsor") + + +def _validate_delegate(line_num, line): + """'Delegate' must have format 'Name '""" + + if line.strip() != "": + yield from _email(line_num, line, "Delegate") + + +def _validate_discussions_to(line_num, line): + """'Discussions-To' must be a thread URL""" + + yield from _thread(line_num, line, "Discussions-To", discussions_to=True) + for suffix in "@python.org", "@googlegroups.com": + if line.endswith(suffix): + remainder = line.removesuffix(suffix) + if re.fullmatch(r'[\w\-]+', remainder) is None: + yield line_num, "Discussions-To must be a valid mailing list" + + +def _validate_status(line_num, line): + """'Status' must be a valid PEP status""" + + if line not in ALL_STATUSES: + yield line_num, "Status must be a valid PEP status" + + +def _validate_type(line_num, line): + """'Type' must be a valid PEP type""" + + if line not in {"Standards Track", "Informational", "Process"}: + yield line_num, "Type must be a valid PEP type" + + +def _validate_topic(line_num, line): + """'Topic' must be for a valid sub-index""" + + topics = line.split(", ") + unique_topics = set(topics) + if len(topics) > len(unique_topics): + yield line_num, "Topic must not contain duplicates" + + if unique_topics - {"Governance", "Packaging", "Typing", "Release"}: + yield line_num, "Topic must be for a valid sub-index" + + +def _validate_content_type(line_num, line): + """'Content-Type' must be 'text/x-rst'""" + + if line != "text/x-rst": + yield line_num, "Content-Type must be 'text/x-rst'" + + +def _validate_pep_references(line_num, line): + """`Requires`/`Replaces`/`Superseded-By` must be 'NNN' PEP IDs""" + + references = line.split(", ") + for reference in references: + yield from _pep_num(line_num, reference, "PEP reference") + + +def _validate_created(line_num, line): + """'Created' must be a 'DD-mmm-YYYY' date""" + + yield from _date(line_num, line, "Created") + + +def _validate_python_version(line_num, line): + """'Python-Version' must be an ``X.Y[.Z]`` version""" + + versions = line.split(", ") + for version in versions: + if version.count(".") > 2: + yield line_num, f"Python-Version must have two or three segments: {version}" + continue + + try: + major, minor, micro = version.split(".", 2) + except ValueError: + major, minor = version.split(".", 1) + micro = "" + + if major not in "123": + yield line_num, f"Python-Version major part must be 1, 2, or 3: {version}" + if not _is_digits(minor) and minor != "x": + yield line_num, f"Python-Version minor part must be numeric: {version}" + + if micro == "": + return + if minor == "x": + yield line_num, f"Python-Version micro part must be empty if minor part is 'x': {version}" + elif micro[0] == "0": + yield line_num, f"Python-Version micro part must not have leading zeros: {version}" + elif not _is_digits(micro): + yield line_num, f"Python-Version micro part must be numeric: {version}" + + +def _validate_post_history(line_num, body): + """'Post-History' must be '`DD-mmm-YYYY `__, …'""" + + body = body + if not body: + return + + for post in body.replace("\n", " ").removesuffix(",").split(", "): + post = post.strip() + + if not post.startswith("`") and not post.endswith(">`__"): + yield from _date(line_num, post, "Post-History") + else: + post_date, post_url = post[1:-4].split(" <") + yield from _date(line_num, post_date, "Post-History") + yield from _thread(line_num, post_url, "Post-History") + + +def _validate_resolution(line_num, line): + """'Resolution' must be a direct thread/message URL""" + + yield from _thread(line_num, line, "Resolution", allow_message=True) + + +######################## +# Validation Helpers # +######################## + +def _pep_num(line_num, pep_number, prefix): + if pep_number.startswith("0") and pep_number != "0": + yield line_num, f"{prefix} must not contain leading zeros: {pep_number}" + if not _is_digits(pep_number): + yield line_num, f"{prefix} must be numeric: {pep_number}" + elif not 0 <= int(pep_number) <= 9999: + yield line_num, f"{prefix} must be between 0 and 9999: {pep_number}" + + +def _is_digits(string): + return all(c in "0123456789" for c in string) + + +def _email(line_num, author_email, prefix): + if EMAIL_PATTERN.match(author_email.strip()) is None: + yield line_num, f"{prefix} must be a list of 'Name ' entries: {author_email}" + + +def _thread(line_num, url, prefix, allow_message=False, discussions_to=False): + msg = f"{prefix} must be a valid thread URL" + + if not url.startswith("https://"): + if not discussions_to: + yield line_num, msg + return + + if url.startswith("https://discuss.python.org/t/"): + # Discussions-To links must be the thread itself, not a post + pattern = DISCOURSE_THREAD_PATTERN if discussions_to else DISCOURSE_POST_PATTERN + + remainder = url.removeprefix("https://discuss.python.org/t/").removesuffix("/") + if pattern.fullmatch(remainder) is not None: + return + + if url.startswith("https://mail.python.org/pipermail/"): + remainder = url.removeprefix("https://mail.python.org/pipermail/") + if MAILMAN_2_PATTERN.fullmatch(remainder) is not None: + return + + if url.startswith("https://mail.python.org/archives/list/"): + remainder = url.removeprefix("https://mail.python.org/archives/list/") + if allow_message and MAILMAN_3_MESSAGE_PATTERN.fullmatch(remainder) is not None: + return + if MAILMAN_3_THREAD_PATTERN.fullmatch(remainder) is not None: + return + + yield line_num, msg + + +def _date(line_num, date_str, prefix): + try: + dt = datetime.datetime.strptime(date_str, "%d-%b-%Y") + except ValueError: + yield line_num, f"{prefix} must be a 'DD-mmm-YYYY' date: {date_str}" + return + + if dt.year < 1990: + yield line_num, f"{prefix} must not be before Python was invented: {date_str}" + if dt > (datetime.datetime.now() + datetime.timedelta(days=14)): + yield line_num, f"{prefix} must not be in the future: {date_str}" + + +if __name__ == '__main__': + raise SystemExit(check()) diff --git a/pep_sphinx_extensions/generate_rss.py b/pep_sphinx_extensions/generate_rss.py index a7120c9d6cb..5e5e0b8bc24 100644 --- a/pep_sphinx_extensions/generate_rss.py +++ b/pep_sphinx_extensions/generate_rss.py @@ -17,9 +17,6 @@ "and some meta-information like release procedure and schedules." ) -# get the directory with the PEP sources -PEP_ROOT = Path(__file__).parent - def _format_rfc_2822(datetime: dt.datetime) -> str: datetime = datetime.replace(tzinfo=dt.timezone.utc) From c6826faaa8660ba09d41bc36cee9f09eff954356 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 5 Aug 2023 23:40:38 +0100 Subject: [PATCH 04/83] remove ``docs/`` from pep-lint --- pep-lint.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pep-lint.py b/pep-lint.py index ffe2ae8cf72..0ac3ba4318e 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -80,11 +80,6 @@ def check(): content = file.read_text(encoding='utf-8') lines = content.splitlines() failed += _output_error(file, lines, check_pep(file, lines)) - for file in PEP_ROOT.glob("docs/*.rst"): - content = file.read_text(encoding='utf-8') - lines = content.splitlines() - for line_num, line in enumerate(lines, start=1): - failed += _output_error(file, lines, check_direct_links(line_num, line)) if failed > 0: print(f"pep-lint failed: {failed} errors", file=sys.stderr) return 1 From b4a8f13e241df6126c0c3ebf239845e74e18a0d8 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 5 Aug 2023 23:54:39 +0100 Subject: [PATCH 05/83] Fix GHA syntax --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d964c8141e8..5d3613a98ec 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -48,4 +48,4 @@ jobs: python-version: "3" - name: Run pep-lint - uses: python pep-lint.py + run: python pep-lint.py From ff95be9e5555c61537d42d072d2007c3380a5cbe Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 6 Aug 2023 17:18:57 +0100 Subject: [PATCH 06/83] datetime as dt --- pep-lint.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pep-lint.py b/pep-lint.py index 0ac3ba4318e..6b6f58d94d7 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -3,7 +3,7 @@ # This file is placed in the public domain or under the # CC0-1.0-Universal license, whichever is more permissive. -import datetime +import datetime as dt import re import sys from pathlib import Path @@ -411,14 +411,14 @@ def _thread(line_num, url, prefix, allow_message=False, discussions_to=False): def _date(line_num, date_str, prefix): try: - dt = datetime.datetime.strptime(date_str, "%d-%b-%Y") + parsed_date = dt.datetime.strptime(date_str, "%d-%b-%Y") except ValueError: yield line_num, f"{prefix} must be a 'DD-mmm-YYYY' date: {date_str}" return - if dt.year < 1990: + if parsed_date.year < 1990: yield line_num, f"{prefix} must not be before Python was invented: {date_str}" - if dt > (datetime.datetime.now() + datetime.timedelta(days=14)): + if parsed_date > (dt.datetime.now() + dt.timedelta(days=14)): yield line_num, f"{prefix} must not be in the future: {date_str}" From 4ad00459afe3db58bfca309636c04fd19ab10d74 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 6 Aug 2023 17:20:55 +0100 Subject: [PATCH 07/83] quotation marks --- pep-lint.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pep-lint.py b/pep-lint.py index 6b6f58d94d7..e1a0afcbd24 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -65,11 +65,11 @@ """, re.IGNORECASE | re.VERBOSE ) -DISCOURSE_THREAD_PATTERN = re.compile(r'([\w\-]+/)?\d+') -DISCOURSE_POST_PATTERN = re.compile(r'([\w\-]+/)?\d+(/\d+)?') -MAILMAN_2_PATTERN = re.compile(r'[\w\-]+/\d{4}-[A-Za-z]+/[A-Za-z0-9]+\.html') -MAILMAN_3_THREAD_PATTERN = re.compile(r'[\w\-]+@python\.org/thread/[A-Za-z0-9]+/?(#[A-Za-z0-9]+)?') -MAILMAN_3_MESSAGE_PATTERN = re.compile(r'[\w\-]+@python\.org/message/[A-Za-z0-9]+/?(#[A-Za-z0-9]+)?') +DISCOURSE_THREAD_PATTERN = re.compile(r"([\w\-]+/)?\d+") +DISCOURSE_POST_PATTERN = re.compile(r"([\w\-]+/)?\d+(/\d+)?") +MAILMAN_2_PATTERN = re.compile(r"[\w\-]+/\d{4}-[A-Za-z]+/[A-Za-z0-9]+\.html") +MAILMAN_3_THREAD_PATTERN = re.compile(r"[\w\-]+@python\.org/thread/[A-Za-z0-9]+/?(#[A-Za-z0-9]+)?") +MAILMAN_3_MESSAGE_PATTERN = re.compile(r"[\w\-]+@python\.org/message/[A-Za-z0-9]+/?(#[A-Za-z0-9]+)?") def check(): @@ -77,7 +77,7 @@ def check(): failed = 0 for iterator in (PEP_ROOT.glob("pep-????.txt"), PEP_ROOT.glob("pep-????.rst")): for file in iterator: - content = file.read_text(encoding='utf-8') + content = file.read_text(encoding="utf-8") lines = content.splitlines() failed += _output_error(file, lines, check_pep(file, lines)) if failed > 0: @@ -251,7 +251,7 @@ def _validate_discussions_to(line_num, line): for suffix in "@python.org", "@googlegroups.com": if line.endswith(suffix): remainder = line.removesuffix(suffix) - if re.fullmatch(r'[\w\-]+', remainder) is None: + if re.fullmatch("[\w\-]+", remainder) is None: yield line_num, "Discussions-To must be a valid mailing list" @@ -422,5 +422,5 @@ def _date(line_num, date_str, prefix): yield line_num, f"{prefix} must not be in the future: {date_str}" -if __name__ == '__main__': +if __name__ == "__main__": raise SystemExit(check()) From b0d6ba4f755891a63243d4f62beccd44a62040a8 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 6 Aug 2023 17:22:49 +0100 Subject: [PATCH 08/83] Use str builtins --- pep-lint.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pep-lint.py b/pep-lint.py index e1a0afcbd24..f5538e10381 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -370,7 +370,8 @@ def _pep_num(line_num, pep_number, prefix): def _is_digits(string): - return all(c in "0123456789" for c in string) + """Match a string of ASCII digits ([0-9]+).""" + return string.isascii() and string.isdigit() def _email(line_num, author_email, prefix): From 8b32d593bac73b13a147bdbbc7aa3616957ec2ac Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 6 Aug 2023 18:28:35 +0100 Subject: [PATCH 09/83] Add initial tests for pep-lint --- pep_sphinx_extensions/tests/test_pep_lint.py | 53 ++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 pep_sphinx_extensions/tests/test_pep_lint.py diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py new file mode 100644 index 00000000000..652c884ec8d --- /dev/null +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -0,0 +1,53 @@ +import importlib.util +import sys +from pathlib import Path + +# Import "pep-lint.py" as "pep_lint" +PEP_LINT_PATH = Path(__file__).resolve().parent.parent.parent.joinpath("pep-lint.py") +spec = importlib.util.spec_from_file_location("pep_lint", PEP_LINT_PATH) +sys.modules["pep_lint"] = pep_lint = importlib.util.module_from_spec(spec) +spec.loader.exec_module(pep_lint) + + +def test_header_pattern_capitalisation(): + pattern = pep_lint.HEADER_PATTERN + + assert pattern.match("Header:") is not None + assert pattern.match("header:") is not None + assert pattern.match("hEADER:") is not None + assert pattern.match("hEaDeR:") is not None + + +def test_header_pattern_trailing_spaces(): + pattern = pep_lint.HEADER_PATTERN + + assert pattern.match("Header:") is not None + assert pattern.match("Header: ") is not None + assert pattern.match("Header: ") is not None + + +def test_header_pattern_trailing_content(): + pattern = pep_lint.HEADER_PATTERN + + assert pattern.match("Header: Text") is not None + assert pattern.match("Header: 123") is not None + assert pattern.match("Header: !") is not None + assert pattern.match("Header:Text") is None + assert pattern.match("Header:123") is None + assert pattern.match("Header:!") is None + + +def test_header_pattern_colon_position(): + pattern = pep_lint.HEADER_PATTERN + + assert pattern.match("Header") is None + assert pattern.match("Header : ") is None + assert pattern.match("Header :") is None + + +def test_header_pattern_separators(): + pattern = pep_lint.HEADER_PATTERN + + assert pattern.match("Hyphenated-Header:") is not None + assert pattern.match("Underscored_Header:") is None + assert pattern.match("Spaced Header:") is None From d2f80c74a0602d76a1122728745891e33c9734a8 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 6 Aug 2023 18:29:55 +0100 Subject: [PATCH 10/83] Make tests stricter --- pep_sphinx_extensions/tests/test_pep_lint.py | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index 652c884ec8d..dbaf7eb3d1d 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -12,26 +12,26 @@ def test_header_pattern_capitalisation(): pattern = pep_lint.HEADER_PATTERN - assert pattern.match("Header:") is not None - assert pattern.match("header:") is not None - assert pattern.match("hEADER:") is not None - assert pattern.match("hEaDeR:") is not None + assert pattern.match("Header:")[1] == "Header" + assert pattern.match("header:")[1] == "header" + assert pattern.match("hEADER:")[1] == "hEADER" + assert pattern.match("hEaDeR:")[1] == "hEaDeR" def test_header_pattern_trailing_spaces(): pattern = pep_lint.HEADER_PATTERN - assert pattern.match("Header:") is not None - assert pattern.match("Header: ") is not None - assert pattern.match("Header: ") is not None + assert pattern.match("Header:")[1] == "Header" + assert pattern.match("Header: ")[1] == "Header" + assert pattern.match("Header: ")[1] == "Header" def test_header_pattern_trailing_content(): pattern = pep_lint.HEADER_PATTERN - assert pattern.match("Header: Text") is not None - assert pattern.match("Header: 123") is not None - assert pattern.match("Header: !") is not None + assert pattern.match("Header: Text")[1] == "Header" + assert pattern.match("Header: 123")[1] == "Header" + assert pattern.match("Header: !")[1] == "Header" assert pattern.match("Header:Text") is None assert pattern.match("Header:123") is None assert pattern.match("Header:!") is None @@ -48,6 +48,6 @@ def test_header_pattern_colon_position(): def test_header_pattern_separators(): pattern = pep_lint.HEADER_PATTERN - assert pattern.match("Hyphenated-Header:") is not None + assert pattern.match("Hyphenated-Header:")[1] == "Hyphenated-Header" assert pattern.match("Underscored_Header:") is None assert pattern.match("Spaced Header:") is None From 018c726d6b1433e3df5c9a5c7e0c44f6b455c3cc Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 6 Aug 2023 19:08:38 +0100 Subject: [PATCH 11/83] typo --- pep-lint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-lint.py b/pep-lint.py index f5538e10381..bd44f474e76 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -251,7 +251,7 @@ def _validate_discussions_to(line_num, line): for suffix in "@python.org", "@googlegroups.com": if line.endswith(suffix): remainder = line.removesuffix(suffix) - if re.fullmatch("[\w\-]+", remainder) is None: + if re.fullmatch(r"[\w\-]+", remainder) is None: yield line_num, "Discussions-To must be a valid mailing list" From a088f99a4ea9c5da28495236197a997148aa28c0 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Mon, 7 Aug 2023 14:36:07 +0100 Subject: [PATCH 12/83] Initial email tests --- pep-lint.py | 67 +++++++++++++++----- pep_sphinx_extensions/tests/test_pep_lint.py | 20 ++++++ 2 files changed, 70 insertions(+), 17 deletions(-) diff --git a/pep-lint.py b/pep-lint.py index bd44f474e76..14966d2ca65 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -53,18 +53,12 @@ # PEPs that are allowed to link directly to PEPs SKIP_DIRECT_PEP_LINK_CHECK = frozenset({"0009", "0287", "0676", "0684", "8001"}) -HEADER_PATTERN = re.compile(r"^([a-z\-]+):(?: |$)", re.IGNORECASE) -EMAIL_PATTERN = re.compile( - r""" - ([^\W\d_]|[. ])+ # Name; any sequence of unicode letters, full stops, or spaces - ( < # Start of optional email part: ' <' - [a-z0-9!#$%&'*+\-/=?^_{|}~.]+ # Local part; ASCII letters, digits, and legal special characters - (@| at ) # Local and domain parts separator - (\w+\.)+[a-z0-9-]+ # Domain, with at least two segments - >)? # End of optional email part: '>' - """, - re.IGNORECASE | re.VERBOSE -) +# any sequence of letters or '-', followed by a single ':' and a space or end of line +HEADER_PATTERN = re.compile(r"^([a-z\-]+):(?: |$)", re.ASCII|re.IGNORECASE) +# any sequence of unicode letters or legal special characters +NAME_PATTERN = re.compile(r"([^\W\d_]|[ ',\-.])+") +# any sequence of ASCII letters, digits, or legal special characters +EMAIL_LOCAL_PART_PATTERN = re.compile(r"[\w!#$%&'*+\-/=?^{|}~.]+", re.ASCII) DISCOURSE_THREAD_PATTERN = re.compile(r"([\w\-]+/)?\d+") DISCOURSE_POST_PATTERN = re.compile(r"([\w\-]+/)?\d+(/\d+)?") MAILMAN_2_PATTERN = re.compile(r"[\w\-]+/\d{4}-[A-Za-z]+/[A-Za-z0-9]+\.html") @@ -228,7 +222,8 @@ def _validate_author(line_num, body): if offset < len(lines) - 1: if not line.endswith(","): yield line_num + offset, "Author continuation lines must end with a comma" - yield from _email(line_num + offset, line, "Author") + for part in line.removesuffix(",").split(", "): + yield from _email(line_num + offset, part, "Author") def _validate_sponsor(line_num, line): @@ -240,8 +235,16 @@ def _validate_sponsor(line_num, line): def _validate_delegate(line_num, line): """'Delegate' must have format 'Name '""" - if line.strip() != "": - yield from _email(line_num, line, "Delegate") + if line.strip() == "": + return + + # PEP 451 + if ", " in line: + for part in line.removesuffix(",").split(", "): + yield from _email(line_num, part, "Delegate") + return + + yield from _email(line_num, line, "Delegate") def _validate_discussions_to(line_num, line): @@ -375,8 +378,38 @@ def _is_digits(string): def _email(line_num, author_email, prefix): - if EMAIL_PATTERN.match(author_email.strip()) is None: - yield line_num, f"{prefix} must be a list of 'Name ' entries: {author_email}" + author_email = author_email.strip() + + name_match = NAME_PATTERN.match(author_email) + if name_match is None: + yield line_num, f"{prefix} entries must begin with a valid 'Name': {author_email!r}" + + email_text = author_email.removeprefix(name_match[0].rstrip()) + if not email_text: + # Does not have the optional email part + return + + if not email_text.startswith(" <") or not email_text.endswith(">"): + yield line_num, f"{prefix} entries must be formatted as 'Name ': {author_email!r}" + email_text = email_text.removeprefix(" <").removesuffix(">") + + if "@" in email_text: + local, domain = email_text.split("@", 1) + elif " at " in email_text: + local, domain = email_text.split(" at ", 1) + else: + yield line_num, f"{prefix} entries must contain a valid email address: {author_email!r}" + return + if EMAIL_LOCAL_PART_PATTERN.fullmatch(local) is None or _invalid_domain(domain): + yield line_num, f"{prefix} entries must contain a valid email address: {author_email!r}" + + +def _invalid_domain(domain_part): + *labels, root = domain_part.split(".") + for label in labels: + if not label.replace("-", "").isalnum(): + return False + return not root.isalnum() or not root.isascii() def _thread(line_num, url, prefix, allow_message=False, discussions_to=False): diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index dbaf7eb3d1d..bf8b605f534 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -2,6 +2,8 @@ import sys from pathlib import Path +import pytest + # Import "pep-lint.py" as "pep_lint" PEP_LINT_PATH = Path(__file__).resolve().parent.parent.parent.joinpath("pep-lint.py") spec = importlib.util.spec_from_file_location("pep_lint", PEP_LINT_PATH) @@ -51,3 +53,21 @@ def test_header_pattern_separators(): assert pattern.match("Hyphenated-Header:")[1] == "Hyphenated-Header" assert pattern.match("Underscored_Header:") is None assert pattern.match("Spaced Header:") is None + + +@pytest.mark.parametrize( + ("email", "expected_warnings"), + [ + ("Cardinal Ximénez", {}), + ] +) +def test_email_checker(email, expected_warnings): + + warnings = list(pep_lint._email(1, email, "")) + + if "valid name" in expected_warnings: + expected_warnings.remove("valid name") + matching = [warning for warning in warnings + if f" entries must begin with a valid 'Name': {email!r}" == warning] + assert len(matching) == 1 + From 8fb81a64e56e2bde2acfeac5d88c0a06cf477444 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 12:20:42 +0100 Subject: [PATCH 13/83] Fully test _email --- pep-lint.py | 33 +++-- pep_sphinx_extensions/tests/test_pep_lint.py | 148 +++++++++++++++++-- pytest.ini | 1 + 3 files changed, 162 insertions(+), 20 deletions(-) diff --git a/pep-lint.py b/pep-lint.py index 14966d2ca65..e12c1a821cc 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -56,7 +56,7 @@ # any sequence of letters or '-', followed by a single ':' and a space or end of line HEADER_PATTERN = re.compile(r"^([a-z\-]+):(?: |$)", re.ASCII|re.IGNORECASE) # any sequence of unicode letters or legal special characters -NAME_PATTERN = re.compile(r"([^\W\d_]|[ ',\-.])+") +NAME_PATTERN = re.compile(r"(?:[^\W\d_]|[ ',\-.])+(?: |$)") # any sequence of ASCII letters, digits, or legal special characters EMAIL_LOCAL_PART_PATTERN = re.compile(r"[\w!#$%&'*+\-/=?^{|}~.]+", re.ASCII) DISCOURSE_THREAD_PATTERN = re.compile(r"([\w\-]+/)?\d+") @@ -240,7 +240,7 @@ def _validate_delegate(line_num, line): # PEP 451 if ", " in line: - for part in line.removesuffix(",").split(", "): + for part in line.removesuffix(", ").split(", "): yield from _email(line_num, part, "Delegate") return @@ -380,23 +380,36 @@ def _is_digits(string): def _email(line_num, author_email, prefix): author_email = author_email.strip() - name_match = NAME_PATTERN.match(author_email) - if name_match is None: - yield line_num, f"{prefix} entries must begin with a valid 'Name': {author_email!r}" + if author_email.count("<") > 1: + msg = f"{prefix} entries must not contain multiple '<': {author_email!r}" + yield line_num, msg + if author_email.count(">") > 1: + msg = f"{prefix} entries must not contain multiple '>': {author_email!r}" + yield line_num, msg + if author_email.count("@") > 1: + msg = f"{prefix} entries must not contain multiple '@': {author_email!r}" + yield line_num, msg + + author = author_email.split("<", 1)[0].rstrip() + if NAME_PATTERN.fullmatch(author) is None: + msg = f"{prefix} entries must begin with a valid 'Name': {author_email!r}" + yield line_num, msg + return - email_text = author_email.removeprefix(name_match[0].rstrip()) + email_text = author_email.removeprefix(author) if not email_text: # Does not have the optional email part return if not email_text.startswith(" <") or not email_text.endswith(">"): - yield line_num, f"{prefix} entries must be formatted as 'Name ': {author_email!r}" + msg = f"{prefix} entries must be formatted as 'Name ': {author_email!r}" + yield line_num, msg email_text = email_text.removeprefix(" <").removesuffix(">") if "@" in email_text: - local, domain = email_text.split("@", 1) + local, domain = email_text.rsplit("@", 1) elif " at " in email_text: - local, domain = email_text.split(" at ", 1) + local, domain = email_text.rsplit(" at ", 1) else: yield line_num, f"{prefix} entries must contain a valid email address: {author_email!r}" return @@ -408,7 +421,7 @@ def _invalid_domain(domain_part): *labels, root = domain_part.split(".") for label in labels: if not label.replace("-", "").isalnum(): - return False + return True return not root.isalnum() or not root.isascii() diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index bf8b605f534..1caded19b0d 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -58,16 +58,144 @@ def test_header_pattern_separators(): @pytest.mark.parametrize( ("email", "expected_warnings"), [ - ("Cardinal Ximénez", {}), - ] + # ... entries must not contain multiple '...' + ("Cardinal Ximénez <<", {"multiple <"}), + ("Cardinal Ximénez <<<", {"multiple <"}), + ("Cardinal Ximénez >>", {"multiple >"}), + ("Cardinal Ximénez >>>", {"multiple >"}), + ("Cardinal Ximénez <<<>>>", {"multiple <", "multiple >"}), + ("Cardinal Ximénez @@", {"multiple @"}), + ("Cardinal Ximénez <<@@@>", {"multiple <", "multiple @"}), + ("Cardinal Ximénez <@@@>>", {"multiple >", "multiple @"}), + ("Cardinal Ximénez <<@@>>", {"multiple <", "multiple >", "multiple @"}), + # valid names + ("Cardinal Ximénez", set()), + (" Cardinal Ximénez", set()), + ("\t\tCardinal Ximénez", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez\t\t", set()), + ("Cardinal O'Ximénez", set()), + ("Cardinal Ximénez, Inquisitor", set()), + ("Cardinal Ximénez-Biggles", set()), + ("Cardinal Ximénez-Biggles, Inquisitor", set()), + ("Cardinal T. S. I. Ximénez", set()), + # ... entries must have a valid 'Name' + ("Cardinal_Ximénez", {"valid name"}), + ("Cardinal Ximénez 3", {"valid name"}), + ("~ Cardinal Ximénez ~", {"valid name"}), + ("Cardinal Ximénez!", {"valid name"}), + ("@Cardinal Ximénez", {"valid name"}), + ("Cardinal_Ximénez <>", {"valid name"}), + ("Cardinal Ximénez 3 <>", {"valid name"}), + ("~ Cardinal Ximénez ~ <>", {"valid name"}), + ("Cardinal Ximénez! <>", {"valid name"}), + ("@Cardinal Ximénez <>", {"valid name"}), + # ... entries must be formatted as 'Name ' + ("Cardinal Ximénez<>", {"name "}), + ("Cardinal Ximénez<", {"name "}), + ("Cardinal Ximénez <", {"name "}), + ("Cardinal Ximénez <", {"name "}), + ("Cardinal Ximénez <>", {"name "}), + # ... entries must contain a valid email address (missing) + ("Cardinal Ximénez <>", {"valid email"}), + ("Cardinal Ximénez <> ", {"valid email"}), + ("Cardinal Ximénez <@> ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez < at > ", {"valid email"}), + # ... entries must contain a valid email address (local) + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez < Cardinal Ximenez @spanish.inquisition> ", {"valid email"}), + ("Cardinal Ximénez <(Cardinal.Ximenez)@spanish.inquisition>", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"multiple <", "multiple >", "valid email"}), + ("Cardinal Ximénez ", {"multiple @", "valid email"}), + (r"Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez <[Cardinal.Ximenez]@spanish.inquisition>", {"valid email"}), + ('Cardinal Ximénez <"Cardinal"Ximenez"@spanish.inquisition>', {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + # ... entries must contain a valid email address (domain) + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + # valid name-emails + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez <{Cardinal.Ximenez}@spanish.inquisition>", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ], + # call str() on each parameterised value in the test ID. + ids=str, ) -def test_email_checker(email, expected_warnings): - - warnings = list(pep_lint._email(1, email, "")) +def test_email_checker(email: str, expected_warnings: set): + warnings = [warning for (_, warning) in pep_lint._email(1, email, "")] + + found_warnings = set() + email = email.strip() + + if "multiple <" in expected_warnings: + found_warnings.add("multiple <") + expected = f" entries must not contain multiple '<': {email!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "multiple >" in expected_warnings: + found_warnings.add("multiple >") + expected = f" entries must not contain multiple '>': {email!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "multiple @" in expected_warnings: + found_warnings.add("multiple @") + expected = f" entries must not contain multiple '@': {email!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings if "valid name" in expected_warnings: - expected_warnings.remove("valid name") - matching = [warning for warning in warnings - if f" entries must begin with a valid 'Name': {email!r}" == warning] - assert len(matching) == 1 - + found_warnings.add("valid name") + expected = f" entries must begin with a valid 'Name': {email!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "name " in expected_warnings: + found_warnings.add("name ") + expected = f" entries must be formatted as 'Name ': {email!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "valid email" in expected_warnings: + found_warnings.add("valid email") + expected = f" entries must contain a valid email address: {email!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if expected_warnings == set(): + assert warnings == [], warnings + + assert found_warnings == expected_warnings diff --git a/pytest.ini b/pytest.ini index 10f7034edce..880e68a317a 100644 --- a/pytest.ini +++ b/pytest.ini @@ -11,3 +11,4 @@ filterwarnings = minversion = 6.0 testpaths = pep_sphinx_extensions xfail_strict = True +disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True From 30ccbe7547242b9ed85bdc2a83db09df299aaa9f Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 12:43:11 +0100 Subject: [PATCH 14/83] Support passing a subset of PEPs via argv --- pep-lint.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/pep-lint.py b/pep-lint.py index e12c1a821cc..8b74e0d07b0 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -4,6 +4,7 @@ # CC0-1.0-Universal license, whichever is more permissive. import datetime as dt +import itertools import re import sys from pathlib import Path @@ -66,16 +67,22 @@ MAILMAN_3_MESSAGE_PATTERN = re.compile(r"[\w\-]+@python\.org/message/[A-Za-z0-9]+/?(#[A-Za-z0-9]+)?") -def check(): +def check(filenames=(), /): """The main entry-point.""" failed = 0 - for iterator in (PEP_ROOT.glob("pep-????.txt"), PEP_ROOT.glob("pep-????.rst")): - for file in iterator: + if not filenames: + filenames = itertools.chain(PEP_ROOT.glob("pep-????.txt"), PEP_ROOT.glob("pep-????.rst")) + for file in filenames: + try: content = file.read_text(encoding="utf-8") + except FileNotFoundError: + failed += _output_error(file, [''], [(1, "Could not read PEP!")]) + else: lines = content.splitlines() failed += _output_error(file, lines, check_pep(file, lines)) if failed > 0: - print(f"pep-lint failed: {failed} errors", file=sys.stderr) + s = "s" * (failed != 1) + print(f"pep-lint failed: {failed} error{s}", file=sys.stderr) return 1 return 0 @@ -470,4 +477,5 @@ def _date(line_num, date_str, prefix): if __name__ == "__main__": - raise SystemExit(check()) + # TODO -h / --help / -? + raise SystemExit(check(sys.argv[1:])) From fa8a54e65297ffb10bfa6d52ed66073be4cfa590 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 12:47:12 +0100 Subject: [PATCH 15/83] Support passing a subset of PEPs via argv --- pep-lint.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/pep-lint.py b/pep-lint.py index 8b74e0d07b0..1734fe4f6cc 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -69,24 +69,25 @@ def check(filenames=(), /): """The main entry-point.""" - failed = 0 if not filenames: filenames = itertools.chain(PEP_ROOT.glob("pep-????.txt"), PEP_ROOT.glob("pep-????.rst")) - for file in filenames: - try: - content = file.read_text(encoding="utf-8") - except FileNotFoundError: - failed += _output_error(file, [''], [(1, "Could not read PEP!")]) - else: - lines = content.splitlines() - failed += _output_error(file, lines, check_pep(file, lines)) - if failed > 0: - s = "s" * (failed != 1) - print(f"pep-lint failed: {failed} error{s}", file=sys.stderr) + if (count := sum(map(check_file, filenames))) > 0: + s = "s" * (count != 1) + print(f"pep-lint failed: {count} error{s}", file=sys.stderr) return 1 return 0 +def check_file(filename, /): + try: + content = filename.read_text(encoding="utf-8") + except FileNotFoundError: + return _output_error(filename, [''], [(1, "Could not read PEP!")]) + else: + lines = content.splitlines() + return _output_error(filename, lines, check_pep(filename, lines)) + + def check_pep(filename, lines): yield from check_headers(lines) for line_num, line in enumerate(lines, start=1): From 61160562419560b5a9b72b00f5cba60733ab982e Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 12:48:10 +0100 Subject: [PATCH 16/83] Add pep-lint hook to .pre-commit-config.yaml --- .pre-commit-config.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5012191ca19..4e1766a915f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -98,6 +98,14 @@ repos: name: "Check for common misspellings in text files" stages: [manual] + # Hook to run 'pep-lint.py' + - id: pep-lint + name: Check PEP metadata + entry: 'python pep-lint.py' + language: system + files: 'pep-\d{4}.(txt|rst)' + pass_filenames: false + # Local checks for PEP headers and more - repo: local hooks: From 84a08df356ee652652cfa8f41cb1e781a9d1b88e Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 12:48:41 +0100 Subject: [PATCH 17/83] Parameterise header tests Co-authored-by: Hugo van Kemenade --- pep_sphinx_extensions/tests/test_pep_lint.py | 82 ++++++++++---------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index 1caded19b0d..42127e28f02 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -11,48 +11,50 @@ spec.loader.exec_module(pep_lint) -def test_header_pattern_capitalisation(): - pattern = pep_lint.HEADER_PATTERN - - assert pattern.match("Header:")[1] == "Header" - assert pattern.match("header:")[1] == "header" - assert pattern.match("hEADER:")[1] == "hEADER" - assert pattern.match("hEaDeR:")[1] == "hEaDeR" - - -def test_header_pattern_trailing_spaces(): - pattern = pep_lint.HEADER_PATTERN - - assert pattern.match("Header:")[1] == "Header" - assert pattern.match("Header: ")[1] == "Header" - assert pattern.match("Header: ")[1] == "Header" - - -def test_header_pattern_trailing_content(): - pattern = pep_lint.HEADER_PATTERN - - assert pattern.match("Header: Text")[1] == "Header" - assert pattern.match("Header: 123")[1] == "Header" - assert pattern.match("Header: !")[1] == "Header" - assert pattern.match("Header:Text") is None - assert pattern.match("Header:123") is None - assert pattern.match("Header:!") is None - - -def test_header_pattern_colon_position(): - pattern = pep_lint.HEADER_PATTERN - - assert pattern.match("Header") is None - assert pattern.match("Header : ") is None - assert pattern.match("Header :") is None - +@pytest.mark.parametrize( + ("test_input", "expected"), + [ + # capitalisation + ("Header:", "Header"), + ("header:", "header"), + ("hEADER:", "hEADER"), + ("hEaDeR:", "hEaDeR"), + # trailing spaces + ("Header: ", "Header"), + ("Header: ", "Header"), + ("Header: \t", "Header"), + # trailing content + ("Header: Text", "Header"), + ("Header: 123", "Header"), + ("Header: !", "Header"), + # separators + ("Hyphenated-Header:", "Hyphenated-Header"), + ], +) +def test_header_pattern(test_input, expected): + assert pep_lint.HEADER_PATTERN.match(test_input)[1] == expected -def test_header_pattern_separators(): - pattern = pep_lint.HEADER_PATTERN - assert pattern.match("Hyphenated-Header:")[1] == "Hyphenated-Header" - assert pattern.match("Underscored_Header:") is None - assert pattern.match("Spaced Header:") is None +@pytest.mark.parametrize( + "test_input", + [ + # trailing content + "Header:Text", + "Header:123", + "Header:!", + # colon position + "Header", + "Header : ", + "Header :", + "SemiColonHeader;", + # separators + "Underscored_Header:", + "Spaced Header:", + "Plus+Header:", + ], +) +def test_header_pattern_no_match(test_input): + assert pep_lint.HEADER_PATTERN.match(test_input) is None @pytest.mark.parametrize( From 3b20b565cd7bf5c41cc8fd675e5bf4bb63142ece Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 12:52:10 +0100 Subject: [PATCH 18/83] Coverage --- pytest.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index 880e68a317a..cbd9737f8fe 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,5 @@ [pytest] -addopts = -r a --strict-config --strict-markers --import-mode=importlib --cov pep_sphinx_extensions --cov-report html --cov-report xml +addopts = -r a --strict-config --strict-markers --import-mode=importlib --cov pep_lint --cov pep_sphinx_extensions --cov-report html --cov-report xml empty_parameter_set_mark = fail_at_collect filterwarnings = error From 89199963f0049718295c28dc7b3908b2b05af251 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 13:23:25 +0100 Subject: [PATCH 19/83] Test _pep_num --- pep-lint.py | 9 ++- pep_sphinx_extensions/tests/test_pep_lint.py | 77 ++++++++++++++++++++ 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/pep-lint.py b/pep-lint.py index 1734fe4f6cc..c7bd6cf574d 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -372,12 +372,15 @@ def _validate_resolution(line_num, line): ######################## def _pep_num(line_num, pep_number, prefix): + if pep_number == "": + yield line_num, f"{prefix} must not be blank: {pep_number!r}" + return if pep_number.startswith("0") and pep_number != "0": - yield line_num, f"{prefix} must not contain leading zeros: {pep_number}" + yield line_num, f"{prefix} must not contain leading zeros: {pep_number!r}" if not _is_digits(pep_number): - yield line_num, f"{prefix} must be numeric: {pep_number}" + yield line_num, f"{prefix} must be numeric: {pep_number!r}" elif not 0 <= int(pep_number) <= 9999: - yield line_num, f"{prefix} must be between 0 and 9999: {pep_number}" + yield line_num, f"{prefix} must be between 0 and 9999: {pep_number!r}" def _is_digits(string): diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index 42127e28f02..f5f7d741fd4 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -57,6 +57,83 @@ def test_header_pattern_no_match(test_input): assert pep_lint.HEADER_PATTERN.match(test_input) is None +@pytest.mark.parametrize( + ("pep_number", "expected_warnings"), + [ + # valid entries + ("0", set()), + ("1", set()), + ("12", set()), + ("20", set()), + ("101", set()), + ("801", set()), + ("3099", set()), + ("9999", set()), + # empty + ("", {"not blank"}), + # leading zeros + ("01", {"leading zeros"}), + ("001", {"leading zeros"}), + ("0001", {"leading zeros"}), + ("00001", {"leading zeros"}), + # non-numeric + ("a", {"non-numeric"}), + ("123abc", {"non-numeric"}), + ("0123A", {"leading zeros", "non-numeric"}), + ("0", {"non-numeric"}), + ("101", {"non-numeric"}), + ("9999", {"non-numeric"}), + ("𝟎", {"non-numeric"}), + ("𝟘", {"non-numeric"}), + ("𝟏𝟚", {"non-numeric"}), + ("𝟸𝟬", {"non-numeric"}), + ("-1", {"non-numeric"}), + ("+1", {"non-numeric"}), + # out of bounds + ("10000", {"range"}), + ("54321", {"range"}), + ("99999", {"range"}), + ("32768", {"range"}), + ], + # call str() on each parameterised value in the test ID. + ids=str, +) +def test_pep_num_checker(pep_number: str, expected_warnings: set): + warnings = [warning for (_, warning) in pep_lint._pep_num(1, pep_number, "")] + + found_warnings = set() + pep_number = pep_number.strip() + + if "not blank" in expected_warnings: + found_warnings.add("not blank") + expected = f" must not be blank: {pep_number!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "leading zeros" in expected_warnings: + found_warnings.add("leading zeros") + expected = f" must not contain leading zeros: {pep_number!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "non-numeric" in expected_warnings: + found_warnings.add("non-numeric") + expected = f" must be numeric: {pep_number!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "range" in expected_warnings: + found_warnings.add("range") + expected = f" must be between 0 and 9999: {pep_number!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if expected_warnings == set(): + assert warnings == [], warnings + + assert found_warnings == expected_warnings + + @pytest.mark.parametrize( ("email", "expected_warnings"), [ From ab876b140c118918c00704429b275750996ae8b0 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 15:43:43 +0100 Subject: [PATCH 20/83] Test _thread --- pep-0011.txt | 2 +- pep-0376.txt | 2 +- pep-0623.rst | 2 +- pep-lint.py | 78 ++++++--- pep_sphinx_extensions/tests/test_pep_lint.py | 162 +++++++++++++++++++ 5 files changed, 225 insertions(+), 21 deletions(-) diff --git a/pep-0011.txt b/pep-0011.txt index ffbd2e76659..63131c3a277 100644 --- a/pep-0011.txt +++ b/pep-0011.txt @@ -8,7 +8,7 @@ Content-Type: text/x-rst Created: 07-Jul-2002 Post-History: `18-Aug-2007 `__, `14-May-2014 `__, - `20-Feb-2015 `__, + `20-Feb-2015 `__, `10-Mar-2022 `__, diff --git a/pep-0376.txt b/pep-0376.txt index 5cc2bfc8f3c..60119c69e74 100644 --- a/pep-0376.txt +++ b/pep-0376.txt @@ -7,7 +7,7 @@ Topic: Packaging Content-Type: text/x-rst Created: 22-Feb-2009 Python-Version: 2.7, 3.2 -Post-History: `22-Jun-2009 `__ +Post-History: `22-Jun-2009 `__ .. canonical-pypa-spec:: :ref:`packaging:core-metadata` diff --git a/pep-0623.rst b/pep-0623.rst index ce0227d5527..0d182add1c0 100644 --- a/pep-0623.rst +++ b/pep-0623.rst @@ -8,7 +8,7 @@ Type: Standards Track Content-Type: text/x-rst Created: 25-Jun-2020 Python-Version: 3.10 -Resolution: https://mail.python.org/archives/list/python-dev@python.org/thread/VQKDIZLZ6HF2MLTNCUFURK2IFTXVQEYA/#VQKDIZLZ6HF2MLTNCUFURK2IFTXVQEYA +Resolution: https://mail.python.org/archives/list/python-dev@python.org/thread/VQKDIZLZ6HF2MLTNCUFURK2IFTXVQEYA/ Abstract diff --git a/pep-lint.py b/pep-lint.py index c7bd6cf574d..03784b9ebc4 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -62,9 +62,9 @@ EMAIL_LOCAL_PART_PATTERN = re.compile(r"[\w!#$%&'*+\-/=?^{|}~.]+", re.ASCII) DISCOURSE_THREAD_PATTERN = re.compile(r"([\w\-]+/)?\d+") DISCOURSE_POST_PATTERN = re.compile(r"([\w\-]+/)?\d+(/\d+)?") -MAILMAN_2_PATTERN = re.compile(r"[\w\-]+/\d{4}-[A-Za-z]+/[A-Za-z0-9]+\.html") -MAILMAN_3_THREAD_PATTERN = re.compile(r"[\w\-]+@python\.org/thread/[A-Za-z0-9]+/?(#[A-Za-z0-9]+)?") -MAILMAN_3_MESSAGE_PATTERN = re.compile(r"[\w\-]+@python\.org/message/[A-Za-z0-9]+/?(#[A-Za-z0-9]+)?") +MAILMAN_2_PATTERN = re.compile(r"[\w\-]+/\d{4}-[a-z]+/\d+\.html", re.ASCII|re.IGNORECASE) +MAILMAN_3_THREAD_PATTERN = re.compile(r"[\w\-]+@python\.org/thread/[a-z0-9]+/?", re.ASCII|re.IGNORECASE) +MAILMAN_3_MESSAGE_PATTERN = re.compile(r"[\w\-]+@python\.org/message/[a-z0-9]+/?(#[a-z0-9]+)?", re.ASCII|re.IGNORECASE) def check(filenames=(), /): @@ -350,15 +350,14 @@ def _validate_post_history(line_num, body): if not body: return - for post in body.replace("\n", " ").removesuffix(",").split(", "): - post = post.strip() - - if not post.startswith("`") and not post.endswith(">`__"): - yield from _date(line_num, post, "Post-History") - else: - post_date, post_url = post[1:-4].split(" <") - yield from _date(line_num, post_date, "Post-History") - yield from _thread(line_num, post_url, "Post-History") + for offset, line in enumerate(body.removesuffix(",").split("\n"), start=line_num): + for post in line.removesuffix(",").strip().split(", "): + if not post.startswith("`") and not post.endswith(">`__"): + yield from _date(offset, post, "Post-History") + else: + post_date, post_url = post[1:-4].split(" <") + yield from _date(offset, post_date, "Post-History") + yield from _thread(offset, post_url, "Post-History") def _validate_resolution(line_num, line): @@ -436,7 +435,11 @@ def _invalid_domain(domain_part): return not root.isalnum() or not root.isascii() -def _thread(line_num, url, prefix, allow_message=False, discussions_to=False): +def _thread(line_num, url, prefix, *, allow_message=False, discussions_to=False): + if allow_message and discussions_to: + msg = "allow_message and discussions_to cannot both be True" + raise ValueError(msg) + msg = f"{prefix} must be a valid thread URL" if not url.startswith("https://"): @@ -445,12 +448,51 @@ def _thread(line_num, url, prefix, allow_message=False, discussions_to=False): return if url.startswith("https://discuss.python.org/t/"): - # Discussions-To links must be the thread itself, not a post - pattern = DISCOURSE_THREAD_PATTERN if discussions_to else DISCOURSE_POST_PATTERN - remainder = url.removeprefix("https://discuss.python.org/t/").removesuffix("/") - if pattern.fullmatch(remainder) is not None: - return + + # Discussions-To links must be the thread itself, not a post + if discussions_to: + # The equivalent pattern is similar to '([\w\-]+/)?\d+', + # but the topic name must contain a non-numeric character + + # We use ``str.rpartition`` as the topic name is optional + topic_name, _, topic_id = remainder.rpartition("/") + if topic_name == '' and _is_digits(topic_id): + return + topic_name = topic_name.replace("-", "0").replace("_", "0") + # the topic name must not be entirely numeric + valid_topic_name = not _is_digits(topic_name) and topic_name.isalnum() + if valid_topic_name and _is_digits(topic_id): + return + else: + # The equivalent pattern is similar to '([\w\-]+/)?\d+(/\d+)?', + # but the topic name must contain a non-numeric character + if remainder.count("/") == 2: + # When there are three parts, the URL must be "topic-name/topic-id/post-id". + topic_name, topic_id, post_id = remainder.rsplit("/", 2) + topic_name = topic_name.replace("-", "0").replace("_", "0") + valid_topic_name = not _is_digits(topic_name) and topic_name.isalnum() + if valid_topic_name and _is_digits(topic_id) and _is_digits(post_id): + # the topic name must not be entirely numeric + return + elif remainder.count("/") == 1: + # When there are only two parts, there's an ambiguity between + # "topic-name/topic-id" and "topic-id/post-id". + # We disambiguate by checking if the LHS is a valid name and + # the RHS is a valid topic ID (for the former), + # and then if both the LHS and RHS are valid IDs (for the latter). + left, right = remainder.rsplit("/") + left = left.replace("-", "0").replace("_", "0") + # the topic name must not be entirely numeric + left_is_name = not _is_digits(left) and left.isalnum() + if left_is_name and _is_digits(right): + return + elif _is_digits(left) and _is_digits(right): + return + else: + # When there's only one part, it must be a valid topic ID. + if _is_digits(remainder): + return if url.startswith("https://mail.python.org/pipermail/"): remainder = url.removeprefix("https://mail.python.org/pipermail/") diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index f5f7d741fd4..617e809a504 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -278,3 +278,165 @@ def test_email_checker(email: str, expected_warnings: set): assert warnings == [], warnings assert found_warnings == expected_warnings + + +@pytest.mark.parametrize( + "thread_url", + [ + "https://discuss.python.org/t/thread-name/123456", + "https://discuss.python.org/t/thread-name/123456/", + "https://discuss.python.org/t/thread_name/123456", + "https://discuss.python.org/t/thread_name/123456/", + "https://discuss.python.org/t/thread-name/123456/654321/", + "https://discuss.python.org/t/thread-name/123456/654321", + "https://discuss.python.org/t/123456", + "https://discuss.python.org/t/123456/", + "https://discuss.python.org/t/123456/654321/", + "https://discuss.python.org/t/123456/654321", + "https://discuss.python.org/t/1", + "https://discuss.python.org/t/1/", + "https://mail.python.org/pipermail/list-name/0000-Month/0123456.html", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/", + ], +) +def test_thread_checker_valid(thread_url: str): + warnings = [warning for (_, warning) in pep_lint._thread(1, thread_url, "")] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "thread_url", + [ + "http://link.example", + "list-name@python.org", + "distutils-sig@python.org", + "csv@python.org", + "python-3000@python.org", + "ipaddr-py-dev@googlegroups.com", + "python-tulip@googlegroups.com", + "https://link.example", + "https://discuss.python.org", + "https://discuss.python.org/", + "https://discuss.python.org/c/category", + "https://discuss.python.org/t/thread_name/123456//", + "https://discuss.python.org/t/thread+name/123456", + "https://discuss.python.org/t/thread+name/123456#", + "https://discuss.python.org/t/thread+name/123456/#", + "https://discuss.python.org/t/thread+name/123456/#anchor", + "https://discuss.python.org/t/thread+name/", + "https://discuss.python.org/t/thread+name", + "https://discuss.python.org/t/thread-name/123abc", + "https://discuss.python.org/t/thread-name/123abc/", + "https://discuss.python.org/t/thread-name/123456/123abc", + "https://discuss.python.org/t/thread-name/123456/123abc/", + "https://discuss.python.org/t/123/456/789", + "https://discuss.python.org/t/123/456/789/", + "https://discuss.python.org/t/#/", + "https://discuss.python.org/t/#", + "https://mail.python.org/pipermail/list+name/0000-Month/0123456.html", + "https://mail.python.org/pipermail/list-name/YYYY-Month/0123456.html", + "https://mail.python.org/pipermail/list-name/0123456/0123456.html", + "https://mail.python.org/pipermail/list-name/0000-Month/0123456", + "https://mail.python.org/pipermail/list-name/0000-Month/0123456/", + "https://mail.python.org/pipermail/list-name/0000-Month/", + "https://mail.python.org/pipermail/list-name/0000-Month", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123#anchor", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/#anchor", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123#anchor", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/#anchor", + "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123/", + ], +) +def test_thread_checker_invalid(thread_url: str): + warnings = [warning for (_, warning) in pep_lint._thread(1, thread_url, "")] + assert warnings == [" must be a valid thread URL"], warnings + + +@pytest.mark.parametrize( + "thread_url", + [ + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123#Anchor", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/#Anchor", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123#Anchor123", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/#Anchor123", + ], +) +def test_thread_checker_valid_allow_message(thread_url: str): + warnings = [warning for (_, warning) in pep_lint._thread(1, thread_url, "", allow_message=True)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "thread_url", + [ + "https://mail.python.org/archives/list/list-name@python.org/thread", + "https://mail.python.org/archives/list/list-name@python.org/message", + "https://mail.python.org/archives/list/list-name@python.org/thread/", + "https://mail.python.org/archives/list/list-name@python.org/message/", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123#anchor", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/#anchor", + "https://mail.python.org/archives/list/list-name@python.org/message/#abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/message/#abcXYZ123/", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/anchor/", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/anchor/", + "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123/", + ], +) +def test_thread_checker_invalid_allow_message(thread_url: str): + warnings = [warning for (_, warning) in pep_lint._thread(1, thread_url, "", allow_message=True)] + assert warnings == [" must be a valid thread URL"], warnings + + +@pytest.mark.parametrize( + "thread_url", + [ + "list-name@python.org", + "distutils-sig@python.org", + "csv@python.org", + "python-3000@python.org", + "ipaddr-py-dev@googlegroups.com", + "python-tulip@googlegroups.com", + "https://discuss.python.org/t/thread-name/123456", + "https://discuss.python.org/t/thread-name/123456/", + "https://discuss.python.org/t/thread_name/123456", + "https://discuss.python.org/t/thread_name/123456/", + "https://discuss.python.org/t/123456/", + "https://discuss.python.org/t/123456", + ], +) +def test_thread_checker_valid_discussions_to(thread_url: str): + warnings = [warning for (_, warning) in pep_lint._thread(1, thread_url, "", discussions_to=True)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "thread_url", + [ + "https://discuss.python.org/t/thread-name/123456/000", + "https://discuss.python.org/t/thread-name/123456/000/", + "https://discuss.python.org/t/thread_name/123456/000", + "https://discuss.python.org/t/thread_name/123456/000/", + "https://discuss.python.org/t/123456/000/", + "https://discuss.python.org/t/12345656/000", + "https://discuss.python.org/t/thread-name", + "https://discuss.python.org/t/thread_name", + "https://discuss.python.org/t/thread+name", + ], +) +def test_thread_checker_invalid_discussions_to(thread_url: str): + warnings = [warning for (_, warning) in pep_lint._thread(1, thread_url, "", discussions_to=True)] + assert warnings == [" must be a valid thread URL"], warnings + + +def test_thread_checker_allow_message_discussions_to(): + with pytest.raises(ValueError, match='cannot both be True'): + list(pep_lint._thread(1, '', "", allow_message=True, discussions_to=True)) From a3ae089e17790d001205013d4baf92718a4bc8e0 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 16:15:20 +0100 Subject: [PATCH 21/83] Test _date --- pep-lint.py | 10 ++- pep_sphinx_extensions/tests/test_pep_lint.py | 84 ++++++++++++++++++++ 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/pep-lint.py b/pep-lint.py index 03784b9ebc4..a97e6ed26ef 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -513,13 +513,17 @@ def _date(line_num, date_str, prefix): try: parsed_date = dt.datetime.strptime(date_str, "%d-%b-%Y") except ValueError: - yield line_num, f"{prefix} must be a 'DD-mmm-YYYY' date: {date_str}" + yield line_num, f"{prefix} must be a 'DD-mmm-YYYY' date: {date_str!r}" return + else: + if date_str[1] == '-': # Date must be zero-padded + yield line_num, f"{prefix} must be a 'DD-mmm-YYYY' date: {date_str!r}" + return if parsed_date.year < 1990: - yield line_num, f"{prefix} must not be before Python was invented: {date_str}" + yield line_num, f"{prefix} must not be before Python was invented: {date_str!r}" if parsed_date > (dt.datetime.now() + dt.timedelta(days=14)): - yield line_num, f"{prefix} must not be in the future: {date_str}" + yield line_num, f"{prefix} must not be in the future: {date_str!r}" if __name__ == "__main__": diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index 617e809a504..52cf5903be8 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -1,3 +1,4 @@ +import datetime as dt import importlib.util import sys from pathlib import Path @@ -440,3 +441,86 @@ def test_thread_checker_invalid_discussions_to(thread_url: str): def test_thread_checker_allow_message_discussions_to(): with pytest.raises(ValueError, match='cannot both be True'): list(pep_lint._thread(1, '', "", allow_message=True, discussions_to=True)) + + +@pytest.mark.parametrize( + "date_str", + [ + # valid entries + "01-Jan-2000", + "29-Feb-2016", + "31-Dec-2000", + "01-Apr-2003", + "01-Apr-2007", + "01-Apr-2009", + "01-Jan-1990", + ], +) +def test_date_checker_valid(date_str: str): + warnings = [warning for (_, warning) in pep_lint._date(1, date_str, "")] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "date_str", + [ + # malformed + "2000-01-01", + "01 January 2000", + "1 Jan 2000", + "1-Jan-2000", + "1-January-2000", + "Jan-1-2000", + "January 1 2000", + "January 01 2000", + "01/01/2000", + "01/Jan/2000", # 🇬🇧, 🇦🇺, 🇨🇦, 🇳🇿, 🇮🇪 , ... + "Jan/01/2000", # 🇺🇸 + "1st January 2000", + "The First day of January in the year of Our Lord Two Thousand", + "Jan, 1, 2000", + "2000-Jan-1", + "2000-Jan-01", + "2000-January-1", + "2000-January-01", + "00 Jan 2000", + "00-Jan-2000", + ], +) +def test_date_checker_malformed(date_str: str): + warnings = [warning for (_, warning) in pep_lint._date(1, date_str, "")] + expected = f" must be a 'DD-mmm-YYYY' date: {date_str!r}" + assert warnings == [expected], warnings + + +@pytest.mark.parametrize( + "date_str", + [ + # too early + "31-Dec-1989", + "01-Apr-1916", + "01-Jan-0020", + "01-Jan-0023", + ], +) +def test_date_checker_too_early(date_str: str): + warnings = [warning for (_, warning) in pep_lint._date(1, date_str, "")] + expected = f" must not be before Python was invented: {date_str!r}" + assert warnings == [expected], warnings + + +@pytest.mark.parametrize( + "date_str", + [ + # the future + "31-Dec-2999", + "01-Jan-2100", + "01-Jan-2100", + (dt.datetime.now() + dt.timedelta(days=15)).strftime("%d-%b-%Y"), + (dt.datetime.now() + dt.timedelta(days=100)).strftime("%d-%b-%Y"), + ], +) +def test_date_checker_too_late(date_str: str): + warnings = [warning for (_, warning) in pep_lint._date(1, date_str, "")] + expected = f" must not be in the future: {date_str!r}" + assert warnings == [expected], warnings From bb3ed78320a0e5181883020eb43af68e1279de97 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 16:16:59 +0100 Subject: [PATCH 22/83] Test _validate_resolution --- pep_sphinx_extensions/tests/test_pep_lint.py | 40 ++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index 52cf5903be8..b296bfc0a2c 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -58,6 +58,46 @@ def test_header_pattern_no_match(test_input): assert pep_lint.HEADER_PATTERN.match(test_input) is None +@pytest.mark.parametrize( + "thread_url", + [ + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123#Anchor", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/#Anchor", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123#Anchor123", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/#Anchor123", + ], +) +def test_validate_resolution_valid(thread_url: str): + warnings = [warning for (_, warning) in pep_lint._validate_resolution(1, thread_url)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "thread_url", + [ + "https://mail.python.org/archives/list/list-name@python.org/thread", + "https://mail.python.org/archives/list/list-name@python.org/message", + "https://mail.python.org/archives/list/list-name@python.org/thread/", + "https://mail.python.org/archives/list/list-name@python.org/message/", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123#anchor", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/#anchor", + "https://mail.python.org/archives/list/list-name@python.org/message/#abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/message/#abcXYZ123/", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/anchor/", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/anchor/", + "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123/", + ], +) +def test_validate_resolution_invalid(thread_url: str): + warnings = [warning for (_, warning) in pep_lint._validate_resolution(1, thread_url)] + assert warnings == ["Resolution must be a valid thread URL"], warnings + + @pytest.mark.parametrize( ("pep_number", "expected_warnings"), [ From 657e6134de0f145b9e5c97615d6b2ee87f05a8ce Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 16:33:19 +0100 Subject: [PATCH 23/83] Test _validate_post_history --- pep-lint.py | 3 +- pep_sphinx_extensions/tests/test_pep_lint.py | 35 ++++++++++++++++---- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/pep-lint.py b/pep-lint.py index a97e6ed26ef..e678159e6cc 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -346,8 +346,7 @@ def _validate_python_version(line_num, line): def _validate_post_history(line_num, body): """'Post-History' must be '`DD-mmm-YYYY `__, …'""" - body = body - if not body: + if body == "": return for offset, line in enumerate(body.removesuffix(",").split("\n"), start=line_num): diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index b296bfc0a2c..eeb26695e56 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -59,7 +59,30 @@ def test_header_pattern_no_match(test_input): @pytest.mark.parametrize( - "thread_url", + "body", + [ + "", + ( + "01-Jan-2001, 02-Feb-2002,\n " + "03-Mar-2003, 04-Apr-2004,\n " + "05-May-2005," + ), + ( + "`01-Jan-2000 `__,\n " + "`11-Mar-2005 `__,\n " + "`21-May-2010 `__,\n " + "`31-Jul-2015 `__," + ), + "01-Jan-2001, `02-Feb-2002 `__,\n03-Mar-2003", + ], +) +def test_validate_post_history_valid(body: str): + warnings = [warning for (_, warning) in pep_lint._validate_post_history(1, body)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "line", [ "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123", "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/", @@ -71,13 +94,13 @@ def test_header_pattern_no_match(test_input): "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/#Anchor123", ], ) -def test_validate_resolution_valid(thread_url: str): - warnings = [warning for (_, warning) in pep_lint._validate_resolution(1, thread_url)] +def test_validate_resolution_valid(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_resolution(1, line)] assert warnings == [], warnings @pytest.mark.parametrize( - "thread_url", + "line", [ "https://mail.python.org/archives/list/list-name@python.org/thread", "https://mail.python.org/archives/list/list-name@python.org/message", @@ -93,8 +116,8 @@ def test_validate_resolution_valid(thread_url: str): "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123/", ], ) -def test_validate_resolution_invalid(thread_url: str): - warnings = [warning for (_, warning) in pep_lint._validate_resolution(1, thread_url)] +def test_validate_resolution_invalid(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_resolution(1, line)] assert warnings == ["Resolution must be a valid thread URL"], warnings From 7b70ecc810edae3b9d47e2bc99b571cd02b3d5a4 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 16:39:44 +0100 Subject: [PATCH 24/83] Add pep-lint hook to .pre-commit-config.yaml --- .pre-commit-config.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b6223222706..aa4ee3ba74e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -98,17 +98,17 @@ repos: name: "Check for common misspellings in text files" stages: [manual] - # Hook to run 'pep-lint.py' - - id: pep-lint - name: Check PEP metadata - entry: 'python pep-lint.py' - language: system - files: 'pep-\d{4}.(txt|rst)' - pass_filenames: false - # Local checks for PEP headers and more - repo: local hooks: + # Hook to run 'pep-lint.py' + - id: pep-lint + name: Check PEP metadata + entry: 'python pep-lint.py' + language: system + files: 'pep-\d{4}.(txt|rst)' + pass_filenames: false + - id: check-no-tabs name: "Check tabs not used in PEPs" language: pygrep From 52a8d7d5a4e31f9ab5e03867e2054e5f995b56b4 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 16:41:19 +0100 Subject: [PATCH 25/83] Format --- pep_sphinx_extensions/tests/test_pep_lint.py | 64 +++++++++++++++----- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index eeb26695e56..37c32a4d2df 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -63,9 +63,9 @@ def test_header_pattern_no_match(test_input): [ "", ( - "01-Jan-2001, 02-Feb-2002,\n " - "03-Mar-2003, 04-Apr-2004,\n " - "05-May-2005," + "01-Jan-2001, 02-Feb-2002,\n " + "03-Mar-2003, 04-Apr-2004,\n " + "05-May-2005," ), ( "`01-Jan-2000 `__,\n " @@ -163,7 +163,9 @@ def test_validate_resolution_invalid(line: str): ids=str, ) def test_pep_num_checker(pep_number: str, expected_warnings: set): - warnings = [warning for (_, warning) in pep_lint._pep_num(1, pep_number, "")] + warnings = [ + warning for (_, warning) in pep_lint._pep_num(1, pep_number, "") + ] found_warnings = set() pep_number = pep_number.strip() @@ -256,8 +258,14 @@ def test_pep_num_checker(pep_number: str, expected_warnings: set): ("Cardinal Ximénez ", {"valid email"}), ("Cardinal Ximénez ", {"valid email"}), ("Cardinal Ximénez ", {"valid email"}), - ("Cardinal Ximénez ", {"multiple <", "multiple >", "valid email"}), - ("Cardinal Ximénez ", {"multiple @", "valid email"}), + ( + "Cardinal Ximénez ", + {"multiple <", "multiple >", "valid email"}, + ), + ( + "Cardinal Ximénez ", + {"multiple @", "valid email"}, + ), (r"Cardinal Ximénez ", {"valid email"}), ("Cardinal Ximénez <[Cardinal.Ximenez]@spanish.inquisition>", {"valid email"}), ('Cardinal Ximénez <"Cardinal"Ximenez"@spanish.inquisition>', {"valid email"}), @@ -265,10 +273,16 @@ def test_pep_num_checker(pep_number: str, expected_warnings: set): ("Cardinal Ximénez ", {"valid email"}), ("Cardinal Ximénez ", {"valid email"}), # ... entries must contain a valid email address (domain) - ("Cardinal Ximénez ", {"valid email"}), + ( + "Cardinal Ximénez ", + {"valid email"}, + ), ("Cardinal Ximénez ", {"valid email"}), ("Cardinal Ximénez ", {"valid email"}), - ("Cardinal Ximénez ", {"valid email"}), + ( + "Cardinal Ximénez ", + {"valid email"}, + ), # valid name-emails ("Cardinal Ximénez ", set()), ("Cardinal Ximénez ", set()), @@ -434,7 +448,12 @@ def test_thread_checker_invalid(thread_url: str): ], ) def test_thread_checker_valid_allow_message(thread_url: str): - warnings = [warning for (_, warning) in pep_lint._thread(1, thread_url, "", allow_message=True)] + warnings = [ + warning + for (_, warning) in pep_lint._thread( + 1, thread_url, "", allow_message=True + ) + ] assert warnings == [], warnings @@ -456,7 +475,12 @@ def test_thread_checker_valid_allow_message(thread_url: str): ], ) def test_thread_checker_invalid_allow_message(thread_url: str): - warnings = [warning for (_, warning) in pep_lint._thread(1, thread_url, "", allow_message=True)] + warnings = [ + warning + for (_, warning) in pep_lint._thread( + 1, thread_url, "", allow_message=True + ) + ] assert warnings == [" must be a valid thread URL"], warnings @@ -478,7 +502,12 @@ def test_thread_checker_invalid_allow_message(thread_url: str): ], ) def test_thread_checker_valid_discussions_to(thread_url: str): - warnings = [warning for (_, warning) in pep_lint._thread(1, thread_url, "", discussions_to=True)] + warnings = [ + warning + for (_, warning) in pep_lint._thread( + 1, thread_url, "", discussions_to=True + ) + ] assert warnings == [], warnings @@ -497,13 +526,20 @@ def test_thread_checker_valid_discussions_to(thread_url: str): ], ) def test_thread_checker_invalid_discussions_to(thread_url: str): - warnings = [warning for (_, warning) in pep_lint._thread(1, thread_url, "", discussions_to=True)] + warnings = [ + warning + for (_, warning) in pep_lint._thread( + 1, thread_url, "", discussions_to=True + ) + ] assert warnings == [" must be a valid thread URL"], warnings def test_thread_checker_allow_message_discussions_to(): - with pytest.raises(ValueError, match='cannot both be True'): - list(pep_lint._thread(1, '', "", allow_message=True, discussions_to=True)) + with pytest.raises(ValueError, match="cannot both be True"): + list( + pep_lint._thread(1, "", "", allow_message=True, discussions_to=True) + ) @pytest.mark.parametrize( From 4b47d410a45418d35baf714fbcaf7af650dc3e94 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 17:08:03 +0100 Subject: [PATCH 26/83] Test _validate_python_version --- pep-lint.py | 4 +- pep_sphinx_extensions/tests/test_pep_lint.py | 97 ++++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/pep-lint.py b/pep-lint.py index e678159e6cc..0cab68902f7 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -318,7 +318,7 @@ def _validate_python_version(line_num, line): versions = line.split(", ") for version in versions: - if version.count(".") > 2: + if version.count(".") not in {1, 2}: yield line_num, f"Python-Version must have two or three segments: {version}" continue @@ -332,6 +332,8 @@ def _validate_python_version(line_num, line): yield line_num, f"Python-Version major part must be 1, 2, or 3: {version}" if not _is_digits(minor) and minor != "x": yield line_num, f"Python-Version minor part must be numeric: {version}" + elif minor != "0" and minor[0] == "0": + yield line_num, f"Python-Version minor part must not have leading zeros: {version}" if micro == "": return diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index 37c32a4d2df..346019bdc8f 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -58,6 +58,103 @@ def test_header_pattern_no_match(test_input): assert pep_lint.HEADER_PATTERN.match(test_input) is None +@pytest.mark.parametrize( + ("line", "expected_warnings"), + [ + # valid entries + ("1.0, 2.4, 2.7, 2.8, 3.0, 3.1, 3.4, 3.7, 3.11, 3.14", set()), + ("2.x", set()), + ("3.x", set()), + ("3.0.1", set()), + # segments + ("", {"segments"}), + ("1", {"segments"}), + ("1.2.3.4", {"segments"}), + # major + ("0.0", {"major"}), + ("4.0", {"major"}), + ("9.0", {"major"}), + # minor number + ("3.a", {"minor numeric"}), + ("3.spam", {"minor numeric"}), + ("3.0+", {"minor numeric"}), + ("3.0-9", {"minor numeric"}), + ("9.Z", {"major", "minor numeric"}), + # minor leading zero + ("3.01", {"minor zero"}), + ("0.00", {"major", "minor zero"}), + # micro empty + ("3.x.1", {"micro empty"}), + ("9.x.1", {"major", "micro empty"}), + # micro leading zero + ("3.3.0", {"micro zero"}), + ("3.3.00", {"micro zero"}), + ("3.3.01", {"micro zero"}), + ("3.0.0", {"micro zero"}), + ("3.00.0", {"minor zero", "micro zero"}), + ("0.00.0", {"major", "minor zero", "micro zero"}), + # micro number + ("3.0.a", {"micro numeric"}), + ("0.3.a", {"major", "micro numeric"}), + ], + # call str() on each parameterised value in the test ID. + ids=str, +) +def test_validate_python_version(line: str, expected_warnings: set): + warnings = [ + warning for (_, warning) in pep_lint._validate_python_version(1, line) + ] + + found_warnings = set() + + if "segments" in expected_warnings: + found_warnings.add("segments") + expected = f"Python-Version must have two or three segments: {line}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "major" in expected_warnings: + found_warnings.add("major") + expected = f"Python-Version major part must be 1, 2, or 3: {line}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "minor numeric" in expected_warnings: + found_warnings.add("minor numeric") + expected = f"Python-Version minor part must be numeric: {line}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "minor zero" in expected_warnings: + found_warnings.add("minor zero") + expected = f"Python-Version minor part must not have leading zeros: {line}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "micro empty" in expected_warnings: + found_warnings.add("micro empty") + expected = f"Python-Version micro part must be empty if minor part is 'x': {line}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "micro zero" in expected_warnings: + found_warnings.add("micro zero") + expected = f"Python-Version micro part must not have leading zeros: {line}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "micro numeric" in expected_warnings: + found_warnings.add("micro numeric") + expected = f"Python-Version micro part must be numeric: {line}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if expected_warnings == set(): + assert warnings == [], warnings + + assert found_warnings == expected_warnings + + @pytest.mark.parametrize( "body", [ From a8218709844ab04caafb4cd204c38bdd2b35d21c Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 17:10:29 +0100 Subject: [PATCH 27/83] Test test_validate_created --- pep_sphinx_extensions/tests/test_pep_lint.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index 346019bdc8f..3be1d7311a4 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -58,6 +58,24 @@ def test_header_pattern_no_match(test_input): assert pep_lint.HEADER_PATTERN.match(test_input) is None +@pytest.mark.parametrize( + "line", + [ + # valid entries + "01-Jan-2000", + "29-Feb-2016", + "31-Dec-2000", + "01-Apr-2003", + "01-Apr-2007", + "01-Apr-2009", + "01-Jan-1990", + ], +) +def test_validate_created(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_created(1, line)] + assert warnings == [], warnings + + @pytest.mark.parametrize( ("line", "expected_warnings"), [ From 1e17a1a06f762db267e05d26e152a9d81ad8df0b Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 17:16:23 +0100 Subject: [PATCH 28/83] Test _validate_pep_references --- pep-lint.py | 5 ++++ pep_sphinx_extensions/tests/test_pep_lint.py | 26 ++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/pep-lint.py b/pep-lint.py index 0cab68902f7..3e1bd8c778e 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -302,6 +302,11 @@ def _validate_content_type(line_num, line): def _validate_pep_references(line_num, line): """`Requires`/`Replaces`/`Superseded-By` must be 'NNN' PEP IDs""" + line = line.removesuffix(",").rstrip() + if line.count(", ") != line.count(","): + yield line_num, "PEP references must be separated by comma-spaces (', ')" + return + references = line.split(", ") for reference in references: yield from _pep_num(line_num, reference, "PEP reference") diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index 3be1d7311a4..1f4ff9c4002 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -58,6 +58,32 @@ def test_header_pattern_no_match(test_input): assert pep_lint.HEADER_PATTERN.match(test_input) is None +@pytest.mark.parametrize( + "line", + [ + "0, 1, 8, 12, 20,", + "101, 801,", + "3099, 9999", + ], +) +def test_validate_pep_references(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_pep_references(1, line)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "line", + [ + "0,1,8, 12, 20,", + "101,801,", + "3099, 9998,9999", + ], +) +def test_validate_pep_references_separators(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_pep_references(1, line)] + assert warnings == ["PEP references must be separated by comma-spaces (', ')"], warnings + + @pytest.mark.parametrize( "line", [ From 8797cb0f776089618370b90d813de6aac694e2a9 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 17:20:43 +0100 Subject: [PATCH 29/83] Test _validate_content_type --- pep_sphinx_extensions/tests/test_pep_lint.py | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index 1f4ff9c4002..1d536103111 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -58,6 +58,28 @@ def test_header_pattern_no_match(test_input): assert pep_lint.HEADER_PATTERN.match(test_input) is None +def test_validate_content_type_valid(): + warnings = [warning for (_, warning) in pep_lint._validate_content_type(1, "text/x-rst")] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "line", + [ + "text/plain", + "text/markdown", + "text/csv", + "text/rtf", + "text/javascript", + "text/html", + "text/xml", + ], +) +def test_validate_content_type_invalid(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_content_type(1, line)] + assert warnings == ["Content-Type must be 'text/x-rst'"], warnings + + @pytest.mark.parametrize( "line", [ From d58ce26fc1856103d89b53699be3f8a415d2fb10 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 17:41:22 +0100 Subject: [PATCH 30/83] Test _validate_topic --- pep-lint.py | 7 +- pep_sphinx_extensions/tests/test_pep_lint.py | 73 ++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/pep-lint.py b/pep-lint.py index 3e1bd8c778e..f6f979ab33d 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -289,7 +289,12 @@ def _validate_topic(line_num, line): yield line_num, "Topic must not contain duplicates" if unique_topics - {"Governance", "Packaging", "Typing", "Release"}: - yield line_num, "Topic must be for a valid sub-index" + if not all(map(str.istitle, unique_topics)): + yield line_num, "Topic must be properly capitalised (Title Case)" + if unique_topics - {"governance", "packaging", "typing", "release"}: + yield line_num, "Topic must be for a valid sub-index" + if sorted(topics) != topics: + yield line_num, "Topic must be sorted lexicographically" def _validate_content_type(line_num, line): diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index 1d536103111..aceab0cd359 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -58,6 +58,79 @@ def test_header_pattern_no_match(test_input): assert pep_lint.HEADER_PATTERN.match(test_input) is None +@pytest.mark.parametrize( + ("line", "expected_warnings"), + [ + # valid entries + ("Governance", set()), + ("Packaging", set()), + ("Typing", set()), + ("Release", set()), + ("Governance, Packaging", set()), + ("Packaging, Typing", set()), + # duplicates + ("Governance, Governance", {"duplicates"}), + ("Release, Release", {"duplicates"}), + ("Release, Release", {"duplicates"}), + ("Spam, Spam", {"duplicates", "valid"}), + ("lobster, lobster", {"duplicates", "capitalisation", "valid"}), + ("governance, governance", {"duplicates", "capitalisation"}), + # capitalisation + ("governance", {"capitalisation"}), + ("packaging", {"capitalisation"}), + ("typing", {"capitalisation"}), + ("release", {"capitalisation"}), + ("Governance, release", {"capitalisation"}), + # validity + ("Spam", {"valid"}), + ("lobster", {"capitalisation", "valid"}), + # sorted + ("Packaging, Governance", {"sorted"}), + ("Typing, Release", {"sorted"}), + ("Release, Governance", {"sorted"}), + ("spam, packaging", {"capitalisation", "valid", "sorted"}), + ], + # call str() on each parameterised value in the test ID. + ids=str, +) +def test_validate_topic(line: str, expected_warnings: set): + warnings = [ + warning for (_, warning) in pep_lint._validate_topic(1, line) + ] + + found_warnings = set() + + if "duplicates" in expected_warnings: + found_warnings.add("duplicates") + expected = "Topic must not contain duplicates" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "capitalisation" in expected_warnings: + found_warnings.add("capitalisation") + expected = "Topic must be properly capitalised (Title Case)" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "valid" in expected_warnings: + found_warnings.add("valid") + expected = "Topic must be for a valid sub-index" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "sorted" in expected_warnings: + found_warnings.add("sorted") + expected = "Topic must be sorted lexicographically" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if expected_warnings == set(): + assert warnings == [], warnings + + assert found_warnings == expected_warnings + + + def test_validate_content_type_valid(): warnings = [warning for (_, warning) in pep_lint._validate_content_type(1, "text/x-rst")] assert warnings == [], warnings From c1143a7c113a1b83e19ed6b86a1cb36d3c3882c5 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 17:42:06 +0100 Subject: [PATCH 31/83] Format --- pep_sphinx_extensions/tests/test_pep_lint.py | 21 ++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index aceab0cd359..777411e5bff 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -94,9 +94,7 @@ def test_header_pattern_no_match(test_input): ids=str, ) def test_validate_topic(line: str, expected_warnings: set): - warnings = [ - warning for (_, warning) in pep_lint._validate_topic(1, line) - ] + warnings = [warning for (_, warning) in pep_lint._validate_topic(1, line)] found_warnings = set() @@ -130,9 +128,10 @@ def test_validate_topic(line: str, expected_warnings: set): assert found_warnings == expected_warnings - def test_validate_content_type_valid(): - warnings = [warning for (_, warning) in pep_lint._validate_content_type(1, "text/x-rst")] + warnings = [ + warning for (_, warning) in pep_lint._validate_content_type(1, "text/x-rst") + ] assert warnings == [], warnings @@ -176,7 +175,9 @@ def test_validate_pep_references(line: str): ) def test_validate_pep_references_separators(line: str): warnings = [warning for (_, warning) in pep_lint._validate_pep_references(1, line)] - assert warnings == ["PEP references must be separated by comma-spaces (', ')"], warnings + assert warnings == [ + "PEP references must be separated by comma-spaces (', ')" + ], warnings @pytest.mark.parametrize( @@ -240,9 +241,7 @@ def test_validate_created(line: str): ids=str, ) def test_validate_python_version(line: str, expected_warnings: set): - warnings = [ - warning for (_, warning) in pep_lint._validate_python_version(1, line) - ] + warnings = [warning for (_, warning) in pep_lint._validate_python_version(1, line)] found_warnings = set() @@ -272,7 +271,9 @@ def test_validate_python_version(line: str, expected_warnings: set): if "micro empty" in expected_warnings: found_warnings.add("micro empty") - expected = f"Python-Version micro part must be empty if minor part is 'x': {line}" + expected = ( + f"Python-Version micro part must be empty if minor part is 'x': {line}" + ) matching = [w for w in warnings if w == expected] assert matching == [expected], warnings From e79f64981006980050275ba1f7a4eaaa88b1fbaa Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 17:53:33 +0100 Subject: [PATCH 32/83] Add pep-lint hook to .pre-commit-config.yaml --- .pre-commit-config.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aa4ee3ba74e..f8520622847 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks -minimum_pre_commit_version: '2.8.2' +minimum_pre_commit_version: '2.9.0' default_language_version: python: python3 @@ -102,12 +102,12 @@ repos: - repo: local hooks: # Hook to run 'pep-lint.py' - - id: pep-lint - name: Check PEP metadata - entry: 'python pep-lint.py' - language: system + - id: "pep-lint" + name: "Check PEP metadata" + entry: "python pep-lint.py" + language: "system" files: 'pep-\d{4}.(txt|rst)' - pass_filenames: false + types_or: ["rst", "plain-text"] - id: check-no-tabs name: "Check tabs not used in PEPs" From 33e98643ca1fa071b40efed7945d268be3ba561e Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 17:55:15 +0100 Subject: [PATCH 33/83] Support string filenames --- pep-lint.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pep-lint.py b/pep-lint.py index f6f979ab33d..385ac1048a1 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -69,7 +69,9 @@ def check(filenames=(), /): """The main entry-point.""" - if not filenames: + if filenames: + filenames = map(Path, filenames) + else: filenames = itertools.chain(PEP_ROOT.glob("pep-????.txt"), PEP_ROOT.glob("pep-????.rst")) if (count := sum(map(check_file, filenames))) > 0: s = "s" * (count != 1) @@ -79,6 +81,7 @@ def check(filenames=(), /): def check_file(filename, /): + filename = filename.resolve() try: content = filename.read_text(encoding="utf-8") except FileNotFoundError: From ee0c1ca1cb54c58963fc48a3e63fc49a2c0b40c1 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 17:55:51 +0100 Subject: [PATCH 34/83] Add pep-lint hook to .pre-commit-config.yaml --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f8520622847..2176440e087 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -108,6 +108,7 @@ repos: language: "system" files: 'pep-\d{4}.(txt|rst)' types_or: ["rst", "plain-text"] + require_serial: true - id: check-no-tabs name: "Check tabs not used in PEPs" From dffe4ea0d3b95e4e21f646988703662b5c66f2a0 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 17:57:05 +0100 Subject: [PATCH 35/83] Add pep-lint hook to .pre-commit-config.yaml --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2176440e087..e3ce12afeac 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -106,7 +106,7 @@ repos: name: "Check PEP metadata" entry: "python pep-lint.py" language: "system" - files: 'pep-\d{4}.(txt|rst)' + files: '^pep-\d{4}.(txt|rst)$' types_or: ["rst", "plain-text"] require_serial: true From 7ab98ad7af0ff900f2e7b0a786d186dc35135a13 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 18:01:42 +0100 Subject: [PATCH 36/83] Test _validate_type --- pep_sphinx_extensions/tests/test_pep_lint.py | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index 777411e5bff..ce17368b86f 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -58,6 +58,36 @@ def test_header_pattern_no_match(test_input): assert pep_lint.HEADER_PATTERN.match(test_input) is None +@pytest.mark.parametrize( + "line", + [ + "Standards Track", + "Informational", + "Process", + ], +) +def test_validate_type_valid(line: str): + warnings = [ + warning for (_, warning) in pep_lint._validate_type(1, line) + ] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "line", + [ + "standards track", + "informational", + "process", + *pep_lint.ALL_STATUSES, + *map(str.lower, pep_lint.ALL_STATUSES), + ], +) +def test_validate_type_invalid(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_type(1, line)] + assert warnings == ["Type must be a valid PEP type"], warnings + + @pytest.mark.parametrize( ("line", "expected_warnings"), [ From e6a0dd9fdf6d54002b9c3cf493eb41e4898548a5 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 18:03:29 +0100 Subject: [PATCH 37/83] Test _validate_status --- pep_sphinx_extensions/tests/test_pep_lint.py | 59 +++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index ce17368b86f..905b836e646 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -58,6 +58,53 @@ def test_header_pattern_no_match(test_input): assert pep_lint.HEADER_PATTERN.match(test_input) is None +@pytest.mark.parametrize( + "line", + [ + "Accepted", + "Active", + "April Fool!", + "Deferred", + "Draft", + "Final", + "Provisional", + "Rejected", + "Superseded", + "Withdrawn", + ], +) +def test_validate_status_valid(line: str): + warnings = [ + warning for (_, warning) in pep_lint._validate_status(1, line) + ] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "line", + [ + "Standards Track", + "Informational", + "Process", + "accepted", + "active", + "april fool!", + "deferred", + "draft", + "final", + "provisional", + "rejected", + "superseded", + "withdrawn", + ], +) +def test_validate_status_invalid(line: str): + warnings = [ + warning for (_, warning) in pep_lint._validate_status(1, line) + ] + assert warnings == ["Status must be a valid PEP status"], warnings + + @pytest.mark.parametrize( "line", [ @@ -79,8 +126,16 @@ def test_validate_type_valid(line: str): "standards track", "informational", "process", - *pep_lint.ALL_STATUSES, - *map(str.lower, pep_lint.ALL_STATUSES), + "Accepted", + "Active", + "April Fool!", + "Deferred", + "Draft", + "Final", + "Provisional", + "Rejected", + "Superseded", + "Withdrawn", ], ) def test_validate_type_invalid(line: str): From a08aaf6aae7ae268766f45d2455092c5f4cd3bfc Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 18:08:56 +0100 Subject: [PATCH 38/83] Test _validate_discussions_to --- pep-lint.py | 5 +- pep_sphinx_extensions/tests/test_pep_lint.py | 58 +++++++++++++++++--- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/pep-lint.py b/pep-lint.py index 385ac1048a1..20355775b68 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -262,12 +262,15 @@ def _validate_discussions_to(line_num, line): """'Discussions-To' must be a thread URL""" yield from _thread(line_num, line, "Discussions-To", discussions_to=True) + if line.startswith("https://"): + return for suffix in "@python.org", "@googlegroups.com": if line.endswith(suffix): remainder = line.removesuffix(suffix) if re.fullmatch(r"[\w\-]+", remainder) is None: yield line_num, "Discussions-To must be a valid mailing list" - + return + yield line_num, "Discussions-To must be a valid thread URL or mailing list" def _validate_status(line_num, line): """'Status' must be a valid PEP status""" diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index 905b836e646..b504079e88c 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -58,6 +58,52 @@ def test_header_pattern_no_match(test_input): assert pep_lint.HEADER_PATTERN.match(test_input) is None +@pytest.mark.parametrize( + "line", + [ + "list-name@python.org", + "distutils-sig@python.org", + "csv@python.org", + "python-3000@python.org", + "ipaddr-py-dev@googlegroups.com", + "python-tulip@googlegroups.com", + "https://discuss.python.org/t/thread-name/123456", + "https://discuss.python.org/t/thread-name/123456/", + "https://discuss.python.org/t/thread_name/123456", + "https://discuss.python.org/t/thread_name/123456/", + "https://discuss.python.org/t/123456/", + "https://discuss.python.org/t/123456", + ], +) +def test_validate_discussions_to_valid(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_discussions_to(1, line)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "line", + [ + "$pecial+chars@python.org", + "a-discussions-to-list!@googlegroups.com", + ], +) +def test_validate_discussions_to_list_name(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_discussions_to(1, line)] + assert warnings == ["Discussions-To must be a valid mailing list"], warnings + + +@pytest.mark.parametrize( + "line", + [ + "list-name@python.org.uk", + "distutils-sig@mail-server.example", + ], +) +def test_validate_discussions_to_invalid_list_domain(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_discussions_to(1, line)] + assert warnings == ["Discussions-To must be a valid thread URL or mailing list"], warnings + + @pytest.mark.parametrize( "line", [ @@ -74,9 +120,7 @@ def test_header_pattern_no_match(test_input): ], ) def test_validate_status_valid(line: str): - warnings = [ - warning for (_, warning) in pep_lint._validate_status(1, line) - ] + warnings = [warning for (_, warning) in pep_lint._validate_status(1, line)] assert warnings == [], warnings @@ -99,9 +143,7 @@ def test_validate_status_valid(line: str): ], ) def test_validate_status_invalid(line: str): - warnings = [ - warning for (_, warning) in pep_lint._validate_status(1, line) - ] + warnings = [warning for (_, warning) in pep_lint._validate_status(1, line)] assert warnings == ["Status must be a valid PEP status"], warnings @@ -114,9 +156,7 @@ def test_validate_status_invalid(line: str): ], ) def test_validate_type_valid(line: str): - warnings = [ - warning for (_, warning) in pep_lint._validate_type(1, line) - ] + warnings = [warning for (_, warning) in pep_lint._validate_type(1, line)] assert warnings == [], warnings From f008a924027ccb8089ad75b150fe79f67bccde1c Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 18:12:52 +0100 Subject: [PATCH 39/83] Test _validate_delegate --- pep-lint.py | 2 +- pep_sphinx_extensions/tests/test_pep_lint.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/pep-lint.py b/pep-lint.py index 20355775b68..f1a12b305a3 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -251,7 +251,7 @@ def _validate_delegate(line_num, line): # PEP 451 if ", " in line: - for part in line.removesuffix(", ").split(", "): + for part in line.removesuffix(",").split(", "): yield from _email(line_num, part, "Delegate") return diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index b504079e88c..753470705e1 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -58,6 +58,21 @@ def test_header_pattern_no_match(test_input): assert pep_lint.HEADER_PATTERN.match(test_input) is None +@pytest.mark.parametrize( + "line", + [ + "", + "Alice, Bob, Charlie", + "Alice, Bob, Charlie,", + "Alice ", + "Cardinal Ximénez ", + ], +) +def test_validate_delegate(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_delegate(1, line)] + assert warnings == [], warnings + + @pytest.mark.parametrize( "line", [ From 24da438b22e69fcf996eac666bee709cbb8d4ed7 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 18:16:40 +0100 Subject: [PATCH 40/83] Test _validate_sponsor --- pep_sphinx_extensions/tests/test_pep_lint.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index 753470705e1..8860f300dae 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -58,6 +58,20 @@ def test_header_pattern_no_match(test_input): assert pep_lint.HEADER_PATTERN.match(test_input) is None +@pytest.mark.parametrize( + "line", + [ + "Alice", + "Cardinal Ximénez", + "Alice ", + "Cardinal Ximénez ", + ], +) +def test_validate_sponsor(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_sponsor(1, line)] + assert warnings == [], warnings + + @pytest.mark.parametrize( "line", [ From 1e21a4240b8d6cbb640de387f1167aa11f7b6b6d Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 18:34:07 +0100 Subject: [PATCH 41/83] Test _validate_author --- pep-lint.py | 17 ++++--- pep_sphinx_extensions/tests/test_pep_lint.py | 49 ++++++++++++++++++++ 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/pep-lint.py b/pep-lint.py index f1a12b305a3..b562fbd3029 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -210,16 +210,16 @@ def _validate_pep_number(line): yield 1, "PEP must begin with the 'PEP:' header" return - pep_number = line.removeprefix("PEP: ").strip() + pep_number = line.removeprefix("PEP: ").lstrip() yield from _pep_num(1, pep_number, "'PEP:' header") def _validate_title(line_num, line): """'Title' must be 1-79 characters""" - if len(line.strip()) == 0: + if len(line) == 0: yield line_num, "PEP must have a title" - elif len(line.strip()) > 79: + elif len(line) > 79: yield line_num, "PEP title must be less than 80 characters" @@ -228,8 +228,13 @@ def _validate_author(line_num, body): lines = body.split("\n") for offset, line in enumerate(lines): - if offset > 1 and line[:9].isspace(): - yield line_num + offset, f"Author line must not be over-indented" + if offset >= 1 and line[:9].isspace(): + # Checks for: + # Author: Alice + # Bob + # ^^^^ + # Note that len("Author: ") == 8 + yield line_num + offset, "Author line must not be over-indented" if offset < len(lines) - 1: if not line.endswith(","): yield line_num + offset, "Author continuation lines must end with a comma" @@ -246,7 +251,7 @@ def _validate_sponsor(line_num, line): def _validate_delegate(line_num, line): """'Delegate' must have format 'Name '""" - if line.strip() == "": + if line == "": return # PEP 451 diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index 8860f300dae..27373435501 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -58,6 +58,55 @@ def test_header_pattern_no_match(test_input): assert pep_lint.HEADER_PATTERN.match(test_input) is None +@pytest.mark.parametrize( + "line", + [ + "Alice", + "Alice,", + "Alice, Bob, Charlie", + "Alice,\nBob,\nCharlie", + "Alice,\n Bob,\n Charlie", + "Alice,\n Bob,\n Charlie", + "Cardinal Ximénez", + "Alice ", + "Cardinal Ximénez ", + ], + ids=repr, # the default calls str and renders newlines. +) +def test_validate_author(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_author(1, line)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "line", + [ + "Alice,\n Bob,\n Charlie", + "Alice,\n Bob,\n Charlie", + "Alice,\n Bob,\n Charlie", + "Alice,\n Bob", + ], + ids=repr, # the default calls str and renders newlines. +) +def test_validate_author_over__indented(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_author(1, line)] + assert {*warnings} == {"Author line must not be over-indented"}, warnings + + +@pytest.mark.parametrize( + "line", + [ + "Cardinal Ximénez\nCardinal Biggles\nCardinal Fang", + "Cardinal Ximénez,\nCardinal Biggles\nCardinal Fang", + "Cardinal Ximénez\nCardinal Biggles,\nCardinal Fang", + ], + ids=repr, # the default calls str and renders newlines. +) +def test_validate_author_continuation(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_author(1, line)] + assert {*warnings} == {"Author continuation lines must end with a comma"}, warnings + + @pytest.mark.parametrize( "line", [ From 557c0f7a7e929feb22e5017a715733e6d658093c Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 18:38:17 +0100 Subject: [PATCH 42/83] Test _validate_title --- pep_sphinx_extensions/tests/test_pep_lint.py | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index 27373435501..818365d1e6a 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -58,6 +58,29 @@ def test_header_pattern_no_match(test_input): assert pep_lint.HEADER_PATTERN.match(test_input) is None +@pytest.mark.parametrize( + "line", + [ + "!", + "The Zen of Python", + "A title that is exactly 79 characters long, but shorter than 80 characters long", + ], +) +def test_validate_title(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_title(1, line)] + assert warnings == [], warnings + + +def test_validate_title_blank(): + warnings = [warning for (_, warning) in pep_lint._validate_title(1, "-" * 80)] + assert warnings == ["PEP title must be less than 80 characters"], warnings + + +def test_validate_title_too_long(): + warnings = [warning for (_, warning) in pep_lint._validate_title(1, "")] + assert warnings == ["PEP must have a title"], warnings + + @pytest.mark.parametrize( "line", [ From 0e1ff27ec999354d42c7cc339b2eaa87015b1aa6 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 18:40:26 +0100 Subject: [PATCH 43/83] Test _validate_pep_number --- pep_sphinx_extensions/tests/test_pep_lint.py | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index 818365d1e6a..e6a110b4f42 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -58,6 +58,33 @@ def test_header_pattern_no_match(test_input): assert pep_lint.HEADER_PATTERN.match(test_input) is None +@pytest.mark.parametrize( + "line", + [ + "PEP: 0", + "PEP: 12", + ], +) +def test_validate_pep_number(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_pep_number(line)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "line", + [ + "0", + "PEP:12", + "PEP 0", + "PEP 12", + "PEP:0", + ], +) +def test_validate_pep_number_invalid_header(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_pep_number(line)] + assert warnings == ["PEP must begin with the 'PEP:' header"], warnings + + @pytest.mark.parametrize( "line", [ From 319a99745974a06c07c352a89009de505f59807d Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 18:48:16 +0100 Subject: [PATCH 44/83] Test _validate_required_headers --- pep-lint.py | 2 +- pep_sphinx_extensions/tests/test_pep_lint.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/pep-lint.py b/pep-lint.py index b562fbd3029..70d544b8a4a 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -120,7 +120,7 @@ def check_headers(lines): else: headers_end_line_num = line_num - yield from _validate_required_headers(found_headers) + yield from _validate_required_headers(found_headers.keys()) shifted_line_nums = list(found_headers.values())[1:] for i, (header, line_num) in enumerate(found_headers.items()): diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index e6a110b4f42..adbcae2ddd9 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -58,6 +58,24 @@ def test_header_pattern_no_match(test_input): assert pep_lint.HEADER_PATTERN.match(test_input) is None +def test_validate_required_headers(): + found_headers = dict.fromkeys(("PEP", "Title", "Author", "Status", "Type", "Created")) + warnings = [warning for (_, warning) in pep_lint._validate_required_headers(found_headers)] + assert warnings == [], warnings + + +def test_validate_required_headers_missing(): + found_headers = dict.fromkeys(("PEP", "Title", "Author", "Type")) + warnings = [warning for (_, warning) in pep_lint._validate_required_headers(found_headers)] + assert warnings == ["Must have required header: Status", "Must have required header: Created"], warnings + + +def test_validate_required_headers_order(): + found_headers = dict.fromkeys(("PEP", "Title", "Sponsor", "Author", "Type", "Status", "Replaces", "Created")) + warnings = [warning for (_, warning) in pep_lint._validate_required_headers(found_headers)] + assert warnings == ["Headers must be in PEP 12 order. Correct order: PEP, Title, Author, Sponsor, Status, Type, Created, Replaces"], warnings + + @pytest.mark.parametrize( "line", [ From 89869d35f4369311a165afec3b7676e1a79d2b0c Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 18:48:27 +0100 Subject: [PATCH 45/83] Format --- pep_sphinx_extensions/tests/test_pep_lint.py | 33 +++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index adbcae2ddd9..dbecb378134 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -59,21 +59,36 @@ def test_header_pattern_no_match(test_input): def test_validate_required_headers(): - found_headers = dict.fromkeys(("PEP", "Title", "Author", "Status", "Type", "Created")) - warnings = [warning for (_, warning) in pep_lint._validate_required_headers(found_headers)] + found_headers = dict.fromkeys( + ("PEP", "Title", "Author", "Status", "Type", "Created") + ) + warnings = [ + warning for (_, warning) in pep_lint._validate_required_headers(found_headers) + ] assert warnings == [], warnings def test_validate_required_headers_missing(): found_headers = dict.fromkeys(("PEP", "Title", "Author", "Type")) - warnings = [warning for (_, warning) in pep_lint._validate_required_headers(found_headers)] - assert warnings == ["Must have required header: Status", "Must have required header: Created"], warnings + warnings = [ + warning for (_, warning) in pep_lint._validate_required_headers(found_headers) + ] + assert warnings == [ + "Must have required header: Status", + "Must have required header: Created", + ], warnings def test_validate_required_headers_order(): - found_headers = dict.fromkeys(("PEP", "Title", "Sponsor", "Author", "Type", "Status", "Replaces", "Created")) - warnings = [warning for (_, warning) in pep_lint._validate_required_headers(found_headers)] - assert warnings == ["Headers must be in PEP 12 order. Correct order: PEP, Title, Author, Sponsor, Status, Type, Created, Replaces"], warnings + found_headers = dict.fromkeys( + ("PEP", "Title", "Sponsor", "Author", "Type", "Status", "Replaces", "Created") + ) + warnings = [ + warning for (_, warning) in pep_lint._validate_required_headers(found_headers) + ] + assert warnings == [ + "Headers must be in PEP 12 order. Correct order: PEP, Title, Author, Sponsor, Status, Type, Created, Replaces" + ], warnings @pytest.mark.parametrize( @@ -247,7 +262,9 @@ def test_validate_discussions_to_list_name(line: str): ) def test_validate_discussions_to_invalid_list_domain(line: str): warnings = [warning for (_, warning) in pep_lint._validate_discussions_to(1, line)] - assert warnings == ["Discussions-To must be a valid thread URL or mailing list"], warnings + assert warnings == [ + "Discussions-To must be a valid thread URL or mailing list" + ], warnings @pytest.mark.parametrize( From ce9d603f7324d328ad6b66b023e00b00a4ef4d6e Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 18:54:16 +0100 Subject: [PATCH 46/83] Test check_direct_links --- pep_sphinx_extensions/tests/test_pep_lint.py | 32 ++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/test_pep_lint.py index dbecb378134..66d0219ec56 100644 --- a/pep_sphinx_extensions/tests/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/test_pep_lint.py @@ -58,6 +58,38 @@ def test_header_pattern_no_match(test_input): assert pep_lint.HEADER_PATTERN.match(test_input) is None +@pytest.mark.parametrize( + "line", + [ + "http://www.python.org/dev/peps/pep-0000/", + "https://www.python.org/dev/peps/pep-0000/", + "http://peps.python.org/pep-0000/", + "https://peps.python.org/pep-0000/", + ], +) +def test_check_direct_links_pep(line: str): + warnings = [ + warning for (_, warning) in pep_lint.check_direct_links(1, line) + ] + assert warnings == ["Use the :pep:`NNN` role to refer to PEPs"], warnings + + +@pytest.mark.parametrize( + "line", + [ + "http://www.rfc-editor.org/rfc/rfc2324", + "https://www.rfc-editor.org/rfc/rfc2324", + "http://datatracker.ietf.org/doc/html/rfc2324", + "https://datatracker.ietf.org/doc/html/rfc2324", + ], +) +def test_check_direct_links_rfc(line: str): + warnings = [ + warning for (_, warning) in pep_lint.check_direct_links(1, line) + ] + assert warnings == ["Use the :rfc:`NNN` role to refer to RFCs"], warnings + + def test_validate_required_headers(): found_headers = dict.fromkeys( ("PEP", "Title", "Author", "Status", "Type", "Created") From 20a459d5982d50e8513f61d0eea5279da22e592e Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 18:56:05 +0100 Subject: [PATCH 47/83] Move to tests\pep_lint\ --- pep_sphinx_extensions/tests/{ => pep_lint}/test_pep_lint.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pep_sphinx_extensions/tests/{ => pep_lint}/test_pep_lint.py (100%) diff --git a/pep_sphinx_extensions/tests/test_pep_lint.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py similarity index 100% rename from pep_sphinx_extensions/tests/test_pep_lint.py rename to pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py From a834b5af136d91332c5db629808d55127c0aa8a5 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 18:56:31 +0100 Subject: [PATCH 48/83] Format --- pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py index 66d0219ec56..208a567ad77 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py @@ -68,9 +68,7 @@ def test_header_pattern_no_match(test_input): ], ) def test_check_direct_links_pep(line: str): - warnings = [ - warning for (_, warning) in pep_lint.check_direct_links(1, line) - ] + warnings = [warning for (_, warning) in pep_lint.check_direct_links(1, line)] assert warnings == ["Use the :pep:`NNN` role to refer to PEPs"], warnings @@ -84,9 +82,7 @@ def test_check_direct_links_pep(line: str): ], ) def test_check_direct_links_rfc(line: str): - warnings = [ - warning for (_, warning) in pep_lint.check_direct_links(1, line) - ] + warnings = [warning for (_, warning) in pep_lint.check_direct_links(1, line)] assert warnings == ["Use the :rfc:`NNN` role to refer to RFCs"], warnings From 12ba3caba49c11695fa3daf2d1763e0aad84e74e Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 19:07:05 +0100 Subject: [PATCH 49/83] Move import machinery to conftest.py --- pep_sphinx_extensions/tests/conftest.py | 9 +++++++++ pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py | 9 +-------- 2 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 pep_sphinx_extensions/tests/conftest.py diff --git a/pep_sphinx_extensions/tests/conftest.py b/pep_sphinx_extensions/tests/conftest.py new file mode 100644 index 00000000000..1e82717da47 --- /dev/null +++ b/pep_sphinx_extensions/tests/conftest.py @@ -0,0 +1,9 @@ +import importlib.util +import sys +from pathlib import Path + +# Import "pep-lint.py" as "pep_lint" +PEP_LINT_PATH = Path(__file__).resolve().parents[2] / "pep-lint.py" +spec = importlib.util.spec_from_file_location("pep_lint", PEP_LINT_PATH) +sys.modules["pep_lint"] = pep_lint = importlib.util.module_from_spec(spec) +spec.loader.exec_module(pep_lint) diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py index 208a567ad77..23af8069a72 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py @@ -1,15 +1,8 @@ import datetime as dt -import importlib.util -import sys -from pathlib import Path import pytest -# Import "pep-lint.py" as "pep_lint" -PEP_LINT_PATH = Path(__file__).resolve().parent.parent.parent.joinpath("pep-lint.py") -spec = importlib.util.spec_from_file_location("pep_lint", PEP_LINT_PATH) -sys.modules["pep_lint"] = pep_lint = importlib.util.module_from_spec(spec) -spec.loader.exec_module(pep_lint) +import pep_lint # NoQA: inserted into sys.modules in conftest.py @pytest.mark.parametrize( From b2ef99e7f024c2c6f6f63ff012b76fc972e03338 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 19:14:31 +0100 Subject: [PATCH 50/83] Split out test_headers --- .../tests/pep_lint/test_headers.py | 82 +++++++++++++++++++ .../tests/pep_lint/test_pep_lint.py | 79 ------------------ 2 files changed, 82 insertions(+), 79 deletions(-) create mode 100644 pep_sphinx_extensions/tests/pep_lint/test_headers.py diff --git a/pep_sphinx_extensions/tests/pep_lint/test_headers.py b/pep_sphinx_extensions/tests/pep_lint/test_headers.py new file mode 100644 index 00000000000..ebc3f386615 --- /dev/null +++ b/pep_sphinx_extensions/tests/pep_lint/test_headers.py @@ -0,0 +1,82 @@ +import pytest + +import pep_lint # NoQA: inserted into sys.modules in conftest.py + + +@pytest.mark.parametrize( + ("test_input", "expected"), + [ + # capitalisation + ("Header:", "Header"), + ("header:", "header"), + ("hEADER:", "hEADER"), + ("hEaDeR:", "hEaDeR"), + # trailing spaces + ("Header: ", "Header"), + ("Header: ", "Header"), + ("Header: \t", "Header"), + # trailing content + ("Header: Text", "Header"), + ("Header: 123", "Header"), + ("Header: !", "Header"), + # separators + ("Hyphenated-Header:", "Hyphenated-Header"), + ], +) +def test_header_pattern(test_input, expected): + assert pep_lint.HEADER_PATTERN.match(test_input)[1] == expected + + +@pytest.mark.parametrize( + "test_input", + [ + # trailing content + "Header:Text", + "Header:123", + "Header:!", + # colon position + "Header", + "Header : ", + "Header :", + "SemiColonHeader;", + # separators + "Underscored_Header:", + "Spaced Header:", + "Plus+Header:", + ], +) +def test_header_pattern_no_match(test_input): + assert pep_lint.HEADER_PATTERN.match(test_input) is None + + +def test_validate_required_headers(): + found_headers = dict.fromkeys( + ("PEP", "Title", "Author", "Status", "Type", "Created") + ) + warnings = [ + warning for (_, warning) in pep_lint._validate_required_headers(found_headers) + ] + assert warnings == [], warnings + + +def test_validate_required_headers_missing(): + found_headers = dict.fromkeys(("PEP", "Title", "Author", "Type")) + warnings = [ + warning for (_, warning) in pep_lint._validate_required_headers(found_headers) + ] + assert warnings == [ + "Must have required header: Status", + "Must have required header: Created", + ], warnings + + +def test_validate_required_headers_order(): + found_headers = dict.fromkeys( + ("PEP", "Title", "Sponsor", "Author", "Type", "Status", "Replaces", "Created") + ) + warnings = [ + warning for (_, warning) in pep_lint._validate_required_headers(found_headers) + ] + assert warnings == [ + "Headers must be in PEP 12 order. Correct order: PEP, Title, Author, Sponsor, Status, Type, Created, Replaces" + ], warnings diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py index 23af8069a72..38265532dd3 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py @@ -5,52 +5,6 @@ import pep_lint # NoQA: inserted into sys.modules in conftest.py -@pytest.mark.parametrize( - ("test_input", "expected"), - [ - # capitalisation - ("Header:", "Header"), - ("header:", "header"), - ("hEADER:", "hEADER"), - ("hEaDeR:", "hEaDeR"), - # trailing spaces - ("Header: ", "Header"), - ("Header: ", "Header"), - ("Header: \t", "Header"), - # trailing content - ("Header: Text", "Header"), - ("Header: 123", "Header"), - ("Header: !", "Header"), - # separators - ("Hyphenated-Header:", "Hyphenated-Header"), - ], -) -def test_header_pattern(test_input, expected): - assert pep_lint.HEADER_PATTERN.match(test_input)[1] == expected - - -@pytest.mark.parametrize( - "test_input", - [ - # trailing content - "Header:Text", - "Header:123", - "Header:!", - # colon position - "Header", - "Header : ", - "Header :", - "SemiColonHeader;", - # separators - "Underscored_Header:", - "Spaced Header:", - "Plus+Header:", - ], -) -def test_header_pattern_no_match(test_input): - assert pep_lint.HEADER_PATTERN.match(test_input) is None - - @pytest.mark.parametrize( "line", [ @@ -79,39 +33,6 @@ def test_check_direct_links_rfc(line: str): assert warnings == ["Use the :rfc:`NNN` role to refer to RFCs"], warnings -def test_validate_required_headers(): - found_headers = dict.fromkeys( - ("PEP", "Title", "Author", "Status", "Type", "Created") - ) - warnings = [ - warning for (_, warning) in pep_lint._validate_required_headers(found_headers) - ] - assert warnings == [], warnings - - -def test_validate_required_headers_missing(): - found_headers = dict.fromkeys(("PEP", "Title", "Author", "Type")) - warnings = [ - warning for (_, warning) in pep_lint._validate_required_headers(found_headers) - ] - assert warnings == [ - "Must have required header: Status", - "Must have required header: Created", - ], warnings - - -def test_validate_required_headers_order(): - found_headers = dict.fromkeys( - ("PEP", "Title", "Sponsor", "Author", "Type", "Status", "Replaces", "Created") - ) - warnings = [ - warning for (_, warning) in pep_lint._validate_required_headers(found_headers) - ] - assert warnings == [ - "Headers must be in PEP 12 order. Correct order: PEP, Title, Author, Sponsor, Status, Type, Created, Replaces" - ], warnings - - @pytest.mark.parametrize( "line", [ From 22e1a7210fdbe6aff8f3c3cfabae640369939737 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 19:15:13 +0100 Subject: [PATCH 51/83] Split out test_direct_links --- .../tests/pep_lint/test_direct_links.py | 31 +++++++++++++++++++ .../tests/pep_lint/test_pep_lint.py | 28 ----------------- 2 files changed, 31 insertions(+), 28 deletions(-) create mode 100644 pep_sphinx_extensions/tests/pep_lint/test_direct_links.py diff --git a/pep_sphinx_extensions/tests/pep_lint/test_direct_links.py b/pep_sphinx_extensions/tests/pep_lint/test_direct_links.py new file mode 100644 index 00000000000..87da05d102a --- /dev/null +++ b/pep_sphinx_extensions/tests/pep_lint/test_direct_links.py @@ -0,0 +1,31 @@ +import pytest + +import pep_lint # NoQA: inserted into sys.modules in conftest.py + + +@pytest.mark.parametrize( + "line", + [ + "http://www.python.org/dev/peps/pep-0000/", + "https://www.python.org/dev/peps/pep-0000/", + "http://peps.python.org/pep-0000/", + "https://peps.python.org/pep-0000/", + ], +) +def test_check_direct_links_pep(line: str): + warnings = [warning for (_, warning) in pep_lint.check_direct_links(1, line)] + assert warnings == ["Use the :pep:`NNN` role to refer to PEPs"], warnings + + +@pytest.mark.parametrize( + "line", + [ + "http://www.rfc-editor.org/rfc/rfc2324", + "https://www.rfc-editor.org/rfc/rfc2324", + "http://datatracker.ietf.org/doc/html/rfc2324", + "https://datatracker.ietf.org/doc/html/rfc2324", + ], +) +def test_check_direct_links_rfc(line: str): + warnings = [warning for (_, warning) in pep_lint.check_direct_links(1, line)] + assert warnings == ["Use the :rfc:`NNN` role to refer to RFCs"], warnings diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py index 38265532dd3..4ab086b75da 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py @@ -5,34 +5,6 @@ import pep_lint # NoQA: inserted into sys.modules in conftest.py -@pytest.mark.parametrize( - "line", - [ - "http://www.python.org/dev/peps/pep-0000/", - "https://www.python.org/dev/peps/pep-0000/", - "http://peps.python.org/pep-0000/", - "https://peps.python.org/pep-0000/", - ], -) -def test_check_direct_links_pep(line: str): - warnings = [warning for (_, warning) in pep_lint.check_direct_links(1, line)] - assert warnings == ["Use the :pep:`NNN` role to refer to PEPs"], warnings - - -@pytest.mark.parametrize( - "line", - [ - "http://www.rfc-editor.org/rfc/rfc2324", - "https://www.rfc-editor.org/rfc/rfc2324", - "http://datatracker.ietf.org/doc/html/rfc2324", - "https://datatracker.ietf.org/doc/html/rfc2324", - ], -) -def test_check_direct_links_rfc(line: str): - warnings = [warning for (_, warning) in pep_lint.check_direct_links(1, line)] - assert warnings == ["Use the :rfc:`NNN` role to refer to RFCs"], warnings - - @pytest.mark.parametrize( "line", [ From 6454342a2c46e664fe7636a231961cf9372aceaa Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 19:25:36 +0100 Subject: [PATCH 52/83] Split out test_pep_number --- .../tests/pep_lint/test_pep_lint.py | 104 ----------------- .../tests/pep_lint/test_pep_number.py | 109 ++++++++++++++++++ 2 files changed, 109 insertions(+), 104 deletions(-) create mode 100644 pep_sphinx_extensions/tests/pep_lint/test_pep_number.py diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py index 4ab086b75da..7142c312ddb 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py @@ -5,31 +5,6 @@ import pep_lint # NoQA: inserted into sys.modules in conftest.py -@pytest.mark.parametrize( - "line", - [ - "PEP: 0", - "PEP: 12", - ], -) -def test_validate_pep_number(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_pep_number(line)] - assert warnings == [], warnings - - -@pytest.mark.parametrize( - "line", - [ - "0", - "PEP:12", - "PEP 0", - "PEP 12", - "PEP:0", - ], -) -def test_validate_pep_number_invalid_header(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_pep_number(line)] - assert warnings == ["PEP must begin with the 'PEP:' header"], warnings @pytest.mark.parametrize( @@ -560,85 +535,6 @@ def test_validate_resolution_invalid(line: str): assert warnings == ["Resolution must be a valid thread URL"], warnings -@pytest.mark.parametrize( - ("pep_number", "expected_warnings"), - [ - # valid entries - ("0", set()), - ("1", set()), - ("12", set()), - ("20", set()), - ("101", set()), - ("801", set()), - ("3099", set()), - ("9999", set()), - # empty - ("", {"not blank"}), - # leading zeros - ("01", {"leading zeros"}), - ("001", {"leading zeros"}), - ("0001", {"leading zeros"}), - ("00001", {"leading zeros"}), - # non-numeric - ("a", {"non-numeric"}), - ("123abc", {"non-numeric"}), - ("0123A", {"leading zeros", "non-numeric"}), - ("0", {"non-numeric"}), - ("101", {"non-numeric"}), - ("9999", {"non-numeric"}), - ("𝟎", {"non-numeric"}), - ("𝟘", {"non-numeric"}), - ("𝟏𝟚", {"non-numeric"}), - ("𝟸𝟬", {"non-numeric"}), - ("-1", {"non-numeric"}), - ("+1", {"non-numeric"}), - # out of bounds - ("10000", {"range"}), - ("54321", {"range"}), - ("99999", {"range"}), - ("32768", {"range"}), - ], - # call str() on each parameterised value in the test ID. - ids=str, -) -def test_pep_num_checker(pep_number: str, expected_warnings: set): - warnings = [ - warning for (_, warning) in pep_lint._pep_num(1, pep_number, "") - ] - - found_warnings = set() - pep_number = pep_number.strip() - - if "not blank" in expected_warnings: - found_warnings.add("not blank") - expected = f" must not be blank: {pep_number!r}" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if "leading zeros" in expected_warnings: - found_warnings.add("leading zeros") - expected = f" must not contain leading zeros: {pep_number!r}" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if "non-numeric" in expected_warnings: - found_warnings.add("non-numeric") - expected = f" must be numeric: {pep_number!r}" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if "range" in expected_warnings: - found_warnings.add("range") - expected = f" must be between 0 and 9999: {pep_number!r}" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if expected_warnings == set(): - assert warnings == [], warnings - - assert found_warnings == expected_warnings - - @pytest.mark.parametrize( ("email", "expected_warnings"), [ diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_number.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_number.py new file mode 100644 index 00000000000..57868a6e84b --- /dev/null +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_number.py @@ -0,0 +1,109 @@ +import pytest + +import pep_lint # NoQA: inserted into sys.modules in conftest.py + + +@pytest.mark.parametrize( + "line", + [ + "PEP: 0", + "PEP: 12", + ], +) +def test_validate_pep_number(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_pep_number(line)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "line", + [ + "0", + "PEP:12", + "PEP 0", + "PEP 12", + "PEP:0", + ], +) +def test_validate_pep_number_invalid_header(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_pep_number(line)] + assert warnings == ["PEP must begin with the 'PEP:' header"], warnings + + +@pytest.mark.parametrize( + ("pep_number", "expected_warnings"), + [ + # valid entries + ("0", set()), + ("1", set()), + ("12", set()), + ("20", set()), + ("101", set()), + ("801", set()), + ("3099", set()), + ("9999", set()), + # empty + ("", {"not blank"}), + # leading zeros + ("01", {"leading zeros"}), + ("001", {"leading zeros"}), + ("0001", {"leading zeros"}), + ("00001", {"leading zeros"}), + # non-numeric + ("a", {"non-numeric"}), + ("123abc", {"non-numeric"}), + ("0123A", {"leading zeros", "non-numeric"}), + ("0", {"non-numeric"}), + ("101", {"non-numeric"}), + ("9999", {"non-numeric"}), + ("𝟎", {"non-numeric"}), + ("𝟘", {"non-numeric"}), + ("𝟏𝟚", {"non-numeric"}), + ("𝟸𝟬", {"non-numeric"}), + ("-1", {"non-numeric"}), + ("+1", {"non-numeric"}), + # out of bounds + ("10000", {"range"}), + ("54321", {"range"}), + ("99999", {"range"}), + ("32768", {"range"}), + ], + # call str() on each parameterised value in the test ID. + ids=str, +) +def test_pep_num_checker(pep_number: str, expected_warnings: set): + warnings = [ + warning for (_, warning) in pep_lint._pep_num(1, pep_number, "") + ] + + found_warnings = set() + pep_number = pep_number.strip() + + if "not blank" in expected_warnings: + found_warnings.add("not blank") + expected = f" must not be blank: {pep_number!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "leading zeros" in expected_warnings: + found_warnings.add("leading zeros") + expected = f" must not contain leading zeros: {pep_number!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "non-numeric" in expected_warnings: + found_warnings.add("non-numeric") + expected = f" must be numeric: {pep_number!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "range" in expected_warnings: + found_warnings.add("range") + expected = f" must be between 0 and 9999: {pep_number!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if expected_warnings == set(): + assert warnings == [], warnings + + assert found_warnings == expected_warnings From ba4b7d47c02923e05b1f39c4b3491eb89e03bb41 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 19:28:59 +0100 Subject: [PATCH 53/83] Split out test_email --- .../tests/pep_lint/test_email.py | 239 ++++++++++++++++++ .../tests/pep_lint/test_pep_lint.py | 238 ----------------- 2 files changed, 239 insertions(+), 238 deletions(-) create mode 100644 pep_sphinx_extensions/tests/pep_lint/test_email.py diff --git a/pep_sphinx_extensions/tests/pep_lint/test_email.py b/pep_sphinx_extensions/tests/pep_lint/test_email.py new file mode 100644 index 00000000000..4d6e4361865 --- /dev/null +++ b/pep_sphinx_extensions/tests/pep_lint/test_email.py @@ -0,0 +1,239 @@ +import pytest + +import pep_lint # NoQA: inserted into sys.modules in conftest.py + + +@pytest.mark.parametrize( + "line", + [ + "Alice", + "Alice,", + "Alice, Bob, Charlie", + "Alice,\nBob,\nCharlie", + "Alice,\n Bob,\n Charlie", + "Alice,\n Bob,\n Charlie", + "Cardinal Ximénez", + "Alice ", + "Cardinal Ximénez ", + ], + ids=repr, # the default calls str and renders newlines. +) +def test_validate_author(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_author(1, line)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "line", + [ + "Alice,\n Bob,\n Charlie", + "Alice,\n Bob,\n Charlie", + "Alice,\n Bob,\n Charlie", + "Alice,\n Bob", + ], + ids=repr, # the default calls str and renders newlines. +) +def test_validate_author_over__indented(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_author(1, line)] + assert {*warnings} == {"Author line must not be over-indented"}, warnings + + +@pytest.mark.parametrize( + "line", + [ + "Cardinal Ximénez\nCardinal Biggles\nCardinal Fang", + "Cardinal Ximénez,\nCardinal Biggles\nCardinal Fang", + "Cardinal Ximénez\nCardinal Biggles,\nCardinal Fang", + ], + ids=repr, # the default calls str and renders newlines. +) +def test_validate_author_continuation(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_author(1, line)] + assert {*warnings} == {"Author continuation lines must end with a comma"}, warnings + + +@pytest.mark.parametrize( + "line", + [ + "Alice", + "Cardinal Ximénez", + "Alice ", + "Cardinal Ximénez ", + ], +) +def test_validate_sponsor(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_sponsor(1, line)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "line", + [ + "", + "Alice, Bob, Charlie", + "Alice, Bob, Charlie,", + "Alice ", + "Cardinal Ximénez ", + ], +) +def test_validate_delegate(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_delegate(1, line)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + ("email", "expected_warnings"), + [ + # ... entries must not contain multiple '...' + ("Cardinal Ximénez <<", {"multiple <"}), + ("Cardinal Ximénez <<<", {"multiple <"}), + ("Cardinal Ximénez >>", {"multiple >"}), + ("Cardinal Ximénez >>>", {"multiple >"}), + ("Cardinal Ximénez <<<>>>", {"multiple <", "multiple >"}), + ("Cardinal Ximénez @@", {"multiple @"}), + ("Cardinal Ximénez <<@@@>", {"multiple <", "multiple @"}), + ("Cardinal Ximénez <@@@>>", {"multiple >", "multiple @"}), + ("Cardinal Ximénez <<@@>>", {"multiple <", "multiple >", "multiple @"}), + # valid names + ("Cardinal Ximénez", set()), + (" Cardinal Ximénez", set()), + ("\t\tCardinal Ximénez", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez\t\t", set()), + ("Cardinal O'Ximénez", set()), + ("Cardinal Ximénez, Inquisitor", set()), + ("Cardinal Ximénez-Biggles", set()), + ("Cardinal Ximénez-Biggles, Inquisitor", set()), + ("Cardinal T. S. I. Ximénez", set()), + # ... entries must have a valid 'Name' + ("Cardinal_Ximénez", {"valid name"}), + ("Cardinal Ximénez 3", {"valid name"}), + ("~ Cardinal Ximénez ~", {"valid name"}), + ("Cardinal Ximénez!", {"valid name"}), + ("@Cardinal Ximénez", {"valid name"}), + ("Cardinal_Ximénez <>", {"valid name"}), + ("Cardinal Ximénez 3 <>", {"valid name"}), + ("~ Cardinal Ximénez ~ <>", {"valid name"}), + ("Cardinal Ximénez! <>", {"valid name"}), + ("@Cardinal Ximénez <>", {"valid name"}), + # ... entries must be formatted as 'Name ' + ("Cardinal Ximénez<>", {"name "}), + ("Cardinal Ximénez<", {"name "}), + ("Cardinal Ximénez <", {"name "}), + ("Cardinal Ximénez <", {"name "}), + ("Cardinal Ximénez <>", {"name "}), + # ... entries must contain a valid email address (missing) + ("Cardinal Ximénez <>", {"valid email"}), + ("Cardinal Ximénez <> ", {"valid email"}), + ("Cardinal Ximénez <@> ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez < at > ", {"valid email"}), + # ... entries must contain a valid email address (local) + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez < Cardinal Ximenez @spanish.inquisition> ", {"valid email"}), + ("Cardinal Ximénez <(Cardinal.Ximenez)@spanish.inquisition>", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ( + "Cardinal Ximénez ", + {"multiple <", "multiple >", "valid email"}, + ), + ( + "Cardinal Ximénez ", + {"multiple @", "valid email"}, + ), + (r"Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez <[Cardinal.Ximenez]@spanish.inquisition>", {"valid email"}), + ('Cardinal Ximénez <"Cardinal"Ximenez"@spanish.inquisition>', {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + # ... entries must contain a valid email address (domain) + ( + "Cardinal Ximénez ", + {"valid email"}, + ), + ("Cardinal Ximénez ", {"valid email"}), + ("Cardinal Ximénez ", {"valid email"}), + ( + "Cardinal Ximénez ", + {"valid email"}, + ), + # valid name-emails + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez <{Cardinal.Ximenez}@spanish.inquisition>", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ("Cardinal Ximénez ", set()), + ], + # call str() on each parameterised value in the test ID. + ids=str, +) +def test_email_checker(email: str, expected_warnings: set): + warnings = [warning for (_, warning) in pep_lint._email(1, email, "")] + + found_warnings = set() + email = email.strip() + + if "multiple <" in expected_warnings: + found_warnings.add("multiple <") + expected = f" entries must not contain multiple '<': {email!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "multiple >" in expected_warnings: + found_warnings.add("multiple >") + expected = f" entries must not contain multiple '>': {email!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "multiple @" in expected_warnings: + found_warnings.add("multiple @") + expected = f" entries must not contain multiple '@': {email!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "valid name" in expected_warnings: + found_warnings.add("valid name") + expected = f" entries must begin with a valid 'Name': {email!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "name " in expected_warnings: + found_warnings.add("name ") + expected = f" entries must be formatted as 'Name ': {email!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "valid email" in expected_warnings: + found_warnings.add("valid email") + expected = f" entries must contain a valid email address: {email!r}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if expected_warnings == set(): + assert warnings == [], warnings + + assert found_warnings == expected_warnings diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py index 7142c312ddb..f65032b1afe 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py @@ -5,8 +5,6 @@ import pep_lint # NoQA: inserted into sys.modules in conftest.py - - @pytest.mark.parametrize( "line", [ @@ -30,84 +28,6 @@ def test_validate_title_too_long(): assert warnings == ["PEP must have a title"], warnings -@pytest.mark.parametrize( - "line", - [ - "Alice", - "Alice,", - "Alice, Bob, Charlie", - "Alice,\nBob,\nCharlie", - "Alice,\n Bob,\n Charlie", - "Alice,\n Bob,\n Charlie", - "Cardinal Ximénez", - "Alice ", - "Cardinal Ximénez ", - ], - ids=repr, # the default calls str and renders newlines. -) -def test_validate_author(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_author(1, line)] - assert warnings == [], warnings - - -@pytest.mark.parametrize( - "line", - [ - "Alice,\n Bob,\n Charlie", - "Alice,\n Bob,\n Charlie", - "Alice,\n Bob,\n Charlie", - "Alice,\n Bob", - ], - ids=repr, # the default calls str and renders newlines. -) -def test_validate_author_over__indented(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_author(1, line)] - assert {*warnings} == {"Author line must not be over-indented"}, warnings - - -@pytest.mark.parametrize( - "line", - [ - "Cardinal Ximénez\nCardinal Biggles\nCardinal Fang", - "Cardinal Ximénez,\nCardinal Biggles\nCardinal Fang", - "Cardinal Ximénez\nCardinal Biggles,\nCardinal Fang", - ], - ids=repr, # the default calls str and renders newlines. -) -def test_validate_author_continuation(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_author(1, line)] - assert {*warnings} == {"Author continuation lines must end with a comma"}, warnings - - -@pytest.mark.parametrize( - "line", - [ - "Alice", - "Cardinal Ximénez", - "Alice ", - "Cardinal Ximénez ", - ], -) -def test_validate_sponsor(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_sponsor(1, line)] - assert warnings == [], warnings - - -@pytest.mark.parametrize( - "line", - [ - "", - "Alice, Bob, Charlie", - "Alice, Bob, Charlie,", - "Alice ", - "Cardinal Ximénez ", - ], -) -def test_validate_delegate(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_delegate(1, line)] - assert warnings == [], warnings - - @pytest.mark.parametrize( "line", [ @@ -535,164 +455,6 @@ def test_validate_resolution_invalid(line: str): assert warnings == ["Resolution must be a valid thread URL"], warnings -@pytest.mark.parametrize( - ("email", "expected_warnings"), - [ - # ... entries must not contain multiple '...' - ("Cardinal Ximénez <<", {"multiple <"}), - ("Cardinal Ximénez <<<", {"multiple <"}), - ("Cardinal Ximénez >>", {"multiple >"}), - ("Cardinal Ximénez >>>", {"multiple >"}), - ("Cardinal Ximénez <<<>>>", {"multiple <", "multiple >"}), - ("Cardinal Ximénez @@", {"multiple @"}), - ("Cardinal Ximénez <<@@@>", {"multiple <", "multiple @"}), - ("Cardinal Ximénez <@@@>>", {"multiple >", "multiple @"}), - ("Cardinal Ximénez <<@@>>", {"multiple <", "multiple >", "multiple @"}), - # valid names - ("Cardinal Ximénez", set()), - (" Cardinal Ximénez", set()), - ("\t\tCardinal Ximénez", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez\t\t", set()), - ("Cardinal O'Ximénez", set()), - ("Cardinal Ximénez, Inquisitor", set()), - ("Cardinal Ximénez-Biggles", set()), - ("Cardinal Ximénez-Biggles, Inquisitor", set()), - ("Cardinal T. S. I. Ximénez", set()), - # ... entries must have a valid 'Name' - ("Cardinal_Ximénez", {"valid name"}), - ("Cardinal Ximénez 3", {"valid name"}), - ("~ Cardinal Ximénez ~", {"valid name"}), - ("Cardinal Ximénez!", {"valid name"}), - ("@Cardinal Ximénez", {"valid name"}), - ("Cardinal_Ximénez <>", {"valid name"}), - ("Cardinal Ximénez 3 <>", {"valid name"}), - ("~ Cardinal Ximénez ~ <>", {"valid name"}), - ("Cardinal Ximénez! <>", {"valid name"}), - ("@Cardinal Ximénez <>", {"valid name"}), - # ... entries must be formatted as 'Name ' - ("Cardinal Ximénez<>", {"name "}), - ("Cardinal Ximénez<", {"name "}), - ("Cardinal Ximénez <", {"name "}), - ("Cardinal Ximénez <", {"name "}), - ("Cardinal Ximénez <>", {"name "}), - # ... entries must contain a valid email address (missing) - ("Cardinal Ximénez <>", {"valid email"}), - ("Cardinal Ximénez <> ", {"valid email"}), - ("Cardinal Ximénez <@> ", {"valid email"}), - ("Cardinal Ximénez ", {"valid email"}), - ("Cardinal Ximénez < at > ", {"valid email"}), - # ... entries must contain a valid email address (local) - ("Cardinal Ximénez ", {"valid email"}), - ("Cardinal Ximénez ", {"valid email"}), - ("Cardinal Ximénez ", {"valid email"}), - ("Cardinal Ximénez ", {"valid email"}), - ("Cardinal Ximénez ", {"valid email"}), - ("Cardinal Ximénez < Cardinal Ximenez @spanish.inquisition> ", {"valid email"}), - ("Cardinal Ximénez <(Cardinal.Ximenez)@spanish.inquisition>", {"valid email"}), - ("Cardinal Ximénez ", {"valid email"}), - ("Cardinal Ximénez ", {"valid email"}), - ("Cardinal Ximénez ", {"valid email"}), - ( - "Cardinal Ximénez ", - {"multiple <", "multiple >", "valid email"}, - ), - ( - "Cardinal Ximénez ", - {"multiple @", "valid email"}, - ), - (r"Cardinal Ximénez ", {"valid email"}), - ("Cardinal Ximénez <[Cardinal.Ximenez]@spanish.inquisition>", {"valid email"}), - ('Cardinal Ximénez <"Cardinal"Ximenez"@spanish.inquisition>', {"valid email"}), - ("Cardinal Ximénez ", {"valid email"}), - ("Cardinal Ximénez ", {"valid email"}), - ("Cardinal Ximénez ", {"valid email"}), - # ... entries must contain a valid email address (domain) - ( - "Cardinal Ximénez ", - {"valid email"}, - ), - ("Cardinal Ximénez ", {"valid email"}), - ("Cardinal Ximénez ", {"valid email"}), - ( - "Cardinal Ximénez ", - {"valid email"}, - ), - # valid name-emails - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez <{Cardinal.Ximenez}@spanish.inquisition>", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ("Cardinal Ximénez ", set()), - ], - # call str() on each parameterised value in the test ID. - ids=str, -) -def test_email_checker(email: str, expected_warnings: set): - warnings = [warning for (_, warning) in pep_lint._email(1, email, "")] - - found_warnings = set() - email = email.strip() - - if "multiple <" in expected_warnings: - found_warnings.add("multiple <") - expected = f" entries must not contain multiple '<': {email!r}" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if "multiple >" in expected_warnings: - found_warnings.add("multiple >") - expected = f" entries must not contain multiple '>': {email!r}" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if "multiple @" in expected_warnings: - found_warnings.add("multiple @") - expected = f" entries must not contain multiple '@': {email!r}" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if "valid name" in expected_warnings: - found_warnings.add("valid name") - expected = f" entries must begin with a valid 'Name': {email!r}" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if "name " in expected_warnings: - found_warnings.add("name ") - expected = f" entries must be formatted as 'Name ': {email!r}" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if "valid email" in expected_warnings: - found_warnings.add("valid email") - expected = f" entries must contain a valid email address: {email!r}" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if expected_warnings == set(): - assert warnings == [], warnings - - assert found_warnings == expected_warnings - - @pytest.mark.parametrize( "thread_url", [ From f1e4683f143016c60e86d82232dd7a805264596a Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 19:33:35 +0100 Subject: [PATCH 54/83] Split out test_post_url --- .../tests/pep_lint/test_pep_lint.py | 295 ----------------- .../tests/pep_lint/test_post_url.py | 298 ++++++++++++++++++ 2 files changed, 298 insertions(+), 295 deletions(-) create mode 100644 pep_sphinx_extensions/tests/pep_lint/test_post_url.py diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py index f65032b1afe..d6a4f59a139 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py @@ -28,54 +28,6 @@ def test_validate_title_too_long(): assert warnings == ["PEP must have a title"], warnings -@pytest.mark.parametrize( - "line", - [ - "list-name@python.org", - "distutils-sig@python.org", - "csv@python.org", - "python-3000@python.org", - "ipaddr-py-dev@googlegroups.com", - "python-tulip@googlegroups.com", - "https://discuss.python.org/t/thread-name/123456", - "https://discuss.python.org/t/thread-name/123456/", - "https://discuss.python.org/t/thread_name/123456", - "https://discuss.python.org/t/thread_name/123456/", - "https://discuss.python.org/t/123456/", - "https://discuss.python.org/t/123456", - ], -) -def test_validate_discussions_to_valid(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_discussions_to(1, line)] - assert warnings == [], warnings - - -@pytest.mark.parametrize( - "line", - [ - "$pecial+chars@python.org", - "a-discussions-to-list!@googlegroups.com", - ], -) -def test_validate_discussions_to_list_name(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_discussions_to(1, line)] - assert warnings == ["Discussions-To must be a valid mailing list"], warnings - - -@pytest.mark.parametrize( - "line", - [ - "list-name@python.org.uk", - "distutils-sig@mail-server.example", - ], -) -def test_validate_discussions_to_invalid_list_domain(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_discussions_to(1, line)] - assert warnings == [ - "Discussions-To must be a valid thread URL or mailing list" - ], warnings - - @pytest.mark.parametrize( "line", [ @@ -392,253 +344,6 @@ def test_validate_python_version(line: str, expected_warnings: set): assert found_warnings == expected_warnings -@pytest.mark.parametrize( - "body", - [ - "", - ( - "01-Jan-2001, 02-Feb-2002,\n " - "03-Mar-2003, 04-Apr-2004,\n " - "05-May-2005," - ), - ( - "`01-Jan-2000 `__,\n " - "`11-Mar-2005 `__,\n " - "`21-May-2010 `__,\n " - "`31-Jul-2015 `__," - ), - "01-Jan-2001, `02-Feb-2002 `__,\n03-Mar-2003", - ], -) -def test_validate_post_history_valid(body: str): - warnings = [warning for (_, warning) in pep_lint._validate_post_history(1, body)] - assert warnings == [], warnings - - -@pytest.mark.parametrize( - "line", - [ - "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123", - "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123#Anchor", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/#Anchor", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123#Anchor123", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/#Anchor123", - ], -) -def test_validate_resolution_valid(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_resolution(1, line)] - assert warnings == [], warnings - - -@pytest.mark.parametrize( - "line", - [ - "https://mail.python.org/archives/list/list-name@python.org/thread", - "https://mail.python.org/archives/list/list-name@python.org/message", - "https://mail.python.org/archives/list/list-name@python.org/thread/", - "https://mail.python.org/archives/list/list-name@python.org/message/", - "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123#anchor", - "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/#anchor", - "https://mail.python.org/archives/list/list-name@python.org/message/#abcXYZ123", - "https://mail.python.org/archives/list/list-name@python.org/message/#abcXYZ123/", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/anchor/", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/anchor/", - "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123", - "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123/", - ], -) -def test_validate_resolution_invalid(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_resolution(1, line)] - assert warnings == ["Resolution must be a valid thread URL"], warnings - - -@pytest.mark.parametrize( - "thread_url", - [ - "https://discuss.python.org/t/thread-name/123456", - "https://discuss.python.org/t/thread-name/123456/", - "https://discuss.python.org/t/thread_name/123456", - "https://discuss.python.org/t/thread_name/123456/", - "https://discuss.python.org/t/thread-name/123456/654321/", - "https://discuss.python.org/t/thread-name/123456/654321", - "https://discuss.python.org/t/123456", - "https://discuss.python.org/t/123456/", - "https://discuss.python.org/t/123456/654321/", - "https://discuss.python.org/t/123456/654321", - "https://discuss.python.org/t/1", - "https://discuss.python.org/t/1/", - "https://mail.python.org/pipermail/list-name/0000-Month/0123456.html", - "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123", - "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/", - ], -) -def test_thread_checker_valid(thread_url: str): - warnings = [warning for (_, warning) in pep_lint._thread(1, thread_url, "")] - assert warnings == [], warnings - - -@pytest.mark.parametrize( - "thread_url", - [ - "http://link.example", - "list-name@python.org", - "distutils-sig@python.org", - "csv@python.org", - "python-3000@python.org", - "ipaddr-py-dev@googlegroups.com", - "python-tulip@googlegroups.com", - "https://link.example", - "https://discuss.python.org", - "https://discuss.python.org/", - "https://discuss.python.org/c/category", - "https://discuss.python.org/t/thread_name/123456//", - "https://discuss.python.org/t/thread+name/123456", - "https://discuss.python.org/t/thread+name/123456#", - "https://discuss.python.org/t/thread+name/123456/#", - "https://discuss.python.org/t/thread+name/123456/#anchor", - "https://discuss.python.org/t/thread+name/", - "https://discuss.python.org/t/thread+name", - "https://discuss.python.org/t/thread-name/123abc", - "https://discuss.python.org/t/thread-name/123abc/", - "https://discuss.python.org/t/thread-name/123456/123abc", - "https://discuss.python.org/t/thread-name/123456/123abc/", - "https://discuss.python.org/t/123/456/789", - "https://discuss.python.org/t/123/456/789/", - "https://discuss.python.org/t/#/", - "https://discuss.python.org/t/#", - "https://mail.python.org/pipermail/list+name/0000-Month/0123456.html", - "https://mail.python.org/pipermail/list-name/YYYY-Month/0123456.html", - "https://mail.python.org/pipermail/list-name/0123456/0123456.html", - "https://mail.python.org/pipermail/list-name/0000-Month/0123456", - "https://mail.python.org/pipermail/list-name/0000-Month/0123456/", - "https://mail.python.org/pipermail/list-name/0000-Month/", - "https://mail.python.org/pipermail/list-name/0000-Month", - "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123#anchor", - "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/#anchor", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123#anchor", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/#anchor", - "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123", - "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123/", - ], -) -def test_thread_checker_invalid(thread_url: str): - warnings = [warning for (_, warning) in pep_lint._thread(1, thread_url, "")] - assert warnings == [" must be a valid thread URL"], warnings - - -@pytest.mark.parametrize( - "thread_url", - [ - "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123", - "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123#Anchor", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/#Anchor", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123#Anchor123", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/#Anchor123", - ], -) -def test_thread_checker_valid_allow_message(thread_url: str): - warnings = [ - warning - for (_, warning) in pep_lint._thread( - 1, thread_url, "", allow_message=True - ) - ] - assert warnings == [], warnings - - -@pytest.mark.parametrize( - "thread_url", - [ - "https://mail.python.org/archives/list/list-name@python.org/thread", - "https://mail.python.org/archives/list/list-name@python.org/message", - "https://mail.python.org/archives/list/list-name@python.org/thread/", - "https://mail.python.org/archives/list/list-name@python.org/message/", - "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123#anchor", - "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/#anchor", - "https://mail.python.org/archives/list/list-name@python.org/message/#abcXYZ123", - "https://mail.python.org/archives/list/list-name@python.org/message/#abcXYZ123/", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/anchor/", - "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/anchor/", - "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123", - "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123/", - ], -) -def test_thread_checker_invalid_allow_message(thread_url: str): - warnings = [ - warning - for (_, warning) in pep_lint._thread( - 1, thread_url, "", allow_message=True - ) - ] - assert warnings == [" must be a valid thread URL"], warnings - - -@pytest.mark.parametrize( - "thread_url", - [ - "list-name@python.org", - "distutils-sig@python.org", - "csv@python.org", - "python-3000@python.org", - "ipaddr-py-dev@googlegroups.com", - "python-tulip@googlegroups.com", - "https://discuss.python.org/t/thread-name/123456", - "https://discuss.python.org/t/thread-name/123456/", - "https://discuss.python.org/t/thread_name/123456", - "https://discuss.python.org/t/thread_name/123456/", - "https://discuss.python.org/t/123456/", - "https://discuss.python.org/t/123456", - ], -) -def test_thread_checker_valid_discussions_to(thread_url: str): - warnings = [ - warning - for (_, warning) in pep_lint._thread( - 1, thread_url, "", discussions_to=True - ) - ] - assert warnings == [], warnings - - -@pytest.mark.parametrize( - "thread_url", - [ - "https://discuss.python.org/t/thread-name/123456/000", - "https://discuss.python.org/t/thread-name/123456/000/", - "https://discuss.python.org/t/thread_name/123456/000", - "https://discuss.python.org/t/thread_name/123456/000/", - "https://discuss.python.org/t/123456/000/", - "https://discuss.python.org/t/12345656/000", - "https://discuss.python.org/t/thread-name", - "https://discuss.python.org/t/thread_name", - "https://discuss.python.org/t/thread+name", - ], -) -def test_thread_checker_invalid_discussions_to(thread_url: str): - warnings = [ - warning - for (_, warning) in pep_lint._thread( - 1, thread_url, "", discussions_to=True - ) - ] - assert warnings == [" must be a valid thread URL"], warnings - - -def test_thread_checker_allow_message_discussions_to(): - with pytest.raises(ValueError, match="cannot both be True"): - list( - pep_lint._thread(1, "", "", allow_message=True, discussions_to=True) - ) - - @pytest.mark.parametrize( "date_str", [ diff --git a/pep_sphinx_extensions/tests/pep_lint/test_post_url.py b/pep_sphinx_extensions/tests/pep_lint/test_post_url.py new file mode 100644 index 00000000000..20cc89dd0d9 --- /dev/null +++ b/pep_sphinx_extensions/tests/pep_lint/test_post_url.py @@ -0,0 +1,298 @@ +import pytest + +import pep_lint # NoQA: inserted into sys.modules in conftest.py + + +@pytest.mark.parametrize( + "line", + [ + "list-name@python.org", + "distutils-sig@python.org", + "csv@python.org", + "python-3000@python.org", + "ipaddr-py-dev@googlegroups.com", + "python-tulip@googlegroups.com", + "https://discuss.python.org/t/thread-name/123456", + "https://discuss.python.org/t/thread-name/123456/", + "https://discuss.python.org/t/thread_name/123456", + "https://discuss.python.org/t/thread_name/123456/", + "https://discuss.python.org/t/123456/", + "https://discuss.python.org/t/123456", + ], +) +def test_validate_discussions_to_valid(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_discussions_to(1, line)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "line", + [ + "$pecial+chars@python.org", + "a-discussions-to-list!@googlegroups.com", + ], +) +def test_validate_discussions_to_list_name(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_discussions_to(1, line)] + assert warnings == ["Discussions-To must be a valid mailing list"], warnings + + +@pytest.mark.parametrize( + "line", + [ + "list-name@python.org.uk", + "distutils-sig@mail-server.example", + ], +) +def test_validate_discussions_to_invalid_list_domain(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_discussions_to(1, line)] + assert warnings == [ + "Discussions-To must be a valid thread URL or mailing list" + ], warnings + + +@pytest.mark.parametrize( + "body", + [ + "", + ( + "01-Jan-2001, 02-Feb-2002,\n " + "03-Mar-2003, 04-Apr-2004,\n " + "05-May-2005," + ), + ( + "`01-Jan-2000 `__,\n " + "`11-Mar-2005 `__,\n " + "`21-May-2010 `__,\n " + "`31-Jul-2015 `__," + ), + "01-Jan-2001, `02-Feb-2002 `__,\n03-Mar-2003", + ], +) +def test_validate_post_history_valid(body: str): + warnings = [warning for (_, warning) in pep_lint._validate_post_history(1, body)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "line", + [ + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123#Anchor", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/#Anchor", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123#Anchor123", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/#Anchor123", + ], +) +def test_validate_resolution_valid(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_resolution(1, line)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "line", + [ + "https://mail.python.org/archives/list/list-name@python.org/thread", + "https://mail.python.org/archives/list/list-name@python.org/message", + "https://mail.python.org/archives/list/list-name@python.org/thread/", + "https://mail.python.org/archives/list/list-name@python.org/message/", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123#anchor", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/#anchor", + "https://mail.python.org/archives/list/list-name@python.org/message/#abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/message/#abcXYZ123/", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/anchor/", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/anchor/", + "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123/", + ], +) +def test_validate_resolution_invalid(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_resolution(1, line)] + assert warnings == ["Resolution must be a valid thread URL"], warnings + + +@pytest.mark.parametrize( + "thread_url", + [ + "https://discuss.python.org/t/thread-name/123456", + "https://discuss.python.org/t/thread-name/123456/", + "https://discuss.python.org/t/thread_name/123456", + "https://discuss.python.org/t/thread_name/123456/", + "https://discuss.python.org/t/thread-name/123456/654321/", + "https://discuss.python.org/t/thread-name/123456/654321", + "https://discuss.python.org/t/123456", + "https://discuss.python.org/t/123456/", + "https://discuss.python.org/t/123456/654321/", + "https://discuss.python.org/t/123456/654321", + "https://discuss.python.org/t/1", + "https://discuss.python.org/t/1/", + "https://mail.python.org/pipermail/list-name/0000-Month/0123456.html", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/", + ], +) +def test_thread_checker_valid(thread_url: str): + warnings = [warning for (_, warning) in pep_lint._thread(1, thread_url, "")] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "thread_url", + [ + "http://link.example", + "list-name@python.org", + "distutils-sig@python.org", + "csv@python.org", + "python-3000@python.org", + "ipaddr-py-dev@googlegroups.com", + "python-tulip@googlegroups.com", + "https://link.example", + "https://discuss.python.org", + "https://discuss.python.org/", + "https://discuss.python.org/c/category", + "https://discuss.python.org/t/thread_name/123456//", + "https://discuss.python.org/t/thread+name/123456", + "https://discuss.python.org/t/thread+name/123456#", + "https://discuss.python.org/t/thread+name/123456/#", + "https://discuss.python.org/t/thread+name/123456/#anchor", + "https://discuss.python.org/t/thread+name/", + "https://discuss.python.org/t/thread+name", + "https://discuss.python.org/t/thread-name/123abc", + "https://discuss.python.org/t/thread-name/123abc/", + "https://discuss.python.org/t/thread-name/123456/123abc", + "https://discuss.python.org/t/thread-name/123456/123abc/", + "https://discuss.python.org/t/123/456/789", + "https://discuss.python.org/t/123/456/789/", + "https://discuss.python.org/t/#/", + "https://discuss.python.org/t/#", + "https://mail.python.org/pipermail/list+name/0000-Month/0123456.html", + "https://mail.python.org/pipermail/list-name/YYYY-Month/0123456.html", + "https://mail.python.org/pipermail/list-name/0123456/0123456.html", + "https://mail.python.org/pipermail/list-name/0000-Month/0123456", + "https://mail.python.org/pipermail/list-name/0000-Month/0123456/", + "https://mail.python.org/pipermail/list-name/0000-Month/", + "https://mail.python.org/pipermail/list-name/0000-Month", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123#anchor", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/#anchor", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123#anchor", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/#anchor", + "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123/", + ], +) +def test_thread_checker_invalid(thread_url: str): + warnings = [warning for (_, warning) in pep_lint._thread(1, thread_url, "")] + assert warnings == [" must be a valid thread URL"], warnings + + +@pytest.mark.parametrize( + "thread_url", + [ + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123#Anchor", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/#Anchor", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123#Anchor123", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/#Anchor123", + ], +) +def test_thread_checker_valid_allow_message(thread_url: str): + warnings = [ + warning + for (_, warning) in pep_lint._thread( + 1, thread_url, "", allow_message=True + ) + ] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "thread_url", + [ + "https://mail.python.org/archives/list/list-name@python.org/thread", + "https://mail.python.org/archives/list/list-name@python.org/message", + "https://mail.python.org/archives/list/list-name@python.org/thread/", + "https://mail.python.org/archives/list/list-name@python.org/message/", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123#anchor", + "https://mail.python.org/archives/list/list-name@python.org/thread/abcXYZ123/#anchor", + "https://mail.python.org/archives/list/list-name@python.org/message/#abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/message/#abcXYZ123/", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/anchor/", + "https://mail.python.org/archives/list/list-name@python.org/message/abcXYZ123/anchor/", + "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123", + "https://mail.python.org/archives/list/list-name@python.org/spam/abcXYZ123/", + ], +) +def test_thread_checker_invalid_allow_message(thread_url: str): + warnings = [ + warning + for (_, warning) in pep_lint._thread( + 1, thread_url, "", allow_message=True + ) + ] + assert warnings == [" must be a valid thread URL"], warnings + + +@pytest.mark.parametrize( + "thread_url", + [ + "list-name@python.org", + "distutils-sig@python.org", + "csv@python.org", + "python-3000@python.org", + "ipaddr-py-dev@googlegroups.com", + "python-tulip@googlegroups.com", + "https://discuss.python.org/t/thread-name/123456", + "https://discuss.python.org/t/thread-name/123456/", + "https://discuss.python.org/t/thread_name/123456", + "https://discuss.python.org/t/thread_name/123456/", + "https://discuss.python.org/t/123456/", + "https://discuss.python.org/t/123456", + ], +) +def test_thread_checker_valid_discussions_to(thread_url: str): + warnings = [ + warning + for (_, warning) in pep_lint._thread( + 1, thread_url, "", discussions_to=True + ) + ] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "thread_url", + [ + "https://discuss.python.org/t/thread-name/123456/000", + "https://discuss.python.org/t/thread-name/123456/000/", + "https://discuss.python.org/t/thread_name/123456/000", + "https://discuss.python.org/t/thread_name/123456/000/", + "https://discuss.python.org/t/123456/000/", + "https://discuss.python.org/t/12345656/000", + "https://discuss.python.org/t/thread-name", + "https://discuss.python.org/t/thread_name", + "https://discuss.python.org/t/thread+name", + ], +) +def test_thread_checker_invalid_discussions_to(thread_url: str): + warnings = [ + warning + for (_, warning) in pep_lint._thread( + 1, thread_url, "", discussions_to=True + ) + ] + assert warnings == [" must be a valid thread URL"], warnings + + +def test_thread_checker_allow_message_discussions_to(): + with pytest.raises(ValueError, match="cannot both be True"): + list( + pep_lint._thread(1, "", "", allow_message=True, discussions_to=True) + ) From c7940b3dc7dbf6095c53b2f011ad648f4c9f9ab1 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 19:37:42 +0100 Subject: [PATCH 55/83] Split out test_date --- .../tests/pep_lint/test_date.py | 106 ++++++++++++++++++ .../tests/pep_lint/test_pep_lint.py | 101 ----------------- 2 files changed, 106 insertions(+), 101 deletions(-) create mode 100644 pep_sphinx_extensions/tests/pep_lint/test_date.py diff --git a/pep_sphinx_extensions/tests/pep_lint/test_date.py b/pep_sphinx_extensions/tests/pep_lint/test_date.py new file mode 100644 index 00000000000..a57c2fc9496 --- /dev/null +++ b/pep_sphinx_extensions/tests/pep_lint/test_date.py @@ -0,0 +1,106 @@ +import datetime as dt + +import pytest + +import pep_lint # NoQA: inserted into sys.modules in conftest.py + + +@pytest.mark.parametrize( + "line", + [ + # valid entries + "01-Jan-2000", + "29-Feb-2016", + "31-Dec-2000", + "01-Apr-2003", + "01-Apr-2007", + "01-Apr-2009", + "01-Jan-1990", + ], +) +def test_validate_created(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_created(1, line)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "date_str", + [ + # valid entries + "01-Jan-2000", + "29-Feb-2016", + "31-Dec-2000", + "01-Apr-2003", + "01-Apr-2007", + "01-Apr-2009", + "01-Jan-1990", + ], +) +def test_date_checker_valid(date_str: str): + warnings = [warning for (_, warning) in pep_lint._date(1, date_str, "")] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "date_str", + [ + # malformed + "2000-01-01", + "01 January 2000", + "1 Jan 2000", + "1-Jan-2000", + "1-January-2000", + "Jan-1-2000", + "January 1 2000", + "January 01 2000", + "01/01/2000", + "01/Jan/2000", # 🇬🇧, 🇦🇺, 🇨🇦, 🇳🇿, 🇮🇪 , ... + "Jan/01/2000", # 🇺🇸 + "1st January 2000", + "The First day of January in the year of Our Lord Two Thousand", + "Jan, 1, 2000", + "2000-Jan-1", + "2000-Jan-01", + "2000-January-1", + "2000-January-01", + "00 Jan 2000", + "00-Jan-2000", + ], +) +def test_date_checker_malformed(date_str: str): + warnings = [warning for (_, warning) in pep_lint._date(1, date_str, "")] + expected = f" must be a 'DD-mmm-YYYY' date: {date_str!r}" + assert warnings == [expected], warnings + + +@pytest.mark.parametrize( + "date_str", + [ + # too early + "31-Dec-1989", + "01-Apr-1916", + "01-Jan-0020", + "01-Jan-0023", + ], +) +def test_date_checker_too_early(date_str: str): + warnings = [warning for (_, warning) in pep_lint._date(1, date_str, "")] + expected = f" must not be before Python was invented: {date_str!r}" + assert warnings == [expected], warnings + + +@pytest.mark.parametrize( + "date_str", + [ + # the future + "31-Dec-2999", + "01-Jan-2100", + "01-Jan-2100", + (dt.datetime.now() + dt.timedelta(days=15)).strftime("%d-%b-%Y"), + (dt.datetime.now() + dt.timedelta(days=100)).strftime("%d-%b-%Y"), + ], +) +def test_date_checker_too_late(date_str: str): + warnings = [warning for (_, warning) in pep_lint._date(1, date_str, "")] + expected = f" must not be in the future: {date_str!r}" + assert warnings == [expected], warnings diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py index d6a4f59a139..395924eb6cc 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py @@ -1,5 +1,3 @@ -import datetime as dt - import pytest import pep_lint # NoQA: inserted into sys.modules in conftest.py @@ -229,24 +227,6 @@ def test_validate_pep_references_separators(line: str): ], warnings -@pytest.mark.parametrize( - "line", - [ - # valid entries - "01-Jan-2000", - "29-Feb-2016", - "31-Dec-2000", - "01-Apr-2003", - "01-Apr-2007", - "01-Apr-2009", - "01-Jan-1990", - ], -) -def test_validate_created(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_created(1, line)] - assert warnings == [], warnings - - @pytest.mark.parametrize( ("line", "expected_warnings"), [ @@ -344,84 +324,3 @@ def test_validate_python_version(line: str, expected_warnings: set): assert found_warnings == expected_warnings -@pytest.mark.parametrize( - "date_str", - [ - # valid entries - "01-Jan-2000", - "29-Feb-2016", - "31-Dec-2000", - "01-Apr-2003", - "01-Apr-2007", - "01-Apr-2009", - "01-Jan-1990", - ], -) -def test_date_checker_valid(date_str: str): - warnings = [warning for (_, warning) in pep_lint._date(1, date_str, "")] - assert warnings == [], warnings - - -@pytest.mark.parametrize( - "date_str", - [ - # malformed - "2000-01-01", - "01 January 2000", - "1 Jan 2000", - "1-Jan-2000", - "1-January-2000", - "Jan-1-2000", - "January 1 2000", - "January 01 2000", - "01/01/2000", - "01/Jan/2000", # 🇬🇧, 🇦🇺, 🇨🇦, 🇳🇿, 🇮🇪 , ... - "Jan/01/2000", # 🇺🇸 - "1st January 2000", - "The First day of January in the year of Our Lord Two Thousand", - "Jan, 1, 2000", - "2000-Jan-1", - "2000-Jan-01", - "2000-January-1", - "2000-January-01", - "00 Jan 2000", - "00-Jan-2000", - ], -) -def test_date_checker_malformed(date_str: str): - warnings = [warning for (_, warning) in pep_lint._date(1, date_str, "")] - expected = f" must be a 'DD-mmm-YYYY' date: {date_str!r}" - assert warnings == [expected], warnings - - -@pytest.mark.parametrize( - "date_str", - [ - # too early - "31-Dec-1989", - "01-Apr-1916", - "01-Jan-0020", - "01-Jan-0023", - ], -) -def test_date_checker_too_early(date_str: str): - warnings = [warning for (_, warning) in pep_lint._date(1, date_str, "")] - expected = f" must not be before Python was invented: {date_str!r}" - assert warnings == [expected], warnings - - -@pytest.mark.parametrize( - "date_str", - [ - # the future - "31-Dec-2999", - "01-Jan-2100", - "01-Jan-2100", - (dt.datetime.now() + dt.timedelta(days=15)).strftime("%d-%b-%Y"), - (dt.datetime.now() + dt.timedelta(days=100)).strftime("%d-%b-%Y"), - ], -) -def test_date_checker_too_late(date_str: str): - warnings = [warning for (_, warning) in pep_lint._date(1, date_str, "")] - expected = f" must not be in the future: {date_str!r}" - assert warnings == [expected], warnings From 8399fc9b95a27d8f97fb1bbde0443321e70c5f19 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 19:38:46 +0100 Subject: [PATCH 56/83] Move remaining to test_headers --- .../tests/pep_lint/test_headers.py | 323 +++++++++++++++++ .../tests/pep_lint/test_pep_lint.py | 326 ------------------ 2 files changed, 323 insertions(+), 326 deletions(-) delete mode 100644 pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py diff --git a/pep_sphinx_extensions/tests/pep_lint/test_headers.py b/pep_sphinx_extensions/tests/pep_lint/test_headers.py index ebc3f386615..df77519d016 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_headers.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_headers.py @@ -80,3 +80,326 @@ def test_validate_required_headers_order(): assert warnings == [ "Headers must be in PEP 12 order. Correct order: PEP, Title, Author, Sponsor, Status, Type, Created, Replaces" ], warnings + + +@pytest.mark.parametrize( + "line", + [ + "!", + "The Zen of Python", + "A title that is exactly 79 characters long, but shorter than 80 characters long", + ], +) +def test_validate_title(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_title(1, line)] + assert warnings == [], warnings + + +def test_validate_title_blank(): + warnings = [warning for (_, warning) in pep_lint._validate_title(1, "-" * 80)] + assert warnings == ["PEP title must be less than 80 characters"], warnings + + +def test_validate_title_too_long(): + warnings = [warning for (_, warning) in pep_lint._validate_title(1, "")] + assert warnings == ["PEP must have a title"], warnings + + +@pytest.mark.parametrize( + "line", + [ + "Accepted", + "Active", + "April Fool!", + "Deferred", + "Draft", + "Final", + "Provisional", + "Rejected", + "Superseded", + "Withdrawn", + ], +) +def test_validate_status_valid(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_status(1, line)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "line", + [ + "Standards Track", + "Informational", + "Process", + "accepted", + "active", + "april fool!", + "deferred", + "draft", + "final", + "provisional", + "rejected", + "superseded", + "withdrawn", + ], +) +def test_validate_status_invalid(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_status(1, line)] + assert warnings == ["Status must be a valid PEP status"], warnings + + +@pytest.mark.parametrize( + "line", + [ + "Standards Track", + "Informational", + "Process", + ], +) +def test_validate_type_valid(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_type(1, line)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "line", + [ + "standards track", + "informational", + "process", + "Accepted", + "Active", + "April Fool!", + "Deferred", + "Draft", + "Final", + "Provisional", + "Rejected", + "Superseded", + "Withdrawn", + ], +) +def test_validate_type_invalid(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_type(1, line)] + assert warnings == ["Type must be a valid PEP type"], warnings + + +@pytest.mark.parametrize( + ("line", "expected_warnings"), + [ + # valid entries + ("Governance", set()), + ("Packaging", set()), + ("Typing", set()), + ("Release", set()), + ("Governance, Packaging", set()), + ("Packaging, Typing", set()), + # duplicates + ("Governance, Governance", {"duplicates"}), + ("Release, Release", {"duplicates"}), + ("Release, Release", {"duplicates"}), + ("Spam, Spam", {"duplicates", "valid"}), + ("lobster, lobster", {"duplicates", "capitalisation", "valid"}), + ("governance, governance", {"duplicates", "capitalisation"}), + # capitalisation + ("governance", {"capitalisation"}), + ("packaging", {"capitalisation"}), + ("typing", {"capitalisation"}), + ("release", {"capitalisation"}), + ("Governance, release", {"capitalisation"}), + # validity + ("Spam", {"valid"}), + ("lobster", {"capitalisation", "valid"}), + # sorted + ("Packaging, Governance", {"sorted"}), + ("Typing, Release", {"sorted"}), + ("Release, Governance", {"sorted"}), + ("spam, packaging", {"capitalisation", "valid", "sorted"}), + ], + # call str() on each parameterised value in the test ID. + ids=str, +) +def test_validate_topic(line: str, expected_warnings: set): + warnings = [warning for (_, warning) in pep_lint._validate_topic(1, line)] + + found_warnings = set() + + if "duplicates" in expected_warnings: + found_warnings.add("duplicates") + expected = "Topic must not contain duplicates" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "capitalisation" in expected_warnings: + found_warnings.add("capitalisation") + expected = "Topic must be properly capitalised (Title Case)" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "valid" in expected_warnings: + found_warnings.add("valid") + expected = "Topic must be for a valid sub-index" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "sorted" in expected_warnings: + found_warnings.add("sorted") + expected = "Topic must be sorted lexicographically" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if expected_warnings == set(): + assert warnings == [], warnings + + assert found_warnings == expected_warnings + + +def test_validate_content_type_valid(): + warnings = [ + warning for (_, warning) in pep_lint._validate_content_type(1, "text/x-rst") + ] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "line", + [ + "text/plain", + "text/markdown", + "text/csv", + "text/rtf", + "text/javascript", + "text/html", + "text/xml", + ], +) +def test_validate_content_type_invalid(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_content_type(1, line)] + assert warnings == ["Content-Type must be 'text/x-rst'"], warnings + + +@pytest.mark.parametrize( + "line", + [ + "0, 1, 8, 12, 20,", + "101, 801,", + "3099, 9999", + ], +) +def test_validate_pep_references(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_pep_references(1, line)] + assert warnings == [], warnings + + +@pytest.mark.parametrize( + "line", + [ + "0,1,8, 12, 20,", + "101,801,", + "3099, 9998,9999", + ], +) +def test_validate_pep_references_separators(line: str): + warnings = [warning for (_, warning) in pep_lint._validate_pep_references(1, line)] + assert warnings == [ + "PEP references must be separated by comma-spaces (', ')" + ], warnings + + +@pytest.mark.parametrize( + ("line", "expected_warnings"), + [ + # valid entries + ("1.0, 2.4, 2.7, 2.8, 3.0, 3.1, 3.4, 3.7, 3.11, 3.14", set()), + ("2.x", set()), + ("3.x", set()), + ("3.0.1", set()), + # segments + ("", {"segments"}), + ("1", {"segments"}), + ("1.2.3.4", {"segments"}), + # major + ("0.0", {"major"}), + ("4.0", {"major"}), + ("9.0", {"major"}), + # minor number + ("3.a", {"minor numeric"}), + ("3.spam", {"minor numeric"}), + ("3.0+", {"minor numeric"}), + ("3.0-9", {"minor numeric"}), + ("9.Z", {"major", "minor numeric"}), + # minor leading zero + ("3.01", {"minor zero"}), + ("0.00", {"major", "minor zero"}), + # micro empty + ("3.x.1", {"micro empty"}), + ("9.x.1", {"major", "micro empty"}), + # micro leading zero + ("3.3.0", {"micro zero"}), + ("3.3.00", {"micro zero"}), + ("3.3.01", {"micro zero"}), + ("3.0.0", {"micro zero"}), + ("3.00.0", {"minor zero", "micro zero"}), + ("0.00.0", {"major", "minor zero", "micro zero"}), + # micro number + ("3.0.a", {"micro numeric"}), + ("0.3.a", {"major", "micro numeric"}), + ], + # call str() on each parameterised value in the test ID. + ids=str, +) +def test_validate_python_version(line: str, expected_warnings: set): + warnings = [warning for (_, warning) in pep_lint._validate_python_version(1, line)] + + found_warnings = set() + + if "segments" in expected_warnings: + found_warnings.add("segments") + expected = f"Python-Version must have two or three segments: {line}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "major" in expected_warnings: + found_warnings.add("major") + expected = f"Python-Version major part must be 1, 2, or 3: {line}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "minor numeric" in expected_warnings: + found_warnings.add("minor numeric") + expected = f"Python-Version minor part must be numeric: {line}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "minor zero" in expected_warnings: + found_warnings.add("minor zero") + expected = f"Python-Version minor part must not have leading zeros: {line}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "micro empty" in expected_warnings: + found_warnings.add("micro empty") + expected = ( + f"Python-Version micro part must be empty if minor part is 'x': {line}" + ) + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "micro zero" in expected_warnings: + found_warnings.add("micro zero") + expected = f"Python-Version micro part must not have leading zeros: {line}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if "micro numeric" in expected_warnings: + found_warnings.add("micro numeric") + expected = f"Python-Version micro part must be numeric: {line}" + matching = [w for w in warnings if w == expected] + assert matching == [expected], warnings + + if expected_warnings == set(): + assert warnings == [], warnings + + assert found_warnings == expected_warnings + + diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py deleted file mode 100644 index 395924eb6cc..00000000000 --- a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py +++ /dev/null @@ -1,326 +0,0 @@ -import pytest - -import pep_lint # NoQA: inserted into sys.modules in conftest.py - - -@pytest.mark.parametrize( - "line", - [ - "!", - "The Zen of Python", - "A title that is exactly 79 characters long, but shorter than 80 characters long", - ], -) -def test_validate_title(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_title(1, line)] - assert warnings == [], warnings - - -def test_validate_title_blank(): - warnings = [warning for (_, warning) in pep_lint._validate_title(1, "-" * 80)] - assert warnings == ["PEP title must be less than 80 characters"], warnings - - -def test_validate_title_too_long(): - warnings = [warning for (_, warning) in pep_lint._validate_title(1, "")] - assert warnings == ["PEP must have a title"], warnings - - -@pytest.mark.parametrize( - "line", - [ - "Accepted", - "Active", - "April Fool!", - "Deferred", - "Draft", - "Final", - "Provisional", - "Rejected", - "Superseded", - "Withdrawn", - ], -) -def test_validate_status_valid(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_status(1, line)] - assert warnings == [], warnings - - -@pytest.mark.parametrize( - "line", - [ - "Standards Track", - "Informational", - "Process", - "accepted", - "active", - "april fool!", - "deferred", - "draft", - "final", - "provisional", - "rejected", - "superseded", - "withdrawn", - ], -) -def test_validate_status_invalid(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_status(1, line)] - assert warnings == ["Status must be a valid PEP status"], warnings - - -@pytest.mark.parametrize( - "line", - [ - "Standards Track", - "Informational", - "Process", - ], -) -def test_validate_type_valid(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_type(1, line)] - assert warnings == [], warnings - - -@pytest.mark.parametrize( - "line", - [ - "standards track", - "informational", - "process", - "Accepted", - "Active", - "April Fool!", - "Deferred", - "Draft", - "Final", - "Provisional", - "Rejected", - "Superseded", - "Withdrawn", - ], -) -def test_validate_type_invalid(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_type(1, line)] - assert warnings == ["Type must be a valid PEP type"], warnings - - -@pytest.mark.parametrize( - ("line", "expected_warnings"), - [ - # valid entries - ("Governance", set()), - ("Packaging", set()), - ("Typing", set()), - ("Release", set()), - ("Governance, Packaging", set()), - ("Packaging, Typing", set()), - # duplicates - ("Governance, Governance", {"duplicates"}), - ("Release, Release", {"duplicates"}), - ("Release, Release", {"duplicates"}), - ("Spam, Spam", {"duplicates", "valid"}), - ("lobster, lobster", {"duplicates", "capitalisation", "valid"}), - ("governance, governance", {"duplicates", "capitalisation"}), - # capitalisation - ("governance", {"capitalisation"}), - ("packaging", {"capitalisation"}), - ("typing", {"capitalisation"}), - ("release", {"capitalisation"}), - ("Governance, release", {"capitalisation"}), - # validity - ("Spam", {"valid"}), - ("lobster", {"capitalisation", "valid"}), - # sorted - ("Packaging, Governance", {"sorted"}), - ("Typing, Release", {"sorted"}), - ("Release, Governance", {"sorted"}), - ("spam, packaging", {"capitalisation", "valid", "sorted"}), - ], - # call str() on each parameterised value in the test ID. - ids=str, -) -def test_validate_topic(line: str, expected_warnings: set): - warnings = [warning for (_, warning) in pep_lint._validate_topic(1, line)] - - found_warnings = set() - - if "duplicates" in expected_warnings: - found_warnings.add("duplicates") - expected = "Topic must not contain duplicates" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if "capitalisation" in expected_warnings: - found_warnings.add("capitalisation") - expected = "Topic must be properly capitalised (Title Case)" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if "valid" in expected_warnings: - found_warnings.add("valid") - expected = "Topic must be for a valid sub-index" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if "sorted" in expected_warnings: - found_warnings.add("sorted") - expected = "Topic must be sorted lexicographically" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if expected_warnings == set(): - assert warnings == [], warnings - - assert found_warnings == expected_warnings - - -def test_validate_content_type_valid(): - warnings = [ - warning for (_, warning) in pep_lint._validate_content_type(1, "text/x-rst") - ] - assert warnings == [], warnings - - -@pytest.mark.parametrize( - "line", - [ - "text/plain", - "text/markdown", - "text/csv", - "text/rtf", - "text/javascript", - "text/html", - "text/xml", - ], -) -def test_validate_content_type_invalid(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_content_type(1, line)] - assert warnings == ["Content-Type must be 'text/x-rst'"], warnings - - -@pytest.mark.parametrize( - "line", - [ - "0, 1, 8, 12, 20,", - "101, 801,", - "3099, 9999", - ], -) -def test_validate_pep_references(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_pep_references(1, line)] - assert warnings == [], warnings - - -@pytest.mark.parametrize( - "line", - [ - "0,1,8, 12, 20,", - "101,801,", - "3099, 9998,9999", - ], -) -def test_validate_pep_references_separators(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_pep_references(1, line)] - assert warnings == [ - "PEP references must be separated by comma-spaces (', ')" - ], warnings - - -@pytest.mark.parametrize( - ("line", "expected_warnings"), - [ - # valid entries - ("1.0, 2.4, 2.7, 2.8, 3.0, 3.1, 3.4, 3.7, 3.11, 3.14", set()), - ("2.x", set()), - ("3.x", set()), - ("3.0.1", set()), - # segments - ("", {"segments"}), - ("1", {"segments"}), - ("1.2.3.4", {"segments"}), - # major - ("0.0", {"major"}), - ("4.0", {"major"}), - ("9.0", {"major"}), - # minor number - ("3.a", {"minor numeric"}), - ("3.spam", {"minor numeric"}), - ("3.0+", {"minor numeric"}), - ("3.0-9", {"minor numeric"}), - ("9.Z", {"major", "minor numeric"}), - # minor leading zero - ("3.01", {"minor zero"}), - ("0.00", {"major", "minor zero"}), - # micro empty - ("3.x.1", {"micro empty"}), - ("9.x.1", {"major", "micro empty"}), - # micro leading zero - ("3.3.0", {"micro zero"}), - ("3.3.00", {"micro zero"}), - ("3.3.01", {"micro zero"}), - ("3.0.0", {"micro zero"}), - ("3.00.0", {"minor zero", "micro zero"}), - ("0.00.0", {"major", "minor zero", "micro zero"}), - # micro number - ("3.0.a", {"micro numeric"}), - ("0.3.a", {"major", "micro numeric"}), - ], - # call str() on each parameterised value in the test ID. - ids=str, -) -def test_validate_python_version(line: str, expected_warnings: set): - warnings = [warning for (_, warning) in pep_lint._validate_python_version(1, line)] - - found_warnings = set() - - if "segments" in expected_warnings: - found_warnings.add("segments") - expected = f"Python-Version must have two or three segments: {line}" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if "major" in expected_warnings: - found_warnings.add("major") - expected = f"Python-Version major part must be 1, 2, or 3: {line}" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if "minor numeric" in expected_warnings: - found_warnings.add("minor numeric") - expected = f"Python-Version minor part must be numeric: {line}" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if "minor zero" in expected_warnings: - found_warnings.add("minor zero") - expected = f"Python-Version minor part must not have leading zeros: {line}" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if "micro empty" in expected_warnings: - found_warnings.add("micro empty") - expected = ( - f"Python-Version micro part must be empty if minor part is 'x': {line}" - ) - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if "micro zero" in expected_warnings: - found_warnings.add("micro zero") - expected = f"Python-Version micro part must not have leading zeros: {line}" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if "micro numeric" in expected_warnings: - found_warnings.add("micro numeric") - expected = f"Python-Version micro part must be numeric: {line}" - matching = [w for w in warnings if w == expected] - assert matching == [expected], warnings - - if expected_warnings == set(): - assert warnings == [], warnings - - assert found_warnings == expected_warnings - - From a3ad8952199ebdb1ac020dec107f4d40847e8b8e Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 19:39:29 +0100 Subject: [PATCH 57/83] Add __init__.py --- pep_sphinx_extensions/tests/pep_lint/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pep_sphinx_extensions/tests/pep_lint/__init__.py diff --git a/pep_sphinx_extensions/tests/pep_lint/__init__.py b/pep_sphinx_extensions/tests/pep_lint/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From 41964b6b0b77f80bdd039e17d56a33c6f4d663b9 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 20:23:40 +0100 Subject: [PATCH 58/83] Add integration test --- pep-lint.py | 4 +- .../tests/pep_lint/test_pep_lint.py | 48 +++++++++++++++++++ pep_sphinx_extensions/tests/peps/pep-9002.rst | 23 +++++++++ 3 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py create mode 100644 pep_sphinx_extensions/tests/peps/pep-9002.rst diff --git a/pep-lint.py b/pep-lint.py index 70d544b8a4a..9e93d107a51 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -195,12 +195,12 @@ def _validate_required_headers(found_headers): if missing := REQUIRED_HEADERS.difference(found_headers): for missing_header in sorted(missing, key=ALL_HEADERS.index): - yield 0, f"Must have required header: {missing_header}" + yield 1, f"Must have required header: {missing_header}" ordered_headers = sorted(found_headers, key=ALL_HEADERS.index) if list(found_headers) != ordered_headers: order_str = ", ".join(ordered_headers) - yield 0, "Headers must be in PEP 12 order. Correct order: " + order_str + yield 1, "Headers must be in PEP 12 order. Correct order: " + order_str def _validate_pep_number(line): diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py new file mode 100644 index 00000000000..1da9e99638d --- /dev/null +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py @@ -0,0 +1,48 @@ +from pathlib import Path + +import pep_lint # NoQA: inserted into sys.modules in conftest.py + +PEP_9002 = Path(__file__).parent.parent / "peps" / "PEP-9002.rst" + + +def test_with_fake_pep(): + content = PEP_9002.read_text(encoding="utf-8").splitlines() + warnings = list(pep_lint.check_pep(PEP_9002, content)) + assert warnings == [ + (1, "PEP must begin with the 'PEP:' header"), + (9, "Must not have duplicate header: Sponsor "), + (10, "Must not have invalid header: Horse-Guards"), + (1, "Must have required header: PEP"), + (1, "Must have required header: Type"), + ( + 1, + "Headers must be in PEP 12 order. Correct order: Title, Version, " + "Author, Sponsor, BDFL-Delegate, Discussions-To, Status, Topic, " + "Content-Type, Requires, Created, Python-Version, Post-History, " + "Resolution", + ), + (4, "Author continuation lines must end with a comma"), + (5, "Author line must not be over-indented"), + (7, "Python-Version major part must be 1, 2, or 3: 4.0"), + ( + 8, + "Sponsor entries must begin with a valid 'Name': " + r"'Sponsor:\nHorse-Guards: Parade'", + ), + (11, "Created must be a 'DD-mmm-YYYY' date: '1-Jan-1989'"), + (12, "Delegate entries must begin with a valid 'Name': 'Barry!'"), + (13, "Status must be a valid PEP status"), + (14, "Topic must not contain duplicates"), + (14, "Topic must be properly capitalised (Title Case)"), + (14, "Topic must be for a valid sub-index"), + (14, "Topic must be sorted lexicographically"), + (15, "Content-Type must be 'text/x-rst'"), + (16, "PEP references must be separated by comma-spaces (', ')"), + (17, "Discussions-To must be a valid thread URL or mailing list"), + (18, "Post-History must be a 'DD-mmm-YYYY' date: '2-Feb-2000'"), + (18, "Post-History must be a valid thread URL"), + (19, "Post-History must be a 'DD-mmm-YYYY' date: '3-Mar-2001'"), + (19, "Post-History must be a valid thread URL"), + (20, "Resolution must be a valid thread URL"), + (23, "Use the :pep:`NNN` role to refer to PEPs"), + ] diff --git a/pep_sphinx_extensions/tests/peps/pep-9002.rst b/pep_sphinx_extensions/tests/peps/pep-9002.rst new file mode 100644 index 00000000000..208569e034c --- /dev/null +++ b/pep_sphinx_extensions/tests/peps/pep-9002.rst @@ -0,0 +1,23 @@ +PEP:9002 +Title: Nobody expects the example PEP! +Author: Cardinal Ximénez , + Cardinal Biggles + Cardinal Fang +Version: 4.0 +Python-Version: 4.0 +Sponsor: +Sponsor: +Horse-Guards: Parade +Created: 1-Jan-1989 +BDFL-Delegate: Barry! +Status: Draught +Topic: Inquisiting, Governance, Governance, packaging +Content-Type: video/quicktime +Requires: 0020,1,2,3, 7, 8 +Discussions-To: MR ALBERT SPIM, I,OOO,OO8 LONDON ROAD, OXFORD +Post-History: `2-Feb-2000 `__ + `3-Mar-2001 `__ +Resolution: + + +https://peps.python.org/pep-9002.html From b95a67f97666c5f92eb4955498c081d43f7f0283 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 20:31:43 +0100 Subject: [PATCH 59/83] Format --- pep_sphinx_extensions/tests/pep_lint/test_date.py | 3 +-- pep_sphinx_extensions/tests/pep_lint/test_direct_links.py | 3 +-- pep_sphinx_extensions/tests/pep_lint/test_email.py | 3 +-- pep_sphinx_extensions/tests/pep_lint/test_headers.py | 5 +---- pep_sphinx_extensions/tests/pep_lint/test_pep_number.py | 3 +-- pep_sphinx_extensions/tests/pep_lint/test_post_url.py | 3 +-- 6 files changed, 6 insertions(+), 14 deletions(-) diff --git a/pep_sphinx_extensions/tests/pep_lint/test_date.py b/pep_sphinx_extensions/tests/pep_lint/test_date.py index a57c2fc9496..1f4127313a8 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_date.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_date.py @@ -1,8 +1,7 @@ import datetime as dt -import pytest - import pep_lint # NoQA: inserted into sys.modules in conftest.py +import pytest @pytest.mark.parametrize( diff --git a/pep_sphinx_extensions/tests/pep_lint/test_direct_links.py b/pep_sphinx_extensions/tests/pep_lint/test_direct_links.py index 87da05d102a..719cac3c6c4 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_direct_links.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_direct_links.py @@ -1,6 +1,5 @@ -import pytest - import pep_lint # NoQA: inserted into sys.modules in conftest.py +import pytest @pytest.mark.parametrize( diff --git a/pep_sphinx_extensions/tests/pep_lint/test_email.py b/pep_sphinx_extensions/tests/pep_lint/test_email.py index 4d6e4361865..4a9fcb3d006 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_email.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_email.py @@ -1,6 +1,5 @@ -import pytest - import pep_lint # NoQA: inserted into sys.modules in conftest.py +import pytest @pytest.mark.parametrize( diff --git a/pep_sphinx_extensions/tests/pep_lint/test_headers.py b/pep_sphinx_extensions/tests/pep_lint/test_headers.py index df77519d016..fd1be0e632d 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_headers.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_headers.py @@ -1,6 +1,5 @@ -import pytest - import pep_lint # NoQA: inserted into sys.modules in conftest.py +import pytest @pytest.mark.parametrize( @@ -401,5 +400,3 @@ def test_validate_python_version(line: str, expected_warnings: set): assert warnings == [], warnings assert found_warnings == expected_warnings - - diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_number.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_number.py index 57868a6e84b..a644d3d5362 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_pep_number.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_number.py @@ -1,6 +1,5 @@ -import pytest - import pep_lint # NoQA: inserted into sys.modules in conftest.py +import pytest @pytest.mark.parametrize( diff --git a/pep_sphinx_extensions/tests/pep_lint/test_post_url.py b/pep_sphinx_extensions/tests/pep_lint/test_post_url.py index 20cc89dd0d9..a99913bec66 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_post_url.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_post_url.py @@ -1,6 +1,5 @@ -import pytest - import pep_lint # NoQA: inserted into sys.modules in conftest.py +import pytest @pytest.mark.parametrize( From 5f21f8cc92d3ba334ee5cf4a5dfee0790ab9fc0d Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 20:52:21 +0100 Subject: [PATCH 60/83] Rework _output_error --- pep-lint.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pep-lint.py b/pep-lint.py index 9e93d107a51..778fe8a4cc1 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -175,12 +175,14 @@ def check_direct_links(line_num, line): def _output_error(filename, lines, errors): + relative_filename = filename.relative_to(PEP_ROOT) err_count = 0 for line_num, msg in errors: - print(f"{filename}:{line_num}: |", file=sys.stderr) - print(f"{filename}:{line_num}: {msg}", file=sys.stderr) - print(f"{filename}:{line_num}: from: {lines[line_num - 1]!r}", file=sys.stderr) - print(f"{filename}:{line_num}: |", file=sys.stderr) + line = lines[line_num - 1] + print(f"{relative_filename}:{line_num}: {msg}") + print(f" |", file=sys.stderr) + print(f"{line_num: >4} | '{line}'", file=sys.stderr) + print(f" |", file=sys.stderr) err_count += 1 return err_count From 9250d8e983c55ed228cab443948789266c4bdf3c Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 20:53:13 +0100 Subject: [PATCH 61/83] lowercase --- pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py index 1da9e99638d..f35f8401ee9 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py @@ -2,7 +2,7 @@ import pep_lint # NoQA: inserted into sys.modules in conftest.py -PEP_9002 = Path(__file__).parent.parent / "peps" / "PEP-9002.rst" +PEP_9002 = Path(__file__).parent.parent / "peps" / "pep-9002.rst" def test_with_fake_pep(): From 4b851c4c4b58d3fefb112e5c4fd7d1b3a3b8fe93 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 21:08:05 +0100 Subject: [PATCH 62/83] Format --- pep-lint.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pep-lint.py b/pep-lint.py index 778fe8a4cc1..381322bc581 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -55,16 +55,16 @@ SKIP_DIRECT_PEP_LINK_CHECK = frozenset({"0009", "0287", "0676", "0684", "8001"}) # any sequence of letters or '-', followed by a single ':' and a space or end of line -HEADER_PATTERN = re.compile(r"^([a-z\-]+):(?: |$)", re.ASCII|re.IGNORECASE) +HEADER_PATTERN = re.compile(r"^([a-z\-]+):(?: |$)", re.ASCII | re.IGNORECASE) # any sequence of unicode letters or legal special characters NAME_PATTERN = re.compile(r"(?:[^\W\d_]|[ ',\-.])+(?: |$)") # any sequence of ASCII letters, digits, or legal special characters -EMAIL_LOCAL_PART_PATTERN = re.compile(r"[\w!#$%&'*+\-/=?^{|}~.]+", re.ASCII) -DISCOURSE_THREAD_PATTERN = re.compile(r"([\w\-]+/)?\d+") -DISCOURSE_POST_PATTERN = re.compile(r"([\w\-]+/)?\d+(/\d+)?") -MAILMAN_2_PATTERN = re.compile(r"[\w\-]+/\d{4}-[a-z]+/\d+\.html", re.ASCII|re.IGNORECASE) -MAILMAN_3_THREAD_PATTERN = re.compile(r"[\w\-]+@python\.org/thread/[a-z0-9]+/?", re.ASCII|re.IGNORECASE) -MAILMAN_3_MESSAGE_PATTERN = re.compile(r"[\w\-]+@python\.org/message/[a-z0-9]+/?(#[a-z0-9]+)?", re.ASCII|re.IGNORECASE) +EMAIL_LOCAL_PART_PATTERN = re.compile(r"[\w!#$%&'*+\-/=?^{|}~.]+", re.ASCII | re.IGNORECASE) +DISCOURSE_THREAD_PATTERN = re.compile(r"([\w\-]+/)?\d+", re.ASCII | re.IGNORECASE) +DISCOURSE_POST_PATTERN = re.compile(r"([\w\-]+/)?\d+(/\d+)?", re.ASCII | re.IGNORECASE) +MAILMAN_2_PATTERN = re.compile(r"[\w\-]+/\d{4}-[a-z]+/\d+\.html", re.ASCII | re.IGNORECASE) +MAILMAN_3_THREAD_PATTERN = re.compile(r"[\w\-]+@python\.org/thread/[a-z0-9]+/?", re.ASCII | re.IGNORECASE) +MAILMAN_3_MESSAGE_PATTERN = re.compile(r"[\w\-]+@python\.org/message/[a-z0-9]+/?(#[a-z0-9]+)?", re.ASCII | re.IGNORECASE) def check(filenames=(), /): @@ -279,6 +279,7 @@ def _validate_discussions_to(line_num, line): return yield line_num, "Discussions-To must be a valid thread URL or mailing list" + def _validate_status(line_num, line): """'Status' must be a valid PEP status""" From 67ab1234adaf86dfad69b7bb99653db5b83e0861 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 21:15:11 +0100 Subject: [PATCH 63/83] Split out _validate_header --- pep-lint.py | 61 ++++++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/pep-lint.py b/pep-lint.py index 381322bc581..5a061b683f8 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -133,35 +133,38 @@ def check_headers(lines): if remainder[0] not in {" ", "\n"}: yield line_num, f"Headers must have a space after the colon: {header}" remainder = remainder.lstrip() - - if header == "Title": - yield from _validate_title(line_num, remainder) - elif header == "Author": - yield from _validate_author(line_num, remainder) - elif header == "Sponsor": - yield from _validate_sponsor(line_num, remainder) - elif header in {"BDFL-Delegate", "PEP-Delegate"}: - yield from _validate_delegate(line_num, remainder) - elif header == "Discussions-To": - yield from _validate_discussions_to(line_num, remainder) - elif header == "Status": - yield from _validate_status(line_num, remainder) - elif header == "Type": - yield from _validate_type(line_num, remainder) - elif header == "Topic": - yield from _validate_topic(line_num, remainder) - elif header == "Content-Type": - yield from _validate_content_type(line_num, remainder) - elif header in {"Requires", "Replaces", "Superseded-By"}: - yield from _validate_pep_references(line_num, remainder) - elif header == "Created": - yield from _validate_created(line_num, remainder) - elif header == "Python-Version": - yield from _validate_python_version(line_num, remainder) - elif header == "Post-History": - yield from _validate_post_history(line_num, remainder) - elif header == "Resolution": - yield from _validate_resolution(line_num, remainder) + yield from _validate_header(header, line_num, remainder) + + +def _validate_header(header, line_num, content): + if header == "Title": + yield from _validate_title(line_num, content) + elif header == "Author": + yield from _validate_author(line_num, content) + elif header == "Sponsor": + yield from _validate_sponsor(line_num, content) + elif header in {"BDFL-Delegate", "PEP-Delegate"}: + yield from _validate_delegate(line_num, content) + elif header == "Discussions-To": + yield from _validate_discussions_to(line_num, content) + elif header == "Status": + yield from _validate_status(line_num, content) + elif header == "Type": + yield from _validate_type(line_num, content) + elif header == "Topic": + yield from _validate_topic(line_num, content) + elif header == "Content-Type": + yield from _validate_content_type(line_num, content) + elif header in {"Requires", "Replaces", "Superseded-By"}: + yield from _validate_pep_references(line_num, content) + elif header == "Created": + yield from _validate_created(line_num, content) + elif header == "Python-Version": + yield from _validate_python_version(line_num, content) + elif header == "Post-History": + yield from _validate_post_history(line_num, content) + elif header == "Resolution": + yield from _validate_resolution(line_num, content) def check_direct_links(line_num, line): From efaff4ec7d0d9f67eb876d1d9edf7287caa5469e Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 21:26:22 +0100 Subject: [PATCH 64/83] Add types --- pep-lint.py | 73 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/pep-lint.py b/pep-lint.py index 5a061b683f8..4a43cdbce19 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -3,12 +3,25 @@ # This file is placed in the public domain or under the # CC0-1.0-Universal license, whichever is more permissive. +from __future__ import annotations + import datetime as dt import itertools import re import sys from pathlib import Path +TYPE_CHECKING = False +if TYPE_CHECKING: + import os + from collections.abc import Iterable, Iterator, KeysView, Sequence + from typing import TypeAlias + + # (line number, warning message) + Message: TypeAlias = tuple[int, str] + MessageIterator: TypeAlias = Iterator[Message] + + # get the directory with the PEP sources PEP_ROOT = Path(__file__).resolve().parent @@ -67,7 +80,7 @@ MAILMAN_3_MESSAGE_PATTERN = re.compile(r"[\w\-]+@python\.org/message/[a-z0-9]+/?(#[a-z0-9]+)?", re.ASCII | re.IGNORECASE) -def check(filenames=(), /): +def check(filenames: Sequence[str | os.PathLike[str]] = (), /) -> int: """The main entry-point.""" if filenames: filenames = map(Path, filenames) @@ -80,7 +93,7 @@ def check(filenames=(), /): return 0 -def check_file(filename, /): +def check_file(filename: Path, /) -> int: filename = filename.resolve() try: content = filename.read_text(encoding="utf-8") @@ -91,7 +104,7 @@ def check_file(filename, /): return _output_error(filename, lines, check_pep(filename, lines)) -def check_pep(filename, lines): +def check_pep(filename: Path, lines: Sequence[str], /) -> MessageIterator: yield from check_headers(lines) for line_num, line in enumerate(lines, start=1): if filename.stem.removeprefix("pep-") in SKIP_DIRECT_PEP_LINK_CHECK: @@ -99,7 +112,7 @@ def check_pep(filename, lines): yield from check_direct_links(line_num, line.lstrip()) -def check_headers(lines): +def check_headers(lines: Sequence[str], /) -> MessageIterator: yield from _validate_pep_number(next(iter(lines), "")) found_headers = {} @@ -136,7 +149,7 @@ def check_headers(lines): yield from _validate_header(header, line_num, remainder) -def _validate_header(header, line_num, content): +def _validate_header(header: str, line_num: int, content: str) -> MessageIterator: if header == "Title": yield from _validate_title(line_num, content) elif header == "Author": @@ -167,17 +180,17 @@ def _validate_header(header, line_num, content): yield from _validate_resolution(line_num, content) -def check_direct_links(line_num, line): +def check_direct_links(line_num: int, line: str) -> MessageIterator: """Check that PEPs and RFCs aren't linked directly""" - line = line.lstrip().lower() + line = line.lower() if "dev/peps/pep-" in line or "peps.python.org/pep-" in line: yield line_num, "Use the :pep:`NNN` role to refer to PEPs" if "rfc-editor.org/rfc/" in line or "ietf.org/doc/html/rfc" in line: yield line_num, "Use the :rfc:`NNN` role to refer to RFCs" -def _output_error(filename, lines, errors): +def _output_error(filename: Path, lines: Sequence[str], errors: Iterable[Message]) -> int: relative_filename = filename.relative_to(PEP_ROOT) err_count = 0 for line_num, msg in errors: @@ -195,7 +208,7 @@ def _output_error(filename, lines, errors): ########################### -def _validate_required_headers(found_headers): +def _validate_required_headers(found_headers: KeysView[str]) -> MessageIterator: """PEPs must have all required headers, in the PEP 12 order""" if missing := REQUIRED_HEADERS.difference(found_headers): @@ -208,7 +221,7 @@ def _validate_required_headers(found_headers): yield 1, "Headers must be in PEP 12 order. Correct order: " + order_str -def _validate_pep_number(line): +def _validate_pep_number(line: str) -> MessageIterator: """'PEP' header must be a number 1-9999""" if not line.startswith("PEP: "): @@ -219,7 +232,7 @@ def _validate_pep_number(line): yield from _pep_num(1, pep_number, "'PEP:' header") -def _validate_title(line_num, line): +def _validate_title(line_num: int, line: str) -> MessageIterator: """'Title' must be 1-79 characters""" if len(line) == 0: @@ -228,7 +241,7 @@ def _validate_title(line_num, line): yield line_num, "PEP title must be less than 80 characters" -def _validate_author(line_num, body): +def _validate_author(line_num: int, body: str) -> MessageIterator: """'Author' must be list of 'Name , …'""" lines = body.split("\n") @@ -247,13 +260,13 @@ def _validate_author(line_num, body): yield from _email(line_num + offset, part, "Author") -def _validate_sponsor(line_num, line): +def _validate_sponsor(line_num: int, line: str) -> MessageIterator: """'Sponsor' must have format 'Name '""" yield from _email(line_num, line, "Sponsor") -def _validate_delegate(line_num, line): +def _validate_delegate(line_num: int, line: str) -> MessageIterator: """'Delegate' must have format 'Name '""" if line == "": @@ -268,7 +281,7 @@ def _validate_delegate(line_num, line): yield from _email(line_num, line, "Delegate") -def _validate_discussions_to(line_num, line): +def _validate_discussions_to(line_num: int, line: str) -> MessageIterator: """'Discussions-To' must be a thread URL""" yield from _thread(line_num, line, "Discussions-To", discussions_to=True) @@ -283,21 +296,21 @@ def _validate_discussions_to(line_num, line): yield line_num, "Discussions-To must be a valid thread URL or mailing list" -def _validate_status(line_num, line): +def _validate_status(line_num: int, line: str) -> MessageIterator: """'Status' must be a valid PEP status""" if line not in ALL_STATUSES: yield line_num, "Status must be a valid PEP status" -def _validate_type(line_num, line): +def _validate_type(line_num: int, line: str) -> MessageIterator: """'Type' must be a valid PEP type""" if line not in {"Standards Track", "Informational", "Process"}: yield line_num, "Type must be a valid PEP type" -def _validate_topic(line_num, line): +def _validate_topic(line_num: int, line: str) -> MessageIterator: """'Topic' must be for a valid sub-index""" topics = line.split(", ") @@ -314,14 +327,14 @@ def _validate_topic(line_num, line): yield line_num, "Topic must be sorted lexicographically" -def _validate_content_type(line_num, line): +def _validate_content_type(line_num: int, line: str) -> MessageIterator: """'Content-Type' must be 'text/x-rst'""" if line != "text/x-rst": yield line_num, "Content-Type must be 'text/x-rst'" -def _validate_pep_references(line_num, line): +def _validate_pep_references(line_num: int, line: str) -> MessageIterator: """`Requires`/`Replaces`/`Superseded-By` must be 'NNN' PEP IDs""" line = line.removesuffix(",").rstrip() @@ -334,13 +347,13 @@ def _validate_pep_references(line_num, line): yield from _pep_num(line_num, reference, "PEP reference") -def _validate_created(line_num, line): +def _validate_created(line_num: int, line: str) -> MessageIterator: """'Created' must be a 'DD-mmm-YYYY' date""" yield from _date(line_num, line, "Created") -def _validate_python_version(line_num, line): +def _validate_python_version(line_num: int, line: str) -> MessageIterator: """'Python-Version' must be an ``X.Y[.Z]`` version""" versions = line.split(", ") @@ -372,7 +385,7 @@ def _validate_python_version(line_num, line): yield line_num, f"Python-Version micro part must be numeric: {version}" -def _validate_post_history(line_num, body): +def _validate_post_history(line_num: int, body: str) -> MessageIterator: """'Post-History' must be '`DD-mmm-YYYY `__, …'""" if body == "": @@ -388,7 +401,7 @@ def _validate_post_history(line_num, body): yield from _thread(offset, post_url, "Post-History") -def _validate_resolution(line_num, line): +def _validate_resolution(line_num: int, line: str) -> MessageIterator: """'Resolution' must be a direct thread/message URL""" yield from _thread(line_num, line, "Resolution", allow_message=True) @@ -398,7 +411,7 @@ def _validate_resolution(line_num, line): # Validation Helpers # ######################## -def _pep_num(line_num, pep_number, prefix): +def _pep_num(line_num: int, pep_number: str, prefix: str) -> MessageIterator: if pep_number == "": yield line_num, f"{prefix} must not be blank: {pep_number!r}" return @@ -410,12 +423,12 @@ def _pep_num(line_num, pep_number, prefix): yield line_num, f"{prefix} must be between 0 and 9999: {pep_number!r}" -def _is_digits(string): +def _is_digits(string: str) -> bool: """Match a string of ASCII digits ([0-9]+).""" return string.isascii() and string.isdigit() -def _email(line_num, author_email, prefix): +def _email(line_num: int, author_email: str, prefix: str) -> MessageIterator: author_email = author_email.strip() if author_email.count("<") > 1: @@ -455,7 +468,7 @@ def _email(line_num, author_email, prefix): yield line_num, f"{prefix} entries must contain a valid email address: {author_email!r}" -def _invalid_domain(domain_part): +def _invalid_domain(domain_part: str) -> bool: *labels, root = domain_part.split(".") for label in labels: if not label.replace("-", "").isalnum(): @@ -463,7 +476,7 @@ def _invalid_domain(domain_part): return not root.isalnum() or not root.isascii() -def _thread(line_num, url, prefix, *, allow_message=False, discussions_to=False): +def _thread(line_num: int, url: str, prefix: str, *, allow_message: bool = False, discussions_to: bool = False) -> MessageIterator: if allow_message and discussions_to: msg = "allow_message and discussions_to cannot both be True" raise ValueError(msg) @@ -537,7 +550,7 @@ def _thread(line_num, url, prefix, *, allow_message=False, discussions_to=False) yield line_num, msg -def _date(line_num, date_str, prefix): +def _date(line_num: int, date_str: str, prefix: str) -> MessageIterator: try: parsed_date = dt.datetime.strptime(date_str, "%d-%b-%Y") except ValueError: From fb8db10d712be2a06422b0f2d2dd096c3b4b151a Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 21:26:55 +0100 Subject: [PATCH 65/83] str --- pep-lint.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pep-lint.py b/pep-lint.py index 4a43cdbce19..51e7c3f28ce 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -13,7 +13,6 @@ TYPE_CHECKING = False if TYPE_CHECKING: - import os from collections.abc import Iterable, Iterator, KeysView, Sequence from typing import TypeAlias @@ -80,7 +79,7 @@ MAILMAN_3_MESSAGE_PATTERN = re.compile(r"[\w\-]+@python\.org/message/[a-z0-9]+/?(#[a-z0-9]+)?", re.ASCII | re.IGNORECASE) -def check(filenames: Sequence[str | os.PathLike[str]] = (), /) -> int: +def check(filenames: Sequence[str] = (), /) -> int: """The main entry-point.""" if filenames: filenames = map(Path, filenames) From f26c170303bd48264dabedc4067452c21d117b20 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 20 Aug 2023 21:33:21 +0100 Subject: [PATCH 66/83] test --- pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py index f35f8401ee9..51cc782a382 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py @@ -43,6 +43,5 @@ def test_with_fake_pep(): (18, "Post-History must be a valid thread URL"), (19, "Post-History must be a 'DD-mmm-YYYY' date: '3-Mar-2001'"), (19, "Post-History must be a valid thread URL"), - (20, "Resolution must be a valid thread URL"), (23, "Use the :pep:`NNN` role to refer to PEPs"), ] From 4e5eafb6c8b74f0e8c9ec43c2e3ce31345889cf5 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Wed, 23 Aug 2023 15:32:28 +0100 Subject: [PATCH 67/83] Disable pre-commit hook --- .pre-commit-config.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e3ce12afeac..c41c5f451c8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -101,14 +101,14 @@ repos: # Local checks for PEP headers and more - repo: local hooks: - # Hook to run 'pep-lint.py' - - id: "pep-lint" - name: "Check PEP metadata" - entry: "python pep-lint.py" - language: "system" - files: '^pep-\d{4}.(txt|rst)$' - types_or: ["rst", "plain-text"] - require_serial: true +# # Hook to run 'pep-lint.py' +# - id: "pep-lint" +# name: "Check PEP metadata" +# entry: "python pep-lint.py" +# language: "system" +# files: '^pep-\d{4}.(txt|rst)$' +# types_or: ["rst", "plain-text"] +# require_serial: true - id: check-no-tabs name: "Check tabs not used in PEPs" From 2456a480b67bb01e1635d4f914f324597302a6c3 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Wed, 23 Aug 2023 19:42:00 +0100 Subject: [PATCH 68/83] Implement help & detailed --- pep-lint.py | 48 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/pep-lint.py b/pep-lint.py index 51e7c3f28ce..794dfc0bae9 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -3,6 +3,16 @@ # This file is placed in the public domain or under the # CC0-1.0-Universal license, whichever is more permissive. +"""pep-lint: Check PEPs for common mistakes. + +Usage: pep-lint [-d | --detailed] + +Only the PEPs specified are checked. +If none are specified, all PEPs are checked. + +Use "--detailed" to show the contents of lines where errors were found. +""" + from __future__ import annotations import datetime as dt @@ -78,6 +88,9 @@ MAILMAN_3_THREAD_PATTERN = re.compile(r"[\w\-]+@python\.org/thread/[a-z0-9]+/?", re.ASCII | re.IGNORECASE) MAILMAN_3_MESSAGE_PATTERN = re.compile(r"[\w\-]+@python\.org/message/[a-z0-9]+/?(#[a-z0-9]+)?", re.ASCII | re.IGNORECASE) +# Controlled by the "--detailed" flag +DETAILED_ERRORS = False + def check(filenames: Sequence[str] = (), /) -> int: """The main entry-point.""" @@ -97,7 +110,7 @@ def check_file(filename: Path, /) -> int: try: content = filename.read_text(encoding="utf-8") except FileNotFoundError: - return _output_error(filename, [''], [(1, "Could not read PEP!")]) + return _output_error(filename, [""], [(0, "Could not read PEP!")]) else: lines = content.splitlines() return _output_error(filename, lines, check_pep(filename, lines)) @@ -193,12 +206,17 @@ def _output_error(filename: Path, lines: Sequence[str], errors: Iterable[Message relative_filename = filename.relative_to(PEP_ROOT) err_count = 0 for line_num, msg in errors: - line = lines[line_num - 1] - print(f"{relative_filename}:{line_num}: {msg}") - print(f" |", file=sys.stderr) - print(f"{line_num: >4} | '{line}'", file=sys.stderr) - print(f" |", file=sys.stderr) err_count += 1 + + print(f"{relative_filename}:{line_num}: {msg}") + if not DETAILED_ERRORS: + continue + + line = lines[line_num - 1] + print(" |") + print(f"{line_num: >4} | '{line}'") + print(" |") + return err_count @@ -556,7 +574,7 @@ def _date(line_num: int, date_str: str, prefix: str) -> MessageIterator: yield line_num, f"{prefix} must be a 'DD-mmm-YYYY' date: {date_str!r}" return else: - if date_str[1] == '-': # Date must be zero-padded + if date_str[1] == "-": # Date must be zero-padded yield line_num, f"{prefix} must be a 'DD-mmm-YYYY' date: {date_str!r}" return @@ -567,5 +585,17 @@ def _date(line_num: int, date_str: str, prefix: str) -> MessageIterator: if __name__ == "__main__": - # TODO -h / --help / -? - raise SystemExit(check(sys.argv[1:])) + if {"-h", "--help", "-?"}.intersection(sys.argv[1:]): + print(__doc__, file=sys.stderr) + raise SystemExit(0) + + files = {} + for arg in sys.argv[1:]: + if not arg.startswith("-"): + files[arg] = None + elif arg in {"-d", "--detailed"}: + DETAILED_ERRORS = True + else: + print(f"Unknown option: {arg!r}", file=sys.stderr) + raise SystemExit(1) + raise SystemExit(check(files)) From 75eb099529265b9c6939b50d6bb6cbbfdf972c5e Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Wed, 23 Aug 2023 20:15:12 +0100 Subject: [PATCH 69/83] Fix regression --- pep-lint.py | 2 +- pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pep-lint.py b/pep-lint.py index 794dfc0bae9..183d6d2b518 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -158,7 +158,7 @@ def check_headers(lines: Sequence[str], /) -> MessageIterator: if remainder[0] not in {" ", "\n"}: yield line_num, f"Headers must have a space after the colon: {header}" remainder = remainder.lstrip() - yield from _validate_header(header, line_num, remainder) + yield from _validate_header(header, line_num, remainder) def _validate_header(header: str, line_num: int, content: str) -> MessageIterator: diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py index 51cc782a382..b56e4925054 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py @@ -43,5 +43,6 @@ def test_with_fake_pep(): (18, "Post-History must be a valid thread URL"), (19, "Post-History must be a 'DD-mmm-YYYY' date: '3-Mar-2001'"), (19, "Post-History must be a valid thread URL"), + (20, 'Resolution must be a valid thread URL'), (23, "Use the :pep:`NNN` role to refer to PEPs"), ] From 29b770a068534d1bdf124c7a393718bcb0c5007b Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Wed, 23 Aug 2023 20:15:43 +0100 Subject: [PATCH 70/83] ENSURE REVERSION: Introduce errors --- pep-0011.txt | 2 +- pep-0376.txt | 2 +- pep-0499.txt | 2 +- pep-0623.rst | 2 +- pep-0703.rst | 1 + pep-0720.rst | 1 + pep-3152.txt | 1 + pep-lint.py | 2 +- 8 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pep-0011.txt b/pep-0011.txt index 63131c3a277..ffbd2e76659 100644 --- a/pep-0011.txt +++ b/pep-0011.txt @@ -8,7 +8,7 @@ Content-Type: text/x-rst Created: 07-Jul-2002 Post-History: `18-Aug-2007 `__, `14-May-2014 `__, - `20-Feb-2015 `__, + `20-Feb-2015 `__, `10-Mar-2022 `__, diff --git a/pep-0376.txt b/pep-0376.txt index 60119c69e74..5cc2bfc8f3c 100644 --- a/pep-0376.txt +++ b/pep-0376.txt @@ -7,7 +7,7 @@ Topic: Packaging Content-Type: text/x-rst Created: 22-Feb-2009 Python-Version: 2.7, 3.2 -Post-History: `22-Jun-2009 `__ +Post-History: `22-Jun-2009 `__ .. canonical-pypa-spec:: :ref:`packaging:core-metadata` diff --git a/pep-0499.txt b/pep-0499.txt index e48a24767a0..9cbf6f8c3f0 100644 --- a/pep-0499.txt +++ b/pep-0499.txt @@ -1,5 +1,5 @@ PEP: 499 -Title: ``python -m foo`` should also bind ``'foo'`` in ``sys.modules`` +Title: ``python -m foo`` should bind ``sys.modules['foo']`` in addition to ``sys.modules['__main__']`` Version: $Revision$ Last-Modified: $Date$ Author: Cameron Simpson , Chris Angelico , Joseph Jevnik diff --git a/pep-0623.rst b/pep-0623.rst index 0d182add1c0..ce0227d5527 100644 --- a/pep-0623.rst +++ b/pep-0623.rst @@ -8,7 +8,7 @@ Type: Standards Track Content-Type: text/x-rst Created: 25-Jun-2020 Python-Version: 3.10 -Resolution: https://mail.python.org/archives/list/python-dev@python.org/thread/VQKDIZLZ6HF2MLTNCUFURK2IFTXVQEYA/ +Resolution: https://mail.python.org/archives/list/python-dev@python.org/thread/VQKDIZLZ6HF2MLTNCUFURK2IFTXVQEYA/#VQKDIZLZ6HF2MLTNCUFURK2IFTXVQEYA Abstract diff --git a/pep-0703.rst b/pep-0703.rst index 92080992bfa..be2a8e99415 100644 --- a/pep-0703.rst +++ b/pep-0703.rst @@ -10,6 +10,7 @@ Created: 09-Jan-2023 Python-Version: 3.13 Post-History: `09-Jan-2023 `__, `04-May-2023 `__ +Resolution: Abstract diff --git a/pep-0720.rst b/pep-0720.rst index 14e0708c7b9..87c00ae7c97 100644 --- a/pep-0720.rst +++ b/pep-0720.rst @@ -7,6 +7,7 @@ Type: Informational Content-Type: text/x-rst Created: 01-Jul-2023 Python-Version: 3.12 +Resolution: Abstract diff --git a/pep-3152.txt b/pep-3152.txt index 46bcab5e362..c6f5be77b39 100644 --- a/pep-3152.txt +++ b/pep-3152.txt @@ -9,6 +9,7 @@ Content-Type: text/x-rst Created: 13-Feb-2009 Python-Version: 3.3 Post-History: +Resolution: Abstract diff --git a/pep-lint.py b/pep-lint.py index 183d6d2b518..fc8ebb176b8 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -74,7 +74,7 @@ }) # PEPs that are allowed to link directly to PEPs -SKIP_DIRECT_PEP_LINK_CHECK = frozenset({"0009", "0287", "0676", "0684", "8001"}) +SKIP_DIRECT_PEP_LINK_CHECK = frozenset() # any sequence of letters or '-', followed by a single ':' and a space or end of line HEADER_PATTERN = re.compile(r"^([a-z\-]+):(?: |$)", re.ASCII | re.IGNORECASE) From 2d7274a74eb922c65d055ae0a7d42022a6520728 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Wed, 23 Aug 2023 20:19:05 +0100 Subject: [PATCH 71/83] Quotation marks --- pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py index b56e4925054..f35f8401ee9 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py @@ -43,6 +43,6 @@ def test_with_fake_pep(): (18, "Post-History must be a valid thread URL"), (19, "Post-History must be a 'DD-mmm-YYYY' date: '3-Mar-2001'"), (19, "Post-History must be a valid thread URL"), - (20, 'Resolution must be a valid thread URL'), + (20, "Resolution must be a valid thread URL"), (23, "Use the :pep:`NNN` role to refer to PEPs"), ] From 5af19b08c1c569afeeafd7a0324e6e6a319a2ab6 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Wed, 23 Aug 2023 20:20:57 +0100 Subject: [PATCH 72/83] Use detailed mode --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5d3613a98ec..3c361f37728 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -48,4 +48,4 @@ jobs: python-version: "3" - name: Run pep-lint - run: python pep-lint.py + run: python pep-lint.py --detailed From ceed08dc4d374dd6a80f5eb72745fbb66be6fa89 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Wed, 23 Aug 2023 20:24:12 +0100 Subject: [PATCH 73/83] Revert "ENSURE REVERSION: Introduce errors" This reverts commit 29b770a068534d1bdf124c7a393718bcb0c5007b. --- pep-0011.txt | 2 +- pep-0376.txt | 2 +- pep-0499.txt | 2 +- pep-0623.rst | 2 +- pep-0703.rst | 1 - pep-0720.rst | 1 - pep-3152.txt | 1 - pep-lint.py | 2 +- 8 files changed, 5 insertions(+), 8 deletions(-) diff --git a/pep-0011.txt b/pep-0011.txt index ffbd2e76659..63131c3a277 100644 --- a/pep-0011.txt +++ b/pep-0011.txt @@ -8,7 +8,7 @@ Content-Type: text/x-rst Created: 07-Jul-2002 Post-History: `18-Aug-2007 `__, `14-May-2014 `__, - `20-Feb-2015 `__, + `20-Feb-2015 `__, `10-Mar-2022 `__, diff --git a/pep-0376.txt b/pep-0376.txt index 5cc2bfc8f3c..60119c69e74 100644 --- a/pep-0376.txt +++ b/pep-0376.txt @@ -7,7 +7,7 @@ Topic: Packaging Content-Type: text/x-rst Created: 22-Feb-2009 Python-Version: 2.7, 3.2 -Post-History: `22-Jun-2009 `__ +Post-History: `22-Jun-2009 `__ .. canonical-pypa-spec:: :ref:`packaging:core-metadata` diff --git a/pep-0499.txt b/pep-0499.txt index 9cbf6f8c3f0..e48a24767a0 100644 --- a/pep-0499.txt +++ b/pep-0499.txt @@ -1,5 +1,5 @@ PEP: 499 -Title: ``python -m foo`` should bind ``sys.modules['foo']`` in addition to ``sys.modules['__main__']`` +Title: ``python -m foo`` should also bind ``'foo'`` in ``sys.modules`` Version: $Revision$ Last-Modified: $Date$ Author: Cameron Simpson , Chris Angelico , Joseph Jevnik diff --git a/pep-0623.rst b/pep-0623.rst index ce0227d5527..0d182add1c0 100644 --- a/pep-0623.rst +++ b/pep-0623.rst @@ -8,7 +8,7 @@ Type: Standards Track Content-Type: text/x-rst Created: 25-Jun-2020 Python-Version: 3.10 -Resolution: https://mail.python.org/archives/list/python-dev@python.org/thread/VQKDIZLZ6HF2MLTNCUFURK2IFTXVQEYA/#VQKDIZLZ6HF2MLTNCUFURK2IFTXVQEYA +Resolution: https://mail.python.org/archives/list/python-dev@python.org/thread/VQKDIZLZ6HF2MLTNCUFURK2IFTXVQEYA/ Abstract diff --git a/pep-0703.rst b/pep-0703.rst index be2a8e99415..92080992bfa 100644 --- a/pep-0703.rst +++ b/pep-0703.rst @@ -10,7 +10,6 @@ Created: 09-Jan-2023 Python-Version: 3.13 Post-History: `09-Jan-2023 `__, `04-May-2023 `__ -Resolution: Abstract diff --git a/pep-0720.rst b/pep-0720.rst index 87c00ae7c97..14e0708c7b9 100644 --- a/pep-0720.rst +++ b/pep-0720.rst @@ -7,7 +7,6 @@ Type: Informational Content-Type: text/x-rst Created: 01-Jul-2023 Python-Version: 3.12 -Resolution: Abstract diff --git a/pep-3152.txt b/pep-3152.txt index c6f5be77b39..46bcab5e362 100644 --- a/pep-3152.txt +++ b/pep-3152.txt @@ -9,7 +9,6 @@ Content-Type: text/x-rst Created: 13-Feb-2009 Python-Version: 3.3 Post-History: -Resolution: Abstract diff --git a/pep-lint.py b/pep-lint.py index fc8ebb176b8..183d6d2b518 100755 --- a/pep-lint.py +++ b/pep-lint.py @@ -74,7 +74,7 @@ }) # PEPs that are allowed to link directly to PEPs -SKIP_DIRECT_PEP_LINK_CHECK = frozenset() +SKIP_DIRECT_PEP_LINK_CHECK = frozenset({"0009", "0287", "0676", "0684", "8001"}) # any sequence of letters or '-', followed by a single ':' and a space or end of line HEADER_PATTERN = re.compile(r"^([a-z\-]+):(?: |$)", re.ASCII | re.IGNORECASE) From 144370f3e8e15c47dd05f0d0b8d63c4541c20c2e Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 24 Aug 2023 00:02:36 +0100 Subject: [PATCH 74/83] Rename pep-lint to check-pep --- .github/CODEOWNERS | 2 +- .github/workflows/lint.yml | 8 ++-- .pre-commit-config.yaml | 6 +-- pep-lint.py => check-pep.py | 6 +-- pep_sphinx_extensions/tests/conftest.py | 10 ++--- .../tests/pep_lint/test_date.py | 12 +++--- .../tests/pep_lint/test_direct_links.py | 6 +-- .../tests/pep_lint/test_email.py | 14 +++---- .../tests/pep_lint/test_headers.py | 38 +++++++++---------- .../tests/pep_lint/test_pep_lint.py | 4 +- .../tests/pep_lint/test_pep_number.py | 8 ++-- .../tests/pep_lint/test_post_url.py | 28 +++++++------- pytest.ini | 2 +- 13 files changed, 72 insertions(+), 72 deletions(-) rename pep-lint.py => check-pep.py (99%) mode change 100755 => 100644 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1a039c96838..112a5ca0ab3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -21,7 +21,7 @@ contents.rst @AA-Turner .codespell/ @CAM-Gerlach @hugovk .codespellrc @CAM-Gerlach @hugovk .pre-commit-config.yaml @CAM-Gerlach @hugovk -pep-lint.py @AA-Turner @CAM-Gerlach @hugovk +check-pep.py @AA-Turner @CAM-Gerlach @hugovk # Git infrastructure .gitattributes @CAM-Gerlach diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3c361f37728..784a9f0a651 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -36,8 +36,8 @@ jobs: with: extra_args: --all-files --hook-stage manual codespell || true - pep-lint: - name: Run pep-lint + check-pep: + name: Run check-pep runs-on: ubuntu-latest steps: @@ -47,5 +47,5 @@ jobs: with: python-version: "3" - - name: Run pep-lint - run: python pep-lint.py --detailed + - name: Run check-pep + run: python check-pep.py --detailed diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c41c5f451c8..9f4a1deb295 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -101,10 +101,10 @@ repos: # Local checks for PEP headers and more - repo: local hooks: -# # Hook to run 'pep-lint.py' -# - id: "pep-lint" +# # Hook to run 'check-pep.py' +# - id: "check-pep" # name: "Check PEP metadata" -# entry: "python pep-lint.py" +# entry: "python check-pep.py" # language: "system" # files: '^pep-\d{4}.(txt|rst)$' # types_or: ["rst", "plain-text"] diff --git a/pep-lint.py b/check-pep.py old mode 100755 new mode 100644 similarity index 99% rename from pep-lint.py rename to check-pep.py index 183d6d2b518..b0021280546 --- a/pep-lint.py +++ b/check-pep.py @@ -3,9 +3,9 @@ # This file is placed in the public domain or under the # CC0-1.0-Universal license, whichever is more permissive. -"""pep-lint: Check PEPs for common mistakes. +"""check-pep: Check PEPs for common mistakes. -Usage: pep-lint [-d | --detailed] +Usage: check-pep [-d | --detailed] Only the PEPs specified are checked. If none are specified, all PEPs are checked. @@ -100,7 +100,7 @@ def check(filenames: Sequence[str] = (), /) -> int: filenames = itertools.chain(PEP_ROOT.glob("pep-????.txt"), PEP_ROOT.glob("pep-????.rst")) if (count := sum(map(check_file, filenames))) > 0: s = "s" * (count != 1) - print(f"pep-lint failed: {count} error{s}", file=sys.stderr) + print(f"check-pep failed: {count} error{s}", file=sys.stderr) return 1 return 0 diff --git a/pep_sphinx_extensions/tests/conftest.py b/pep_sphinx_extensions/tests/conftest.py index 1e82717da47..d555193e7fc 100644 --- a/pep_sphinx_extensions/tests/conftest.py +++ b/pep_sphinx_extensions/tests/conftest.py @@ -2,8 +2,8 @@ import sys from pathlib import Path -# Import "pep-lint.py" as "pep_lint" -PEP_LINT_PATH = Path(__file__).resolve().parents[2] / "pep-lint.py" -spec = importlib.util.spec_from_file_location("pep_lint", PEP_LINT_PATH) -sys.modules["pep_lint"] = pep_lint = importlib.util.module_from_spec(spec) -spec.loader.exec_module(pep_lint) +# Import "check-pep.py" as "check_pep" +CHECK_PEP_PATH = Path(__file__).resolve().parents[2] / "check-pep.py" +spec = importlib.util.spec_from_file_location("check_pep", CHECK_PEP_PATH) +sys.modules["check_pep"] = check_pep = importlib.util.module_from_spec(spec) +spec.loader.exec_module(check_pep) diff --git a/pep_sphinx_extensions/tests/pep_lint/test_date.py b/pep_sphinx_extensions/tests/pep_lint/test_date.py index 1f4127313a8..7f1960c7fcf 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_date.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_date.py @@ -1,6 +1,6 @@ import datetime as dt -import pep_lint # NoQA: inserted into sys.modules in conftest.py +import check_pep # NoQA: inserted into sys.modules in conftest.py import pytest @@ -18,7 +18,7 @@ ], ) def test_validate_created(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_created(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_created(1, line)] assert warnings == [], warnings @@ -36,7 +36,7 @@ def test_validate_created(line: str): ], ) def test_date_checker_valid(date_str: str): - warnings = [warning for (_, warning) in pep_lint._date(1, date_str, "")] + warnings = [warning for (_, warning) in check_pep._date(1, date_str, "")] assert warnings == [], warnings @@ -67,7 +67,7 @@ def test_date_checker_valid(date_str: str): ], ) def test_date_checker_malformed(date_str: str): - warnings = [warning for (_, warning) in pep_lint._date(1, date_str, "")] + warnings = [warning for (_, warning) in check_pep._date(1, date_str, "")] expected = f" must be a 'DD-mmm-YYYY' date: {date_str!r}" assert warnings == [expected], warnings @@ -83,7 +83,7 @@ def test_date_checker_malformed(date_str: str): ], ) def test_date_checker_too_early(date_str: str): - warnings = [warning for (_, warning) in pep_lint._date(1, date_str, "")] + warnings = [warning for (_, warning) in check_pep._date(1, date_str, "")] expected = f" must not be before Python was invented: {date_str!r}" assert warnings == [expected], warnings @@ -100,6 +100,6 @@ def test_date_checker_too_early(date_str: str): ], ) def test_date_checker_too_late(date_str: str): - warnings = [warning for (_, warning) in pep_lint._date(1, date_str, "")] + warnings = [warning for (_, warning) in check_pep._date(1, date_str, "")] expected = f" must not be in the future: {date_str!r}" assert warnings == [expected], warnings diff --git a/pep_sphinx_extensions/tests/pep_lint/test_direct_links.py b/pep_sphinx_extensions/tests/pep_lint/test_direct_links.py index 719cac3c6c4..f6e8657b95a 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_direct_links.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_direct_links.py @@ -1,4 +1,4 @@ -import pep_lint # NoQA: inserted into sys.modules in conftest.py +import check_pep # NoQA: inserted into sys.modules in conftest.py import pytest @@ -12,7 +12,7 @@ ], ) def test_check_direct_links_pep(line: str): - warnings = [warning for (_, warning) in pep_lint.check_direct_links(1, line)] + warnings = [warning for (_, warning) in check_pep.check_direct_links(1, line)] assert warnings == ["Use the :pep:`NNN` role to refer to PEPs"], warnings @@ -26,5 +26,5 @@ def test_check_direct_links_pep(line: str): ], ) def test_check_direct_links_rfc(line: str): - warnings = [warning for (_, warning) in pep_lint.check_direct_links(1, line)] + warnings = [warning for (_, warning) in check_pep.check_direct_links(1, line)] assert warnings == ["Use the :rfc:`NNN` role to refer to RFCs"], warnings diff --git a/pep_sphinx_extensions/tests/pep_lint/test_email.py b/pep_sphinx_extensions/tests/pep_lint/test_email.py index 4a9fcb3d006..163298ebd0a 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_email.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_email.py @@ -1,4 +1,4 @@ -import pep_lint # NoQA: inserted into sys.modules in conftest.py +import check_pep # NoQA: inserted into sys.modules in conftest.py import pytest @@ -18,7 +18,7 @@ ids=repr, # the default calls str and renders newlines. ) def test_validate_author(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_author(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_author(1, line)] assert warnings == [], warnings @@ -33,7 +33,7 @@ def test_validate_author(line: str): ids=repr, # the default calls str and renders newlines. ) def test_validate_author_over__indented(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_author(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_author(1, line)] assert {*warnings} == {"Author line must not be over-indented"}, warnings @@ -47,7 +47,7 @@ def test_validate_author_over__indented(line: str): ids=repr, # the default calls str and renders newlines. ) def test_validate_author_continuation(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_author(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_author(1, line)] assert {*warnings} == {"Author continuation lines must end with a comma"}, warnings @@ -61,7 +61,7 @@ def test_validate_author_continuation(line: str): ], ) def test_validate_sponsor(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_sponsor(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_sponsor(1, line)] assert warnings == [], warnings @@ -76,7 +76,7 @@ def test_validate_sponsor(line: str): ], ) def test_validate_delegate(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_delegate(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_delegate(1, line)] assert warnings == [], warnings @@ -191,7 +191,7 @@ def test_validate_delegate(line: str): ids=str, ) def test_email_checker(email: str, expected_warnings: set): - warnings = [warning for (_, warning) in pep_lint._email(1, email, "")] + warnings = [warning for (_, warning) in check_pep._email(1, email, "")] found_warnings = set() email = email.strip() diff --git a/pep_sphinx_extensions/tests/pep_lint/test_headers.py b/pep_sphinx_extensions/tests/pep_lint/test_headers.py index fd1be0e632d..dbdfdaf19c8 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_headers.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_headers.py @@ -1,4 +1,4 @@ -import pep_lint # NoQA: inserted into sys.modules in conftest.py +import check_pep # NoQA: inserted into sys.modules in conftest.py import pytest @@ -23,7 +23,7 @@ ], ) def test_header_pattern(test_input, expected): - assert pep_lint.HEADER_PATTERN.match(test_input)[1] == expected + assert check_pep.HEADER_PATTERN.match(test_input)[1] == expected @pytest.mark.parametrize( @@ -45,7 +45,7 @@ def test_header_pattern(test_input, expected): ], ) def test_header_pattern_no_match(test_input): - assert pep_lint.HEADER_PATTERN.match(test_input) is None + assert check_pep.HEADER_PATTERN.match(test_input) is None def test_validate_required_headers(): @@ -53,7 +53,7 @@ def test_validate_required_headers(): ("PEP", "Title", "Author", "Status", "Type", "Created") ) warnings = [ - warning for (_, warning) in pep_lint._validate_required_headers(found_headers) + warning for (_, warning) in check_pep._validate_required_headers(found_headers) ] assert warnings == [], warnings @@ -61,7 +61,7 @@ def test_validate_required_headers(): def test_validate_required_headers_missing(): found_headers = dict.fromkeys(("PEP", "Title", "Author", "Type")) warnings = [ - warning for (_, warning) in pep_lint._validate_required_headers(found_headers) + warning for (_, warning) in check_pep._validate_required_headers(found_headers) ] assert warnings == [ "Must have required header: Status", @@ -74,7 +74,7 @@ def test_validate_required_headers_order(): ("PEP", "Title", "Sponsor", "Author", "Type", "Status", "Replaces", "Created") ) warnings = [ - warning for (_, warning) in pep_lint._validate_required_headers(found_headers) + warning for (_, warning) in check_pep._validate_required_headers(found_headers) ] assert warnings == [ "Headers must be in PEP 12 order. Correct order: PEP, Title, Author, Sponsor, Status, Type, Created, Replaces" @@ -90,17 +90,17 @@ def test_validate_required_headers_order(): ], ) def test_validate_title(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_title(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_title(1, line)] assert warnings == [], warnings def test_validate_title_blank(): - warnings = [warning for (_, warning) in pep_lint._validate_title(1, "-" * 80)] + warnings = [warning for (_, warning) in check_pep._validate_title(1, "-" * 80)] assert warnings == ["PEP title must be less than 80 characters"], warnings def test_validate_title_too_long(): - warnings = [warning for (_, warning) in pep_lint._validate_title(1, "")] + warnings = [warning for (_, warning) in check_pep._validate_title(1, "")] assert warnings == ["PEP must have a title"], warnings @@ -120,7 +120,7 @@ def test_validate_title_too_long(): ], ) def test_validate_status_valid(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_status(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_status(1, line)] assert warnings == [], warnings @@ -143,7 +143,7 @@ def test_validate_status_valid(line: str): ], ) def test_validate_status_invalid(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_status(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_status(1, line)] assert warnings == ["Status must be a valid PEP status"], warnings @@ -156,7 +156,7 @@ def test_validate_status_invalid(line: str): ], ) def test_validate_type_valid(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_type(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_type(1, line)] assert warnings == [], warnings @@ -179,7 +179,7 @@ def test_validate_type_valid(line: str): ], ) def test_validate_type_invalid(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_type(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_type(1, line)] assert warnings == ["Type must be a valid PEP type"], warnings @@ -219,7 +219,7 @@ def test_validate_type_invalid(line: str): ids=str, ) def test_validate_topic(line: str, expected_warnings: set): - warnings = [warning for (_, warning) in pep_lint._validate_topic(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_topic(1, line)] found_warnings = set() @@ -255,7 +255,7 @@ def test_validate_topic(line: str, expected_warnings: set): def test_validate_content_type_valid(): warnings = [ - warning for (_, warning) in pep_lint._validate_content_type(1, "text/x-rst") + warning for (_, warning) in check_pep._validate_content_type(1, "text/x-rst") ] assert warnings == [], warnings @@ -273,7 +273,7 @@ def test_validate_content_type_valid(): ], ) def test_validate_content_type_invalid(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_content_type(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_content_type(1, line)] assert warnings == ["Content-Type must be 'text/x-rst'"], warnings @@ -286,7 +286,7 @@ def test_validate_content_type_invalid(line: str): ], ) def test_validate_pep_references(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_pep_references(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_pep_references(1, line)] assert warnings == [], warnings @@ -299,7 +299,7 @@ def test_validate_pep_references(line: str): ], ) def test_validate_pep_references_separators(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_pep_references(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_pep_references(1, line)] assert warnings == [ "PEP references must be separated by comma-spaces (', ')" ], warnings @@ -348,7 +348,7 @@ def test_validate_pep_references_separators(line: str): ids=str, ) def test_validate_python_version(line: str, expected_warnings: set): - warnings = [warning for (_, warning) in pep_lint._validate_python_version(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_python_version(1, line)] found_warnings = set() diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py index f35f8401ee9..b46cb6a93d1 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py @@ -1,13 +1,13 @@ from pathlib import Path -import pep_lint # NoQA: inserted into sys.modules in conftest.py +import check_pep # NoQA: inserted into sys.modules in conftest.py PEP_9002 = Path(__file__).parent.parent / "peps" / "pep-9002.rst" def test_with_fake_pep(): content = PEP_9002.read_text(encoding="utf-8").splitlines() - warnings = list(pep_lint.check_pep(PEP_9002, content)) + warnings = list(check_pep.check_pep(PEP_9002, content)) assert warnings == [ (1, "PEP must begin with the 'PEP:' header"), (9, "Must not have duplicate header: Sponsor "), diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_number.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_number.py index a644d3d5362..0ee2609046a 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_pep_number.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_number.py @@ -1,4 +1,4 @@ -import pep_lint # NoQA: inserted into sys.modules in conftest.py +import check_pep # NoQA: inserted into sys.modules in conftest.py import pytest @@ -10,7 +10,7 @@ ], ) def test_validate_pep_number(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_pep_number(line)] + warnings = [warning for (_, warning) in check_pep._validate_pep_number(line)] assert warnings == [], warnings @@ -25,7 +25,7 @@ def test_validate_pep_number(line: str): ], ) def test_validate_pep_number_invalid_header(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_pep_number(line)] + warnings = [warning for (_, warning) in check_pep._validate_pep_number(line)] assert warnings == ["PEP must begin with the 'PEP:' header"], warnings @@ -72,7 +72,7 @@ def test_validate_pep_number_invalid_header(line: str): ) def test_pep_num_checker(pep_number: str, expected_warnings: set): warnings = [ - warning for (_, warning) in pep_lint._pep_num(1, pep_number, "") + warning for (_, warning) in check_pep._pep_num(1, pep_number, "") ] found_warnings = set() diff --git a/pep_sphinx_extensions/tests/pep_lint/test_post_url.py b/pep_sphinx_extensions/tests/pep_lint/test_post_url.py index a99913bec66..89b9e11f890 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_post_url.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_post_url.py @@ -1,4 +1,4 @@ -import pep_lint # NoQA: inserted into sys.modules in conftest.py +import check_pep # NoQA: inserted into sys.modules in conftest.py import pytest @@ -20,7 +20,7 @@ ], ) def test_validate_discussions_to_valid(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_discussions_to(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_discussions_to(1, line)] assert warnings == [], warnings @@ -32,7 +32,7 @@ def test_validate_discussions_to_valid(line: str): ], ) def test_validate_discussions_to_list_name(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_discussions_to(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_discussions_to(1, line)] assert warnings == ["Discussions-To must be a valid mailing list"], warnings @@ -44,7 +44,7 @@ def test_validate_discussions_to_list_name(line: str): ], ) def test_validate_discussions_to_invalid_list_domain(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_discussions_to(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_discussions_to(1, line)] assert warnings == [ "Discussions-To must be a valid thread URL or mailing list" ], warnings @@ -69,7 +69,7 @@ def test_validate_discussions_to_invalid_list_domain(line: str): ], ) def test_validate_post_history_valid(body: str): - warnings = [warning for (_, warning) in pep_lint._validate_post_history(1, body)] + warnings = [warning for (_, warning) in check_pep._validate_post_history(1, body)] assert warnings == [], warnings @@ -87,7 +87,7 @@ def test_validate_post_history_valid(body: str): ], ) def test_validate_resolution_valid(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_resolution(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_resolution(1, line)] assert warnings == [], warnings @@ -109,7 +109,7 @@ def test_validate_resolution_valid(line: str): ], ) def test_validate_resolution_invalid(line: str): - warnings = [warning for (_, warning) in pep_lint._validate_resolution(1, line)] + warnings = [warning for (_, warning) in check_pep._validate_resolution(1, line)] assert warnings == ["Resolution must be a valid thread URL"], warnings @@ -134,7 +134,7 @@ def test_validate_resolution_invalid(line: str): ], ) def test_thread_checker_valid(thread_url: str): - warnings = [warning for (_, warning) in pep_lint._thread(1, thread_url, "")] + warnings = [warning for (_, warning) in check_pep._thread(1, thread_url, "")] assert warnings == [], warnings @@ -185,7 +185,7 @@ def test_thread_checker_valid(thread_url: str): ], ) def test_thread_checker_invalid(thread_url: str): - warnings = [warning for (_, warning) in pep_lint._thread(1, thread_url, "")] + warnings = [warning for (_, warning) in check_pep._thread(1, thread_url, "")] assert warnings == [" must be a valid thread URL"], warnings @@ -205,7 +205,7 @@ def test_thread_checker_invalid(thread_url: str): def test_thread_checker_valid_allow_message(thread_url: str): warnings = [ warning - for (_, warning) in pep_lint._thread( + for (_, warning) in check_pep._thread( 1, thread_url, "", allow_message=True ) ] @@ -232,7 +232,7 @@ def test_thread_checker_valid_allow_message(thread_url: str): def test_thread_checker_invalid_allow_message(thread_url: str): warnings = [ warning - for (_, warning) in pep_lint._thread( + for (_, warning) in check_pep._thread( 1, thread_url, "", allow_message=True ) ] @@ -259,7 +259,7 @@ def test_thread_checker_invalid_allow_message(thread_url: str): def test_thread_checker_valid_discussions_to(thread_url: str): warnings = [ warning - for (_, warning) in pep_lint._thread( + for (_, warning) in check_pep._thread( 1, thread_url, "", discussions_to=True ) ] @@ -283,7 +283,7 @@ def test_thread_checker_valid_discussions_to(thread_url: str): def test_thread_checker_invalid_discussions_to(thread_url: str): warnings = [ warning - for (_, warning) in pep_lint._thread( + for (_, warning) in check_pep._thread( 1, thread_url, "", discussions_to=True ) ] @@ -293,5 +293,5 @@ def test_thread_checker_invalid_discussions_to(thread_url: str): def test_thread_checker_allow_message_discussions_to(): with pytest.raises(ValueError, match="cannot both be True"): list( - pep_lint._thread(1, "", "", allow_message=True, discussions_to=True) + check_pep._thread(1, "", "", allow_message=True, discussions_to=True) ) diff --git a/pytest.ini b/pytest.ini index 975055e1e9d..6ccd243d610 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,5 @@ [pytest] -addopts = -r a --strict-config --strict-markers --import-mode=importlib --cov pep_lint --cov pep_sphinx_extensions --cov-report html --cov-report xml +addopts = -r a --strict-config --strict-markers --import-mode=importlib --cov check_pep --cov pep_sphinx_extensions --cov-report html --cov-report xml empty_parameter_set_mark = fail_at_collect filterwarnings = error From 9366257cade73c87dc55242377dd0fd8d1c35f84 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 31 Aug 2023 03:55:28 +0100 Subject: [PATCH 75/83] Pluralise --- .github/CODEOWNERS | 2 +- .github/workflows/lint.yml | 8 ++-- .pre-commit-config.yaml | 8 ++-- check-pep.py => check-peps.py | 10 ++--- pep_sphinx_extensions/tests/conftest.py | 10 ++--- .../tests/pep_lint/test_date.py | 12 +++--- .../tests/pep_lint/test_direct_links.py | 6 +-- .../tests/pep_lint/test_email.py | 14 +++---- .../tests/pep_lint/test_headers.py | 38 +++++++++---------- .../tests/pep_lint/test_pep_lint.py | 4 +- .../tests/pep_lint/test_pep_number.py | 8 ++-- .../tests/pep_lint/test_post_url.py | 28 +++++++------- pytest.ini | 2 +- 13 files changed, 75 insertions(+), 75 deletions(-) rename check-pep.py => check-peps.py (98%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 112a5ca0ab3..ada101ef6f1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -21,7 +21,7 @@ contents.rst @AA-Turner .codespell/ @CAM-Gerlach @hugovk .codespellrc @CAM-Gerlach @hugovk .pre-commit-config.yaml @CAM-Gerlach @hugovk -check-pep.py @AA-Turner @CAM-Gerlach @hugovk +check-peps.py @AA-Turner @CAM-Gerlach @hugovk # Git infrastructure .gitattributes @CAM-Gerlach diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 784a9f0a651..37ccaee036e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -36,8 +36,8 @@ jobs: with: extra_args: --all-files --hook-stage manual codespell || true - check-pep: - name: Run check-pep + check-peps: + name: Run check-peps runs-on: ubuntu-latest steps: @@ -47,5 +47,5 @@ jobs: with: python-version: "3" - - name: Run check-pep - run: python check-pep.py --detailed + - name: Run check-peps + run: python check-peps.py --detailed diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9f4a1deb295..a70965d7ddf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -101,10 +101,10 @@ repos: # Local checks for PEP headers and more - repo: local hooks: -# # Hook to run 'check-pep.py' -# - id: "check-pep" -# name: "Check PEP metadata" -# entry: "python check-pep.py" +# # Hook to run 'check-peps.py' +# - id: "check-peps" +# name: "Check PEPs for metadata and content enforcement" +# entry: "python check-peps.py" # language: "system" # files: '^pep-\d{4}.(txt|rst)$' # types_or: ["rst", "plain-text"] diff --git a/check-pep.py b/check-peps.py similarity index 98% rename from check-pep.py rename to check-peps.py index b0021280546..cae91ca7641 100644 --- a/check-pep.py +++ b/check-peps.py @@ -3,9 +3,9 @@ # This file is placed in the public domain or under the # CC0-1.0-Universal license, whichever is more permissive. -"""check-pep: Check PEPs for common mistakes. +"""check-peps: Check PEPs for common mistakes. -Usage: check-pep [-d | --detailed] +Usage: check-peps [-d | --detailed] Only the PEPs specified are checked. If none are specified, all PEPs are checked. @@ -100,7 +100,7 @@ def check(filenames: Sequence[str] = (), /) -> int: filenames = itertools.chain(PEP_ROOT.glob("pep-????.txt"), PEP_ROOT.glob("pep-????.rst")) if (count := sum(map(check_file, filenames))) > 0: s = "s" * (count != 1) - print(f"check-pep failed: {count} error{s}", file=sys.stderr) + print(f"check-peps failed: {count} error{s}", file=sys.stderr) return 1 return 0 @@ -113,10 +113,10 @@ def check_file(filename: Path, /) -> int: return _output_error(filename, [""], [(0, "Could not read PEP!")]) else: lines = content.splitlines() - return _output_error(filename, lines, check_pep(filename, lines)) + return _output_error(filename, lines, check_peps(filename, lines)) -def check_pep(filename: Path, lines: Sequence[str], /) -> MessageIterator: +def check_peps(filename: Path, lines: Sequence[str], /) -> MessageIterator: yield from check_headers(lines) for line_num, line in enumerate(lines, start=1): if filename.stem.removeprefix("pep-") in SKIP_DIRECT_PEP_LINK_CHECK: diff --git a/pep_sphinx_extensions/tests/conftest.py b/pep_sphinx_extensions/tests/conftest.py index d555193e7fc..0fd10fa8d2f 100644 --- a/pep_sphinx_extensions/tests/conftest.py +++ b/pep_sphinx_extensions/tests/conftest.py @@ -2,8 +2,8 @@ import sys from pathlib import Path -# Import "check-pep.py" as "check_pep" -CHECK_PEP_PATH = Path(__file__).resolve().parents[2] / "check-pep.py" -spec = importlib.util.spec_from_file_location("check_pep", CHECK_PEP_PATH) -sys.modules["check_pep"] = check_pep = importlib.util.module_from_spec(spec) -spec.loader.exec_module(check_pep) +# Import "check-peps.py" as "check_peps" +CHECK_PEPS_PATH = Path(__file__).resolve().parents[2] / "check-peps.py" +spec = importlib.util.spec_from_file_location("check_peps", CHECK_PEPS_PATH) +sys.modules["check_peps"] = check_peps = importlib.util.module_from_spec(spec) +spec.loader.exec_module(check_peps) diff --git a/pep_sphinx_extensions/tests/pep_lint/test_date.py b/pep_sphinx_extensions/tests/pep_lint/test_date.py index 7f1960c7fcf..3ce46661079 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_date.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_date.py @@ -1,6 +1,6 @@ import datetime as dt -import check_pep # NoQA: inserted into sys.modules in conftest.py +import check_peps # NoQA: inserted into sys.modules in conftest.py import pytest @@ -18,7 +18,7 @@ ], ) def test_validate_created(line: str): - warnings = [warning for (_, warning) in check_pep._validate_created(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_created(1, line)] assert warnings == [], warnings @@ -36,7 +36,7 @@ def test_validate_created(line: str): ], ) def test_date_checker_valid(date_str: str): - warnings = [warning for (_, warning) in check_pep._date(1, date_str, "")] + warnings = [warning for (_, warning) in check_peps._date(1, date_str, "")] assert warnings == [], warnings @@ -67,7 +67,7 @@ def test_date_checker_valid(date_str: str): ], ) def test_date_checker_malformed(date_str: str): - warnings = [warning for (_, warning) in check_pep._date(1, date_str, "")] + warnings = [warning for (_, warning) in check_peps._date(1, date_str, "")] expected = f" must be a 'DD-mmm-YYYY' date: {date_str!r}" assert warnings == [expected], warnings @@ -83,7 +83,7 @@ def test_date_checker_malformed(date_str: str): ], ) def test_date_checker_too_early(date_str: str): - warnings = [warning for (_, warning) in check_pep._date(1, date_str, "")] + warnings = [warning for (_, warning) in check_peps._date(1, date_str, "")] expected = f" must not be before Python was invented: {date_str!r}" assert warnings == [expected], warnings @@ -100,6 +100,6 @@ def test_date_checker_too_early(date_str: str): ], ) def test_date_checker_too_late(date_str: str): - warnings = [warning for (_, warning) in check_pep._date(1, date_str, "")] + warnings = [warning for (_, warning) in check_peps._date(1, date_str, "")] expected = f" must not be in the future: {date_str!r}" assert warnings == [expected], warnings diff --git a/pep_sphinx_extensions/tests/pep_lint/test_direct_links.py b/pep_sphinx_extensions/tests/pep_lint/test_direct_links.py index f6e8657b95a..66edc0552bd 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_direct_links.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_direct_links.py @@ -1,4 +1,4 @@ -import check_pep # NoQA: inserted into sys.modules in conftest.py +import check_peps # NoQA: inserted into sys.modules in conftest.py import pytest @@ -12,7 +12,7 @@ ], ) def test_check_direct_links_pep(line: str): - warnings = [warning for (_, warning) in check_pep.check_direct_links(1, line)] + warnings = [warning for (_, warning) in check_peps.check_direct_links(1, line)] assert warnings == ["Use the :pep:`NNN` role to refer to PEPs"], warnings @@ -26,5 +26,5 @@ def test_check_direct_links_pep(line: str): ], ) def test_check_direct_links_rfc(line: str): - warnings = [warning for (_, warning) in check_pep.check_direct_links(1, line)] + warnings = [warning for (_, warning) in check_peps.check_direct_links(1, line)] assert warnings == ["Use the :rfc:`NNN` role to refer to RFCs"], warnings diff --git a/pep_sphinx_extensions/tests/pep_lint/test_email.py b/pep_sphinx_extensions/tests/pep_lint/test_email.py index 163298ebd0a..2ff4ba61f23 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_email.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_email.py @@ -1,4 +1,4 @@ -import check_pep # NoQA: inserted into sys.modules in conftest.py +import check_peps # NoQA: inserted into sys.modules in conftest.py import pytest @@ -18,7 +18,7 @@ ids=repr, # the default calls str and renders newlines. ) def test_validate_author(line: str): - warnings = [warning for (_, warning) in check_pep._validate_author(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_author(1, line)] assert warnings == [], warnings @@ -33,7 +33,7 @@ def test_validate_author(line: str): ids=repr, # the default calls str and renders newlines. ) def test_validate_author_over__indented(line: str): - warnings = [warning for (_, warning) in check_pep._validate_author(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_author(1, line)] assert {*warnings} == {"Author line must not be over-indented"}, warnings @@ -47,7 +47,7 @@ def test_validate_author_over__indented(line: str): ids=repr, # the default calls str and renders newlines. ) def test_validate_author_continuation(line: str): - warnings = [warning for (_, warning) in check_pep._validate_author(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_author(1, line)] assert {*warnings} == {"Author continuation lines must end with a comma"}, warnings @@ -61,7 +61,7 @@ def test_validate_author_continuation(line: str): ], ) def test_validate_sponsor(line: str): - warnings = [warning for (_, warning) in check_pep._validate_sponsor(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_sponsor(1, line)] assert warnings == [], warnings @@ -76,7 +76,7 @@ def test_validate_sponsor(line: str): ], ) def test_validate_delegate(line: str): - warnings = [warning for (_, warning) in check_pep._validate_delegate(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_delegate(1, line)] assert warnings == [], warnings @@ -191,7 +191,7 @@ def test_validate_delegate(line: str): ids=str, ) def test_email_checker(email: str, expected_warnings: set): - warnings = [warning for (_, warning) in check_pep._email(1, email, "")] + warnings = [warning for (_, warning) in check_peps._email(1, email, "")] found_warnings = set() email = email.strip() diff --git a/pep_sphinx_extensions/tests/pep_lint/test_headers.py b/pep_sphinx_extensions/tests/pep_lint/test_headers.py index dbdfdaf19c8..17226909d25 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_headers.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_headers.py @@ -1,4 +1,4 @@ -import check_pep # NoQA: inserted into sys.modules in conftest.py +import check_peps # NoQA: inserted into sys.modules in conftest.py import pytest @@ -23,7 +23,7 @@ ], ) def test_header_pattern(test_input, expected): - assert check_pep.HEADER_PATTERN.match(test_input)[1] == expected + assert check_peps.HEADER_PATTERN.match(test_input)[1] == expected @pytest.mark.parametrize( @@ -45,7 +45,7 @@ def test_header_pattern(test_input, expected): ], ) def test_header_pattern_no_match(test_input): - assert check_pep.HEADER_PATTERN.match(test_input) is None + assert check_peps.HEADER_PATTERN.match(test_input) is None def test_validate_required_headers(): @@ -53,7 +53,7 @@ def test_validate_required_headers(): ("PEP", "Title", "Author", "Status", "Type", "Created") ) warnings = [ - warning for (_, warning) in check_pep._validate_required_headers(found_headers) + warning for (_, warning) in check_peps._validate_required_headers(found_headers) ] assert warnings == [], warnings @@ -61,7 +61,7 @@ def test_validate_required_headers(): def test_validate_required_headers_missing(): found_headers = dict.fromkeys(("PEP", "Title", "Author", "Type")) warnings = [ - warning for (_, warning) in check_pep._validate_required_headers(found_headers) + warning for (_, warning) in check_peps._validate_required_headers(found_headers) ] assert warnings == [ "Must have required header: Status", @@ -74,7 +74,7 @@ def test_validate_required_headers_order(): ("PEP", "Title", "Sponsor", "Author", "Type", "Status", "Replaces", "Created") ) warnings = [ - warning for (_, warning) in check_pep._validate_required_headers(found_headers) + warning for (_, warning) in check_peps._validate_required_headers(found_headers) ] assert warnings == [ "Headers must be in PEP 12 order. Correct order: PEP, Title, Author, Sponsor, Status, Type, Created, Replaces" @@ -90,17 +90,17 @@ def test_validate_required_headers_order(): ], ) def test_validate_title(line: str): - warnings = [warning for (_, warning) in check_pep._validate_title(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_title(1, line)] assert warnings == [], warnings def test_validate_title_blank(): - warnings = [warning for (_, warning) in check_pep._validate_title(1, "-" * 80)] + warnings = [warning for (_, warning) in check_peps._validate_title(1, "-" * 80)] assert warnings == ["PEP title must be less than 80 characters"], warnings def test_validate_title_too_long(): - warnings = [warning for (_, warning) in check_pep._validate_title(1, "")] + warnings = [warning for (_, warning) in check_peps._validate_title(1, "")] assert warnings == ["PEP must have a title"], warnings @@ -120,7 +120,7 @@ def test_validate_title_too_long(): ], ) def test_validate_status_valid(line: str): - warnings = [warning for (_, warning) in check_pep._validate_status(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_status(1, line)] assert warnings == [], warnings @@ -143,7 +143,7 @@ def test_validate_status_valid(line: str): ], ) def test_validate_status_invalid(line: str): - warnings = [warning for (_, warning) in check_pep._validate_status(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_status(1, line)] assert warnings == ["Status must be a valid PEP status"], warnings @@ -156,7 +156,7 @@ def test_validate_status_invalid(line: str): ], ) def test_validate_type_valid(line: str): - warnings = [warning for (_, warning) in check_pep._validate_type(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_type(1, line)] assert warnings == [], warnings @@ -179,7 +179,7 @@ def test_validate_type_valid(line: str): ], ) def test_validate_type_invalid(line: str): - warnings = [warning for (_, warning) in check_pep._validate_type(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_type(1, line)] assert warnings == ["Type must be a valid PEP type"], warnings @@ -219,7 +219,7 @@ def test_validate_type_invalid(line: str): ids=str, ) def test_validate_topic(line: str, expected_warnings: set): - warnings = [warning for (_, warning) in check_pep._validate_topic(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_topic(1, line)] found_warnings = set() @@ -255,7 +255,7 @@ def test_validate_topic(line: str, expected_warnings: set): def test_validate_content_type_valid(): warnings = [ - warning for (_, warning) in check_pep._validate_content_type(1, "text/x-rst") + warning for (_, warning) in check_peps._validate_content_type(1, "text/x-rst") ] assert warnings == [], warnings @@ -273,7 +273,7 @@ def test_validate_content_type_valid(): ], ) def test_validate_content_type_invalid(line: str): - warnings = [warning for (_, warning) in check_pep._validate_content_type(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_content_type(1, line)] assert warnings == ["Content-Type must be 'text/x-rst'"], warnings @@ -286,7 +286,7 @@ def test_validate_content_type_invalid(line: str): ], ) def test_validate_pep_references(line: str): - warnings = [warning for (_, warning) in check_pep._validate_pep_references(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_pep_references(1, line)] assert warnings == [], warnings @@ -299,7 +299,7 @@ def test_validate_pep_references(line: str): ], ) def test_validate_pep_references_separators(line: str): - warnings = [warning for (_, warning) in check_pep._validate_pep_references(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_pep_references(1, line)] assert warnings == [ "PEP references must be separated by comma-spaces (', ')" ], warnings @@ -348,7 +348,7 @@ def test_validate_pep_references_separators(line: str): ids=str, ) def test_validate_python_version(line: str, expected_warnings: set): - warnings = [warning for (_, warning) in check_pep._validate_python_version(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_python_version(1, line)] found_warnings = set() diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py index b46cb6a93d1..2c52fa3977e 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_lint.py @@ -1,13 +1,13 @@ from pathlib import Path -import check_pep # NoQA: inserted into sys.modules in conftest.py +import check_peps # NoQA: inserted into sys.modules in conftest.py PEP_9002 = Path(__file__).parent.parent / "peps" / "pep-9002.rst" def test_with_fake_pep(): content = PEP_9002.read_text(encoding="utf-8").splitlines() - warnings = list(check_pep.check_pep(PEP_9002, content)) + warnings = list(check_peps.check_peps(PEP_9002, content)) assert warnings == [ (1, "PEP must begin with the 'PEP:' header"), (9, "Must not have duplicate header: Sponsor "), diff --git a/pep_sphinx_extensions/tests/pep_lint/test_pep_number.py b/pep_sphinx_extensions/tests/pep_lint/test_pep_number.py index 0ee2609046a..3443f7144dd 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_pep_number.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_pep_number.py @@ -1,4 +1,4 @@ -import check_pep # NoQA: inserted into sys.modules in conftest.py +import check_peps # NoQA: inserted into sys.modules in conftest.py import pytest @@ -10,7 +10,7 @@ ], ) def test_validate_pep_number(line: str): - warnings = [warning for (_, warning) in check_pep._validate_pep_number(line)] + warnings = [warning for (_, warning) in check_peps._validate_pep_number(line)] assert warnings == [], warnings @@ -25,7 +25,7 @@ def test_validate_pep_number(line: str): ], ) def test_validate_pep_number_invalid_header(line: str): - warnings = [warning for (_, warning) in check_pep._validate_pep_number(line)] + warnings = [warning for (_, warning) in check_peps._validate_pep_number(line)] assert warnings == ["PEP must begin with the 'PEP:' header"], warnings @@ -72,7 +72,7 @@ def test_validate_pep_number_invalid_header(line: str): ) def test_pep_num_checker(pep_number: str, expected_warnings: set): warnings = [ - warning for (_, warning) in check_pep._pep_num(1, pep_number, "") + warning for (_, warning) in check_peps._pep_num(1, pep_number, "") ] found_warnings = set() diff --git a/pep_sphinx_extensions/tests/pep_lint/test_post_url.py b/pep_sphinx_extensions/tests/pep_lint/test_post_url.py index 89b9e11f890..05e4f476062 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_post_url.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_post_url.py @@ -1,4 +1,4 @@ -import check_pep # NoQA: inserted into sys.modules in conftest.py +import check_peps # NoQA: inserted into sys.modules in conftest.py import pytest @@ -20,7 +20,7 @@ ], ) def test_validate_discussions_to_valid(line: str): - warnings = [warning for (_, warning) in check_pep._validate_discussions_to(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_discussions_to(1, line)] assert warnings == [], warnings @@ -32,7 +32,7 @@ def test_validate_discussions_to_valid(line: str): ], ) def test_validate_discussions_to_list_name(line: str): - warnings = [warning for (_, warning) in check_pep._validate_discussions_to(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_discussions_to(1, line)] assert warnings == ["Discussions-To must be a valid mailing list"], warnings @@ -44,7 +44,7 @@ def test_validate_discussions_to_list_name(line: str): ], ) def test_validate_discussions_to_invalid_list_domain(line: str): - warnings = [warning for (_, warning) in check_pep._validate_discussions_to(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_discussions_to(1, line)] assert warnings == [ "Discussions-To must be a valid thread URL or mailing list" ], warnings @@ -69,7 +69,7 @@ def test_validate_discussions_to_invalid_list_domain(line: str): ], ) def test_validate_post_history_valid(body: str): - warnings = [warning for (_, warning) in check_pep._validate_post_history(1, body)] + warnings = [warning for (_, warning) in check_peps._validate_post_history(1, body)] assert warnings == [], warnings @@ -87,7 +87,7 @@ def test_validate_post_history_valid(body: str): ], ) def test_validate_resolution_valid(line: str): - warnings = [warning for (_, warning) in check_pep._validate_resolution(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_resolution(1, line)] assert warnings == [], warnings @@ -109,7 +109,7 @@ def test_validate_resolution_valid(line: str): ], ) def test_validate_resolution_invalid(line: str): - warnings = [warning for (_, warning) in check_pep._validate_resolution(1, line)] + warnings = [warning for (_, warning) in check_peps._validate_resolution(1, line)] assert warnings == ["Resolution must be a valid thread URL"], warnings @@ -134,7 +134,7 @@ def test_validate_resolution_invalid(line: str): ], ) def test_thread_checker_valid(thread_url: str): - warnings = [warning for (_, warning) in check_pep._thread(1, thread_url, "")] + warnings = [warning for (_, warning) in check_peps._thread(1, thread_url, "")] assert warnings == [], warnings @@ -185,7 +185,7 @@ def test_thread_checker_valid(thread_url: str): ], ) def test_thread_checker_invalid(thread_url: str): - warnings = [warning for (_, warning) in check_pep._thread(1, thread_url, "")] + warnings = [warning for (_, warning) in check_peps._thread(1, thread_url, "")] assert warnings == [" must be a valid thread URL"], warnings @@ -205,7 +205,7 @@ def test_thread_checker_invalid(thread_url: str): def test_thread_checker_valid_allow_message(thread_url: str): warnings = [ warning - for (_, warning) in check_pep._thread( + for (_, warning) in check_peps._thread( 1, thread_url, "", allow_message=True ) ] @@ -232,7 +232,7 @@ def test_thread_checker_valid_allow_message(thread_url: str): def test_thread_checker_invalid_allow_message(thread_url: str): warnings = [ warning - for (_, warning) in check_pep._thread( + for (_, warning) in check_peps._thread( 1, thread_url, "", allow_message=True ) ] @@ -259,7 +259,7 @@ def test_thread_checker_invalid_allow_message(thread_url: str): def test_thread_checker_valid_discussions_to(thread_url: str): warnings = [ warning - for (_, warning) in check_pep._thread( + for (_, warning) in check_peps._thread( 1, thread_url, "", discussions_to=True ) ] @@ -283,7 +283,7 @@ def test_thread_checker_valid_discussions_to(thread_url: str): def test_thread_checker_invalid_discussions_to(thread_url: str): warnings = [ warning - for (_, warning) in check_pep._thread( + for (_, warning) in check_peps._thread( 1, thread_url, "", discussions_to=True ) ] @@ -293,5 +293,5 @@ def test_thread_checker_invalid_discussions_to(thread_url: str): def test_thread_checker_allow_message_discussions_to(): with pytest.raises(ValueError, match="cannot both be True"): list( - check_pep._thread(1, "", "", allow_message=True, discussions_to=True) + check_peps._thread(1, "", "", allow_message=True, discussions_to=True) ) diff --git a/pytest.ini b/pytest.ini index 6ccd243d610..9829a156ceb 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,5 @@ [pytest] -addopts = -r a --strict-config --strict-markers --import-mode=importlib --cov check_pep --cov pep_sphinx_extensions --cov-report html --cov-report xml +addopts = -r a --strict-config --strict-markers --import-mode=importlib --cov check_peps --cov pep_sphinx_extensions --cov-report html --cov-report xml empty_parameter_set_mark = fail_at_collect filterwarnings = error From 341e1861864ca7084d1af5f7f37fd300f2150763 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 31 Aug 2023 03:56:35 +0100 Subject: [PATCH 76/83] Run black --- .../tests/pep_lint/test_headers.py | 12 +++++++--- .../tests/pep_lint/test_post_url.py | 24 ++++++++++++++----- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/pep_sphinx_extensions/tests/pep_lint/test_headers.py b/pep_sphinx_extensions/tests/pep_lint/test_headers.py index 17226909d25..8246271e363 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_headers.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_headers.py @@ -286,7 +286,9 @@ def test_validate_content_type_invalid(line: str): ], ) def test_validate_pep_references(line: str): - warnings = [warning for (_, warning) in check_peps._validate_pep_references(1, line)] + warnings = [ + warning for (_, warning) in check_peps._validate_pep_references(1, line) + ] assert warnings == [], warnings @@ -299,7 +301,9 @@ def test_validate_pep_references(line: str): ], ) def test_validate_pep_references_separators(line: str): - warnings = [warning for (_, warning) in check_peps._validate_pep_references(1, line)] + warnings = [ + warning for (_, warning) in check_peps._validate_pep_references(1, line) + ] assert warnings == [ "PEP references must be separated by comma-spaces (', ')" ], warnings @@ -348,7 +352,9 @@ def test_validate_pep_references_separators(line: str): ids=str, ) def test_validate_python_version(line: str, expected_warnings: set): - warnings = [warning for (_, warning) in check_peps._validate_python_version(1, line)] + warnings = [ + warning for (_, warning) in check_peps._validate_python_version(1, line) + ] found_warnings = set() diff --git a/pep_sphinx_extensions/tests/pep_lint/test_post_url.py b/pep_sphinx_extensions/tests/pep_lint/test_post_url.py index 05e4f476062..dd04b4c3941 100644 --- a/pep_sphinx_extensions/tests/pep_lint/test_post_url.py +++ b/pep_sphinx_extensions/tests/pep_lint/test_post_url.py @@ -20,7 +20,9 @@ ], ) def test_validate_discussions_to_valid(line: str): - warnings = [warning for (_, warning) in check_peps._validate_discussions_to(1, line)] + warnings = [ + warning for (_, warning) in check_peps._validate_discussions_to(1, line) + ] assert warnings == [], warnings @@ -32,7 +34,9 @@ def test_validate_discussions_to_valid(line: str): ], ) def test_validate_discussions_to_list_name(line: str): - warnings = [warning for (_, warning) in check_peps._validate_discussions_to(1, line)] + warnings = [ + warning for (_, warning) in check_peps._validate_discussions_to(1, line) + ] assert warnings == ["Discussions-To must be a valid mailing list"], warnings @@ -44,7 +48,9 @@ def test_validate_discussions_to_list_name(line: str): ], ) def test_validate_discussions_to_invalid_list_domain(line: str): - warnings = [warning for (_, warning) in check_peps._validate_discussions_to(1, line)] + warnings = [ + warning for (_, warning) in check_peps._validate_discussions_to(1, line) + ] assert warnings == [ "Discussions-To must be a valid thread URL or mailing list" ], warnings @@ -134,7 +140,9 @@ def test_validate_resolution_invalid(line: str): ], ) def test_thread_checker_valid(thread_url: str): - warnings = [warning for (_, warning) in check_peps._thread(1, thread_url, "")] + warnings = [ + warning for (_, warning) in check_peps._thread(1, thread_url, "") + ] assert warnings == [], warnings @@ -185,7 +193,9 @@ def test_thread_checker_valid(thread_url: str): ], ) def test_thread_checker_invalid(thread_url: str): - warnings = [warning for (_, warning) in check_peps._thread(1, thread_url, "")] + warnings = [ + warning for (_, warning) in check_peps._thread(1, thread_url, "") + ] assert warnings == [" must be a valid thread URL"], warnings @@ -293,5 +303,7 @@ def test_thread_checker_invalid_discussions_to(thread_url: str): def test_thread_checker_allow_message_discussions_to(): with pytest.raises(ValueError, match="cannot both be True"): list( - check_peps._thread(1, "", "", allow_message=True, discussions_to=True) + check_peps._thread( + 1, "", "", allow_message=True, discussions_to=True + ) ) From e9f99c013f452fdfa88966324b0043665bd8716d Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 31 Aug 2023 03:57:27 +0100 Subject: [PATCH 77/83] Executability --- check-peps.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 check-peps.py diff --git a/check-peps.py b/check-peps.py old mode 100644 new mode 100755 From efeaebb698df6677e77ee0a559c94a7399ce2fca Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 1 Sep 2023 12:42:32 +0100 Subject: [PATCH 78/83] Format pytest addopts as a nicer list --- pytest.ini | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index 9829a156ceb..99bea8065b9 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,12 @@ [pytest] -addopts = -r a --strict-config --strict-markers --import-mode=importlib --cov check_peps --cov pep_sphinx_extensions --cov-report html --cov-report xml +# https://docs.pytest.org/en/7.3.x/reference/reference.html#command-line-flags +addopts = + -r a + --strict-config + --strict-markers + --import-mode=importlib + --cov check_peps --cov pep_sphinx_extensions + --cov-report html --cov-report xml empty_parameter_set_mark = fail_at_collect filterwarnings = error From 5db830c2197a57f249ddfed74ab4b37186c94b4b Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Tue, 5 Sep 2023 03:52:47 +0100 Subject: [PATCH 79/83] pre-commit tweaks --- .pre-commit-config.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index df7fb622029..8cd1ef3e4f3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -101,13 +101,12 @@ repos: # Local checks for PEP headers and more - repo: local hooks: -# # Hook to run 'check-peps.py' +# # Hook to run "check-peps.py" # - id: "check-peps" # name: "Check PEPs for metadata and content enforcement" # entry: "python check-peps.py" # language: "system" -# files: '^pep-\d{4}.(txt|rst)$' -# types_or: ["rst", "plain-text"] +# files: "^pep-\d{4}\.(rst|txt)$" # require_serial: true - id: check-no-tabs From 017b0c8cba701e3bded164a30cc3e6c4d054d759 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Tue, 5 Sep 2023 03:56:07 +0100 Subject: [PATCH 80/83] match indentation --- pytest.ini | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pytest.ini b/pytest.ini index 99bea8065b9..10404cc0b3b 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,12 +1,12 @@ [pytest] # https://docs.pytest.org/en/7.3.x/reference/reference.html#command-line-flags addopts = - -r a - --strict-config - --strict-markers - --import-mode=importlib - --cov check_peps --cov pep_sphinx_extensions - --cov-report html --cov-report xml + -r a + --strict-config + --strict-markers + --import-mode=importlib + --cov check_peps --cov pep_sphinx_extensions + --cov-report html --cov-report xml empty_parameter_set_mark = fail_at_collect filterwarnings = error From 712d4b23d1659072fb5bafc4519b61ab1caa7ae1 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Tue, 5 Sep 2023 04:07:02 +0100 Subject: [PATCH 81/83] Extract default re flags out --- check-peps.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/check-peps.py b/check-peps.py index cae91ca7641..623bdc040ef 100755 --- a/check-peps.py +++ b/check-peps.py @@ -76,17 +76,21 @@ # PEPs that are allowed to link directly to PEPs SKIP_DIRECT_PEP_LINK_CHECK = frozenset({"0009", "0287", "0676", "0684", "8001"}) +DEFAULT_FLAGS = re.ASCII | re.IGNORECASE # Insensitive latin + # any sequence of letters or '-', followed by a single ':' and a space or end of line -HEADER_PATTERN = re.compile(r"^([a-z\-]+):(?: |$)", re.ASCII | re.IGNORECASE) +HEADER_PATTERN = re.compile(r"^([a-z\-]+):(?: |$)", DEFAULT_FLAGS) # any sequence of unicode letters or legal special characters NAME_PATTERN = re.compile(r"(?:[^\W\d_]|[ ',\-.])+(?: |$)") # any sequence of ASCII letters, digits, or legal special characters -EMAIL_LOCAL_PART_PATTERN = re.compile(r"[\w!#$%&'*+\-/=?^{|}~.]+", re.ASCII | re.IGNORECASE) -DISCOURSE_THREAD_PATTERN = re.compile(r"([\w\-]+/)?\d+", re.ASCII | re.IGNORECASE) -DISCOURSE_POST_PATTERN = re.compile(r"([\w\-]+/)?\d+(/\d+)?", re.ASCII | re.IGNORECASE) -MAILMAN_2_PATTERN = re.compile(r"[\w\-]+/\d{4}-[a-z]+/\d+\.html", re.ASCII | re.IGNORECASE) -MAILMAN_3_THREAD_PATTERN = re.compile(r"[\w\-]+@python\.org/thread/[a-z0-9]+/?", re.ASCII | re.IGNORECASE) -MAILMAN_3_MESSAGE_PATTERN = re.compile(r"[\w\-]+@python\.org/message/[a-z0-9]+/?(#[a-z0-9]+)?", re.ASCII | re.IGNORECASE) +EMAIL_LOCAL_PART_PATTERN = re.compile(r"[\w!#$%&'*+\-/=?^{|}~.]+", DEFAULT_FLAGS) + +DISCOURSE_THREAD_PATTERN = re.compile(r"([\w\-]+/)?\d+", DEFAULT_FLAGS) +DISCOURSE_POST_PATTERN = re.compile(r"([\w\-]+/)?\d+(/\d+)?", DEFAULT_FLAGS) + +MAILMAN_2_PATTERN = re.compile(r"[\w\-]+/\d{4}-[a-z]+/\d+\.html", DEFAULT_FLAGS) +MAILMAN_3_THREAD_PATTERN = re.compile(r"[\w\-]+@python\.org/thread/[a-z0-9]+/?", DEFAULT_FLAGS) +MAILMAN_3_MESSAGE_PATTERN = re.compile(r"[\w\-]+@python\.org/message/[a-z0-9]+/?(#[a-z0-9]+)?", DEFAULT_FLAGS) # Controlled by the "--detailed" flag DETAILED_ERRORS = False From 11c5457871cf5c4842b0358828f5c588b0b7d152 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Tue, 5 Sep 2023 04:08:47 +0100 Subject: [PATCH 82/83] Not using types_or any more --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8cd1ef3e4f3..8775917c8f2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks -minimum_pre_commit_version: '2.9.0' +minimum_pre_commit_version: '2.8.2' default_language_version: python: python3 From f205724b0eb99c4f89aef6aab955c14af766feee Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Tue, 5 Sep 2023 04:11:23 +0100 Subject: [PATCH 83/83] Consolidate paths --- pep_sphinx_extensions/tests/conftest.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pep_sphinx_extensions/tests/conftest.py b/pep_sphinx_extensions/tests/conftest.py index d56fe494c42..2c207ebcd9e 100644 --- a/pep_sphinx_extensions/tests/conftest.py +++ b/pep_sphinx_extensions/tests/conftest.py @@ -2,10 +2,11 @@ import sys from pathlib import Path -PEP_ROOT = Path(__file__, "..", "..", "..").resolve() +_ROOT_PATH = Path(__file__, "..", "..", "..").resolve() +PEP_ROOT = _ROOT_PATH # Import "check-peps.py" as "check_peps" -CHECK_PEPS_PATH = Path(__file__).resolve().parents[2] / "check-peps.py" +CHECK_PEPS_PATH = _ROOT_PATH / "check-peps.py" spec = importlib.util.spec_from_file_location("check_peps", CHECK_PEPS_PATH) sys.modules["check_peps"] = check_peps = importlib.util.module_from_spec(spec) spec.loader.exec_module(check_peps)