Skip to content

Instantly share code, notes, and snippets.

@baweaver
Last active August 3, 2023 22:53
Show Gist options
  • Save baweaver/dcd24398191095bec42561dea9f3cf31 to your computer and use it in GitHub Desktop.
Save baweaver/dcd24398191095bec42561dea9f3cf31 to your computer and use it in GitHub Desktop.
Pattern matching applied to ASTs, defining the transformation between shorthand and standard block format.
require "rubocop"
# Useful for debugging and seeing how the nodes deconstruct
def deep_deconstruct(node)
return node unless node.respond_to?(:deconstruct)
node.deconstruct.map { deep_deconstruct(_1) }
end
def block_to_shorthand?(a, b)
return false unless a.block_type? && b.send_type?
a in [:block, # s(:block,
[:send, target, target_method], # s(:send, s(:array), :select),
[[:arg, v]], # s(:args, s(:arg, :v)),
[:send, [:lvar, ^v], block_method] # s(:send, s(:lvar, :v), :even?))
] or return false
b in [:send, # s(:send,
^target, ^target_method, # s(:array), :select,
[:block_pass, [:sym, ^block_method]] # s(:block_pass, s(:sym, :even?)))
] or return false
true
end
def shorthand_to_block?(a, b)
return false unless a.send_type? && b.block_type?
a in [:send, # s(:send,
target, target_method, # s(:array), :select,
[:block_pass, [:sym, block_method]] # s(:block_pass, s(:sym, :even?)))
] or return false
b in [:block, # s(:block,
[:send, ^target, ^target_method], # s(:send, s(:array), :select),
[[:arg, v]], # s(:args, s(:arg, :v)),
[:send, [:lvar, ^v], ^block_method] # s(:send, s(:lvar, :v), :even?))
] or return false
true
end
def ast_from(string)
case string
when String then processed_source_from(string).ast
when RuboCop::ProcessedSource then string.ast
when RuboCop::AST::Node then string
else nil
end
end
standard_ast = ast_from("[].select { |v| v.even? }")
standard_ast_alt = ast_from("[].select { |v| x.even? }")
shorthand_ast = ast_from("[].select(&:even?)")
shorthand_ast_alt = ast_from("[].select(&:odd?)")
block_to_shorthand?(standard_ast, shorthand_ast)
# => true
block_to_shorthand?(standard_ast_alt, shorthand_ast)
# => false
block_to_shorthand?(standard_ast, shorthand_ast_alt)
# => false
shorthand_to_block?(shorthand_ast, standard_ast)
# => true
def method_rename?(a, b)
return false unless a.type == b.type
if a.block_type?
a in [:block, [:send, target, name, *a_args], *a_rest] or return false
b in [:block, [:send, ^target, new_name, *b_args], *b_rest] or return false
name != new_name && a_args == b_args && a_rest == b_rest
elsif a.send_type?
a in [:send, target, name, *a_args] or return false
b in [:send, ^target, new_name, *b_args] or return false
name != new_name && a_args == b_args
else
false
end
end
method_rename?(ast_from("s.method(a)"), ast_from("s.meth(a)"))
# => true
method_rename?(ast_from("s.method(a)"), ast_from("s.meth(a, b)"))
# => false
method_rename?(ast_from("s.method"), ast_from("s.meth"))
# => true
method_rename?(ast_from("s.method {}"), ast_from("s.meth {}"))
# => true
method_rename?(ast_from("s.method {}"), ast_from("s.meth { _1 }"))
# => false
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment