codemod_recipe

Pub Version Dart CI License: BSD-3-Clause

Reusable primitives for deterministic, recipe-based Dart codemods and file scaffolds.

This package intentionally contains no project-specific transforms, paths, or naming conventions. Projects build their own integration layer on top of the generic recipe, runner, template, operation, patch, and Dart analyzer editor APIs.

Installation

Add codemod_recipe to your pubspec.yaml:

dependencies:
  codemod_recipe: ^0.1.0

Or install via command line:

dart pub add codemod_recipe

Core Concepts

CLI args -> CodemodContext -> CodemodRecipe -> CodemodOperation -> FileChange -> preview/apply -> PostExecution
  • CodemodRecipe declares arguments, ordered operations, and post actions.
  • CodemodRunner handles CLI parsing, dry-run/apply, and errors.
  • CodemodContext stores raw argument values and generic case helpers.
  • CodemodTemplate renders inline or file-backed templates.
  • CodemodOperation plans file edits or file creation.
  • CodeTransform converts source text into deterministic SourcePatches.
  • CodeEditor provides AST-guided Dart insertion helpers.
  • PostExecution runs reusable commands after successful apply.

Example

import 'package:codemod_recipe/codemod_recipe.dart';

Future<void> main(List<String> args) {
  return CodemodRunner(addMethodRecipe).run(args);
}

final addMethodRecipe = CodemodRecipe(
  name: 'add_method',
  args: [
    CodemodArg.required('file'),
    CodemodArg.required('class'),
    CodemodArg.required('method'),
  ],
  operations: [
    EditDartFileOperation(
      path: (context) => context.require('file'),
      transforms: (context) => [
        AddMethodTransform(
          className: (context) => context.require('class'),
          methodName: (context) => context.camel('method'),
          body: const CodemodTemplate.inline('''
  void {{method:camel}}() {}
'''),
        ),
      ],
    ),
  ],
  postExecution: const [DartFormatPostExecution()],
);

Templates

Templates use Mustache-style placeholders with package-defined casing filters:

lib/features/{{feature:snake}}/{{feature:snake}}_view.dart
class {{feature:pascal}}View {}
final {{feature:camel}}Controller = ...

Supported casing filters are snake, camel, and pascal. Missing variables and unsupported placeholders fail the run instead of rendering silently.

File Creation

Use CreateFileOperation to scaffold files:

CreateFileOperation(
  path: (context) => context.render(
    'lib/features/{{feature:snake}}/{{feature:snake}}_view.dart',
  ),
  template: const CodemodTemplate.inline('''
class {{feature:pascal}}View {}
'''),
)

The default behavior fails when a file already exists. Use FileExistsStrategy.skip or FileExistsStrategy.overwrite only when a recipe explicitly wants that behavior.

Post Execution

Post actions run only after --apply succeeds. Built-ins include:

  • DartFormatPostExecution
  • ProcessPostExecution
  • BuildRunnerPostExecution

Project Integration

Keep project conventions outside this package:

extension ProjectCodemodContext on CodemodContext {
  String get featureName => require('feature');
  String get featureFile => 'lib/features/${snake('feature')}.dart';
}

Then project-specific recipes can import this package plus their local extension file.

API Reference

See the API documentation for detailed information about all public classes and functions.

Key components:

  • CodemodRecipe: Define recipes with arguments, operations, and post-execution actions.
  • CodemodRunner: Execute recipes with CLI parsing and dry-run/apply support.
  • CodemodContext: Access arguments and convert between naming conventions.
  • CodemodTemplate: Render file and code templates with variable substitution.
  • CodeEditor: AST-guided code modifications using the Dart analyzer.
  • Transforms: Pre-built transforms for common operations like adding imports, methods, fields, and annotations.

Examples

The example/ directory contains runnable examples demonstrating:

  • add_method_example: Adding methods to existing classes
  • scaffold_feature_example: Creating new feature files from templates
  • composed_recipe_example: Combining multiple recipes with shared arguments

Run an example:

cd example/add_method_example
dart pub get
dart run bin/add_method.dart --help

Testing

Run package tests:

dart test

Tests should cover generic template rendering, operation behavior, patch behavior, recipe composition, and AST-guided editor primitives. Project-specific transforms should be tested in the consuming project.

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines on development setup, running tests, and submitting pull requests.

License

This project is licensed under the BSD-3-Clause License - see the LICENSE file for details.

Libraries

codemod_recipe
Public API for building deterministic, recipe-based Dart codemods.