typescript-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: 21ec9f6ce57ba276ee2cd6713ce86001d78c65b8c4aabade24693ef11bbf4f03
4
- data.tar.gz: 48a163ba332882e39cf8e67190d29ef4a01c1ede53a68c3126f0466b32f8ff4a
3
+ metadata.gz: 92374fc2c22a8142c444657938d8f099d4b8f6fd141ee84bb9a59817d5ff573c
4
+ data.tar.gz: 9caa1d69534ee9c62820e3c6c93244e5068667403bddfbead6cfefe23915f7ed
5
5
  SHA512:
6
- metadata.gz: d49657661f31655f72f27836e503529209465c037be78d323fb4606e78612a2b35061013ccf67d74589cba1f3c41b915e83d86e0963bc9a5ebb1d9e61675c869
7
- data.tar.gz: 8584890377190ece90ac9f04ce895c039b4062da51a1f08699b3c7d24e305d463e7d6dfa381be195664526cacecf384bdecb368a1a55adb416cf5d975769b581
6
+ metadata.gz: cf3322b809657af80cbe142168bf5e12dcbd253d7e98c671adf20a5eba537c172b68e1d43b16bfafa576ce268211279fb3507989cdd927a32406a9bc705c0841
7
+ data.tar.gz: 7ca821538482d3163f7aa3bbb6874116df64fbdb47d9b36584f62b479831b8ec5e3857a8d8c7bbecc0dc26d696d146eaec58084e1ec080be0381dd091ada611f
checksums.yaml.gz.sig CHANGED
@@ -1,3 +1 @@
1
- �`��5A.L����sQ�ڣ����~�d����z Z~]�h�����ֆ��ߠ�F~g� FX;���
2
- Ml-�$h��L*�֎��b�D��3a��Pؑ��4��“xOdݖ�׃�D3[�zȝ��X�r��
3
- ��O˻�=��Էi)�S8�6���1vb�B3(�6�&ޙ���ߤ(�a4�XY�$�l`�Z��VE8�@(�/xӜ�w'��.�+��q�Cz"@?RI^�8OKC�6D����p?Z��3�N��Q�+�##9f��2����q`
1
+ |ojH���BtH]��۠��6���F���A�g}[c7������LL�M�Q�>B�8��.4�� '�+!AU���l���Ǖ6������{�~�^�~C=PKm�V'��5 ^�1�]V�#4h^K& \���+3��J ���,fc��H7/\���:@BU�E���ɀ �aN�������j�Un)G:4��U�� cT���S��Z@<+Y�V �I�i�w ���`TG� [�F
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,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypeScript
4
+ module Merge
5
+ # Native TreeHaver analysis of safely attributable top-level TypeScript 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, :dialect
9
+
10
+ def initialize(source, dialect: :typescript)
11
+ @source = source
12
+ @dialect = dialect.to_sym
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
+ language = dialect == :tsx ? :tsx : :typescript
26
+ @root_node = TreeHaver.parser_for(language, backend_type: :tree_sitter).parse(source).root_node
27
+ raise TreeHaver::NotAvailable, 'TypeScript parse returned no root node' unless root_node
28
+ raise TreeHaver::NotAvailable, 'TypeScript 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
+ raise TreeHaver::NotAvailable, "Unmanaged top-level TypeScript node #{node.type.inspect}" if node.named?
43
+
44
+ pending_comments.clear
45
+ next
46
+ end
47
+
48
+ wrappers << NodeWrapper.new(node, source: source, leading_comments: attached_comments(pending_comments, node))
49
+ pending_comments.clear
50
+ end
51
+ @declarations = wrappers.freeze
52
+ end
53
+
54
+ def trailing_comment?(owner, comment)
55
+ owner && whitespace_only?(source.byteslice(owner.end_byte...comment.start_byte).to_s) &&
56
+ source.byteslice(owner.end_byte...comment.start_byte).to_s.count("\n").zero?
57
+ end
58
+
59
+ def attached_comments(comments, owner)
60
+ attached = []
61
+ cursor = owner.start_byte
62
+ comments.reverse_each do |comment|
63
+ gap = source.byteslice(comment.end_byte...cursor).to_s
64
+ break unless whitespace_only?(gap) && gap.count("\n") <= 1
65
+
66
+ attached.unshift(comment)
67
+ cursor = comment.start_byte
68
+ end
69
+ attached.freeze
70
+ end
71
+
72
+ def whitespace_only?(value)
73
+ value.each_byte.all? { |byte| [9, 10, 13, 32].include?(byte) }
74
+ end
75
+ end
76
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
77
+ end
78
+ end
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypeScript
4
+ module Merge
5
+ # Structural identity and source-range adapter for one top-level TypeScript owner.
6
+ # rubocop:disable Metrics/AbcSize, Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity -- TypeScript declaration forms share one structural identity boundary
7
+ class NodeWrapper
8
+ DECLARATION_TYPES = %w[
9
+ ambient_declaration
10
+ class_declaration
11
+ enum_declaration
12
+ expression_statement
13
+ export_statement
14
+ function_declaration
15
+ function_signature
16
+ import_statement
17
+ interface_declaration
18
+ internal_module
19
+ lexical_declaration
20
+ type_alias_declaration
21
+ variable_declaration
22
+ ].freeze
23
+ NAMED_DECLARATIONS = {
24
+ 'class_declaration' => [:class, %w[type_identifier identifier]],
25
+ 'enum_declaration' => [:enum, %w[identifier type_identifier]],
26
+ 'interface_declaration' => [:interface, %w[type_identifier identifier]],
27
+ 'type_alias_declaration' => [:type, %w[type_identifier identifier]],
28
+ 'internal_module' => [:namespace, %w[identifier string nested_identifier]],
29
+ 'function_declaration' => [:function, %w[identifier]],
30
+ 'function_signature' => [:function, %w[identifier]]
31
+ }.freeze
32
+
33
+ attr_reader :node, :source, :leading_comments, :trailing_comments
34
+
35
+ def initialize(node, source:, leading_comments: [])
36
+ @node = node
37
+ @source = source
38
+ @leading_comments = leading_comments.freeze
39
+ @trailing_comments = []
40
+ end
41
+
42
+ def signature
43
+ return expression_statement_signature if node.type == 'expression_statement'
44
+ return import_signature if node.type == 'import_statement'
45
+ return export_signature if node.type == 'export_statement'
46
+ return variable_signature(node) if variable_declaration?(node)
47
+ return ambient_signature if node.type == 'ambient_declaration'
48
+
49
+ named_signature(node)
50
+ end
51
+
52
+ def membership_signature
53
+ signature
54
+ end
55
+
56
+ def grouped? = variable_declaration?(node)
57
+
58
+ def start_byte = leading_comments.first&.start_byte || node.start_byte
59
+ def end_byte = trailing_comments.last&.end_byte || node.end_byte
60
+ def start_line = line_for_byte(start_byte)
61
+ def end_line = line_for_byte(end_byte.zero? ? 0 : end_byte - 1)
62
+ def source_text = source.byteslice(start_byte...end_byte).to_s
63
+ def attach_trailing_comment!(comment) = trailing_comments << comment
64
+ def semantic_tree = semantic_node(node)
65
+
66
+ private
67
+
68
+ def expression_statement_signature
69
+ declaration = node.children.find { |child| NAMED_DECLARATIONS.key?(child.type) }
70
+ return named_signature(declaration) if declaration
71
+
72
+ call = direct_child(node, 'call_expression')
73
+ raise ArgumentError, 'expression statement is not a supported top-level call' unless call
74
+
75
+ callee = call.children.find(&:named?)
76
+ unless callee && callee.type == 'identifier'
77
+ raise ArgumentError, 'top-level call has no safely attributable named callee'
78
+ end
79
+
80
+ arguments = direct_child(call, 'arguments')
81
+ identity = arguments&.children&.find(&:named?)
82
+ unless identity && identity.type == 'string'
83
+ raise ArgumentError, 'top-level call has no literal string identity'
84
+ end
85
+
86
+ [:call, text(callee), text(identity)]
87
+ end
88
+
89
+ def import_signature
90
+ source_node = direct_child(node, 'string') || find_descendant(node, %w[string])
91
+ raise ArgumentError, 'import_statement has no structural module source' unless source_node
92
+
93
+ return [:import, text(source_node), :import_equals] if direct_child(node, 'import_require_clause')
94
+
95
+ clause = direct_child(node, 'import_clause')
96
+ type_only = !direct_child(node, 'type').nil?
97
+ [:import, text(source_node), type_only ? :type : :value, import_form(clause)]
98
+ end
99
+
100
+ def export_signature
101
+ default = node.children.any? { |child| child.type == 'default' }
102
+ return [:default_export] if default
103
+
104
+ declaration = node.children.find { |child| DECLARATION_TYPES.include?(child.type) }
105
+ return [:export_declaration, declaration_signature(declaration)] if declaration
106
+
107
+ source_node = direct_child(node, 'string')
108
+ type_only = !direct_child(node, 'type').nil?
109
+ if source_node
110
+ return [:export_from, text(source_node), type_only ? :type : :value, export_form]
111
+ end
112
+ return [:export_local, type_only ? :type : :value, :named] if direct_child(node, 'export_clause')
113
+ return [:export_assignment] if node.children.any? { |child| child.type == '=' }
114
+ return [:export_as_namespace] if node.children.any? { |child| child.type == 'namespace' }
115
+
116
+ [:export, semantic_node(node)]
117
+ end
118
+
119
+ def import_form(clause)
120
+ return :side_effect unless clause
121
+
122
+ child_types = clause.children.select(&:named?).map(&:type)
123
+ default = child_types.include?('identifier')
124
+ named = child_types.include?('named_imports')
125
+ namespace = child_types.include?('namespace_import')
126
+ return :default_named if default && named
127
+ return :default_namespace if default && namespace
128
+ return :default if default
129
+ return :named if named
130
+ return :namespace if namespace
131
+
132
+ raise ArgumentError, "unsupported structural import clause #{child_types.inspect}"
133
+ end
134
+
135
+ def export_form
136
+ return :named if direct_child(node, 'export_clause')
137
+ return :namespace if direct_child(node, 'namespace_export')
138
+ return :all if node.children.any? { |child| child.type == '*' }
139
+
140
+ raise ArgumentError, 'unsupported structural export-from form'
141
+ end
142
+
143
+ def declaration_signature(declaration)
144
+ return variable_signature(declaration) if variable_declaration?(declaration)
145
+ return ambient_signature(declaration) if declaration.type == 'ambient_declaration'
146
+
147
+ named_signature(declaration)
148
+ end
149
+
150
+ def named_signature(value)
151
+ kind, name_types = NAMED_DECLARATIONS.fetch(value.type) do
152
+ raise ArgumentError, "Unsupported top-level TypeScript node #{value.type.inspect}"
153
+ end
154
+ name = direct_named_child_text(value, name_types)
155
+ raise ArgumentError, "#{value.type} has no structurally attributable name" unless name
156
+
157
+ [kind, name]
158
+ end
159
+
160
+ def variable_signature(value)
161
+ declarators = value.children.select { |child| child.type == 'variable_declarator' }
162
+ names = declarators.map do |declarator|
163
+ name = declarator.children.find(&:named?)
164
+ raise ArgumentError, 'variable_declarator has no binding' unless name
165
+ raise ArgumentError, "unsupported variable binding #{name.type}" unless %w[identifier].include?(name.type)
166
+
167
+ text(name)
168
+ end
169
+ raise ArgumentError, "#{value.type} has no variable declarators" if names.empty?
170
+
171
+ keyword = value.children.find { |child| %w[const let var].include?(child.type) }
172
+ [:variables, keyword&.type, names.freeze]
173
+ end
174
+
175
+ def ambient_signature(value = node)
176
+ declaration = value.children.find { |child| child.named? && child.type != 'declare' }
177
+ raise ArgumentError, 'ambient_declaration has no declaration' unless declaration
178
+
179
+ if declaration.type == 'module'
180
+ name = declaration.children.find { |child| %w[string identifier nested_identifier].include?(child.type) }
181
+ raise ArgumentError, 'module augmentation has no structural name' unless name
182
+
183
+ return [:module_augmentation, text(name)]
184
+ end
185
+
186
+ [:ambient, named_signature(declaration)]
187
+ end
188
+
189
+ def variable_declaration?(value)
190
+ %w[lexical_declaration variable_declaration].include?(value.type)
191
+ end
192
+
193
+ def direct_child(parent, type)
194
+ parent.children.find { |child| child.type == type }
195
+ end
196
+
197
+ def direct_named_child_text(parent, types)
198
+ child = parent.children.find { |candidate| types.include?(candidate.type) }
199
+ child && text(child)
200
+ end
201
+
202
+ def find_descendant(parent, types)
203
+ return parent if types.include?(parent.type)
204
+
205
+ parent.children.each do |child|
206
+ found = find_descendant(child, types)
207
+ return found if found
208
+ end
209
+ nil
210
+ end
211
+
212
+ def semantic_node(value)
213
+ children = value.children
214
+ return [value.type, text(value)] if children.empty?
215
+
216
+ [value.type, children.select(&:named?).map { |child| semantic_node(child) }]
217
+ end
218
+
219
+ def text(value) = source.byteslice(value.start_byte...value.end_byte).to_s
220
+ def line_for_byte(byte) = source.byteslice(0...byte).to_s.count("\n") + 1
221
+ end
222
+ # rubocop:enable Metrics/AbcSize, Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
223
+ end
224
+ end