Class: Ast::Merge::ConflictResolverBase Abstract

Inherits:
Object
  • Object
show all
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



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/ast/merge/conflict_resolver_base.rb', line 152

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)



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

def add_template_only_nodes
  @add_template_only_nodes
end

#dest_analysisObject (readonly)

Returns Destination file analysis.

Returns:

  • (Object)

    Destination file analysis



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

def dest_analysis
  @dest_analysis
end

#match_refinerObject? (readonly)

Returns Match refiner for fuzzy matching.

Returns:

  • (Object, nil)

    Match refiner for fuzzy matching



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

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 }



110
111
112
# File 'lib/ast/merge/conflict_resolver_base.rb', line 110

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


128
129
130
# File 'lib/ast/merge/conflict_resolver_base.rb', line 128

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)



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

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)



104
105
106
# File 'lib/ast/merge/conflict_resolver_base.rb', line 104

def strategy
  @strategy
end

#template_analysisObject (readonly)

Returns Template file analysis.

Returns:

  • (Object)

    Template file analysis



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

def template_analysis
  @template_analysis
end

Instance Method Details

#default_preferenceSymbol

Get the default preference (used as fallback).

Returns:

  • (Symbol)

    :destination or :template



228
229
230
231
232
233
234
# File 'lib/ast/merge/conflict_resolver_base.rb', line 228

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



194
195
196
# File 'lib/ast/merge/conflict_resolver_base.rb', line 194

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



239
240
241
# File 'lib/ast/merge/conflict_resolver_base.rb', line 239

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



213
214
215
216
217
218
219
220
221
222
223
# File 'lib/ast/merge/conflict_resolver_base.rb', line 213

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)



179
180
181
182
183
184
185
186
187
188
# File 'lib/ast/merge/conflict_resolver_base.rb', line 179

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