typescript-merge 7.0.0 → 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.
@@ -2,10 +2,12 @@
2
2
 
3
3
  module TypeScript
4
4
  module Merge
5
+ # Version namespace for this gem.
5
6
  module Version
6
- VERSION = "7.0.0"
7
+ # Current gem version.
8
+ VERSION = '7.1.3'
7
9
  end
8
-
9
- VERSION = Version::VERSION
10
+ # Current gem version exposed at the traditional constant location.
11
+ VERSION = Version::VERSION # Traditional Constant Location
10
12
  end
11
13
  end
@@ -1,27 +1,58 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "tree_haver"
4
- require "ast/merge"
3
+ require 'tree_haver'
4
+ require 'ast/merge'
5
+ require_relative 'merge/version'
5
6
 
6
7
  module TypeScript
7
8
  module Merge
8
- extend self
9
+ module_function
9
10
 
10
- PACKAGE_NAME = "typescript-merge"
11
+ PACKAGE_NAME = 'typescript-merge'
11
12
  TREE_SITTER_BACKEND = TreeHaver::KREUZBERG_LANGUAGE_PACK_BACKEND
12
- DESTINATION_WINS_ARRAY_POLICY = { surface: "array", name: "destination_wins_array" }.freeze
13
+ DESTINATION_WINS_ARRAY_POLICY = { surface: 'array', name: 'destination_wins_array' }.freeze
14
+ BACKEND_REGISTRY = Struct.new(:registered, :mutex).new(false, Mutex.new)
15
+
16
+ autoload :NodeWrapper, 'typescript/merge/node_wrapper'
17
+ autoload :FileAnalysis, 'typescript/merge/file_analysis'
18
+ autoload :Provider, 'typescript/merge/provider'
19
+
20
+ def register_backend!
21
+ BACKEND_REGISTRY.mutex.synchronize do
22
+ return if BACKEND_REGISTRY.registered
23
+
24
+ TreeHaver::BackendRegistry.register(TREE_SITTER_BACKEND)
25
+
26
+ grammar_finder = TreeHaver::GrammarFinder.new(:typescript)
27
+ grammar_finder.register! if grammar_finder.available?
28
+ TreeHaver.register_language(
29
+ :tsx,
30
+ backend_module: TreeHaver::Backends::Tslp,
31
+ backend_type: :tslp,
32
+ gem_name: 'tree_sitter_language_pack'
33
+ )
34
+
35
+ BACKEND_REGISTRY.registered = true
36
+ end
37
+ end
13
38
 
14
39
  def type_script_feature_profile
15
- { family: "typescript", supported_dialects: ["typescript"], supported_policies: [DESTINATION_WINS_ARRAY_POLICY] }
40
+ {
41
+ family: 'typescript',
42
+ supported_dialects: %w[typescript tsx],
43
+ supported_policies: [DESTINATION_WINS_ARRAY_POLICY]
44
+ }
16
45
  end
17
46
 
18
47
  def available_type_script_backends
19
- [TREE_SITTER_BACKEND]
48
+ type_script_backend_available_for_analysis?(TREE_SITTER_BACKEND.id) ? [TREE_SITTER_BACKEND] : []
20
49
  end
21
50
 
22
51
  def type_script_backend_feature_profile(backend: nil)
23
- requested = backend.to_s.empty? ? TREE_SITTER_BACKEND.id : backend.to_s
24
- return unsupported_feature_result("Unsupported TypeScript backend #{requested}.") unless requested == TREE_SITTER_BACKEND.id
52
+ requested = requested_tree_sitter_backend_id(backend)
53
+ unless type_script_backend_available_for_analysis?(requested)
54
+ return unsupported_feature_result("Unsupported TypeScript backend #{requested}.")
55
+ end
25
56
 
26
57
  type_script_feature_profile.merge(
27
58
  backend: requested,
@@ -45,101 +76,163 @@ module TypeScript
45
76
  end
46
77
 
47
78
  def parse_type_script(source, dialect)
48
- requested = TREE_SITTER_BACKEND.id
49
- return unsupported_feature_result("Unsupported TypeScript backend #{requested}.") unless requested == TREE_SITTER_BACKEND.id
50
- return analyze_type_script_module(source) if dialect == "typescript"
79
+ unless type_script_backend_available_for_analysis?(nil)
80
+ diagnostic_backend = TreeHaver.current_backend_id || 'tree-sitter'
81
+ return unsupported_feature_result("Unsupported TypeScript backend #{diagnostic_backend}.")
82
+ end
83
+ return analyze_type_script_module(source, dialect: dialect) if %w[typescript tsx].include?(dialect)
84
+
85
+ { ok: false,
86
+ diagnostics: [{ severity: 'error', category: 'unsupported_feature', message: "Unsupported TypeScript dialect #{dialect}." }], policies: [] }
87
+ end
51
88
 
52
- { ok: false, diagnostics: [{ severity: "error", category: "unsupported_feature", message: "Unsupported TypeScript dialect #{dialect}." }], policies: [] }
89
+ def type_script_backend_available_for_analysis?(backend_id)
90
+ register_backend!
91
+
92
+ if backend_id.to_s.empty?
93
+ TreeHaver.parser_for(:typescript, backend_type: :tree_sitter)
94
+ else
95
+ TreeHaver.with_backend(backend_id) { TreeHaver.parser_for(:typescript, backend_type: :tree_sitter) }
96
+ end
97
+ true
98
+ rescue TreeHaver::Error, ArgumentError
99
+ false
53
100
  end
54
101
 
102
+ def requested_tree_sitter_backend_id(backend)
103
+ return backend.to_s unless backend.to_s.empty?
104
+
105
+ contextual = TreeHaver.current_backend_id || ENV['TREE_HAVER_BACKEND']
106
+ contextual.to_s.empty? || contextual.to_s == 'auto' ? TREE_SITTER_BACKEND.id : contextual.to_s
107
+ end
108
+ private_class_method :requested_tree_sitter_backend_id
109
+
55
110
  def match_type_script_owners(template, destination)
56
- destination_paths = destination[:owners].to_h { |owner| [owner[:path], true] }
57
- template_paths = template[:owners].to_h { |owner| [owner[:path], true] }
58
- {
59
- matched: template[:owners].filter { |owner| destination_paths[owner[:path]] }.map { |owner| { template_path: owner[:path], destination_path: owner[:path] } },
60
- unmatched_template: template[:owners].map { |owner| owner[:path] }.reject { |path| destination_paths[path] },
61
- unmatched_destination: destination[:owners].map { |owner| owner[:path] }.reject { |path| template_paths[path] }
62
- }
111
+ Ast::Merge::OwnerSelection.match_by_path(template, destination)
112
+ end
113
+
114
+ def merge_provider
115
+ @merge_provider ||= Provider.new
116
+ end
117
+
118
+ def register_provider!(replace: false)
119
+ return unless Ast::Merge.respond_to?(:register_provider)
120
+
121
+ Ast::Merge.register_provider(merge_provider, replace: replace)
63
122
  end
64
123
 
65
124
  def merge_type_script(template_source, destination_source, dialect)
66
125
  template = parse_type_script(template_source, dialect)
67
126
  return { ok: false, diagnostics: template[:diagnostics], policies: [] } unless template[:ok]
127
+
68
128
  destination = parse_type_script(destination_source, dialect)
69
129
  unless destination[:ok]
70
130
  return {
71
131
  ok: false,
72
- diagnostics: destination[:diagnostics].map { |diagnostic| diagnostic[:category] == "parse_error" ? diagnostic.merge(category: "destination_parse_error") : diagnostic },
132
+ diagnostics: destination[:diagnostics].map do |diagnostic|
133
+ diagnostic[:category] == 'parse_error' ? diagnostic.merge(category: 'destination_parse_error') : diagnostic
134
+ end,
73
135
  policies: []
74
136
  }
75
137
  end
76
138
 
77
139
  destination_declarations = destination.dig(:analysis, :declarations).to_h { |item| [item[:path], item] }
78
140
  merged_declaration_texts = destination.dig(:analysis, :declarations).map { |item| item[:text] } +
79
- template.dig(:analysis, :declarations).reject { |item| destination_declarations[item[:path]] }.map { |item| item[:text] }
141
+ template.dig(:analysis, :declarations).reject do |item|
142
+ destination_declarations[item[:path]]
143
+ end.map { |item| item[:text] }
80
144
  import_block = destination.dig(:analysis, :imports).map { |item| item[:text] }.join
81
145
  declaration_block = merged_declaration_texts.join("\n").rstrip
82
146
  sections = [import_block.rstrip, declaration_block].reject(&:empty?)
83
147
 
84
- { ok: true, diagnostics: [], output: "#{sections.join("\n\n").rstrip}\n", policies: [DESTINATION_WINS_ARRAY_POLICY] }
148
+ { ok: true, diagnostics: [], output: "#{sections.join("\n\n").rstrip}\n",
149
+ policies: [DESTINATION_WINS_ARRAY_POLICY] }
85
150
  end
86
151
 
87
- def analyze_type_script_module(source)
88
- parsed = TreeHaver.parse_with_language_pack(TreeHaver::ParserRequest.new(source: source, language: "typescript", dialect: "typescript"))
89
- return { ok: false, diagnostics: parsed[:diagnostics], policies: [] } unless parsed[:ok]
90
- processed = TreeHaver.process_with_language_pack(TreeHaver::ProcessRequest.new(source: source, language: "typescript"))
91
- return { ok: false, diagnostics: processed[:diagnostics], policies: [] } unless processed[:ok]
92
-
93
- imports = processed[:analysis].imports.each_with_index.map do |item, index|
94
- { path: "/imports/#{index}", match_key: item.source, text: import_text(source, item.span) }
152
+ def analyze_type_script_module(source, dialect: 'typescript')
153
+ analysis = FileAnalysis.new(source, dialect: dialect)
154
+ raise analysis.errors.first unless analysis.errors.empty?
155
+
156
+ imports = []
157
+ declarations = []
158
+ analysis.declarations.each do |wrapper|
159
+ signature = wrapper.signature
160
+ if signature.first == :import
161
+ imports << {
162
+ path: "/imports/#{imports.length}",
163
+ owner_kind: 'import',
164
+ match_key: legacy_import_key(signature),
165
+ text: "#{wrapper.source_text}\n"
166
+ }
167
+ else
168
+ name = legacy_declaration_key(signature)
169
+ declarations << {
170
+ path: "/declarations/#{name}",
171
+ owner_kind: 'declaration',
172
+ match_key: name,
173
+ text: "#{wrapper.source_text}\n"
174
+ }
175
+ end
95
176
  end
96
- declarations = processed[:analysis].structure
97
- .select { |item| item.name }
98
- .map { |item| { path: "/declarations/#{item.name}", match_key: item.name, text: declaration_text(source, item.span) } }
99
- .sort_by { |item| item[:path] }
100
177
 
101
178
  {
102
179
  ok: true,
103
180
  diagnostics: [],
104
181
  analysis: {
105
- kind: "typescript",
106
- dialect: "typescript",
107
- source: source,
108
- owners: imports.map { |item| { path: item[:path], owner_kind: "import", match_key: item[:match_key] } } +
109
- declarations.map { |item| { path: item[:path], owner_kind: "declaration", match_key: item[:match_key] } },
182
+ kind: 'typescript',
183
+ dialect: dialect,
110
184
  imports: imports,
111
- declarations: declarations
185
+ declarations: declarations,
186
+ owners: owner_views(imports + declarations)
112
187
  },
113
188
  policies: []
114
189
  }
190
+ rescue TreeHaver::Error, StandardError => e
191
+ parse_failure_result(e)
115
192
  end
116
193
  private_class_method :analyze_type_script_module
117
194
 
118
- def import_text(source, span)
119
- "#{slice_span(source, span)}\n"
195
+ def legacy_declaration_key(signature)
196
+ nested = signature.first == :export_declaration ? signature.fetch(1) : signature
197
+ value = nested.last
198
+ value.is_a?(Array) ? value.join(',') : value
199
+ end
200
+ private_class_method :legacy_declaration_key
201
+
202
+ def legacy_import_key(signature)
203
+ literal = signature.fetch(1)
204
+ literal.byteslice(1...-1).to_s
120
205
  end
121
- private_class_method :import_text
206
+ private_class_method :legacy_import_key
122
207
 
123
- def declaration_text(source, span)
124
- "#{line_anchored_slice(source, span)}\n"
208
+ def parse_failure_result(error)
209
+ { ok: false,
210
+ diagnostics: [{ severity: 'error', category: 'parse_error', message: error.message }],
211
+ policies: [] }
125
212
  end
126
- private_class_method :declaration_text
213
+ private_class_method :parse_failure_result
127
214
 
128
- def slice_span(source, span)
129
- source[span.start_byte...span.end_byte].strip
215
+ def owner_view(item)
216
+ item.slice(:path, :owner_kind, :match_key)
130
217
  end
131
- private_class_method :slice_span
218
+ private_class_method :owner_view
132
219
 
133
- def line_anchored_slice(source, span)
134
- line_start = source.rindex("\n", [span.start_byte - 1, 0].max)
135
- line_start = line_start ? line_start + 1 : 0
136
- source[line_start...span.end_byte].strip
220
+ def owner_views(items)
221
+ imports, declarations = items.partition { |item| item.fetch(:owner_kind) == 'import' }
222
+ (imports + declarations.sort_by { |item| item.fetch(:path) }).map { |item| owner_view(item) }
137
223
  end
138
- private_class_method :line_anchored_slice
224
+ private_class_method :owner_views
139
225
 
140
226
  def unsupported_feature_result(message)
141
- Ast::Merge.unsupported_feature_result(message)
227
+ {
228
+ ok: false,
229
+ diagnostics: [{ severity: 'error', category: 'unsupported_feature', message: message }],
230
+ policies: []
231
+ }
142
232
  end
143
233
  private_class_method :unsupported_feature_result
144
234
  end
145
235
  end
236
+
237
+ TypeScript::Merge.register_backend!
238
+ TypeScript::Merge.register_provider!
@@ -1,3 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "typescript/merge"
3
+ require_relative 'typescript/merge'
4
+ require 'version_gem'
5
+ require_relative 'typescript/merge/version'
6
+
7
+ TypeScript::Merge::Version.class_eval do
8
+ extend VersionGem::Basic
9
+ end
@@ -0,0 +1,54 @@
1
+ module TypeScript
2
+ module Merge
3
+ class NodeWrapper
4
+ DECLARATION_TYPES: Array[String]
5
+ NAMED_DECLARATIONS: Hash[String, [Symbol, Array[String]]]
6
+
7
+ attr_reader node: untyped
8
+ attr_reader source: String
9
+ attr_reader leading_comments: Array[untyped]
10
+ attr_reader trailing_comments: Array[untyped]
11
+
12
+ def initialize: (untyped node, source: String, ?leading_comments: Array[untyped]) -> void
13
+ def signature: () -> Array[untyped]
14
+ def membership_signature: () -> Array[untyped]
15
+ def grouped?: () -> bool
16
+ def start_byte: () -> Integer
17
+ def end_byte: () -> Integer
18
+ def start_line: () -> Integer
19
+ def end_line: () -> Integer
20
+ def source_text: () -> String
21
+ def semantic_tree: () -> Array[untyped]
22
+ def attach_trailing_comment!: (untyped comment) -> untyped
23
+ end
24
+
25
+ class FileAnalysis
26
+ attr_reader source: String
27
+ attr_reader root_node: untyped
28
+ attr_reader declarations: Array[NodeWrapper]
29
+ attr_reader errors: Array[StandardError]
30
+ attr_reader backend: Symbol
31
+ attr_reader dialect: Symbol
32
+
33
+ def initialize: (String source, ?dialect: Symbol) -> void
34
+ end
35
+
36
+ class Provider
37
+ def provider_id: () -> String
38
+ def family: () -> String
39
+ def capabilities: () -> Hash[Symbol, untyped]
40
+ def analyze: (Hash[Symbol, untyped] request) -> Hash[Symbol, untyped]
41
+ def diff2: (Hash[Symbol, untyped] request) -> Hash[Symbol, untyped]
42
+ def merge2: (Hash[Symbol, untyped] request) -> Hash[Symbol, untyped]
43
+ def merge3: (Hash[Symbol, untyped] request) -> Hash[Symbol, untyped]
44
+ end
45
+
46
+ def self.merge_provider: () -> Provider
47
+ def self.register_provider!: (?replace: bool) -> untyped
48
+
49
+ module Version
50
+ VERSION: String
51
+ end
52
+ VERSION: String
53
+ end
54
+ end
data.tar.gz.sig CHANGED
Binary file