rust-merge 7.1.1 → 7.1.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1dfc835a820ae028e33c90465264b92dc88cd4cdf34438a2f0ba6764cce2767f
4
- data.tar.gz: a93887e99883c9407d844fff137fd1481ba63ab0954c0323e5083239f6180d9e
3
+ metadata.gz: aef016d95324b777f2300aa31db682b1cb717ea3ade223d5f44058e0f8c42428
4
+ data.tar.gz: 2a97cb609a0a13efa790fec5a8a7bd76feff180babd2932c7751ed0d0bed3209
5
5
  SHA512:
6
- metadata.gz: 5c5322b6242de321539d64c67357d99c14de95da18869a06c1580d58dbc42b827589ae0d7e45ab58c0f8bfba66139f137eea095ae848f7bad7ec07bbbb425664
7
- data.tar.gz: 7be12db8ca09e406f25efbccdc4d72440955eecc1567694678b4798e09a9afd8d1da68eceef3de4e3e22c135820ce062b0c069b7a8a55ad6ab54cfd397001374
6
+ metadata.gz: 2fc3aa2c3a852d2bd196b2a963fda2b3f3a890e4cf6a27270b4311ab0bb70c7ff7d6357cd9ba8ed6b7859bd9b290d35bc0bfd2c2b43471955b602127b5bd74fe
7
+ data.tar.gz: 7ee67c2c39b1fad125389987ab0341de1f0fdf0e95d9cf3e4ed9627cc84c9f7d37ddbd15ad6b116569f951b1b0c0b1669612c5fe513cfc2da76b3905d2827cd2
checksums.yaml.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -45,7 +45,7 @@ I've summarized my thoughts in [this blog post](https://dev.to/galtzo/hostile-ta
45
45
 
46
46
  ### Compatibility
47
47
 
48
- Compatible with MRI Ruby 4.0.0+, and concordant releases of JRuby, and TruffleRuby.
48
+ Compatible with MRI Ruby 4.0.0+, JRuby, and TruffleRuby.
49
49
  CI workflows and Appraisals are generated for MRI Ruby 4.0.0+.
50
50
  This test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml` and
51
51
  may be higher than the gem's runtime compatibility floor when legacy Rubies are
@@ -119,20 +119,12 @@ See [SECURITY.md][🔐security].
119
119
  ## 🤝 Contributing
120
120
 
121
121
  If you need some ideas of where to help, you could work on adding more code coverage,
122
- or if it is already 💯 (see [below](#code-coverage)) check [issues][🤝gh-issues] or [PRs][🤝gh-pulls],
123
- or use the gem and think about how it could be better.
122
+ check [issues][🤝gh-issues] or [PRs][🤝gh-pulls], or use the gem and think about how it could be better.
124
123
 
125
124
  We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
126
125
 
127
126
  See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
128
127
 
129
- ### Code Coverage
130
-
131
- <details markdown="1">
132
- <summary>Coverage service badges</summary>
133
-
134
- </details>
135
-
136
128
  ## 📌 Versioning
137
129
 
138
130
  This library follows [![Semantic Versioning 2.0.0][📌semver-img]][📌semver] for its public API where practical.
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rust
4
+ module Merge
5
+ # Native TreeHaver analysis of safely attributable top-level Rust items.
6
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity -- one AST pass establishes ordered ownership and attribute/comment attachment
7
+ class FileAnalysis
8
+ attr_reader :source, :root_node, :declarations, :errors, :backend
9
+
10
+ def initialize(source)
11
+ @source = source
12
+ @backend = TREE_SITTER_BACKEND.id.to_sym
13
+ @errors = []
14
+ parse!
15
+ rescue StandardError => e
16
+ @root_node = nil
17
+ @declarations = [].freeze
18
+ @errors = [e].freeze
19
+ end
20
+
21
+ private
22
+
23
+ def parse!
24
+ @root_node = TreeHaver.parser_for(:rust, backend_type: :tree_sitter).parse(source).root_node
25
+ raise TreeHaver::NotAvailable, 'Rust parse returned no root node' unless root_node
26
+ raise TreeHaver::NotAvailable, 'Rust parse contains syntax errors' if root_node.has_error?
27
+
28
+ wrappers = []
29
+ pending = []
30
+ root_node.children.each do |node|
31
+ if attachable?(node)
32
+ if comment?(node) && trailing_comment?(wrappers.last, node)
33
+ wrappers.last.attach_trailing_comment!(node)
34
+ else
35
+ pending << node
36
+ end
37
+ next
38
+ end
39
+ if NodeWrapper::DECLARATION_TYPES.include?(node.type)
40
+ wrappers << NodeWrapper.new(node, source: source, leading_comments: attached_prefix(pending, node))
41
+ pending.clear
42
+ elsif globally_unmanaged?(node)
43
+ pending.clear
44
+ elsif node.named?
45
+ raise TreeHaver::NotAvailable, "Unmanaged top-level Rust node #{node.type.inspect}"
46
+ end
47
+ end
48
+ @declarations = wrappers.freeze
49
+ end
50
+
51
+ def attachable?(node)
52
+ comment?(node) || NodeWrapper::ATTRIBUTE_TYPES.include?(node.type)
53
+ end
54
+
55
+ def comment?(node) = NodeWrapper::COMMENT_TYPES.include?(node.type)
56
+
57
+ def globally_unmanaged?(node)
58
+ %w[inner_attribute_item shebang].include?(node.type)
59
+ end
60
+
61
+ def trailing_comment?(owner, comment)
62
+ owner && whitespace_only?(source.byteslice(owner.end_byte...comment.start_byte).to_s) &&
63
+ source.byteslice(owner.end_byte...comment.start_byte).to_s.count("\n").zero?
64
+ end
65
+
66
+ def attached_prefix(items, owner)
67
+ attached = []
68
+ cursor = owner.start_byte
69
+ items.reverse_each do |item|
70
+ gap = source.byteslice(item.end_byte...cursor).to_s
71
+ break unless whitespace_only?(gap)
72
+ break if gap.count("\n") > 1 && !structural_attribute?(item)
73
+
74
+ attached.unshift(item)
75
+ cursor = item.start_byte
76
+ end
77
+ attached.freeze
78
+ end
79
+
80
+ def structural_attribute?(item)
81
+ return true if NodeWrapper::ATTRIBUTE_TYPES.include?(item.type)
82
+
83
+ !find_descendant(item, 'outer_doc_comment_marker').nil?
84
+ end
85
+
86
+ def find_descendant(parent, type)
87
+ return parent if parent.type == type
88
+
89
+ parent.children.each do |child|
90
+ found = find_descendant(child, type)
91
+ return found if found
92
+ end
93
+ nil
94
+ end
95
+
96
+ def whitespace_only?(value)
97
+ value.each_byte.all? { |byte| [9, 10, 13, 32].include?(byte) }
98
+ end
99
+ end
100
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
101
+ end
102
+ end
@@ -0,0 +1,219 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rust
4
+ module Merge
5
+ # Structural identity and source-range adapter for one top-level Rust item.
6
+ # rubocop:disable Metrics/AbcSize, Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity -- Rust item forms share one structural identity boundary
7
+ class NodeWrapper
8
+ DECLARATION_TYPES = %w[
9
+ const_item
10
+ enum_item
11
+ expression_statement
12
+ extern_crate_declaration
13
+ function_item
14
+ impl_item
15
+ macro_definition
16
+ mod_item
17
+ static_item
18
+ struct_item
19
+ trait_item
20
+ type_item
21
+ union_item
22
+ use_declaration
23
+ ].freeze
24
+ NAMED_ITEMS = {
25
+ 'const_item' => [:const, %w[identifier]],
26
+ 'enum_item' => [:enum, %w[type_identifier]],
27
+ 'function_item' => [:function, %w[identifier]],
28
+ 'macro_definition' => [:macro, %w[identifier]],
29
+ 'mod_item' => [:module, %w[identifier]],
30
+ 'static_item' => [:static, %w[identifier]],
31
+ 'struct_item' => [:struct, %w[type_identifier]],
32
+ 'trait_item' => [:trait, %w[type_identifier]],
33
+ 'type_item' => [:type, %w[type_identifier]],
34
+ 'union_item' => [:union, %w[type_identifier]]
35
+ }.freeze
36
+ COMMENT_TYPES = %w[block_comment line_comment].freeze
37
+ ATTRIBUTE_TYPES = %w[attribute_item].freeze
38
+
39
+ attr_reader :node, :source, :leading_comments, :trailing_comments
40
+
41
+ def initialize(node, source:, leading_comments: [])
42
+ @node = node
43
+ @source = source
44
+ @leading_comments = leading_comments.freeze
45
+ @trailing_comments = []
46
+ end
47
+
48
+ def signature
49
+ value = case node.type
50
+ when 'use_declaration' then use_signature
51
+ when 'extern_crate_declaration' then extern_crate_signature
52
+ when 'impl_item' then impl_signature
53
+ when 'expression_statement' then macro_invocation_signature
54
+ else named_signature(node)
55
+ end
56
+ value.freeze
57
+ end
58
+
59
+ def membership_signature
60
+ return semantic_node(use_body) if compound_use?
61
+
62
+ signature
63
+ end
64
+
65
+ def compound? = compound_use?
66
+ def start_byte = leading_comments.first&.start_byte || node.start_byte
67
+ def end_byte = trailing_comments.last&.end_byte || node.end_byte
68
+ def start_line = line_for_byte(start_byte)
69
+ def end_line = line_for_byte(end_byte.zero? ? 0 : end_byte - 1)
70
+ def source_text = source.byteslice(start_byte...end_byte).to_s
71
+ def attach_trailing_comment!(comment) = trailing_comments << comment
72
+ def semantic_tree = semantic_node(node)
73
+
74
+ private
75
+
76
+ def named_signature(value)
77
+ kind, name_types = NAMED_ITEMS.fetch(value.type) do
78
+ raise ArgumentError, "Unsupported top-level Rust node #{value.type.inspect}"
79
+ end
80
+ name = direct_named_child(value, name_types)
81
+ raise ArgumentError, "#{value.type} has no structurally attributable name" unless name
82
+
83
+ [kind, text(name)]
84
+ end
85
+
86
+ def use_signature
87
+ body = use_body
88
+ return [:use_tree, use_tree_root(body)] if compound_use?
89
+
90
+ [:use, use_target(body)]
91
+ end
92
+
93
+ def use_body
94
+ node.children.find do |child|
95
+ child.named? && child.type != 'visibility_modifier'
96
+ end || raise(ArgumentError, 'use declaration has no structural tree')
97
+ end
98
+
99
+ def compound_use?
100
+ node.type == 'use_declaration' && !find_descendant(use_body, %w[use_list]).nil?
101
+ end
102
+
103
+ def use_tree_root(body)
104
+ return :root if body.type == 'use_list'
105
+
106
+ child = body.children.find { |candidate| candidate.named? && candidate.type != 'use_list' }
107
+ raise ArgumentError, 'compound use tree has no structural root' unless child
108
+
109
+ path_head(child)
110
+ end
111
+
112
+ def use_target(body)
113
+ case body.type
114
+ when 'use_as_clause'
115
+ target = body.children.find(&:named?)
116
+ raise ArgumentError, 'use alias has no structural target' unless target
117
+
118
+ path_head(target)
119
+ when 'scoped_identifier'
120
+ path_head(body)
121
+ when 'use_wildcard'
122
+ [:wildcard, path_head(body)]
123
+ when 'identifier', 'self', 'super', 'crate'
124
+ text(body)
125
+ else
126
+ [:tree, semantic_node(body)]
127
+ end
128
+ end
129
+
130
+ def extern_crate_signature
131
+ identifiers = node.children.select { |child| child.type == 'identifier' }
132
+ raise ArgumentError, 'extern crate declaration has no crate name' if identifiers.empty?
133
+
134
+ [:extern_crate, text(identifiers.first)]
135
+ end
136
+
137
+ def impl_signature
138
+ children = node.children
139
+ for_index = children.index { |child| child.type == 'for' }
140
+ type_nodes = children.select { |child| rust_type_node?(child) }
141
+ target = for_index ? children[(for_index + 1)..].find { |child| rust_type_node?(child) } : type_nodes.last
142
+ trait = for_index && children[...for_index].reverse.find { |child| rust_type_node?(child) }
143
+ raise ArgumentError, 'impl item has no structural target' unless target
144
+
145
+ polarity = children.any? { |child| child.type == '!' } ? :negative : :positive
146
+ [:impl, trait && path_head(trait), path_head(target), polarity, generic_arity]
147
+ end
148
+
149
+ def generic_arity
150
+ parameters = node.children.find { |child| child.type == 'type_parameters' }
151
+ return 0 unless parameters
152
+
153
+ parameters.children.count do |child|
154
+ %w[type_parameter lifetime_parameter const_parameter].include?(child.type)
155
+ end
156
+ end
157
+
158
+ def rust_type_node?(value)
159
+ return false unless value.named?
160
+
161
+ excluded = %w[type_parameters where_clause declaration_list visibility_modifier] +
162
+ COMMENT_TYPES + ATTRIBUTE_TYPES
163
+ !excluded.include?(value.type)
164
+ end
165
+
166
+ def macro_invocation_signature
167
+ invocation = node.children.find { |child| child.type == 'macro_invocation' }
168
+ raise ArgumentError, 'expression statement is not a top-level macro invocation' unless invocation
169
+
170
+ path = invocation.children.find do |child|
171
+ %w[identifier scoped_identifier].include?(child.type)
172
+ end
173
+ raise ArgumentError, 'macro invocation has no structural path' unless path
174
+
175
+ [:macro_invocation, path_head(path)]
176
+ end
177
+
178
+ def path_head(value)
179
+ case value.type
180
+ when 'generic_type', 'reference_type', 'pointer_type'
181
+ child = value.children.find(&:named?)
182
+ child ? path_head(child) : value.type
183
+ when 'scoped_identifier', 'scoped_type_identifier'
184
+ value.children.select(&:named?).map { |child| path_head(child) }
185
+ when 'use_wildcard'
186
+ child = value.children.find(&:named?)
187
+ child ? path_head(child) : :wildcard
188
+ else
189
+ text(value)
190
+ end
191
+ end
192
+
193
+ def direct_named_child(parent, types)
194
+ parent.children.find { |candidate| types.include?(candidate.type) }
195
+ end
196
+
197
+ def find_descendant(parent, types)
198
+ return parent if types.include?(parent.type)
199
+
200
+ parent.children.each do |child|
201
+ found = find_descendant(child, types)
202
+ return found if found
203
+ end
204
+ nil
205
+ end
206
+
207
+ def semantic_node(value)
208
+ children = value.children
209
+ return [value.type, text(value)] if children.empty?
210
+
211
+ [value.type, children.select(&:named?).map { |child| semantic_node(child) }]
212
+ end
213
+
214
+ def text(value) = source.byteslice(value.start_byte...value.end_byte).to_s
215
+ def line_for_byte(byte) = source.byteslice(0...byte).to_s.count("\n") + 1
216
+ end
217
+ # rubocop:enable Metrics/AbcSize, Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
218
+ end
219
+ end