はじめに
ASTとはなにかというものを、簡単なコードを書きながら理解していく記事です。
今回は、parser
を用いて Ruby のコードから AST(Abstract Syntax Tree)の構造を確認し、さらに Rubocop で簡単なカスタムCopを作成して、理解を深めてみました。
背景・動機
先日の柏rbの振り返り回が始まる前に、チラっと話題に出た 「AST」 というワード。
正直、自分はそれが何かすらまったくわかっていませんでした。
「抽象構文木?」「構文解析?」という状態。
なので、まずは手を動かしながら自分なりに調べてみよう!という気持ちで、実際に試してみました。
実例・やってみたこと
まずは、ASTとはどのような構造なのかを実際に確認してみました。
Parserをつかってみることで表せるみたいなので、簡単にファイルを作成してみます。
# sample.rb require 'parser/current' require 'pp' code = 'puts "hello world"' ast = Parser::CurrentRuby.parse(code) pp ast # 以下のように出力されます # s(:send, nil, :puts, # s(:str, "hello world"))
ASTの理解
- :sendは、 メソッド呼び出し
- nilは、レシーバーが無いことを表している
- :putsは、メソッド名
- s(:srt, "hello world")は、引数の文字列
Rubyのコードが各ノードとして構造化されるようになる。これが、ASTと言われる構造
そのノードを使って、putsを使わないカスタムcopを作成する
custom_cops/no_puts_cop.rb require 'rubocop' module RuboCop module Cop module Custom class NoPuts < Base MSG = 'putsを使わず、Rails.loggerなどを使いましょう。' def on_send(node) # メソッドの呼び出しに反応するメソッド on_defとかもある return unless node.method?(:puts) return unless node.receiver.nil? add_offense(node) # ルール違反があれば、出力する end end end end end
上記をrubocop.yml で定義する
# ./rubocop.yml require: - ./custom_cops/no_puts_cop.rb Custom/NoPuts: Enabled: true
実際に使用してみる(sample.rbにputsを使った処理を記述する)
rubocop sample.rb -config /.rubocop.yml
hello_world.rb:1:1: C: Custom/NoPuts: putsを使わず、Rails.loggerなどを使いましょう。 puts "hello world" ^^^^^^^^^^^^^^^^^^
学び・気づき・解釈
ASTは「コードをツリー構造で表したもの」として理解すれば、意外と読みやすかも?( 最初はとっつきにくいですが、、、)
parser gemで簡単にASTを出力でき、Copの設計にも役立つ
RubocopのCopはASTのノード(例: on_send, on_def)に反応して動く
「puts禁止」のような単純なルールなら、ASTの基本的な理解だけで十分実装できる
まとめ
初めて触れるASTは、最初こそとっつきにくく感じますが、「ツリー構造を読む」意識で見ると少しずつ見えてきます。
今回のように parser で構文を分解して見てみることで、Rubocopの仕組みにも親しみやすくなりました。