0.0
No commit activity in last 3 years
No release in over 3 years
An AST transformation framework.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 2.1
~> 0.8
~> 5.14
~> 0.13
~> 13.0

Runtime

~> 2.5
~> 0.4
 Project Readme

Build Status Coverage Status

ASTTransform

ASTTransform is an Abstract Syntax Tree (AST) transformation framework. It hooks into the compilation process and allows to perform AST transformations using an annotation: transform!.

Installation

Add this line to your application's Gemfile:

gem 'ast_transform'

And then execute:

$ bundle

Or install it yourself as:

$ gem install ast_transform

Add this to the very beginning of your script or application to install the ASTTransform hook:

require 'ast_transform'
ASTTransform.install

Compatibility with Bootsnap

ASTTransform is compatible with Bootsnap. The only requirement is to install the above hook after Bootsnap, and ASTTransform does the rest for you.

Usage

Getting started using ASTTransform is extremely easy! All you need is to use the transform! annotation:

transform!(MyTransformation)
class MyClass
  # ...
end

When your class is required and loaded into the runtime, ASTTransform will run the MyTransformation transformation on the annotated code.

Supported annotated code

The following expressions can be annotated, which will pass only the annotated AST node to the transformation:

Class definitions

transform!(MyTransformation)
class Foo
  # ...
end

Constant assignments

transform!(MyTransformation)
Foo = Class.new do
  # ...
end

Running multiple transformations

On the same AST node

You can run multiple transformations on the same code, by passing multiple transformations to the annotation:

transform!(MyTransformation1, MyTransformation2)
class Foo
  # ...
end

Note: The transformations will be executed in order, the output of the previous transformation being fed into the next, etc...

On different AST nodes

Because each transform! annotation runs transformations in isolated scope, it is possible to have multiple annotated nodes in the same file:

transform!(MyTransformation)
class Foo
  # ...
end

transform!(MyTransformation)
class Bar
  # ...
end

You can even have nested transform! annotations:

transform!(FooTransformation)
class Foo
  transform!(BarTransformation)
  class Bar
    # ...
  end

  # ...
end

The above code would first process class Foo using FooTransformation (which could even make modifications to Bar on its own), and then BarTransformation would be run against Bar.

Writing Transformations

For more in-depth information regarding processing AST nodes, we recommend looking at https://github.com/whitequark/ast, as transformations are built on top of Parser::AST::Processor, which in turn is built on top of the ast gem.

Transformations should derive from ASTTransform::AbstractTransformation:

require 'ast_transform/abstract_transformation'

class MyTransformation < ASTTransformation::AbstractTransformation
  # ...
end

Transformation discoverability

ASTTransform automatically loads your transformations at compile time. As such, we expect your files to be located at a known path.

Transformations are required using the following scheme, i.e. for MyNamespace::MyTransformation, it will make the following call, so your file must be placed accordingly for ASTTransform to find it:

require 'my_namespace/my_transformation'

Processing each node

To do some processing on each node, override the process_node private method. If you do this, make sure to also process the children nodes if required.

require 'ast_transform/abstract_transformation'

class MyTransformation < ASTTransformation::AbstractTransformation
  private

  def process_node(node)
    # ... processing
    node.updated(nil, process_all(node.children))
  end
end

In the above, node#updated allows updating the node, either its type or its children. Each node is immutable, so updating nodes requires recursively re-creating the tree from the deepest modified nodes. Passing nil as the first argument keeps the same node type.

Processing on certain types of nodes only

The ast gem uses a pattern in which a Transformation may implement a method matching a node type, i.e. on_class, on_send, on_lvar, etc... This is very useful when transformations should process all nodes of this type.

Parameterizable transformations

If you want your transformation to be customizable, accept the parameters in the constructor. The annotation can the be changed accordingly:

class FooTransformation < ASTTransform::AbstractTransformation
  def initialize(param1, params2: false)
    # ...
  end

  # ...
end

transform!(FooTransformation.new(param1, param2: true))
class Foo
  # ...
end

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.