Skip to content

Commit 57a73d7

Browse files
committed
Merge bitcoin/bitcoin#24794: lint: Convert Python linter to Python
47b66ac lint: Convert Python linter to Python (Fabian Jahr) Pull request description: The outputs provided by the Python version should be exactly the same as the ones from the shell version. There is small improvement here: Previously only the dependency of `flake9` was checked, now all dependencies are checked before running. I also tried to mostly follow the [recommendations here](bitcoin/bitcoin#24766 (review)) but happy to make more changes if there is still room for improvement. ACKs for top commit: laanwj: Tested ACK 47b66ac Tree-SHA512: 1630188e176c1063b8905669b76682b361a858cde6990ab17e51ad4333bf376eab796050cdb9f2967b84f1f74379d9e860c4258561b1964e1a47183c593e5bb4
2 parents 5fdf37e + 47b66ac commit 57a73d7

File tree

3 files changed

+134
-114
lines changed

3 files changed

+134
-114
lines changed

test/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -305,9 +305,9 @@ Use the `-v` option for verbose output.
305305

306306
| Lint test | Dependency |
307307
|-----------|:----------:|
308-
| [`lint-python.sh`](lint/lint-python.sh) | [flake8](https://gitlab.com/pycqa/flake8)
309-
| [`lint-python.sh`](lint/lint-python.sh) | [mypy](https://github.com/python/mypy)
310-
| [`lint-python.sh`](lint/lint-python.sh) | [pyzmq](https://github.com/zeromq/pyzmq)
308+
| [`lint-python.py`](lint/lint-python.py) | [flake8](https://gitlab.com/pycqa/flake8)
309+
| [`lint-python.py`](lint/lint-python.py) | [mypy](https://github.com/python/mypy)
310+
| [`lint-python.py`](lint/lint-python.py) | [pyzmq](https://github.com/zeromq/pyzmq)
311311
| [`lint-python-dead-code.py`](lint/lint-python-dead-code.py) | [vulture](https://github.com/jendrikseipp/vulture)
312312
| [`lint-shell.sh`](lint/lint-shell.sh) | [ShellCheck](https://github.com/koalaman/shellcheck)
313313
| [`lint-spelling.py`](lint/lint-spelling.py) | [codespell](https://github.com/codespell-project/codespell)

test/lint/lint-python.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright (c) 2022 The Bitcoin Core developers
4+
# Distributed under the MIT software license, see the accompanying
5+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
6+
7+
"""
8+
Check for specified flake8 and mypy warnings in python files.
9+
"""
10+
11+
import os
12+
import pkg_resources
13+
import subprocess
14+
import sys
15+
16+
DEPS = ['flake8', 'mypy', 'pyzmq']
17+
MYPY_CACHE_DIR = f"{os.getenv('BASE_ROOT_DIR', '')}/test/.mypy_cache"
18+
FILES_ARGS = ['git', 'ls-files', 'test/functional/*.py', 'contrib/devtools/*.py']
19+
20+
ENABLED = (
21+
'E101,' # indentation contains mixed spaces and tabs
22+
'E112,' # expected an indented block
23+
'E113,' # unexpected indentation
24+
'E115,' # expected an indented block (comment)
25+
'E116,' # unexpected indentation (comment)
26+
'E125,' # continuation line with same indent as next logical line
27+
'E129,' # visually indented line with same indent as next logical line
28+
'E131,' # continuation line unaligned for hanging indent
29+
'E133,' # closing bracket is missing indentation
30+
'E223,' # tab before operator
31+
'E224,' # tab after operator
32+
'E242,' # tab after ','
33+
'E266,' # too many leading '#' for block comment
34+
'E271,' # multiple spaces after keyword
35+
'E272,' # multiple spaces before keyword
36+
'E273,' # tab after keyword
37+
'E274,' # tab before keyword
38+
'E275,' # missing whitespace after keyword
39+
'E304,' # blank lines found after function decorator
40+
'E306,' # expected 1 blank line before a nested definition
41+
'E401,' # multiple imports on one line
42+
'E402,' # module level import not at top of file
43+
'E502,' # the backslash is redundant between brackets
44+
'E701,' # multiple statements on one line (colon)
45+
'E702,' # multiple statements on one line (semicolon)
46+
'E703,' # statement ends with a semicolon
47+
'E711,' # comparison to None should be 'if cond is None:'
48+
'E714,' # test for object identity should be "is not"
49+
'E721,' # do not compare types, use "isinstance()"
50+
'E742,' # do not define classes named "l", "O", or "I"
51+
'E743,' # do not define functions named "l", "O", or "I"
52+
'E901,' # SyntaxError: invalid syntax
53+
'E902,' # TokenError: EOF in multi-line string
54+
'F401,' # module imported but unused
55+
'F402,' # import module from line N shadowed by loop variable
56+
'F403,' # 'from foo_module import *' used; unable to detect undefined names
57+
'F404,' # future import(s) name after other statements
58+
'F405,' # foo_function may be undefined, or defined from star imports: bar_module
59+
'F406,' # "from module import *" only allowed at module level
60+
'F407,' # an undefined __future__ feature name was imported
61+
'F601,' # dictionary key name repeated with different values
62+
'F602,' # dictionary key variable name repeated with different values
63+
'F621,' # too many expressions in an assignment with star-unpacking
64+
'F622,' # two or more starred expressions in an assignment (a, *b, *c = d)
65+
'F631,' # assertion test is a tuple, which are always True
66+
'F632,' # use ==/!= to compare str, bytes, and int literals
67+
'F701,' # a break statement outside of a while or for loop
68+
'F702,' # a continue statement outside of a while or for loop
69+
'F703,' # a continue statement in a finally block in a loop
70+
'F704,' # a yield or yield from statement outside of a function
71+
'F705,' # a return statement with arguments inside a generator
72+
'F706,' # a return statement outside of a function/method
73+
'F707,' # an except: block as not the last exception handler
74+
'F811,' # redefinition of unused name from line N
75+
'F812,' # list comprehension redefines 'foo' from line N
76+
'F821,' # undefined name 'Foo'
77+
'F822,' # undefined name name in __all__
78+
'F823,' # local variable name … referenced before assignment
79+
'F831,' # duplicate argument name in function definition
80+
'F841,' # local variable 'foo' is assigned to but never used
81+
'W191,' # indentation contains tabs
82+
'W291,' # trailing whitespace
83+
'W292,' # no newline at end of file
84+
'W293,' # blank line contains whitespace
85+
'W601,' # .has_key() is deprecated, use "in"
86+
'W602,' # deprecated form of raising exception
87+
'W603,' # "<>" is deprecated, use "!="
88+
'W604,' # backticks are deprecated, use "repr()"
89+
'W605,' # invalid escape sequence "x"
90+
'W606,' # 'async' and 'await' are reserved keywords starting with Python 3.7
91+
)
92+
93+
94+
def check_dependencies():
95+
working_set = {pkg.key for pkg in pkg_resources.working_set}
96+
97+
for dep in DEPS:
98+
if dep not in working_set:
99+
print(f"Skipping Python linting since {dep} is not installed.")
100+
exit(0)
101+
102+
103+
def main():
104+
check_dependencies()
105+
106+
if len(sys.argv) > 1:
107+
flake8_files = sys.argv[1:]
108+
else:
109+
files_args = ['git', 'ls-files', '*.py']
110+
flake8_files = subprocess.check_output(files_args).decode("utf-8").splitlines()
111+
112+
flake8_args = ['flake8', '--ignore=B,C,E,F,I,N,W', f'--select={ENABLED}'] + flake8_files
113+
flake8_env = os.environ.copy()
114+
flake8_env["PYTHONWARNINGS"] = "ignore"
115+
116+
try:
117+
subprocess.check_call(flake8_args, env=flake8_env)
118+
except subprocess.CalledProcessError:
119+
exit(1)
120+
121+
mypy_files = subprocess.check_output(FILES_ARGS).decode("utf-8").splitlines()
122+
mypy_args = ['mypy', '--show-error-codes'] + mypy_files
123+
124+
try:
125+
subprocess.check_call(mypy_args)
126+
except subprocess.CalledProcessError:
127+
exit(1)
128+
129+
130+
if __name__ == "__main__":
131+
main()

test/lint/lint-python.sh

Lines changed: 0 additions & 111 deletions
This file was deleted.

0 commit comments

Comments
 (0)