or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

code-navigation.mdcode-refactoring.mdconfiguration.mdenvironment-management.mdindex.mdinterpreter-integration.mdproject-configuration.mdscript-analysis.md

code-refactoring.mddocs/

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