Class: TreeHaver::Base::Node Abstract

Inherits:
Object
  • Object
show all
Includes:
Comparable, Enumerable
Defined in:
lib/tree_haver/base/node.rb

Overview

This class is abstract.

Subclasses must implement #type, #start_byte, #end_byte, and #children

Base class for all backend Node implementations

This class defines the API contract for Node objects across all backends. It provides shared implementation for common behaviors and documents required/optional methods that subclasses must implement.

Backend Architecture

TreeHaver supports two categories of backends:

Tree-sitter Backends (MRI, Rust, FFI, Java)

These backends use the native tree-sitter library (via different bindings). They return raw ::TreeSitter::Node objects which are wrapped by TreeHaver::Node (which inherits from this class).

  • Backend Tree#root_node returns: ::TreeSitter::Node (raw)

  • TreeHaver::Tree#root_node wraps it in: TreeHaver::Node

  • These backends do NOT define their own Tree/Node classes

Pure-Ruby/Plugin Backends (Citrus, Prism, Psych, Commonmarker, Markly)

These backends define their own complete implementations:

  • Backend::X::Node - wraps parser-specific node objects

  • Backend::X::Tree - wraps parser-specific tree objects

For consistency, these should also inherit from Base::Node and Base::Tree.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(node, source: nil, lines: nil) ⇒ Node

Create a new Node wrapper

Parameters:

  • node (Object)

    The backend-specific node object

  • source (String, nil) (defaults to: nil)

    The source code

  • lines (Array<String>, nil) (defaults to: nil)

    Pre-split lines (optional optimization)



57
58
59
60
61
# File 'lib/tree_haver/base/node.rb', line 57

def initialize(node, source: nil, lines: nil)
  @inner_node = node
  @source = source
  @lines = lines || source&.lines || []
end

Instance Attribute Details

#inner_nodeObject (readonly)

The underlying backend-specific node object

Returns:

  • (Object)

    Backend node



42
43
44
# File 'lib/tree_haver/base/node.rb', line 42

def inner_node
  @inner_node
end

#linesArray<String> (readonly)

Source lines for byte offset calculations

Returns:

  • (Array<String>)

    Lines of source



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

def lines
  @lines
end

#sourceString (readonly)

The source text

Returns:

  • (String)

    Source code



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

def source
  @source
end

Instance Method Details

#<=>(other) ⇒ Integer?

Comparison based on byte range

Parameters:

  • other (Object)

Returns:

  • (Integer, nil)


204
205
206
207
208
209
210
211
# File 'lib/tree_haver/base/node.rb', line 204

def <=>(other)
  return unless other.respond_to?(:start_byte) && other.respond_to?(:end_byte)

  cmp = start_byte <=> other.start_byte
  return cmp unless cmp == 0

  end_byte <=> other.end_byte
end

#==(other) ⇒ Boolean

Equality based on type and byte range

Parameters:

  • other (Object)

Returns:

  • (Boolean)


293
294
295
296
297
# File 'lib/tree_haver/base/node.rb', line 293

def ==(other)
  return false unless other.respond_to?(:type) && other.respond_to?(:start_byte) && other.respond_to?(:end_byte)

  type == other.type && start_byte == other.start_byte && end_byte == other.end_byte
end

#child(index) ⇒ Node?

Get a child node by index

Returns nil for negative indices or indices out of bounds. This matches tree-sitter behavior where negative indices are invalid.

Parameters:

  • index (Integer)

    Child index (0-based, non-negative)

Returns:

  • (Node, nil)

    The child node or nil



104
105
106
107
108
109
# File 'lib/tree_haver/base/node.rb', line 104

def child(index)
  return if index.negative?
  return if index >= child_count

  children[index]
end

#child_by_field_name(_name) ⇒ Node?

Get a child by field name

Parameters:

  • _name (String, Symbol)

    Field name

Returns:

  • (Node, nil)

    Child node or nil



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

def child_by_field_name(_name)
  nil
end

#child_countInteger

Get the number of child nodes

Returns:

  • (Integer)

    Number of children



93
94
95
# File 'lib/tree_haver/base/node.rb', line 93

def child_count
  children.size
end

#childrenArray<Node>

Get all children as an array

Returns:

Raises:

  • (NotImplementedError)


85
86
87
# File 'lib/tree_haver/base/node.rb', line 85

def children
  raise NotImplementedError, "#{self.class}#children must be implemented"
end

#each {|Node| ... } ⇒ Object

Iterate over children

Yields:

  • (Node)

    Child node



113
114
115
116
117
# File 'lib/tree_haver/base/node.rb', line 113

def each(&block)
  return to_enum(__method__) unless block

  children.each(&block)
end

#end_byteInteger

Get byte offset where the node ends

Returns:

  • (Integer)

    End byte offset

Raises:

  • (NotImplementedError)


79
80
81
# File 'lib/tree_haver/base/node.rb', line 79

def end_byte
  raise NotImplementedError, "#{self.class}#end_byte must be implemented"
end

#end_lineInteger

Get 1-based end line

Returns:

  • (Integer)


227
228
229
230
231
232
233
234
235
# File 'lib/tree_haver/base/node.rb', line 227

def end_line
  ep = end_point
  row = if ep.is_a?(Hash)
    ep[:row]
  else
    (ep.respond_to?(:row) ? ep.row : 0)
  end
  row + 1
end

#end_pointHash{Symbol => Integer}

Get end position (row/col) - 0-based

Returns:

  • (Hash{Symbol => Integer})

    0, column: 0



195
196
197
# File 'lib/tree_haver/base/node.rb', line 195

def end_point
  {row: 0, column: 0}
end

#first_childNode?

Retrieve the first child

Returns:



121
122
123
# File 'lib/tree_haver/base/node.rb', line 121

def first_child
  children.first
end

#has_error?Boolean

Check if this node represents a syntax error

Returns:

  • (Boolean)

    true on error



162
163
164
# File 'lib/tree_haver/base/node.rb', line 162

def has_error?
  false
end

#inspectString

Human-readable representation

Returns:

  • (String)


274
275
276
277
278
279
280
281
282
# File 'lib/tree_haver/base/node.rb', line 274

def inspect
  class_name = self.class.name || "#{self.class.superclass&.name}(anonymous)"
  node_type = begin
    type
  rescue NotImplementedError
    "(not implemented)"
  end
  "#<#{class_name} type=#{node_type}>"
end

#last_childNode?

Retrieve the last child

Returns:



127
128
129
# File 'lib/tree_haver/base/node.rb', line 127

def last_child
  children.last
end

#missing?Boolean

Check if this node was inserted for error recovery

Returns:

  • (Boolean)

    true if missing



168
169
170
# File 'lib/tree_haver/base/node.rb', line 168

def missing?
  false
end

#named?Boolean Also known as: structural?

Check if this node is named (structural)

Returns:

  • (Boolean)

    true if named



153
154
155
# File 'lib/tree_haver/base/node.rb', line 153

def named?
  true
end

#next_siblingNode?

Get the next sibling node

Returns:

  • (Node, nil)

    Next sibling or nil



141
142
143
# File 'lib/tree_haver/base/node.rb', line 141

def next_sibling
  nil
end

#parentNode?

Get the parent node

Returns:

  • (Node, nil)

    Parent node or nil



135
136
137
# File 'lib/tree_haver/base/node.rb', line 135

def parent
  nil
end

#prev_siblingNode?

Get the previous sibling node

Returns:

  • (Node, nil)

    Previous sibling or nil



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

def prev_sibling
  nil
end

#source_positionHash{Symbol => Integer}

Get unified source position hash

Returns:

  • (Hash{Symbol => Integer})


239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/tree_haver/base/node.rb', line 239

def source_position
  sp = start_point
  ep = end_point

  sp_row = if sp.is_a?(Hash)
    sp[:row]
  else
    (sp.respond_to?(:row) ? sp.row : 0)
  end
  sp_col = if sp.is_a?(Hash)
    sp[:column]
  else
    (sp.respond_to?(:column) ? sp.column : 0)
  end
  ep_row = if ep.is_a?(Hash)
    ep[:row]
  else
    (ep.respond_to?(:row) ? ep.row : 0)
  end
  ep_col = if ep.is_a?(Hash)
    ep[:column]
  else
    (ep.respond_to?(:column) ? ep.column : 0)
  end

  {
    start_line: sp_row + 1,
    end_line: ep_row + 1,
    start_column: sp_col,
    end_column: ep_col,
  }
end

#start_byteInteger

Get byte offset where the node starts

Returns:

  • (Integer)

    Start byte offset

Raises:

  • (NotImplementedError)


73
74
75
# File 'lib/tree_haver/base/node.rb', line 73

def start_byte
  raise NotImplementedError, "#{self.class}#start_byte must be implemented"
end

#start_lineInteger

Get 1-based start line

Returns:

  • (Integer)


215
216
217
218
219
220
221
222
223
# File 'lib/tree_haver/base/node.rb', line 215

def start_line
  sp = start_point
  row = if sp.is_a?(Hash)
    sp[:row]
  else
    (sp.respond_to?(:row) ? sp.row : 0)
  end
  row + 1
end

#start_pointHash{Symbol => Integer}

Get start position (row/col) - 0-based

Returns:

  • (Hash{Symbol => Integer})

    0, column: 0



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

def start_point
  {row: 0, column: 0}
end

#textString

Get the text content of this node

Returns:

  • (String)

    Node text



174
175
176
177
178
# File 'lib/tree_haver/base/node.rb', line 174

def text
  return "" unless source

  source[start_byte...end_byte] || ""
end

#to_sString

String conversion returns the text content

Returns:

  • (String)


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

def to_s
  text
end

#typeString

Get the node type as a string

Returns:

  • (String)

    Node type

Raises:

  • (NotImplementedError)


67
68
69
# File 'lib/tree_haver/base/node.rb', line 67

def type
  raise NotImplementedError, "#{self.class}#type must be implemented"
end