/
refactoring.py
147 lines (123 loc) · 5.09 KB
/
refactoring.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
""" Introduce refactoring """
import modules
import difflib
import helpers
class Refactoring(object):
def __init__(self, change_dct):
"""
:param change_dct: dict(old_path=(new_path, old_lines, new_lines))
"""
self.change_dct = change_dct
def old_files(self):
dct = {}
for old_path, (new_path, old_l, new_l) in self.change_dct.items():
dct[new_path] = '\n'.join(new_l)
return dct
def new_files(self):
dct = {}
for old_path, (new_path, old_l, new_l) in self.change_dct.items():
dct[new_path] = '\n'.join(new_l)
return dct
def diff(self):
texts = []
for old_path, (new_path, old_l, new_l) in self.change_dct.items():
if old_path:
udiff = difflib.unified_diff(old_l, new_l)
else:
udiff = difflib.unified_diff(old_l, new_l, old_path, new_path)
texts.append('\n'.join(udiff))
return '\n'.join(texts)
def rename(script, new_name):
""" The `args` / `kwargs` params are the same as in `api.Script`.
:param operation: The refactoring operation to execute.
:type operation: str
:type source: str
:return: list of changed lines/changed files
"""
dct = {}
def process(path, old_lines, new_lines):
if new_lines is not None: # goto next file, save last
dct[path] = path, old_lines, new_lines
old_names = script.related_names()
order = sorted(old_names, key=lambda x: (x.module_path, x.start_pos),
reverse=True)
current_path = object()
new_lines = old_lines = None
for name in order:
if name.in_builtin_module():
continue
if current_path != name.module_path:
current_path = name.module_path
process(current_path, old_lines, new_lines)
if current_path is not None:
# None means take the source that is a normal param.
with open(current_path) as f:
source = f.read()
new_lines = modules.source_to_unicode(source).splitlines()
old_lines = new_lines[:]
nr, indent = name.start_pos
line = new_lines[nr - 1]
new_lines[nr - 1] = line[:indent] + new_name + \
line[indent + len(name.name_part):]
process(current_path, old_lines, new_lines)
return Refactoring(dct)
def extract(script, new_name):
""" The `args` / `kwargs` params are the same as in `api.Script`.
:param operation: The refactoring operation to execute.
:type operation: str
:type source: str
:return: list of changed lines/changed files
"""
new_lines = modules.source_to_unicode(script.source).splitlines()
old_lines = new_lines[:]
user_stmt = script._parser.user_stmt
# TODO care for multiline extracts
dct = {}
if user_stmt:
indent = user_stmt.start_pos[0]
pos = script.pos
line_index = pos[0] - 1
import parsing
assert isinstance(user_stmt, parsing.Statement)
call, index, stop = helpers.scan_array_for_pos(
user_stmt.get_assignment_calls(), pos)
assert isinstance(call, parsing.Call)
exe = call.execution
if exe:
s = exe.start_pos[0], exe.start_pos[1] + 1
positions = [s] + call.execution.arr_el_pos + [exe.end_pos]
start_pos = positions[index]
end_pos = positions[index + 1][0], positions[index + 1][1] - 1
# take full line if the start line is different from end line
e = end_pos[1] if end_pos[0] == start_pos[0] else None
start_line = new_lines[start_pos[0] - 1]
text = start_line[start_pos[1]:e]
for l in range(start_pos[0], end_pos[0] - 1):
text += '\n' + l
if e is None:
end_line = new_lines[end_pos[0] - 1]
text += '\n' + end_line[:end_pos[1]]
# remove code from new lines
t = text.lstrip()
del_start = start_pos[1] + len(text) - len(t)
text = t.rstrip()
del_end = len(t) - len(text)
if e is None:
new_lines[end_pos[0] - 1] = end_line[end_pos[1] - del_end:]
e = len(start_line)
else:
e = e - del_end
start_line = start_line[:del_start] + new_name + start_line[e:]
new_lines[start_pos[0] - 1] = start_line
new_lines[start_pos[0]:end_pos[0]-1] = []
# add parentheses in multiline case
open_brackets = ['(', '[', '{']
close_brackets = [')', ']', '}']
if '\n' in text and not (text[0] in open_brackets and text[-1] ==
close_brackets[open_brackets.index(text[0])]):
text = '(%s)' % text
# add new line before statement
new = "%s%s = %s" % (' ' * indent, new_name, text)
new_lines.insert(line_index, new)
dct[script.source_path] = script.source_path, old_lines, new_lines
return Refactoring(dct)