Class: Ast::Merge::MergerConfig

Inherits:
Object
  • Object
show all
Defined in:
lib/ast/merge/merger_config.rb

Overview

Configuration object for SmartMerger options.

This class encapsulates common configuration options used across all *-merge gem SmartMerger implementations. It provides a standardized interface for merge configuration and validates option values.

Examples:

Creating a config with defaults

config = MergerConfig.new
config.preference  # => :destination
config.add_template_only_nodes     # => false

Creating a config for template-wins merge

config = MergerConfig.new(
  preference: :template,
  add_template_only_nodes: true
)

Using with SmartMerger

config = MergerConfig.new(preference: :template)
merger = SmartMerger.new(template, dest, **config.to_h)

Per-node-type preferences with node_typing

node_typing = {
  CallNode: ->(node) {
    return node unless node.name == :gem
    gem_name = node.arguments&.arguments&.first&.unescaped
    if gem_name&.start_with?("rubocop")
      Ast::Merge::NodeTyping.with_merge_type(node, :lint_gem)
    else
      node
    end
  }
}

config = MergerConfig.new(
  node_typing: node_typing,
  preference: {
    default: :destination,
    lint_gem: :template  # Use template versions for lint gems
  }
)

Constant Summary collapse

VALID_PREFERENCES =

Valid values for preference (when using Symbol)

%i[destination template].freeze
VALID_RESOLUTION_MODES =
%i[eager unresolved].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(preference: :destination, add_template_only_nodes: false, freeze_token: nil, signature_generator: nil, node_typing: nil, resolution_mode: :eager, unresolved_policy: nil) ⇒ MergerConfig

Initialize a new MergerConfig.

Parameters:

  • preference (Symbol, Hash) (defaults to: :destination)

    Which version to prefer on match. As Symbol: :destination or :template As Hash: Maps node types/merge_types to preferences @example { default: :destination, lint_gem: :template }

  • add_template_only_nodes (Boolean) (defaults to: false)

    Whether to add template-only nodes

  • freeze_token (String, nil) (defaults to: nil)

    Token for freeze block markers (nil uses gem default)

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

    Custom signature generator

  • node_typing (Hash{Symbol,String => #call}, nil) (defaults to: nil)

    Node typing configuration

Raises:

  • (ArgumentError)

    If preference is invalid

  • (ArgumentError)

    If node_typing is invalid



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/ast/merge/merger_config.rb', line 96

def initialize(
  preference: :destination,
  add_template_only_nodes: false,
  freeze_token: nil,
  signature_generator: nil,
  node_typing: nil,
  resolution_mode: :eager,
  unresolved_policy: nil
)
  validate_preference!(preference)
  validate_resolution_mode!(resolution_mode)
  NodeTyping.validate!(node_typing) if node_typing

  @preference = preference
  @add_template_only_nodes = add_template_only_nodes
  @freeze_token = freeze_token
  @signature_generator = signature_generator
  @node_typing = node_typing
  @resolution_mode = resolution_mode
  @unresolved_policy = normalize_unresolved_policy(unresolved_policy)
end

Instance Attribute Details

#add_template_only_nodesBoolean (readonly)

Returns Whether to add nodes that only exist in template

  • false (default) - Skip template-only nodes
  • true - Add template-only nodes to result.

Returns:

  • (Boolean)

    Whether to add nodes that only exist in template

    • false (default) - Skip template-only nodes
    • true - Add template-only nodes to result


65
66
67
# File 'lib/ast/merge/merger_config.rb', line 65

def add_template_only_nodes
  @add_template_only_nodes
end

#freeze_tokenString (readonly)

Returns Token used for freeze block markers.

Returns:

  • (String)

    Token used for freeze block markers



68
69
70
# File 'lib/ast/merge/merger_config.rb', line 68

def freeze_token
  @freeze_token
end

#node_typingHash{Symbol,String => #call}? (readonly)

Returns Node typing configuration. Maps node type names to callable objects that can transform nodes and optionally add merge_type attributes for per-node-type preferences.

Returns:

  • (Hash{Symbol,String => #call}, nil)

    Node typing configuration. Maps node type names to callable objects that can transform nodes and optionally add merge_type attributes for per-node-type preferences.



76
77
78
# File 'lib/ast/merge/merger_config.rb', line 76

def node_typing
  @node_typing
end

#preferenceSymbol, Hash (readonly)

Returns Which version to prefer when nodes have matching signatures. As Symbol:

  • :destination (default) - Keep destination version (preserves customizations)
  • :template - Use template version (applies updates) As Hash:
  • Keys are node types (Symbol) or merge_types from node_typing
  • Values are :destination or :template
  • Use :default key for fallback preference @example { default: :destination, lint_gem: :template, config_call: :template }.

Returns:

  • (Symbol, Hash)

    Which version to prefer when nodes have matching signatures. As Symbol:

    • :destination (default) - Keep destination version (preserves customizations)
    • :template - Use template version (applies updates) As Hash:
    • Keys are node types (Symbol) or merge_types from node_typing
    • Values are :destination or :template
    • Use :default key for fallback preference @example { default: :destination, lint_gem: :template, config_call: :template }


60
61
62
# File 'lib/ast/merge/merger_config.rb', line 60

def preference
  @preference
end

#resolution_modeSymbol (readonly)

Returns How merge differences should be surfaced to callers.

Returns:

  • (Symbol)

    How merge differences should be surfaced to callers.



79
80
81
# File 'lib/ast/merge/merger_config.rb', line 79

def resolution_mode
  @resolution_mode
end

#signature_generatorProc? (readonly)

Returns Custom signature generator proc.

Returns:

  • (Proc, nil)

    Custom signature generator proc



71
72
73
# File 'lib/ast/merge/merger_config.rb', line 71

def signature_generator
  @signature_generator
end

#unresolved_policyUnresolvedPolicy (readonly)

Returns Caller-facing policy for reviewable unresolved behavior.

Returns:

  • (UnresolvedPolicy)

    Caller-facing policy for reviewable unresolved behavior.



81
82
83
# File 'lib/ast/merge/merger_config.rb', line 81

def unresolved_policy
  @unresolved_policy
end

Class Method Details

.destination_wins(freeze_token: nil, signature_generator: nil, node_typing: nil, resolution_mode: :eager, unresolved_policy: nil) ⇒ MergerConfig

Create a config preset for "destination wins" merging. Destination customizations are preserved, template-only content is skipped.

Parameters:

  • freeze_token (String, nil) (defaults to: nil)

    Optional freeze token

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

    Optional signature generator

  • node_typing (Hash, nil) (defaults to: nil)

    Optional node typing configuration

Returns:



235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/ast/merge/merger_config.rb', line 235

def destination_wins(freeze_token: nil, signature_generator: nil, node_typing: nil, resolution_mode: :eager,
                     unresolved_policy: nil)
  new(
    preference: :destination,
    add_template_only_nodes: false,
    freeze_token: freeze_token,
    signature_generator: signature_generator,
    node_typing: node_typing,
    resolution_mode: resolution_mode,
    unresolved_policy: unresolved_policy
  )
end

.template_wins(freeze_token: nil, signature_generator: nil, node_typing: nil, resolution_mode: :eager, unresolved_policy: nil) ⇒ MergerConfig

Create a config preset for "template wins" merging. Template updates are applied, template-only content is added.

Parameters:

  • freeze_token (String, nil) (defaults to: nil)

    Optional freeze token

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

    Optional signature generator

  • node_typing (Hash, nil) (defaults to: nil)

    Optional node typing configuration

Returns:



255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/ast/merge/merger_config.rb', line 255

def template_wins(freeze_token: nil, signature_generator: nil, node_typing: nil, resolution_mode: :eager,
                  unresolved_policy: nil)
  new(
    preference: :template,
    add_template_only_nodes: true,
    freeze_token: freeze_token,
    signature_generator: signature_generator,
    node_typing: node_typing,
    resolution_mode: resolution_mode,
    unresolved_policy: unresolved_policy
  )
end

Instance Method Details

#eager_resolution?Boolean

Returns:

  • (Boolean)


177
178
179
# File 'lib/ast/merge/merger_config.rb', line 177

def eager_resolution?
  @resolution_mode == :eager
end

#per_type_preference?Boolean

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

Returns:

  • (Boolean)

    true if preference is a Hash



173
174
175
# File 'lib/ast/merge/merger_config.rb', line 173

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

#prefer_destination?Boolean

Check if destination version should be preferred on signature match. For Hash preferences, checks the :default key.

Returns:

  • (Boolean)

    true if destination preference



122
123
124
125
126
127
128
# File 'lib/ast/merge/merger_config.rb', line 122

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

#prefer_template?Boolean

Check if template version should be preferred on signature match. For Hash preferences, checks the :default key.

Returns:

  • (Boolean)

    true if template preference



134
135
136
137
138
139
140
# File 'lib/ast/merge/merger_config.rb', line 134

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

#preference_for(type) ⇒ Symbol

Get the preference for a specific node type or merge_type.

When preference is a Hash, looks up the preference for the given type, falling back to :default, then to :destination.

Examples:

With Symbol preference

config = MergerConfig.new(preference: :template)
config.preference_for(:any_type)  # => :template

With Hash preference

config = MergerConfig.new(
  preference: { default: :destination, lint_gem: :template }
)
config.preference_for(:lint_gem)   # => :template
config.preference_for(:other_type) # => :destination

Parameters:

  • type (Symbol, nil)

    The node type or merge_type to look up

Returns:

  • (Symbol)

    :destination or :template



160
161
162
163
164
165
166
167
168
# File 'lib/ast/merge/merger_config.rb', line 160

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

#provisional_unresolved_winner_for(kind, fallback: nil) ⇒ Object



189
190
191
# File 'lib/ast/merge/merger_config.rb', line 189

def provisional_unresolved_winner_for(kind, fallback: nil)
  unresolved_policy.provisional_winner_for(kind, fallback: fallback)
end

#to_h(default_freeze_token: nil) ⇒ Hash

Note:

Uses :preference key to match SmartMerger's API (not :preference)

Convert config to a hash suitable for passing to SmartMerger.

Parameters:

  • default_freeze_token (String, nil) (defaults to: nil)

    Default freeze token to use if none specified

Returns:

  • (Hash)

    Configuration as keyword arguments hash



198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/ast/merge/merger_config.rb', line 198

def to_h(default_freeze_token: nil)
  result = {
    preference: @preference,
    add_template_only_nodes: @add_template_only_nodes,
    resolution_mode: @resolution_mode,
    unresolved_policy: @unresolved_policy.to_h
  }
  result[:freeze_token] = @freeze_token || default_freeze_token if @freeze_token || default_freeze_token
  result[:signature_generator] = @signature_generator if @signature_generator
  result[:node_typing] = @node_typing if @node_typing
  result
end

#unresolved_for?(kind) ⇒ Boolean

Returns:

  • (Boolean)


185
186
187
# File 'lib/ast/merge/merger_config.rb', line 185

def unresolved_for?(kind)
  unresolved_resolution? && unresolved_policy.unresolved_for?(kind)
end

#unresolved_resolution?Boolean

Returns:

  • (Boolean)


181
182
183
# File 'lib/ast/merge/merger_config.rb', line 181

def unresolved_resolution?
  @resolution_mode == :unresolved
end

#with(**options) ⇒ MergerConfig

Create a new config with updated values.

Parameters:

  • options (Hash)

    Options to override

Returns:



215
216
217
218
219
220
221
222
223
224
225
# File 'lib/ast/merge/merger_config.rb', line 215

def with(**options)
  self.class.new(
    preference: options.fetch(:preference, @preference),
    add_template_only_nodes: options.fetch(:add_template_only_nodes, @add_template_only_nodes),
    freeze_token: options.fetch(:freeze_token, @freeze_token),
    signature_generator: options.fetch(:signature_generator, @signature_generator),
    node_typing: options.fetch(:node_typing, @node_typing),
    resolution_mode: options.fetch(:resolution_mode, @resolution_mode),
    unresolved_policy: options.fetch(:unresolved_policy, @unresolved_policy)
  )
end