Skip to content
Open
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
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,8 +416,6 @@ Will not show up if no `.readthedocs.yml`/`.readthedocs.yaml` file is present.
- [`RF002`](https://learn.scientific-python.org/development/guides/style#RF002): Target version must be set
- [`RF003`](https://learn.scientific-python.org/development/guides/style#RF003): src directory doesn't need to be specified anymore (0.6+)
- [`RF101`](https://learn.scientific-python.org/development/guides/style#RF101): Bugbear must be selected
- [`RF102`](https://learn.scientific-python.org/development/guides/style#RF102): isort must be selected
- [`RF103`](https://learn.scientific-python.org/development/guides/style#RF103): pyupgrade must be selected
- `RF201`: Avoid using deprecated config settings
- `RF202`: Use (new) lint config section

Expand Down
43 changes: 16 additions & 27 deletions docs/guides/style.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,41 +273,31 @@ won't tell you what or why it fixed things.
:::{tab-item} Simple config

```ini
[tool.ruff.lint]
extend-select = [
"B", # flake8-bugbear
"I", # isort
"RUF", # Ruff-specific
"UP", # pyupgrade
]
[tool.ruff]
show-fixes = true
```

:::
:::{tab-item} Full config

```ini
[tool.ruff.lint]
extend-select = [
[tool.ruff]
show-fixes = true
lint.extend-select = [
"ARG", # flake8-unused-arguments
"B", # flake8-bugbear
"BLE", # flake8-blind-except
"C4", # flake8-comprehensions
"DTZ", # flake8-datetimez
"EM", # flake8-errmsg
"EXE", # flake8-executable
"FA", # flake8-future-annotations
"FLY", # flynt
"FURB", # refurb
"G", # flake8-logging-format
"I", # isort
"ICN", # flake8-import-conventions
"ISC", # flake8-implicit-str-concat
"LOG", # flake8-logging
"NPY", # NumPy specific rules
"PD", # pandas-vet
"PERF", # perflint
"PGH", # pygrep-hooks
"PIE", # flake8-pie
"PL", # pylint
"PT", # flake8-pytest-style
"PTH", # flake8-use-pathlib
Expand All @@ -323,13 +313,12 @@ extend-select = [
"TC", # flake8-type-checking
"TRY", # tryceratops
"UP", # pyupgrade
"YTT", # flake8-2020
]
ignore = [
lint.ignore = [
"PLR09", # Too many <...>
"PLR2004", # Magic value used in comparison
]
typing-modules = ["mypackage._compat.typing"]
lint.typing-modules = ["mypackage._compat.typing"]
[tool.ruff.lint.per-file-ignores]
"tests/**" = ["T20"]
Expand Down Expand Up @@ -372,8 +361,7 @@ typing-modules = ["mypackage._compat.typing"]
Ruff [provides dozens of rule sets](https://beta.ruff.rs/docs/rules/); you can
select what you want from these. Like Flake8, plugins match by whole letter
sequences (with the special exception of pylint's "PL" shortcut), then you can
also include leading or whole error codes. Codes starting with 9 must be
selected explicitly, with at least the letters followed by a 9. You can also
also include leading or whole error codes. You can also
ignore certain error codes via `ignore`. You can also set codes per paths to
ignore in `per-file-ignores`. If you don't like certain auto-fixes, you can
disable auto-fixing for specific error codes via `unfixable`.
Expand All @@ -394,7 +382,7 @@ If you don't use a `[project]` table (older setuptools or Poetry), then you
should also set:

```ini
target-version = "py39"
target-version = "py310"
```

This selects the minimum version you want to target (primarily for `"UP"` and
Expand All @@ -407,11 +395,10 @@ Here are some good error codes to enable on most (but not all!) projects:
stood the test of time. Not required if you use `extend-select` (`W` not
needed if you use a formatter)
- `B`: This finds patterns that are very bug-prone. {rr}`RF101`
- `I`: This sorts your includes. There are multiple benefits, such as smaller
diffs, fewer conflicts, a way to auto-inject `__future__` imports, and easier
for readers to tell what's built-in, third-party, and local. It has a lot of
configuration options, but defaults to a Black-compatible style.
{rr}`RF102`
- `I`: This sorts your includes (on by default since Ruff 0.16). Select this
group only if you also want `I002`, which can auto-inject imports, such as
`from __future__ import annotations`; it needs the
`lint.isort.required-imports` setting.
- `ARG`: This looks for unused arguments. You might need to `# noqa: ARG001`
occasionally, but it's overall pretty useful.
- `C4`: This looks for places that could use comprehensions, and can autofix
Expand All @@ -433,7 +420,9 @@ Here are some good error codes to enable on most (but not all!) projects:
- `RUF`: Codes specific to Ruff, including removing noqa's that aren't used.
- `T20`: Disallow `print` in your code (built on the assumption that it's a
common debugging tool).
- `UP`: Upgrade old Python syntax to your `target-version`. {rr}`RF103`
- `UP`: Upgrade old Python syntax to your `target-version`. Ruff 0.16 turns most
of these on by default; select the group to also get the few remaining rules,
such as `UP015` (redundant open modes).
- `FURB`: From the refurb tool, a collection of helpful cleanups.
- `PYI`: Typing related checks

Expand Down
14 changes: 0 additions & 14 deletions src/sp_repo_review/checks/ruff.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,20 +157,6 @@ class RF101(RF1xx):
name = "flake8-bugbear"


class RF102(RF1xx):
"isort must be selected"

code = "I"
name = "isort"


class RF103(RF1xx):
"pyupgrade must be selected"

code = "UP"
name = "pyupgrade"


class RF2xxMixin(Protocol):
@staticmethod
def iter_check(ruff: dict[str, Any]) -> Generator[str, None, None]: ...
Expand Down
32 changes: 28 additions & 4 deletions src/sp_repo_review/ruff_checks/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
select_info = json.load(f)
LIBS = frozenset(select_info["libs"])
SPECIALTY = frozenset(r["name"] for r in select_info["specialty"])
# Selected by Ruff without configuration (0.16+)
DEFAULTS = {r["name"]: r["reason"] for r in select_info["defaults"]}

with RESOURCE_DIR.joinpath("ignore.json").open(encoding="utf-8") as f:
IGNORE_INFO = json.load(f)
Expand Down Expand Up @@ -105,6 +107,7 @@ def _print_output_rich(
libs_items: dict[str, str],
spec_items: dict[str, str],
unselected_items: dict[str, str],
default_items: dict[str, str],
) -> None:
"""Print rich formatted output."""
import rich.columns
Expand All @@ -128,6 +131,10 @@ def _print_output_rich(
uns = "\n".join(_print_each_rich(unselected_items))

rich.print(rich.columns.Columns([panel_sel, panel_lib, panel_spec]))
if default_items:
rich.print("[yellow]Selected, but on by default [dim](safe to remove)")
for item in _print_each_rich(default_items):
rich.print(item)
if uns:
rich.print("[red]Unselected [dim](copy and paste ready)")
rich.print(uns)
Expand All @@ -138,12 +145,18 @@ def _print_output_plain(
libs_items: dict[str, str],
spec_items: dict[str, str],
unselected_items: dict[str, str],
default_items: dict[str, str],
) -> None:
"""Print plain formatted output."""
print("Selected:")
for item in _print_each_plain(selected_items):
print(item)

if default_items:
print("\nSelected, but on by default (safe to remove):")
for item in _print_each_plain(default_items):
print(item)

if libs_items:
print("\nLibrary specific:")
for item in _print_each_plain(libs_items):
Expand Down Expand Up @@ -236,18 +249,29 @@ def process_dir(path: Path, format: str = "auto") -> None:
_handle_all_selected(fmt, ruff_config)
return

selected_items = {k: v for k, v in LINT_INFO.items() if k in selected}
all_uns_items = {k: v for k, v in LINT_INFO.items() if k not in selected}
default_items = {
k: DEFAULTS[k] for k in LINT_INFO if k in selected & DEFAULTS.keys()
}
selected_items = {
k: v for k, v in LINT_INFO.items() if k in selected and k not in DEFAULTS
}
all_uns_items = {
k: v for k, v in LINT_INFO.items() if k not in selected and k not in DEFAULTS
}
unselected_items = {
k: v for k, v in all_uns_items.items() if k not in LIBS | SPECIALTY
}
libs_items = {k: v for k, v in all_uns_items.items() if k in LIBS}
spec_items = {k: v for k, v in all_uns_items.items() if k in SPECIALTY}

if fmt == "rich":
_print_output_rich(selected_items, libs_items, spec_items, unselected_items)
_print_output_rich(
selected_items, libs_items, spec_items, unselected_items, default_items
)
else:
_print_output_plain(selected_items, libs_items, spec_items, unselected_items)
_print_output_plain(
selected_items, libs_items, spec_items, unselected_items, default_items
)


def main() -> None:
Expand Down
12 changes: 11 additions & 1 deletion src/sp_repo_review/ruff_checks/select.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
{
"libs": ["AIR", "ASYNC", "DJ", "FAST", "INT", "NPY", "PD"],
"libs": ["AIR", "ASYNC", "DJ", "FAST", "NPY", "PD"],
"defaults": [
{ "name": "BLE", "reason": "on by default" },
{ "name": "DTZ", "reason": "on by default" },
{ "name": "FA", "reason": "on by default" },
{ "name": "FLY", "reason": "on by default" },
{ "name": "I", "reason": "on by default, except I002, which needs config" },
{ "name": "INT", "reason": "on by default" },
{ "name": "PIE", "reason": "on by default" },
{ "name": "YTT", "reason": "on by default" }
],
"specialty": [
{ "name": "CPY", "reason": "preview only" },
{ "name": "A", "reason": "Naming related" },
Expand Down
22 changes: 1 addition & 21 deletions tests/test_ruff.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,27 +68,7 @@ def test_rf101_selected():


def test_rf101_missing():
assert not compute_check("RF101", ruff={"lint": {"select": ["I"]}}).result


def test_rf102_selected():
assert compute_check("RF102", ruff={"lint": {"select": ["I"]}}).result


def test_rf102_missing():
assert not compute_check("RF102", ruff={"lint": {"select": ["UP"]}}).result


def test_rf103_selected():
assert compute_check("RF103", ruff={"lint": {"select": ["UP"]}}).result


def test_rf103_selected_by_all():
assert compute_check("RF103", ruff={"lint": {"select": ["ALL"]}}).result


def test_rf103_missing():
assert not compute_check("RF103", ruff={"lint": {"select": ["B"]}}).result
assert not compute_check("RF101", ruff={"lint": {"select": ["UP"]}}).result


def test_rf201_no_deprecated_keys():
Expand Down
19 changes: 19 additions & 0 deletions tests/test_ruff_checks_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,22 @@ def test_plain_format_has_quotes_and_comma(monkeypatch, tmp_path, capsys):
ruff_checks.process_dir(tmp_path, format="plain")
captured = capsys.readouterr()
assert '"A",' in captured.out


def test_plain_format_default_rules(monkeypatch, tmp_path, capsys):
"""Rules Ruff selects itself are reported as removable, not as missing."""
monkeypatch.setattr(ruff_checks, "ruff", lambda *_a, **_k: {"tool": "ruff"})
monkeypatch.setattr(ruff_checks, "get_rule_selection", lambda *_a, **_k: {"A", "B"})
monkeypatch.setattr(ruff_checks, "LINT_INFO", {"A": "Rule A", "B": "Rule B"})
monkeypatch.setattr(ruff_checks, "LIBS", frozenset())
monkeypatch.setattr(ruff_checks, "SPECIALTY", frozenset())
monkeypatch.setattr(
ruff_checks, "DEFAULTS", {"A": "on by default", "C": "likewise"}
)

ruff_checks.process_dir(tmp_path, format="plain")
captured = capsys.readouterr()
selected, defaults = captured.out.split("Selected, but on by default")
assert '"B",' in selected
assert '"A",' in defaults
assert '"C",' not in captured.out
7 changes: 0 additions & 7 deletions {{cookiecutter.project_name}}/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -288,24 +288,18 @@ show-fixes = true
extend-select = [
"ARG", # flake8-unused-arguments
"B", # flake8-bugbear
"BLE", # flake8-blind-except
"C4", # flake8-comprehensions
"DTZ", # flake8-datetimez
"EM", # flake8-errmsg
"EXE", # flake8-executable
"FA", # flake8-future-annotations
"FLY", # flynt
"FURB", # refurb
"G", # flake8-logging-format
"I", # isort
"ICN", # flake8-import-conventions
"ISC", # flake8-implicit-str-concat
"LOG", # flake8-logging
"NPY", # NumPy specific rules
"PD", # pandas-vet
"PERF", # perflint
"PGH", # pygrep-hooks
"PIE", # flake8-pie
"PL", # pylint
"PT", # flake8-pytest-style
"PTH", # flake8-use-pathlib
Expand All @@ -321,7 +315,6 @@ extend-select = [
"TC", # flake8-type-checking
"TRY", # tryceratops
"UP", # pyupgrade
"YTT", # flake8-2020
]
ignore = [
"PLR09", # Too many <...>
Expand Down
Loading