Class: NodeMutation::SyntaxTreeAdapter

Inherits:
Adapter
  • Object
show all
Defined in:
lib/node_mutation/adapter/syntax_tree.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 = SyntaxTree.parse('foo.bar(test)').statements.body.first
child_node_range(node, 'receiver') # { start: 0, end: 'foo'.length }

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

# index for node array
node = SyntaxTree.parse('foo.bar(a, b)').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 = SyntaxTree.parse('foo | bar').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:

Raises:



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
# File 'lib/node_mutation/adapter/syntax_tree.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_char, child_node.location.end_char)
    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_char, child_node.location.end_char)
  end

  if node.is_a?(SyntaxTree::Binary) && child_name.to_sym == :operator
    start_char = node.left.location.end_char
    start_char += 1 while node.source[start_char] == ' '
    end_char = node.right.location.start_char
    end_char -= 1 while node.source[end_char - 1] == ' '
    return NodeMutation::Struct::Range.new(start_char, end_char)
  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 nil if child_node.nil?
  return nil if child_node == []

  if child_node.is_a?(SyntaxTree::Node)
    return(
      NodeMutation::Struct::Range.new(child_node.location.start_char, child_node.location.end_char)
    )
  end

  return(
    NodeMutation::Struct::Range.new(child_node.first.location.start_char, child_node.last.location.end_char)
  )
end

#file_source(node) ⇒ Object



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

def file_source(node)
  node.source
end

#get_end(node, child_name = nil) ⇒ Object



173
174
175
176
# File 'lib/node_mutation/adapter/syntax_tree.rb', line 173

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

#get_end_loc(node, child_name = nil) ⇒ Object



183
184
185
186
# File 'lib/node_mutation/adapter/syntax_tree.rb', line 183

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_column)
end

#get_source(node) ⇒ Object



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

def get_source(node)
  if node.is_a?(Array)
    return node.first.source[node.first.location.start_char...node.last.location.end_char]
  end

  node.source[node.location.start_char...node.location.end_char]
end

#get_start(node, child_name = nil) ⇒ Object



168
169
170
171
# File 'lib/node_mutation/adapter/syntax_tree.rb', line 168

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

#get_start_loc(node, child_name = nil) ⇒ Object



178
179
180
181
# File 'lib/node_mutation/adapter/syntax_tree.rb', line 178

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_column)
end

#rewritten_source(node, code) ⇒ String

It gets the new source code after evaluating the node.

Examples:

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

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

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

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

# to_single_quote for StringLiteral node
node = SyntaxTree.parse('"foo"').statements.body.first
rewritten_source(node, 'to_single_quote') # "'foo'"

# to_double_quote for StringLiteral node
node = SyntaxTree.parse("'foo'").statements.body.first
rewritten_source(node, 'to_double_quote') # '"foo"'

# to_symbol for StringLiteral node
node = SyntaxTree.parse("'foo'").statements.body.first
rewritten_source(node, 'to_symbol') # ':foo'

# to_string for SymbolLiteral node
node = SyntaxTree.parse(":foo").statements.body.first
rewritten_source(node, 'to_string') # 'foo'

# to_lambda_literal for MethodAddBlock node
node = SyntaxTree.parse('lambda { foobar }').statements.body.first
rewritten_source(node, 'to_lambda_literal') # '-> { foobar }'

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

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

Parameters:

  • node (SyntaxTree::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/syntax_tree.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 SyntaxTree::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