pax_global_header00006660000000000000000000000064141670641420014517gustar00rootroot0000000000000052 comment=752fb0a0c48599c0033d52aa177823c791470618 flake8-comprehensions-3.8.0/000077500000000000000000000000001416706414200157335ustar00rootroot00000000000000flake8-comprehensions-3.8.0/.editorconfig000066400000000000000000000003451416706414200204120ustar00rootroot00000000000000# http://editorconfig.org root = true [*] indent_style = space indent_size = 2 trim_trailing_whitespace = true insert_final_newline = true charset = utf-8 end_of_line = lf [*.py] indent_size = 4 [Makefile] indent_style = tab flake8-comprehensions-3.8.0/.github/000077500000000000000000000000001416706414200172735ustar00rootroot00000000000000flake8-comprehensions-3.8.0/.github/CODE_OF_CONDUCT.md000066400000000000000000000001311416706414200220650ustar00rootroot00000000000000This project follows [Django's Code of Conduct](https://www.djangoproject.com/conduct/). flake8-comprehensions-3.8.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001416706414200214565ustar00rootroot00000000000000flake8-comprehensions-3.8.0/.github/ISSUE_TEMPLATE/feature-request.yml000066400000000000000000000004111416706414200253160ustar00rootroot00000000000000name: Feature Request description: Request an enhancement or new feature. body: - type: textarea id: description attributes: label: Description description: Please describe your feature request with appropriate detail. validations: required: true flake8-comprehensions-3.8.0/.github/ISSUE_TEMPLATE/issue.yml000066400000000000000000000015261416706414200233350ustar00rootroot00000000000000name: Issue description: File an issue body: - type: input id: python_version attributes: label: Python Version description: Which version of Python were you using? placeholder: 3.9.0 validations: required: false - type: input id: flake8_version attributes: label: flake8 Version description: Which version of flake8 were you using? placeholder: 3.9.2 validations: required: false - type: input id: package_version attributes: label: Package Version description: Which version of this package were you using? If not the latest version, please check this issue has not since been resolved. placeholder: 1.0.0 validations: required: false - type: textarea id: description attributes: label: Description description: Please describe your issue. validations: required: true flake8-comprehensions-3.8.0/.github/SECURITY.md000066400000000000000000000001011416706414200210540ustar00rootroot00000000000000Please report security issues directly over email to me@adamj.eu flake8-comprehensions-3.8.0/.github/workflows/000077500000000000000000000000001416706414200213305ustar00rootroot00000000000000flake8-comprehensions-3.8.0/.github/workflows/main.yml000066400000000000000000000013311416706414200227750ustar00rootroot00000000000000name: CI on: push: branches: - main pull_request: jobs: tests: name: Python ${{ matrix.python-version }} runs-on: ubuntu-20.04 strategy: matrix: python-version: - 3.7 - 3.8 - 3.9 - '3.10' steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} cache: pip cache-dependency-path: 'requirements/*.txt' - name: Install dependencies run: | python -m pip install --upgrade pip setuptools wheel python -m pip install --upgrade tox tox-py - name: Run tox targets for ${{ matrix.python-version }} run: tox --py current flake8-comprehensions-3.8.0/.gitignore000066400000000000000000000013441416706414200177250ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover .hypothesis/ .pytest_cache # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ flake8-comprehensions-3.8.0/.pre-commit-config.yaml000066400000000000000000000022211416706414200222110ustar00rootroot00000000000000default_language_version: python: python3.10 repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.1.0 hooks: - id: check-added-large-files - id: check-case-conflict - id: check-json - id: check-merge-conflict - id: check-symlinks - id: check-toml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/pyupgrade rev: v2.31.0 hooks: - id: pyupgrade args: [--py37-plus] - repo: https://github.com/psf/black rev: 21.12b0 hooks: - id: black - repo: https://github.com/asottile/blacken-docs rev: v1.12.0 hooks: - id: blacken-docs additional_dependencies: - black==21.11b1 - repo: https://github.com/pycqa/isort rev: 5.10.1 hooks: - id: isort - repo: https://github.com/PyCQA/flake8 rev: 4.0.1 hooks: - id: flake8 additional_dependencies: - flake8-bugbear - flake8-comprehensions - flake8-tidy-imports - flake8-typing-imports - repo: https://github.com/mgedmin/check-manifest rev: "0.47" hooks: - id: check-manifest args: [--no-build-isolation] - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.930 hooks: - id: mypy flake8-comprehensions-3.8.0/HISTORY.rst000066400000000000000000000151031416706414200176260ustar00rootroot00000000000000======= History ======= 3.8.0 (2022-01-10) ------------------ * Drop Python 3.6 support. * Remove upper bound on Flake8 version. 3.7.0 (2021-10-11) ------------------ * Support Flake8 4. 3.6.1 (2021-08-16) ------------------ * Fix type hint for ``tree`` argument. Thanks to kasium for the report in `Issue #352 `__. 3.6.0 (2021-08-13) ------------------ * Add type hints. 3.5.0 (2021-05-10) ------------------ * Support Python 3.10. * Stop distributing tests to reduce package size. Tests are not intended to be run outside of the tox setup in the repository. Repackagers can use GitHub's tarballs per tag. 3.4.0 (2021-03-18) ------------------ * Remove rules C407 (Unnecessary ```` comprehension - ```` can take a generator) and C412 (Unnecessary ```` comprehension - 'in' can take a generator). Both rules recommended increasing laziness, which is not always desirable and can lead to subtle bugs. Also, a fully exhausted generator is slower than an equivalent comprehension, so the advice did not always improve performance. Thanks to David Smith, Dylan Young, and Leonidas Loucas for the report in `Issue #247 `__. 3.3.1 (2020-12-19) ------------------ * Drop Python 3.5 support. * Improved installation instructions in README. 3.3.0 (2020-10-23) ------------------ * Support Python 3.9. * Move license from ISC to MIT License. * Partially reverted the change to ``C408`` to make it apply again to when ``dict`` is called with keyword arguments, e.g. ``dict(a=1, b=2)`` will be flagged to be rewritten in the literal form ``{"a": 1, "b": 2}`` 3.2.3 (2020-06-06) ------------------ * Made ``C408`` only apply when no arguments are passed to ``dict``/``list``/``tuple``. 3.2.2 (2020-01-20) ------------------ * Remove check for dict comprehensions in rule C407 as it would also change the results for certain builtins such as ``sum()``. 3.2.1 (2020-01-20) ------------------ * Remove check for set comprehensions in rule C407 as it would change the results for certain builtins such as ``sum()``. 3.2.0 (2020-01-20) ------------------ * Add ``filter`` and ``map`` to rule C407. * Check for dict and set comprehensions in rules C407 and C412. 3.1.4 (2019-11-20) ------------------ * Remove the tuple/unpacking check from C416 to prevent false positives where the type of the iterable is changed from some iterable to a tuple. 3.1.3 (2019-11-19) ------------------ * Ensure the fix for false positives in ``C416`` rule for asynchronous comprehensions runs on Python 3.6 too. 3.1.2 (2019-11-18) ------------------ * Fix false positives in ``C416`` rule for list comprehensions returning tuples. 3.1.1 (2019-11-16) ------------------ * Fix false positives in ``C416`` rule for asynchronous comprehensions. 3.1.0 (2019-11-15) ------------------ * Update Python support to 3.5-3.8. * Fix false positives for C404 for list comprehensions not directly creating tuples. * Add ``C413`` rule that checks for unnecessary use of ``list()`` or ``reversed()`` around ``sorted()``. * Add ``C414`` rule that checks for unnecessary use of the following: * ``list()``, ``reversed()``, ``sorted()``, or ``tuple()`` within ``set`` or ``sorted()`` * ``list()`` or ``tuple()`` within ``list()`` or ``tuple()`` * ``set()`` within ``set`` * Add ``C415`` rule that checks for unnecessary reversal of an iterable via subscript within ``reversed()``, ``set()``, or ``sorted()``. * Add ``C416`` rule that checks for unnecessary list or set comprehensions that can be rewritten using ``list()`` or ``set()``. 3.0.1 (2019-10-28) ------------------ * Fix version display on ``flake8 --version`` (removing dependency on ``cached-property``). Thanks to Jon Dufresne. 3.0.0 (2019-10-25) ------------------ * Update Flake8 support to 3.0+ only. 3.0.0 was released in 2016 and the plugin hasn't been tested with it since. 2.3.0 (2019-10-25) ------------------ * Converted setuptools metadata to configuration file. This meant removing the ``__version__`` attribute from the package. If you want to inspect the installed version, use ``importlib.metadata.version("flake8-comprehensions")`` (`docs `__ / `backport `__). * Add dependencies on ``cached-property`` and ``importlib-metadata``. * Fix false negatives in ``C407`` for cases when ``enumerate`` and ``sum()`` are passed more than one argument. 2.2.0 (2019-08-12) ------------------ * Update Python support to 3.5-3.7, as 3.4 has reached its end of life. * ``C412`` rule that complains about using list comprehension with ``in``. 2.1.0 (2019-03-01) ------------------ * Add missing builtin ``enumerate`` to ``C407``. 2.0.0 (2019-02-02) ------------------ * Drop Python 2 support, only Python 3.4+ is supported now. 1.4.1 (2017-05-17) ------------------ * Fix false positives in ``C408`` for calls using ``*args`` or ``**kwargs``. 1.4.0 (2017-05-14) ------------------ * Plugin now reserves the full ``C4XX`` code space rather than just ``C40X`` * ``C408`` rule that complains about using ``tuple()``, ``list()``, or ``dict()`` instead of a literal. * ``C409`` and ``C410`` rules that complain about an unnecessary list or tuple that could be rewritten as a literal. * ``C411`` rule that complains about using list comprehension inside a ``list()`` call. 1.3.0 (2017-05-01) ------------------ * Don't allow installation with Flake8 3.2.0 which doesn't enable the plugin. This bug was fixed in Flake8 3.2.1. * Prevent false positives of ``C402`` from generators of expressions that aren't two-tuples. * ``C405`` and ``C406`` now also complain about unnecessary tuple literals. 1.2.1 (2016-06-27) ------------------ * ``C407`` rule that complains about unnecessary list comprehensions inside builtins that can work on generators. 1.2.0 (2016-07-11) ------------------ * Split all rule codes by type. This allows granular selection of the rules in flake8 configuration. 1.1.1 (2016-04-06) ------------------ * Fix crash on method calls 1.1.0 (2016-04-06) ------------------ * ``C401`` rule that complains about unnecessary list comprehensions inside calls to ``set()`` or ``dict()``. * ``C402`` rule that complains about unnecessary list literals inside calls to ``set()`` or ``dict()``. 1.0.0 (2016-04-05) ------------------ * ``C400`` rule that complains about an unnecessary usage of a generator when a list/set/dict comprehension would do. flake8-comprehensions-3.8.0/LICENSE000066400000000000000000000020621416706414200167400ustar00rootroot00000000000000MIT License Copyright (c) 2017-2021 Adam Johnson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. flake8-comprehensions-3.8.0/MANIFEST.in000066400000000000000000000003651416706414200174750ustar00rootroot00000000000000global-exclude *.py[cod] prune __pycache__ prune requirements prune tests exclude .editorconfig exclude .pre-commit-config.yaml exclude tox.ini include HISTORY.rst include LICENSE include pyproject.toml include README.rst include src/*/py.typed flake8-comprehensions-3.8.0/README.rst000066400000000000000000000155531416706414200174330ustar00rootroot00000000000000===================== flake8-comprehensions ===================== .. image:: https://img.shields.io/github/workflow/status/adamchainz/flake8-comprehensions/CI/main?style=for-the-badge :target: https://github.com/adamchainz/flake8-comprehensions/actions?workflow=CI .. image:: https://img.shields.io/pypi/v/flake8-comprehensions.svg?style=for-the-badge :target: https://pypi.org/project/flake8-comprehensions/ .. image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge :target: https://github.com/psf/black .. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=for-the-badge :target: https://github.com/pre-commit/pre-commit :alt: pre-commit A `flake8 `_ plugin that helps you write better list/set/dict comprehensions. Requirements ============ Python 3.7 to 3.10 supported. Installation ============ First, install with ``pip``: .. code-block:: sh python -m pip install flake8-comprehensions Second, if you define Flake8’s ``select`` setting, add the ``C4`` prefix to it. Otherwise, the plugin should be active by default. ---- **Linting a Django project?** Check out my book `Boost Your Django DX `__ which covers Flake8 and many other code quality tools. ---- Rules ===== C400-402: Unnecessary generator - rewrite as a ```` comprehension. --------------------------------------------------------------------------------- It's unnecessary to use ``list``, ``set``, or ``dict`` around a generator expression, since there are equivalent comprehensions for these types. For example: * Rewrite ``list(f(x) for x in foo)`` as ``[f(x) for x in foo]`` * Rewrite ``set(f(x) for x in foo)`` as ``{f(x) for x in foo}`` * Rewrite ``dict((x, f(x)) for x in foo)`` as ``{x: f(x) for x in foo}`` C403-404: Unnecessary list comprehension - rewrite as a ```` comprehension. ------------------------------------------------------------------------------------- It's unnecessary to use a list comprehension inside a call to ``set`` or ``dict``, since there are equivalent comprehensions for these types. For example: * Rewrite ``set([f(x) for x in foo])`` as ``{f(x) for x in foo}`` * Rewrite ``dict([(x, f(x)) for x in foo])`` as ``{x: f(x) for x in foo}`` C405-406: Unnecessary ```` literal - rewrite as a ```` literal. ------------------------------------------------------------------------------------- It's unnecessary to use a list or tuple literal within a call to ``set`` or ``dict``. For example: * Rewrite ``set([1, 2])`` as ``{1, 2}`` * Rewrite ``set((1, 2))`` as ``{1, 2}`` * Rewrite ``set([])`` as ``set()`` * Rewrite ``dict([(1, 2)])`` as ``{1: 2}`` * Rewrite ``dict(((1, 2),))`` as ``{1: 2}`` * Rewrite ``dict([])`` as ``{}`` C408: Unnecessary ```` call - rewrite as a literal. -------------------------------------------------------------------- It's slower to call e.g. ``dict()`` than using the empty literal, because the name ``dict`` must be looked up in the global scope in case it has been rebound. Same for the other two basic types here. For example: * Rewrite ``dict()`` as ``{}`` * Rewrite ``dict(a=1, b=2)`` as ``{"a": 1, "b": 2}`` * Rewrite ``list()`` as ``[]`` * Rewrite ``tuple()`` as ``()`` C409-410: Unnecessary ```` passed to ````\() - (remove the outer call to ````()/rewrite as a ```` literal). ----------------------------------------------------------------------------------------------------------------------------------------------------------- It's unnecessary to use a list or tuple literal within a call to ``list`` or ``tuple``, since there is literal syntax for these types. For example: * Rewrite ``tuple([1, 2])`` as ``(1, 2)`` * Rewrite ``tuple((1, 2))`` as ``(1, 2)`` * Rewrite ``tuple([])`` as ``()`` * Rewrite ``list([1, 2])`` as ``[1, 2]`` * Rewrite ``list((1, 2))`` as ``[1, 2]`` * Rewrite ``list([])`` as ``[]`` C411: Unnecessary list call - remove the outer call to list(). -------------------------------------------------------------- It's unnecessary to use a ``list`` around a list comprehension, since it is equivalent without it. For example: * Rewrite ``list([f(x) for x in foo])`` as ``[f(x) for x in foo]`` C413: Unnecessary ```` call around sorted(). ----------------------------------------------------------- It's unnecessary to use ``list()`` around ``sorted()`` as it already returns a list. It is also unnecessary to use ``reversed()`` around ``sorted()`` as the latter has a ``reverse`` argument. For example: * Rewrite ``list(sorted([2, 3, 1]))`` as ``sorted([2, 3, 1])`` * Rewrite ``reversed(sorted([2, 3, 1]))`` as ``sorted([2, 3, 1], reverse=True)`` * Rewrite ``reversed(sorted([2, 3, 1], reverse=True))`` as ``sorted([2, 3, 1])`` C414: Unnecessary ```` call within ````\(). -------------------------------------------------------------------------------------------------- It's unnecessary to double-cast or double-process iterables by wrapping the listed functions within ``list``/``set``/``sorted``/``tuple``. For example: * Rewrite ``list(list(iterable))`` as ``list(iterable)`` * Rewrite ``list(tuple(iterable))`` as ``list(iterable)`` * Rewrite ``tuple(list(iterable))`` as ``tuple(iterable)`` * Rewrite ``tuple(tuple(iterable))`` as ``tuple(iterable)`` * Rewrite ``set(set(iterable))`` as ``set(iterable)`` * Rewrite ``set(list(iterable))`` as ``set(iterable)`` * Rewrite ``set(tuple(iterable))`` as ``set(iterable)`` * Rewrite ``set(sorted(iterable))`` as ``set(iterable)`` * Rewrite ``set(reversed(iterable))`` as ``set(iterable)`` * Rewrite ``sorted(list(iterable))`` as ``sorted(iterable)`` * Rewrite ``sorted(tuple(iterable))`` as ``sorted(iterable)`` * Rewrite ``sorted(sorted(iterable))`` as ``sorted(iterable)`` * Rewrite ``sorted(reversed(iterable))`` as ``sorted(iterable)`` C415: Unnecessary subscript reversal of iterable within ````\(). ------------------------------------------------------------------------------------- It's unnecessary to reverse the order of an iterable when passing it into one of the listed functions will change the order again. For example: * Rewrite ``set(iterable[::-1])`` as ``set(iterable)`` * Rewrite ``sorted(iterable)[::-1]`` as ``sorted(iterable, reverse=True)`` * Rewrite ``reversed(iterable[::-1])`` as ``iterable`` C416: Unnecessary ```` comprehension - rewrite using ````\(). --------------------------------------------------------------------------------- It's unnecessary to use a list comprehension if the elements are unchanged. The iterable should be wrapped in ``list()`` or ``set()`` instead. For example: * Rewrite ``[x for x in iterable]`` as ``list(iterable)`` * Rewrite ``{x for x in iterable}`` as ``set(iterable)`` flake8-comprehensions-3.8.0/pyproject.toml000066400000000000000000000011101416706414200206400ustar00rootroot00000000000000[build-system] requires = ["setuptools >= 40.6.0", "wheel"] build-backend = "setuptools.build_meta" [tool.black] target-version = ['py37'] [tool.isort] profile = "black" [tool.mypy] check_untyped_defs = true disallow_any_generics = true disallow_incomplete_defs = true disallow_untyped_defs = true mypy_path = "src/" no_implicit_optional = true show_error_codes = true warn_unreachable = true warn_unused_ignores = true [[tool.mypy.overrides]] module = "tests.*" allow_untyped_defs = true [tool.pytest.ini_options] addopts = """\ --strict-config --strict-markers """ flake8-comprehensions-3.8.0/requirements/000077500000000000000000000000001416706414200204565ustar00rootroot00000000000000flake8-comprehensions-3.8.0/requirements/compile.py000077500000000000000000000016551416706414200224720ustar00rootroot00000000000000#!/usr/bin/env python import os import subprocess import sys from pathlib import Path if __name__ == "__main__": os.chdir(Path(__file__).parent) os.environ["CUSTOM_COMPILE_COMMAND"] = "requirements/compile.py" os.environ.pop("PIP_REQUIRE_VIRTUALENV", None) common_args = [ "-m", "piptools", "compile", "--generate-hashes", "--allow-unsafe", ] + sys.argv[1:] subprocess.run( ["python3.7", *common_args, "-o", "py37.txt"], check=True, capture_output=True, ) subprocess.run( ["python3.8", *common_args, "-o", "py38.txt"], check=True, capture_output=True, ) subprocess.run( ["python3.9", *common_args, "-o", "py39.txt"], check=True, capture_output=True, ) subprocess.run( ["python3.10", *common_args, "-o", "py310.txt"], check=True, capture_output=True, ) flake8-comprehensions-3.8.0/requirements/py310.txt000066400000000000000000000057771416706414200221130ustar00rootroot00000000000000# # This file is autogenerated by pip-compile with python 3.10 # To update, run: # # requirements/compile.py # attrs==21.4.0 \ --hash=sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4 \ --hash=sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd # via pytest flake8==4.0.1 \ --hash=sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d \ --hash=sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d # via pytest-flake8-path iniconfig==1.1.1 \ --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 # via pytest mccabe==0.6.1 \ --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \ --hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f # via flake8 packaging==21.3 \ --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \ --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 # via pytest pluggy==1.0.0 \ --hash=sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159 \ --hash=sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3 # via pytest py==1.11.0 \ --hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 \ --hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 # via pytest pycodestyle==2.8.0 \ --hash=sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20 \ --hash=sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f # via flake8 pyflakes==2.4.0 \ --hash=sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c \ --hash=sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e # via flake8 pyparsing==3.0.6 \ --hash=sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4 \ --hash=sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81 # via packaging pytest==6.2.5 \ --hash=sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89 \ --hash=sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134 # via # -r requirements.in # pytest-flake8-path # pytest-randomly pytest-flake8-path==1.1.0 \ --hash=sha256:7e55d16c7d26031bebff282283e02326c780e9314a1b4567f04bf0495e4d9a8f \ --hash=sha256:d86248a3f7d7be4eb18f28fa27f31a31a43f07ce8d348c30055cfddc7339f58e # via -r requirements.in pytest-randomly==3.10.3 \ --hash=sha256:22154cdcff7ba44e0599596490e6b75278ca973a33812ea6a54bf14d0b042ef1 \ --hash=sha256:b05a7a45f54cae2b5095752c6a10cb559df84448421b0420ae492dd2fb1727ef # via -r requirements.in toml==0.10.2 \ --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f # via pytest flake8-comprehensions-3.8.0/requirements/py37.txt000066400000000000000000000073701416706414200220300ustar00rootroot00000000000000# # This file is autogenerated by pip-compile with python 3.7 # To update, run: # # requirements/compile.py # attrs==21.4.0 \ --hash=sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4 \ --hash=sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd # via pytest flake8==4.0.1 \ --hash=sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d \ --hash=sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d # via pytest-flake8-path importlib-metadata==4.2.0 ; python_version < "3.8" \ --hash=sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b \ --hash=sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31 # via # -r requirements.in # flake8 # pluggy # pytest # pytest-randomly iniconfig==1.1.1 \ --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 # via pytest mccabe==0.6.1 \ --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \ --hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f # via flake8 packaging==21.3 \ --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \ --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 # via pytest pluggy==1.0.0 \ --hash=sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159 \ --hash=sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3 # via pytest py==1.11.0 \ --hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 \ --hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 # via pytest pycodestyle==2.8.0 \ --hash=sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20 \ --hash=sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f # via flake8 pyflakes==2.4.0 \ --hash=sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c \ --hash=sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e # via flake8 pyparsing==3.0.6 \ --hash=sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4 \ --hash=sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81 # via packaging pytest==6.2.5 \ --hash=sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89 \ --hash=sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134 # via # -r requirements.in # pytest-flake8-path # pytest-randomly pytest-flake8-path==1.1.0 \ --hash=sha256:7e55d16c7d26031bebff282283e02326c780e9314a1b4567f04bf0495e4d9a8f \ --hash=sha256:d86248a3f7d7be4eb18f28fa27f31a31a43f07ce8d348c30055cfddc7339f58e # via -r requirements.in pytest-randomly==3.10.3 \ --hash=sha256:22154cdcff7ba44e0599596490e6b75278ca973a33812ea6a54bf14d0b042ef1 \ --hash=sha256:b05a7a45f54cae2b5095752c6a10cb559df84448421b0420ae492dd2fb1727ef # via -r requirements.in toml==0.10.2 \ --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f # via pytest typing-extensions==4.0.1 \ --hash=sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e \ --hash=sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b # via importlib-metadata zipp==3.7.0 \ --hash=sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d \ --hash=sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375 # via importlib-metadata flake8-comprehensions-3.8.0/requirements/py38.txt000066400000000000000000000066601416706414200220320ustar00rootroot00000000000000# # This file is autogenerated by pip-compile with python 3.8 # To update, run: # # requirements/compile.py # attrs==21.4.0 \ --hash=sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4 \ --hash=sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd # via pytest flake8==4.0.1 \ --hash=sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d \ --hash=sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d # via pytest-flake8-path importlib-metadata==4.10.0 \ --hash=sha256:92a8b58ce734b2a4494878e0ecf7d79ccd7a128b5fc6014c401e0b61f006f0f6 \ --hash=sha256:b7cf7d3fef75f1e4c80a96ca660efbd51473d7e8f39b5ab9210febc7809012a4 # via pytest-randomly iniconfig==1.1.1 \ --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 # via pytest mccabe==0.6.1 \ --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \ --hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f # via flake8 packaging==21.3 \ --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \ --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 # via pytest pluggy==1.0.0 \ --hash=sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159 \ --hash=sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3 # via pytest py==1.11.0 \ --hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 \ --hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 # via pytest pycodestyle==2.8.0 \ --hash=sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20 \ --hash=sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f # via flake8 pyflakes==2.4.0 \ --hash=sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c \ --hash=sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e # via flake8 pyparsing==3.0.6 \ --hash=sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4 \ --hash=sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81 # via packaging pytest==6.2.5 \ --hash=sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89 \ --hash=sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134 # via # -r requirements.in # pytest-flake8-path # pytest-randomly pytest-flake8-path==1.1.0 \ --hash=sha256:7e55d16c7d26031bebff282283e02326c780e9314a1b4567f04bf0495e4d9a8f \ --hash=sha256:d86248a3f7d7be4eb18f28fa27f31a31a43f07ce8d348c30055cfddc7339f58e # via -r requirements.in pytest-randomly==3.10.3 \ --hash=sha256:22154cdcff7ba44e0599596490e6b75278ca973a33812ea6a54bf14d0b042ef1 \ --hash=sha256:b05a7a45f54cae2b5095752c6a10cb559df84448421b0420ae492dd2fb1727ef # via -r requirements.in toml==0.10.2 \ --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f # via pytest zipp==3.7.0 \ --hash=sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d \ --hash=sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375 # via importlib-metadata flake8-comprehensions-3.8.0/requirements/py39.txt000066400000000000000000000066601416706414200220330ustar00rootroot00000000000000# # This file is autogenerated by pip-compile with python 3.9 # To update, run: # # requirements/compile.py # attrs==21.4.0 \ --hash=sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4 \ --hash=sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd # via pytest flake8==4.0.1 \ --hash=sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d \ --hash=sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d # via pytest-flake8-path importlib-metadata==4.10.0 \ --hash=sha256:92a8b58ce734b2a4494878e0ecf7d79ccd7a128b5fc6014c401e0b61f006f0f6 \ --hash=sha256:b7cf7d3fef75f1e4c80a96ca660efbd51473d7e8f39b5ab9210febc7809012a4 # via pytest-randomly iniconfig==1.1.1 \ --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 # via pytest mccabe==0.6.1 \ --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \ --hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f # via flake8 packaging==21.3 \ --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \ --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522 # via pytest pluggy==1.0.0 \ --hash=sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159 \ --hash=sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3 # via pytest py==1.11.0 \ --hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 \ --hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 # via pytest pycodestyle==2.8.0 \ --hash=sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20 \ --hash=sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f # via flake8 pyflakes==2.4.0 \ --hash=sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c \ --hash=sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e # via flake8 pyparsing==3.0.6 \ --hash=sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4 \ --hash=sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81 # via packaging pytest==6.2.5 \ --hash=sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89 \ --hash=sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134 # via # -r requirements.in # pytest-flake8-path # pytest-randomly pytest-flake8-path==1.1.0 \ --hash=sha256:7e55d16c7d26031bebff282283e02326c780e9314a1b4567f04bf0495e4d9a8f \ --hash=sha256:d86248a3f7d7be4eb18f28fa27f31a31a43f07ce8d348c30055cfddc7339f58e # via -r requirements.in pytest-randomly==3.10.3 \ --hash=sha256:22154cdcff7ba44e0599596490e6b75278ca973a33812ea6a54bf14d0b042ef1 \ --hash=sha256:b05a7a45f54cae2b5095752c6a10cb559df84448421b0420ae492dd2fb1727ef # via -r requirements.in toml==0.10.2 \ --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f # via pytest zipp==3.7.0 \ --hash=sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d \ --hash=sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375 # via importlib-metadata flake8-comprehensions-3.8.0/requirements/requirements.in000066400000000000000000000001261416706414200235300ustar00rootroot00000000000000importlib-metadata ; python_version < "3.8" pytest pytest-flake8-path pytest-randomly flake8-comprehensions-3.8.0/setup.cfg000066400000000000000000000026571416706414200175660ustar00rootroot00000000000000[metadata] name = flake8-comprehensions version = 3.8.0 description = A flake8 plugin to help you write better list/set/dict comprehensions. long_description = file: README.rst long_description_content_type = text/x-rst author = Adam Johnson author_email = me@adamj.eu url = https://github.com/adamchainz/flake8-comprehensions project_urls = Changelog = https://github.com/adamchainz/flake8-comprehensions/blob/main/HISTORY.rst Twitter = https://twitter.com/adamchainz license = MIT keywords = flake8, comprehensions, list comprehension, set comprehension, dict comprehension classifiers = Development Status :: 5 - Production/Stable Framework :: Flake8 Intended Audience :: Developers License :: OSI Approved :: MIT License Natural Language :: English Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 license_file = LICENSE [options] package_dir= =src packages = find: include_package_data = True install_requires = flake8>=3.0,!=3.2.0 importlib-metadata ; python_version < "3.8" python_requires = >=3.7 zip_safe = False [options.packages.find] where = src [options.entry_points] flake8.extension = C4 = flake8_comprehensions:ComprehensionChecker [flake8] max-line-length = 88 extend-ignore = E203 flake8-comprehensions-3.8.0/setup.py000066400000000000000000000000461416706414200174450ustar00rootroot00000000000000from setuptools import setup setup() flake8-comprehensions-3.8.0/src/000077500000000000000000000000001416706414200165225ustar00rootroot00000000000000flake8-comprehensions-3.8.0/src/flake8_comprehensions/000077500000000000000000000000001416706414200230105ustar00rootroot00000000000000flake8-comprehensions-3.8.0/src/flake8_comprehensions/__init__.py000066400000000000000000000257201416706414200251270ustar00rootroot00000000000000import ast import sys from typing import Any, Generator, Optional, Tuple, Type if sys.version_info >= (3, 8): from importlib.metadata import version else: from importlib_metadata import version class ComprehensionChecker: """ Flake8 plugin to help you write better list/set/dict comprehensions. """ name = "flake8-comprehensions" version = version("flake8-comprehensions") __slots__ = ("tree",) def __init__(self, tree: ast.AST) -> None: self.tree = tree messages = { "C400": "C400 Unnecessary generator - rewrite as a list comprehension.", "C401": "C401 Unnecessary generator - rewrite as a set comprehension.", "C402": "C402 Unnecessary generator - rewrite as a dict comprehension.", "C403": "C403 Unnecessary list comprehension - rewrite as a set comprehension.", "C404": ( "C404 Unnecessary list comprehension - rewrite as a dict comprehension." ), "C405": "C405 Unnecessary {type} literal - ", "C406": "C406 Unnecessary {type} literal - ", "C408": "C408 Unnecessary {type} call - rewrite as a literal.", "C409": "C409 Unnecessary {type} passed to tuple() - ", "C410": "C410 Unnecessary {type} passed to list() - ", "C411": "C411 Unnecessary list call - remove the outer call to list().", "C413": "C413 Unnecessary {outer} call around {inner}(){remediation}.", "C414": "C414 Unnecessary {inner} call within {outer}().", "C415": "C415 Unnecessary subscript reversal of iterable within {func}().", "C416": "C416 Unnecessary {type} comprehension - rewrite using {type}().", } def run(self) -> Generator[Tuple[int, int, str, Type[Any]], None, None]: for node in ast.walk(self.tree): if isinstance(node, ast.Call) and isinstance(node.func, ast.Name): num_positional_args = len(node.args) num_keyword_args = len(node.keywords) if ( num_positional_args == 1 and isinstance(node.args[0], ast.GeneratorExp) and node.func.id in ("list", "set") ): msg_key = {"list": "C400", "set": "C401"}[node.func.id] yield ( node.lineno, node.col_offset, self.messages[msg_key], type(self), ) elif ( num_positional_args == 1 and isinstance(node.args[0], (ast.GeneratorExp, ast.ListComp)) and isinstance(node.args[0].elt, ast.Tuple) and len(node.args[0].elt.elts) == 2 and node.func.id == "dict" ): if isinstance(node.args[0], ast.GeneratorExp): msg = "C402" else: msg = "C404" yield ( node.lineno, node.col_offset, self.messages[msg], type(self), ) elif ( num_positional_args == 1 and isinstance(node.args[0], ast.ListComp) and node.func.id in ("list", "set") ): msg_key = {"list": "C411", "set": "C403"}[node.func.id] yield ( node.lineno, node.col_offset, self.messages[msg_key], type(self), ) elif num_positional_args == 1 and ( isinstance(node.args[0], ast.Tuple) and node.func.id == "tuple" or isinstance(node.args[0], ast.List) and node.func.id == "list" ): suffix = "remove the outer call to {func}()." msg_key = {"tuple": "C409", "list": "C410"}[node.func.id] msg = self.messages[msg_key] + suffix yield ( node.lineno, node.col_offset, msg.format( type=type(node.args[0]).__name__.lower(), func=node.func.id ), type(self), ) elif ( num_positional_args == 1 and isinstance(node.args[0], (ast.Tuple, ast.List)) and node.func.id in ("tuple", "list", "set", "dict") ): suffix = "rewrite as a {func} literal." msg_key = { "tuple": "C409", "list": "C410", "set": "C405", "dict": "C406", }[node.func.id] msg = self.messages[msg_key] + suffix yield ( node.lineno, node.col_offset, msg.format( type=type(node.args[0]).__name__.lower(), func=node.func.id ), type(self), ) elif ( num_positional_args == 0 and not has_star_args(node) and not has_double_star_args(node) and node.func.id == "dict" ): yield ( node.lineno, node.col_offset, self.messages["C408"].format(type=node.func.id), type(self), ) elif ( num_positional_args == 0 and num_keyword_args == 0 and node.func.id in ("tuple", "list") ): yield ( node.lineno, node.col_offset, self.messages["C408"].format(type=node.func.id), type(self), ) elif ( node.func.id in {"list", "reversed"} and num_positional_args > 0 and isinstance(node.args[0], ast.Call) and isinstance(node.args[0].func, ast.Name) and node.args[0].func.id == "sorted" ): remediation = "" if node.func.id == "reversed": reverse_flag_value: Optional[bool] = False for keyword in node.args[0].keywords: if keyword.arg != "reverse": continue if isinstance(keyword.value, ast.NameConstant): reverse_flag_value = keyword.value.value elif isinstance(keyword.value, ast.Num): reverse_flag_value = bool(keyword.value.n) else: # Complex value reverse_flag_value = None if reverse_flag_value is None: remediation = " - toggle reverse argument to sorted()" else: remediation = " - use sorted(..., reverse={!r})".format( not reverse_flag_value ) msg = self.messages["C413"].format( inner=node.args[0].func.id, outer=node.func.id, remediation=remediation, ) yield ( node.lineno, node.col_offset, msg, type(self), ) elif ( num_positional_args > 0 and isinstance(node.args[0], ast.Call) and isinstance(node.args[0].func, ast.Name) and ( ( node.func.id in {"set", "sorted"} and node.args[0].func.id in {"list", "reversed", "sorted", "tuple"} ) or ( node.func.id in {"list", "tuple"} and node.args[0].func.id in {"list", "tuple"} ) or (node.func.id == "set" and node.args[0].func.id == "set") ) ): yield ( node.lineno, node.col_offset, self.messages["C414"].format( inner=node.args[0].func.id, outer=node.func.id ), type(self), ) elif ( node.func.id in {"reversed", "set", "sorted"} and num_positional_args > 0 and isinstance(node.args[0], ast.Subscript) and isinstance(node.args[0].slice, ast.Slice) and node.args[0].slice.lower is None and node.args[0].slice.upper is None and isinstance(node.args[0].slice.step, ast.UnaryOp) and isinstance(node.args[0].slice.step.op, ast.USub) and isinstance(node.args[0].slice.step.operand, ast.Num) and node.args[0].slice.step.operand.n == 1 ): yield ( node.lineno, node.col_offset, self.messages["C415"].format(func=node.func.id), type(self), ) elif isinstance(node, (ast.ListComp, ast.SetComp)): if ( len(node.generators) == 1 and not node.generators[0].ifs and not node.generators[0].is_async and ( isinstance(node.elt, ast.Name) and isinstance(node.generators[0].target, ast.Name) and node.elt.id == node.generators[0].target.id ) ): yield ( node.lineno, node.col_offset, self.messages["C416"].format(type=comp_type[node.__class__]), type(self), ) def has_star_args(call_node: ast.Call) -> bool: return any(isinstance(a, ast.Starred) for a in call_node.args) def has_double_star_args(call_node: ast.Call) -> bool: return any(k.arg is None for k in call_node.keywords) comp_type = { ast.DictComp: "dict", ast.ListComp: "list", ast.SetComp: "set", } flake8-comprehensions-3.8.0/src/flake8_comprehensions/py.typed000066400000000000000000000000001416706414200244750ustar00rootroot00000000000000flake8-comprehensions-3.8.0/tests/000077500000000000000000000000001416706414200170755ustar00rootroot00000000000000flake8-comprehensions-3.8.0/tests/__init__.py000066400000000000000000000000001416706414200211740ustar00rootroot00000000000000flake8-comprehensions-3.8.0/tests/test_flake8_comprehensions.py000066400000000000000000000531541416706414200250040ustar00rootroot00000000000000import re import sys from textwrap import dedent import pytest if sys.version_info >= (3, 8): from importlib.metadata import version else: from importlib_metadata import version @pytest.fixture def flake8_path(flake8_path): (flake8_path / "setup.cfg").write_text( dedent( """\ [flake8] select = C4 """ ) ) yield flake8_path def test_version(flake8_path): result = flake8_path.run_flake8(["--version"]) version_regex = r"flake8-comprehensions:( )*" + version("flake8-comprehensions") unwrapped = "".join(result.out_lines) assert re.search(version_regex, unwrapped) # C400 def test_C400_pass_1(flake8_path): (flake8_path / "example.py").write_text("foo = [x + 1 for x in range(10)]") result = flake8_path.run_flake8() assert result.out_lines == [] def test_C400_fail_1(flake8_path): (flake8_path / "example.py").write_text("foo = list(x + 1 for x in range(10))") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C400 Unnecessary generator - rewrite as a list " + "comprehension." ] def test_C400_fail_2(flake8_path): (flake8_path / "example.py").write_text( dedent( """\ foobar = list( str(x) for x in range(10) ) """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:10: C400 Unnecessary generator - rewrite as a list " + "comprehension." ] # C401 def test_C401_pass_1(flake8_path): (flake8_path / "example.py").write_text("foo = {x + 1 for x in range(10)}") result = flake8_path.run_flake8() assert result.out_lines == [] def test_C401_fail_1(flake8_path): (flake8_path / "example.py").write_text("foo = set(x + 1 for x in range(10))") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C401 Unnecessary generator - rewrite as a set " + "comprehension." ] def test_C401_fail_2(flake8_path): (flake8_path / "example.py").write_text( dedent( """\ foobar = set( str(x) for x in range(10) ) """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:10: C401 Unnecessary generator - rewrite as a set " + "comprehension." ] # C402 def test_C402_pass_1(flake8_path): (flake8_path / "example.py").write_text("foo = {x: str(x) for x in range(10)}") result = flake8_path.run_flake8() assert result.out_lines == [] def test_C402_pass_2(flake8_path): (flake8_path / "example.py").write_text( dedent( """\ foo = ['a=1', 'b=2', 'c=3'] dict(pair.split('=') for pair in foo) """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [] def test_C402_pass_3(flake8_path): (flake8_path / "example.py").write_text( dedent( """\ foo = [('a', 1), ('b', 2), ('c', 3)] dict(pair for pair in foo if pair[1] % 2 == 0) """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [] def test_C402_fail_1(flake8_path): (flake8_path / "example.py").write_text( "foo = dict((x, str(x)) for x in range(10))" ) result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C402 Unnecessary generator - rewrite as a dict " + "comprehension." ] def test_C402_fail_2(flake8_path): (flake8_path / "example.py").write_text( dedent( """\ foobar = dict( (x, str(x)) for x in range(10) ) """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:10: C402 Unnecessary generator - rewrite as a dict " + "comprehension." ] # C403 def test_C403_pass_1(flake8_path): (flake8_path / "example.py").write_text("foo = {x + 1 for x in range(10)}") result = flake8_path.run_flake8() assert result.out_lines == [] def test_C403_fail_1(flake8_path): (flake8_path / "example.py").write_text("foo = set([x + 1 for x in range(10)])") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C403 Unnecessary list comprehension - rewrite as a " + "set comprehension." ] # C404 def test_C404_pass_1(flake8_path): (flake8_path / "example.py").write_text("foo = {x: x for x in range(10)}") result = flake8_path.run_flake8() assert result.out_lines == [] def test_C404_pass_2(flake8_path): # Previously a false positive (flake8_path / "example.py").write_text( "foo = dict([x.split('=') for x in ['a=1', 'b=2']])" ) result = flake8_path.run_flake8() assert result.out_lines == [] def test_C404_fail_1(flake8_path): (flake8_path / "example.py").write_text("foo = dict([(x, x) for x in range(10)])") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C404 Unnecessary list comprehension - rewrite as a " + "dict comprehension." ] # C405 def test_C405_pass_1(flake8_path): (flake8_path / "example.py").write_text("foo = set(range)") result = flake8_path.run_flake8() assert result.out_lines == [] def test_C405_fail_1(flake8_path): (flake8_path / "example.py").write_text("foo = set([])") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C405 Unnecessary list literal - rewrite as a set " + "literal." ] def test_C405_fail_2(flake8_path): (flake8_path / "example.py").write_text("foo = set([1])") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C405 Unnecessary list literal - rewrite as a set " + "literal." ] def test_C405_fail_3(flake8_path): (flake8_path / "example.py").write_text("foo = set(())") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C405 Unnecessary tuple literal - rewrite as a set " + "literal." ] def test_C405_fail_4(flake8_path): (flake8_path / "example.py").write_text("foo = set((1,))") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C405 Unnecessary tuple literal - rewrite as a set " + "literal." ] # C406 def test_C406_pass_1(flake8_path): (flake8_path / "example.py").write_text("foo = dict(range)") result = flake8_path.run_flake8() assert result.out_lines == [] def test_C406_fail_1(flake8_path): (flake8_path / "example.py").write_text("foo = dict([])") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C406 Unnecessary list literal - rewrite as a dict " + "literal." ] def test_C406_fail_2(flake8_path): (flake8_path / "example.py").write_text("foo = dict([(1, 2)])") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C406 Unnecessary list literal - rewrite as a dict " + "literal." ] def test_C406_fail_3(flake8_path): (flake8_path / "example.py").write_text("foo = dict(())") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C406 Unnecessary tuple literal - rewrite as a dict " + "literal." ] def test_C406_fail_4(flake8_path): (flake8_path / "example.py").write_text("foo = dict(((1, 2),))") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C406 Unnecessary tuple literal - rewrite as a dict " + "literal." ] # C408 def test_C408_pass_1(flake8_path): (flake8_path / "example.py").write_text("()") result = flake8_path.run_flake8() assert result.out_lines == [] def test_C408_pass_2(flake8_path): (flake8_path / "example.py").write_text("[]") result = flake8_path.run_flake8() assert result.out_lines == [] def test_C408_pass_3(flake8_path): (flake8_path / "example.py").write_text("{}") result = flake8_path.run_flake8() assert result.out_lines == [] def test_C408_pass_4(flake8_path): (flake8_path / "example.py").write_text("set()") result = flake8_path.run_flake8() assert result.out_lines == [] def test_C408_pass_5(flake8_path): (flake8_path / "example.py").write_text( dedent( """\ foo = [('foo', 2)] dict(foo) """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [] def test_C408_pass_6(flake8_path): (flake8_path / "example.py").write_text( dedent( """\ foo = {} dict(bar=1, **foo) """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [] def test_C408_pass_7(flake8_path): (flake8_path / "example.py").write_text( dedent( """\ foo = [1, 2] list(foo) """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [] def test_C408_pass_8(flake8_path): (flake8_path / "example.py").write_text( dedent( """\ foo = [1, 2] list(*foo) """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [] def test_C408_fail_1(flake8_path): (flake8_path / "example.py").write_text("tuple()") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:1: C408 Unnecessary tuple call - rewrite as a literal." ] def test_C408_fail_2(flake8_path): (flake8_path / "example.py").write_text("list()") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:1: C408 Unnecessary list call - rewrite as a literal." ] def test_C408_fail_3(flake8_path): (flake8_path / "example.py").write_text("dict()") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:1: C408 Unnecessary dict call - rewrite as a literal." ] def test_C408_fail_4(flake8_path): (flake8_path / "example.py").write_text("dict(a=1)") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:1: C408 Unnecessary dict call - rewrite as a literal." ] # C409 def test_C409_pass_1(flake8_path): (flake8_path / "example.py").write_text("foo = tuple(range)") result = flake8_path.run_flake8() assert result.out_lines == [] def test_C409_fail_1(flake8_path): (flake8_path / "example.py").write_text("foo = tuple([])") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C409 Unnecessary list passed to tuple() - rewrite as " + "a tuple literal." ] def test_C409_fail_2(flake8_path): (flake8_path / "example.py").write_text("foo = tuple([1, 2])") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C409 Unnecessary list passed to tuple() - rewrite as " + "a tuple literal." ] def test_C409_fail_3(flake8_path): (flake8_path / "example.py").write_text("foo = tuple(())") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C409 Unnecessary tuple passed to tuple() - remove " + "the outer call to tuple()." ] def test_C409_fail_4(flake8_path): (flake8_path / "example.py").write_text("foo = tuple((1, 2))") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C409 Unnecessary tuple passed to tuple() - remove " + "the outer call to tuple()." ] # C410 def test_C410_pass_1(flake8_path): (flake8_path / "example.py").write_text("foo = list(range)") result = flake8_path.run_flake8() assert result.out_lines == [] def test_C410_fail_1(flake8_path): (flake8_path / "example.py").write_text("foo = list([])") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C410 Unnecessary list passed to list() - remove the " + "outer call to list()." ] def test_C410_fail_2(flake8_path): (flake8_path / "example.py").write_text("foo = list([1, 2])") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C410 Unnecessary list passed to list() - remove the " + "outer call to list()." ] def test_C410_fail_3(flake8_path): (flake8_path / "example.py").write_text("foo = list(())") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C410 Unnecessary tuple passed to list() - rewrite as " + "a list literal." ] def test_C410_fail_4(flake8_path): (flake8_path / "example.py").write_text("foo = list((1, 2))") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:7: C410 Unnecessary tuple passed to list() - rewrite as " + "a list literal." ] # C411 def test_C411_pass_1(flake8_path): (flake8_path / "example.py").write_text("[x + 1 for x in range(10)]") result = flake8_path.run_flake8() assert result.out_lines == [] def test_C411_fail_1(flake8_path): (flake8_path / "example.py").write_text("list([x + 1 for x in range(10)])") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:1: C411 Unnecessary list call - remove the outer call " + "to list()." ] # C413 def test_C413_pass_1(flake8_path): (flake8_path / "example.py").write_text( dedent( """\ sorted([2, 3, 1]) sorted([2, 3, 1], reverse=True) sorted([2, 3, 1], reverse=False) sorted([2, 3, 1], reverse=0) sorted([2, 3, 1], reverse=1) reversed([2, 3, 1]) """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [] def test_C413_fail_1(flake8_path): (flake8_path / "example.py").write_text( dedent( """\ list(sorted([2, 3, 1])) reversed(sorted([2, 3, 1])) reversed(sorted([2, 3, 1], reverse=False)) reversed(sorted([2, 3, 1], reverse=True)) reversed(sorted([2, 3, 1], reverse=0)) reversed(sorted([2, 3, 1], reverse=1)) reversed(sorted([2, 3, 1], reverse=bool())) reversed(sorted([2, 3, 1], reverse=not True)) """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:1: C413 Unnecessary list call around sorted().", "./example.py:2:1: C413 Unnecessary reversed call around sorted()" + " - use sorted(..., reverse=True).", "./example.py:3:1: C413 Unnecessary reversed call around sorted()" + " - use sorted(..., reverse=True).", "./example.py:4:1: C413 Unnecessary reversed call around sorted()" + " - use sorted(..., reverse=False).", "./example.py:5:1: C413 Unnecessary reversed call around sorted()" + " - use sorted(..., reverse=True).", "./example.py:6:1: C413 Unnecessary reversed call around sorted()" + " - use sorted(..., reverse=False).", "./example.py:7:1: C413 Unnecessary reversed call around sorted()" + " - toggle reverse argument to sorted().", "./example.py:8:1: C413 Unnecessary reversed call around sorted()" + " - toggle reverse argument to sorted().", ] # C414 def test_C414_pass_1(flake8_path): (flake8_path / "example.py").write_text( dedent( """\ a = [2, 3, 1] list(set(a)) tuple(set(a)) sorted(set(a)) """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [] def test_C414_fail_1(flake8_path): (flake8_path / "example.py").write_text( dedent( """\ a = [2, 3, 1] list(list(a)) list(tuple(a)) tuple(list(a)) tuple(tuple(a)) set(set(a)) set(list(a)) set(tuple(a)) set(sorted(a)) set(sorted(a, reverse=True)) set(reversed(a)) sorted(list(a)) sorted(tuple(a)) sorted(sorted(a)) sorted(sorted(a), reverse=True) sorted(sorted(a, reverse=True)) sorted(sorted(a, reverse=True), reverse=True) sorted(reversed(a)) sorted(reversed(a), reverse=True) """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:2:1: C414 Unnecessary list call within list().", "./example.py:3:1: C414 Unnecessary tuple call within list().", "./example.py:4:1: C414 Unnecessary list call within tuple().", "./example.py:5:1: C414 Unnecessary tuple call within tuple().", "./example.py:6:1: C414 Unnecessary set call within set().", "./example.py:7:1: C414 Unnecessary list call within set().", "./example.py:8:1: C414 Unnecessary tuple call within set().", "./example.py:9:1: C414 Unnecessary sorted call within set().", "./example.py:10:1: C414 Unnecessary sorted call within set().", "./example.py:11:1: C414 Unnecessary reversed call within set().", "./example.py:12:1: C414 Unnecessary list call within sorted().", "./example.py:13:1: C414 Unnecessary tuple call within sorted().", "./example.py:14:1: C414 Unnecessary sorted call within sorted().", "./example.py:15:1: C414 Unnecessary sorted call within sorted().", "./example.py:16:1: C414 Unnecessary sorted call within sorted().", "./example.py:17:1: C414 Unnecessary sorted call within sorted().", "./example.py:18:1: C414 Unnecessary reversed call within sorted().", "./example.py:19:1: C414 Unnecessary reversed call within sorted().", ] # C415 def test_C415_pass_1(flake8_path): (flake8_path / "example.py").write_text( dedent( """\ set([2, 3, 1][::1]) sorted([2, 3, 1][::1]) reversed([2, 3, 1][::1]) """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [] def test_C415_fail_1(flake8_path): (flake8_path / "example.py").write_text( dedent( """\ set([2, 3, 1][::-1]) sorted([2, 3, 1][::-1]) sorted([2, 3, 1][::-1], reverse=True) reversed([2, 3, 1][::-1]) """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:1: C415 Unnecessary subscript reversal of iterable " + "within set().", "./example.py:2:1: C415 Unnecessary subscript reversal of iterable " + "within sorted().", "./example.py:3:1: C415 Unnecessary subscript reversal of iterable " + "within sorted().", "./example.py:4:1: C415 Unnecessary subscript reversal of iterable " + "within reversed().", ] # C416 def test_C416_pass_1(flake8_path): (flake8_path / "example.py").write_text( dedent( """\ [str(x) for x in range(5)] [x + 1 for x in range(5)] [x for x in range(5) if x % 2] {str(x) for x in range(5)} {x + 1 for x in range(5)} {x for x in range(5) if x % 2} """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [] def test_C416_pass_2_async_list(flake8_path): (flake8_path / "example.py").write_text( dedent( """\ async def foo(): [x async for x in range(5)] """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [] def test_C416_pass_3_async_set(flake8_path): (flake8_path / "example.py").write_text( dedent( """\ async def foo(): return {x async for x in range(5)} """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [] def test_C416_pass_4_tuples(flake8_path): (flake8_path / "example.py").write_text("[(x, y, 1) for x, y in []]") result = flake8_path.run_flake8() assert result.out_lines == [] def test_C416_fail_5_unpacking(flake8_path): # We can't assume unpacking came from tuples, so these examples should pass (flake8_path / "example.py").write_text( dedent( """\ [(x, y) for x, y in zip('abc', '123')] [(x, y) for (x, y) in zip('abc', '123')] {(x, y) for x, y in zip('abc', '123')} {(x, y) for (x, y) in zip('abc', '123')} """ ) ) result = flake8_path.run_flake8() assert result.out_lines == [] def test_C416_fail_1_list(flake8_path): (flake8_path / "example.py").write_text("[x for x in range(5)]") result = flake8_path.run_flake8() # Column offset for list comprehensions was incorrect in Python < 3.8. # See https://bugs.python.org/issue31241 for details. col_offset = 1 if sys.version_info >= (3, 8) else 2 assert result.out_lines == [ "./example.py:1:%d: C416 Unnecessary list comprehension - rewrite using list()." % col_offset, ] def test_C416_fail_2_set(flake8_path): (flake8_path / "example.py").write_text("{x for x in range(5)}") result = flake8_path.run_flake8() assert result.out_lines == [ "./example.py:1:1: C416 Unnecessary set comprehension - rewrite using set().", ] flake8-comprehensions-3.8.0/tox.ini000066400000000000000000000003621416706414200172470ustar00rootroot00000000000000[tox] isolated_build = True envlist = py{36,37,38,39,310} [testenv] commands = python -W error::DeprecationWarning -W error::PendingDeprecationWarning -m pytest {posargs} deps = -r requirements/{envname}.txt setenv = PYTHONDEVMODE=1