0
# Code Refactoring
1
2
Code refactoring operations including rename, extract variable, extract function, and inline operations. Provides safe refactoring with proper scope analysis, conflict detection, and cross-file changes.
3
4
## Capabilities
5
6
### Variable Renaming
7
8
Rename variables, functions, classes, and other symbols across their scope with conflict detection.
9
10
```python { .api }
11
def rename(self, line=None, column=None, *, new_name):
12
"""
13
Rename the symbol at cursor position.
14
15
Parameters:
16
- line (int, optional): Line number (1-based).
17
- column (int, optional): Column number (0-based).
18
- new_name (str): New name for the symbol.
19
20
Returns:
21
Refactoring object with rename changes.
22
23
Raises:
24
RefactoringError: If rename is not possible or conflicts exist.
25
"""
26
```
27
28
**Usage Example:**
29
```python
30
import jedi
31
from jedi.api.exceptions import RefactoringError
32
33
code = '''
34
class Calculator:
35
def __init__(self):
36
self.result = 0
37
38
def add(self, value):
39
self.result += value
40
return self.result
41
42
def get_result(self):
43
return self.result
44
45
calc = Calculator()
46
calc.add(5)
47
print(calc.get_result())
48
'''
49
50
script = jedi.Script(code=code, path='calculator.py')
51
52
try:
53
# Rename 'result' attribute to 'total'
54
refactoring = script.rename(line=3, column=13, new_name='total') # At 'result'
55
56
# Get the changes
57
changes = refactoring.get_changed_files()
58
for file_path, changed_file in changes.items():
59
print(f"Changes in {file_path}:")
60
print(changed_file.get_diff())
61
62
# Apply the changes
63
refactoring.apply()
64
print("Rename applied successfully")
65
66
except RefactoringError as e:
67
print(f"Rename failed: {e}")
68
```
69
70
### Extract Variable
71
72
Extract expressions into new variables to improve code readability and reduce duplication.
73
74
```python { .api }
75
def extract_variable(self, line, column, *, new_name, until_line=None, until_column=None):
76
"""
77
Extract expression to a new variable.
78
79
Parameters:
80
- line (int): Start line number (1-based).
81
- column (int): Start column number (0-based).
82
- new_name (str): Name for the extracted variable.
83
- until_line (int, optional): End line number.
84
- until_column (int, optional): End column number.
85
86
Returns:
87
Refactoring object with extraction changes.
88
89
Raises:
90
RefactoringError: If extraction is not possible.
91
"""
92
```
93
94
**Usage Example:**
95
```python
96
import jedi
97
from jedi.api.exceptions import RefactoringError
98
99
code = '''
100
def calculate_area(radius):
101
return 3.14159 * radius * radius
102
103
def calculate_circumference(radius):
104
return 2 * 3.14159 * radius
105
106
def calculate_volume(radius, height):
107
base_area = 3.14159 * radius * radius
108
return base_area * height
109
'''
110
111
script = jedi.Script(code=code, path='geometry.py')
112
113
try:
114
# Extract pi constant from first function
115
refactoring = script.extract_variable(
116
line=2,
117
column=11, # Start of '3.14159'
118
until_line=2,
119
until_column=19, # End of '3.14159'
120
new_name='PI'
121
)
122
123
print("Extraction preview:")
124
changes = refactoring.get_changed_files()
125
for file_path, changed_file in changes.items():
126
print(changed_file.get_new_code())
127
128
# Apply the extraction
129
refactoring.apply()
130
131
except RefactoringError as e:
132
print(f"Extraction failed: {e}")
133
134
# Extract complex expression
135
complex_code = '''
136
def process_data(items):
137
filtered_items = [item for item in items if len(item.strip()) > 0 and item.strip().startswith('data_')]
138
return [item.strip().upper() for item in filtered_items]
139
'''
140
141
script = jedi.Script(code=complex_code, path='processor.py')
142
143
# Extract the filtering condition
144
refactoring = script.extract_variable(
145
line=2,
146
column=47, # Start of condition
147
until_line=2,
148
until_column=95, # End of condition
149
new_name='is_valid_data_item'
150
)
151
```
152
153
### Extract Function
154
155
Extract code blocks into new functions to improve modularity and reusability.
156
157
```python { .api }
158
def extract_function(self, line, column, *, new_name, until_line=None, until_column=None):
159
"""
160
Extract code block to a new function.
161
162
Parameters:
163
- line (int): Start line number (1-based).
164
- column (int): Start column number (0-based).
165
- new_name (str): Name for the extracted function.
166
- until_line (int, optional): End line number.
167
- until_column (int, optional): End column number.
168
169
Returns:
170
Refactoring object with extraction changes.
171
172
Raises:
173
RefactoringError: If extraction is not possible.
174
"""
175
```
176
177
**Usage Example:**
178
```python
179
import jedi
180
from jedi.api.exceptions import RefactoringError
181
182
code = '''
183
def process_user_data(users):
184
results = []
185
for user in users:
186
# Complex validation logic
187
if user.get('email') and '@' in user['email']:
188
if user.get('age') and user['age'] >= 18:
189
if user.get('name') and len(user['name'].strip()) > 0:
190
normalized_user = {
191
'name': user['name'].strip().title(),
192
'email': user['email'].lower(),
193
'age': user['age']
194
}
195
results.append(normalized_user)
196
return results
197
'''
198
199
script = jedi.Script(code=code, path='user_processor.py')
200
201
try:
202
# Extract validation and normalization logic
203
refactoring = script.extract_function(
204
line=4,
205
column=8, # Start of validation logic
206
until_line=11,
207
column=42, # End of normalization
208
new_name='validate_and_normalize_user'
209
)
210
211
print("Function extraction preview:")
212
changes = refactoring.get_changed_files()
213
for file_path, changed_file in changes.items():
214
print(changed_file.get_new_code())
215
216
# Apply the extraction
217
refactoring.apply()
218
219
except RefactoringError as e:
220
print(f"Function extraction failed: {e}")
221
222
# Extract with automatic parameter detection
223
calculation_code = '''
224
def calculate_compound_interest(principal, rate, time, frequency):
225
annual_rate = rate / 100
226
compound_frequency = frequency
227
amount = principal * (1 + annual_rate / compound_frequency) ** (compound_frequency * time)
228
interest = amount - principal
229
return interest
230
'''
231
232
script = jedi.Script(code=calculation_code, path='finance.py')
233
234
# Extract compound calculation
235
refactoring = script.extract_function(
236
line=3,
237
column=4, # Start of calculation
238
until_line=4,
239
column=30, # End of calculation
240
new_name='calculate_compound_amount'
241
)
242
```
243
244
### Inline Variable
245
246
Inline variables by replacing their usage with their values, removing unnecessary indirection.
247
248
```python { .api }
249
def inline(self, line=None, column=None):
250
"""
251
Inline the variable at cursor position.
252
253
Parameters:
254
- line (int, optional): Line number (1-based).
255
- column (int, optional): Column number (0-based).
256
257
Returns:
258
Refactoring object with inline changes.
259
260
Raises:
261
RefactoringError: If inlining is not possible.
262
"""
263
```
264
265
**Usage Example:**
266
```python
267
import jedi
268
from jedi.api.exceptions import RefactoringError
269
270
code = '''
271
def calculate_discount(price, discount_rate):
272
discount_decimal = discount_rate / 100
273
discount_amount = price * discount_decimal
274
final_price = price - discount_amount
275
return final_price
276
'''
277
278
script = jedi.Script(code=code, path='pricing.py')
279
280
try:
281
# Inline 'discount_decimal' variable
282
refactoring = script.inline(line=2, column=4) # At 'discount_decimal'
283
284
print("Inline preview:")
285
changes = refactoring.get_changed_files()
286
for file_path, changed_file in changes.items():
287
print("Before:")
288
print(code)
289
print("\nAfter:")
290
print(changed_file.get_new_code())
291
292
# Apply the inline
293
refactoring.apply()
294
295
except RefactoringError as e:
296
print(f"Inline failed: {e}")
297
298
# Inline with multiple usages
299
multi_usage_code = '''
300
def format_message(user_name, message_type, content):
301
prefix = f"[{message_type}]"
302
timestamp = "2024-01-01 12:00:00"
303
formatted_message = f"{timestamp} {prefix} {user_name}: {content}"
304
log_entry = f"LOG: {formatted_message}"
305
return log_entry
306
'''
307
308
script = jedi.Script(code=multi_usage_code, path='messaging.py')
309
310
# Inline 'prefix' variable (used once)
311
refactoring = script.inline(line=2, column=4) # At 'prefix'
312
```
313
314
### Cross-File Refactoring
315
316
Perform refactoring operations across multiple files in a project.
317
318
**Usage Example:**
319
```python
320
import jedi
321
from jedi import Project
322
323
# Project with multiple files
324
project = Project("/path/to/project")
325
326
# File 1: models.py
327
models_code = '''
328
class UserModel:
329
def __init__(self, name, email):
330
self.user_name = name
331
self.user_email = email
332
333
def get_display_name(self):
334
return f"{self.user_name} <{self.user_email}>"
335
'''
336
337
# File 2: services.py
338
services_code = '''
339
from models import UserModel
340
341
class UserService:
342
def create_user(self, name, email):
343
user = UserModel(name, email)
344
print(f"Created user: {user.get_display_name()}")
345
return user
346
347
def update_user_name(self, user, new_name):
348
user.user_name = new_name
349
return user
350
'''
351
352
# Rename 'user_name' across all files
353
script = jedi.Script(
354
code=models_code,
355
path="/path/to/project/models.py",
356
project=project
357
)
358
359
try:
360
# Rename user_name to username across project
361
refactoring = script.rename(line=3, column=13, new_name='username')
362
363
# Check all affected files
364
changes = refactoring.get_changed_files()
365
print("Files to be changed:")
366
for file_path, changed_file in changes.items():
367
print(f"\n{file_path}:")
368
print(changed_file.get_diff())
369
370
# Get file renames if any
371
renames = refactoring.get_renames()
372
if renames:
373
print("File renames:")
374
for old_path, new_path in renames.items():
375
print(f" {old_path} -> {new_path}")
376
377
# Apply changes to all files
378
refactoring.apply()
379
380
except RefactoringError as e:
381
print(f"Cross-file refactoring failed: {e}")
382
```
383
384
### Refactoring Validation and Preview
385
386
Validate refactoring operations and preview changes before applying them.
387
388
**Usage Example:**
389
```python
390
import jedi
391
392
code = '''
393
def calculate_tax(income, tax_rate):
394
base_tax = income * 0.1
395
additional_tax = (income - 50000) * (tax_rate - 0.1) if income > 50000 else 0
396
total_tax = base_tax + additional_tax
397
return total_tax
398
'''
399
400
script = jedi.Script(code=code, path='tax_calculator.py')
401
402
# Preview variable extraction
403
try:
404
refactoring = script.extract_variable(
405
line=3,
406
column=21, # Start of '(income - 50000)'
407
until_line=3,
408
until_column=38, # End of '(income - 50000)'
409
new_name='excess_income'
410
)
411
412
# Preview changes without applying
413
print("Refactoring preview:")
414
changes = refactoring.get_changed_files()
415
for file_path, changed_file in changes.items():
416
print(f"\nFile: {file_path}")
417
print("Diff:")
418
print(changed_file.get_diff())
419
420
print("\nNew content preview:")
421
print(changed_file.get_new_code())
422
423
# Get unified diff for the entire refactoring
424
unified_diff = refactoring.get_diff()
425
print("\nUnified diff:")
426
print(unified_diff)
427
428
# Only apply if preview looks good
429
user_input = input("Apply refactoring? (y/n): ")
430
if user_input.lower() == 'y':
431
refactoring.apply()
432
print("Refactoring applied successfully")
433
434
except RefactoringError as e:
435
print(f"Refactoring validation failed: {e}")
436
```
437
438
## Refactoring Result Types
439
440
### Refactoring
441
442
```python { .api }
443
class Refactoring:
444
def get_changed_files(self):
445
"""Get dictionary of Path -> ChangedFile for all changes."""
446
447
def get_renames(self):
448
"""Get dictionary of Path -> Path for file renames."""
449
450
def get_diff(self):
451
"""Get unified diff string for all changes."""
452
453
def apply(self):
454
"""Apply all refactoring changes to files."""
455
```
456
457
### ChangedFile
458
459
```python { .api }
460
class ChangedFile:
461
def get_diff(self):
462
"""Get diff string for this file."""
463
464
def get_new_code(self):
465
"""Get new file content after changes."""
466
467
def apply(self):
468
"""Apply changes to this specific file."""
469
```
470
471
## Error Handling
472
473
### RefactoringError
474
475
```python { .api }
476
class RefactoringError(Exception):
477
"""Raised when refactoring operations cannot be completed safely."""
478
```
479
480
**Common refactoring error scenarios:**
481
- Name conflicts (new name already exists in scope)
482
- Invalid syntax after refactoring
483
- Cross-file dependencies that would break
484
- Attempting to refactor built-in or imported symbols
485
- Extracting code with complex control flow dependencies