Class: Canon::Diff::DiffNodeMapper

Inherits:
Object
  • Object
show all
Defined in:
lib/canon/diff/diff_node_mapper.rb

Overview

Maps semantic DiffNodes to textual DiffLines This is Layer 2 of the diff pipeline, bridging semantic differences (from comparators) to textual representation (for formatters)

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(diff_nodes, text1, text2, options = {}) ⇒ DiffNodeMapper

Returns a new instance of DiffNodeMapper.



24
25
26
27
28
29
30
# File 'lib/canon/diff/diff_node_mapper.rb', line 24

def initialize(diff_nodes, text1, text2, options = {})
  @diff_nodes = diff_nodes
  @text1 = text1
  @text2 = text2
  @line_map1 = options[:line_map1]
  @line_map2 = options[:line_map2]
end

Class Method Details

.map(diff_nodes, text1, text2, options = {}) ⇒ Array<DiffLine>

Map diff nodes to diff lines

Parameters:

  • diff_nodes (Array<DiffNode>)

    The semantic differences

  • text1 (String)

    The first text being compared

  • text2 (String)

    The second text being compared

  • options (Hash) (defaults to: {})

    Mapping options

Options Hash (options):

  • :line_map1 (Hash)

    Pre-built line range map for text1

  • :line_map2 (Hash)

    Pre-built line range map for text2

Returns:

  • (Array<DiffLine>)

    Diff lines with semantic linkage



20
21
22
# File 'lib/canon/diff/diff_node_mapper.rb', line 20

def self.map(diff_nodes, text1, text2, options = {})
  new(diff_nodes, text1, text2, options).map
end

Instance Method Details

#mapObject



32
33
34
35
36
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/canon/diff/diff_node_mapper.rb', line 32

def map
  lines1 = @text1.split("\n")
  lines2 = @text2.split("\n")

  # Use LCS to get structural diff
  require "diff/lcs"
  lcs_diffs = ::Diff::LCS.sdiff(lines1, lines2)

  # Check if ALL DiffNodes are informative
  all_informative = @diff_nodes && !@diff_nodes.empty? &&
    @diff_nodes.all?(&:informative?)

  # Convert LCS diffs to DiffLines
  # If all DiffNodes are informative, we create a single shared informative DiffNode
  # for all changed lines (this avoids complex linking)
  shared_informative_node = if all_informative
                              @diff_nodes.first # Use any informative node
                            end

  diff_lines = []
  line_num = 0

  lcs_diffs.each do |change|
    diff_line = case change.action
                when "="
                  DiffLine.new(
                    line_number: line_num,
                    content: change.old_element,
                    type: :unchanged,
                    diff_node: nil,
                  )
                when "-"
                  # Find the diff node for this line
                  node = shared_informative_node || find_diff_node_for_line(
                    line_num, lines1, :removed
                  )

                  # Check if this is formatting-only:
                  # 1. First check if the DiffNode itself is marked as formatting-only
                  # 2. Otherwise, check line-level formatting
                  formatting = if node.respond_to?(:formatting?) && node.formatting?
                                 true
                               else
                                 formatting_only_line?(
                                   change.old_element, ""
                                 )
                               end

                  DiffLine.new(
                    line_number: line_num,
                    content: change.old_element,
                    type: :removed,
                    diff_node: node,
                    formatting: formatting,
                  )
                when "+"
                  # Find the diff node for this line
                  node = shared_informative_node || find_diff_node_for_line(
                    line_num, lines2, :added
                  )

                  # Check if this is formatting-only:
                  # 1. First check if the DiffNode itself is marked as formatting-only
                  # 2. Otherwise, check line-level formatting
                  formatting = if node.respond_to?(:formatting?) && node.formatting?
                                 true
                               else
                                 formatting_only_line?("",
                                                       change.new_element)
                               end

                  DiffLine.new(
                    line_number: line_num,
                    content: change.new_element,
                    type: :added,
                    diff_node: node,
                    formatting: formatting,
                  )
                when "!"
                  # Find the diff node for this line
                  node = shared_informative_node || find_diff_node_for_line(
                    line_num, lines2, :changed
                  )

                  # Check if this is formatting-only:
                  # 1. First check if the DiffNode itself is marked as formatting-only
                  # 2. Otherwise, check line-level formatting
                  formatting = if node.respond_to?(:formatting?) && node.formatting?
                                 true
                               else
                                 formatting_only_line?(
                                   change.old_element, change.new_element
                                 )
                               end

                  DiffLine.new(
                    line_number: line_num,
                    content: change.new_element,
                    type: :changed,
                    diff_node: node,
                    formatting: formatting,
                  )
                end

    diff_lines << diff_line
    line_num += 1
  end

  diff_lines
end