Class: NodeMutation::PrismAdapter

Inherits:
Adapter
  • Object
show all
Defined in:
lib/node_mutation/adapter/prism.rb

Constant Summary

Constants inherited from Adapter

Adapter::INDEX_REGEXP

Instance Method Summary collapse

Instance Method Details

#child_node_range(node, child_name) ⇒ NodeMutation::Struct::Range

Get the range of the child node.

Examples:

node = Prism.parse('foo.bar(test)').value.statements.body.first
child_node_range(node, 'receiver') # { start: 0, end: 'foo'.length }

# node array
node = Prism.parse('foo.bar(a, b)').value.statements.body.first
child_node_range(node, 'arguments.arguments') # { start: 'foo.bar('.length, end: 'foo.bar(a, b'.length }

# index for node array
node = Prism.parse('foo.bar(a, b)').value.statements.body.first
child_node_range(node, 'arguments.arguments.parts.-1') # { start: 'foo.bar(a, '.length, end: 'foo.bar(a, b'.length }

# operator of Binary node
node = Prism.parse('foo | bar').value.statements.body.first
child_node_range(node, 'operator') # { start: 'foo '.length, end: 'foo |'.length }

Parameters:

  • node (Parser::AST::Node)

    The node.

  • child_name (String)

    THe name to find child node.

Returns:



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/node_mutation/adapter/prism.rb', line 117

def child_node_range(node, child_name)
  direct_child_name, nested_child_name = child_name.to_s.split('.', 2)

  if node.is_a?(Array)
    if direct_child_name =~ INDEX_REGEXP
      child_node = node[direct_child_name.to_i]
      raise NodeMutation::MethodNotSupported,
            "#{direct_child_name} is not supported for #{get_source(node)}" unless child_node
      return child_node_range(child_node, nested_child_name) if nested_child_name

      return NodeMutation::Struct::Range.new(child_node.location.start_character_offset, child_node.location.end_character_offset)
    end

    raise NodeMutation::MethodNotSupported,
          "#{direct_child_name} is not supported for #{get_source(node)}" unless node.respond_to?(direct_child_name)

    child_node = node.send(direct_child_name)
    return child_node_range(child_node, nested_child_name) if nested_child_name

    return NodeMutation::Struct::Range.new(child_node.location.start_character_offset, child_node.location.end_character_offset)
  end

  if node.respond_to?("#{child_name}_loc")
    node_loc = node.send("#{child_name}_loc")
    NodeMutation::Struct::Range.new(node_loc.start_character_offset, node_loc.end_character_offset) if node_loc
  elsif node.is_a?(Prism::CallNode) && child_name.to_sym == :name
    NodeMutation::Struct::Range.new(node.message_loc.start_character_offset, node.message_loc.end_character_offset)
  elsif node.is_a?(Prism::LocalVariableReadNode) && child_name.to_sym == :name
    NodeMutation::Struct::Range.new(node.location.start_character_offset, node.location.end_character_offset)
  else
    raise NodeMutation::MethodNotSupported,
          "#{direct_child_name} is not supported for #{get_source(node)}" unless node.respond_to?(direct_child_name)

    child_node = node.send(direct_child_name)

    return child_node_range(child_node, nested_child_name) if nested_child_name

    return nil if child_node.nil?
    return nil if child_node == []

    if child_node.is_a?(Prism::Node)
      return(
        NodeMutation::Struct::Range.new(child_node.location.start_character_offset, child_node.location.end_character_offset)
      )
    end

    return(
      NodeMutation::Struct::Range.new(child_node.first.location.start_character_offset, child_node.last.location.end_character_offset)
    )
  end
end

#file_source(node) ⇒ Object



94
95
96
# File 'lib/node_mutation/adapter/prism.rb', line 94

def file_source(node)
  node.instance_variable_get(:@source).source
end

#get_end(node, child_name = nil) ⇒ Object



174
175
176
177
# File 'lib/node_mutation/adapter/prism.rb', line 174

def get_end(node, child_name = nil)
  node = child_node_by_name(node, child_name) if child_name
  node.location.end_character_offset
end

#get_end_loc(node, child_name = nil) ⇒ Object



184
185
186
187
# File 'lib/node_mutation/adapter/prism.rb', line 184

def get_end_loc(node, child_name = nil)
  node = child_node_by_name(node, child_name) if child_name
  NodeMutation::Struct::Location.new(node.location.end_line, node.location.end_character_column)
end

#get_source(node) ⇒ Object



7
8
9
10
11
12
13
# File 'lib/node_mutation/adapter/prism.rb', line 7

def get_source(node)
  if node.is_a?(Array)
    return node.first.instance_variable_get(:@source).source[node.first.location.start_character_offset...node.last.location.end_character_offset]
  end

  node.to_source
end

#get_start(node, child_name = nil) ⇒ Object



169
170
171
172
# File 'lib/node_mutation/adapter/prism.rb', line 169

def get_start(node, child_name = nil)
  node = child_node_by_name(node, child_name) if child_name
  node.location.start_character_offset
end

#get_start_loc(node, child_name = nil) ⇒ Object



179
180
181
182
# File 'lib/node_mutation/adapter/prism.rb', line 179

def get_start_loc(node, child_name = nil)
  node = child_node_by_name(node, child_name) if child_name
  NodeMutation::Struct::Location.new(node.location.start_line, node.location.start_character_column)
end

#rewritten_source(node, code) ⇒ String

It gets the new source code after evaluating the node.

Examples:

node = Prism.parse('class Synvert; end').value.statements.body.first
rewritten_source(node, '{{constant}}') # 'Synvert'

# index for node array
node = Prism.parse("foo.bar(a, b)").value.statements.body.first
rewritten_source(node, '{{arguments.arguments.parts.-1}}')) # 'b'

# {key}_assoc for HashNode node
node = Prism.parse("after_commit :do_index, on: :create, if: :indexable?").value.statements.body.first
rewritten_source(node, '{{arguments.parts.-1.on_assoc}}')) # 'on: :create'

# {key}_value for hash node
node = Prism.parse("after_commit :do_index, on: :create, if: :indexable?").value.statements.body.first
rewritten_source(node, '{{arguments.parts.-1.on_value}}')) # ':create'

# to_single_quote for StringNode
node = Prism.parse('"foo"').value.statements.body.first
rewritten_source(node, 'to_single_quote') # "'foo'"

# to_double_quote for StringNode
node = Prism.parse("'foo'").value.statements.body.first
rewritten_source(node, 'to_double_quote') # '"foo"'

# to_symbol for StringNode
node = Prism.parse("'foo'").value.statements.body.first
rewritten_source(node, 'to_symbol') # ':foo'

# to_string for SymbolNode
node = Prism.parse(":foo").value.statements.body.first
rewritten_source(node, 'to_string') # 'foo'

# to_lambda_literal for CallNode with lambda
node = Prism.parse('lambda { foobar }').value.statements.body.first
rewritten_source(node, 'to_lambda_literal') # '-> { foobar }'

# strip_curly_braces for HashNode
node = Prism.parse("{ foo: 'bar' }").value.statements.body.first
rewritten_source(node, 'strip_curly_braces') # "foo: 'bar'"

# wrap_curly_braces for KeywordHashNode
node = Prism.parse("test(foo: 'bar')").value.statements.body.first
rewritten_source(node.arguments.arguments.parts.first, 'wrap_curly_braces') # "{ foo: 'bar' }"

Parameters:

  • node (Prism::Node)

    The node to evaluate.

  • code (String)

    The code to evaluate.

Returns:

  • (String)

    The new source code.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/node_mutation/adapter/prism.rb', line 62

def rewritten_source(node, code)
  code.gsub(/{{(.+?)}}/m) do
    old_code = Regexp.last_match(1)
    evaluated = child_node_by_name(node, old_code)
    case evaluated
    when Prism::Node
      get_source(evaluated)
    when Array
      if evaluated.size > 0
        source = get_source(evaluated)
        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[get_start_loc(evaluated.first).column - NodeMutation.tab_width..-1])
          }
          new_code.join("\n")
        else
          source
        end
      end
    when String, Symbol, Integer, Float
      evaluated
    when NilClass
      ''
    else
      raise "can not parse \"#{code}\""
    end
  end
end