class Parser::AST::Node

Parser::AST::Node monkey patch.

Public Instance Methods

arguments() click to toggle source

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
body() click to toggle source

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
caller() click to toggle source

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
child_node_range(child_name) click to toggle source

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
column() click to toggle source

Get the column of current node.

@return [Integer] column.

# File lib/synvert/core/node_ext.rb, line 328
def column
  loc.expression.column
end
condition() click to toggle source

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
debug_info() click to toggle source
# 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
hash_value(key) click to toggle source

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
key() click to toggle source

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
key?(key) click to toggle source

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
keys() click to toggle source

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
left_value() click to toggle source

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
line() click to toggle source

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?(rules) click to toggle source

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
message() click to toggle source

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
method_missing(method_name, *args, &block) click to toggle source

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

Calls superclass method
# 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
name() click to toggle source

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
parent_class() click to toggle source

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
parent_const() click to toggle source

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
receiver() click to toggle source

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
recursive_children() { |child| ... } click to toggle source

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
respond_to_missing?(method_name, *args) click to toggle source
Calls superclass method
# 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
rewritten_source(code) click to toggle source

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
right_value() click to toggle source

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() click to toggle source

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
to_lambda_literal() click to toggle source

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
to_s() click to toggle source
# File lib/synvert/core/node_ext.rb, line 302
def to_s
  if :mlhs == type
    "(#{children.map(&:name).join(', ')})"
  end
end
to_single_quote() click to toggle source

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
to_source() click to toggle source

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
to_symbol() click to toggle source

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
to_value() click to toggle source

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
value() click to toggle source

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
values() click to toggle source

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() click to toggle source

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

actual_value(node, multi_keys) click to toggle source

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
expected_value(rules, multi_keys) click to toggle source

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
flat_hash(h, k = []) click to toggle source

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
match_value?(actual, expected) click to toggle source

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
unwrap_quote(string) click to toggle source
# 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
wrap_quote(string) click to toggle source
# File lib/synvert/core/node_ext.rb, line 640
def wrap_quote(string)
  if string.include?("'")
    "\"#{string}\""
  else
    "'#{string}'"
  end
end