-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathvcs-diff-lint-csdiff-mypy
executable file
·102 lines (81 loc) · 3.47 KB
/
vcs-diff-lint-csdiff-mypy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#! /usr/bin/python3
"""
The csdiff tool doesn't support the Mypy's output. The thing is that Mypy
doesn't provide any machine-readable output:
https://github.com/python/mypy/issues/10816
So this just a trivial wrapper which reads Mypy's report and transforms it to
JSON which is supported by csdiff.
"""
import os
import sys
import logging
import subprocess
MYPY = ["mypy", "--no-error-summary", "--ignore-missing-imports"]
logging.basicConfig(level=logging.INFO)
LOG = logging.getLogger()
class MultilineSkipper:
"""
Skip the Mypy's multiline errors if `self.skip_line(line)` return True.
Mypy seems to have only one multi-line error ATM.
"""
searching_for = None
def skip_line(self, line):
"""
Return True if the LINE should be skipped (not parsed).
"""
if self.searching_for:
if self.searching_for in line:
self.searching_for = None
return True
# This one is hard to avoid:
# error: Duplicate module named "__main__" (also at "vcs-diff-lint")
# note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#mapping-file-paths-to-modules for more info
# note: Common resolutions include: a) using `--exclude` to avoid checking one of them, b) adding `__init__.py` somewhere, c) using `--explicit-package-bases` or adjusting MYPYPATH
if 'Duplicate module named "__main__"' in line:
self.searching_for = "note: Common resolutions include: a) using `--exclude`"
return True
# note: This violates the Liskov substitution principle
# note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
# note: It is recommended for "__eq__" to work with arbitrary objects, for example:
# note: def __eq__(self, other: object) -> bool:
# note: if not isinstance(other, X):
# note: return NotImplemented
# note: return <logic to compare two X instances>
if '"__eq__" is incompatible with supertype "object"' in line:
self.searching_for = "logic to compare two X instances"
return False
return False
def _main():
files = sys.argv[1:]
mypy_cmd = MYPY + files
real_paths = [os.path.realpath(x) for x in files]
with subprocess.Popen(mypy_cmd, shell=False,
stdout=subprocess.PIPE) as sp:
skipper = MultilineSkipper()
for line in iter(sp.stdout.readline, b''):
line = line.decode("utf8").strip()
if skipper.skip_line(line):
LOG.debug("Skipping line: %s", line)
continue
try:
file, rest = line.split(":", 1)
lineno, rest = rest.split(": ", 1)
msgtype, msg = rest.split(": ", 1)
except ValueError:
LOG.error("Can't parse line: %s", line)
return 1
real_path = os.path.realpath(file)
if real_path not in real_paths:
LOG.debug("Skipping non-related %s in file %s", msg, file)
continue
print("Error: MYPY_{}:".format(msgtype.upper()))
print("{file}:{line}: {event}: {msg}".format(
file=file,
line=lineno,
event="{}[{}]".format("mypy", msgtype),
msg=msg,
))
print()
return sp.returncode
if __name__ == "__main__":
sys.exit(_main())