1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
|
#!/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
|