Skip to content

Commit

Permalink
Lint on GitHub Actions via pre-commit (#104)
Browse files Browse the repository at this point in the history
* Format with Black via pre-commit

* Lint with Ruff

* Add some handy pre-commit hooks

* Apply common format to pyproject.toml and validate

* Format tox.ini

* autoupdate_schedule: quarterly

* Ignore UP038: it makes code slower and more verbose

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
  • Loading branch information
hugovk and AlexWaygood committed Nov 24, 2023
1 parent f5ee878 commit 74db402
Show file tree
Hide file tree
Showing 14 changed files with 158 additions and 40 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Lint

on: [push, pull_request, workflow_dispatch]

env:
FORCE_COLOR: 1

permissions:
contents: read

jobs:
lint:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: "3.x"
- uses: pre-commit/action@v3.0.0
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
# when adding new versions, update the one used to test
# when adding new versions, update the one used to test
# friend projects below to the latest one
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
os: [ubuntu-latest, macos-latest, windows-latest]
Expand Down
48 changes: 48 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.6
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]

- repo: https://github.com/psf/black-pre-commit-mirror
rev: 23.11.0
hooks:
- id: black

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-case-conflict
- id: check-merge-conflict
- id: check-toml
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
exclude: tests/fixtures/xfail/missing-newline-at-end-of-file.rst
- id: trailing-whitespace
exclude: tests/fixtures/xfail/trailing-whitespaces.rst

- repo: https://github.com/tox-dev/pyproject-fmt
rev: 1.5.1
hooks:
- id: pyproject-fmt
additional_dependencies: [tox]

- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.15
hooks:
- id: validate-pyproject

- repo: https://github.com/tox-dev/tox-ini-fmt
rev: 1.3.1
hooks:
- id: tox-ini-fmt

- repo: meta
hooks:
- id: check-hooks-apply
- id: check-useless-excludes

ci:
autoupdate_schedule: quarterly
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ $ docutils --writer=pseudoxml tests/fixtures/xpass/role-in-code-sample.rst

1. Make sure that the [CI tests pass](https://github.com/sphinx-contrib/sphinx-lint/actions)
and optionally double-check locally with "friends projects" by running:

sh download-more-tests.sh
python -m pytest
2. Go on the [Releases page](https://github.com/sphinx-contrib/sphinx-lint/releases)
Expand Down
53 changes: 39 additions & 14 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,36 @@ authors = [
{name = "Georg Brandl", email = "georg@python.org"},
{name = "Julien Palard", email = "julien@palard.fr"},
]
requires-python = ">= 3.8"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Topic :: Documentation :: Sphinx",
"Intended Audience :: Developers",
"License :: OSI Approved :: Python Software Foundation License",
"Natural Language :: English",
"Programming Language :: Python :: 3",
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: Python Software Foundation License",
"Natural Language :: English",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Documentation :: Sphinx",
]
dynamic = [
"version",
]
requires-python = ">= 3.8"
dependencies = [
"regex",
"polib",
"polib",
"regex",
]
dynamic = ["version"]


[project.optional-dependencies]
tests = [
"pytest",
"pytest-cov",
]
[project.urls]
Repository = "https://github.com/sphinx-contrib/sphinx-lint"
Changelog = "https://github.com/sphinx-contrib/sphinx-lint/releases"

Repository = "https://github.com/sphinx-contrib/sphinx-lint"
[project.scripts]
sphinx-lint = "sphinxlint.cli:main"

Expand All @@ -50,5 +55,25 @@ local_scheme = "no-local-version"

[tool.black]

[tool.ruff]
select = [
"E", # pycodestyle errors
"F", # pyflakes errors
"I", # isort
"ISC", # flake8-implicit-str-concat
"PGH", # pygrep-hooks
"RUF100", # unused noqa (yesqa)
"UP", # pyupgrade
"W", # pycodestyle warnings
"YTT", # flake8-2020
]
extend-ignore = [
"E203", # Whitespace before ':'
"E221", # Multiple spaces before operator
"E226", # Missing whitespace around arithmetic operator
"E241", # Multiple spaces after ','
"UP038", # makes code slower and more verbose
]

[tool.pylint.variables]
callbacks = ["check_"]
36 changes: 27 additions & 9 deletions sphinxlint/checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
paragraphs,
)


all_checkers = {}


Expand Down Expand Up @@ -60,7 +59,10 @@ def check_missing_backtick_after_role(file, lines, options=None):
error = rst.ROLE_MISSING_CLOSING_BACKTICK_RE.search(paragraph)
if error:
error_offset = paragraph[: error.start()].count("\n")
yield paragraph_lno + error_offset, f"role missing closing backtick: {error.group(0)!r}"
yield (
paragraph_lno + error_offset,
f"role missing closing backtick: {error.group(0)!r}",
)


_RST_ROLE_RE = re.compile("``.+?``(?!`).", flags=re.DOTALL)
Expand Down Expand Up @@ -129,8 +131,12 @@ def check_default_role(file, lines, options=None):
before_match = line[: match.start()]
after_match = line[match.end() :]
stripped_line = line.strip()
if (stripped_line.startswith("|") and stripped_line.endswith("|") and
stripped_line.count("|") >= 4 and "|" in match.group(0)):
if (
stripped_line.startswith("|")
and stripped_line.endswith("|")
and stripped_line.count("|") >= 4
and "|" in match.group(0)
):
return # we don't handle tables yet.
if _ends_with_role_tag(before_match):
# It's not a default role: it ends with a tag.
Expand All @@ -141,7 +147,10 @@ def check_default_role(file, lines, options=None):
if match.group(0).startswith("``") and match.group(0).endswith("``"):
# It's not a default role: it's an inline literal.
continue
yield lno, "default role used (hint: for inline literals, use double backticks)"
yield (
lno,
"default role used (hint: for inline literals, use double backticks)",
)


@checker(".rst", ".po")
Expand Down Expand Up @@ -287,7 +296,10 @@ def check_role_with_double_backticks(file, lines, options=None):
before = paragraph[: inline_literal.start()]
if _ends_with_role_tag(before):
error_offset = paragraph[: inline_literal.start()].count("\n")
yield paragraph_lno + error_offset, "role use a single backtick, double backtick found."
yield (
paragraph_lno + error_offset,
"role use a single backtick, double backtick found.",
)
paragraph = (
paragraph[: inline_literal.start()] + paragraph[inline_literal.end() :]
)
Expand All @@ -308,9 +320,15 @@ def check_missing_space_before_role(file, lines, options=None):
if match:
error_offset = paragraph[: match.start()].count("\n")
if looks_like_glued(match):
yield paragraph_lno + error_offset, f"missing space before role ({match.group(0)})."
yield (
paragraph_lno + error_offset,
f"missing space before role ({match.group(0)}).",
)
else:
yield paragraph_lno + error_offset, f"role missing opening tag colon ({match.group(0)})."
yield (
paragraph_lno + error_offset,
f"role missing opening tag colon ({match.group(0)}).",
)


@checker(".rst", ".po")
Expand Down Expand Up @@ -494,4 +512,4 @@ def check_dangling_hyphen(file, lines, options):
for lno, line in enumerate(lines):
stripped_line = line.rstrip("\n")
if _has_dangling_hyphen(stripped_line):
yield lno + 1, f"Line ends with dangling hyphen"
yield lno + 1, "Line ends with dangling hyphen"
6 changes: 4 additions & 2 deletions sphinxlint/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ def __call__(self, parser, namespace, values, option_string=None):
sort_fields.append(SortField[field_name.upper()])
except KeyError:
raise ValueError(
f"Unsupported sort field: {field_name}, supported values are {SortField.as_supported_options()}"
f"Unsupported sort field: {field_name}, "
f"supported values are {SortField.as_supported_options()}"
) from None
setattr(namespace, self.dest, sort_fields)

Expand Down Expand Up @@ -85,7 +86,8 @@ def job_count(values):
"-d",
"--disable",
action=DisableAction,
help='comma-separated list of checks to disable. Give "all" to disable them all. '
help="comma-separated list of checks to disable. "
'Give "all" to disable them all. '
"Can be used in conjunction with --enable (it's evaluated left-to-right). "
'"--disable all --enable trailing-whitespace" can be used to enable a '
"single check.",
Expand Down
6 changes: 1 addition & 5 deletions sphinxlint/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

from sphinxlint import rst


PER_FILE_CACHES = []


Expand Down Expand Up @@ -187,10 +186,7 @@ def hide_non_rst_blocks(lines, hidden_block_cb=None):
in_literal = len(_ZERO_OR_MORE_SPACES_RE.match(line)[0])
block_line_start = lineno
assert not excluded_lines
if (
type_of_explicit_markup(line) == "comment"
and _COMMENT_RE.search(line)
):
if type_of_explicit_markup(line) == "comment" and _COMMENT_RE.search(line):
line = "\n"
output.append(line)
if excluded_lines and hidden_block_cb:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_default_role_re.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ def test_shall_not_pass():
assert not rst.INTERPRETED_TEXT_RE.search("``")
assert not rst.INTERPRETED_TEXT_RE.search("2 * x a ** b (* BOM32_* ` `` _ __ |")
assert not rst.INTERPRETED_TEXT_RE.search(
""""`" '|' (`) [`] {`} <`> ‘`’ ‚`‘ ‘`‚ ’`’ ‚`’ “`” „`“ “`„ ”`” „`” »`« ›`‹ «`» »`» ›`›"""
""""`" '|' (`) [`] {`} <`> ‘`’ ‚`‘ ‘`‚ ’`’ ‚`’ “`” „`“ “`„ ”`” „`” »`« ›`‹ «`» »`» ›`›""" # noqa: E501
)
2 changes: 1 addition & 1 deletion tests/test_enable_disable.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from random import choice
import re
from random import choice

from sphinxlint.cli import main

Expand Down
1 change: 0 additions & 1 deletion tests/test_filter_out_literal.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from sphinxlint.utils import hide_non_rst_blocks


LITERAL = r"""
Hide non-RST Blocks
===================
Expand Down
7 changes: 4 additions & 3 deletions tests/test_sphinxlint.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from pathlib import Path

from sphinxlint.utils import paragraphs

import pytest

from sphinxlint.cli import main
from sphinxlint.utils import paragraphs

FIXTURE_DIR = Path(__file__).resolve().parent / "fixtures"

Expand Down Expand Up @@ -64,7 +63,9 @@ def test_sphinxlint_shall_not_pass(file, expected_errors, capsys):
assert expected_error in err
number_of_expected_errors = len(expected_errors)
number_of_reported_errors = len(err.splitlines())
assert number_of_expected_errors == number_of_reported_errors, f"{number_of_reported_errors=}, {err=}"
assert (
number_of_expected_errors == number_of_reported_errors
), f"{number_of_reported_errors=}, {err=}"


@pytest.mark.parametrize("file", [str(FIXTURE_DIR / "paragraphs.rst")])
Expand Down
3 changes: 1 addition & 2 deletions tests/test_xpass_friends.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
This is useful to avoid a sphinx-lint release to break many CIs.
"""

from pathlib import Path
import shlex
from pathlib import Path

import pytest

from sphinxlint.cli import main


FIXTURE_DIR = Path(__file__).resolve().parent / "fixtures"


Expand Down
10 changes: 10 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
requires =
tox>=4.2
env_list =
lint
py{py3, 313, 312, 311, 310, 39, 38}

[testenv]
Expand All @@ -17,3 +18,12 @@ commands =
--cov-report term \
--cov-report xml \
{posargs}

[testenv:lint]
skip_install = true
deps =
pre-commit
pass_env =
PRE_COMMIT_COLOR
commands =
pre-commit run --all-files --show-diff-on-failure

0 comments on commit 74db402

Please sign in to comment.