Skip to content

Commit 3bc0d13

Browse files
feat: implement check registry (#1396)
Implement a fully typed check registry and specification system. - Port all checks to new specification system - Add register exports to each module - Add registry - Refactor tests - Backport `itertools.batched` Benchmarks: ``` On main Benchmark 1: uv run proselint --demo Time (mean ± σ): 1.090 s ± 0.265 s [User: 1.041 s, System: 0.033 s] Range (min … max): 1.001 s … 1.843 s 10 runs On #1396 Benchmark 1: uv run proselint --demo Time (mean ± σ): 432.2 ms ± 2.0 ms [User: 405.4 ms, System: 24.8 ms] Range (min … max): 429.5 ms … 435.8 ms 10 runs ``` --------- Co-authored-by: drainpixie <121581793+drainpixie@users.noreply.github.com>
1 parent 0e1feba commit 3bc0d13

File tree

209 files changed

+6795
-7380
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

209 files changed

+6795
-7380
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ corpora/*
1818
dist/
1919
*.pstore
2020
.pre-commit-config.yaml
21+
.hypothesis

alejandra.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
indentation = "Tabs"

flake.lock

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 85 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,93 @@
11
# TODO: use uv2nix
22
{
3-
inputs = {
4-
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
5-
hooks = {
6-
url = "github:cachix/git-hooks.nix";
7-
inputs.nixpkgs.follows = "nixpkgs";
8-
};
9-
pyproject = {
10-
url = "github:pyproject-nix/pyproject.nix";
11-
inputs.nixpkgs.follows = "nixpkgs";
12-
};
13-
};
3+
inputs = {
4+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
5+
hooks = {
6+
url = "github:cachix/git-hooks.nix";
7+
inputs.nixpkgs.follows = "nixpkgs";
8+
};
9+
pyproject = {
10+
url = "github:pyproject-nix/pyproject.nix";
11+
inputs.nixpkgs.follows = "nixpkgs";
12+
};
13+
};
1414

15-
outputs = {
16-
self,
17-
hooks,
18-
nixpkgs,
19-
pyproject,
20-
...
21-
}: let
22-
forAllSystems = function:
23-
nixpkgs.lib.genAttrs [
24-
"x86_64-linux"
25-
"aarch64-linux"
26-
"x86_64-darwin"
27-
"aarch64-darwin"
28-
] (system: function system nixpkgs.legacyPackages.${system});
29-
project = pyproject.lib.project.loadPyproject {projectRoot = ./.;};
30-
in {
31-
devShells = forAllSystems (system: pkgs: let
32-
python = pkgs.python312;
33-
arg = project.renderers.withPackages {inherit python;};
34-
pyenv = python.withPackages arg;
35-
check = self.checks.${system}.pre-commit-check;
36-
in {
37-
default = pkgs.mkShell {
38-
inherit (check) shellHook;
15+
outputs = {
16+
self,
17+
hooks,
18+
nixpkgs,
19+
pyproject,
20+
...
21+
}: let
22+
forAllSystems = function:
23+
nixpkgs.lib.genAttrs [
24+
"x86_64-linux"
25+
"aarch64-linux"
26+
"x86_64-darwin"
27+
"aarch64-darwin"
28+
] (system: function system nixpkgs.legacyPackages.${system});
29+
project = pyproject.lib.project.loadPyproject {projectRoot = ./.;};
30+
in {
31+
devShells =
32+
forAllSystems (system: pkgs: let
33+
python = pkgs.python312;
34+
arg = project.renderers.withPackages {inherit python;};
35+
pyenv = python.withPackages arg;
36+
check = self.checks.${system}.pre-commit-check;
37+
in {
38+
default =
39+
pkgs.mkShell {
40+
inherit (check) shellHook;
3941

40-
packages =
41-
check.enabledPackages
42-
++ [
43-
pyenv
44-
pkgs.uv
45-
pkgs.pyright
46-
pkgs.hyperfine
47-
];
48-
};
49-
});
42+
packages =
43+
check.enabledPackages
44+
++ [
45+
pyenv
46+
pkgs.uv
47+
];
48+
};
49+
});
5050

51-
packages = forAllSystems (system: pkgs: let
52-
python = pkgs.python312;
53-
attrs = project.renderers.buildPythonPackage {inherit python;};
54-
in {
55-
default = python.pkgs.buildPythonPackage attrs;
56-
});
51+
packages =
52+
forAllSystems (system: pkgs: let
53+
python = pkgs.python312;
54+
attrs = project.renderers.buildPythonPackage {inherit python;};
55+
in {
56+
default = python.pkgs.buildPythonPackage attrs;
57+
});
5758

58-
apps = forAllSystems (system: _: {
59-
default = {
60-
type = "app";
61-
program = "${self.packages.${system}.default}/bin/proselint";
62-
};
63-
});
59+
apps =
60+
forAllSystems (system: _: {
61+
default = {
62+
type = "app";
63+
program = "${self.packages.${system}.default}/bin/proselint";
64+
};
65+
});
6466

65-
checks = forAllSystems (system: _: {
66-
pre-commit-check = hooks.lib.${system}.run {
67-
src = ./.;
68-
hooks = {
69-
trim-trailing-whitespace.enable = true;
70-
end-of-file-fixer.enable = true;
71-
mixed-line-endings.enable = true;
72-
markdownlint.enable = true;
73-
ruff.enable = true;
74-
pyright.enable = true;
75-
convco.enable = true;
76-
alejandra.enable = true;
77-
statix = {
78-
enable = true;
79-
settings.ignore = ["/.direnv"];
80-
};
81-
};
82-
};
83-
});
84-
};
67+
checks =
68+
forAllSystems (system: pkgs: {
69+
pre-commit-check =
70+
hooks.lib.${system}.run {
71+
src = ./.;
72+
hooks = {
73+
trim-trailing-whitespace.enable = true;
74+
end-of-file-fixer.enable = true;
75+
mixed-line-endings.enable = true;
76+
markdownlint.enable = true;
77+
ruff.enable = true;
78+
pyright = {
79+
enable = true;
80+
package = pkgs.basedpyright;
81+
entry = "${pkgs.basedpyright}/bin/basedpyright";
82+
};
83+
convco.enable = true;
84+
alejandra.enable = true;
85+
statix = {
86+
enable = true;
87+
settings.ignore = ["/.direnv"];
88+
};
89+
};
90+
};
91+
});
92+
};
8593
}

proselint/backports.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Backport features for older versions of Python."""
2+
3+
from __future__ import annotations
4+
5+
from itertools import islice
6+
from sys import version_info
7+
from typing import TYPE_CHECKING, TypeVar
8+
9+
if TYPE_CHECKING:
10+
from collections.abc import Generator, Iterable
11+
12+
T = TypeVar("T")
13+
14+
if version_info >= (3, 13):
15+
from itertools import batched
16+
else:
17+
def batched(
18+
iterable: Iterable[T], n: int, *, strict: bool = False
19+
) -> Generator[tuple[T, ...]]:
20+
"""Batch data from the `iterable` into `n`-tuples."""
21+
if n < 0:
22+
raise ValueError("n must be at least one")
23+
iterator = iter(iterable)
24+
while batch := tuple(islice(iterator, n)):
25+
if strict and len(batch) != n:
26+
raise ValueError("batched(): incomplete batch")
27+
yield batch
28+
29+
30+
__all__ = ("batched",)

proselint/checks/__init__.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,33 @@
11
"""All the checks are organized into modules and places here."""
2+
3+
from proselint.registry import build_modules_register
4+
5+
__register__ = build_modules_register(
6+
(
7+
".annotations",
8+
".archaism",
9+
".cliches",
10+
".dates_times",
11+
".hedging",
12+
".industrial_language",
13+
".lexical_illusions",
14+
".malapropisms",
15+
".misc",
16+
".mixed_metaphors",
17+
".mondegreens",
18+
".needless_variants",
19+
".nonwords",
20+
".oxymorons",
21+
".psychology",
22+
".redundancy",
23+
".restricted",
24+
".skunked_terms",
25+
".social_awareness",
26+
".spelling",
27+
".terms",
28+
".typography",
29+
".uncomparables",
30+
".weasel_words",
31+
),
32+
"proselint.checks",
33+
)

proselint/checks/annotations.py

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
"""Annotation left in text.
1+
"""
2+
Annotation left in text.
23
34
---
45
layout: post
@@ -12,23 +13,23 @@
1213
Annotation left in text.
1314
1415
"""
15-
from proselint.tools import existence_check
16-
17-
18-
19-
def check(text):
20-
"""Check the text."""
21-
err = "annotations.misc"
22-
msg = "Annotation left in text."
23-
24-
annotations = [
25-
"FIXME",
26-
"FIX ME",
27-
"TODO",
28-
"todo",
29-
"ERASE THIS",
30-
"FIX THIS",
31-
]
3216

33-
return existence_check(
34-
text, annotations, err, msg, ignore_case=False, join=True)
17+
from proselint.registry.checks import Check, types
18+
19+
check = Check(
20+
check_type=types.Existence(
21+
items=(
22+
"FIXME",
23+
"FIX ME",
24+
"TODO",
25+
"todo",
26+
"ERASE THIS",
27+
"FIX THIS",
28+
)
29+
),
30+
path="annotations.misc",
31+
message="Annotation left in text.",
32+
ignore_case=False,
33+
)
34+
35+
__register__ = (check,)

0 commit comments

Comments
 (0)