Class: Prism::Node

Inherits:
Object
  • Object
show all
Defined in:
lib/prism/node.rb,
lib/prism/node_ext.rb,
lib/prism/parse_result/newlines.rb,
ext/prism/extension.c

Overview

This represents a node in the tree. It is the parent class of all of the various node types.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#node_idObject (readonly)

A unique identifier for this node. This is used in a very specific use case where you want to keep around a reference to a node without having to keep around the syntax tree in memory. This unique identifier will be consistent across multiple parses of the same source code.



24
25
26
# File 'lib/prism/node.rb', line 24

def node_id
  @node_id
end

Class Method Details

.fieldsObject

Returns a list of the fields that exist for this node class. Fields describe the structure of the node. This kind of reflection is useful for things like recursively visiting each node and field in the tree.

Raises:

  • (NoMethodError)


245
246
247
248
249
250
251
# File 'lib/prism/node.rb', line 245

def self.fields
  # This method should only be called on subclasses of Node, not Node
  # itself.
  raise NoMethodError, "undefined method `fields' for #{inspect}" if self == Node

  Reflection.fields_for(self)
end

.typeObject

Similar to #type, this method returns a symbol that you can use for splitting on the type of the node without having to do a long === chain. Note that like #type, it will still be slower than using == for a single class, but should be faster in a case statement or an array comparison.

Raises:

  • (NoMethodError)


310
311
312
# File 'lib/prism/node.rb', line 310

def self.type
  raise NoMethodError, "undefined method `type' for #{inspect}"
end

Instance Method Details

#accept(visitor) ⇒ Object

Accepts a visitor and calls back into the specialized visit function.

Raises:

  • (NoMethodError)


261
262
263
# File 'lib/prism/node.rb', line 261

def accept(visitor)
  raise NoMethodError, "undefined method `accept' for #{inspect}"
end

#breadth_first_search(&block) ⇒ Object

Returns the first node that matches the given block when visited in a depth-first search. This is useful for finding a node that matches a particular condition.

node.breadth_first_search { |node| node.node_id == node_id }


231
232
233
234
235
236
237
238
239
240
# File 'lib/prism/node.rb', line 231

def breadth_first_search(&block)
  queue = [self] #: Array[Prism::node]

  while (node = queue.shift)
    return node if yield node
    queue.concat(node.compact_child_nodes)
  end

  nil
end

#cached_end_code_units_column(cache) ⇒ Object

Delegates to the cached_end_code_units_column of the associated location object.



118
119
120
# File 'lib/prism/node.rb', line 118

def cached_end_code_units_column(cache)
  location.cached_end_code_units_column(cache)
end

#cached_end_code_units_offset(cache) ⇒ Object

Delegates to the cached_end_code_units_offset of the associated location object.



86
87
88
# File 'lib/prism/node.rb', line 86

def cached_end_code_units_offset(cache)
  location.cached_end_code_units_offset(cache)
end

#cached_start_code_units_column(cache) ⇒ Object

Delegates to the cached_start_code_units_column of the associated location object.



112
113
114
# File 'lib/prism/node.rb', line 112

def cached_start_code_units_column(cache)
  location.cached_start_code_units_column(cache)
end

#cached_start_code_units_offset(cache) ⇒ Object

Delegates to the cached_start_code_units_offset of the associated location object.



80
81
82
# File 'lib/prism/node.rb', line 80

def cached_start_code_units_offset(cache)
  location.cached_start_code_units_offset(cache)
end

#child_nodesObject Also known as: deconstruct

Returns an array of child nodes, including ‘nil`s in the place of optional nodes that were not present.

Raises:

  • (NoMethodError)


267
268
269
# File 'lib/prism/node.rb', line 267

def child_nodes
  raise NoMethodError, "undefined method `child_nodes' for #{inspect}"
end

#comment_targetsObject

Returns an array of child nodes and locations that could potentially have comments attached to them.

Raises:

  • (NoMethodError)


281
282
283
# File 'lib/prism/node.rb', line 281

def comment_targets
  raise NoMethodError, "undefined method `comment_targets' for #{inspect}"
end

#commentsObject

Delegates to the comments of the associated location object.



133
134
135
# File 'lib/prism/node.rb', line 133

def comments
  location.comments
end

#compact_child_nodesObject

Returns an array of child nodes, excluding any ‘nil`s in the place of optional nodes that were not present.

Raises:

  • (NoMethodError)


275
276
277
# File 'lib/prism/node.rb', line 275

def compact_child_nodes
  raise NoMethodError, "undefined method `compact_child_nodes' for #{inspect}"
end

#deprecated(*replacements) ⇒ Object

:nodoc:



10
11
12
13
14
15
16
17
18
19
20
# File 'lib/prism/node_ext.rb', line 10

def deprecated(*replacements) # :nodoc:
  location = caller_locations(1, 1)
  location = location[0].label if location
  suggest = replacements.map { |replacement| "#{self.class}##{replacement}" }

  warn(<<~MSG, uplevel: 1, category: :deprecated)
    [deprecation]: #{self.class}##{location} is deprecated and will be \
    removed in the next major version. Use #{suggest.join("/")} instead.
    #{(caller(1, 3) || []).join("\n")}
  MSG
end

#end_character_columnObject

Delegates to the end_character_column of the associated location object.



106
107
108
# File 'lib/prism/node.rb', line 106

def end_character_column
  location.end_character_column
end

#end_character_offsetObject

Delegates to the end_character_offset of the associated location object.



74
75
76
# File 'lib/prism/node.rb', line 74

def end_character_offset
  location.end_character_offset
end

#end_columnObject

Delegates to the end_column of the associated location object.



96
97
98
# File 'lib/prism/node.rb', line 96

def end_column
  location.end_column
end

#end_lineObject

Delegates to the end_line of the associated location object.



50
51
52
# File 'lib/prism/node.rb', line 50

def end_line
  location.end_line
end

#end_offsetObject

The end offset of the node in the source. This method is effectively a delegate method to the location object.



63
64
65
66
# File 'lib/prism/node.rb', line 63

def end_offset
  location = @location
  location.is_a?(Location) ? location.end_offset : ((location >> 32) + (location & 0xFFFFFFFF))
end

#inspectObject

Returns a string representation of the node.

Raises:

  • (NoMethodError)


286
287
288
# File 'lib/prism/node.rb', line 286

def inspect
  raise NoMethodError, "undefined method `inspect' for #{inspect}"
end

#leading_commentsObject

Delegates to the leading_comments of the associated location object.



123
124
125
# File 'lib/prism/node.rb', line 123

def leading_comments
  location.leading_comments
end

#locationObject

A Location instance that represents the location of this node in the source.



33
34
35
36
37
# File 'lib/prism/node.rb', line 33

def location
  location = @location
  return location if location.is_a?(Location)
  @location = Location.new(source, location >> 32, location & 0xFFFFFFFF)
end

#newline?Boolean

Returns true if the node has the newline flag set.

Returns:

  • (Boolean)


164
165
166
# File 'lib/prism/node.rb', line 164

def newline?
  flags.anybits?(NodeFlags::NEWLINE)
end

#newline_flag!(lines) ⇒ Object

:nodoc:



70
71
72
73
74
75
76
# File 'lib/prism/parse_result/newlines.rb', line 70

def newline_flag!(lines) # :nodoc:
  line = location.start_line
  unless lines[line]
    lines[line] = true
    @newline_flag = true
  end
end

#newline_flag?Boolean

:nodoc:

Returns:

  • (Boolean)


66
67
68
# File 'lib/prism/parse_result/newlines.rb', line 66

def newline_flag? # :nodoc:
  !!defined?(@newline_flag)
end

#pretty_print(q) ⇒ Object

Similar to inspect, but respects the current level of indentation given by the pretty print object.



175
176
177
178
179
180
# File 'lib/prism/node.rb', line 175

def pretty_print(q)
  q.seplist(inspect.chomp.each_line, -> { q.breakable }) do |line|
    q.text(line.chomp)
  end
  q.current_group.break
end

#save(repository) ⇒ Object

Save this node using a saved source so that it can be retrieved later.



27
28
29
# File 'lib/prism/node.rb', line 27

def save(repository)
  repository.enter(node_id, :itself)
end

#save_location(repository) ⇒ Object

Save the location using a saved source so that it can be retrieved later.



40
41
42
# File 'lib/prism/node.rb', line 40

def save_location(repository)
  repository.enter(node_id, :location)
end

#sliceObject

Slice the location of the node from the source.



147
148
149
# File 'lib/prism/node.rb', line 147

def slice
  location.slice
end

#slice_linesObject

Slice the location of the node from the source, starting at the beginning of the line that the location starts on, ending at the end of the line that the location ends on.



154
155
156
# File 'lib/prism/node.rb', line 154

def slice_lines
  location.slice_lines
end

#source_linesObject Also known as: script_lines

Returns all of the lines of the source code associated with this node.



138
139
140
# File 'lib/prism/node.rb', line 138

def source_lines
  location.source_lines
end

#start_character_columnObject

Delegates to the start_character_column of the associated location object.



101
102
103
# File 'lib/prism/node.rb', line 101

def start_character_column
  location.start_character_column
end

#start_character_offsetObject

Delegates to the start_character_offset of the associated location object.



69
70
71
# File 'lib/prism/node.rb', line 69

def start_character_offset
  location.start_character_offset
end

#start_columnObject

Delegates to the start_column of the associated location object.



91
92
93
# File 'lib/prism/node.rb', line 91

def start_column
  location.start_column
end

#start_lineObject

Delegates to the start_line of the associated location object.



45
46
47
# File 'lib/prism/node.rb', line 45

def start_line
  location.start_line
end

#start_offsetObject

The start offset of the node in the source. This method is effectively a delegate method to the location object.



56
57
58
59
# File 'lib/prism/node.rb', line 56

def start_offset
  location = @location
  location.is_a?(Location) ? location.start_offset : location >> 32
end

#static_literal?Boolean

Returns true if the node has the static literal flag set.

Returns:

  • (Boolean)


169
170
171
# File 'lib/prism/node.rb', line 169

def static_literal?
  flags.anybits?(NodeFlags::STATIC_LITERAL)
end

#to_dotObject

Convert this node into a graphviz dot graph string.



183
184
185
186
# File 'lib/prism/node.rb', line 183

def to_dot
  # @type self: node
  DotVisitor.new.tap { |visitor| accept(visitor) }.to_dot
end

#trailing_commentsObject

Delegates to the trailing_comments of the associated location object.



128
129
130
# File 'lib/prism/node.rb', line 128

def trailing_comments
  location.trailing_comments
end

#tunnel(line, column) ⇒ Object

Returns a list of nodes that are descendants of this node that contain the given line and column. This is useful for locating a node that is selected based on the line and column of the source code.

Important to note is that the column given to this method should be in bytes, as opposed to characters or code units.



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
# File 'lib/prism/node.rb', line 194

def tunnel(line, column)
  queue = [self] #: Array[Prism::node]
  result = [] #: Array[Prism::node]

  while (node = queue.shift)
    result << node

    node.compact_child_nodes.each do |child_node|
      child_location = child_node.location

      start_line = child_location.start_line
      end_line = child_location.end_line

      if start_line == end_line
        if line == start_line && column >= child_location.start_column && column < child_location.end_column
          queue << child_node
          break
        end
      elsif (line == start_line && column >= child_location.start_column) || (line == end_line && column < child_location.end_column)
        queue << child_node
        break
      elsif line > start_line && line < end_line
        queue << child_node
        break
      end
    end
  end

  result
end

#typeObject

Sometimes you want to check an instance of a node against a list of classes to see what kind of behavior to perform. Usually this is done by calling ‘[cls1, cls2].include?(node.class)` or putting the node into a case statement and doing `case node; when cls1; when cls2; end`. Both of these approaches are relatively slow because of the constant lookups, method calls, and/or array allocations.

Instead, you can call #type, which will return to you a symbol that you can use for comparison. This is faster than the other approaches because it uses a single integer comparison, but also because if you’re on CRuby you can take advantage of the fact that case statements with all symbol keys will use a jump table.

Raises:

  • (NoMethodError)


302
303
304
# File 'lib/prism/node.rb', line 302

def type
  raise NoMethodError, "undefined method `type' for #{inspect}"
end