LoginSignup
11
10

More than 5 years have passed since last update.

Synvert で Ruby のコードを構文レベルでリファクタリングする

Posted at

Synvert とは?

Synvert = syntax + convert, makes it easy to convert ruby code automatically.

README - xinminlabs/synvert より

Synvert で用意されいている AST の操作の操作や置換を行うDSLを用いて Ruby のコードを変換できます。
Synvert 自体はリファクタリング専用のツールではありませんが、リファクタリングに利用することもできます。

サンプル1 String#gsub to String#tr

Synvert の Snippet として用意されている例をみます。
gsub で 1文字の置換を行っている箇所を tr に置きかえる Snippet です。
なぜ gsub を tr に変えるか?についてはパフォーマンスです。詳しくは以下の記事に。

例えば Rails で以下のような Pull Request がありました。

Synvert でひとたび gsub => tr への変換 Snippet を作っておけば
以降はこれを実行するだけでリファクタリングは完了です。

コード

Synvert::Rewriter.new 'ruby', 'gsub_to_tr' do
  description <<-EOF
It converts String#gsub to String#tr
    'slug from title'.gsub(' ', '_')
    =>
    'slug from title'.tr(' ', '_')
  EOF

  within_files '**/*.rb' do
    # 'slug from title'.gsub(' ', '_')
    # =>
    # 'slug from title'.tr(' ', '_')
    with_node type: 'send', message: 'gsub', arguments: {size: 2, first: {type: 'str'}, last: {type: 'str'}} do
      if node.arguments.first.to_value.length == 1 && node.arguments.last.to_value.length < 2
        replace_with "{{receiver}}.tr({{arguments}})"
      end
    end
  end
end

サンプル2 > 0, < 0 to Numeric#positive?, negative?

Ruby 2.3 で導入された Numeric#positive?, negative? 記法へのリファクタリングのための Snippet を作成します。
該当メソッドに関する詳細説明はいつも素晴らしい情報を発信してくださっている伊藤さん( @jnchito )の記事がわかりやすいです。

置換対象コード

10.times do
  a = rand(-5..5)
  if (a > 0)
    puts "positive"
  elsif (a < 0)
    puts "negative"
  else
    puts "zero"
  end
end

Synvert 自作 Snippet

Synvert::Rewriter.new "ruby_2_3_new_feature", "nega_posi" do
  description "replace hoge > 0 to hoge.positive?, hoge < 0 to hoge.negative?"

  within_file 'sample_target*.rb' do
    with_node type: 'send', message: '>', arguments: {first: {to_value: 0}} do
      replace_with "{{receiver}}.positive?"
    end

    with_node type: 'send', message: '<', arguments: {first: {to_value: 0}} do
      replace_with "{{receiver}}.negative?"
    end
  end
end

実行

  • synvert を実行
$ synvert --load sample_execute.rb -r ruby_2_3_new_feature/nega_posi
===== ruby_2_3_new_feature/nega_posi started =====
===== ruby_2_3_new_feature/nega_posi done =====
  • 実行後にリファクタリングされたコードを確認
10.times do
  a = rand(-5..5)
  if (a.positive?)
    puts "positive"
  elsif (a.negative?)
    puts "negative"
  else
    puts "zero"
  end
end

メモ

Synvert は内部で parser gem を利用している。

外部資料

11
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
10