#!/usr/bin/env ruby # frozen_string_literal: true # Emit AST from parsed Ruby code by RuboCop. # # This is an alternative to `ruby-parser` shipped with `parser` gem. # # Usage: # rubocop-parse -e 'puts "hello"' # (send nil :puts # (str "hello")) # # rubocop-parse -e 'puts "hello"' -v 3.0 # (send nil :puts # (str "hello")) # # rubocop-parse app/models/project.rb # (begin # (send nil :require # (str "carrierwave/orm/activerecord")) # (class # (const nil :Project) # (const nil :ApplicationRecord) # (begin # (send nil :include # ... require_relative '../config/bundler_setup' require 'rubocop' require 'optparse' module Helper extend self class << self attr_writer :ruby_version end def ast(source, file: '', version: nil) version ||= ruby_version ast = RuboCop::AST::ProcessedSource.new(source, version).ast return ast if ast warn "Syntax error in `#{source}`." end def pattern(string) RuboCop::NodePattern.new(string) end def help! puts <<~HELP Use `ast(source_string, version: nil)` method to parse code and return its AST. Use `pattern(string)` to compile RuboCop's node patterns. See https://docs.rubocop.org/rubocop-ast/node_pattern.html. Examples: node = ast('puts :hello') pat = pattern('`(sym :hello)') pat.match(node) # => true HELP nil end def ruby_version @ruby_version ||= rubocop_target_ruby_version end def rubocop_target_ruby_version @rubocop_target_ruby_version ||= RuboCop::ConfigStore.new.for_file('.').target_ruby_version end end def start_irb require 'irb' include Helper # rubocop:disable Style/MixinUsage puts <<~BANNER Ruby version: #{ruby_version} Type `help!` for instructions and examples. BANNER IRB.start end options = Struct.new(:eval, :interactive, :print_help, keyword_init: true).new parser = OptionParser.new do |opts| opts.banner = "Usage: #{$PROGRAM_NAME} [-e code] [FILE...]" opts.on('-e FRAGMENT', '--eval FRAGMENT', 'Process a fragment of Ruby code') do |code| options.eval = code end opts.on('-i', '--interactive', '') do options.interactive = true end opts.on('-v RUBY_VERSION', '--ruby-version RUBY_VERSION', 'Parse as Ruby would. Defaults to RuboCop TargetRubyVersion setting.') do |ruby_version| Helper.ruby_version = Float(ruby_version) end opts.on('-h', '--help') do options.print_help = true end end files = parser.parse! if options.print_help puts parser elsif options.interactive if options.eval || files.any? puts "Cannot combine `--interactive` with `--eval` or passing files. Aborting..." puts puts parser exit 1 else start_irb end elsif options.eval puts Helper.ast(options.eval) elsif files.any? files.each do |file| if File.file?(file) source = File.read(file) puts Helper.ast(source, file: file) else warn "Skipping non-file #{file.inspect}" end end else puts parser end