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 を利用している。