go-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: 5b0b435b61118c6713db06822163d8580bb16efa4d6b0c062607bbabdfc9072d
4
- data.tar.gz: 598971a08e823d4766f33fe24fd6b57adefc71c60667e98acb2456177e722c7c
3
+ metadata.gz: fc2818e672c1921f963df9cb173320cfe7a180ecd33a6c0a3b4d855fa76044a8
4
+ data.tar.gz: 239be77b2d5ff59d3d00d5e729f55361f13aa5c7e7d4cb0c9ff5c6c0efbeaa48
5
5
  SHA512:
6
- metadata.gz: bf5053390f52d5f671a1a1c936c46f711aef088a7f1596dc9c7ca909468d997f838008c71f0f422428ba50b7fea754b2caed65ce88862bd0912095a9e0057883
7
- data.tar.gz: 70d589fb1ad395149a1e0968b6af7b835f95f1bf59c89190e2aef27c701a17a87dee356a166f47bad9eb4cfb4ec8b512be18406e6ce7b66cc34e2e576f73d472
6
+ metadata.gz: 933ef214c37510ad01f07b2a305b06a23d7d0b8061e8d7ebb656eab06415eefe08064a5ce8d9257f72540caa3eceec8c81a052a871989eb6779dc2fb671650b9
7
+ data.tar.gz: add66ed0b4da849c4723780e316a256bc9cfcc7a9ac5bcd965ffc7f0b71f1f8cacd34a3951bdab12b4ce63d4571dc6803c1554badc03c479f88e64f05f830f04
checksums.yaml.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -46,7 +46,7 @@ I've summarized my thoughts in [this blog post](https://dev.to/galtzo/hostile-ta
46
46
 
47
47
  ### Compatibility
48
48
 
49
- Compatible with MRI Ruby 4.0.0+, and concordant releases of JRuby, and TruffleRuby.
49
+ Compatible with MRI Ruby 4.0.0+, JRuby, and TruffleRuby.
50
50
  CI workflows and Appraisals are generated for MRI Ruby 4.0.0+.
51
51
  This test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml` and
52
52
  may be higher than the gem's runtime compatibility floor when legacy Rubies are
@@ -120,20 +120,12 @@ See [SECURITY.md][🔐security].
120
120
  ## 🤝 Contributing
121
121
 
122
122
  If you need some ideas of where to help, you could work on adding more code coverage,
123
- or if it is already 💯 (see [below](#code-coverage)) check [issues][🤝gh-issues] or [PRs][🤝gh-pulls],
124
- or use the gem and think about how it could be better.
123
+ check [issues][🤝gh-issues] or [PRs][🤝gh-pulls], or use the gem and think about how it could be better.
125
124
 
126
125
  We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
127
126
 
128
127
  See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
129
128
 
130
- ### Code Coverage
131
-
132
- <details markdown="1">
133
- <summary>Coverage service badges</summary>
134
-
135
- </details>
136
-
137
129
  ## 📌 Versioning
138
130
 
139
131
  This library follows [![Semantic Versioning 2.0.0][📌semver-img]][📌semver] for its public API where practical.
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Go
4
+ module Merge
5
+ # Native TreeHaver analysis of safely attributable top-level Go source.
6
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity -- one AST pass establishes ordered ownership and comment attachment
7
+ class FileAnalysis
8
+ attr_reader :source, :root_node, :declarations, :errors, :backend
9
+
10
+ def initialize(source, require_package: true)
11
+ @source = source
12
+ @require_package = require_package
13
+ @backend = TREE_SITTER_BACKEND.id.to_sym
14
+ @errors = []
15
+ parse!
16
+ rescue StandardError => e
17
+ @root_node = nil
18
+ @declarations = [].freeze
19
+ @errors = [e].freeze
20
+ end
21
+
22
+ private
23
+
24
+ def parse!
25
+ parser = TreeHaver.parser_for(:go, backend_type: :tree_sitter)
26
+ @root_node = parser.parse(source).root_node
27
+ raise TreeHaver::NotAvailable, 'Go parse returned no root node' unless root_node
28
+ raise TreeHaver::NotAvailable, 'Go parse contains syntax errors' if root_node.has_error?
29
+
30
+ wrappers = []
31
+ pending_comments = []
32
+ root_node.children.each do |node|
33
+ if node.type == 'comment'
34
+ if trailing_comment?(wrappers.last, node)
35
+ wrappers.last.attach_trailing_comment!(node)
36
+ else
37
+ pending_comments << node
38
+ end
39
+ next
40
+ end
41
+ unless NodeWrapper::DECLARATION_TYPES.include?(node.type)
42
+ pending_comments.clear
43
+ next
44
+ end
45
+
46
+ comments = attached_comments(pending_comments, node)
47
+ wrappers << NodeWrapper.new(node, source: source, leading_comments: comments)
48
+ pending_comments.clear
49
+ end
50
+ validate_package!(wrappers) if @require_package
51
+ @declarations = wrappers.freeze
52
+ end
53
+
54
+ def trailing_comment?(owner, comment)
55
+ return false unless owner
56
+
57
+ gap = source.byteslice(owner.end_byte...comment.start_byte).to_s
58
+ whitespace_only?(gap) && gap.count("\n").zero?
59
+ end
60
+
61
+ def attached_comments(comments, owner)
62
+ return [] if comments.empty?
63
+
64
+ attached = []
65
+ cursor = owner.start_byte
66
+ comments.reverse_each do |comment|
67
+ gap = source.byteslice(comment.end_byte...cursor).to_s
68
+ break unless whitespace_only?(gap) && gap.count("\n") <= 1
69
+
70
+ attached.unshift(comment)
71
+ cursor = comment.start_byte
72
+ end
73
+ attached.freeze
74
+ end
75
+
76
+ def validate_package!(wrappers)
77
+ packages = wrappers.select { |wrapper| wrapper.node.type == 'package_clause' }
78
+ raise TreeHaver::NotAvailable, 'Go source must contain exactly one package clause' unless packages.length == 1
79
+ return if wrappers.first.equal?(packages.first)
80
+
81
+ raise TreeHaver::NotAvailable, 'Go package clause must be the first owned top-level construct'
82
+ end
83
+
84
+ def whitespace_only?(value)
85
+ value.each_byte.all? { |byte| [9, 10, 13, 32].include?(byte) }
86
+ end
87
+ end
88
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
89
+ end
90
+ end
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Go
4
+ module Merge
5
+ # Structural identity and source-range adapter for a top-level Go AST node.
6
+ # rubocop:disable Metrics/AbcSize, Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/MethodLength -- Go declaration forms share one structural identity boundary
7
+ class NodeWrapper
8
+ DECLARATION_TYPES = %w[
9
+ const_declaration
10
+ function_declaration
11
+ import_declaration
12
+ method_declaration
13
+ package_clause
14
+ type_declaration
15
+ var_declaration
16
+ ].freeze
17
+ GROUP_SPEC_TYPES = %w[const_spec import_spec type_spec var_spec].freeze
18
+
19
+ attr_reader :node, :source, :leading_comments, :trailing_comments
20
+
21
+ def initialize(node, source:, leading_comments: [])
22
+ @node = node
23
+ @source = source
24
+ @leading_comments = leading_comments.freeze
25
+ @trailing_comments = []
26
+ end
27
+
28
+ def signature
29
+ case node.type
30
+ when 'package_clause'
31
+ [:package, required_descendant_text(node, 'package_identifier')]
32
+ when 'import_declaration'
33
+ import_signature
34
+ when 'function_declaration'
35
+ [:function, required_direct_child_text(node, 'identifier')]
36
+ when 'method_declaration'
37
+ [:method, receiver_type_name, required_direct_child_text(node, 'field_identifier')]
38
+ when 'type_declaration'
39
+ declaration_signature(:type, 'type_spec', 'type_identifier')
40
+ when 'const_declaration'
41
+ declaration_signature(:const, 'const_spec', 'identifier')
42
+ when 'var_declaration'
43
+ declaration_signature(:var, 'var_spec', 'identifier')
44
+ else
45
+ raise ArgumentError, "Unsupported top-level Go node #{node.type.inspect}"
46
+ end.freeze
47
+ end
48
+
49
+ def grouped?
50
+ case node.type
51
+ when 'import_declaration'
52
+ !find_descendant(node, ['import_spec_list']).nil?
53
+ when 'type_declaration', 'const_declaration'
54
+ node.children.any? { |child| child.type == '(' }
55
+ when 'var_declaration'
56
+ !find_descendant(node, ['var_spec_list']).nil?
57
+ else
58
+ false
59
+ end
60
+ end
61
+
62
+ def start_byte
63
+ leading_comments.first&.start_byte || node.start_byte
64
+ end
65
+
66
+ def attach_trailing_comment!(comment)
67
+ trailing_comments << comment
68
+ end
69
+
70
+ def end_byte = trailing_comments.last&.end_byte || node.end_byte
71
+ def start_line = line_for_byte(start_byte)
72
+ def end_line = line_for_byte(end_byte.zero? ? 0 : end_byte - 1)
73
+ def source_text = source.byteslice(start_byte...end_byte).to_s
74
+
75
+ def semantic_tree
76
+ semantic_node(node)
77
+ end
78
+
79
+ private
80
+
81
+ def import_signature
82
+ specs = descendants(node, 'import_spec')
83
+ values = specs.map do |spec|
84
+ literal = required_descendant(spec, 'interpreted_string_literal', 'raw_string_literal')
85
+ alias_node = spec.children.find do |child|
86
+ %w[package_identifier dot blank_identifier].include?(child.type)
87
+ end
88
+ [alias_node ? text(alias_node) : nil, import_path(literal)]
89
+ end
90
+ return [:import_group, values.sort_by { |value| value.map(&:to_s) }.freeze] if grouped?
91
+
92
+ [:import, values.fetch(0)]
93
+ end
94
+
95
+ def import_path(literal)
96
+ content = literal.children.find do |child|
97
+ %w[interpreted_string_literal_content raw_string_literal_content].include?(child.type)
98
+ end
99
+ return text(content) if content
100
+
101
+ literal_text = text(literal)
102
+ literal_text.byteslice(1...-1).to_s
103
+ end
104
+
105
+ def receiver_type_name
106
+ receiver = node.children.find { |child| child.type == 'parameter_list' }
107
+ parameter = required_descendant(receiver, 'parameter_declaration')
108
+ required_descendant_text(parameter, 'type_identifier')
109
+ end
110
+
111
+ def declaration_signature(kind, spec_type, identifier_type)
112
+ specs = descendants(node, spec_type)
113
+ member_names = specs.map do |spec|
114
+ spec.children.select { |child| child.type == identifier_type }.map { |child| text(child) }
115
+ end
116
+ raise ArgumentError, "#{node.type} has no declared name" if member_names.flatten.empty?
117
+ return [:"#{kind}_group", member_names.map(&:freeze).freeze] if grouped?
118
+
119
+ names = member_names.flatten
120
+ [kind, names.length == 1 ? names.first : names.freeze]
121
+ end
122
+
123
+ def required_direct_child_text(parent, type)
124
+ child = parent.children.find { |candidate| candidate.type == type }
125
+ raise ArgumentError, "#{parent.type} has no #{type}" unless child
126
+
127
+ text(child)
128
+ end
129
+
130
+ def required_descendant_text(parent, *types)
131
+ text(required_descendant(parent, *types))
132
+ end
133
+
134
+ def required_descendant(parent, *types)
135
+ found = find_descendant(parent, types)
136
+ raise ArgumentError, "#{parent.type} has no #{types.join(' or ')}" unless found
137
+
138
+ found
139
+ end
140
+
141
+ def find_descendant(parent, types)
142
+ return parent if types.include?(parent.type)
143
+
144
+ parent.children.each do |child|
145
+ found = find_descendant(child, types)
146
+ return found if found
147
+ end
148
+ nil
149
+ end
150
+
151
+ def descendants(parent, type, found = [])
152
+ found << parent if parent.type == type
153
+ parent.children.each { |child| descendants(child, type, found) }
154
+ found
155
+ end
156
+
157
+ def semantic_node(value)
158
+ children = value.children
159
+ return [value.type, text(value)] if children.empty?
160
+
161
+ [value.type, children.select(&:named?).map { |child| semantic_node(child) }]
162
+ end
163
+
164
+ def text(value)
165
+ source.byteslice(value.start_byte...value.end_byte).to_s
166
+ end
167
+
168
+ def line_for_byte(byte)
169
+ source.byteslice(0...byte).to_s.count("\n") + 1
170
+ end
171
+ end
172
+ # rubocop:enable Metrics/AbcSize, Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/MethodLength
173
+ end
174
+ end