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)


197
198
199
200
201
202
203
204
# File 'lib/tree_haver/base/node.rb', line 197

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)


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

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

Parameters:

  • index (Integer)

    Child index

Returns:

  • (Node, nil)

    The child node or nil



100
101
102
# File 'lib/tree_haver/base/node.rb', line 100

def child(index)
  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



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

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



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

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)


220
221
222
223
224
225
226
227
228
# File 'lib/tree_haver/base/node.rb', line 220

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



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

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

#first_childNode?

Retrieve the first child

Returns:



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

def first_child
  children.first
end

#has_error?Boolean

Check if this node represents a syntax error

Returns:

  • (Boolean)

    true on error



155
156
157
# File 'lib/tree_haver/base/node.rb', line 155

def has_error?
  false
end

#inspectString

Human-readable representation

Returns:

  • (String)


267
268
269
270
271
272
273
274
275
# File 'lib/tree_haver/base/node.rb', line 267

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:



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

def last_child
  children.last
end

#missing?Boolean

Check if this node was inserted for error recovery

Returns:

  • (Boolean)

    true if missing



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

def missing?
  false
end

#named?Boolean Also known as: structural?

Check if this node is named (structural)

Returns:

  • (Boolean)

    true if named



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

def named?
  true
end

#next_siblingNode?

Get the next sibling node

Returns:

  • (Node, nil)

    Next sibling or nil



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

def next_sibling
  nil
end

#parentNode?

Get the parent node

Returns:

  • (Node, nil)

    Parent node or nil



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

def parent
  nil
end

#prev_siblingNode?

Get the previous sibling node

Returns:

  • (Node, nil)

    Previous sibling or nil



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

def prev_sibling
  nil
end

#source_positionHash{Symbol => Integer}

Get unified source position hash

Returns:

  • (Hash{Symbol => Integer})


232
233
234
235
236
237
238
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
# File 'lib/tree_haver/base/node.rb', line 232

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)


208
209
210
211
212
213
214
215
216
# File 'lib/tree_haver/base/node.rb', line 208

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



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

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

#textString

Get the text content of this node

Returns:

  • (String)

    Node text



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

def text
  return "" unless source

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

#to_sString

String conversion returns the text content

Returns:

  • (String)


279
280
281
# File 'lib/tree_haver/base/node.rb', line 279

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