Skip to content

Commit 36fa1f1

Browse files
authored
Use Ruff instead of other Python linters (#50)
* Replace most Python linters with ruff * Fix new detected issues * Update CHANGELOG
1 parent 9c38fce commit 36fa1f1

22 files changed

+287
-257
lines changed

.codespell.allow

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
astroid

.pre-commit-config.yaml

Lines changed: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ repos:
2020
- id: trailing-whitespace
2121

2222
- repo: https://github.com/Lucas-C/pre-commit-hooks
23-
rev: v1.4.1
23+
rev: v1.5.1
2424
hooks:
2525
- id: insert-license
2626
files: \.py$
@@ -37,25 +37,19 @@ repos:
3737
additional_dependencies: [tomli]
3838

3939
- repo: https://github.com/codespell-project/codespell
40-
rev: v2.2.2
40+
rev: v2.2.4
4141
hooks:
4242
- id: codespell
4343
files: .*\.(py|txt|cmake|md|rst|sh|ps1|hpp|tpp|cpp|cc)$
4444
args: [-I, .codespell.allow]
4545

4646
- repo: https://github.com/adrienverge/yamllint.git
47-
rev: v1.29.0
47+
rev: v1.32.0
4848
hooks:
4949
- id: yamllint
5050

51-
- repo: https://github.com/PyCQA/isort
52-
rev: 5.11.4
53-
hooks:
54-
- id: isort
55-
name: isort (python)
56-
5751
- repo: https://github.com/psf/black
58-
rev: 22.12.0
52+
rev: 23.3.0
5953
hooks:
6054
- id: black
6155
language_version: python3
@@ -65,32 +59,10 @@ repos:
6559
hooks:
6660
- id: blacken-docs
6761
args: [-S, -l, '120']
68-
additional_dependencies: [black==22.12.0]
69-
70-
- repo: https://github.com/asottile/pyupgrade
71-
rev: v3.3.1
72-
hooks:
73-
- id: pyupgrade
74-
args: [--py38-plus, --keep-mock]
75-
76-
- repo: https://github.com/PyCQA/flake8
77-
rev: 5.0.4
78-
hooks:
79-
- id: flake8
80-
exclude: ^(.*_test\.py)$
81-
additional_dependencies: [flake8-breakpoint, flake8-comprehensions,
82-
flake8-docstrings, flake8-eradicate,
83-
flake8-mutable]
84-
85-
- id: flake8
86-
name: flake8-test-files
87-
additional_dependencies: [flake8-breakpoint, flake8-comprehensions,
88-
flake8-eradicate, flake8-mutable]
89-
files: ^(.*_test\.py)$
62+
additional_dependencies: [black==23.3.0]
9063

91-
- repo: https://github.com/pre-commit/mirrors-pylint
92-
rev: 'v3.0.0a5'
64+
- repo: https://github.com/charliermarsh/ruff-pre-commit
65+
rev: v0.0.270
9366
hooks:
94-
- id: pylint
95-
additional_dependencies: [pylint-secure-coding-standard]
96-
args: [--load-plugins=pylint_secure_coding_standard]
67+
- id: ruff
68+
args: [--fix, --exit-non-zero-on-fix, --show-source, --show-fixes]

CHANGELOG.md

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
### Updated
1515

1616
- Update GitHub release publishing workflow
17+
- Replace most Python pre-commit hooks with [ruff](https://beta.ruff.rs/docs/)
1718
- Added some more pre-commit hooks:
1819
+ doc8
1920
+ codespell
@@ -24,16 +25,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2425
### Repository
2526

2627
- Update `thomaseizinger/create-pull-request` GitHub Action to v1.3.0
27-
- Update `Lucas-C/pre-commit-hooks` hook to v1.4.1
28-
- Update `asottile/pyupgrade` to v3.2.0
29-
- Update `black` hook to v22.10.0
28+
- Update `Lucas-C/pre-commit-hooks` hook to v1.5.1
29+
- Update `black` hook to v23.3.0
3030
- Update `blacken-docs` hook to v1.13.0
31-
- Update `flake8` hook to v5.0.4
32-
- Update `pre-commit/mirrors-pylint` to v3.0.0a5
3331
- Update `pre-commit/pre-commit-hooks` to v4.3.0
34-
- Update `pyupgrade` hook to v3.3.1
35-
- Update `yamllint` hook to v1.29.0
36-
- Update `isort` hook to v5.11.4
32+
- Update `yamllint` hook to v1.32.0
33+
- Update `codespell` hook to v2.2.4
3734

3835
## [1.4.1] - 2022-05-04
3936

pylint_secure_coding_standard.py

Lines changed: 55 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,10 @@ def _is_builtin_open_for_writing(node):
146146

147147
if any(m in mode for m in 'awx'):
148148
# Cover:
149-
# * open(..., "w")
150-
# * open(..., "wb")
151-
# * open(..., "a")
152-
# * open(..., "x")
149+
# * open(..., "w").
150+
# * open(..., "wb").
151+
# * open(..., "a").
152+
# * open(..., "x").
153153
return True
154154
return False
155155

@@ -176,6 +176,7 @@ def _is_allowed_mode(node, allowed_modes, args_idx):
176176

177177

178178
def _is_shell_true_call(node):
179+
_n_args_max = 8
179180
if not (isinstance(node.func, astroid.Attribute) and isinstance(node.func.expr, astroid.Name)):
180181
return False
181182

@@ -191,7 +192,11 @@ def _is_shell_true_call(node):
191192
):
192193
return True
193194

194-
if len(node.args) > 8 and isinstance(node.args[8], astroid.Const) and bool(node.args[8].value):
195+
if (
196+
len(node.args) > _n_args_max
197+
and isinstance(node.args[_n_args_max], astroid.Const)
198+
and bool(node.args[_n_args_max].value)
199+
):
195200
return True
196201

197202
if node.func.attrname in ('getoutput', 'getstatusoutput'):
@@ -207,76 +212,76 @@ def _is_shell_true_call(node):
207212

208213

209214
def _is_pdb_call(node):
210-
if isinstance(node.func, astroid.Attribute):
211-
if isinstance(node.func.expr, astroid.Name) and node.func.expr.name == 'pdb':
212-
# Cover:
213-
# * pdb.func()
214-
return True
215-
if isinstance(node.func, astroid.Name):
216-
if node.func.name == 'Pdb':
217-
# Cover:
218-
# * Pdb()
219-
return True
215+
if (
216+
isinstance(node.func, astroid.Attribute)
217+
and isinstance(node.func.expr, astroid.Name)
218+
and node.func.expr.name == 'pdb'
219+
):
220+
# Cover:
221+
return True
222+
if isinstance(node.func, astroid.Name) and node.func.name == 'Pdb':
223+
# Cover:
224+
return True
220225
return False
221226

222227

223228
def _is_mktemp_call(node):
224-
if isinstance(node.func, astroid.Attribute):
225-
if node.func.attrname == 'mktemp':
226-
# Cover:
227-
# * tempfile.mktemp()
228-
# * xxxx.mktemp()
229-
return True
230-
if isinstance(node.func, astroid.Name):
231-
if node.func.name == 'mktemp':
232-
# Cover:
233-
# * mktemp()
234-
return True
229+
if isinstance(node.func, astroid.Attribute) and node.func.attrname == 'mktemp':
230+
# Cover:
231+
# * pdb.func().
232+
return True
233+
if isinstance(node.func, astroid.Name) and node.func.name == 'mktemp':
234+
# Cover:
235+
# * Pdb().
236+
return True
235237
return False
236238

237239

238240
def _is_yaml_unsafe_call(node):
241+
_load_n_args_max = 2
239242
_safe_loaders = ('BaseLoader', 'SafeLoader')
240243
_unsafe_loaders = ('Loader', 'UnsafeLoader', 'FullLoader')
244+
241245
if (
242246
isinstance(node.func, astroid.Attribute)
243247
and isinstance(node.func.expr, astroid.Name)
244248
and node.func.expr.name == 'yaml'
245249
):
246250
if node.func.attrname in ('unsafe_load', 'full_load'):
247251
# Cover:
248-
# * yaml.full_load()
249-
# * yaml.unsafe_load()
252+
# * yaml.full_load().
253+
# * yaml.unsafe_load().
250254
return True
251255
if node.func.attrname == 'load' and node.keywords:
252256
for keyword in node.keywords:
253257
if keyword.arg == 'Loader' and isinstance(keyword.value, astroid.Name):
254258
if keyword.value.name in _unsafe_loaders:
255259
# Cover:
256-
# * yaml.load(x, Loader=Loader)
257-
# * yaml.load(x, Loader=UnsafeLoader)
258-
# * yaml.load(x, Loader=FullLoader)
260+
# * yaml.load(x, Loader=Loader).
261+
# * yaml.load(x, Loader=UnsafeLoader).
262+
# * yaml.load(x, Loader=FullLoader).
259263
return True
260264
if keyword.value.name in _safe_loaders:
261265
# Cover:
262-
# * yaml.load(x, Loader=BaseLoader)
263-
# * yaml.load(x, Loader=SafeLoader)
266+
# * yaml.load(x, Loader=BaseLoader).
267+
# * yaml.load(x, Loader=SafeLoader).
264268
return False
265-
elif node.func.attrname == 'load':
266-
if len(node.args) < 2 or (isinstance(node.args[1], astroid.Name) and node.args[1].name in _unsafe_loaders):
267-
# Cover:
268-
# * yaml.load(x)
269-
# * yaml.load(x, Loader)
270-
# * yaml.load(x, UnsafeLoader)
271-
# * yaml.load(x, FullLoader)
272-
return True
273-
274-
if isinstance(node.func, astroid.Name):
275-
if node.func.name in ('unsafe_load', 'full_load'):
269+
elif node.func.attrname == 'load' and (
270+
len(node.args) < _load_n_args_max
271+
or (isinstance(node.args[1], astroid.Name) and node.args[1].name in _unsafe_loaders)
272+
):
276273
# Cover:
277-
# * unsafe_load(...)
278-
# * full_load(...)
274+
# * yaml.load(x).
275+
# * yaml.load(x, Loader).
276+
# * yaml.load(x, UnsafeLoader).
277+
# * yaml.load(x, FullLoader).
279278
return True
279+
280+
if isinstance(node.func, astroid.Name) and node.func.name in ('unsafe_load', 'full_load'):
281+
# Cover:
282+
# * unsafe_load(...).
283+
# * full_load(...).
284+
return True
280285
return False
281286

282287

@@ -537,7 +542,7 @@ def __init__(self, linter):
537542
self._os_mknod_msg_arg = ''
538543
self._os_mknod_modes_allowed = []
539544

540-
def visit_call(self, node): # pylint: disable=too-many-branches
545+
def visit_call(self, node): # pylint: disable=too-many-branches # noqa: PLR0912
541546
"""Visitor method called for astroid.Call nodes."""
542547
if _is_pdb_call(node):
543548
self.add_message('avoid-debug-stmt', node=node)
@@ -597,11 +602,11 @@ def visit_call(self, node): # pylint: disable=too-many-branches
597602

598603
def visit_import(self, node):
599604
"""Visitor method called for astroid.Import nodes."""
600-
for (name, _) in node.names:
605+
for name, _ in node.names:
601606
if name == 'pdb':
602607
# Cover:
603-
# * import pdb
604-
# * import pdb as xxx
608+
# * import pdb.
609+
# * import pdb as xxx.
605610
self.add_message('avoid-debug-stmt', node=node)
606611

607612
def visit_importfrom(self, node):

pyproject.toml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,70 @@
22
requires = ["setuptools>=43", "wheel", "setuptools_scm[toml]>=3.4"]
33
build-backend = "setuptools.build_meta"
44

5+
# ==============================================================================
6+
7+
[tool.ruff]
8+
line-length = 120
9+
target-version = 'py38'
10+
11+
select = ['F', # pyflakes
12+
'E', # pycodestyle
13+
'W', # pycodestyle
14+
'I', # isort
15+
'N', # pep8-naming
16+
'D', # pydocstyle
17+
'UP', # pyupgrade
18+
'YTT', # flake-2020
19+
'ANN', # flake8-annotations
20+
'S', # flake8-bandit
21+
'BLE', # flake8-blind-except
22+
'B', # flake8-bugbear
23+
'A', # flake8-builtins
24+
'C4', # flake8-comprehensions
25+
'T10', # flake8-debugger
26+
'ISC', # flake8-implicit-str-concat
27+
'ICN', # flake8-import-conventions
28+
'PIE', # flake8-pie
29+
'PT', # flake8-pytest-style
30+
'Q', # flake8-quotes
31+
'RSE', # flake8-raise
32+
'RET', # flake8-return
33+
'SLF', # flake8-self
34+
'SIM', # flake8-simplify
35+
'TID', # flake8-tidy-imports
36+
'ARG', # flake8-unused-arguments
37+
'PTH', # flake8-use-pathlib
38+
'ERA', # eradicate
39+
'PL', # pylint
40+
'RUF', # ruff-specific rules
41+
]
42+
ignore = ['ANN101', # missing-type-self
43+
'D203', # one-blank-line-before-class
44+
'D212', # multi-line-summary-first-line
45+
'S603' # subprocess-without-shell-equals-true
46+
]
47+
48+
[tool.ruff.per-file-ignores]
49+
50+
'tests/*.py' = ['S101', 'SLF001', 'PLR0913', 'PLR2004', 'D']
51+
52+
53+
[tool.ruff.flake8-annotations]
54+
allow-star-arg-any = true
55+
ignore-fully-untyped = true
56+
mypy-init-return = true
57+
suppress-dummy-args = true
58+
suppress-none-returning = true
59+
60+
[tool.ruff.flake8-quotes]
61+
docstring-quotes = 'double'
62+
inline-quotes = 'single'
63+
multiline-quotes = 'single'
64+
65+
[tool.ruff.pydocstyle]
66+
convention = 'google'
67+
68+
569
# ==============================================================================
670

771
[tool.black]

tests/assert_test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ class TestSecureCodingStandardChecker(pylint.testutils.CheckerTestCase):
2929

3030
def test_assert_ok(self):
3131
call_node1, call_node2 = astroid.extract_node(
32-
"""
32+
'''
3333
int(0) #@
3434
foo() #@
35-
"""
35+
'''
3636
)
3737

3838
with self.assertNoMessages():
@@ -41,10 +41,10 @@ def test_assert_ok(self):
4141

4242
@pytest.mark.parametrize(
4343
's',
44-
(
44+
[
4545
'assert len(s) > 0',
4646
'assert (my_set and my_list), "Some message"',
47-
),
47+
],
4848
)
4949
def test_assert_not_ok(self, s):
5050
node = astroid.extract_node(s + ' #@')

0 commit comments

Comments
 (0)