From 4e87722865d5a5dfd8a478cd15bd9c48dea504e0 Mon Sep 17 00:00:00 2001 From: dosisod <39638017+dosisod@users.noreply.github.com> Date: Wed, 22 Feb 2023 23:43:02 -0800 Subject: [PATCH] Replace `flake8` with `ruff`, bump version (#212) --- .flake8 | 6 -- .github/workflows/ci.yml | 4 +- Makefile | 8 +- dev-requirements.txt | 7 +- pyproject.toml | 22 ++++- refurb/checks/flow/no_trailing_continue.py | 2 +- refurb/checks/flow/no_trailing_return.py | 2 +- .../checks/function/use_implicit_default.py | 2 +- refurb/checks/math/use_constants.py | 2 +- refurb/gen.py | 2 +- refurb/main.py | 4 +- refurb/settings.py | 3 +- refurb/visitor/visitor.py | 2 +- test/mypy_visitor.py | 7 +- test/test_arg_parsing.py | 86 +++++++++---------- test/test_check_formatting.py | 2 +- test/test_checks.py | 28 +++--- test/test_main.py | 2 +- test/test_visitor.py | 4 +- 19 files changed, 97 insertions(+), 98 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index f556a0e..0000000 --- a/.flake8 +++ /dev/null @@ -1,6 +0,0 @@ -[flake8] -exclude = .git,__pycache__,.venv,test/data* -per-file-ignores = - test/*:E501 - refurb/main.py:E501 -ignore = SIM102, W503 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb36c87..8f4fcdc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,8 +36,8 @@ jobs: - name: Pip install run: make install - - name: Run flake8 - run: make flake8 + - name: Run ruff + run: make ruff - name: Run mypy run: make mypy diff --git a/Makefile b/Makefile index a977964..83a3671 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -.PHONY: install flake8 mypy black isort test test-e2e refurb +.PHONY: install ruff mypy black isort test test-e2e refurb -all: flake8 mypy black isort test refurb +all: ruff mypy black isort test refurb install: pip install . @@ -9,8 +9,8 @@ install: install-local: pip install -e . -flake8: - flake8 +ruff: + ruff refurb test mypy: mypy refurb diff --git a/dev-requirements.txt b/dev-requirements.txt index dc1d331..a15462b 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -5,12 +5,8 @@ click==8.1.3 colorama==0.4.6 coverage==7.1.0 exceptiongroup==1.1.0 -flake8-bugbear==23.1.20 -flake8-simplify==0.19.3 -flake8==6.0.0 iniconfig==2.0.0 isort==5.12.0 -mccabe==0.7.0 mypy-extensions==1.0.0 mypy==0.991 packaging==23.0 @@ -18,8 +14,7 @@ pathspec==0.11.0 platformdirs==3.0.0 pluggy==1.0.0 py==1.11.0 -pycodestyle==2.10.0 -pyflakes==3.0.1 pyparsing==3.0.9 pytest-cov==4.0.0 pytest==7.2.1 +ruff==0.0.252 diff --git a/pyproject.toml b/pyproject.toml index 8a8f745..0eabe10 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "refurb" -version = "1.12.0" +version = "1.13.0" description = "A tool for refurbish and modernize Python codebases" authors = ["dosisod"] license = "GPL-3.0-only" @@ -22,7 +22,6 @@ tomli = {version = "^2.0.1", python = "<3.11"} [tool.poetry.dev-dependencies] black = "^22.6.0" -flake8 = "^5.0.4" isort = "^5.10.1" pytest = "^7.1.2" @@ -63,6 +62,25 @@ color = true [tool.pytest.ini_options] addopts = "--cov=refurb --cov-report=html --cov-report=term-missing --cov-fail-under=100" +[tool.ruff] +line-length = 80 + +extend-select = [ + "W", "N", "UP", "YTT", "S", "BLE", "B", + "C4", "DTZ", "ISC", "PIE", "PT", "RET", + "SIM", "PTH", "PLE", "RUF", "FBT", +] + +# TODO: fix RUF100 not playing well with refurb +# TODO: fix UP007 not working +extend-ignore = ["S101", "N813", "N818", "SIM102", "PT012", "RUF100", "F821", "B905", "FBT001", "UP007"] + +extend-exclude = ["test/data*"] + +[tool.ruff.per-file-ignores] +"test/*" = ["E501"] +"refurb/main.py" = ["E501"] + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" diff --git a/refurb/checks/flow/no_trailing_continue.py b/refurb/checks/flow/no_trailing_continue.py index 85aa91c..7b2ec99 100644 --- a/refurb/checks/flow/no_trailing_continue.py +++ b/refurb/checks/flow/no_trailing_continue.py @@ -1,5 +1,5 @@ +from collections.abc import Generator from dataclasses import dataclass -from typing import Generator from mypy.nodes import ( Block, diff --git a/refurb/checks/flow/no_trailing_return.py b/refurb/checks/flow/no_trailing_return.py index 597fbee..5866b36 100644 --- a/refurb/checks/flow/no_trailing_return.py +++ b/refurb/checks/flow/no_trailing_return.py @@ -1,5 +1,5 @@ +from collections.abc import Generator from dataclasses import dataclass -from typing import Generator from mypy.nodes import ( Block, diff --git a/refurb/checks/function/use_implicit_default.py b/refurb/checks/function/use_implicit_default.py index 7797be9..4aebe3a 100644 --- a/refurb/checks/function/use_implicit_default.py +++ b/refurb/checks/function/use_implicit_default.py @@ -1,5 +1,5 @@ +from collections.abc import Iterator from dataclasses import dataclass -from typing import Iterator from mypy.nodes import ( ArgKind, diff --git a/refurb/checks/math/use_constants.py b/refurb/checks/math/use_constants.py index 97b6ed5..1a545a1 100644 --- a/refurb/checks/math/use_constants.py +++ b/refurb/checks/math/use_constants.py @@ -44,7 +44,7 @@ def check(node: FloatExpr, errors: list[Error]) -> None: num = str(node.value) if len(num) <= 3: - return None + return for name, value in CONSTANTS.items(): if num.startswith(value): diff --git a/refurb/gen.py b/refurb/gen.py index be8af0f..90a1ecf 100644 --- a/refurb/gen.py +++ b/refurb/gen.py @@ -54,7 +54,7 @@ def fzf(data: list[str] | None, args: list[str]) -> str: } process = run( - ["fzf", "--height=20"] + args, + ["fzf", "--height=20", *args], env=env, stdout=PIPE, input=bytes("\n".join(data), "utf8") if data else None, diff --git a/refurb/main.py b/refurb/main.py index 20c8183..1bd8805 100644 --- a/refurb/main.py +++ b/refurb/main.py @@ -1,9 +1,9 @@ import re +from collections.abc import Sequence from functools import cache from importlib import metadata from io import StringIO from pathlib import Path -from typing import Sequence from mypy.build import build from mypy.errors import CompileError @@ -195,7 +195,7 @@ def sort_errors( def format_errors(errors: Sequence[Error | str], quiet: bool) -> str: - done = "\n".join((str(error) for error in errors)) + done = "\n".join(str(error) for error in errors) if not quiet and any(isinstance(error, Error) for error in errors): done += "\n\nRun `refurb --explain ERR` to further explain an error. Use `--quiet` to silence this message" diff --git a/refurb/settings.py b/refurb/settings.py index 4ea8394..f718d39 100644 --- a/refurb/settings.py +++ b/refurb/settings.py @@ -2,9 +2,10 @@ import re import sys +from collections.abc import Iterator from dataclasses import dataclass, field, replace from pathlib import Path -from typing import Any, Iterator +from typing import Any if sys.version_info >= (3, 11): import tomllib # pragma: no cover diff --git a/refurb/visitor/visitor.py b/refurb/visitor/visitor.py index d3bbf78..1265505 100644 --- a/refurb/visitor/visitor.py +++ b/refurb/visitor/visitor.py @@ -1,5 +1,5 @@ from collections import defaultdict -from typing import Callable +from collections.abc import Callable from mypy.nodes import CallExpr, Node from mypy.traverser import TraverserVisitor diff --git a/test/mypy_visitor.py b/test/mypy_visitor.py index 471fdfc..ab2cecc 100644 --- a/test/mypy_visitor.py +++ b/test/mypy_visitor.py @@ -32,13 +32,13 @@ import inspect import sys import typing -from collections.abc import Iterator +from collections.abc import Callable, Iterator from contextlib import contextmanager from dataclasses import dataclass from importlib.abc import PathEntryFinder from importlib.machinery import FileFinder from types import FunctionType -from typing import Any, Callable +from typing import Any import mypy.nodes import mypy.traverser @@ -183,5 +183,4 @@ def get_mypy_visitor_mapping() -> VisitorNodeTypeMap: namespace) """ with pure_python_mypy(): - mapping = _make_mappings(globalns=_globals) - return mapping + return _make_mappings(globalns=_globals) diff --git a/test/test_arg_parsing.py b/test/test_arg_parsing.py index 0aa185e..eb42097 100644 --- a/test/test_arg_parsing.py +++ b/test/test_arg_parsing.py @@ -54,14 +54,14 @@ def test_parse_version_args() -> None: def test_parse_ignore() -> None: got = parse_args(["--ignore", "FURB123", "--ignore", "321"]) - expected = Settings(ignore=set((ErrorCode(123), ErrorCode(321)))) + expected = Settings(ignore={ErrorCode(123), ErrorCode(321)}) assert got == expected def test_parse_ignore_category() -> None: got = parse_args(["--ignore", "#category"]) - expected = Settings(ignore=set((ErrorCategory("category"),))) + expected = Settings(ignore={ErrorCategory("category")}) assert got == expected @@ -75,14 +75,14 @@ def test_parse_ignore_check_missing_arg() -> None: def test_parse_enable() -> None: got = parse_args(["--enable", "FURB123", "--enable", "321"]) - expected = Settings(enable=set((ErrorCode(123), ErrorCode(321)))) + expected = Settings(enable={ErrorCode(123), ErrorCode(321)}) assert got == expected def test_parse_enable_category() -> None: got = parse_args(["--enable", "#category"]) - expected = Settings(enable=set((ErrorCategory("category"),))) + expected = Settings(enable={ErrorCategory("category")}) assert got == expected @@ -148,8 +148,8 @@ def test_parse_config_file() -> None: assert config == Settings( load=["some", "folders"], - ignore=set((ErrorCode(100), ErrorCode(101))), - enable=set((ErrorCode(111), ErrorCode(222))), + ignore={ErrorCode(100), ErrorCode(101)}, + enable={ErrorCode(111), ErrorCode(222)}, ) @@ -168,7 +168,7 @@ def test_merge_command_line_args_and_config_file() -> None: assert merged == Settings( files=["some_file.py"], load=["some", "folders"], - ignore=set((ErrorCode(100), ErrorCode(101))), + ignore={ErrorCode(100), ErrorCode(101)}, ) @@ -190,8 +190,8 @@ def test_command_line_args_merge_config_file() -> None: assert merged == Settings( load=["some", "folders", "x"], - ignore=set((ErrorCode(100), ErrorCode(101), ErrorCode(123))), - enable=set((ErrorCode(111), ErrorCode(222), ErrorCode(200))), + ignore={ErrorCode(100), ErrorCode(101), ErrorCode(123)}, + enable={ErrorCode(111), ErrorCode(222), ErrorCode(200)}, quiet=True, ) @@ -211,9 +211,7 @@ def test_config_missing_load_option_is_allowed() -> None: ignore = [123] """ - assert parse_config_file(contents) == Settings( - ignore=set((ErrorCode(123),)) - ) + assert parse_config_file(contents) == Settings(ignore={ErrorCode(123)}) def test_parse_error_codes() -> None: @@ -231,7 +229,9 @@ def test_parse_error_codes() -> None: for input, output in tests.items(): if output == ValueError: - with pytest.raises(ValueError): + msg = "must be in form FURB123 or 123" + + with pytest.raises(ValueError, match=msg): parse_error_id(input) else: @@ -241,25 +241,25 @@ def test_parse_error_codes() -> None: def test_disable_error() -> None: settings = parse_args(["--disable", "FURB100"]) - assert settings == Settings(disable=set((ErrorCode(100),))) + assert settings == Settings(disable={ErrorCode(100)}) def test_disable_error_category() -> None: settings = parse_args(["--disable", "#category"]) - assert settings == Settings(disable=set((ErrorCategory("category"),))) + assert settings == Settings(disable={ErrorCategory("category")}) def test_disable_existing_enabled_error() -> None: settings = parse_args(["--enable", "FURB100", "--disable", "FURB100"]) - assert settings == Settings(disable=set((ErrorCode(100),))) + assert settings == Settings(disable={ErrorCode(100)}) def test_enable_existing_disabled_error() -> None: settings = parse_args(["--disable", "FURB100", "--enable", "FURB100"]) - assert settings == Settings(enable=set((ErrorCode(100),))) + assert settings == Settings(enable={ErrorCode(100)}) def test_parse_disable_check_missing_arg() -> None: @@ -277,9 +277,7 @@ def test_disable_in_config_file() -> None: config_file = parse_config_file(contents) - assert config_file == Settings( - disable=set((ErrorCode(111), ErrorCode(222))) - ) + assert config_file == Settings(disable={ErrorCode(111), ErrorCode(222)}) def test_disable_overrides_enable_in_config_file() -> None: @@ -292,8 +290,8 @@ def test_disable_overrides_enable_in_config_file() -> None: config_file = parse_config_file(contents) assert config_file == Settings( - enable=set((ErrorCode(222),)), - disable=set((ErrorCode(111), ErrorCode(333), ErrorCode(444))), + enable={ErrorCode(222)}, + disable={ErrorCode(111), ErrorCode(333), ErrorCode(444)}, ) @@ -311,8 +309,8 @@ def test_disable_cli_arg_overrides_config_file() -> None: merged = Settings.merge(config_file, command_line_args) assert merged == Settings( - enable=set((ErrorCode(222),)), - disable=set((ErrorCode(111), ErrorCode(333), ErrorCode(444))), + enable={ErrorCode(222)}, + disable={ErrorCode(111), ErrorCode(333), ErrorCode(444)}, ) @@ -327,9 +325,7 @@ def test_disable_all_flag_disables_existing_enables() -> None: ["--enable", "FURB123", "--disable-all", "--enable", "FURB456"] ) - assert settings == Settings( - disable_all=True, enable=set((ErrorCode(456),)) - ) + assert settings == Settings(disable_all=True, enable={ErrorCode(456)}) def test_disable_all_in_config_file() -> None: @@ -343,7 +339,7 @@ def test_disable_all_in_config_file() -> None: assert config_file == Settings( disable_all=True, - enable=set((ErrorCode(123),)), + enable={ErrorCode(123)}, ) @@ -362,7 +358,7 @@ def test_disable_all_command_line_override() -> None: assert merged == Settings( disable_all=True, - enable=set((ErrorCode(456),)), + enable={ErrorCode(456)}, ) @@ -376,7 +372,7 @@ def test_parse_invalid_python_version_flag_will_fail() -> None: versions = ["3.10.8", "x.y", "-3.-8"] for version in versions: - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="version must be in form `x.y`"): parse_args(["--python-version", version]) @@ -429,7 +425,7 @@ def test_merging_enable_all_field() -> None: merged_settings = Settings.merge(config_file, command_line_args) assert merged_settings == Settings( - enable_all=True, disable=set((ErrorCode(105),)) + enable_all=True, disable={ErrorCode(105)} ) @@ -444,9 +440,9 @@ def test_parse_config_file_categories() -> None: config_file = parse_config_file(config) assert config_file == Settings( - enable=set((ErrorCategory("category-a"),)), - disable=set((ErrorCategory("category-b"),)), - ignore=set((ErrorCategory("category-c"),)), + enable={ErrorCategory("category-a")}, + disable={ErrorCategory("category-b")}, + ignore={ErrorCategory("category-c")}, ) @@ -494,9 +490,9 @@ def test_flags_which_support_comma_separated_cli_args() -> None: ) assert settings == Settings( - enable=set((ErrorCode(100), ErrorCode(101))), - disable=set((ErrorCode(102), ErrorCode(103))), - ignore=set((ErrorCode(104), ErrorCode(105))), + enable={ErrorCode(100), ErrorCode(101)}, + disable={ErrorCode(102), ErrorCode(103)}, + ignore={ErrorCode(104), ErrorCode(105)}, ) @@ -517,15 +513,13 @@ def test_parse_amend_file_paths() -> None: config_file = parse_config_file(config) assert config_file == Settings( - ignore=set( - ( - ErrorCode(100), - ErrorCode(101, path=Path("some/file/path")), - ErrorCode(102, path=Path("some/file/path")), - ErrorCode(102, path=Path("some/other/path")), - ErrorCode(103, path=Path("some/other/path")), - ) - ) + ignore={ + ErrorCode(100), + ErrorCode(101, path=Path("some/file/path")), + ErrorCode(102, path=Path("some/file/path")), + ErrorCode(102, path=Path("some/other/path")), + ErrorCode(103, path=Path("some/other/path")), + } ) diff --git a/test/test_check_formatting.py b/test/test_check_formatting.py index 41ff8f7..365fe2e 100644 --- a/test/test_check_formatting.py +++ b/test/test_check_formatting.py @@ -79,4 +79,4 @@ def test_checks_are_formatted_properly() -> None: assert_name_is_unique(error.name, error_names) except AssertionError as ex: - raise ValueError(f"{module.__file__}: {ex}") + raise ValueError(f"{module.__file__}: {ex}") from ex diff --git a/test/test_checks.py b/test/test_checks.py index f599960..264d273 100644 --- a/test/test_checks.py +++ b/test/test_checks.py @@ -41,9 +41,7 @@ def test_ignore_check_is_respected() -> None: test_file = str(TEST_DATA_PATH / "err_100.py") errors = run_refurb( - Settings( - files=[test_file], ignore=set((ErrorCode(100), ErrorCode(123))) - ) + Settings(files=[test_file], ignore={ErrorCode(100), ErrorCode(123)}) ) assert len(errors) == 0 @@ -56,7 +54,7 @@ def test_ignore_custom_check_is_respected() -> None: "test.custom_checks.disallow_call", ] - ignore_args = args + ["--ignore", "XYZ100"] + ignore_args = [*args, "--ignore", "XYZ100"] errors_normal = run_refurb(parse_command_line_args(args)) errors_while_ignoring = run_refurb(parse_command_line_args(ignore_args)) @@ -91,7 +89,7 @@ def test_disabled_check_ran_if_explicitly_enabled() -> None: Settings( files=["test/e2e/dummy.py"], load=[DISABLED_CHECK], - enable=set((ErrorCode(prefix="XYZ", id=101),)), + enable={ErrorCode(prefix="XYZ", id=101)}, ) ) @@ -121,7 +119,7 @@ def test_disable_all_will_only_load_explicitly_enabled_checks() -> None: Settings( files=["test/data/"], disable_all=True, - enable=set((ErrorCode(100),)), + enable={ErrorCode(100)}, ) ) @@ -134,7 +132,7 @@ def test_disable_will_actually_disable_check_loading() -> None: errors = run_refurb( Settings( files=["test/data/err_123.py"], - disable=set((ErrorCode(123),)), + disable={ErrorCode(123)}, ) ) @@ -191,7 +189,7 @@ def test_explicitly_disabled_check_is_ignored_when_enable_all_is_set() -> None: Settings( files=["test/data/err_123.py"], enable_all=True, - disable=set((ErrorCode(123),)), + disable={ErrorCode(123)}, ) ) @@ -202,8 +200,8 @@ def test_explicitly_enabled_check_from_disabled_category_is_ran() -> None: errors = run_refurb( Settings( files=["test/data/err_123.py"], - disable=set((ErrorCategory("readability"),)), - enable=set((ErrorCode(123),)), + disable={ErrorCategory("readability")}, + enable={ErrorCode(123)}, ) ) @@ -215,7 +213,7 @@ def test_explicitly_enabled_category_still_runs() -> None: Settings( files=["test/data/err_123.py"], disable_all=True, - enable=set((ErrorCategory("readability"),)), + enable={ErrorCategory("readability")}, ) ) @@ -226,7 +224,7 @@ def test_error_not_ignored_if_path_doesnt_apply() -> None: errors = run_refurb( Settings( files=["test/data/err_123.py"], - ignore=set((ErrorCode(123, path=Path("some_other_file.py")),)), + ignore={ErrorCode(123, path=Path("some_other_file.py"))}, ) ) @@ -237,7 +235,7 @@ def test_error_not_ignored_if_error_code_doesnt_apply() -> None: errors = run_refurb( Settings( files=["test/data/err_123.py"], - ignore=set((ErrorCode(456, path=Path("test/data/err_123.py")),)), + ignore={ErrorCode(456, path=Path("test/data/err_123.py"))}, ) ) @@ -248,7 +246,7 @@ def test_error_ignored_if_path_applies() -> None: errors = run_refurb( Settings( files=["test/data/err_123.py"], - ignore=set((ErrorCode(123, path=Path("test/data/err_123.py")),)), + ignore={ErrorCode(123, path=Path("test/data/err_123.py"))}, ) ) @@ -259,7 +257,7 @@ def test_error_ignored_if_category_matches() -> None: error = ErrorCategory("readability", path=Path("test/data/err_123.py")) errors = run_refurb( - Settings(files=["test/data/err_123.py"], ignore=set((error,))) + Settings(files=["test/data/err_123.py"], ignore={error}) ) assert not errors diff --git a/test/test_main.py b/test/test_main.py index 5c62d3c..1bcf624 100644 --- a/test/test_main.py +++ b/test/test_main.py @@ -56,7 +56,7 @@ class CustomError100(Error): "some other error", ] - sorted_errors = list(sorted(errors, key=sort_errors)) + sorted_errors = sorted(errors, key=sort_errors) assert sorted_errors == [ "some other error", diff --git a/test/test_visitor.py b/test/test_visitor.py index 3dc4b06..3bcd8c4 100644 --- a/test/test_visitor.py +++ b/test/test_visitor.py @@ -13,7 +13,7 @@ from .mypy_visitor import get_mypy_visitor_mapping -@pytest.fixture +@pytest.fixture() def dummy_visitor() -> RefurbVisitor: """ This fixture provides a RefurbVisitor instance with a visit method for each @@ -73,4 +73,4 @@ def test_mypy_consistence() -> None: """ mypy_visitor_mapping = get_mypy_visitor_mapping() - assert METHOD_NODE_MAPPINGS == mypy_visitor_mapping + assert mypy_visitor_mapping == METHOD_NODE_MAPPINGS