ruby-merge 7.1.3 → 7.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruby
4
+ module Merge
5
+ # Shared Ruby wrapper for nodes carrying unbalanced or inline nocov markers.
6
+ #
7
+ # The wrapped parser node remains responsible for structural identity; this
8
+ # wrapper only marks it as a Ruby coverage directive participant.
9
+ class NocovWrapperBase
10
+ include Ast::Merge::BlockDirective
11
+
12
+ attr_reader :node, :merge_type
13
+
14
+ def initialize(node, merge_type = :nocov)
15
+ @node = node
16
+ @merge_type = merge_type
17
+ end
18
+
19
+ def kind = :nocov
20
+ def children = []
21
+ def merge_policy = nil
22
+
23
+ def start_line
24
+ @node.location&.start_line
25
+ end
26
+
27
+ def end_line
28
+ @node.location&.end_line
29
+ end
30
+
31
+ def unwrap = @node
32
+
33
+ def location = @node.location
34
+
35
+ def slice = @node.slice
36
+
37
+ def nocov_wrapper? = true
38
+ def nocov_node? = false
39
+ def block_directive? = true
40
+
41
+ def inspect
42
+ "#<#{self.class} merge_type=#{@merge_type.inspect} node=#{@node.inspect}>"
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,219 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruby
4
+ module Merge
5
+ # Normalizes rescue clause ordering while preserving source-defined
6
+ # exception hierarchies and Ruby's broader-handler semantics.
7
+ # rubocop:disable Metrics/AbcSize, Metrics/ClassLength, Metrics/CyclomaticComplexity
8
+ # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
9
+ class RescueSemantics
10
+ def initialize(source_defined_exception_definitions: [])
11
+ @source_defined_exception_definitions = source_defined_exception_definitions
12
+ end
13
+
14
+ def merge_ordered_clause_types(primary_types, secondary_types)
15
+ ordered = primary_types.dup
16
+
17
+ secondary_types.each_with_index do |clause_type, secondary_index|
18
+ next if ordered.include?(clause_type)
19
+
20
+ previous_shared = secondary_types[0...secondary_index].reverse.find { |type| ordered.include?(type) }
21
+ next_shared = secondary_types[(secondary_index + 1)..]&.find { |type| ordered.include?(type) }
22
+
23
+ if previous_shared
24
+ insert_at = ordered.index(previous_shared) + 1
25
+ ordered.insert(insert_at, clause_type)
26
+ elsif next_shared
27
+ insert_at = ordered.index(next_shared)
28
+ ordered.insert(insert_at, clause_type)
29
+ else
30
+ ordered << clause_type
31
+ end
32
+ end
33
+
34
+ ordered
35
+ end
36
+
37
+ def canonicalize_rescue_clause_order(clause_types)
38
+ rescue_clause_types = clause_types.select { |clause_type| rescue_clause_type?(clause_type) }
39
+ return clause_types if rescue_clause_types.length < 2
40
+
41
+ ordered_rescue_types = rescue_clause_types.dup
42
+
43
+ if rescue_clause_types.any? { |clause_type| broad_rescue_clause_type?(clause_type) } &&
44
+ rescue_clause_types.any? { |clause_type| !broad_rescue_clause_type?(clause_type) }
45
+ specific_rescue_types = ordered_rescue_types.reject { |clause_type| broad_rescue_clause_type?(clause_type) }
46
+ broad_rescue_types = ordered_rescue_types.select { |clause_type| broad_rescue_clause_type?(clause_type) }
47
+ ordered_rescue_types = specific_rescue_types + broad_rescue_types
48
+ end
49
+
50
+ loop do
51
+ swapped = false
52
+
53
+ (0...(ordered_rescue_types.length - 1)).each do |index|
54
+ left_clause_type = ordered_rescue_types[index]
55
+ right_clause_type = ordered_rescue_types[index + 1]
56
+ next unless broader_rescue_clause_type_than?(left_clause_type, right_clause_type)
57
+
58
+ ordered_rescue_types[index] = right_clause_type
59
+ ordered_rescue_types[index + 1] = left_clause_type
60
+ swapped = true
61
+ end
62
+
63
+ break unless swapped
64
+ end
65
+
66
+ clause_types.map do |clause_type|
67
+ rescue_clause_type?(clause_type) ? ordered_rescue_types.shift : clause_type
68
+ end
69
+ end
70
+
71
+ def canonicalize_begin_clause_kind_order(clause_types)
72
+ clause_types.each_with_index
73
+ .sort_by { |(clause_type, index)| [clause_kind_sort_key(clause_type), index] }
74
+ .map(&:first)
75
+ end
76
+
77
+ def rescue_clause_signature(exception_names)
78
+ normalized_exceptions = Array(exception_names).filter_map { |exception| normalize_exception_name(exception) }
79
+ if normalized_exceptions.empty? || normalized_exceptions == ['StandardError']
80
+ [:standard_error]
81
+ else
82
+ normalized_exceptions.sort
83
+ end
84
+ end
85
+
86
+ def rescue_clause_type?(clause_type)
87
+ clause_type.is_a?(Array) && clause_type.first == :rescue_clause
88
+ end
89
+
90
+ def broad_rescue_clause_type?(clause_type)
91
+ rescue_clause_type?(clause_type) && clause_type[1] == [:standard_error]
92
+ end
93
+
94
+ def clause_kind_sort_key(clause_type)
95
+ return 0 if rescue_clause_type?(clause_type)
96
+ return 1 if clause_type == :else_clause
97
+ return 2 if clause_type == :ensure_clause
98
+
99
+ 3
100
+ end
101
+
102
+ def normalize_exception_name(exception_name)
103
+ return 'StandardError' if exception_name == :standard_error
104
+
105
+ name = exception_name.to_s.sub(/\A::/, '')
106
+ name.empty? ? nil : name
107
+ end
108
+
109
+ def qualify_source_constant_name(constant_name, namespace = nil)
110
+ normalized_name = normalize_exception_name(constant_name)
111
+ return if normalized_name.nil?
112
+ return normalized_name if constant_name.to_s.start_with?('::') || namespace.nil? || namespace.empty?
113
+
114
+ "#{namespace}::#{normalized_name}"
115
+ end
116
+
117
+ def source_defined_exception_hierarchy
118
+ @source_defined_exception_hierarchy ||= begin
119
+ definitions = @source_defined_exception_definitions
120
+ defined_names = definitions.map { |definition| definition[:name] }.compact.to_set
121
+
122
+ definitions.each_with_object({}) do |definition, hierarchy|
123
+ next unless definition[:name] && definition[:superclass]
124
+
125
+ superclass_name = if definition[:superclass].to_s.start_with?('::')
126
+ normalize_exception_name(definition[:superclass])
127
+ else
128
+ candidate_name = qualify_source_constant_name(definition[:superclass],
129
+ definition[:namespace])
130
+ if defined_names.include?(candidate_name)
131
+ candidate_name
132
+ else
133
+ normalize_exception_name(definition[:superclass])
134
+ end
135
+ end
136
+
137
+ hierarchy[definition[:name]] ||= superclass_name if superclass_name
138
+ end
139
+ end
140
+ end
141
+
142
+ def resolve_exception_constant(exception_name)
143
+ return ::StandardError if exception_name == :standard_error
144
+ return unless exception_name.is_a?(String) && !exception_name.empty?
145
+
146
+ exception_name.split('::').reject(&:empty?).inject(Object) { |scope, const_name| scope.const_get(const_name) }
147
+ rescue NameError
148
+ nil
149
+ end
150
+
151
+ def rescue_clause_exception_names(clause_type)
152
+ return [] unless rescue_clause_type?(clause_type)
153
+
154
+ Array(clause_type[1]).filter_map { |exception_name| normalize_exception_name(exception_name) }
155
+ end
156
+
157
+ def rescue_clause_exception_constants(clause_type)
158
+ rescue_clause_exception_names(clause_type).filter_map do |exception_name|
159
+ resolve_exception_constant(exception_name)
160
+ end
161
+ end
162
+
163
+ def exception_constant_covers?(covering_constant, covered_constant)
164
+ return true if covering_constant == covered_constant
165
+
166
+ covered_constant < covering_constant
167
+ rescue StandardError
168
+ false
169
+ end
170
+
171
+ def source_defined_exception_covers?(covering_name, covered_name)
172
+ normalized_covering = normalize_exception_name(covering_name)
173
+ current_name = normalize_exception_name(covered_name)
174
+ return false if normalized_covering.nil? || current_name.nil?
175
+ return true if normalized_covering == current_name
176
+
177
+ while (current_name = source_defined_exception_hierarchy[current_name])
178
+ return true if current_name == normalized_covering
179
+ end
180
+
181
+ false
182
+ end
183
+
184
+ def exception_name_covers?(covering_name, covered_name)
185
+ covering_constant = resolve_exception_constant(covering_name)
186
+ covered_constant = resolve_exception_constant(covered_name)
187
+
188
+ if covering_constant && covered_constant
189
+ exception_constant_covers?(covering_constant, covered_constant)
190
+ else
191
+ source_defined_exception_covers?(covering_name, covered_name)
192
+ end
193
+ end
194
+
195
+ def rescue_clause_covers?(covering_clause_type, covered_clause_type)
196
+ return false unless rescue_clause_type?(covering_clause_type) && rescue_clause_type?(covered_clause_type)
197
+
198
+ covering_names = rescue_clause_exception_names(covering_clause_type)
199
+ covered_names = rescue_clause_exception_names(covered_clause_type)
200
+ return false if covering_names.empty? || covered_names.empty?
201
+
202
+ covered_names.all? do |covered_name|
203
+ covering_names.any? do |covering_name|
204
+ exception_name_covers?(covering_name, covered_name)
205
+ end
206
+ end
207
+ end
208
+
209
+ def broader_rescue_clause_type_than?(left_clause_type, right_clause_type)
210
+ return false unless rescue_clause_type?(left_clause_type) && rescue_clause_type?(right_clause_type)
211
+
212
+ rescue_clause_covers?(left_clause_type, right_clause_type) &&
213
+ !rescue_clause_covers?(right_clause_type, left_clause_type)
214
+ end
215
+ end
216
+ # rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
217
+ # rubocop:enable Metrics/AbcSize, Metrics/ClassLength, Metrics/CyclomaticComplexity
218
+ end
219
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruby
4
+ module Merge
5
+ # Shared chunk anchors used when preserving project-specific Rake sections.
6
+ module ScaffoldChunkSupport
7
+ ChunkSpec = Struct.new(
8
+ :anchor_type,
9
+ :anchor_value,
10
+ :satellite_patterns,
11
+ :jaccard_threshold,
12
+ :max_lookahead,
13
+ :max_lookbehind,
14
+ keyword_init: true
15
+ )
16
+
17
+ BUNDLER_GEM_TASKS_SPEC = ChunkSpec.new(
18
+ anchor_type: :require_call,
19
+ anchor_value: 'bundler/gem_tasks',
20
+ satellite_patterns: [],
21
+ jaccard_threshold: 0.35,
22
+ max_lookahead: 0,
23
+ max_lookbehind: 0
24
+ )
25
+
26
+ RSPEC_SPEC = ChunkSpec.new(
27
+ anchor_type: :require_call,
28
+ anchor_value: 'rspec/core/rake_task',
29
+ satellite_patterns: ['RSpec::Core::RakeTask.new'],
30
+ jaccard_threshold: 0.35,
31
+ max_lookahead: 5,
32
+ max_lookbehind: 2
33
+ )
34
+
35
+ RUBOCOP_SPEC = ChunkSpec.new(
36
+ anchor_type: :require_call,
37
+ anchor_value: 'rubocop/rake_task',
38
+ satellite_patterns: ['RuboCop::RakeTask.new'],
39
+ jaccard_threshold: 0.35,
40
+ max_lookahead: 5,
41
+ max_lookbehind: 2
42
+ )
43
+
44
+ DEFAULT_TASK_SPEC = ChunkSpec.new(
45
+ anchor_type: :task_call,
46
+ anchor_value: 'default',
47
+ satellite_patterns: [],
48
+ jaccard_threshold: 0.35,
49
+ max_lookahead: 0,
50
+ max_lookbehind: 0
51
+ )
52
+
53
+ ALL_SPECS = [BUNDLER_GEM_TASKS_SPEC, RSPEC_SPEC, RUBOCOP_SPEC, DEFAULT_TASK_SPEC].freeze
54
+
55
+ module_function
56
+
57
+ def jaccard_tokens(text)
58
+ text.scan(/[A-Za-z0-9_]+/).to_set
59
+ end
60
+
61
+ def jaccard(a_set, b_set)
62
+ union = a_set | b_set
63
+ return 0.0 if union.empty?
64
+
65
+ (a_set & b_set).size.to_f / union.size
66
+ end
67
+
68
+ def task_anchor_match?(source, anchor_value, threshold)
69
+ node_tokens = jaccard_tokens(source.to_s)
70
+ pattern_tokens = jaccard_tokens("task #{anchor_value}")
71
+ jaccard(pattern_tokens, node_tokens) >= threshold
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruby
4
+ module Merge
5
+ # Stable structural signatures used by Ruby owner and method matching.
6
+ module SignatureSupport
7
+ module_function
8
+
9
+ def method_definition(name, params)
10
+ [:def, name, Array(params)]
11
+ end
12
+
13
+ def class_definition(constant_path)
14
+ [:class, constant_path]
15
+ end
16
+
17
+ def module_definition(constant_path)
18
+ [:module, constant_path]
19
+ end
20
+
21
+ def singleton_class(expression)
22
+ [:singleton_class, expression]
23
+ end
24
+
25
+ def constant(name)
26
+ [:const, name]
27
+ end
28
+
29
+ def variable_assignment(kind, name)
30
+ [kind, name]
31
+ end
32
+
33
+ def multi_write(targets)
34
+ [:multi_write, Array(targets)]
35
+ end
36
+
37
+ def conditional(kind, predicate)
38
+ [kind, predicate]
39
+ end
40
+
41
+ def case_statement(predicate)
42
+ [:case, predicate || '']
43
+ end
44
+
45
+ def case_match_statement(predicate)
46
+ [:case_match, predicate || '']
47
+ end
48
+
49
+ def loop_statement(kind, *parts)
50
+ [kind, *parts]
51
+ end
52
+
53
+ def begin_block(first_statement_preview)
54
+ [:begin, first_statement_preview || '']
55
+ end
56
+
57
+ def call(method_name, identifier, block: false)
58
+ [block ? :call_with_block : :call, method_name, identifier]
59
+ end
60
+
61
+ def super_call(block:)
62
+ [:super, block ? :with_block : :no_block]
63
+ end
64
+
65
+ def forwarding_super_call(block:)
66
+ [:forwarding_super, block ? :with_block : :no_block]
67
+ end
68
+
69
+ def call_operator_write(write_name, receiver)
70
+ [:call_op_write, write_name, receiver]
71
+ end
72
+
73
+ def lambda_literal(parameters_source)
74
+ [:lambda, parameters_source || '']
75
+ end
76
+
77
+ def execution_block(kind, line_number)
78
+ [kind, line_number]
79
+ end
80
+
81
+ def parenthesized(first_expression_preview)
82
+ [:parens, first_expression_preview || '']
83
+ end
84
+
85
+ def embedded(statements_source)
86
+ [:embedded, statements_source || '']
87
+ end
88
+
89
+ def other(class_name, line_number)
90
+ [:other, class_name, line_number]
91
+ end
92
+
93
+ def textual_method_signature(receiver_prefix, method_name)
94
+ "#{receiver_prefix}#{method_name}"
95
+ end
96
+ end
97
+ end
98
+ end
@@ -5,7 +5,7 @@ module Ruby
5
5
  # Version namespace for this gem.
6
6
  module Version
7
7
  # Current gem version.
8
- VERSION = '7.1.3'
8
+ VERSION = '7.1.4'
9
9
  end
10
10
  # Current gem version exposed at the traditional constant location.
11
11
  VERSION = Version::VERSION # Traditional Constant Location