Class: Toml::Merge::FileAnalysis

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

Overview

Analyzes TOML file structure, extracting statements for merging.
This is the main analysis class that prepares TOML content for merging.

Examples:

Basic usage

analysis = FileAnalysis.new(toml_source)
analysis.valid? # => true
analysis.statements # => [NodeWrapper, ...]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source, signature_generator: nil, parser_path: nil, **options) ⇒ FileAnalysis

Note:

To force a specific backend, use TreeHaver.with_backend or TREE_HAVER_BACKEND env var.
TreeHaver handles backend selection, auto-detection, and fallback.

Initialize file analysis

Parameters:

  • source (String)

    TOML source code to analyze

  • source (String)

    TOML source code to analyze

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

    Custom signature generator

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

    Path to tree-sitter-toml parser library

  • options (Hash)

    Additional options (forward compatibility - freeze_token, node_typing, etc.)



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/toml/merge/file_analysis.rb', line 43

def initialize(source, signature_generator: nil, parser_path: nil, **options)
  @source = source
  @lines = source.lines.map(&:chomp)
  @signature_generator = signature_generator
  @parser_path = parser_path || self.class.find_parser_path
  @errors = []
  @backend = :tree_sitter  # Default, will be updated during parsing
  # **options captures any additional parameters (e.g., freeze_token, node_typing) for forward compatibility

  # Parse the TOML
  DebugLogger.time("FileAnalysis#parse_toml") { parse_toml }

  @statements = integrate_nodes

  DebugLogger.debug("FileAnalysis initialized", {
    signature_generator: signature_generator ? "custom" : "default",
    statements_count: @statements.size,
    valid: valid?,
  })
end

Instance Attribute Details

#astTreeHaver::Tree? (readonly)

Returns Parsed AST.

Returns:

  • (TreeHaver::Tree, nil)

    Parsed AST



16
17
18
# File 'lib/toml/merge/file_analysis.rb', line 16

def ast
  @ast
end

#backendSymbol (readonly)

Returns The backend used for parsing (:tree_sitter or :citrus).

Returns:

  • (Symbol)

    The backend used for parsing (:tree_sitter or :citrus)



22
23
24
# File 'lib/toml/merge/file_analysis.rb', line 22

def backend
  @backend
end

#errorsArray (readonly)

Returns Parse errors if any.

Returns:

  • (Array)

    Parse errors if any



19
20
21
# File 'lib/toml/merge/file_analysis.rb', line 19

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



28
29
30
# File 'lib/toml/merge/file_analysis.rb', line 28

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

Instance Method Details

#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



73
74
75
# File 'lib/toml/merge/file_analysis.rb', line 73

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

#root_nodeNodeWrapper?

Get the root node of the parse tree

Returns:



79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/toml/merge/file_analysis.rb', line 79

def root_node
  return unless valid?

  root = @ast.root_node
  NodeWrapper.new(
    root,
    lines: @lines,
    source: @source,
    backend: @backend,
    document_root: root,
  )
end

#root_pairsArray<NodeWrapper>

Get all top-level key-value pairs (not in tables)

For tree-sitter backend: pairs are nested under tables, so root-level
pairs are direct children of the document.

For Citrus backend: ALL pairs are siblings at document level (flat structure).
We must filter to only include pairs that appear BEFORE the first table header.

Returns:



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/toml/merge/file_analysis.rb', line 131

def root_pairs
  return [] unless valid?

  result = []
  root = @ast.root_node

  # Find the line number of the first table (if any)
  first_table_line = nil
  root.each do |child|
    canonical_type = NodeTypeNormalizer.canonical_type(child.type, @backend)
    if NodeTypeNormalizer.table_type?(canonical_type)
      child_line = child.respond_to?(:start_point) ? child.start_point.row + 1 : nil
      if child_line && (first_table_line.nil? || child_line < first_table_line)
        first_table_line = child_line
      end
    end
  end

  root.each do |child|
    canonical_type = NodeTypeNormalizer.canonical_type(child.type, @backend)
    next unless canonical_type == :pair

    # For Citrus backend, only include pairs before the first table
    if first_table_line
      child_line = child.respond_to?(:start_point) ? child.start_point.row + 1 : nil
      next if child_line && child_line >= first_table_line
    end

    result << NodeWrapper.new(
      child,
      lines: @lines,
      source: @source,
      backend: @backend,
      document_root: root,
    )
  end
  result
end

#signature_mapHash<Array, NodeWrapper>

Get a hash mapping signatures to nodes

Returns:



94
95
96
# File 'lib/toml/merge/file_analysis.rb', line 94

def signature_map
  @signature_map ||= build_signature_map
end

#tablesArray<NodeWrapper>

Get all top-level tables (sections) in the TOML document
Uses NodeTypeNormalizer for backend-agnostic type checking.
Passes document_root to enable Citrus backend normalization (pairs as siblings).

Returns:



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/toml/merge/file_analysis.rb', line 102

def tables
  return [] unless valid?

  result = []
  root = @ast.root_node
  root.each do |child|
    canonical_type = NodeTypeNormalizer.canonical_type(child.type, @backend)
    next unless NodeTypeNormalizer.table_type?(canonical_type)

    result << NodeWrapper.new(
      child,
      lines: @lines,
      source: @source,
      backend: @backend,
      document_root: root,
    )
  end
  result
end

#valid?Boolean

Check if parse was successful

Returns:

  • (Boolean)


66
67
68
# File 'lib/toml/merge/file_analysis.rb', line 66

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