Class: Ast::Merge::AstNode

Inherits:
Object
  • Object
show all
Includes:
Comparable
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 implements the TreeHaver::Node protocol, making it compatible with all code that expects TreeHaver nodes. 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)



94
95
96
97
98
# File 'lib/ast/merge/ast_node.rb', line 94

def initialize(slice:, location:, source: nil)
  @slice = slice
  @location = location
  @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



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

def location
  @location
end

#sliceString (readonly)

Returns The source text for this node.

Returns:

  • (String)

    The source text for this node



84
85
86
# File 'lib/ast/merge/ast_node.rb', line 84

def slice
  @slice
end

#sourceString? (readonly)

Returns The full source text (for text extraction).

Returns:

  • (String, nil)

    The full source text (for text extraction)



87
88
89
# File 'lib/ast/merge/ast_node.rb', line 87

def source
  @source
end

Instance Method Details

#<=>(other) ⇒ Integer?

Comparable: compare nodes by position

Parameters:

  • other (AstNode)

    node to compare with

Returns:

  • (Integer, nil)

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



255
256
257
258
259
260
261
262
# File 'lib/ast/merge/ast_node.rb', line 255

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



190
191
192
# File 'lib/ast/merge/ast_node.rb', line 190

def child(index)
  children[index]
end

#child_countInteger

TreeHaver::Node protocol: child_count

Returns:

  • (Integer)

    Number of children



183
184
185
# File 'lib/ast/merge/ast_node.rb', line 183

def child_count
  children.size
end

#childrenArray<AstNode>

TreeHaver::Node protocol: children

Returns:

  • (Array<AstNode>)

    Child nodes (empty for leaf nodes)



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

def children
  []
end

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

TreeHaver::Node protocol: each Iterate over children

Yields:

Returns:

  • (Enumerator, nil)


231
232
233
234
# File 'lib/ast/merge/ast_node.rb', line 231

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



149
150
151
# File 'lib/ast/merge/ast_node.rb', line 149

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



168
169
170
171
172
173
# File 'lib/ast/merge/ast_node.rb', line 168

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



214
215
216
# File 'lib/ast/merge/ast_node.rb', line 214

def has_error?
  false
end

#inner_nodeAstNode

TreeHaver::Node protocol: inner_node For synthetic nodes, this returns self (no wrapping layer)

Returns:



104
105
106
# File 'lib/ast/merge/ast_node.rb', line 104

def inner_node
  self
end

#inspectString

Returns Human-readable representation.

Returns:

  • (String)

    Human-readable representation



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

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



222
223
224
# File 'lib/ast/merge/ast_node.rb', line 222

def missing?
  false
end

#named?Boolean

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

Returns:

  • (Boolean)

    true



198
199
200
# File 'lib/ast/merge/ast_node.rb', line 198

def named?
  true
end

#normalized_contentString

Returns Normalized content for signature comparison.

Returns:

  • (String)

    Normalized content for signature comparison



247
248
249
# File 'lib/ast/merge/ast_node.rb', line 247

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



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

def signature
  [type.to_sym, normalized_content]
end

#start_byteInteger

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

Returns:

  • (Integer)

    Starting byte offset



134
135
136
137
138
139
140
141
142
143
144
# File 'lib/ast/merge/ast_node.rb', line 134

def start_byte
  return 0 unless source && location

  # Calculate byte offset from line/column
  lines = source.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



157
158
159
160
161
162
# File 'lib/ast/merge/ast_node.rb', line 157

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



206
207
208
# File 'lib/ast/merge/ast_node.rb', line 206

def structural?
  true
end

#textString

TreeHaver::Node protocol: text

Returns:

  • (String)

    The source text



126
127
128
# File 'lib/ast/merge/ast_node.rb', line 126

def text
  slice.to_s
end

#to_sString

Returns The source text.

Returns:

  • (String)

    The source text



270
271
272
# File 'lib/ast/merge/ast_node.rb', line 270

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



113
114
115
116
117
118
119
# File 'lib/ast/merge/ast_node.rb', line 113

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:



276
277
278
# File 'lib/ast/merge/ast_node.rb', line 276

def unwrap
  self
end