-
Notifications
You must be signed in to change notification settings - Fork 144
/
flake8_isort.py
157 lines (139 loc) · 5.5 KB
/
flake8_isort.py
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
from contextlib import redirect_stdout
from difflib import unified_diff
from importlib.metadata import PackageNotFoundError
from importlib.metadata import version
from io import StringIO
from pathlib import Path
import isort
import warnings
def _version():
try:
return version('flake8_isort')
except PackageNotFoundError:
return 'dev' # for local development if package is not installed yet
class Flake8IsortBase:
name = 'flake8_isort'
version = _version()
isort_unsorted = 'I001 isort found an import in the wrong position'
no_config_msg = 'I002 no configuration found (.isort.cfg or [isort] in configs)'
isort_blank_req = 'I003 isort expected 1 blank line in imports, found 0'
isort_blank_unexp = 'I004 isort found an unexpected blank line in imports'
isort_add_unexp = 'I005 isort found an unexpected missing import'
show_traceback = False
no_skip_gitignore = False
stdin_display_name = None
search_current = True
def __init__(self, tree, filename, lines):
self.filename = filename
self.lines = lines
@staticmethod
def add_options(option_manager):
option_manager.add_option(
'--isort-show-traceback',
action='store_true',
parse_from_config=True,
help='Show full traceback with diff from isort',
)
option_manager.add_option(
'--isort-no-skip-gitignore',
action='store_true',
parse_from_config=True,
help=(
"Temporarily override the set value of isort's `skip_gitignore` option "
'with `False`. This can cause flake8-isort to run significantly faster '
"at the cost of making flake8-isort's behavior differ slightly from "
'the behavior of `isort --check`.'
),
)
@classmethod
def parse_options(cls, option_manager, options, args):
cls.stdin_display_name = options.stdin_display_name
cls.show_traceback = options.isort_show_traceback
cls.no_skip_gitignore = options.isort_no_skip_gitignore
class Flake8Isort5(Flake8IsortBase):
"""class for isort >=5"""
def run(self):
if self.filename is not self.stdin_display_name:
file_path = Path(self.filename)
settings_path = file_path.parent
else:
file_path = None
settings_path = Path.cwd()
if self.no_skip_gitignore:
isort_config = isort.settings.Config(
settings_path=settings_path, skip_gitignore=False
)
else:
isort_config = isort.settings.Config(settings_path=settings_path)
input_string = ''.join(self.lines)
traceback = ''
isort_changed = False
input_stream = StringIO(input_string)
output_stream = StringIO()
isort_stdout = StringIO()
try:
with redirect_stdout(isort_stdout):
isort_changed = isort.api.sort_stream(
input_stream=input_stream,
output_stream=output_stream,
config=isort_config,
file_path=file_path,
)
except isort.exceptions.FileSkipped:
pass
except isort.exceptions.ISortError as e:
warnings.warn(e, stacklevel=2)
if isort_changed:
outlines = output_stream.getvalue()
diff_delta = ''.join(
unified_diff(
input_string.splitlines(keepends=True),
outlines.splitlines(keepends=True),
fromfile=f'{self.filename}:before',
tofile=f'{self.filename}:after',
)
)
traceback = f'{isort_stdout.getvalue()}\n{diff_delta}'
for line_num, message in self.isort_linenum_msg(diff_delta):
if self.show_traceback:
message += traceback
yield line_num, 0, message, type(self)
def isort_linenum_msg(self, udiff):
"""Parse unified diff for changes and generate messages
Args
----
udiff : unified diff delta
Yields
------
tuple: A tuple of the specific isort line number and message.
"""
line_num = 0
additions = []
moves = []
for line in udiff.splitlines():
if line.startswith('@@', 0, 2):
line_num = int(line[4:].split(' ')[0].split(',')[0])
continue
elif not line_num: # skip lines before first hunk
continue
if line.startswith(' ', 0, 1):
line_num += 1 # Ignore unchanged lines but increment line_num.
elif line.startswith('-', 0, 1):
if line.strip() == '-':
yield line_num, self.isort_blank_unexp
line_num += 1
else:
moves.append(line[1:])
yield line_num, self.isort_unsorted
line_num += 1
elif line.startswith('+', 0, 1):
if line.strip() == '+':
# Include newline additions but do not increment line_num.
yield line_num, self.isort_blank_req
else:
additions.append((line_num, line))
# return all additions that did not move
for line_num, line in additions:
if not line[1:] in moves:
yield line_num, self.isort_add_unexp
Flake8Isort = Flake8Isort5