Initial commit
This commit is contained in:
commit
1ce8864a60
34
.github/workflows/build.yaml
vendored
Normal file
34
.github/workflows/build.yaml
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-publish:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python_version: ['3.9']
|
||||||
|
os: [ubuntu-latest]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Python ${{ matrix.python_version }}
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python_version }}
|
||||||
|
|
||||||
|
- name: Install tox
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade --upgrade-strategy eager tox
|
||||||
|
|
||||||
|
- name: Build and install wheel
|
||||||
|
run: |
|
||||||
|
tox run -m build
|
||||||
|
python -m pip install dist/*.whl
|
55
.github/workflows/release.yaml
vendored
Normal file
55
.github/workflows/release.yaml
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "[0-9]+.[0-9]+.[0-9]+*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Specifying a GitHub environment is optional, but strongly encouraged
|
||||||
|
environment: release
|
||||||
|
permissions:
|
||||||
|
# IMPORTANT: this permission is mandatory for trusted publishing
|
||||||
|
id-token: write
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Python 3.9
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: 3.9
|
||||||
|
|
||||||
|
- name: Install tox
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade --upgrade-strategy eager tox
|
||||||
|
|
||||||
|
- name: Build and install wheel
|
||||||
|
run: |
|
||||||
|
tox run -m build
|
||||||
|
python -m pip install dist/*.whl
|
||||||
|
|
||||||
|
- name: "Publish distribution 📦 to PyPI"
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
|
||||||
|
- name: Get release name
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
git fetch --depth=1 origin +refs/tags/*:refs/tags/* # github is dumb
|
||||||
|
echo "release_name=$(git tag -l --format "%(refname:strip=2): %(contents:lines=1)" ${{ github.ref_name }})" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
with:
|
||||||
|
name: "${{ env.release_name }}"
|
||||||
|
draft: false
|
||||||
|
files: |
|
||||||
|
dist/*.whl
|
88
.gitignore
vendored
Normal file
88
.gitignore
vendored
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion
|
||||||
|
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
## Directory-based project format:
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
### Other editors
|
||||||
|
.*.swp
|
||||||
|
nbproject/
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.zip
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# ruff
|
||||||
|
.ruff_cache
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# for testing
|
||||||
|
temp/
|
||||||
|
tmp/
|
33
.pre-commit-config.yaml
Normal file
33
.pre-commit-config.yaml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.5.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: check-yaml
|
||||||
|
- id: debug-statements
|
||||||
|
- id: double-quote-string-fixer
|
||||||
|
- id: name-tests-test
|
||||||
|
- repo: https://github.com/asottile/reorder-python-imports
|
||||||
|
rev: v3.12.0
|
||||||
|
hooks:
|
||||||
|
- id: reorder-python-imports
|
||||||
|
args: [--py38-plus, --add-import, 'from __future__ import annotations']
|
||||||
|
- repo: https://github.com/hhatto/autopep8
|
||||||
|
rev: v2.0.4
|
||||||
|
hooks:
|
||||||
|
- id: autopep8
|
||||||
|
- repo: https://github.com/asottile/add-trailing-comma
|
||||||
|
rev: v3.1.0
|
||||||
|
hooks:
|
||||||
|
- id: add-trailing-comma
|
||||||
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
|
# Ruff version.
|
||||||
|
rev: v0.1.14
|
||||||
|
hooks:
|
||||||
|
- id: ruff
|
||||||
|
args: [ --fix ]
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
|
rev: v1.8.0
|
||||||
|
hooks:
|
||||||
|
- id: mypy
|
19
LICENSE
Normal file
19
LICENSE
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2014 pre-commit dev team: Anthony Sottile, Ken Struys
|
||||||
|
|
||||||
|
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.
|
38
README.md
Normal file
38
README.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
[](https://github.com/lordwelch/flake8-no-nested-comprehensions/actions/workflows/build.yaml)
|
||||||
|
[](https://results.pre-commit.ci/latest/github/lordwelch/flake8-no-nested-comprehensions/main)
|
||||||
|
|
||||||
|
flake8-no-nested-comprehensions
|
||||||
|
================
|
||||||
|
|
||||||
|
flake8 plugin which forbids nested comprehensions
|
||||||
|
|
||||||
|
## installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install flake8-no-nested-comprehensions
|
||||||
|
```
|
||||||
|
|
||||||
|
## flake8 codes
|
||||||
|
|
||||||
|
| Code | Description |
|
||||||
|
|--------|----------------------------------|
|
||||||
|
| CMP100 | do not use nested comprehensions |
|
||||||
|
|
||||||
|
## rationale
|
||||||
|
|
||||||
|
I don't like them.
|
||||||
|
If you need them for performance you can put a `# noqa: CMP100` and preferrably put a comment in explaining it.
|
||||||
|
|
||||||
|
## as a pre-commit hook
|
||||||
|
|
||||||
|
See [pre-commit](https://github.com/pre-commit/pre-commit) for instructions
|
||||||
|
|
||||||
|
Sample `.pre-commit-config.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- repo: https://github.com/pycqa/flake8
|
||||||
|
rev: 3.8.1
|
||||||
|
hooks:
|
||||||
|
- id: flake8
|
||||||
|
additional_dependencies: [flake8-no-nested-comprehensions==1.0.0]
|
||||||
|
```
|
54
flake8_no_nested_comprehensions.py
Normal file
54
flake8_no_nested_comprehensions.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import importlib.metadata
|
||||||
|
from typing import Any
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
|
import argparse
|
||||||
|
import flake8.options.manager
|
||||||
|
from collections.abc import Generator
|
||||||
|
|
||||||
|
|
||||||
|
class Visitor(ast.NodeVisitor):
|
||||||
|
def __init__(self, max_generators: int) -> None:
|
||||||
|
self.problems: list[tuple[int, int]] = []
|
||||||
|
self.max_generators = max_generators
|
||||||
|
|
||||||
|
self.visit_ListComp = self._visit_max_generators
|
||||||
|
self.visit_SetComp = self._visit_max_generators
|
||||||
|
self.visit_GeneratorExp = self._visit_max_generators
|
||||||
|
self.visit_DictComp = self._visit_max_generators
|
||||||
|
|
||||||
|
def _visit_max_generators(self, node: ast.ListComp | ast.SetComp | ast.GeneratorExp | ast.DictComp) -> None:
|
||||||
|
if len(node.generators) > self.max_generators:
|
||||||
|
self.problems.append((node.lineno, node.col_offset))
|
||||||
|
self.generic_visit(node)
|
||||||
|
|
||||||
|
|
||||||
|
class Plugin:
|
||||||
|
name = __name__
|
||||||
|
version = importlib.metadata.version(__name__)
|
||||||
|
max_generators = 1
|
||||||
|
|
||||||
|
def __init__(self, tree: ast.AST) -> None:
|
||||||
|
self._tree = tree
|
||||||
|
|
||||||
|
def add_options(manager: flake8.options.manager.OptionManager) -> None: # pragma: no cover
|
||||||
|
manager.add_option(
|
||||||
|
'--max-comprehensions', type=int, metavar='n',
|
||||||
|
default=1, parse_from_config=True,
|
||||||
|
help='Maximum allowed generators in a single comprehension. (Default: %(default)s)',
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_options(options: argparse.Namespace) -> None: # pragma: no cover
|
||||||
|
Plugin.max_generators = options.max_comprehensions
|
||||||
|
|
||||||
|
def run(self) -> Generator[tuple[int, int, str, type[Any]], None, None]:
|
||||||
|
visitor = Visitor(self.max_generators)
|
||||||
|
visitor.visit(self._tree)
|
||||||
|
for line, col in visitor.problems:
|
||||||
|
yield line, col, 'CMP100 no nested comprehension', type(self)
|
114
pyproject.toml
Normal file
114
pyproject.toml
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.2", "wheel", "setuptools_scm[toml]>=3.4"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "flake8_no_nested_comprehensions"
|
||||||
|
description = "A flake8 plugin to disallow nested comprehensions"
|
||||||
|
authors = [{name = "Timmy Welch", email = "timmy@narnian.us"}]
|
||||||
|
license = {text = "MIT"}
|
||||||
|
classifiers = [
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
|
]
|
||||||
|
urls = {Homepage = "https://github.com/lordwelch/flake8_no_nested_comprehensions"}
|
||||||
|
requires-python = ">=3.9"
|
||||||
|
dependencies = ["flake8>4"]
|
||||||
|
dynamic = ["version"]
|
||||||
|
|
||||||
|
[project.readme]
|
||||||
|
file = "README.md"
|
||||||
|
content-type = "text/markdown"
|
||||||
|
|
||||||
|
[tool.setuptools_scm]
|
||||||
|
local_scheme = "no-local-version"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
py-modules = ["flake8_no_nested_comprehensions"]
|
||||||
|
include-package-data = true
|
||||||
|
license-files = ["LICENSE"]
|
||||||
|
|
||||||
|
[project.entry-points."flake8.extension"]
|
||||||
|
CMP = "flake8_no_nested_comprehensions:Plugin"
|
||||||
|
|
||||||
|
[tool.tox]
|
||||||
|
legacy_tox_ini = """
|
||||||
|
[tox:tox]
|
||||||
|
envlist = py3.9
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
deps = -rrequirements-dev.txt
|
||||||
|
commands =
|
||||||
|
coverage erase
|
||||||
|
coverage run -m pytest {posargs:tests}
|
||||||
|
coverage report
|
||||||
|
|
||||||
|
[testenv:wheel]
|
||||||
|
description = Generate wheel and tar.gz
|
||||||
|
labels =
|
||||||
|
release
|
||||||
|
build
|
||||||
|
skip_install = true
|
||||||
|
deps =
|
||||||
|
build
|
||||||
|
commands_pre =
|
||||||
|
-python -c 'import shutil,pathlib; \
|
||||||
|
shutil.rmtree("./build/", ignore_errors=True); \
|
||||||
|
shutil.rmtree("./dist/", ignore_errors=True)'
|
||||||
|
commands =
|
||||||
|
python -m build
|
||||||
|
|
||||||
|
[testenv:pypi-upload]
|
||||||
|
description = Upload wheel to PyPi
|
||||||
|
platform = Linux
|
||||||
|
labels =
|
||||||
|
release
|
||||||
|
skip_install = true
|
||||||
|
depends = wheel
|
||||||
|
deps =
|
||||||
|
twine
|
||||||
|
passenv =
|
||||||
|
TWINE_*
|
||||||
|
setenv =
|
||||||
|
TWINE_NON_INTERACTIVE=true
|
||||||
|
commands =
|
||||||
|
python -m twine upload dist/*.whl dist/*.tar.gz
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
[tool.pep8]
|
||||||
|
ignore = "E265,E501"
|
||||||
|
max_line_length = "120"
|
||||||
|
|
||||||
|
[tool.autopep8]
|
||||||
|
max_line_length = 120
|
||||||
|
ignore = "E265,E501"
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
check_untyped_defs = true
|
||||||
|
disallow_any_generics = true
|
||||||
|
warn_return_any = false
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
no_implicit_optional = true
|
||||||
|
warn_redundant_casts = true
|
||||||
|
warn_unused_ignores = true
|
||||||
|
disable_error_code = ['method-assign']
|
||||||
|
|
||||||
|
[[tool.mypy.overrides]]
|
||||||
|
module = ["testing.*"]
|
||||||
|
warn_return_any = false
|
||||||
|
disallow_untyped_defs = false
|
||||||
|
|
||||||
|
[[tool.mypy.overrides]]
|
||||||
|
module = ["tests.*"]
|
||||||
|
warn_return_any = false
|
||||||
|
disallow_untyped_defs = false
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 120
|
||||||
|
extend-safe-fixes = ["TCH"]
|
||||||
|
extend-select = ["COM812", "TCH"]
|
5
requirements-dev.txt
Normal file
5
requirements-dev.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
build
|
||||||
|
covdefaults
|
||||||
|
coverage
|
||||||
|
pytest
|
||||||
|
tox
|
25
tests/no_nested_comprehensions_test.py
Normal file
25
tests/no_nested_comprehensions_test.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import ast
|
||||||
|
|
||||||
|
from flake8_no_nested_comprehensions import Plugin
|
||||||
|
|
||||||
|
|
||||||
|
def _results(s: str) -> set[str]:
|
||||||
|
tree = ast.parse(s)
|
||||||
|
plugin = Plugin(tree)
|
||||||
|
return {f'{line}:{col} {msg}' for line, col, msg, _ in plugin.run()}
|
||||||
|
|
||||||
|
|
||||||
|
def test_trivial_case() -> None:
|
||||||
|
assert _results('') == set()
|
||||||
|
|
||||||
|
|
||||||
|
def test_nested_comprehension() -> None:
|
||||||
|
ret = _results('[attr for style in styles for attr in {"testing", "set"}]')
|
||||||
|
assert ret == {'1:0 CMP100 no nested comprehension'}
|
||||||
|
|
||||||
|
|
||||||
|
def test_allowed_comprehension() -> None:
|
||||||
|
ret = _results('[style for style in {"testing", "set"}]')
|
||||||
|
assert ret == set()
|
Loading…
x
Reference in New Issue
Block a user