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.



21
22
23
# File 'lib/prism/node.rb', line 21

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)


153
154
155
156
157
158
159
# File 'lib/prism/node.rb', line 153

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)


218
219
220
# File 'lib/prism/node.rb', line 218

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)


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

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 }


139
140
141
142
143
144
145
146
147
148
# File 'lib/prism/node.rb', line 139

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

#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)


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

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)


189
190
191
# File 'lib/prism/node.rb', line 189

def comment_targets
  raise NoMethodError, "undefined method `comment_targets' for #{inspect}"
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)


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

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

#deprecated(*replacements) ⇒ Object

:nodoc:



7
8
9
10
11
12
13
14
15
16
17
# File 'lib/prism/node_ext.rb', line 7

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

  warn(<<~MSG, 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_offsetObject

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



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

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)


194
195
196
# File 'lib/prism/node.rb', line 194

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

#locationObject

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



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

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)


72
73
74
# File 'lib/prism/node.rb', line 72

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

#newline_flag!(lines) ⇒ Object

:nodoc:



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

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)


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

def newline_flag? # :nodoc:
  @newline_flag ? true : false
end

#pretty_print(q) ⇒ Object

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



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

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

#sliceObject

Slice the location of the node from the source.



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

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.



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

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.



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

def source_lines
  location.source_lines
end

#start_offsetObject

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



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

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)


77
78
79
# File 'lib/prism/node.rb', line 77

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

#to_dotObject

Convert this node into a graphviz dot graph string.



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

def to_dot
  # @type self: node
  DotVisitor.new.tap { |visitor| accept(visitor) }.to_dot
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.



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/prism/node.rb', line 102

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

  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)


210
211
212
# File 'lib/prism/node.rb', line 210

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