Class: Psych::Merge::FileAnalysis

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

Overview

Analyzes YAML file structure, extracting statements, comments, and freeze blocks.
This is the main analysis class that prepares YAML content for merging.

Examples:

Basic usage

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

Constant Summary collapse

DEFAULT_FREEZE_TOKEN =

Default freeze token for identifying freeze blocks

"psych-merge"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

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

Initialize file analysis

Parameters:

  • source (String)

    YAML 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

  • options (Hash)

    Additional options (forward compatibility - ignored by FileAnalysis)



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/psych/merge/file_analysis.rb', line 37

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

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

  # Parse the YAML
  DebugLogger.time("FileAnalysis#parse_yaml") { parse_yaml }

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

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

Instance Attribute Details

#astPsych::Nodes::Stream? (readonly)

Returns Parsed AST.

Returns:

  • (Psych::Nodes::Stream, nil)

    Parsed AST



23
24
25
# File 'lib/psych/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/psych/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



29
30
31
# File 'lib/psych/merge/file_analysis.rb', line 29

def errors
  @errors
end

#treeTreeHaver::Backends::Psych::Tree? (readonly)

Returns TreeHaver tree (for future use).

Returns:

  • (TreeHaver::Backends::Psych::Tree, nil)

    TreeHaver tree (for future use)



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

def tree
  @tree
end

Instance Method Details

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

Build a passive shared comment attachment for an owner.

Parameters:

  • owner (Object)

    Structural owner for the attachment

  • line_num (Integer, nil) (defaults to: nil)

    Optional line number override

  • options (Hash)

    Additional attachment metadata

Returns:

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


189
190
191
# File 'lib/psych/merge/file_analysis.rb', line 189

def comment_attachment_for(owner, line_num: nil, **options)
  @comment_tracker.comment_attachment_for(owner, line_num: line_num, **options)
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)


110
111
112
113
114
115
# File 'lib/psych/merge/file_analysis.rb', line 110

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)


72
73
74
# File 'lib/psych/merge/file_analysis.rb', line 72

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)


87
88
89
# File 'lib/psych/merge/file_analysis.rb', line 87

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

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

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

Returns:

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


79
80
81
# File 'lib/psych/merge/file_analysis.rb', line 79

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)


97
98
99
100
101
102
103
# File 'lib/psych/merge/file_analysis.rb', line 97

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

#fallthrough_node?(value) ⇒ Boolean

Override to detect Psych nodes for signature generator fallthrough

Parameters:

  • value (Object)

    The value to check

Returns:

  • (Boolean)

    true if this is a fallthrough node



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

def fallthrough_node?(value)
  value.is_a?(NodeWrapper) || value.is_a?(Ast::Merge::FreezeNodeBase) || value.is_a?(MappingEntry) || super
end

#freeze_block_at(line_num) ⇒ FreezeNode?

Get the freeze block containing the given line.

NOTE: This method intentionally does NOT call super or use the base
freeze_blocks method. The base implementation derives freeze blocks from
statements.select { |n| n.is_a?(Freezable) }, but during initialization
@freeze_blocks is extracted BEFORE @statements is populated (see
integrate_nodes_and_freeze_blocks). This method is called during that
integration process, so we must use @freeze_blocks directly.

Parameters:

  • line_num (Integer)

    1-based line number

Returns:



143
144
145
# File 'lib/psych/merge/file_analysis.rb', line 143

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.

NOTE: This method intentionally does NOT call super or use the base
freeze_blocks method. The base implementation derives freeze blocks from
statements.select { |n| n.is_a?(Freezable) }, but during initialization
@freeze_blocks is extracted BEFORE @statements is populated (see
integrate_nodes_and_freeze_blocks). This method is called during that
integration process, so we must use @freeze_blocks directly.

Parameters:

  • line_num (Integer)

    1-based line number

Returns:

  • (Boolean)


128
129
130
# File 'lib/psych/merge/file_analysis.rb', line 128

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

#root_mapping_entriesArray<Array(NodeWrapper, NodeWrapper)>

Get mapping entries from the root document

Returns:



156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/psych/merge/file_analysis.rb', line 156

def root_mapping_entries
  return [] unless valid? && @ast.children&.any?

  doc = @ast.children.first
  return [] unless doc.is_a?(::Psych::Nodes::Document)

  root = doc.children&.first
  return [] unless root.is_a?(::Psych::Nodes::Mapping)

  root_wrapper = wrap_root_node(root)
  root_wrapper.mapping_entries(comment_tracker: @comment_tracker)
end

#root_nodeNodeWrapper?

Get the root node of the first document

Returns:



171
172
173
174
175
176
177
178
179
180
181
# File 'lib/psych/merge/file_analysis.rb', line 171

def root_node
  return unless valid? && @ast.children&.any?

  doc = @ast.children.first
  return unless doc.is_a?(::Psych::Nodes::Document)

  root = doc.children&.first
  return unless root

  wrap_root_node(root)
end

#valid?Boolean

Check if parse was successful

Returns:

  • (Boolean)


65
66
67
# File 'lib/psych/merge/file_analysis.rb', line 65

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