Class: Ast::Merge::AstNode

Inherits:
TreeHaver::Base::Node
  • Object
show all
Defined in:
lib/ast/merge/ast_node.rb

Overview

Base class for synthetic AST nodes in the ast-merge framework.

“Synthetic” nodes are nodes that aren’t backed by a real parser - they’re
created by ast-merge for representing content that doesn’t have a native
AST (comments, text lines, env file entries, etc.).

This class inherits from TreeHaver::Base::Node, ensuring it stays in sync
with the canonical Node API. This allows synthetic nodes to be used
interchangeably with parser-backed nodes in merge operations.

Implements the TreeHaver::Node protocol:

  • type → String node type
  • text / slice → Source text content
  • start_byte / end_byte → Byte offsets
  • start_point / end_point → Point (row, column)
  • children → Array of child nodes
  • named? / structural? → Node classification
  • inner_node → Returns self (no wrapping layer for synthetic nodes)

Adds merge-specific methods:

  • signature → Array used for matching nodes across files
  • normalized_content → Cleaned text for comparison

Examples:

Subclassing for custom node types

class MyNode < AstNode
  def type
    "my_node"
  end

  def signature
    [:my_node, normalized_content]
  end
end

See Also:

Defined Under Namespace

Classes: Location, Point

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(slice:, location:, source: nil) ⇒ AstNode

Initialize a new AstNode.

Parameters:

  • slice (String)

    The source text for this node

  • location (Location, #start_line)

    Location object

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

    Full source text (optional)



89
90
91
92
93
94
# File 'lib/ast/merge/ast_node.rb', line 89

def initialize(slice:, location:, source: nil)
  @slice = slice
  @location = location
  # Call parent constructor with self as inner_node
  super(self, source: source)
end

Instance Attribute Details

#locationLocation (readonly)

Returns The location of this node in source.

Returns:

  • (Location)

    The location of this node in source



79
80
81
# File 'lib/ast/merge/ast_node.rb', line 79

def location
  @location
end

#sliceString (readonly)

Returns The source text for this node.

Returns:

  • (String)

    The source text for this node



82
83
84
# File 'lib/ast/merge/ast_node.rb', line 82

def slice
  @slice
end

Instance Method Details

#<=>(other) ⇒ Integer?

Comparable: compare nodes by position
Note: Inherits Comparable from TreeHaver::Base::Node

Parameters:

  • other (AstNode)

    node to compare with

Returns:

  • (Integer, nil)

    -1, 0, 1, or nil if not comparable



251
252
253
254
255
256
257
258
# File 'lib/ast/merge/ast_node.rb', line 251

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

  cmp = start_byte <=> other.start_byte
  return cmp if cmp.nonzero?

  end_byte <=> other.end_byte
end

#child(index) ⇒ AstNode?

TreeHaver::Node protocol: child(index)

Parameters:

  • index (Integer)

    Child index

Returns:

  • (AstNode, nil)

    Child at index



185
186
187
# File 'lib/ast/merge/ast_node.rb', line 185

def child(index)
  children[index]
end

#child_countInteger

TreeHaver::Node protocol: child_count

Returns:

  • (Integer)

    Number of children



178
179
180
# File 'lib/ast/merge/ast_node.rb', line 178

def child_count
  children.size
end

#childrenArray<AstNode>

TreeHaver::Node protocol: children

Returns:

  • (Array<AstNode>)

    Child nodes (empty for leaf nodes)



172
173
174
# File 'lib/ast/merge/ast_node.rb', line 172

def children
  []
end

#each {|AstNode| ... } ⇒ Enumerator?

TreeHaver::Node protocol: each
Iterate over children

Yields:

Returns:

  • (Enumerator, nil)


226
227
228
229
# File 'lib/ast/merge/ast_node.rb', line 226

def each(&block)
  return to_enum(__method__) unless block_given?
  children.each(&block)
end

#end_byteInteger

TreeHaver::Node protocol: end_byte

Returns:

  • (Integer)

    Ending byte offset



144
145
146
# File 'lib/ast/merge/ast_node.rb', line 144

def end_byte
  start_byte + slice.to_s.bytesize
end

#end_pointPoint

TreeHaver::Node protocol: end_point
Returns a Point with row (0-based) and column

Returns:

  • (Point)

    Ending position



163
164
165
166
167
168
# File 'lib/ast/merge/ast_node.rb', line 163

def end_point
  Point.new(
    row: (location&.end_line || 1) - 1,  # Convert to 0-based
    column: location&.end_column || 0,
  )
end

#has_error?Boolean

TreeHaver::Node protocol: has_error?
Synthetic nodes don’t have parse errors

Returns:

  • (Boolean)

    false



209
210
211
# File 'lib/ast/merge/ast_node.rb', line 209

def has_error?
  false
end

#inspectString

Returns Human-readable representation.

Returns:

  • (String)

    Human-readable representation



261
262
263
# File 'lib/ast/merge/ast_node.rb', line 261

def inspect
  "#<#{self.class.name} type=#{type} lines=#{location&.start_line}..#{location&.end_line}>"
end

#missing?Boolean

TreeHaver::Node protocol: missing?
Synthetic nodes are never “missing”

Returns:

  • (Boolean)

    false



217
218
219
# File 'lib/ast/merge/ast_node.rb', line 217

def missing?
  false
end

#named?Boolean

TreeHaver::Node protocol: named?
Synthetic nodes are always “named” (structural) nodes

Returns:

  • (Boolean)

    true



193
194
195
# File 'lib/ast/merge/ast_node.rb', line 193

def named?
  true
end

#normalized_contentString

Returns Normalized content for signature comparison.

Returns:

  • (String)

    Normalized content for signature comparison



242
243
244
# File 'lib/ast/merge/ast_node.rb', line 242

def normalized_content
  slice.to_s.strip
end

#signatureArray

Generate a signature for this node for matching purposes.

Override in subclasses for custom signature logic.
Default returns the node type and a normalized form of the slice.

Returns:

  • (Array)

    Signature array for matching



237
238
239
# File 'lib/ast/merge/ast_node.rb', line 237

def signature
  [type.to_sym, normalized_content]
end

#sourceString?

Override source to return stored value (not parent’s)

Returns:

  • (String, nil)

    The full source text (for text extraction)



98
99
100
# File 'lib/ast/merge/ast_node.rb', line 98

def source
  @source || super
end

#start_byteInteger

TreeHaver::Node protocol: start_byte
Calculates byte offset from source if available, otherwise estimates from lines

Returns:

  • (Integer)

    Starting byte offset



128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/ast/merge/ast_node.rb', line 128

def start_byte
  src = source
  return 0 unless src && location

  # Calculate byte offset from line/column
  lines = src.lines
  byte_offset = 0
  (0...(location.start_line - 1)).each do |i|
    byte_offset += lines[i]&.bytesize || 0
  end
  byte_offset + (location.start_column || 0)
end

#start_pointPoint

TreeHaver::Node protocol: start_point
Returns a Point with row (0-based) and column

Returns:

  • (Point)

    Starting position



152
153
154
155
156
157
# File 'lib/ast/merge/ast_node.rb', line 152

def start_point
  Point.new(
    row: (location&.start_line || 1) - 1,  # Convert to 0-based
    column: location&.start_column || 0,
  )
end

#structural?Boolean

TreeHaver::Node protocol: structural?
Synthetic nodes are always structural

Returns:

  • (Boolean)

    true



201
202
203
# File 'lib/ast/merge/ast_node.rb', line 201

def structural?
  true
end

#textString

TreeHaver::Node protocol: text

Returns:

  • (String)

    The source text



120
121
122
# File 'lib/ast/merge/ast_node.rb', line 120

def text
  slice.to_s
end

#to_sString

Returns The source text.

Returns:

  • (String)

    The source text



266
267
268
# File 'lib/ast/merge/ast_node.rb', line 266

def to_s
  slice.to_s
end

#typeString Also known as: kind

TreeHaver::Node protocol: type
Returns the node type as a string.
Subclasses should override this with specific type names.

Returns:

  • (String)

    Node type



107
108
109
110
111
112
113
# File 'lib/ast/merge/ast_node.rb', line 107

def type
  # Default: derive from class name (MyNode → "my_node")
  self.class.name.split("::").last
    .gsub(/([A-Z])/, '_\1')
    .downcase
    .sub(/^_/, "")
end

#unwrapAstNode

Support unwrap protocol (returns self for non-wrapper nodes)

Returns:



272
273
274
# File 'lib/ast/merge/ast_node.rb', line 272

def unwrap
  self
end