DSL

Synvert provides a simple dsl to define a snippet.

Synvert::Rewriter.new "group_name", "snippet_name" do
  description "description"

  if_gem gem_name, gem_version

  within_file file_pattern do
    within_node rules do
      with_node rules do
        remove
      end
    end
  end

  within_files files_pattern do
    with_node rules do
      unless_exist_node rule do
        append code
      end
    end
  end
end

description

Describe what the snippet does.

description 'description of snippet'

if_ruby

Checks if current ruby version is greater than or equal to the specified version.

if_ruby '2.0.0'

if_gem

Checks the gem in Gemfile.lock, if gem version in Gemfile.lock is less than, greater than or equal to the version in if_gem, the snippet will be executed, otherwise, the snippet will be ignored.

if_gem 'factory_girl', '= 2.0.0'
if_gem 'factory_girl', '~> 2.0.0'
if_gem 'factory_girl', '> 2.0.0'
if_gem 'factory_girl', '< 2.0.0'
if_gem 'factory_girl', '>= 2.0.0'
if_gem 'factory_girl', '<= 2.0.0'

add_file

Add a new file and write content.

content = <<~EOS
  ActiveSupport.on_load(:action_controller) do
    wrap_parameters format: [:json]
  end
EOS
add_file 'config/initializers/wrap_parameters.rb', content

remove_file

Remove a file.

remove_file 'config/initiliazers/secret_token.rb'

within_file / within_files

Find files according to file pattern, the block will be executed only for the matching files.

within_file 'spec/spec_helper.rb' do
  # find nodes
  # check nodes
  # add / replace / remove code
end
within_files 'spec/**/*_spec.rb' do
  # find nodes
  # check nodes
  # add / replace / remove code
end

with_node / within_node

Find ast nodes according to the rules, the block will be executed for the matching nodes.

with_node type: 'send', receiver: 'FactoryGirl', message: 'create' do
  # check nodes
  # add / replace / remove code
end
with_node type: 'block', 'caller.receiver': 'FactoryGirl', message: 'create' do
  # check nodes
  # add / replace / remove code
end

goto_node

Go to the specified child code.

with_node type: 'block' do
  goto_node :caller do
    # change code in block caller
  end
end
with_node type: 'block' do
  goto_node 'caller.receiver' do
    # change code in block caller's receiver
  end
end

if_exist_node

Check if the node matches rules exists, if matches, then executes the block.

if_exist_node type: 'send', receiver: 'params', message: '[]' do
  # add / replace / remove code
end

unless_exist_node

Check if the node matches rules does not exist, if does not match, then executes the block.

unless_exist_node type: 'send', message: 'include', arguments: ['FactoryGirl::Syntax::Methods'] do
  # add / replace / remove code
end

if_only_exist_node

Check if the current node contains only one child node and the child node matches rules, if matches, then executes the node.

if_only_exist_node type: 'send', receiver: 'self', message: 'include_root_in_json=', arguments: [false] do
  # add / replace / remove code
end

append

Add the code at the bottom of the current node body.

append 'config.eager_load = false'

prepend

Add the code at the top of the current node body.

prepend "include FactoryGirl::Syntax::Methods"

insert

Insert the code at the beginning or end of the current node.

insert '.first', at: 'end'
insert 'URI.', at: 'beginning'

insert_after

Add the code next to the current node.


secret = SecureRandom.hex(64)
insert_after "{{receiver}}.secret_key_base = '#{secret}'"

replace_with

Replace the current node with the code.


replace_with "create({{arguments}})"

replace

Reaplce child node with the code.


replace :message, with: 'tr'

remove

Remove the current node.

with_node type: 'send', message: 'rename_index' do
  remove
end

delete

Delete the child nodes

with_node type: 'send', message: 'flatten' do
  delete :dot, :mesage
end

wrap

Wrap the current node with a new code.

with_node type: 'class', name: 'TestCase' do
  wrap with: 'module ActiveSupport'
end

replace_erb_stmt_with_expr

Replace erb statemet code with expression code.

with_node type: 'block', caller: { type: 'send', receiver: nil, message: 'form_for' } do
  replace_erb_stmt_with_expr
end

warn

Don’t change any code, but will give a warning message.

warn 'Using a return statement in an inline callback block causes a LocalJumpError to be raised when the callback is executed.'

add_snippet

Add other snippet, it’s easy to reuse other snippets.

add_snippet 'rails', 'convert_dynamic_finders'

redo_until_no_change

Rerun the snippet until no change affected.

redo_until_no_change

helper_method

Add a method which is available in the current snippet.

helper_method :method1 do |arg1, arg2|
  # do anything you want
end

method1(arg1, arg2)

todo

List somethings the snippet should do, but not do yet.

todo <<~EOS
  Rails 4.0 no longer supports loading plugins from vendor/plugins. You
  must replace any plugins by extracting them to gems and adding them to
  your Gemfile. If you choose not to make them gems, you can move them
  into, say, lib/my_plugin/* and add an appropriate initializer in
  config/initializers/my_plugin.rb.
EOS

Check out the source code of DSLs here: this and that