class Parser::AST::Node
Parser::AST::Node
monkey patch.
Public Instance Methods
Get arguments node of :send, :block or :defined? node.
@return [Array<Parser::AST::Node>] arguments node. @raise [Synvert::Core::MethodNotSupported] if calls on other node.
# File lib/synvert/core/node_ext.rb, line 78 def arguments case type when :def, :block children[1] when :defs children[2] when :send children[2..-1] when :defined? children else raise Synvert::Core::MethodNotSupported, "arguments is not handled for #{debug_info}" end end
Get body node of :begin or :block node.
@return [Array<Parser::AST::Node>] body node. @raise [Synvert::Core::MethodNotSupported] if calls on other node.
# File lib/synvert/core/node_ext.rb, line 109 def body case type when :begin children when :def, :block, :class, :module return [] if children[2].nil? :begin == children[2].type ? children[2].body : children[2..-1] when :defs return [] if children[3].nil? :begin == children[3].type ? children[3].body : children[3..-1] else raise Synvert::Core::MethodNotSupported, "body is not handled for #{debug_info}" end end
Get caller node of :block node.
@return [Parser::AST::Node] caller node. @raise [Synvert::Core::MethodNotSupported] if calls on other node.
# File lib/synvert/core/node_ext.rb, line 97 def caller if :block == type children[0] else raise Synvert::Core::MethodNotSupported, "caller is not handled for #{debug_info}" end end
Get the source range of child node.
@param [String] name of child node. @return [Parser::Source::Range] source range of child node.
# File lib/synvert/core/node_ext.rb, line 343 def child_node_range(child_name) case [type, child_name.to_sym] when %i[block pipes], %i[def parentheses], %i[defs parentheses] Parser::Source::Range.new('(string)', arguments.loc.expression.begin_pos, arguments.loc.expression.end_pos) when %i[block arguments], %i[def arguments], %i[defs arguments] Parser::Source::Range.new( '(string)', arguments.first.loc.expression.begin_pos, arguments.last.loc.expression.end_pos ) when %i[class name], %i[def name], %i[defs name] loc.name when %i[defs dot] loc.operator when %i[defs self] Parser::Source::Range.new('(string)', loc.operator.begin_pos - 'self'.length, loc.operator.begin_pos) when %i[send dot] loc.dot when %i[send message] if loc.operator Parser::Source::Range.new('(string)', loc.selector.begin_pos, loc.operator.end_pos) else loc.selector end when %i[send parentheses] if loc.begin && loc.end Parser::Source::Range.new('(string)', loc.begin.begin_pos, loc.end.end_pos) end else direct_child_name, nested_child_name = child_name.to_s.split('.', 2) if respond_to?(direct_child_name) child_node = send(direct_child_name) return child_node.child_node_range(nested_child_name) if nested_child_name return nil if child_node.nil? if child_node.is_a?(Parser::AST::Node) return( Parser::Source::Range.new( '(string)', child_node.loc.expression.begin_pos, child_node.loc.expression.end_pos ) ) end # arguments return nil if child_node.empty? return( Parser::Source::Range.new( '(string)', child_node.first.loc.expression.begin_pos, child_node.last.loc.expression.end_pos ) ) end raise Synvert::Core::MethodNotSupported, "child_node_range is not handled for #{debug_info}, child_name: #{child_name}" end end
Get the column of current node.
@return [Integer] column.
# File lib/synvert/core/node_ext.rb, line 328 def column loc.expression.column end
Get condition node of :if node.
@return [Parser::AST::Node] condition node. @raise [Synvert::Core::MethodNotSupported] if calls on other node.
# File lib/synvert/core/node_ext.rb, line 130 def condition if :if == type children[0] else raise Synvert::Core::MethodNotSupported, "condition is not handled for #{debug_info}" end end
# File lib/synvert/core/node_ext.rb, line 308 def debug_info "\n" + [ "file: #{loc.expression.source_buffer.name}", "line: #{loc.expression.line}", "source: #{to_source}", "node: #{inspect}" ].join("\n") end
Get hash value node according to specified key.
@param [Object] key value. @return [Parser::AST::Node] value node. @raise [Synvert::Core::MethodNotSupported] if calls on other node.
# File lib/synvert/core/node_ext.rb, line 180 def hash_value(key) if :hash == type value_node = children.find { |pair_node| pair_node.key.to_value == key } value_node&.value else raise Synvert::Core::MethodNotSupported, "hash_value is not handled for #{debug_info}" end end
Get key node of hash :pair node.
@return [Parser::AST::Node] key node. @raise [Synvert::Core::MethodNotSupported] if calls on other node.
# File lib/synvert/core/node_ext.rb, line 193 def key if :pair == type children.first else raise Synvert::Core::MethodNotSupported, "key is not handled for #{debug_info}" end end
Test if hash node contains specified key.
@param [Object] key value. @return [Boolean] true if specified key exists. @raise [Synvert::Core::MethodNotSupported] if calls on other node.
# File lib/synvert/core/node_ext.rb, line 167 def key?(key) if :hash == type children.any? { |pair_node| pair_node.key.to_value == key } else raise Synvert::Core::MethodNotSupported, "key? is not handled for #{debug_info}" end end
Get keys node of :hash node.
@return [Array<Parser::AST::Node>] keys node. @raise [Synvert::Core::MethodNotSupported] if calls on other node.
# File lib/synvert/core/node_ext.rb, line 142 def keys if :hash == type children.map { |child| child.children[0] } else raise Synvert::Core::MethodNotSupported, "keys is not handled for #{debug_info}" end end
Return the left value.
@return [Parser::AST::Node] variable nodes. @raise [Synvert::Core::MethodNotSupported] if calls on other node.
# File lib/synvert/core/node_ext.rb, line 217 def left_value if %i[masgn lvasgn ivasgn].include? type children[0] elsif :or_asgn == type children[0].children[0] else raise Synvert::Core::MethodNotSupported, "left_value is not handled for #{debug_info}" end end
Get the line of current node.
@return [Integer] line.
# File lib/synvert/core/node_ext.rb, line 335 def line loc.expression.line end
Match current node with rules.
@param rules [Hash] rules to match. @return true if matches.
# File lib/synvert/core/node_ext.rb, line 424 def match?(rules) flat_hash(rules).keys.all? do |multi_keys| case multi_keys.last when :any, :contain actual_values = actual_value(self, multi_keys[0...-1]) expected = expected_value(rules, multi_keys) actual_values.any? { |actual| match_value?(actual, expected) } when :not actual = actual_value(self, multi_keys[0...-1]) expected = expected_value(rules, multi_keys) !match_value?(actual, expected) when :in actual = actual_value(self, multi_keys[0...-1]) expected_values = expected_value(rules, multi_keys) expected_values.any? { |expected| match_value?(actual, expected) } when :not_in actual = actual_value(self, multi_keys[0...-1]) expected_values = expected_value(rules, multi_keys) expected_values.all? { |expected| !match_value?(actual, expected) } else actual = actual_value(self, multi_keys) expected = expected_value(rules, multi_keys) match_value?(actual, expected) end end end
Get message node of :super or :send node.
@return [Parser::AST::Node] mesage node. @raise [Synvert::Core::MethodNotSupported] if calls on other node.
# File lib/synvert/core/node_ext.rb, line 63 def message case type when :super, :zsuper :super when :send children[1] else raise Synvert::Core::MethodNotSupported, "message is not handled for #{debug_info}" end end
Respond key value for hash node, e.g.
Current node is s(:hash, s(:pair, s(:sym, :number), s(:int, 10))) node.number_value is 10
# File lib/synvert/core/node_ext.rb, line 268 def method_missing(method_name, *args, &block) if :args == type && children.respond_to?(method_name) return children.send(method_name, *args, &block) elsif :hash == type && method_name.to_s.include?('_value') key = method_name.to_s.sub('_value', '') return hash_value(key.to_sym)&.to_value if key?(key.to_sym) return hash_value(key.to_s)&.to_value if key?(key.to_s) return nil elsif :hash == type && method_name.to_s.include?('_source') key = method_name.to_s.sub('_source', '') return hash_value(key.to_sym)&.to_source if key?(key.to_sym) return hash_value(key.to_s)&.to_source if key?(key.to_s) return nil end super end
Get name node of :class, :module, :const, :mlhs, :def and :defs node.
@return [Parser::AST::Node] name node. @raise [Synvert::Core::MethodNotSupported] if calls on other node.
# File lib/synvert/core/node_ext.rb, line 10 def name case type when :class, :module, :def, :arg, :blockarg, :restarg, :lvar, :ivar, :cvar children[0] when :defs, :const children[1] when :mlhs self else raise Synvert::Core::MethodNotSupported, "name is not handled for #{debug_info}" end end
Get parent_class
node of :class node.
@return [Parser::AST::Node] parent_class
node. @raise [Synvert::Core::MethodNotSupported] if calls on other node.
# File lib/synvert/core/node_ext.rb, line 27 def parent_class if :class == type children[1] else raise Synvert::Core::MethodNotSupported, "parent_class is not handled for #{debug_info}" end end
Get parent constant node of :const node.
@return [Parser::AST::Node] parent const node. @raise [Synvert::Core::MethodNotSupported] if calls on other node.
# File lib/synvert/core/node_ext.rb, line 39 def parent_const if :const == type children[0] else raise Synvert::Core::MethodNotSupported, "parent_const is not handled for #{debug_info}" end end
Get receiver node of :send node.
@return [Parser::AST::Node] receiver node. @raise [Synvert::Core::MethodNotSupported] if calls on other node.
# File lib/synvert/core/node_ext.rb, line 51 def receiver if :send == type children[0] else raise Synvert::Core::MethodNotSupported, "receiver is not handled for #{debug_info}" end end
Recursively iterate all child nodes of current node.
@yield [child] Gives a child node. @yieldparam child [Parser::AST::Node] child node
# File lib/synvert/core/node_ext.rb, line 411 def recursive_children(&block) children.each do |child| if child.is_a?(Parser::AST::Node) yield child child.recursive_children(&block) end end end
# File lib/synvert/core/node_ext.rb, line 288 def respond_to_missing?(method_name, *args) if :args == type && children.respond_to?(method_name) return true elsif :hash == type && method_name.to_s.include?('_value') key = method_name.to_s.sub('_value', '') return true if key?(key.to_sym) || key?(key.to_s) elsif :hash == type && method_name.to_s.include?('_source') key = method_name.to_s.sub('_source', '') return true if key?(key.to_sym) || key?(key.to_s) end super end
Get rewritten source code. @example
node.rewritten_source("create({{arguments}})") #=> "create(:post)"
@param code [String] raw code. @return [String] rewritten code, replace string in block {{ }} in raw code. @raise [Synvert::Core::MethodNotSupported] if string in block {{ }} does not support.
# File lib/synvert/core/node_ext.rb, line 458 def rewritten_source(code) code.gsub(/{{(.*?)}}/m) do old_code = Regexp.last_match(1) if respond_to? old_code.split(/\.|\[/).first evaluated = instance_eval old_code case evaluated when Parser::AST::Node if evaluated.type == :args evaluated.loc.expression.source[1...-1] else evaluated.loc.expression.source end when Array if evaluated.size > 0 file_source = evaluated.first.loc.expression.source_buffer.source source = file_source[evaluated.first.loc.expression.begin_pos...evaluated.last.loc.expression.end_pos] lines = source.split "\n" lines_count = lines.length if lines_count > 1 && lines_count == evaluated.size new_code = [] lines.each_with_index { |line, index| new_code << (index == 0 ? line : line[evaluated.first.indent - 2..-1]) } new_code.join("\n") else source end end when String, Symbol, Integer, Float evaluated when NilClass 'nil' else raise Synvert::Core::MethodNotSupported, "rewritten_source is not handled for #{evaluated.inspect}" end else "{{#{old_code}}}" end end end
Return the right value.
@return [Array<Parser::AST::Node>] variable nodes. @raise [Synvert::Core::MethodNotSupported] if calls on other node.
# File lib/synvert/core/node_ext.rb, line 231 def right_value if %i[masgn lvasgn ivasgn or_asgn].include? type children[1] else raise Synvert::Core::MethodNotSupported, "right_value is not handled for #{debug_info}" end end
strip curly braces for hash
# File lib/synvert/core/node_ext.rb, line 500 def strip_curly_braces return to_source unless type == :hash to_source.sub(/^{(.*)}$/) { Regexp.last_match(1).strip } end
convert lambda {} to -> {}
# File lib/synvert/core/node_ext.rb, line 528 def to_lambda_literal if type == :block && caller.type == :send && caller.receiver.nil? && caller.message == :lambda new_source = to_source if arguments.size > 1 new_source = new_source[0...arguments.loc.begin.to_range.begin - 1] + new_source[arguments.loc.end.to_range.end..-1] new_source = new_source.sub('lambda', "->(#{arguments.map(&:to_source).join(', ')})") else new_source = new_source.sub('lambda', '->') end new_source else to_source end end
# File lib/synvert/core/node_ext.rb, line 302 def to_s if :mlhs == type "(#{children.map(&:name).join(', ')})" end end
get single quote string
# File lib/synvert/core/node_ext.rb, line 514 def to_single_quote return to_source unless type == :str "'#{to_value}'" end
Get the source code of current node.
@return [String] source code.
# File lib/synvert/core/node_ext.rb, line 321 def to_source loc.expression&.source end
convert string to symbol
# File lib/synvert/core/node_ext.rb, line 521 def to_symbol return to_source unless type == :str ":#{to_value}" end
Return the exact value.
@return [Object] exact value. @raise [Synvert::Core::MethodNotSupported] if calls on other node.
# File lib/synvert/core/node_ext.rb, line 243 def to_value case type when :int, :float, :str, :sym children.last when :true true when :false false when :array children.map(&:to_value) when :irange (children.first.to_value..children.last.to_value) when :erange (children.first.to_value...children.last.to_value) when :begin children.first.to_value else self end end
Get value node of hash :pair node.
@return [Parser::AST::Node] value node. @raise [Synvert::Core::MethodNotSupported] if calls on other node.
# File lib/synvert/core/node_ext.rb, line 205 def value if :pair == type children.last else raise Synvert::Core::MethodNotSupported, "value is not handled for #{debug_info}" end end
Get values node of :hash node.
@return [Array<Parser::AST::Node>] values node. @raise [Synvert::Core::MethodNotSupported] if calls on other node.
# File lib/synvert/core/node_ext.rb, line 154 def values if :hash == type children.map { |child| child.children[1] } else raise Synvert::Core::MethodNotSupported, "keys is not handled for #{debug_info}" end end
wrap curly braces for hash
# File lib/synvert/core/node_ext.rb, line 507 def wrap_curly_braces return to_source unless type == :hash "{ #{to_source} }" end
Private Instance Methods
Get actual value from the node.
@param node [Parser::AST::Node] @param multi_keys [Array<Symbol>] @return [Object] actual value.
# File lib/synvert/core/node_ext.rb, line 627 def actual_value(node, multi_keys) multi_keys.inject(node) { |n, key| n.send(key) if n } end
Get expected value from rules.
@param rules [Hash] @param multi_keys [Array<Symbol>] @return [Object] expected value.
# File lib/synvert/core/node_ext.rb, line 636 def expected_value(rules, multi_keys) multi_keys.inject(rules) { |o, key| o[key] } end
Convert a hash to flat one.
@example
flat_hash(type: 'block', caller: {type: 'send', receiver: 'RSpec'}) #=> {[:type] => 'block', [:caller, :type] => 'send', [:caller, :receiver] => 'RSpec'}
@param h [Hash] original hash. @return flatten hash.
# File lib/synvert/core/node_ext.rb, line 610 def flat_hash(h, k = []) new_hash = {} h.each_pair do |key, val| if val.is_a?(Hash) new_hash.merge!(flat_hash(val, k + [key])) else new_hash[k + [key]] = val end end new_hash end
Compare actual value with expected value.
@param actual [Object] actual value. @param expected [Object] expected value. @return [Integer] -1, 0 or 1. @raise [Synvert::Core::MethodNotSupported] if expected class is not supported.
# File lib/synvert/core/node_ext.rb, line 551 def match_value?(actual, expected) return true if actual == expected case expected when Symbol if actual.is_a?(Parser::AST::Node) actual.to_source == ":#{expected}" || actual.to_source == expected.to_s else actual.to_sym == expected end when String if actual.is_a?(Parser::AST::Node) actual.to_source == expected || actual.to_source == unwrap_quote(expected) || unwrap_quote(actual.to_source) == expected || unwrap_quote(actual.to_source) == unwrap_quote(expected) else actual.to_s == expected || wrap_quote(actual.to_s) == expected end when Regexp if actual.is_a?(Parser::AST::Node) actual.to_source =~ Regexp.new(expected.to_s, Regexp::MULTILINE) else actual.to_s =~ Regexp.new(expected.to_s, Regexp::MULTILINE) end when Array return false unless expected.length == actual.length actual.zip(expected).all? { |a, e| match_value?(a, e) } when NilClass if actual.is_a?(Parser::AST::Node) :nil == actual.type else actual.nil? end when Numeric if actual.is_a?(Parser::AST::Node) actual.children[0] == expected else actual == expected end when TrueClass :true == actual.type when FalseClass :false == actual.type when Parser::AST::Node actual == expected when Synvert::Core::Rewriter::AnyValue !actual.nil? else raise Synvert::Core::MethodNotSupported, "#{expected.class} is not handled for match_value?" end end
# File lib/synvert/core/node_ext.rb, line 648 def unwrap_quote(string) if (string[0] == '"' && string[-1] == '"') || (string[0] == "'" && string[-1] == "'") string[1...-1] else string end end
# File lib/synvert/core/node_ext.rb, line 640 def wrap_quote(string) if string.include?("'") "\"#{string}\"" else "'#{string}'" end end