diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 83facb2e5..28174abc4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -38,7 +38,7 @@ If applicable, add copy/paste the output or attach a screenshots to help explain - Git version [e.g. 2.36.0] - Darker version [e.g. 2.1.1] - Black version [e.g. 22.3.0] - - other reformatter and linter versions [e.g. `isort==5.10.1`, `mypy==0.942` + - other reformatter versions [e.g. `isort==5.10.1`, `flynt==1.0.1`] **Additional context** Add any other context about the problem here. diff --git a/.github/workflows/darker-flake8.yml b/.github/workflows/darker-flake8.yml deleted file mode 100644 index 841ba1954..000000000 --- a/.github/workflows/darker-flake8.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Test linting using the Darker GitHub Action - -on: push # yamllint disable-line rule:truthy - -jobs: - darker-github-action-linting-test: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Run Darker's own GitHub action straight from the repository - uses: ./ - with: - options: --check --diff --color - src: action release_tools setup.py src/darker - revision: origin/master... - lint: flake8 - version: "@${{ github.ref_name }}" diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 07600d6e3..8545afa59 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -15,7 +15,6 @@ jobs: black \ git+https://github.com/akaihola/darkgraylib.git@main \ flynt \ - git+https://github.com/akaihola/graylint.git@main \ isort \ mypy>=0.990 \ pytest \ diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 1812ad969..084a442cf 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -16,8 +16,6 @@ jobs: black \ git+https://github.com/akaihola/darkgraylib.git@main \ defusedxml \ - git+https://github.com/akaihola/graylint.git@main \ - pip-requirements-parser \ pygments \ pylint \ pytest>=6.2.0 \ diff --git a/CHANGES.rst b/CHANGES.rst index a5fd39f27..0973556b8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -13,6 +13,11 @@ Added - The ``--preview`` configuration flag is now supported in the configuration files for Darker and Black +Removed +------- +- **Backwards incompatible change:** Baseline linting support (``-L``/``--lint`` option) + has been removed. Use the Graylint_ tool instead. + Fixed ----- - Update ``darkgray-dev-tools`` for Pip >= 24.1 compatibility. diff --git a/README.rst b/README.rst index 5121952ce..ecf3c331a 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ -================================================= - Darker – reformat and lint modified Python code -================================================= +======================================== + Darker – reformat modified Python code +======================================== |build-badge| |license-badge| |pypi-badge| |downloads-badge| |black-badge| |changelog-badge| @@ -32,12 +32,8 @@ What? This utility reformats and checks Python source code files. However, when run in a Git repository, it compares an old revision of the source tree -to a newer revision (or the working tree). It then - -- only applies reformatting in regions which have changed in the Git working tree - between the two revisions, and -- only reports those linting messages which appeared after the modifications to the - source code files. +to a newer revision (or the working tree). It then only applies reformatting +in regions which have changed in the Git working tree between the two revisions. The reformatters supported are: @@ -45,7 +41,7 @@ The reformatters supported are: - isort_ for sorting imports - flynt_ for turning old-style format strings to f-strings -See `Using linters`_ below for the list of supported linters. +**NOTE:** Baseline linting support has been moved to the Graylint_ package. To easily run Darker as a Pytest_ plugin, see pytest-darker_. @@ -168,12 +164,16 @@ You can enable additional features with command line options: your modules to determine whether they are first or third party modules. - ``-f`` / ``--flynt``: Also convert string formatting to use f-strings using the ``flynt`` package -- ``-L `` / ``--lint ``: Run a supported linter (see `Using linters`_) *New in version 1.1.0:* The ``-L`` / ``--lint`` option. + *New in version 1.2.2:* Package available in conda-forge_. + *New in version 1.7.0:* The ``-f`` / ``--flynt`` option +*New in version 3.0.0:* Removed the ``-L`` / ``--lint`` functionality and moved it into +the Graylint_ package. + .. _Conda: https://conda.io/ .. _conda-forge: https://conda-forge.org/ @@ -280,8 +280,8 @@ instead of the last commit: $ darker --revision master . -Customizing ``darker``, Black_, isort_, flynt_ and linter behavior -================================================================== +Customizing ``darker``, Black_, isort_ and flynt_ behavior +========================================================== ``darker`` invokes Black_ and isort_ internals directly instead of running their binaries, so it needs to read and pass configuration options to them explicitly. @@ -289,20 +289,13 @@ Project-specific default options for ``darker`` itself, Black_ and isort_ are re the project's ``pyproject.toml`` file in the repository root. isort_ does also look for a few other places for configuration. -Mypy_, Pylint_, Flake8_ and other compatible linters are invoked as -subprocesses by ``darker``, so normal configuration mechanisms apply for each of those -tools. Linters can also be configured on the command line, for example:: - - darker -L "mypy --strict" . - darker --lint "pylint --errors-only" . - flynt_ (option ``-f`` / ``--flynt``) is also invoked as a subprocess, but passing command line options to it is currently not supported. Configuration files need to be used instead. Darker does honor exclusion options in Black configuration files when recursing -directories, but the exclusions are only applied to Black reformatting. Isort and -linters are still run on excluded files. Also, individual files explicitly listed on the +directories, but the exclusions are only applied to Black reformatting. +Isort is still run on excluded files. Also, individual files explicitly listed on the command line are still reformatted even if they match exclusion patterns. For more details, see: @@ -329,8 +322,7 @@ The following `command line arguments`_ can also be used to modify the defaults: (``HEAD..:STDIN:`` being the default if ``--stdin-filename`` is enabled). -c PATH, --config PATH Make ``darker``, ``black`` and ``isort`` read configuration from ``PATH``. Note - that other tools like ``flynt``, ``mypy``, ``pylint`` or ``flake8`` won't use - this configuration file. + that other tools like ``flynt`` won't use this configuration file. -v, --verbose Show steps taken and summarize modifications -q, --quiet @@ -362,11 +354,7 @@ The following `command line arguments`_ can also be used to modify the defaults: In Black, enable potentially disruptive style changes that may be added to Black in the future -L CMD, --lint CMD - Run a linter on changed files. ``CMD`` can be a name or path of the linter - binary, or a full quoted command line with the command and options. Linters read - their configuration as normally, and aren't affected by ``-c`` / ``--config``. - Linter output is syntax highlighted when the ``pygments`` package is available if - run on a terminal and or enabled by explicitly (see ``--color``). + Show information about baseline linting using the Graylint package. -S, --skip-string-normalization Don't normalize string quotes or prefixes --no-skip-string-normalization @@ -410,9 +398,6 @@ An example ``pyproject.toml`` configuration file: check = true isort = true flynt = true - lint = [ - "pylint", - ] line-length = 80 # Passed to isort and Black, override their config target-version = ["py312"] # Passed to Black, overriding its config log_level = "INFO" @@ -433,10 +418,6 @@ An example ``pyproject.toml`` configuration file: known_third_party = ["pytest"] line_length = 88 # Overridden by [tool.darker] above -Be careful to not use options which generate output which is unexpected for -other tools. For example, VSCode only expects the reformat diff, so -``lint = [ ... ]`` can't be used with it. - *New in version 1.0.0:* - The ``-c``, ``-S`` and ``-l`` command line options. @@ -470,6 +451,10 @@ command line options *New in version 2.1.1:* In ``[tool.darker]``, deprecate the the Black options ``skip_string_normalization`` and ``skip_magic_trailing_comma`` +*New in version 3.0.0:* Removed the ``-L`` / ``--lint`` functionality and moved it into +the Graylint_ package. Also removed ``lint =``, ``skip_string_normalization =`` and +``skip_magic_trailing_comma =`` from ``[tool.darker]``. + .. _Black documentation about pyproject.toml: https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#configuration-via-a-file .. _isort documentation about config files: https://timothycrosley.github.io/isort/docs/configuration/config_files/ .. _public GitHub repositories which install and run Darker: https://github.com/search?q=%2Fpip+install+.*darker%2F+path%3A%2F%5E.github%5C%2Fworkflows%5C%2F.*%2F&type=code @@ -581,8 +566,6 @@ __ https://github.com/microsoft/vscode-black-formatter VSCode will always add ``--diff --quiet`` as arguments to Darker, but you can also pass additional arguments in the ``black-formatter.args`` option (e.g. ``["-d", "--isort", "--revision=master..."]``). -Be sure to *not* enable any linters here or in ``pyproject.toml`` -since VSCode won't be able to understand output from them. Note that VSCode first copies the file to reformat into a temporary ``.py..tmp`` file, then calls Black (or Darker in this case) on that @@ -676,7 +659,7 @@ introduced by Black updates. See `Guarding against Black compatibility breakage` more information. If you'd prefer to not update but keep a stable pre-commit setup, you can pin Black and -other reformatter/linter tools you use to known compatible versions, for example: +other reformatter tools you use to known compatible versions, for example: .. code-block:: yaml @@ -686,18 +669,9 @@ other reformatter/linter tools you use to known compatible versions, for example - id: darker args: - --isort - - --lint - - mypy - - --lint - - flake8 - - --lint - - pylint additional_dependencies: - black==22.12.0 - isort==5.11.4 - - mypy==0.990 - - flake8==5.0.4 - - pylint==2.15.5 .. _pre-commit: https://pre-commit.com/ .. _pre-commit Installation: https://pre-commit.com/#installation @@ -745,12 +719,12 @@ Create a file named ``.github/workflows/darker.yml`` inside your repository with .. code-block:: yaml - name: Lint + name: Reformat on: [push, pull_request] jobs: - lint: + reformat: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -762,7 +736,6 @@ Create a file named ``.github/workflows/darker.yml`` inside your repository with options: "--check --diff --isort --color" src: "./src" version: "~=2.1.1" - lint: "flake8,pylint==2.13.1" There needs to be a working Python environment, set up using ``actions/setup-python`` in the above example. Darker will be installed in an isolated virtualenv to prevent @@ -789,11 +762,6 @@ You can also configure other arguments passed to Darker via ``"options:"``. It defaults to ``"--check --diff --color"``. You can e.g. add ``"--isort"`` to sort imports, or ``"--verbose"`` for debug logging. -To run linters through Darker, you can provide a comma separated list of linters using -the ``lint:`` option. Only ``flake8``, ``pylint`` and ``mypy`` are supported. Other -linters may or may not work with Darker, depending on their message output format. -Versions can be constrained using ``pip`` syntax, e.g. ``"flake8>=3.9.2"``. - *New in version 1.1.0:* GitHub Actions integration. Modeled after how Black_ does it, thanks to Black authors for the example! @@ -804,76 +772,16 @@ The ``revision:`` option, with smart default value if omitted. *New in version 1.5.0:* The ``lint:`` option. - -.. _Using linters: - -Using linters -============= - -One way to use Darker is to filter linter output to only those linter messages -which appeared after the modifications to source code files, -as well as old messages which concern modified lines. -Darker supports any linter with output in one of the following formats:: - - :: - ::: - -Most notably, the following linters/checkers have been verified to work with Darker: - -- Mypy_ for static type checking -- Pylint_ for generic static checking of code -- Flake8_ for style guide enforcement -- `cov_to_lint.py`_ for test coverage - -*New in version 1.1.0:* Support for Mypy_, Pylint_, Flake8_ and compatible linters. - -*New in version 1.2.0:* Support for test coverage output using `cov_to_lint.py`_. - -To run a linter, use the ``--lint`` / ``-L`` command line option with the linter -command or a full command line to pass to a linter. Some examples: - -- ``-L flake8``: enforce the Python style guide using Flake8_ -- ``-L "mypy --strict"``: do static type checking using Mypy_ -- ``--lint="pylint --ignore='setup.py'"``: analyze code using Pylint_ -- ``-L cov_to_lint.py``: read ``.coverage`` and list non-covered modified lines - -**Note:** Full command lines aren't fully tested on Windows. See issue `#456`_ for a -possible bug. - -Darker also groups linter output into blocks of consecutive lines -separated by blank lines. -Here's an example of `cov_to_lint.py`_ output:: - - $ darker --revision 0.1.0.. --check --lint cov_to_lint.py src - src/darker/__main__.py:94: no coverage: logger.debug("No changes in %s after isort", src) - src/darker/__main__.py:95: no coverage: break - - src/darker/__main__.py:125: no coverage: except NotEquivalentError: - - src/darker/__main__.py:130: no coverage: if context_lines == max_context_lines: - src/darker/__main__.py:131: no coverage: raise - src/darker/__main__.py:132: no coverage: logger.debug( - -+-----------------------------------------------------------------------+ -| ⚠ NOTE ⚠ | -+=======================================================================+ -| Don't enable linting on the command line or in the configuration when | -| running Darker as a reformatter in VSCode. You will confuse VSCode | -| with unexpected output from Darker, as it only expect black's output | -+-----------------------------------------------------------------------+ - -.. _Mypy: https://pypi.org/project/mypy -.. _Pylint: https://pypi.org/project/pylint -.. _Flake8: https://pypi.org/project/flake8 -.. _cov_to_lint.py: https://gist.github.com/akaihola/2511fe7d2f29f219cb995649afd3d8d2 -.. _#456: https://github.com/akaihola/darker/issues/456 +*New in version 3.0.0:* +Removed the ``lint:`` option and moved it into the GitHub action +of the Graylint_ package. Syntax highlighting =================== -Darker automatically enables syntax highlighting for the ``--diff``, -``-d``/``--stdout`` and ``-L``/``--lint`` options if it's running on a terminal and the +Darker automatically enables syntax highlighting for the ``--diff`` and +``-d``/``--stdout`` options if it's running on a terminal and the Pygments_ package is installed. You can force enable syntax highlighting on non-terminal output using @@ -970,8 +878,6 @@ To sort imports when the ``--isort`` option was specified, Darker proceeds like - if all lines between the first and last line modified by isort_ were unchanged between the revisions, discard import sorting modifications for that file -For details on how linting support works, see Graylint_ documentation. - Limitations and work-arounds ============================= diff --git a/action.yml b/action.yml index 1fc6b8bf6..cd1196805 100644 --- a/action.yml +++ b/action.yml @@ -24,9 +24,7 @@ inputs: required: false lint: description: >- - Comma-separated list of linters to `pip install` and run from Darker. - Optionally, version constraints (using pip syntax) can be specified. - Example: flake8,pylint==2.13.1 + NOTE: Baseline linting has been moved to the Graylint package. required: false default: '' branding: @@ -56,7 +54,6 @@ runs: sys.exit(proc.returncode) " - pip install pip-requirements-parser if [ "$RUNNER_OS" == "Windows" ]; then echo $entrypoint | python else diff --git a/action/main.py b/action/main.py index e196079c9..b903bca4d 100644 --- a/action/main.py +++ b/action/main.py @@ -6,18 +6,20 @@ from pathlib import Path from subprocess import PIPE, STDOUT, run # nosec -from pip_requirements_parser import parse_reqparts_from_string - -LINTER_WHITELIST = {"flake8", "pylint", "mypy"} ACTION_PATH = Path(os.environ["GITHUB_ACTION_PATH"]) ENV_PATH = ACTION_PATH / ".darker-env" ENV_BIN = ENV_PATH / ("Scripts" if sys.platform == "win32" else "bin") OPTIONS = os.getenv("INPUT_OPTIONS", default="") SRC = os.getenv("INPUT_SRC", default="") VERSION = os.getenv("INPUT_VERSION", default="") -LINT = os.getenv("INPUT_LINT", default="") REVISION = os.getenv("INPUT_REVISION") or os.getenv("INPUT_COMMIT_RANGE") or "HEAD^" +if os.getenv("INPUT_LINT", default=""): + print( + "::notice:: Baseline linting has been moved to the Graylint package." + " See https://pypi.org/project/graylint for more information.", + ) + run([sys.executable, "-m", "venv", str(ENV_PATH)], check=True) # nosec req = ["darker[color,isort]"] @@ -28,18 +30,6 @@ req[0] += VERSION else: req[0] += f"=={VERSION}" -linter_options = [] -for requirement_string in LINT.split(","): - if not requirement_string.strip(): - continue - linter_requirement = parse_reqparts_from_string(requirement_string).requirement - linter = linter_requirement.name - if linter not in LINTER_WHITELIST: - raise RuntimeError( - f"{linter!r} is not supported as a linter by the GitHub Action" - ) - req.append(str(linter_requirement)) - linter_options.extend(["--lint", linter]) pip_proc = run( # nosec [str(ENV_BIN / "python"), "-m", "pip", "install"] + req, @@ -59,7 +49,6 @@ [ *base_cmd, *shlex.split(OPTIONS), - *linter_options, "--revision", REVISION, *shlex.split(SRC), diff --git a/action/tests/test_main.py b/action/tests/test_main.py index e0d726154..beca44654 100644 --- a/action/tests/test_main.py +++ b/action/tests/test_main.py @@ -2,18 +2,21 @@ # pylint: disable=use-dict-literal -import re +from __future__ import annotations + import sys from contextlib import contextmanager -from pathlib import Path from runpy import run_module from subprocess import PIPE, STDOUT, CompletedProcess # nosec from types import SimpleNamespace -from typing import Dict, Generator +from typing import TYPE_CHECKING, Generator from unittest.mock import ANY, Mock, call, patch import pytest +if TYPE_CHECKING: + from pathlib import Path + # pylint: disable=redefined-outer-name,unused-argument @@ -25,7 +28,7 @@ class SysExitCalled(Exception): @pytest.fixture -def run_main_env() -> Dict[str, str]: +def run_main_env() -> dict[str, str]: """By default, call `main.py` with just `GITHUB_ACTION_PATH` in the environment""" return {} @@ -33,9 +36,9 @@ def run_main_env() -> Dict[str, str]: @contextmanager def patch_main( tmp_path: Path, - run_main_env: Dict[str, str], + run_main_env: dict[str, str], pip_returncode: int = 0, -) -> Generator[SimpleNamespace, None, None]: +) -> Generator[SimpleNamespace]: """Patch `subprocess.run`, `sys.exit` and environment variables :param tmp_path: Path to use for the `GITHUB_ACTION_PATH` environment variable @@ -61,8 +64,9 @@ def run(args, **kwargs): @pytest.fixture def main_patch( - tmp_path: Path, run_main_env: Dict[str, str] -) -> Generator[SimpleNamespace, None, None]: + tmp_path: Path, + run_main_env: dict[str, str], +) -> Generator[SimpleNamespace]: """`subprocess.run, `sys.exit` and environment variables patching as Pytest fixture :param tmp_path: Path to use for the `GITHUB_ACTION_PATH` environment variable @@ -98,24 +102,12 @@ def test_creates_virtualenv(tmp_path, main_patch): ], ), dict( - run_main_env={"INPUT_LINT": "pylint"}, - expect=["darker[color,isort]", "pylint"], - ), - dict( - run_main_env={"INPUT_LINT": "pylint,flake8"}, - expect=["darker[color,isort]", "pylint", "flake8"], - ), - dict( - run_main_env={"INPUT_LINT": " flake8 "}, - expect=["darker[color,isort]", "flake8"], - ), - dict( - run_main_env={"INPUT_LINT": " flake8 , pylint "}, - expect=["darker[color,isort]", "flake8", "pylint"], + run_main_env={"INPUT_LINT": "dummy"}, + expect=["darker[color,isort]"], ), dict( - run_main_env={"INPUT_LINT": " flake8 >= 3.9.2 , pylint == 2.13.1 "}, - expect=["darker[color,isort]", "flake8>=3.9.2", "pylint==2.13.1"], + run_main_env={"INPUT_LINT": "dummy,foobar"}, + expect=["darker[color,isort]"], ), ) def test_installs_packages(tmp_path, main_patch, run_main_env, expect): @@ -139,24 +131,6 @@ def test_installs_packages(tmp_path, main_patch, run_main_env, expect): ) -@pytest.mark.parametrize( - "linters", ["foo", " foo ", "foo==2.0,bar", " foo>1.0 , bar ", "pylint,foo"] -) -def test_wont_install_unknown_packages(tmp_path, linters): - """Non-whitelisted linters raise an exception""" - with patch_main(tmp_path, {"INPUT_LINT": linters}) as main_patch, pytest.raises( - RuntimeError, - match=re.escape("'foo' is not supported as a linter by the GitHub Action"), - ): - - run_module("main") - - # only virtualenv `run` called, `pip` and `darker` not called - (venv_create,) = main_patch.subprocess.run.call_args_list - assert venv_create == call([ANY, "-m", "venv", ANY], check=True) - assert not main_patch.sys.exit.called - - @pytest.mark.kwparametrize( dict(env={"INPUT_SRC": "."}, expect=["--revision", "HEAD^", "."]), dict( @@ -184,12 +158,12 @@ def test_wont_install_unknown_packages(tmp_path, linters): expect=["--revision", "master...", "."], ), dict( - env={"INPUT_SRC": ".", "INPUT_LINT": "pylint,flake8"}, - expect=["--lint", "pylint", "--lint", "flake8", "--revision", "HEAD^", "."], + env={"INPUT_SRC": ".", "INPUT_LINT": "dummy,foobar"}, + expect=["--revision", "HEAD^", "."], ), dict( - env={"INPUT_SRC": ".", "INPUT_LINT": "pylint == 2.13.1,flake8>=3.9.2"}, - expect=["--lint", "pylint", "--lint", "flake8", "--revision", "HEAD^", "."], + env={"INPUT_SRC": ".", "INPUT_LINT": "dummy == 2.13.1,foobar>=3.9.2"}, + expect=["--revision", "HEAD^", "."], ), dict( env={ @@ -197,15 +171,11 @@ def test_wont_install_unknown_packages(tmp_path, linters): "INPUT_OPTIONS": "--isort --verbose", "INPUT_REVISION": "master...", "INPUT_COMMIT_RANGE": "ignored", - "INPUT_LINT": "pylint,flake8", + "INPUT_LINT": "dummy,foobar", }, expect=[ "--isort", "--verbose", - "--lint", - "pylint", - "--lint", - "flake8", "--revision", "master...", "here.py", @@ -213,7 +183,7 @@ def test_wont_install_unknown_packages(tmp_path, linters): ], ), ) -def test_runs_darker(tmp_path, env, expect): +def test_runs_darker(tmp_path: Path, env: dict[str, str], expect: list[str]) -> None: """Configuration translates correctly into a Darker command line""" with patch_main(tmp_path, env) as main_patch, pytest.raises(SysExitCalled): @@ -221,7 +191,12 @@ def test_runs_darker(tmp_path, env, expect): darker = str(tmp_path / ".darker-env" / BIN / "darker") # This gets the first list item of the first positional argument to the `run` call. - assert darker in [c.args[0][0] for c in main_patch.subprocess.run.call_args_list] + (darker_call,) = ( + c.args[0] + for c in main_patch.subprocess.run.call_args_list + if c.args[0][0] == darker + ) + assert darker_call[1:] == expect def test_error_if_pip_fails(tmp_path, capsys): diff --git a/check-darker.toml b/check-darker.toml index e29e6fad3..85532ab66 100644 --- a/check-darker.toml +++ b/check-darker.toml @@ -24,6 +24,10 @@ src = [ ] revision = "origin/master..." isort = true + +[tool.graylint] +src = ["."] +revision = "origin/master..." lint = [ "flake8", "mypy", diff --git a/constraints-oldest.txt b/constraints-oldest.txt index 0f9c6153b..fc3d5b986 100644 --- a/constraints-oldest.txt +++ b/constraints-oldest.txt @@ -10,7 +10,6 @@ flake8-2020==1.6.1 flake8-bugbear==22.1.11 flake8-comprehensions==3.7.0 flynt==0.76 -graylint==2.0.0 mypy==0.990 Pygments==2.4.0 pytest==6.2.0 diff --git a/pyproject.toml b/pyproject.toml index d19411ebb..3c30c6d83 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,10 @@ src = [ ] revision = "origin/master..." +[tool.graylint] +revision = "origin/master..." +src = ["."] + [tool.pylint."messages control"] # Check import order only with isort. Pylint doesn't support a custom list of first # party packages. We want to consider "darkgraylib" and "graylint" as first party. @@ -46,6 +50,15 @@ ignore = [ ] [tool.ruff.lint.per-file-ignores] +"action/*.py" = [ + "T201", # `print` found +] +"action/tests/*.py" = [ + "S101", # Use of `assert` detected +] +"src/darker/__main__.py" = [ + "T201", # `print` found +] "src/darker/tests/*.py" = [ "ANN001", # Missing type annotation for function argument "ANN201", # Missing return type annotation for public function diff --git a/setup.cfg b/setup.cfg index f3c842a85..191d76a96 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,7 +30,6 @@ install_requires = # NOTE: remember to keep `constraints-oldest.txt` in sync with these black>=22.3.0 darkgraylib~=2.0.1 - graylint~=2.0.0 toml>=0.10.0 # NOTE: remember to keep `.github/workflows/python-package.yml` in sync # with the minimum required Python version @@ -64,7 +63,6 @@ test = isort>=5.0.1 mypy>=0.990 pathspec # to test `gen_python_files` in `test_black_diff.py` - pip-requirements-parser pydocstyle pygments pylint diff --git a/src/darker/__main__.py b/src/darker/__main__.py index 195d5f78a..9d598379a 100644 --- a/src/darker/__main__.py +++ b/src/darker/__main__.py @@ -30,7 +30,7 @@ git_get_modified_python_files, git_is_repository, ) -from darker.help import get_extra_instruction +from darker.help import LINTING_GUIDE, get_extra_instruction from darker.import_sorting import apply_isort, isort from darker.utils import debug_dump, glob_any from darker.verification import ASTVerifier, BinarySearch, NotEquivalentError @@ -53,7 +53,6 @@ from darkgraylib.log import setup_logging from darkgraylib.main import resolve_paths from darkgraylib.utils import GIT_DATEFORMAT, WINDOWS, DiffChunk, TextDocument -from graylint.linting import run_linters logger = logging.getLogger(__name__) @@ -627,20 +626,9 @@ def main( # noqa: C901,PLR0912,PLR0915 print_source(new, use_color) if write_modified_files: modify_file(path, new) - linter_failures_on_modified_lines = run_linters( - args.lint, - common_root, - # paths to lint are not limited to modified files or just Python files: - {p.resolve().relative_to(common_root) for p in paths}, - revrange, - use_color, - ) - return ( - 1 - if linter_failures_on_modified_lines - or (args.check and formatting_failures_on_modified_lines) - else 0 - ) + if args.lint: + print(LINTING_GUIDE, end="") + return 1 if args.check and formatting_failures_on_modified_lines else 0 def main_with_error_handling() -> int: diff --git a/src/darker/command_line.py b/src/darker/command_line.py index f6bc28980..77972663d 100644 --- a/src/darker/command_line.py +++ b/src/darker/command_line.py @@ -12,7 +12,6 @@ from darker.config import DEPRECATED_CONFIG_OPTIONS, DarkerConfig, OutputMode from darker.version import __version__ from darkgraylib.command_line import add_parser_argument -from graylint.command_line import add_lint_arg def make_argument_parser(require_src: bool) -> ArgumentParser: @@ -27,8 +26,7 @@ def make_argument_parser(require_src: bool) -> ArgumentParser: "Darker", hlp.DESCRIPTION, "Make `darker`, `black` and `isort` read configuration from `PATH`. Note that" - " other tools like `flynt`, `mypy`, `pylint` or `flake8` won't use this" - " configuration file.", + " other tools like `flynt` won't use this configuration file.", __version__, ) @@ -40,7 +38,7 @@ def make_argument_parser(require_src: bool) -> ArgumentParser: add_arg(hlp.FLYNT, "-f", "--flynt", action="store_true") add_arg(hlp.ISORT, "-i", "--isort", action="store_true") add_arg(hlp.PREVIEW, "--preview", action="store_true") - add_lint_arg(parser) + add_arg(hlp.LINT, "-L", "--lint", action="append", metavar="CMD", default=[]) add_arg( hlp.SKIP_STRING_NORMALIZATION, "-S", @@ -91,6 +89,13 @@ def show_config_deprecations(config: DarkerConfig) -> None: DeprecationWarning, stacklevel=2, ) + if "lint" in config: + warnings.warn( + "Baseline linting has been moved to the Graylint package. Please" + " remove the `lint =` option from your configuration file.", + DeprecationWarning, + stacklevel=2, + ) def parse_command_line( diff --git a/src/darker/help.py b/src/darker/help.py index 96890c638..83766032b 100644 --- a/src/darker/help.py +++ b/src/darker/help.py @@ -1,5 +1,7 @@ """Help and usage instruction texts used for the command line parser""" +from textwrap import dedent + from black import TargetVersion @@ -100,12 +102,19 @@ def get_extra_instruction(dependency: str) -> str: ) ISORT = "".join(ISORT_PARTS) -LINT = ( - "Also run a linter on changed files. `CMD` can be a name or path of the linter" - " binary, or a full quoted command line with the command and options. Linters read" - " their configuration as normally, and aren't affected by `-c` / `--config`. Linter" - " output is syntax highlighted when the `pygments` package is available if run on" - " a terminal and or enabled by explicitly (see `--color`)." +LINT = "Show information about baseline linting using the Graylint package." +LINTING_GUIDE = dedent( + """ + Baseline linting support has been moved to the Graylint package. Please install + and run it using: + + pip install graylint + graylint -L CMD + graylint --lint CMD + + See https://pypi.org/project/graylint for more information." + + """, ) VERBOSE = "Show steps taken and summarize modifications" diff --git a/src/darker/tests/test_command_line.py b/src/darker/tests/test_command_line.py index e8940afda..698a0ba8d 100644 --- a/src/darker/tests/test_command_line.py +++ b/src/darker/tests/test_command_line.py @@ -118,16 +118,16 @@ def get_darker_help_output(capsys): expect_modified=("lint", ...), ), dict( - argv=["-L", "pylint", "."], - expect_value=("lint", ["pylint"]), - expect_config=("lint", ["pylint"]), - expect_modified=("lint", ["pylint"]), + argv=["-L", "dummy", "."], + expect_value=("lint", ["dummy"]), + expect_config=("lint", ["dummy"]), + expect_modified=("lint", ["dummy"]), ), dict( - argv=["--lint", "flake8", "-L", "mypy", "."], - expect_value=("lint", ["flake8", "mypy"]), - expect_config=("lint", ["flake8", "mypy"]), - expect_modified=("lint", ["flake8", "mypy"]), + argv=["--lint", "dummy", "-L", "foobar", "."], + expect_value=("lint", ["dummy", "foobar"]), + expect_config=("lint", ["dummy", "foobar"]), + expect_modified=("lint", ["dummy", "foobar"]), ), dict( argv=["."], @@ -255,7 +255,13 @@ def test_parse_command_line( dict(config={"stdout": True}, expect_warn=set()), dict(config={"check": True}, expect_warn=set()), dict(config={"isort": True}, expect_warn=set()), - dict(config={"lint": ["pylint"]}, expect_warn=set()), + dict( + config={"lint": ["dummy"]}, + expect_warn={ + "Baseline linting has been moved to the Graylint package. Please remove the" + " `lint =` option from your configuration file.", + }, + ), dict( config={"skip_string_normalization": True}, expect_warn={ @@ -278,13 +284,15 @@ def test_parse_command_line( "stdout": False, "check": True, "isort": True, - "lint": ["pylint"], + "lint": ["dummy"], "skip_string_normalization": True, "skip_magic_trailing_comma": True, "line_length": 88, "target_version": "py37", }, expect_warn={ + "Baseline linting has been moved to the Graylint package. Please remove the" + " `lint =` option from your configuration file.", "The configuration option `skip_magic_trailing_comma` in [tool.darker] is" " deprecated and will be removed in Darker 3.0.", "The configuration option `skip_string_normalization` in [tool.darker] is" @@ -717,16 +725,14 @@ def test_options(git_repo, options, expect): @pytest.mark.kwparametrize( - dict(check=False, changes=False, lintfail=False), - dict(check=False, changes=False, lintfail=True, expect_retval=1), - dict(check=False, changes=True, lintfail=False), - dict(check=False, changes=True, lintfail=True, expect_retval=1), - dict(check=True, changes=False, lintfail=False), - dict(check=True, changes=True, lintfail=True, expect_retval=1), + dict(arguments=["a.py"], changes=False), + dict(arguments=["a.py"], changes=True), + dict(arguments=["--check", "a.py"], changes=False), + dict(arguments=["--check", "a.py"], changes=True, expect_retval=1), expect_retval=0, ) -def test_main_retval(git_repo, check, changes, lintfail, expect_retval): - """main() return value is correct based on --check, reformatting and linters""" +def test_main_retval(git_repo, arguments, changes, expect_retval): + """``main()`` return value is correct based on ``--check`` and reformatting.""" git_repo.add({"a.py": ""}, commit="Initial commit") format_edited_parts = Mock() format_edited_parts.return_value = ( @@ -740,16 +746,13 @@ def test_main_retval(git_repo, check, changes, lintfail, expect_retval): if changes else [] ) - run_linters = Mock(return_value=lintfail) - check_arg_maybe = ["--check"] if check else [] with patch.multiple( "darker.__main__", format_edited_parts=format_edited_parts, modify_file=DEFAULT, - run_linters=run_linters, ): - retval = main(check_arg_maybe + ["a.py"]) + retval = main(arguments) assert retval == expect_retval diff --git a/src/darker/tests/test_main.py b/src/darker/tests/test_main.py index b07431690..dcedaa2d8 100644 --- a/src/darker/tests/test_main.py +++ b/src/darker/tests/test_main.py @@ -10,13 +10,13 @@ from argparse import ArgumentError from pathlib import Path from textwrap import dedent -from unittest.mock import ANY, call, patch import pytest import darker.__main__ import darker.import_sorting from darker.git import EditedLinenumsDiffer +from darker.help import LINTING_GUIDE from darker.tests.examples import A_PY, A_PY_BLACK, A_PY_BLACK_FLYNT, A_PY_BLACK_ISORT from darker.tests.test_fstring import FLYNTED_SOURCE, MODIFIED_SOURCE, ORIGINAL_SOURCE from darkgraylib.git import RevisionRange @@ -112,17 +112,16 @@ def _replace_diff_timestamps(text, replacement=""): expect_retval=1, ), dict( - arguments=["--check", "--lint", "echo subdir/a.py:1: message"], + arguments=["--check", "--lint", "dummy"], # Windows compatible path assertion using `pathlib.Path()` - expect_stdout=["", f"{Path('subdir/a.py')}:1: message {Path('subdir')} [echo]"], + expect_stdout=["", *LINTING_GUIDE.lstrip().splitlines()], expect_retval=1, ), dict( - arguments=["--diff", "--lint", "echo subdir/a.py:1: message"], + arguments=["--diff", "--lint", "dummy"], # Windows compatible path assertion using `pathlib.Path()` - expect_stdout=A_PY_DIFF_BLACK - + ["", f"{Path('subdir/a.py')}:1: message {Path('subdir')} [echo]"], - expect_retval=1, + expect_stdout=[*A_PY_DIFF_BLACK, "", *LINTING_GUIDE.lstrip().splitlines()], + expect_retval=0, ), dict( arguments=[], @@ -250,41 +249,35 @@ def test_main_in_plain_directory(tmp_path, capsys): (subdir_a / "non-python file.txt").write_text("not reformatted\n") (subdir_a / "python file.py").write_text("import sys, os\nprint('ok')") (subdir_c / "another python file.py").write_text("a =5") - with patch.object(darker.__main__, "run_linters") as run_linters: - retval = darker.__main__.main( - ["--diff", "--check", "--isort", "--lint", "echo", str(tmp_path)] - ) + retval = darker.__main__.main( + ["--diff", "--check", "--isort", "--lint", "dummy", str(tmp_path)], + ) assert retval == 1 - assert run_linters.call_args_list == [ - call( - ["echo"], - tmp_path, - {Path(".")}, - RevisionRange(rev1="HEAD", rev2=":WORKTREE:"), - False, - ) - ] output = capsys.readouterr().out output = _replace_diff_timestamps(output) - assert output == dedent( - """\ - --- subdir_a/python file.py +0000 - +++ subdir_a/python file.py +0000 - @@ -1,2 +1,4 @@ - -import sys, os - -print('ok') - +import os - +import sys - + - +print("ok") - --- subdir_b/subdir_c/another python file.py +0000 - +++ subdir_b/subdir_c/another python file.py +0000 - @@ -1 +1 @@ - -a =5 - +a = 5 - """ + assert ( + output + == dedent( + """\ + --- subdir_a/python file.py +0000 + +++ subdir_a/python file.py +0000 + @@ -1,2 +1,4 @@ + -import sys, os + -print('ok') + +import os + +import sys + + + +print("ok") + --- subdir_b/subdir_c/another python file.py +0000 + +++ subdir_b/subdir_c/another python file.py +0000 + @@ -1 +1 @@ + -a =5 + +a = 5 + """, + ) + + LINTING_GUIDE ) @@ -395,20 +388,6 @@ def test_main_vscode_tmpfile(git_repo, capsys): ] -def test_main_lint_unchanged(git_repo): - """Linters are run on all ``src`` command line options, modified or not""" - git_repo.add({"src/a.py": "foo\n", "src/subdir/b.py": "bar\n"}, commit="Initial") - with patch.object(darker.__main__, "run_linters") as run_linters: - run_linters.return_value = 0 - - retval = darker.__main__.main(["--check", "--lint=mylint", "src"]) - - run_linters.assert_called_once_with( - ["mylint"], Path("src").absolute(), {Path(".")}, ANY, ANY - ) - assert retval == 0 - - def test_print_diff(tmp_path, capsys): """print_diff() prints Black-style diff output with 5 lines of context""" Path(tmp_path / "a.py").write_text("dummy\n", encoding="utf-8")