or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli-tool.mdcodemods.mdindex.mdmatchers.mdmetadata.mdnodes.mdparsing.mdutilities.mdvisitors.md

codemods.mddocs/

0

# Codemod Framework

1

2

LibCST's codemod framework provides a high-level system for building automated code transformation applications. It handles context management, parallel processing, file discovery, error handling, and testing utilities for large-scale refactoring operations.

3

4

## Capabilities

5

6

### Base Codemod Classes

7

8

Foundation classes for building code transformation commands.

9

10

```python { .api }

11

class CodemodCommand:

12

"""Base class for codemod commands."""

13

14

def transform_module(self, tree: Module) -> Module:

15

"""

16

Transform a module CST.

17

18

Parameters:

19

- tree: Module CST to transform

20

21

Returns:

22

Module: Transformed module CST

23

"""

24

25

def get_description(self) -> str:

26

"""Get human-readable description of the transformation."""

27

28

class VisitorBasedCodemodCommand(CodemodCommand):

29

"""Base class for visitor-based codemod commands."""

30

31

def leave_Module(self, original_node: Module, updated_node: Module) -> Module:

32

"""Transform module using visitor pattern."""

33

34

class MagicArgsCodemodCommand(CodemodCommand):

35

"""Base class for commands with automatic argument handling."""

36

```

37

38

### Context Management

39

40

Manage execution context and state during transformations.

41

42

```python { .api }

43

class CodemodContext:

44

"""Execution context for codemod operations."""

45

46

def __init__(self, metadata_wrapper: MetadataWrapper) -> None:

47

"""

48

Initialize codemod context.

49

50

Parameters:

51

- metadata_wrapper: Metadata wrapper for the module

52

"""

53

54

@property

55

def metadata_wrapper(self) -> MetadataWrapper:

56

"""Access to metadata for the current module."""

57

58

class ContextAwareTransformer(CSTTransformer):

59

"""Transformer with access to codemod context."""

60

61

def __init__(self, context: CodemodContext) -> None:

62

"""

63

Initialize context-aware transformer.

64

65

Parameters:

66

- context: Codemod execution context

67

"""

68

69

@property

70

def context(self) -> CodemodContext:

71

"""Access to codemod context."""

72

73

class ContextAwareVisitor(CSTVisitor):

74

"""Visitor with access to codemod context."""

75

76

def __init__(self, context: CodemodContext) -> None:

77

"""

78

Initialize context-aware visitor.

79

80

Parameters:

81

- context: Codemod execution context

82

"""

83

84

@property

85

def context(self) -> CodemodContext:

86

"""Access to codemod context."""

87

```

88

89

### Transform Results

90

91

Result types for tracking transformation outcomes.

92

93

```python { .api }

94

class TransformResult:

95

"""Base class for transformation results."""

96

code: Optional[str]

97

encoding: str

98

99

class TransformSuccess(TransformResult):

100

"""Successful transformation result."""

101

102

class TransformFailure(TransformResult):

103

"""Failed transformation result."""

104

error: Exception

105

106

class TransformSkip(TransformResult):

107

"""Skipped transformation result."""

108

skip_reason: SkipReason

109

110

class TransformExit(TransformResult):

111

"""Early exit transformation result."""

112

113

class SkipFile(TransformResult):

114

"""File skip result."""

115

116

class SkipReason:

117

"""Enumeration of skip reasons."""

118

BLACKLISTED: ClassVar[str]

119

OTHER: ClassVar[str]

120

121

class ParallelTransformResult:

122

"""Results from parallel transformation execution."""

123

successes: int

124

failures: int

125

skips: int

126

warnings: int

127

```

128

129

### Execution Functions

130

131

High-level functions for running codemod transformations.

132

133

```python { .api }

134

def transform_module(

135

code: str,

136

command: CodemodCommand,

137

*,

138

python_version: str = "3.8"

139

) -> TransformResult:

140

"""

141

Transform a single module with a codemod command.

142

143

Parameters:

144

- code: Source code to transform

145

- command: Codemod command to apply

146

- python_version: Target Python version

147

148

Returns:

149

TransformResult: Result of the transformation

150

"""

151

152

def gather_files(

153

paths: Sequence[str],

154

*,

155

include_generated: bool = False,

156

exclude_patterns: Sequence[str] = ()

157

) -> Sequence[str]:

158

"""

159

Gather Python files for transformation.

160

161

Parameters:

162

- paths: Directories or files to search

163

- include_generated: Include generated files

164

- exclude_patterns: Patterns to exclude

165

166

Returns:

167

Sequence[str]: List of Python file paths

168

"""

169

170

def exec_transform_with_prettyprint(

171

command: CodemodCommand,

172

files: Sequence[str],

173

*,

174

jobs: int = 1,

175

show_diff: bool = True,

176

unified_diff_lines: int = 5,

177

hide_generated: bool = True,

178

hide_blacklisted: bool = True,

179

hide_progress: bool = False

180

) -> int:

181

"""

182

Execute transformation with formatted output.

183

184

Parameters:

185

- command: Codemod command to execute

186

- files: Files to transform

187

- jobs: Number of parallel jobs

188

- show_diff: Show code differences

189

- unified_diff_lines: Context lines in diff

190

- hide_generated: Hide generated files from output

191

- hide_blacklisted: Hide blacklisted files

192

- hide_progress: Hide progress indicators

193

194

Returns:

195

int: Exit code (0 for success)

196

"""

197

198

def parallel_exec_transform_with_prettyprint(

199

command: CodemodCommand,

200

files: Sequence[str],

201

*,

202

jobs: int = 1,

203

**kwargs

204

) -> ParallelTransformResult:

205

"""

206

Execute transformation in parallel with formatted output.

207

208

Parameters:

209

- command: Codemod command to execute

210

- files: Files to transform

211

- jobs: Number of parallel jobs

212

- kwargs: Additional arguments for exec_transform_with_prettyprint

213

214

Returns:

215

ParallelTransformResult: Aggregated results

216

"""

217

218

def diff_code(

219

old_code: str,

220

new_code: str,

221

*,

222

filename: str = "<unknown>",

223

lines: int = 5

224

) -> str:

225

"""

226

Generate unified diff between code versions.

227

228

Parameters:

229

- old_code: Original code

230

- new_code: Transformed code

231

- filename: Filename for diff header

232

- lines: Context lines

233

234

Returns:

235

str: Unified diff output

236

"""

237

```

238

239

### Testing Framework

240

241

Testing utilities for codemod development and validation.

242

243

```python { .api }

244

class CodemodTest:

245

"""Base class for codemod testing."""

246

247

def assert_transform(

248

self,

249

before: str,

250

after: str,

251

*,

252

command: Optional[CodemodCommand] = None,

253

python_version: str = "3.8"

254

) -> None:

255

"""

256

Assert that transformation produces expected result.

257

258

Parameters:

259

- before: Source code before transformation

260

- after: Expected code after transformation

261

- command: Codemod command (defaults to self.command)

262

- python_version: Target Python version

263

"""

264

265

def assert_unchanged(

266

self,

267

code: str,

268

*,

269

command: Optional[CodemodCommand] = None,

270

python_version: str = "3.8"

271

) -> None:

272

"""

273

Assert that transformation leaves code unchanged.

274

275

Parameters:

276

- code: Source code to test

277

- command: Codemod command (defaults to self.command)

278

- python_version: Target Python version

279

"""

280

```

281

282

## Usage Examples

283

284

### Simple Codemod Command

285

286

```python

287

import libcst as cst

288

from libcst.codemod import VisitorBasedCodemodCommand

289

290

class RemovePrintStatements(VisitorBasedCodemodCommand):

291

"""Remove all print() calls from code."""

292

293

def leave_Call(self, original_node, updated_node):

294

# Remove print function calls

295

if isinstance(updated_node.func, cst.Name) and updated_node.func.value == "print":

296

return cst.RemoveFromParent

297

return updated_node

298

299

def get_description(self):

300

return "Remove all print() statements"

301

302

# Usage

303

from libcst.codemod import transform_module

304

305

source = '''

306

def process_data(data):

307

print("Processing data...")

308

result = data * 2

309

print(f"Result: {result}")

310

return result

311

'''

312

313

command = RemovePrintStatements()

314

result = transform_module(source, command)

315

316

if isinstance(result, cst.codemod.TransformSuccess):

317

print("Transformation successful:")

318

print(result.code)

319

else:

320

print(f"Transformation failed: {result.error}")

321

```

322

323

### Context-Aware Codemod

324

325

```python

326

import libcst as cst

327

from libcst.codemod import VisitorBasedCodemodCommand, CodemodContext

328

from libcst.metadata import ScopeProvider

329

330

class SafeVariableRenamer(VisitorBasedCodemodCommand):

331

"""Rename variables while avoiding conflicts."""

332

333

METADATA_DEPENDENCIES = (ScopeProvider,)

334

335

def __init__(self, old_name: str, new_name: str):

336

super().__init__()

337

self.old_name = old_name

338

self.new_name = new_name

339

340

def leave_Name(self, original_node, updated_node):

341

if updated_node.value == self.old_name:

342

# Check if new name conflicts in current scope

343

scope = self.resolve(ScopeProvider)[updated_node]

344

if self.new_name not in scope:

345

return updated_node.with_changes(value=self.new_name)

346

return updated_node

347

348

def get_description(self):

349

return f"Rename '{self.old_name}' to '{self.new_name}' safely"

350

351

# Usage

352

source = '''

353

def example():

354

x = 1

355

y = x + 2

356

return y

357

'''

358

359

command = SafeVariableRenamer("x", "input_value")

360

result = transform_module(source, command)

361

```

362

363

### Batch File Processing

364

365

```python

366

from libcst.codemod import (

367

gather_files,

368

exec_transform_with_prettyprint,

369

parallel_exec_transform_with_prettyprint

370

)

371

372

class UpdateImports(VisitorBasedCodemodCommand):

373

"""Update deprecated import statements."""

374

375

def leave_ImportFrom(self, original_node, updated_node):

376

if isinstance(updated_node.module, cst.Attribute):

377

if updated_node.module.value.value == "oldmodule":

378

new_module = updated_node.module.with_changes(

379

value=cst.Name("newmodule")

380

)

381

return updated_node.with_changes(module=new_module)

382

return updated_node

383

384

# Process multiple files

385

command = UpdateImports()

386

files = gather_files(["src/", "tests/"], exclude_patterns=["**/generated/**"])

387

388

# Sequential processing

389

exit_code = exec_transform_with_prettyprint(

390

command,

391

files,

392

show_diff=True,

393

unified_diff_lines=3

394

)

395

396

# Parallel processing for large codebases

397

result = parallel_exec_transform_with_prettyprint(

398

command,

399

files,

400

jobs=4,

401

show_diff=True

402

)

403

404

print(f"Processed {result.successes} files successfully")

405

print(f"Failed on {result.failures} files")

406

print(f"Skipped {result.skips} files")

407

```

408

409

### Advanced Pattern-Based Codemod

410

411

```python

412

import libcst as cst

413

import libcst.matchers as m

414

from libcst.codemod import VisitorBasedCodemodCommand

415

416

class ModernizeExceptionHandling(VisitorBasedCodemodCommand):

417

"""Modernize exception handling patterns."""

418

419

def leave_ExceptHandler(self, original_node, updated_node):

420

# Transform "except Exception, e:" to "except Exception as e:"

421

if (isinstance(updated_node.type, cst.Name) and

422

isinstance(updated_node.name, cst.AsName) and

423

isinstance(updated_node.name.name, cst.Name)):

424

425

# Check if this is old-style exception syntax

426

if updated_node.name.asname is None:

427

return updated_node.with_changes(

428

name=cst.AsName(

429

name=updated_node.name.name,

430

asname=cst.AsName(name=updated_node.name.name)

431

)

432

)

433

return updated_node

434

435

def get_description(self):

436

return "Modernize exception handling syntax"

437

438

# Usage with matcher-based replacement

439

class ReplaceAssertWithRaise(VisitorBasedCodemodCommand):

440

"""Replace assert False with raise statements."""

441

442

def leave_Assert(self, original_node, updated_node):

443

# Replace "assert False, msg" with "raise AssertionError(msg)"

444

if m.matches(updated_node.test, m.Name(value="False")):

445

if updated_node.msg:

446

return cst.Raise(

447

exc=cst.Call(

448

func=cst.Name("AssertionError"),

449

args=[cst.Arg(value=updated_node.msg)]

450

)

451

)

452

else:

453

return cst.Raise(exc=cst.Call(func=cst.Name("AssertionError")))

454

return updated_node

455

```

456

457

### Codemod Testing

458

459

```python

460

import unittest

461

from libcst.codemod import CodemodTest

462

463

class TestRemovePrintStatements(CodemodTest):

464

command = RemovePrintStatements()

465

466

def test_removes_print_calls(self):

467

before = '''

468

def example():

469

print("hello")

470

x = 42

471

print("world")

472

return x

473

'''

474

after = '''

475

def example():

476

x = 42

477

return x

478

'''

479

self.assert_transform(before, after)

480

481

def test_preserves_other_calls(self):

482

code = '''

483

def example():

484

log("message")

485

return calculate()

486

'''

487

self.assert_unchanged(code)

488

489

def test_removes_nested_prints(self):

490

before = '''

491

if condition:

492

print("debug")

493

process()

494

'''

495

after = '''

496

if condition:

497

process()

498

'''

499

self.assert_transform(before, after)

500

501

if __name__ == "__main__":

502

unittest.main()

503

```

504

505

## Types

506

507

```python { .api }

508

# Base command types

509

class CodemodCommand:

510

"""Base class for codemod commands."""

511

512

class VisitorBasedCodemodCommand(CodemodCommand):

513

"""Visitor-based codemod command."""

514

515

class MagicArgsCodemodCommand(CodemodCommand):

516

"""Command with automatic argument handling."""

517

518

# Context types

519

class CodemodContext:

520

"""Execution context for codemods."""

521

metadata_wrapper: MetadataWrapper

522

523

class ContextAwareTransformer(CSTTransformer):

524

"""Transformer with context access."""

525

context: CodemodContext

526

527

class ContextAwareVisitor(CSTVisitor):

528

"""Visitor with context access."""

529

context: CodemodContext

530

531

# Result types

532

class TransformResult:

533

"""Base transformation result."""

534

code: Optional[str]

535

encoding: str

536

537

class TransformSuccess(TransformResult): ...

538

class TransformFailure(TransformResult):

539

error: Exception

540

class TransformSkip(TransformResult):

541

skip_reason: SkipReason

542

class TransformExit(TransformResult): ...

543

class SkipFile(TransformResult): ...

544

545

class SkipReason:

546

BLACKLISTED: ClassVar[str]

547

OTHER: ClassVar[str]

548

549

class ParallelTransformResult:

550

"""Results from parallel execution."""

551

successes: int

552

failures: int

553

skips: int

554

warnings: int

555

556

# Testing types

557

class CodemodTest:

558

"""Base class for codemod testing."""

559

command: CodemodCommand

560

```