Class: NodeMutation::ParserAdapter

Inherits:
Adapter
  • Object
show all
Defined in:
lib/node_mutation/adapter/parser.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 = Parser::CurrentRuby.parse('Factory.define :user do; end')
child_node_range(node, 'caller.receiver') # { start: 0, end: 'Factory'.length }

# node array
node = Parser::CurrentRuby.parse('foobar arg1, arg2)')
child_node_range(node, 'arguments') # { start: 'foobar '.length, end: 'foobar arg1, arg2'.length }

# index for node array
node = Parser::CurrentRuby.parse('foobar(arg1, arg2)')
child_node_range(node, 'arguments.-1') # { start: 'foobar(arg1, '.length, end: 'foobar(arg1, arg2'.length }

# pips for block node
node = Parser::CurrentRuby.parse('Factory.define :user do |user|; end')
child_node_range(node, 'pipes') # { start: 'Factory.deine :user do '.length, end: 'Factory.define :user do |user|'.length }

# parentheses for def and defs node
node = Parser::CurrentRuby.parse('def foo(bar); end')
child_node_range(node, 'parentheses') # { start: 'def foo'.length, end: 'def foo(bar)'.length }

# double_colon for const node
node = Parser::CurrentRuby.parse('Foo::Bar')
child_node_range(node, 'double_colon') # { start: 'Foo'.length, end: 'Foo::'.length }

# self and dot for defs node
node = Parser::CurrentRuby.parse('def self.foo(bar); end')
child_node_range(node, 'self') # { start: 'def '.length, end: 'def self'.length }
child_node_range(node, 'dot') # { start: 'def self'.length, end: 'def self.'.length }

# dot for send and csend node
node = Parser::CurrentRuby.parse('foo.bar(test)')
child_node_range(node, 'self') # { start: 'foo'.length, end: 'foo.'.length }

# parentheses for send and csend node
node = Parser::CurrentRuby.parse('foo.bar(test)')
child_node_range(node, 'parentheses') # { start: 'foo.bar'.length, end: 'foo.bar(test)'.length }

Parameters:

  • node (Parser::AST::Node)

    The node.

  • child_name (String)

    THe name to find child node.

Returns:



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/node_mutation/adapter/parser.rb', line 146

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.loc.expression.begin_pos, child_node.loc.expression.end_pos)
    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.loc.expression.begin_pos, child_node.loc.expression.end_pos)
  end

  case [node.type, child_name.to_sym]
  when %i[block pipes], %i[def parentheses], %i[defs parentheses]
    if node.arguments.empty?
      nil
    else
      NodeMutation::Struct::Range.new(
        node.arguments.first.loc.expression.begin_pos - 1,
        node.arguments.last.loc.expression.end_pos + 1
      )
    end
  when %i[arg name], %i[class name], %i[const name], %i[cvar name], %i[casgn name], %i[def name], %i[defs name],
       %i[gvar name], %i[ivar name], %i[lvar name]

    NodeMutation::Struct::Range.new(node.loc.name.begin_pos, node.loc.name.end_pos)
  when %i[const double_colon]
    NodeMutation::Struct::Range.new(node.loc.double_colon.begin_pos, node.loc.double_colon.end_pos)
  when %i[defs dot]
    NodeMutation::Struct::Range.new(node.loc.operator.begin_pos, node.loc.operator.end_pos) if node.loc.operator
  when %i[defs self]
    NodeMutation::Struct::Range.new(node.loc.operator.begin_pos - 'self'.length, node.loc.operator.begin_pos)
  when %i[float value], %i[int value], %i[sym value]
    NodeMutation::Struct::Range.new(node.loc.expression.begin_pos, node.loc.expression.end_pos)
  when %i[lvasgn variable], %i[ivasgn variable], %i[cvasgn variable], %i[gvasgn variable]
    NodeMutation::Struct::Range.new(node.loc.name.begin_pos, node.loc.name.end_pos)
  when %i[send dot], %i[csend dot]
    NodeMutation::Struct::Range.new(node.loc.dot.begin_pos, node.loc.dot.end_pos) if node.loc.dot
  when %i[send message], %i[csend message]
    if node.loc.operator
      NodeMutation::Struct::Range.new(node.loc.selector.begin_pos, node.loc.operator.end_pos)
    else
      NodeMutation::Struct::Range.new(node.loc.selector.begin_pos, node.loc.selector.end_pos)
    end
  when %i[send parentheses], %i[csend parentheses]
    if node.loc.begin && node.loc.end
      NodeMutation::Struct::Range.new(node.loc.begin.begin_pos, node.loc.end.end_pos)
    end
  when %i[str value]
    NodeMutation::Struct::Range.new(node.loc.expression.begin_pos + 1, node.loc.expression.end_pos - 1)
  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?

    if child_node.is_a?(Parser::AST::Node)
      return(
        NodeMutation::Struct::Range.new(child_node.loc.expression.begin_pos, child_node.loc.expression.end_pos)
      )
    end

    # arguments
    return nil if child_node.empty?

    return(
      NodeMutation::Struct::Range.new(
        child_node.first.loc.expression.begin_pos,
        child_node.last.loc.expression.end_pos
      )
    )
  end
end

#file_source(node) ⇒ Object



102
103
104
# File 'lib/node_mutation/adapter/parser.rb', line 102

def file_source(node)
  node.loc.expression.source_buffer.source
end

#get_end(node, child_name = nil) ⇒ Object



239
240
241
242
# File 'lib/node_mutation/adapter/parser.rb', line 239

def get_end(node, child_name = nil)
  node = child_node_by_name(node, child_name) if child_name
  node.loc.expression.end_pos
end

#get_end_loc(node, child_name = nil) ⇒ Object



250
251
252
253
254
# File 'lib/node_mutation/adapter/parser.rb', line 250

def get_end_loc(node, child_name = nil)
  node = child_node_by_name(node, child_name) if child_name
  end_loc = node.loc.expression.end
  NodeMutation::Struct::Location.new(end_loc.line, end_loc.column)
end

#get_source(node) ⇒ Object



7
8
9
10
11
12
13
14
15
16
# File 'lib/node_mutation/adapter/parser.rb', line 7

def get_source(node)
  if node.is_a?(Array)
    return "" if node.empty?

    source = file_source(node.first)
    source[node.first.loc.expression.begin_pos...node.last.loc.expression.end_pos]
  else
    node.loc.expression.source
  end
end

#get_start(node, child_name = nil) ⇒ Object



234
235
236
237
# File 'lib/node_mutation/adapter/parser.rb', line 234

def get_start(node, child_name = nil)
  node = child_node_by_name(node, child_name) if child_name
  node.loc.expression.begin_pos
end

#get_start_loc(node, child_name = nil) ⇒ Object



244
245
246
247
248
# File 'lib/node_mutation/adapter/parser.rb', line 244

def get_start_loc(node, child_name = nil)
  node = child_node_by_name(node, child_name) if child_name
  begin_loc = node.loc.expression.begin
  NodeMutation::Struct::Location.new(begin_loc.line, begin_loc.column)
end

#rewritten_source(node, code) ⇒ String

Gets the new source code after evaluating the node.

Examples:

node = Parser::CurrentRuby.parse('Factory.define :user do; end')
rewritten_source(node, '{{call.receiver}}') # 'Factory'

# index for node array
node = Parser::CurrentRuby.parse("test(foo, bar)")
rewritten_source(node, '{{arguments.0}}')) # 'foo'

# {key}_pair for hash node
node = Parser::CurrentRuby.parse("after_commit :do_index, on: :create, if: :indexable?")
rewritten_source(node, '{{arguments.-1.on_pair}}')) # 'on: :create'

# {key}_value for hash node
node = Parser::CurrentRuby.parse("after_commit :do_index, on: :create, if: :indexable?")
rewritten_source(node, '{{arguments.-1.on_value}}')) # ':create'

# to_single_quote for str node
node = Parser::CurrentRuby.parse('"foo"')
rewritten_source(node, 'to_single_quote') # "'foo'"

# to_double_quote for str node
node = Parser::CurrentRuby.parse("'foo'")
rewritten_source(node, 'to_double_quote') # '"foo"'

# to_symbol for str node
node = Parser::CurrentRuby.parse("'foo'")
rewritten_source(node, 'to_symbol') # ':foo'

# to_string for sym node
node = Parser::CurrentRuby.parse(":foo")
rewritten_source(node, 'to_string') # 'foo'

# to_lambda_literal for block node
node = Parser::CurrentRuby.parse('lambda { foobar }')
rewritten_source(node, 'to_lambda_literal') # '-> { foobar }'

# strip_curly_braces for hash node
node = Parser::CurrentRuby.parse("{ foo: 'bar' }")
rewritten_source(node, 'strip_curly_braces') # "foo: 'bar'"

# wrap_curly_braces for hash node
node = Parser::CurrentRuby.parse("test(foo: 'bar')")
rewritten_source(node.arguments.first, 'wrap_curly_braces') # "{ foo: 'bar' }"

Parameters:

  • node (Parser::AST::Node)

    The node to evaluate.

  • code (String)

    The code to evaluate.

Returns:

  • (String)

    The new source code.



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
93
94
95
96
97
98
99
100
# File 'lib/node_mutation/adapter/parser.rb', line 65

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 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 = file_source(evaluated.first)
        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[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