Class: Bash::Merge::NodeWrapper

Inherits:
Object
  • Object
show all
Defined in:
lib/bash/merge/node_wrapper.rb

Overview

Wraps TreeHaver nodes with comment associations, line information, and signatures. This provides a unified interface for working with Bash AST nodes during merging.

Examples:

Basic usage

parser = TreeHaver::Parser.new
parser.language = TreeHaver::Language.bash
tree = parser.parse(source)
wrapper = NodeWrapper.new(tree.root_node, lines: source.lines, source: source)
wrapper.signature # => [:program, ...]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(node, lines:, source: nil, leading_comments: [], inline_comment: nil) ⇒ NodeWrapper

Returns a new instance of NodeWrapper.

Parameters:

  • node (TreeHaver::Node)

    tree-haver node to wrap

  • lines (Array<String>)

    Source lines for content extraction

  • source (String) (defaults to: nil)

    Original source string for byte-based text extraction

  • leading_comments (Array<Hash>) (defaults to: [])

    Comments before this node

  • inline_comment (Hash, nil) (defaults to: nil)

    Inline comment on the node’s line



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/bash/merge/node_wrapper.rb', line 41

def initialize(node, lines:, source: nil, leading_comments: [], inline_comment: nil)
  @node = node
  @lines = lines
  @source = source || lines.join("\n")
  @leading_comments = leading_comments
  @inline_comment = inline_comment

  # Extract line information from the tree-haver node (0-indexed to 1-indexed)
  if node.respond_to?(:start_point)
    point = node.start_point
    @start_line = (point.respond_to?(:row) ? point.row : point[:row]) + 1
  end
  if node.respond_to?(:end_point)
    point = node.end_point
    @end_line = (point.respond_to?(:row) ? point.row : point[:row]) + 1
  end

  # Handle edge case where end_line might be before start_line
  @end_line = @start_line if @start_line && @end_line && @end_line < @start_line
end

Instance Attribute Details

#end_lineInteger (readonly)

Returns End line (1-based).

Returns:

  • (Integer)

    End line (1-based)



28
29
30
# File 'lib/bash/merge/node_wrapper.rb', line 28

def end_line
  @end_line
end

#inline_commentHash? (readonly)

Returns Inline/trailing comment on the same line.

Returns:

  • (Hash, nil)

    Inline/trailing comment on the same line



22
23
24
# File 'lib/bash/merge/node_wrapper.rb', line 22

def inline_comment
  @inline_comment
end

#leading_commentsArray<Hash> (readonly)

Returns Leading comments associated with this node.

Returns:

  • (Array<Hash>)

    Leading comments associated with this node



19
20
21
# File 'lib/bash/merge/node_wrapper.rb', line 19

def leading_comments
  @leading_comments
end

#linesArray<String> (readonly)

Returns Source lines.

Returns:

  • (Array<String>)

    Source lines



31
32
33
# File 'lib/bash/merge/node_wrapper.rb', line 31

def lines
  @lines
end

#nodeTreeHaver::Node (readonly)

Returns The wrapped tree-haver node.

Returns:

  • (TreeHaver::Node)

    The wrapped tree-haver node



16
17
18
# File 'lib/bash/merge/node_wrapper.rb', line 16

def node
  @node
end

#sourceString (readonly)

Returns The original source string.

Returns:

  • (String)

    The original source string



34
35
36
# File 'lib/bash/merge/node_wrapper.rb', line 34

def source
  @source
end

#start_lineInteger (readonly)

Returns Start line (1-based).

Returns:

  • (Integer)

    Start line (1-based)



25
26
27
# File 'lib/bash/merge/node_wrapper.rb', line 25

def start_line
  @start_line
end

Instance Method Details

#case_statement?Boolean

Check if this is a case statement

Returns:

  • (Boolean)


121
122
123
# File 'lib/bash/merge/node_wrapper.rb', line 121

def case_statement?
  @node.type.to_s == "case_statement"
end

#childrenArray<NodeWrapper>

Get children wrapped as NodeWrappers

Returns:



179
180
181
182
183
184
185
186
187
# File 'lib/bash/merge/node_wrapper.rb', line 179

def children
  return [] unless @node.respond_to?(:each)

  result = []
  @node.each do |child|
    result << NodeWrapper.new(child, lines: @lines, source: @source)
  end
  result
end

#command?Boolean

Check if this is a command

Returns:

  • (Boolean)


127
128
129
# File 'lib/bash/merge/node_wrapper.rb', line 127

def command?
  @node.type.to_s == "command"
end

#command_nameString?

Get the command name if this is a command

Returns:

  • (String, nil)


165
166
167
168
169
170
171
172
173
174
175
# File 'lib/bash/merge/node_wrapper.rb', line 165

def command_name
  return unless command?

  # First child that is a word or simple_expansion
  @node.each do |child|
    next if %w[comment file_redirect heredoc_redirect].include?(child.type.to_s)

    return node_text(child) if %w[word command_name].include?(child.type.to_s)
  end
  nil
end

#comment?Boolean

Check if this is a comment

Returns:

  • (Boolean)


139
140
141
# File 'lib/bash/merge/node_wrapper.rb', line 139

def comment?
  @node.type.to_s == "comment"
end

#contentString

Get the content for this node from source lines

Returns:

  • (String)


227
228
229
230
231
# File 'lib/bash/merge/node_wrapper.rb', line 227

def content
  return "" unless @start_line && @end_line

  (@start_line..@end_line).map { |ln| @lines[ln - 1] }.compact.join("\n")
end

#find_child_by_field(field_name) ⇒ TreeSitter::Node?

Find a child by field name

Parameters:

  • field_name (String)

    Field name to look for

Returns:

  • (TreeSitter::Node, nil)


192
193
194
195
196
# File 'lib/bash/merge/node_wrapper.rb', line 192

def find_child_by_field(field_name)
  return unless @node.respond_to?(:child_by_field_name)

  @node.child_by_field_name(field_name)
end

#find_child_by_type(type_name) ⇒ TreeSitter::Node?

Find a child by type

Parameters:

  • type_name (String)

    Type name to look for

Returns:

  • (TreeSitter::Node, nil)


201
202
203
204
205
206
207
208
# File 'lib/bash/merge/node_wrapper.rb', line 201

def find_child_by_type(type_name)
  return unless @node.respond_to?(:each)

  @node.each do |child|
    return child if child.type.to_s == type_name
  end
  nil
end

#for_statement?Boolean

Check if this is a for loop

Returns:

  • (Boolean)


109
110
111
# File 'lib/bash/merge/node_wrapper.rb', line 109

def for_statement?
  %w[for_statement c_style_for_statement].include?(@node.type.to_s)
end

#freeze_node?Boolean

Check if this is a freeze node

Returns:

  • (Boolean)


72
73
74
# File 'lib/bash/merge/node_wrapper.rb', line 72

def freeze_node?
  false
end

#function_definition?Boolean

Check if this is a function definition

Returns:

  • (Boolean)


91
92
93
# File 'lib/bash/merge/node_wrapper.rb', line 91

def function_definition?
  @node.type.to_s == "function_definition"
end

#function_nameString?

Get the function name if this is a function definition

Returns:

  • (String, nil)


145
146
147
148
149
150
151
# File 'lib/bash/merge/node_wrapper.rb', line 145

def function_name
  return unless function_definition?

  # In bash tree-sitter, function name is in a 'name' or 'word' child
  name_node = find_child_by_type("word") || find_child_by_field("name")
  node_text(name_node) if name_node
end

#if_statement?Boolean

Check if this is an if statement

Returns:

  • (Boolean)


103
104
105
# File 'lib/bash/merge/node_wrapper.rb', line 103

def if_statement?
  @node.type.to_s == "if_statement"
end

#inspectString

String representation for debugging

Returns:

  • (String)


235
236
237
# File 'lib/bash/merge/node_wrapper.rb', line 235

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

#node_text(ts_node) ⇒ String

Extract text from a tree-sitter node using byte positions

Parameters:

  • ts_node (TreeSitter::Node)

    The tree-sitter node

Returns:

  • (String)


219
220
221
222
223
# File 'lib/bash/merge/node_wrapper.rb', line 219

def node_text(ts_node)
  return "" unless ts_node.respond_to?(:start_byte) && ts_node.respond_to?(:end_byte)

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

#pipeline?Boolean

Check if this is a pipeline

Returns:

  • (Boolean)


133
134
135
# File 'lib/bash/merge/node_wrapper.rb', line 133

def pipeline?
  @node.type.to_s == "pipeline"
end

#signatureArray?

Generate a signature for this node for matching purposes. Signatures are used to identify corresponding nodes between template and destination.

Returns:

  • (Array, nil)

    Signature array or nil if not signaturable



66
67
68
# File 'lib/bash/merge/node_wrapper.rb', line 66

def signature
  compute_signature(@node)
end

#textString

Get the text content for this node by extracting from source using byte positions

Returns:

  • (String)


212
213
214
# File 'lib/bash/merge/node_wrapper.rb', line 212

def text
  node_text(@node)
end

#typeSymbol

Get the node type as a symbol

Returns:

  • (Symbol)


78
79
80
# File 'lib/bash/merge/node_wrapper.rb', line 78

def type
  @node.type.to_sym
end

#type?(type_name) ⇒ Boolean

Check if this node has a specific type

Parameters:

  • type_name (Symbol, String)

    Type to check

Returns:

  • (Boolean)


85
86
87
# File 'lib/bash/merge/node_wrapper.rb', line 85

def type?(type_name)
  @node.type.to_s == type_name.to_s
end

#variable_assignment?Boolean

Check if this is a variable assignment

Returns:

  • (Boolean)


97
98
99
# File 'lib/bash/merge/node_wrapper.rb', line 97

def variable_assignment?
  @node.type.to_s == "variable_assignment"
end

#variable_nameString?

Get the variable name if this is a variable assignment

Returns:

  • (String, nil)


155
156
157
158
159
160
161
# File 'lib/bash/merge/node_wrapper.rb', line 155

def variable_name
  return unless variable_assignment?

  # In bash tree-sitter, variable name is a child of type 'variable_name'
  name_node = find_child_by_type("variable_name")
  node_text(name_node) if name_node
end

#while_statement?Boolean

Check if this is a while loop

Returns:

  • (Boolean)


115
116
117
# File 'lib/bash/merge/node_wrapper.rb', line 115

def while_statement?
  @node.type.to_s == "while_statement"
end