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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/README.md +2 -10
- data/lib/go/merge/file_analysis.rb +90 -0
- data/lib/go/merge/node_wrapper.rb +174 -0
- data/lib/go/merge/provider.rb +956 -0
- data/lib/go/merge/version.rb +1 -1
- data/lib/go/merge.rb +39 -51
- data/sig/go/merge.rbs +44 -0
- data.tar.gz.sig +0 -0
- metadata +19 -16
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fc2818e672c1921f963df9cb173320cfe7a180ecd33a6c0a3b4d855fa76044a8
|
|
4
|
+
data.tar.gz: 239be77b2d5ff59d3d00d5e729f55361f13aa5c7e7d4cb0c9ff5c6c0efbeaa48
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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+,
|
|
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
|
-
|
|
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
|