Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly we could enable this setting for black. I don't much like the magic trailing comma, and I think most of the current black maintainers don't really like it much either. But I also don't care that much, if you disagree :)

Suggested change
[tool.black]
[tool.black]
skip-magic-trailing-comma = true

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't mind too much. This is the only change it would make:

diff --git a/sphinxlint/cli.py b/sphinxlint/cli.py
index c0a5638..0cab6d4 100644
--- a/sphinxlint/cli.py
+++ b/sphinxlint/cli.py
@@ -76,11 +76,7 @@ def parse_args(argv=None):
         help="verbose (print all checked file names)",
     )
     parser.add_argument(
-        "-i",
-        "--ignore",
-        action="append",
-        help="ignore subdir or file path",
-        default=[],
+        "-i", "--ignore", action="append", help="ignore subdir or file path", default=[]
     )
     parser.add_argument(
         "-d",

I can see there could be value keeping it, if we wanted to make sure each parser.add_argument block had each arg on its own line. 🤷

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd personally vote for my suggestion above^, but no strong opinion either way. Feel free to merge!


[tool.ruff]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a fan of autofixes, so we could add fix = true for local use. But we can leave adding that to a followup PR; we'd want to check exactly which rules we're okay with being autofixed etc.

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