Class: Bash::Merge::FileAnalysis

Inherits:
Object
  • Object
show all
Includes:
Ast::Merge::FileAnalyzable
Defined in:
lib/bash/merge/file_analysis.rb

Overview

Analyzes Bash script structure, extracting nodes, comments, and freeze blocks. This is the main analysis class that prepares Bash content for merging.

Examples:

Basic usage

analysis = FileAnalysis.new(bash_source)
analysis.valid? # => true
analysis.nodes # => [NodeWrapper, FreezeNodeBase, ...]
analysis.freeze_blocks # => [FreezeNodeBase, ...]

Constant Summary collapse

DEFAULT_FREEZE_TOKEN =

Default freeze token for identifying freeze blocks

'bash-merge'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source, freeze_token: DEFAULT_FREEZE_TOKEN, signature_generator: nil, parser_path: nil, **_options) ⇒ FileAnalysis

Initialize file analysis

Parameters:

  • source (String)

    Bash source code to analyze

  • freeze_token (String) (defaults to: DEFAULT_FREEZE_TOKEN)

    Token for freeze block markers

  • signature_generator (Proc, nil) (defaults to: nil)

    Custom signature generator

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

    Path to tree-sitter-bash parser library

  • options (Hash)

    Additional options (forward compatibility - ignored by FileAnalysis)



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/bash/merge/file_analysis.rb', line 45

def initialize(source, freeze_token: DEFAULT_FREEZE_TOKEN, signature_generator: nil, parser_path: nil, **_options)
  @source = source
  @lines = source.lines.map(&:chomp)
  @freeze_token = freeze_token
  @signature_generator = signature_generator
  @parser_path = parser_path
  @errors = []
  # **options captures any additional parameters (e.g., node_typing) for forward compatibility

  # Initialize comment tracking
  @comment_tracker = CommentTracker.new(source)

  # Parse the Bash script
  DebugLogger.time('FileAnalysis#parse_bash') { parse_bash }

  # Extract freeze blocks and integrate with nodes
  @freeze_blocks = extract_freeze_blocks
  @nodes = integrate_nodes_and_freeze_blocks

  DebugLogger.debug('FileAnalysis initialized', {
                      signature_generator: signature_generator ? 'custom' : 'default',
                      nodes_count: @nodes.size,
                      freeze_blocks: @freeze_blocks.size,
                      valid: valid?
                    })
end

Instance Attribute Details

#astTreeHaver::Tree? (readonly)

Returns Parsed AST.

Returns:

  • (TreeHaver::Tree, nil)

    Parsed AST



23
24
25
# File 'lib/bash/merge/file_analysis.rb', line 23

def ast
  @ast
end

#comment_trackerCommentTracker (readonly)

Returns Comment tracker for this file.

Returns:



20
21
22
# File 'lib/bash/merge/file_analysis.rb', line 20

def comment_tracker
  @comment_tracker
end

#errorsArray (readonly)

Returns Parse errors if any.

Returns:

  • (Array)

    Parse errors if any



26
27
28
# File 'lib/bash/merge/file_analysis.rb', line 26

def errors
  @errors
end

Class Method Details

.find_parser_pathString?

Find the parser library path using TreeHaver::GrammarFinder

Returns:

  • (String, nil)

    Path to the parser library or nil if not found

Raises:

  • (TreeHaver::NotAvailable)

    if ENV is set to invalid path



33
34
35
# File 'lib/bash/merge/file_analysis.rb', line 33

def find_parser_path
  TreeHaver::GrammarFinder.new(:bash).find_library_path
end

Instance Method Details

#comment_attachment_for(owner, **options) ⇒ Ast::Merge::Comment::Attachment

Build a passive shared comment attachment for an owner.

Parameters:

  • owner (Object)

    Structural owner for the attachment

  • options (Hash)

    Additional metadata / lookup overrides

Returns:

  • (Ast::Merge::Comment::Attachment)


133
134
135
136
137
138
139
# File 'lib/bash/merge/file_analysis.rb', line 133

def comment_attachment_for(owner, **options)
  shared_comment_attachment_for(
    owner,
    tracker_attachment: comment_tracker.comment_attachment_for(owner, **options),
    **options
  )
end

#comment_attachment_strategySymbol

Returns:

  • (Symbol)


142
143
144
# File 'lib/bash/merge/file_analysis.rb', line 142

def comment_attachment_strategy
  :augmenter_preferred_tracker_layout
end

#comment_augmenter(owners: nil, **options) ⇒ Ast::Merge::Comment::Augmenter

Build a passive shared comment augmenter for this analysis.

Parameters:

  • owners (Array<#start_line,#end_line>, nil) (defaults to: nil)

    Owners used for attachment inference

  • options (Hash)

    Additional augmenter options

Returns:

  • (Ast::Merge::Comment::Augmenter)


159
160
161
162
163
164
# File 'lib/bash/merge/file_analysis.rb', line 159

def comment_augmenter(owners: nil, **options)
  comment_tracker.augment(
    owners: owners || comment_augmenter_default_owners,
    **options
  )
end

#comment_capabilityAst::Merge::Comment::Capability

Get shared comment capability information for this analysis.

Returns:

  • (Ast::Merge::Comment::Capability)


81
82
83
# File 'lib/bash/merge/file_analysis.rb', line 81

def comment_capability
  @comment_capability ||= comment_tracker.augment(owners: []).capability
end

#comment_node_at(line_num) ⇒ Ast::Merge::Comment::Line?

Get a shared Ast::Merge comment node at a specific line.

Parameters:

  • line_num (Integer)

    1-based line number

Returns:

  • (Ast::Merge::Comment::Line, nil)


110
111
112
# File 'lib/bash/merge/file_analysis.rb', line 110

def comment_node_at(line_num)
  comment_tracker.comment_node_at(line_num)
end

#comment_nodesArray<Ast::Merge::Comment::Line>

Get all tracked comments converted to shared Ast::Merge comment nodes.

Returns:

  • (Array<Ast::Merge::Comment::Line>)


102
103
104
# File 'lib/bash/merge/file_analysis.rb', line 102

def comment_nodes
  comment_tracker.comment_nodes
end

#comment_region_for_range(range, kind:, full_line_only: false) ⇒ Ast::Merge::Comment::Region

Get comments in a line range converted to a shared comment region.

Parameters:

  • range (Range)

    Range of 1-based line numbers

  • kind (Symbol)

    Region kind (:leading, :inline, :orphan, etc.)

  • full_line_only (Boolean) (defaults to: false)

    Whether to keep only full-line comments

Returns:

  • (Ast::Merge::Comment::Region)


120
121
122
123
124
125
126
# File 'lib/bash/merge/file_analysis.rb', line 120

def comment_region_for_range(range, kind:, full_line_only: false)
  comment_tracker.comment_region_for_range(
    range,
    kind: kind,
    full_line_only: full_line_only
  )
end

#comment_support_styleAst::Merge::Comment::SupportStyle

Describe how Bash merges currently own and emit comments.

Bash comment handling is fully source-augmented and emitted through the synthetic merge layer.

Returns:

  • (Ast::Merge::Comment::SupportStyle)


91
92
93
94
95
96
97
# File 'lib/bash/merge/file_analysis.rb', line 91

def comment_support_style
  @comment_support_style ||= shared_comment_support_style(
    source: :bash_source,
    style: :hash_comment,
    read_strategy: :source_augmented_portable_write
  )
end

#fallthrough_node?(value) ⇒ Boolean

Override to detect tree-sitter nodes for signature generator fallthrough

Parameters:

  • value (Object)

    The value to check

Returns:

  • (Boolean)

    true if this is a fallthrough node



194
195
196
# File 'lib/bash/merge/file_analysis.rb', line 194

def fallthrough_node?(value)
  value.is_a?(NodeWrapper) || value.is_a?(FreezeNode) || super
end

#freeze_block_at(line_num) ⇒ FreezeNode?

Get the freeze block containing the given line.

Parameters:

  • line_num (Integer)

    1-based line number

Returns:



187
188
189
# File 'lib/bash/merge/file_analysis.rb', line 187

def freeze_block_at(line_num)
  @freeze_blocks.find { |fb| fb.location.cover?(line_num) }
end

#in_freeze_block?(line_num) ⇒ Boolean

Check if a line is within a freeze block.

Parameters:

  • line_num (Integer)

    1-based line number

Returns:

  • (Boolean)


179
180
181
# File 'lib/bash/merge/file_analysis.rb', line 179

def in_freeze_block?(line_num)
  @freeze_blocks.any? { |fb| fb.location.cover?(line_num) }
end

#root_nodeNodeWrapper?

Get the root node of the parse tree

Returns:



200
201
202
203
204
# File 'lib/bash/merge/file_analysis.rb', line 200

def root_node
  return unless valid?

  NodeWrapper.new(@ast.root_node, lines: @lines, source: @source)
end

#ruleset_owner_selectorObject



146
147
148
# File 'lib/bash/merge/file_analysis.rb', line 146

def ruleset_owner_selector
  :line_bound_statements
end

#ruleset_render_familyObject



150
151
152
# File 'lib/bash/merge/file_analysis.rb', line 150

def ruleset_render_family
  :bash_script_statements
end

#statementsArray<NodeWrapper, FreezeNodeBase> Also known as: nodes

The base module uses 'statements' - provide both names for compatibility

Returns:



168
169
170
# File 'lib/bash/merge/file_analysis.rb', line 168

def statements
  @nodes || []
end

#top_level_statementsArray<NodeWrapper>

Get top-level statements from the script

Returns:



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/bash/merge/file_analysis.rb', line 208

def top_level_statements
  return [] unless valid?

  @top_level_statements ||= begin
    root = @ast.root_node
    if root
      statements = []
      root.each do |child|
        next if child.type.to_s == 'comment' # Comments handled separately

        statements << NodeWrapper.new(child, lines: @lines, source: @source)
      end
      statements
    else
      []
    end
  end
end

#valid?Boolean

Check if parse was successful

Returns:

  • (Boolean)


74
75
76
# File 'lib/bash/merge/file_analysis.rb', line 74

def valid?
  @errors.empty? && !@ast.nil?
end