Class: Prism::Merge::FileAnalysis

Inherits:
Object
  • Object
show all
Defined in:
lib/prism/merge/file_analysis.rb

Overview

Comprehensive metadata capture for a Ruby file being merged. Tracks Prism parse result, line-to-node mapping, comment associations, structural signatures, and sequential anchor lines for merge alignment.

Constant Summary collapse

FREEZE_START =

Regex pattern for freeze block start marker. Matches comments like: # kettle-dev:freeze Case-insensitive to allow variations like FREEZE or Freeze

/#\s*kettle-dev:freeze/i
FREEZE_END =

Regex pattern for freeze block end marker. Matches comments like: # kettle-dev:unfreeze Case-insensitive to allow variations like UNFREEZE or Unfreeze

/#\s*kettle-dev:unfreeze/i
FREEZE_BLOCK =

Combined regex pattern for matching complete freeze blocks. Captures content between freeze/unfreeze markers (inclusive). Used to identify sections that should always be preserved from destination.

Examples:

Freeze block in Ruby code

# kettle-dev:freeze
CUSTOM_CONFIG = { key: "secret" }
# kettle-dev:unfreeze
Regexp.new("(#{FREEZE_START.source}).*?(#{FREEZE_END.source})", Regexp::IGNORECASE | Regexp::MULTILINE)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(content, signature_generator: nil) ⇒ FileAnalysis

Returns a new instance of FileAnalysis.

Parameters:

  • content (String)

    Ruby source code to analyze

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

    Optional proc to generate node signatures



35
36
37
38
39
40
41
42
43
44
45
# File 'lib/prism/merge/file_analysis.rb', line 35

def initialize(content, signature_generator: nil)
  @content = content
  @lines = content.lines
  @parse_result = Prism.parse(content)
  @statements = extract_statements
  @freeze_blocks = extract_freeze_blocks
  @signature_generator = signature_generator
  @line_to_node_map = nil
  @node_to_line_map = nil
  @comment_map = nil
end

Instance Attribute Details

#contentObject (readonly)

Returns the value of attribute content.



31
32
33
# File 'lib/prism/merge/file_analysis.rb', line 31

def content
  @content
end

#freeze_blocksObject (readonly)

Returns the value of attribute freeze_blocks.



31
32
33
# File 'lib/prism/merge/file_analysis.rb', line 31

def freeze_blocks
  @freeze_blocks
end

#linesObject (readonly)

Returns the value of attribute lines.



31
32
33
# File 'lib/prism/merge/file_analysis.rb', line 31

def lines
  @lines
end

#parse_resultObject (readonly)

Returns the value of attribute parse_result.



31
32
33
# File 'lib/prism/merge/file_analysis.rb', line 31

def parse_result
  @parse_result
end

#statementsObject (readonly)

Returns the value of attribute statements.



31
32
33
# File 'lib/prism/merge/file_analysis.rb', line 31

def statements
  @statements
end

Instance Method Details

#comment_mapHash<Integer, Array<Prism::Comment>>

Get comment map by line number

Returns:

  • (Hash<Integer, Array<Prism::Comment>>)

    Line number => comments



114
115
116
# File 'lib/prism/merge/file_analysis.rb', line 114

def comment_map
  @comment_map ||= build_comment_map
end

#extract_freeze_blocksArray<Hash>

Extract freeze block information

Returns:

  • (Array<Hash>)

    Array of freeze block metadata



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/prism/merge/file_analysis.rb', line 69

def extract_freeze_blocks
  return [] unless content.match?(FREEZE_START)

  blocks = []
  content.to_enum(:scan, FREEZE_BLOCK).each do
    match = Regexp.last_match
    next unless match

    start_idx = match.begin(0)
    end_idx = match.end(0)
    segment = match[0]
    start_line = content[0...start_idx].count("\n") + 1
    end_line = content[0...end_idx].count("\n") + 1

    blocks << {
      range: start_idx...end_idx,
      line_range: start_line..end_line,
      text: segment,
      start_marker: segment&.lines&.first&.strip,
    }
  end

  blocks
end

#extract_statementsArray<Prism::Node>

Get all top-level statement nodes

Returns:

  • (Array<Prism::Node>)


55
56
57
58
59
60
61
62
63
64
65
# File 'lib/prism/merge/file_analysis.rb', line 55

def extract_statements
  return [] unless valid?
  body = @parse_result.value.statements
  return [] unless body

  if body.is_a?(Prism::StatementsNode)
    body.body.compact
  else
    [body].compact
  end
end

#freeze_block_at(line_num) ⇒ Hash?

Get the freeze block containing the given line, if any

Parameters:

  • line_num (Integer)

    1-based line number

Returns:

  • (Hash, nil)

    Freeze block metadata or nil



147
148
149
# File 'lib/prism/merge/file_analysis.rb', line 147

def freeze_block_at(line_num)
  freeze_blocks.find { |block| block[:line_range].cover?(line_num) }
end

#generate_signature(node) ⇒ Array?

Generate signature for a node

Parameters:

  • node (Prism::Node)

    Node to generate signature for

Returns:

  • (Array, nil)

    Signature array



129
130
131
132
133
134
135
# File 'lib/prism/merge/file_analysis.rb', line 129

def generate_signature(node)
  if @signature_generator
    @signature_generator.call(node)
  else
    default_signature(node)
  end
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)


140
141
142
# File 'lib/prism/merge/file_analysis.rb', line 140

def in_freeze_block?(line_num)
  freeze_blocks.any? { |block| block[:line_range].cover?(line_num) }
end

#line_at(line_num) ⇒ String?

Get raw line content

Parameters:

  • line_num (Integer)

    1-based line number

Returns:

  • (String, nil)


162
163
164
165
# File 'lib/prism/merge/file_analysis.rb', line 162

def line_at(line_num)
  return if line_num < 1 || line_num > lines.length
  lines[line_num - 1]
end

#line_to_node_mapHash<Integer, Array<Prism::Node>>

Build mapping from line numbers to AST nodes

Returns:

  • (Hash<Integer, Array<Prism::Node>>)

    Line number => nodes on that line



96
97
98
# File 'lib/prism/merge/file_analysis.rb', line 96

def line_to_node_map
  @line_to_node_map ||= build_line_to_node_map
end

#node_to_line_mapHash<Prism::Node, Range>

Build mapping from nodes to line ranges

Returns:

  • (Hash<Prism::Node, Range>)

    Node => line range



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

def node_to_line_map
  @node_to_line_map ||= build_node_to_line_map
end

#nodes_with_commentsArray<Hash>

Get nodes with their associated comments and metadata

Returns:

  • (Array<Hash>)

    Array of node info hashes



108
109
110
# File 'lib/prism/merge/file_analysis.rb', line 108

def nodes_with_comments
  @nodes_with_comments ||= extract_nodes_with_comments
end

#normalized_line(line_num) ⇒ String?

Get normalized line content (stripped)

Parameters:

  • line_num (Integer)

    1-based line number

Returns:

  • (String, nil)


154
155
156
157
# File 'lib/prism/merge/file_analysis.rb', line 154

def normalized_line(line_num)
  return if line_num < 1 || line_num > lines.length
  lines[line_num - 1].strip
end

#signature_at(index) ⇒ Array?

Get structural signature for a statement at given index

Parameters:

  • index (Integer)

    Statement index

Returns:

  • (Array, nil)

    Signature array



121
122
123
124
# File 'lib/prism/merge/file_analysis.rb', line 121

def signature_at(index)
  return if index < 0 || index >= statements.length
  generate_signature(statements[index])
end

#valid?Boolean

Check if parsing was successful

Returns:

  • (Boolean)


49
50
51
# File 'lib/prism/merge/file_analysis.rb', line 49

def valid?
  @parse_result.success?
end