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
|