|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +require "parser/current" |
| 4 | + |
| 5 | +module CopyCat |
| 6 | + extend self |
| 7 | + |
| 8 | + # Copy methods from The original rails class to our adapter. |
| 9 | + # While copying, we can rewrite the source code of the method using |
| 10 | + # ast. Use `debug: true` to lead you true that process. |
| 11 | + def copy_methods(new_klass, old_klass, *methods, debug: false, &block) |
| 12 | + methods.each do |met| |
| 13 | + file, _ = old_klass.instance_method(met).source_location |
| 14 | + ast = find_method(Parser::CurrentRuby.parse_file(file), met) |
| 15 | + code = |
| 16 | + if block_given? |
| 17 | + source = ast.location.expression.source |
| 18 | + buffer = Parser::Source::Buffer.new(met, source: source) |
| 19 | + # We need to recompute the ast to have correct locations. |
| 20 | + ast = Parser::CurrentRuby.parse(source) |
| 21 | + |
| 22 | + if debug |
| 23 | + puts "=" * 80 |
| 24 | + puts "Rewriter doc: https://www.rubydoc.info/gems/parser/3.3.0.5/Parser/TreeRewriter" |
| 25 | + puts "Pattern matching doc: https://docs.ruby-lang.org/en/master/syntax/pattern_matching_rdoc.html" |
| 26 | + puts |
| 27 | + puts "Method: #{met}" |
| 28 | + puts |
| 29 | + puts "Source:" |
| 30 | + puts buffer.source |
| 31 | + puts |
| 32 | + puts "AST:" |
| 33 | + pp ast |
| 34 | + puts |
| 35 | + end |
| 36 | + rewriter_class = Class.new(Parser::TreeRewriter, &block) |
| 37 | + rewriter_class.new.rewrite(buffer, ast) |
| 38 | + else |
| 39 | + ast.location.expression.source |
| 40 | + end |
| 41 | + if debug and block_given? |
| 42 | + puts "Rewritten source:" |
| 43 | + puts code |
| 44 | + puts "=" * 80 |
| 45 | + end |
| 46 | + location = caller_locations(3, 1).first |
| 47 | + new_klass.class_eval(code, location.absolute_path, location.lineno) |
| 48 | + end |
| 49 | + end |
| 50 | + |
| 51 | + def find_method(ast, method_name) |
| 52 | + method_name = method_name.to_sym |
| 53 | + to_search = [ast] |
| 54 | + while !to_search.empty? |
| 55 | + node = to_search.shift |
| 56 | + next unless node.is_a?(Parser::AST::Node) |
| 57 | + if node in [:def, ^method_name, *] |
| 58 | + return node |
| 59 | + end |
| 60 | + to_search += node.children |
| 61 | + end |
| 62 | + return nil |
| 63 | + end |
| 64 | +end |
0 commit comments