16
loading...
This website collects cookies to deliver better user experience
pp Ripper.sexp('def hello(world) "Hello, #{world}!"; end')
# [:program,
# [[:def,
# [:@ident, "hello", [1, 4]],
# [:paren,
# [:params, [[:@ident, "world", [1, 10]]], nil, nil, nil, nil, nil, nil]],
# [:bodystmt,
# [[:string_literal,
# [:string_content,
# [:@tstring_content, "Hello, ", [1, 18]],
# [:string_embexpr, [[:var_ref, [:@ident, "world", [1, 27]]]]],
# [:@tstring_content, "!", [1, 33]]]]],
# nil,
# nil,
# nil]]]]
ruby_code = 'def hello(world) "Hello, #{world}!"; end'
expression = Parser::CurrentRuby.parse(ruby_code)
# s(:def, :hello,
# s(:args,
# s(:arg, :world)),
# s(:dstr,
# s(:str, "Hello, "),
# s(:begin,
# s(:lvar, :world)),
# s(:str, "!")))
hash
and want to replace it with something more descriptive, such as person
:hash = HTTP.get('some_site/people/1.json').then { JSON.parse(_1) }
puts "#{hash['name']} was found!"
puts "#{test_object.name} is currently #{test_object.hash}"
# HEREDOCs surrounded in single-quotes prevents interpolation, which
# we need here.
Parser::CurrentRuby.parse <<~'RUBY'
hash = HTTP.get('some_site/people/1.json').then { JSON.parse(_1) }
puts "#{hash['name']} was found!"
puts "#{test_object.name} is currently #{test_object.hash}"
RUBY
# This generates the following AST:
s(:begin,
s(:lvasgn, :hash,
s(:numblock,
s(:send,
s(:send,
s(:const, nil, :HTTP), :get,
s(:str, "some_site/people/1.json")), :then), 1,
s(:send,
s(:const, nil, :JSON), :parse,
s(:lvar, :_1)))),
s(:send, nil, :puts,
s(:dstr,
s(:begin,
s(:send,
s(:lvar, :hash), :[],
s(:str, "name"))),
s(:str, " was found!"))),
s(:send, nil, :puts,
s(:dstr,
s(:begin,
s(:send,
s(:send, nil, :test_object), :name)),
s(:str, " is currently "),
s(:begin,
s(:send,
s(:send, nil, :test_object), :hash)))))
hash
referring to a node of type :lvasgn
(local variable assign) and :lvar
(local variable) as opposed to the later node s(:send, s(:send, nil, :test_object), :hash)))
which relates to the hash
method being called on the test_object
.hash
(borrowing some from src):require "rubocop"
require "parser/current"
def ruby_parser
builder = ::RuboCop::AST::Builder.new
parser = ::Parser::CurrentRuby.new(builder)
parser.diagnostics.all_errors_are_fatal = true
parser
end
def ast_of(s)
buffer = ::Parser::Source::Buffer.new('(ruby)', source: s)
ruby_parser.parse(buffer)
end
def node_pattern(s) = RuboCop::NodePattern.new(s)
hash_match = node_pattern <<~NODE
{ # OR pattern
({lvasgn lvar} :hash _) # Either a local var or assignment
(send nil? :hash) # ...or a call to that variable
} # End OR pattern
NODE
hash_match.match(ast_of("hash = {}"))
# => true
-a
will fix them for you:rubocop -a
def check_method_node(node)
method_name = node.method_name
return unless bad_methods.include?(method_name)
message = format(MSG, prefer: preferred_method(method_name), current: method_name)
add_offense(node.loc.selector, message: message) do |corrector|
corrector.replace(node.loc.selector, preferred_method(node.loc.selector.source))
end
end
add_offense
it uses corrector.replace
to replace with a preferred method source. Not only that, but if we went over to the specs:described_class::FILTER_METHODS.each do |method|
it "registers an offense for #{method}" do
offenses = inspect_source("#{method} :name")
expect(offenses.size).to eq(1)
end
# ...
[1, 2, 3].select { |v| v.even? }
[1, 2, 3].select(&:even?)
O(n^2)
to O(n)
, meaning massive performance benefits.matchable
:public_send
with a directly inlined code-path:valid_keys.each do |key|
deconstructed_values[key] = ${key}
end
deconstructed_values
public_send
is slow compared to directly calling a method. If taken to logical extremes one could not only inline the actual method call, but extract the method code and interpolate it directly into such a method.