stouputils.check.rules module#

The rules stouputils check knows, the settings a project tunes them with, and the violations they report.

RULES: dict[str, str] = {'banned-characters': 'Long dashes, ellipsis, multiplication sign, arrows, curly quotes and box-drawing comment banners', 'constant-comment': 'A module constant documented by a trailing comment instead of a docstring below it', 'examples-header': 'An ``Examples:`` header above doctests, which ``>>>`` already marks', 'final-newlines': 'A file not ending with the configured number of newline characters', 'initial-newlines': 'A file not starting with the configured number of newline characters', 'long-comment': 'A block of consecutive comment lines over the limit', 'long-docstring': 'A function or class docstring over the line limit', 'module-docstring-position': 'A module docstring below line 1', 'space-alignment': 'Python code aligned with a tab after the first character instead of spaces', 'split-span': 'Inline code or a quote opened on one line of a comment or docstring and closed on another', 'stranded-fragment': 'A line break in a comment or docstring leaving a few words of a clause alone', 'syntax-error': 'A Python file the tokenizer or the parser rejects', 'tab-indentation': 'Python code indented with a space instead of tabs', 'typed-argument': 'An ``Args:`` entry repeating the type the signature carries', 'unused-ignore': 'A suppression comment naming no rule, an unknown one, or one it does not silence'}[source]#

Every rule by the name ignore takes, with what it reports.

class Violation(
line: int,
rule: str,
message: str,
end_line: int | None = None,
)[source]#

Bases: object

One rule broken at one line, or on every checked line of a range.

line: int[source]#
rule: str[source]#
message: str[source]#
property span: str[source]#

The line, or the range of lines, as written in a report.

>>> Violation(3, "long-comment", "").span, Violation(3, "tab-indentation", "", end_line=9).span
('3', '3-9')
class Suppression(
line: int,
rules: tuple[str, ...],
lines: range | None,
)[source]#

Bases: object

Rules a # stp: ignore[...] or # stouputils: ignore[...] comment silences, and on which lines.

>>> suppression = Suppression(line=4, rules=("long-docstring",), lines=range(2, 5))
>>> suppression.covers(Violation(2, "long-docstring", "")), suppression.covers(Violation(2, "long-comment", ""))
(True, False)
>>> Suppression(line=1, rules=("long-comment",), lines=None).covers(Violation(90, "long-comment", ""))
True
line: int[source]#

Line of the comment, where a rule it does not use is reported.

rules: tuple[str, ...][source]#
lines: range | None[source]#

Lines whose violations it silences, None for the whole file.

covers(
violation: Violation,
) → bool[source]#

Whether this comment silences the violation.

static unused(
suppressions: Iterable[Suppression],
violations: Collection[Violation],
) → Iterator[Violation][source]#

An unused-ignore for every rule a comment names without silencing anything.

>>> found = [Violation(3, "long-docstring", "")]
>>> stale = Suppression(line=3, rules=("long-docstring", "long-comment", "tabs"), lines=range(1, 4))
>>> [v.message for v in Suppression.unused([stale], found)]
["'long-comment' silences nothing here", "unknown rule 'tabs'"]
>>> [v.message for v in Suppression.unused([Suppression(line=1, rules=(), lines=None)], found)]
['names no rule, write ignore[rule-name]']
class CheckConfig(
ignore: Collection[str] = (),
per_file_ignores: dict[str,
list[str]]=<factory>,
root: Path = PosixPath('.'),
final_newlines: dict[str,
int]=<factory>,
initial_newlines: dict[str,
int]=<factory>,
docstring_max_lines: int = 15,
comment_max_lines: int = 2,
fragment_max_words: int = 3,
)[source]#

Bases: object

Settings of the [tool.stouputils.check] table of a pyproject.toml, keys spelled with dashes.

>>> CheckConfig(ignore=["tab-indentation", "space-alignment"]).final_newlines
{'.py': 2, '.json': 1, '.md': 2, '.yml': 2, '.yaml': 2}
>>> CheckConfig().initial_newlines
{'.md': 1, '.yml': 1, '.yaml': 1, '.mcfunction': 1}
>>> CheckConfig(per_file_ignores={"*.json": ["tabs"]})
Traceback (most recent call last):
        ...
ValueError: Unknown rules in ignore: ['tabs'], the known ones are banned-characters, ...
ignore: Collection[str] = ()[source]#

Rules left unchecked, by their name in RULES.

per_file_ignores: dict[str, list[str]][source]#

Rules left unchecked in the files a glob matches, relative to root, or anywhere when it holds no slash.

root: Path = PosixPath('.')[source]#

Directory of the pyproject.toml the settings come from, which per_file_ignores patterns are relative to.

final_newlines: dict[str, int][source]#

Exact number of newline characters a file ends with, by suffix, replacing the defaults as a whole.

initial_newlines: dict[str, int][source]#

Exact number of newline characters a file starts with, by suffix, replacing the defaults as a whole.

docstring_max_lines: int = 15[source]#

Longest docstring of a function or a class, doctests included.

comment_max_lines: int = 2[source]#

Longest block of consecutive comment lines.

fragment_max_words: int = 3[source]#

Most words a clause cut by a line break may leave alone on one side of it.

ignored_rules(path: Path) → set[str][source]#

Rules left unchecked in one file: the global ones and those of every per_file_ignores pattern it matches.

>>> patterns = {"tests/**": ["final-newlines"], "*.json": ["banned-characters"]}
>>> config = CheckConfig(ignore=["long-comment"], per_file_ignores=patterns)
>>> sorted(config.ignored_rules(Path("tests/fixture/data.json")))
['banned-characters', 'final-newlines', 'long-comment']
>>> sorted(config.ignored_rules(Path("src/tests.py")))
['long-comment']
classmethod for_directory(
directory: Path,
) → Self[source]#

Settings of the nearest pyproject.toml holding the table, looking upward like ruff, defaults without one.