Class: Ast::Merge::ConflictResolverBase Abstract

Inherits:
Object
  • Object
show all
Includes:
UnresolvedSupport
Defined in:
lib/ast/merge/conflict_resolver_base.rb

Overview

This class is abstract.

Subclass and implement resolve_node_pair, resolve_batch, or resolve_boundary

Base class for conflict resolvers across all *-merge gems.

Provides common functionality for resolving conflicts between template and destination content during merge operations. Supports three resolution strategies that can be selected based on the needs of each file format:

  • :node - Per-node resolution (resolve individual node pairs)
  • :batch - Batch resolution (resolve entire file using signature maps)
  • :boundary - Boundary resolution (resolve sections/ranges of content)

Examples:

Node-based resolution (commonmarker-merge style)

class ConflictResolver < Ast::Merge::ConflictResolverBase
  def initialize(preference:, template_analysis:, dest_analysis:)
    super(
      strategy: :node,
      preference: preference,
      template_analysis: template_analysis,
      dest_analysis: dest_analysis
    )
  end

  # Called for each node pair
  def resolve_node_pair(template_node, dest_node, template_index:, dest_index:)
    # Return resolution hash
  end
end

Batch resolution (psych-merge/json-merge style)

class ConflictResolver < Ast::Merge::ConflictResolverBase
  def initialize(template_analysis, dest_analysis, preference: :destination)
    super(
      strategy: :batch,
      preference: preference,
      template_analysis: template_analysis,
      dest_analysis: dest_analysis
    )
  end

  # Called once for entire merge
  def resolve_batch(result)
    # Populate result with merged content
  end
end

Boundary resolution (prism-merge style)

class ConflictResolver < Ast::Merge::ConflictResolverBase
  def initialize(template_analysis, dest_analysis, preference: :destination)
    super(
      strategy: :boundary,
      preference: preference,
      template_analysis: template_analysis,
      dest_analysis: dest_analysis
    )
  end

  # Called for each boundary (section with differences)
  def resolve_boundary(boundary, result)
    # Process boundary and populate result
  end
end

Direct Known Subclasses

Text::ConflictResolver

Constant Summary collapse

DECISION_DESTINATION =

Use destination version (customization preserved)

:destination
DECISION_TEMPLATE =

Use template version (update applied)

:template
DECISION_ADDED =

Content was added from template (template-only)

:added
DECISION_FROZEN =

Content preserved from frozen block

:frozen
DECISION_IDENTICAL =

Content was identical (no conflict)

:identical
DECISION_KEPT_DEST =

Content was kept from destination (signature match, dest preferred)

:kept_destination
DECISION_KEPT_TEMPLATE =

Content was kept from template (signature match, template preferred)

:kept_template
DECISION_APPENDED =

Content was appended from destination (dest-only)

:appended
DECISION_FREEZE_BLOCK =

Content preserved from freeze block marker

:freeze_block
DECISION_RECURSIVE =

Content requires recursive merge (container types)

:recursive
DECISION_REPLACED =

Content was replaced (signature match with different content)

:replaced

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(strategy:, preference:, template_analysis:, dest_analysis:, add_template_only_nodes: false, remove_template_missing_nodes: false, recursive: true, match_refiner: nil, **_options) ⇒ ConflictResolverBase

Initialize the conflict resolver

Parameters:

  • strategy (Symbol)

    Resolution strategy (:node, :batch, or :boundary)

  • preference (Symbol, Hash)

    Which version to prefer. As Symbol: :destination or :template (applies to all nodes) As Hash: Maps node types/merge_types to preferences

    • Use :default key for fallback preference @example { default: :destination, lint_gem: :template }
  • template_analysis (Object)

    Analysis of the template file

  • dest_analysis (Object)

    Analysis of the destination file

  • add_template_only_nodes (Boolean) (defaults to: false)

    Whether to add nodes only in template (batch/boundary strategy)

  • remove_template_missing_nodes (Boolean) (defaults to: false)

    Whether to remove destination nodes not in template

  • recursive (Boolean, Integer) (defaults to: true)

    Whether to merge nested structures recursively

    • true: unlimited depth (default)
    • false: disabled
    • Integer > 0: max depth
    • 0: invalid, raises ArgumentError
  • match_refiner (#call, nil) (defaults to: nil)

    Optional match refiner for fuzzy matching

  • options (Hash)

    Additional options for forward compatibility



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/ast/merge/conflict_resolver_base.rb', line 156

def initialize(strategy:, preference:, template_analysis:, dest_analysis:, add_template_only_nodes: false,
               remove_template_missing_nodes: false, recursive: true, match_refiner: nil, **_options)
  unless %i[node batch boundary].include?(strategy)
    raise ArgumentError, "Invalid strategy: #{strategy}. Must be :node, :batch, or :boundary"
  end

  validate_preference!(preference)
  validate_recursive!(recursive)

  @strategy = strategy
  @preference = preference
  @template_analysis = template_analysis
  @dest_analysis = dest_analysis
  @add_template_only_nodes = add_template_only_nodes
  @remove_template_missing_nodes = remove_template_missing_nodes
  @recursive = recursive
  @match_refiner = match_refiner
  # **options captured for forward compatibility - subclasses may use additional options
end

Instance Attribute Details

#add_template_only_nodesBoolean (readonly)

Returns Whether to add template-only nodes (batch strategy).

Returns:

  • (Boolean)

    Whether to add template-only nodes (batch strategy)



123
124
125
# File 'lib/ast/merge/conflict_resolver_base.rb', line 123

def add_template_only_nodes
  @add_template_only_nodes
end

#dest_analysisObject (readonly)

Returns Destination file analysis.

Returns:

  • (Object)

    Destination file analysis



120
121
122
# File 'lib/ast/merge/conflict_resolver_base.rb', line 120

def dest_analysis
  @dest_analysis
end

#match_refinerObject? (readonly)

Returns Match refiner for fuzzy matching.

Returns:

  • (Object, nil)

    Match refiner for fuzzy matching



135
136
137
# File 'lib/ast/merge/conflict_resolver_base.rb', line 135

def match_refiner
  @match_refiner
end

#preferenceSymbol, Hash (readonly)

Returns Merge preference. As Symbol: :destination or :template (applies to all nodes) As Hash: Maps node types/merge_types to preferences @example { default: :destination, lint_gem: :template }.

Returns:

  • (Symbol, Hash)

    Merge preference. As Symbol: :destination or :template (applies to all nodes) As Hash: Maps node types/merge_types to preferences @example { default: :destination, lint_gem: :template }



114
115
116
# File 'lib/ast/merge/conflict_resolver_base.rb', line 114

def preference
  @preference
end

#recursiveBoolean, Integer (readonly)

Returns Whether to merge nested structures recursively

  • true: unlimited depth (default)
  • false: disabled
  • Integer > 0: max depth.

Returns:

  • (Boolean, Integer)

    Whether to merge nested structures recursively

    • true: unlimited depth (default)
    • false: disabled
    • Integer > 0: max depth


132
133
134
# File 'lib/ast/merge/conflict_resolver_base.rb', line 132

def recursive
  @recursive
end

#remove_template_missing_nodesBoolean (readonly)

Returns Whether to remove destination nodes not in template (batch strategy).

Returns:

  • (Boolean)

    Whether to remove destination nodes not in template (batch strategy)



126
127
128
# File 'lib/ast/merge/conflict_resolver_base.rb', line 126

def remove_template_missing_nodes
  @remove_template_missing_nodes
end

#strategySymbol (readonly)

Returns Resolution strategy (:node, :batch, or :boundary).

Returns:

  • (Symbol)

    Resolution strategy (:node, :batch, or :boundary)



108
109
110
# File 'lib/ast/merge/conflict_resolver_base.rb', line 108

def strategy
  @strategy
end

#template_analysisObject (readonly)

Returns Template file analysis.

Returns:

  • (Object)

    Template file analysis



117
118
119
# File 'lib/ast/merge/conflict_resolver_base.rb', line 117

def template_analysis
  @template_analysis
end

Instance Method Details

#default_preferenceSymbol

Get the default preference (used as fallback).

Returns:

  • (Symbol)

    :destination or :template



233
234
235
236
237
238
239
# File 'lib/ast/merge/conflict_resolver_base.rb', line 233

def default_preference
  if @preference.is_a?(Hash)
    @preference.fetch(:default, :destination)
  else
    @preference
  end
end

#freeze_node?(node) ⇒ Boolean

Check if a node is a freeze node using duck typing

Parameters:

  • node (Object)

    Node to check

Returns:

  • (Boolean)

    True if node is a freeze node



199
200
201
# File 'lib/ast/merge/conflict_resolver_base.rb', line 199

def freeze_node?(node)
  node.respond_to?(:freeze_node?) && node.freeze_node?
end

#per_type_preference?Boolean

Check if Hash-based per-type preferences are configured.

Returns:

  • (Boolean)

    true if preference is a Hash



244
245
246
# File 'lib/ast/merge/conflict_resolver_base.rb', line 244

def per_type_preference?
  @preference.is_a?(Hash)
end

#preference_for_node(node) ⇒ Symbol

Get the preference for a specific node.

When preference is a Hash, looks up the preference for the node's merge_type (if wrapped with NodeTyping) or falls back to :default.

Examples:

With Symbol preference

preference_for_node(any_node)  # => returns @preference

With Hash preference and typed node

# Given preference: { default: :destination, lint_gem: :template }
preference_for_node(lint_gem_node)  # => :template
preference_for_node(other_node)     # => :destination

Parameters:

  • node (Object, nil)

    The node to get preference for

Returns:

  • (Symbol)

    :destination or :template



218
219
220
221
222
223
224
225
226
227
228
# File 'lib/ast/merge/conflict_resolver_base.rb', line 218

def preference_for_node(node)
  return default_preference unless @preference.is_a?(Hash)
  return default_preference unless node

  # Check if node has a merge_type (from NodeTyping)
  merge_type = NodeTyping.merge_type_for(node)
  return @preference.fetch(merge_type) { default_preference } if merge_type

  # Fall back to default
  default_preference
end

#resolve(*args, **kwargs) ⇒ Object

Resolve conflicts using the configured strategy

For :node strategy, this delegates to resolve_node_pair For :batch strategy, this delegates to resolve_batch For :boundary strategy, this delegates to resolve_boundary

Parameters:

  • args (Array)

    Arguments passed to the strategy method

Returns:

  • (Object)

    Resolution result (format depends on strategy)



184
185
186
187
188
189
190
191
192
193
# File 'lib/ast/merge/conflict_resolver_base.rb', line 184

def resolve(*args, **kwargs)
  case @strategy
  when :node
    resolve_node_pair(*args, **kwargs)
  when :batch
    resolve_batch(*args)
  when :boundary
    resolve_boundary(*args)
  end
end