ruby-lsp 0.23.11 → 0.26.4

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.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +10 -4
  5. data/exe/ruby-lsp-check +0 -4
  6. data/exe/ruby-lsp-launcher +45 -22
  7. data/exe/ruby-lsp-test-exec +6 -0
  8. data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +1 -2
  9. data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +3 -6
  10. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +82 -116
  11. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +140 -183
  12. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +10 -14
  13. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +107 -236
  14. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +188 -285
  15. data/lib/ruby_indexer/lib/ruby_indexer/location.rb +4 -27
  16. data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +23 -27
  17. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +25 -57
  18. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +58 -68
  19. data/lib/ruby_indexer/lib/ruby_indexer/uri.rb +17 -19
  20. data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +7 -11
  21. data/lib/ruby_indexer/test/class_variables_test.rb +14 -14
  22. data/lib/ruby_indexer/test/classes_and_modules_test.rb +65 -40
  23. data/lib/ruby_indexer/test/configuration_test.rb +49 -9
  24. data/lib/ruby_indexer/test/constant_test.rb +34 -34
  25. data/lib/ruby_indexer/test/enhancements_test.rb +1 -1
  26. data/lib/ruby_indexer/test/index_test.rb +225 -135
  27. data/lib/ruby_indexer/test/instance_variables_test.rb +61 -37
  28. data/lib/ruby_indexer/test/method_test.rb +166 -123
  29. data/lib/ruby_indexer/test/prefix_tree_test.rb +21 -21
  30. data/lib/ruby_indexer/test/rbs_indexer_test.rb +70 -75
  31. data/lib/ruby_indexer/test/reference_finder_test.rb +79 -14
  32. data/lib/ruby_indexer/test/test_case.rb +9 -3
  33. data/lib/ruby_indexer/test/uri_test.rb +15 -2
  34. data/lib/ruby_lsp/addon.rb +88 -86
  35. data/lib/ruby_lsp/base_server.rb +79 -65
  36. data/lib/ruby_lsp/client_capabilities.rb +16 -13
  37. data/lib/ruby_lsp/document.rb +205 -104
  38. data/lib/ruby_lsp/erb_document.rb +45 -47
  39. data/lib/ruby_lsp/global_state.rb +73 -57
  40. data/lib/ruby_lsp/internal.rb +8 -3
  41. data/lib/ruby_lsp/listeners/code_lens.rb +82 -89
  42. data/lib/ruby_lsp/listeners/completion.rb +81 -76
  43. data/lib/ruby_lsp/listeners/definition.rb +44 -58
  44. data/lib/ruby_lsp/listeners/document_highlight.rb +149 -151
  45. data/lib/ruby_lsp/listeners/document_link.rb +94 -82
  46. data/lib/ruby_lsp/listeners/document_symbol.rb +38 -52
  47. data/lib/ruby_lsp/listeners/folding_ranges.rb +40 -43
  48. data/lib/ruby_lsp/listeners/hover.rb +107 -115
  49. data/lib/ruby_lsp/listeners/inlay_hints.rb +8 -13
  50. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +54 -56
  51. data/lib/ruby_lsp/listeners/signature_help.rb +12 -27
  52. data/lib/ruby_lsp/listeners/spec_style.rb +231 -0
  53. data/lib/ruby_lsp/listeners/test_discovery.rb +107 -0
  54. data/lib/ruby_lsp/listeners/test_style.rb +207 -95
  55. data/lib/ruby_lsp/node_context.rb +12 -39
  56. data/lib/ruby_lsp/rbs_document.rb +10 -11
  57. data/lib/ruby_lsp/requests/code_action_resolve.rb +65 -61
  58. data/lib/ruby_lsp/requests/code_actions.rb +14 -26
  59. data/lib/ruby_lsp/requests/code_lens.rb +31 -21
  60. data/lib/ruby_lsp/requests/completion.rb +8 -21
  61. data/lib/ruby_lsp/requests/completion_resolve.rb +6 -6
  62. data/lib/ruby_lsp/requests/definition.rb +8 -20
  63. data/lib/ruby_lsp/requests/diagnostics.rb +8 -11
  64. data/lib/ruby_lsp/requests/discover_tests.rb +20 -7
  65. data/lib/ruby_lsp/requests/document_highlight.rb +6 -16
  66. data/lib/ruby_lsp/requests/document_link.rb +6 -17
  67. data/lib/ruby_lsp/requests/document_symbol.rb +5 -8
  68. data/lib/ruby_lsp/requests/folding_ranges.rb +7 -15
  69. data/lib/ruby_lsp/requests/formatting.rb +6 -9
  70. data/lib/ruby_lsp/requests/go_to_relevant_file.rb +139 -0
  71. data/lib/ruby_lsp/requests/hover.rb +12 -25
  72. data/lib/ruby_lsp/requests/inlay_hints.rb +8 -19
  73. data/lib/ruby_lsp/requests/on_type_formatting.rb +32 -40
  74. data/lib/ruby_lsp/requests/prepare_rename.rb +5 -10
  75. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +5 -15
  76. data/lib/ruby_lsp/requests/range_formatting.rb +5 -6
  77. data/lib/ruby_lsp/requests/references.rb +17 -57
  78. data/lib/ruby_lsp/requests/rename.rb +27 -51
  79. data/lib/ruby_lsp/requests/request.rb +13 -25
  80. data/lib/ruby_lsp/requests/selection_ranges.rb +7 -7
  81. data/lib/ruby_lsp/requests/semantic_highlighting.rb +16 -35
  82. data/lib/ruby_lsp/requests/show_syntax_tree.rb +7 -8
  83. data/lib/ruby_lsp/requests/signature_help.rb +9 -27
  84. data/lib/ruby_lsp/requests/support/annotation.rb +4 -10
  85. data/lib/ruby_lsp/requests/support/common.rb +23 -61
  86. data/lib/ruby_lsp/requests/support/formatter.rb +16 -15
  87. data/lib/ruby_lsp/requests/support/package_url.rb +414 -0
  88. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +27 -35
  89. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +13 -16
  90. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +34 -36
  91. data/lib/ruby_lsp/requests/support/selection_range.rb +1 -3
  92. data/lib/ruby_lsp/requests/support/sorbet.rb +29 -38
  93. data/lib/ruby_lsp/requests/support/source_uri.rb +20 -32
  94. data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +12 -19
  95. data/lib/ruby_lsp/requests/support/test_item.rb +16 -14
  96. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +5 -6
  97. data/lib/ruby_lsp/requests/workspace_symbol.rb +24 -16
  98. data/lib/ruby_lsp/response_builders/collection_response_builder.rb +6 -9
  99. data/lib/ruby_lsp/response_builders/document_symbol.rb +15 -21
  100. data/lib/ruby_lsp/response_builders/hover.rb +12 -18
  101. data/lib/ruby_lsp/response_builders/response_builder.rb +6 -7
  102. data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +62 -91
  103. data/lib/ruby_lsp/response_builders/signature_help.rb +6 -8
  104. data/lib/ruby_lsp/response_builders/test_collection.rb +35 -13
  105. data/lib/ruby_lsp/ruby_document.rb +32 -98
  106. data/lib/ruby_lsp/scope.rb +7 -11
  107. data/lib/ruby_lsp/scripts/compose_bundle.rb +6 -4
  108. data/lib/ruby_lsp/server.rb +305 -198
  109. data/lib/ruby_lsp/setup_bundler.rb +131 -82
  110. data/lib/ruby_lsp/static_docs.rb +12 -7
  111. data/lib/ruby_lsp/store.rb +21 -49
  112. data/lib/ruby_lsp/test_helper.rb +3 -16
  113. data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +241 -0
  114. data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +145 -0
  115. data/lib/ruby_lsp/test_reporters/test_unit_reporter.rb +92 -0
  116. data/lib/ruby_lsp/type_inferrer.rb +13 -14
  117. data/lib/ruby_lsp/utils.rb +138 -93
  118. data/static_docs/break.md +103 -0
  119. metadata +15 -20
  120. data/lib/ruby_lsp/load_sorbet.rb +0 -62
@@ -0,0 +1,414 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ # This is a copy of the implementation from the `package_url` gem with the
5
+ # following license. Original source can be found at:
6
+ # https://github.com/package-url/packageurl-ruby/blob/main/lib/package_url.rb
7
+
8
+ # MIT License
9
+ #
10
+ # Copyright (c) 2021 package-url
11
+ #
12
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ # of this software and associated documentation files (the "Software"), to deal
14
+ # in the Software without restriction, including without limitation the rights
15
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ # copies of the Software, and to permit persons to whom the Software is
17
+ # furnished to do so, subject to the following conditions:
18
+ #
19
+ # The above copyright notice and this permission notice shall be included in all
20
+ # copies or substantial portions of the Software.
21
+ #
22
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ # SOFTWARE.
29
+
30
+ require "uri"
31
+
32
+ # A package URL, or _purl_, is a URL string used to
33
+ # identify and locate a software package in a mostly universal and uniform way
34
+ # across programing languages, package managers, packaging conventions, tools,
35
+ # APIs and databases.
36
+ #
37
+ # A purl is a URL composed of seven components:
38
+ #
39
+ # ```
40
+ # scheme:type/namespace/name@version?qualifiers#subpath
41
+ # ```
42
+ #
43
+ # For example,
44
+ # the package URL for this Ruby package at version 0.1.0 is
45
+ # `pkg:ruby/mattt/packageurl-ruby@0.1.0`.
46
+ module RubyLsp
47
+ class PackageURL
48
+ # Raised when attempting to parse an invalid package URL string.
49
+ # @see #parse
50
+ class InvalidPackageURL < ArgumentError; end
51
+
52
+ # The URL scheme, which has a constant value of `"pkg"`.
53
+ def scheme
54
+ "pkg"
55
+ end
56
+
57
+ # The package type or protocol, such as `"gem"`, `"npm"`, and `"github"`.
58
+ attr_reader :type
59
+
60
+ # A name prefix, specific to the type of package.
61
+ # For example, an npm scope, a Docker image owner, or a GitHub user.
62
+ attr_reader :namespace
63
+
64
+ # The name of the package.
65
+ attr_reader :name
66
+
67
+ # The version of the package.
68
+ attr_reader :version
69
+
70
+ # Extra qualifying data for a package, specific to the type of package.
71
+ # For example, the operating system or architecture.
72
+ attr_reader :qualifiers
73
+
74
+ # An extra subpath within a package, relative to the package root.
75
+ attr_reader :subpath
76
+
77
+ # Constructs a package URL from its components
78
+ # @param type [String] The package type or protocol.
79
+ # @param namespace [String] A name prefix, specific to the type of package.
80
+ # @param name [String] The name of the package.
81
+ # @param version [String] The version of the package.
82
+ # @param qualifiers [Hash] Extra qualifying data for a package, specific to the type of package.
83
+ # @param subpath [String] An extra subpath within a package, relative to the package root.
84
+ def initialize(type:, name:, namespace: nil, version: nil, qualifiers: nil, subpath: nil)
85
+ raise ArgumentError, "type is required" if type.nil? || type.empty?
86
+ raise ArgumentError, "name is required" if name.nil? || name.empty?
87
+
88
+ @type = type.downcase
89
+ @namespace = namespace
90
+ @name = name
91
+ @version = version
92
+ @qualifiers = qualifiers
93
+ @subpath = subpath
94
+ end
95
+
96
+ # Creates a new PackageURL from a string.
97
+ # @param [String] string The package URL string.
98
+ # @raise [InvalidPackageURL] If the string is not a valid package URL.
99
+ # @return [PackageURL]
100
+ def self.parse(string)
101
+ components = {
102
+ type: nil,
103
+ namespace: nil,
104
+ name: nil,
105
+ version: nil,
106
+ qualifiers: nil,
107
+ subpath: nil,
108
+ }
109
+
110
+ # Split the purl string once from right on '#'
111
+ # - The left side is the remainder
112
+ # - Strip the right side from leading and trailing '/'
113
+ # - Split this on '/'
114
+ # - Discard any empty string segment from that split
115
+ # - Discard any '.' or '..' segment from that split
116
+ # - Percent-decode each segment
117
+ # - UTF-8-decode each segment if needed in your programming language
118
+ # - Join segments back with a '/'
119
+ # - This is the subpath
120
+ case string.rpartition("#")
121
+ in String => remainder, separator, String => subpath unless separator.empty?
122
+ subpath_components = []
123
+ subpath.split("/").each do |segment|
124
+ next if segment.empty? || segment == "." || segment == ".."
125
+
126
+ subpath_components << URI.decode_www_form_component(segment)
127
+ end
128
+
129
+ components[:subpath] = subpath_components.compact.join("/")
130
+
131
+ string = remainder
132
+ else
133
+ components[:subpath] = nil
134
+ end
135
+
136
+ # Split the remainder once from right on '?'
137
+ # - The left side is the remainder
138
+ # - The right side is the qualifiers string
139
+ # - Split the qualifiers on '&'. Each part is a key=value pair
140
+ # - For each pair, split the key=value once from left on '=':
141
+ # - The key is the lowercase left side
142
+ # - The value is the percent-decoded right side
143
+ # - UTF-8-decode the value if needed in your programming language
144
+ # - Discard any key/value pairs where the value is empty
145
+ # - If the key is checksums,
146
+ # split the value on ',' to create a list of checksums
147
+ # - This list of key/value is the qualifiers object
148
+ case string.rpartition("?")
149
+ in String => remainder, separator, String => qualifiers unless separator.empty?
150
+ components[:qualifiers] = {}
151
+
152
+ qualifiers.split("&").each do |pair|
153
+ case pair.partition("=")
154
+ in String => key, separator, String => value unless separator.empty?
155
+ key = key.downcase
156
+ value = URI.decode_www_form_component(value)
157
+ next if value.empty?
158
+
159
+ components[:qualifiers][key] = case key
160
+ when "checksums"
161
+ value.split(",")
162
+ else
163
+ value
164
+ end
165
+ else
166
+ next
167
+ end
168
+ end
169
+
170
+ string = remainder
171
+ else
172
+ components[:qualifiers] = nil
173
+ end
174
+
175
+ # Split the remainder once from left on ':'
176
+ # - The left side lowercased is the scheme
177
+ # - The right side is the remainder
178
+ case string.partition(":")
179
+ in "pkg", separator, String => remainder unless separator.empty?
180
+ string = remainder
181
+ else
182
+ raise InvalidPackageURL, 'invalid or missing "pkg:" URL scheme'
183
+ end
184
+
185
+ # Strip the remainder from leading and trailing '/'
186
+ # Use gsub to remove ALL leading slashes instead of just one
187
+ string = string.gsub(%r{^/+}, "").delete_suffix("/")
188
+ # - Split this once from left on '/'
189
+ # - The left side lowercased is the type
190
+ # - The right side is the remainder
191
+ case string.partition("/")
192
+ in String => type, separator, remainder unless separator.empty?
193
+ components[:type] = type
194
+
195
+ string = remainder
196
+ else
197
+ raise InvalidPackageURL, "invalid or missing package type"
198
+ end
199
+
200
+ # Split the remainder once from right on '@'
201
+ # - The left side is the remainder
202
+ # - Percent-decode the right side. This is the version.
203
+ # - UTF-8-decode the version if needed in your programming language
204
+ # - This is the version
205
+ case string.rpartition("@")
206
+ in String => remainder, separator, String => version unless separator.empty?
207
+ components[:version] = URI.decode_www_form_component(version)
208
+
209
+ string = remainder
210
+ else
211
+ components[:version] = nil
212
+ end
213
+
214
+ # Split the remainder once from right on '/'
215
+ # - The left side is the remainder
216
+ # - Percent-decode the right side. This is the name
217
+ # - UTF-8-decode this name if needed in your programming language
218
+ # - Apply type-specific normalization to the name if needed
219
+ # - This is the name
220
+ case string.rpartition("/")
221
+ in String => remainder, separator, String => name unless separator.empty?
222
+ components[:name] = URI.decode_www_form_component(name)
223
+
224
+ # Split the remainder on '/'
225
+ # - Discard any empty segment from that split
226
+ # - Percent-decode each segment
227
+ # - UTF-8-decode the each segment if needed in your programming language
228
+ # - Apply type-specific normalization to each segment if needed
229
+ # - Join segments back with a '/'
230
+ # - This is the namespace
231
+ components[:namespace] = remainder.split("/").map { |s| URI.decode_www_form_component(s) }.compact.join("/")
232
+ in _, _, String => name
233
+ components[:name] = URI.decode_www_form_component(name)
234
+ components[:namespace] = nil
235
+ end
236
+
237
+ # Ensure type and name are not nil before creating the PackageURL instance
238
+ raise InvalidPackageURL, "missing package type" if components[:type].nil?
239
+ raise InvalidPackageURL, "missing package name" if components[:name].nil?
240
+
241
+ # Create a new PackageURL with validated components
242
+ type = components[:type] || "" # This ensures type is never nil
243
+ name = components[:name] || "" # This ensures name is never nil
244
+
245
+ new(
246
+ type: type,
247
+ name: name,
248
+ namespace: components[:namespace],
249
+ version: components[:version],
250
+ qualifiers: components[:qualifiers],
251
+ subpath: components[:subpath],
252
+ )
253
+ end
254
+
255
+ # Returns a hash containing the
256
+ # scheme, type, namespace, name, version, qualifiers, and subpath components
257
+ # of the package URL.
258
+ def to_h
259
+ {
260
+ scheme: scheme,
261
+ type: @type,
262
+ namespace: @namespace,
263
+ name: @name,
264
+ version: @version,
265
+ qualifiers: @qualifiers,
266
+ subpath: @subpath,
267
+ }
268
+ end
269
+
270
+ # Returns a string representation of the package URL.
271
+ # Package URL representations are created according to the instructions from
272
+ # https://github.com/package-url/purl-spec/blob/0b1559f76b79829e789c4f20e6d832c7314762c5/PURL-SPECIFICATION.rst#how-to-build-purl-string-from-its-components.
273
+ def to_s
274
+ # Start a purl string with the "pkg:" scheme as a lowercase ASCII string
275
+ purl = "pkg:"
276
+
277
+ # Append the type string to the purl as a lowercase ASCII string
278
+ # Append '/' to the purl
279
+
280
+ purl += @type
281
+ purl += "/"
282
+
283
+ # If the namespace is not empty:
284
+ # - Strip the namespace from leading and trailing '/'
285
+ # - Split on '/' as segments
286
+ # - Apply type-specific normalization to each segment if needed
287
+ # - UTF-8-encode each segment if needed in your programming language
288
+ # - Percent-encode each segment
289
+ # - Join the segments with '/'
290
+ # - Append this to the purl
291
+ # - Append '/' to the purl
292
+ # - Strip the name from leading and trailing '/'
293
+ # - Apply type-specific normalization to the name if needed
294
+ # - UTF-8-encode the name if needed in your programming language
295
+ # - Append the percent-encoded name to the purl
296
+ #
297
+ # If the namespace is empty:
298
+ # - Apply type-specific normalization to the name if needed
299
+ # - UTF-8-encode the name if needed in your programming language
300
+ # - Append the percent-encoded name to the purl
301
+ case @namespace
302
+ in String => namespace unless namespace.empty?
303
+ segments = []
304
+ @namespace.delete_prefix("/").delete_suffix("/").split("/").each do |segment|
305
+ next if segment.empty?
306
+
307
+ segments << URI.encode_www_form_component(segment)
308
+ end
309
+ purl += segments.join("/")
310
+
311
+ purl += "/"
312
+ purl += URI.encode_www_form_component(@name.delete_prefix("/").delete_suffix("/"))
313
+ else
314
+ purl += URI.encode_www_form_component(@name)
315
+ end
316
+
317
+ # If the version is not empty:
318
+ # - Append '@' to the purl
319
+ # - UTF-8-encode the version if needed in your programming language
320
+ # - Append the percent-encoded version to the purl
321
+ case @version
322
+ in String => version unless version.empty?
323
+ purl += "@"
324
+ purl += URI.encode_www_form_component(@version)
325
+ else
326
+ nil
327
+ end
328
+
329
+ # If the qualifiers are not empty and not composed only of key/value pairs
330
+ # where the value is empty:
331
+ # - Append '?' to the purl
332
+ # - Build a list from all key/value pair:
333
+ # - discard any pair where the value is empty.
334
+ # - UTF-8-encode each value if needed in your programming language
335
+ # - If the key is checksums and this is a list of checksums
336
+ # join this list with a ',' to create this qualifier value
337
+ # - create a string by joining the lowercased key,
338
+ # the equal '=' sign and the percent-encoded value to create a qualifier
339
+ # - sort this list of qualifier strings lexicographically
340
+ # - join this list of qualifier strings with a '&' ampersand
341
+ # - Append this string to the purl
342
+ case @qualifiers
343
+ in Hash => qualifiers unless qualifiers.empty?
344
+ list = []
345
+ qualifiers.each do |key, value|
346
+ next if value.empty?
347
+
348
+ list << case [key, value]
349
+ in "checksums", Array => checksums
350
+ "#{key.downcase}=#{checksums.join(",")}"
351
+ else
352
+ "#{key.downcase}=#{URI.encode_www_form_component(value)}"
353
+ end
354
+ end
355
+
356
+ unless list.empty?
357
+ purl += "?"
358
+ purl += list.sort.join("&")
359
+ end
360
+ else
361
+ nil
362
+ end
363
+
364
+ # If the subpath is not empty and not composed only of
365
+ # empty, '.' and '..' segments:
366
+ # - Append '#' to the purl
367
+ # - Strip the subpath from leading and trailing '/'
368
+ # - Split this on '/' as segments
369
+ # - Discard empty, '.' and '..' segments
370
+ # - Percent-encode each segment
371
+ # - UTF-8-encode each segment if needed in your programming language
372
+ # - Join the segments with '/'
373
+ # - Append this to the purl
374
+ case @subpath
375
+ in String => subpath unless subpath.empty?
376
+ segments = []
377
+ subpath.delete_prefix("/").delete_suffix("/").split("/").each do |segment|
378
+ next if segment.empty? || segment == "." || segment == ".."
379
+
380
+ # Custom encoding for URL fragment segments:
381
+ # 1. Explicitly encode % as %25 to prevent double-encoding issues
382
+ # 2. Percent-encode special characters according to URL fragment rules
383
+ # 3. This ensures proper round-trip encoding/decoding with the parse method
384
+ segments << segment.gsub(/%|[^A-Za-z0-9\-\._~]/) do |m|
385
+ m == "%" ? "%25" : format("%%%02X", m.ord)
386
+ end
387
+ end
388
+
389
+ unless segments.empty?
390
+ purl += "#"
391
+ purl += segments.join("/")
392
+ end
393
+ else
394
+ nil
395
+ end
396
+
397
+ purl
398
+ end
399
+
400
+ # Returns an array containing the
401
+ # scheme, type, namespace, name, version, qualifiers, and subpath components
402
+ # of the package URL.
403
+ def deconstruct
404
+ [scheme, @type, @namespace, @name, @version, @qualifiers, @subpath]
405
+ end
406
+
407
+ # Returns a hash containing the
408
+ # scheme, type, namespace, name, version, qualifiers, and subpath components
409
+ # of the package URL.
410
+ def deconstruct_keys(_keys)
411
+ to_h
412
+ end
413
+ end
414
+ end
@@ -5,40 +5,32 @@ module RubyLsp
5
5
  module Requests
6
6
  module Support
7
7
  class RuboCopDiagnostic
8
- extend T::Sig
9
-
10
- RUBOCOP_TO_LSP_SEVERITY = T.let(
11
- {
12
- info: Constant::DiagnosticSeverity::HINT,
13
- refactor: Constant::DiagnosticSeverity::INFORMATION,
14
- convention: Constant::DiagnosticSeverity::INFORMATION,
15
- warning: Constant::DiagnosticSeverity::WARNING,
16
- error: Constant::DiagnosticSeverity::ERROR,
17
- fatal: Constant::DiagnosticSeverity::ERROR,
18
- }.freeze,
19
- T::Hash[Symbol, Integer],
20
- )
21
-
22
- ENHANCED_DOC_URL = T.let(
23
- begin
24
- gem("rubocop", ">= 1.64.0")
25
- true
26
- rescue LoadError
27
- false
28
- end,
29
- T::Boolean,
30
- )
8
+ RUBOCOP_TO_LSP_SEVERITY = {
9
+ info: Constant::DiagnosticSeverity::HINT,
10
+ refactor: Constant::DiagnosticSeverity::INFORMATION,
11
+ convention: Constant::DiagnosticSeverity::INFORMATION,
12
+ warning: Constant::DiagnosticSeverity::WARNING,
13
+ error: Constant::DiagnosticSeverity::ERROR,
14
+ fatal: Constant::DiagnosticSeverity::ERROR,
15
+ }.freeze #: Hash[Symbol, Integer]
16
+
17
+ ENHANCED_DOC_URL = begin
18
+ gem("rubocop", ">= 1.64.0")
19
+ true
20
+ rescue LoadError
21
+ false
22
+ end #: bool
31
23
 
32
24
  # TODO: avoid passing document once we have alternative ways to get at
33
25
  # encoding and file source
34
- sig { params(document: RubyDocument, offense: ::RuboCop::Cop::Offense, uri: URI::Generic).void }
26
+ #: (RubyDocument document, ::RuboCop::Cop::Offense offense, URI::Generic uri) -> void
35
27
  def initialize(document, offense, uri)
36
28
  @document = document
37
29
  @offense = offense
38
30
  @uri = uri
39
31
  end
40
32
 
41
- sig { returns(T::Array[Interface::CodeAction]) }
33
+ #: -> Array[Interface::CodeAction]
42
34
  def to_lsp_code_actions
43
35
  code_actions = []
44
36
 
@@ -48,7 +40,7 @@ module RubyLsp
48
40
  code_actions
49
41
  end
50
42
 
51
- sig { params(config: ::RuboCop::Config).returns(Interface::Diagnostic) }
43
+ #: (::RuboCop::Config config) -> Interface::Diagnostic
52
44
  def to_lsp_diagnostic(config)
53
45
  # highlighted_area contains the begin and end position of the first line
54
46
  # This ensures that multiline offenses don't clutter the editor
@@ -78,19 +70,19 @@ module RubyLsp
78
70
 
79
71
  private
80
72
 
81
- sig { returns(String) }
73
+ #: -> String
82
74
  def message
83
75
  message = @offense.message
84
76
  message += "\n\nThis offense is not auto-correctable.\n" unless correctable?
85
77
  message
86
78
  end
87
79
 
88
- sig { returns(T.nilable(Integer)) }
80
+ #: -> Integer?
89
81
  def severity
90
82
  RUBOCOP_TO_LSP_SEVERITY[@offense.severity.name]
91
83
  end
92
84
 
93
- sig { params(config: ::RuboCop::Config).returns(T.nilable(Interface::CodeDescription)) }
85
+ #: (::RuboCop::Config config) -> Interface::CodeDescription?
94
86
  def code_description(config)
95
87
  cop = RuboCopRunner.find_cop_by_name(@offense.cop_name)
96
88
  return unless cop
@@ -103,7 +95,7 @@ module RubyLsp
103
95
  Interface::CodeDescription.new(href: doc_url) if doc_url
104
96
  end
105
97
 
106
- sig { returns(Interface::CodeAction) }
98
+ #: -> Interface::CodeAction
107
99
  def autocorrect_action
108
100
  Interface::CodeAction.new(
109
101
  title: "Autocorrect #{@offense.cop_name}",
@@ -123,7 +115,7 @@ module RubyLsp
123
115
  )
124
116
  end
125
117
 
126
- sig { returns(T::Array[Interface::TextEdit]) }
118
+ #: -> Array[Interface::TextEdit]
127
119
  def offense_replacements
128
120
  @offense.corrector.as_replacements.map do |range, replacement|
129
121
  Interface::TextEdit.new(
@@ -136,7 +128,7 @@ module RubyLsp
136
128
  end
137
129
  end
138
130
 
139
- sig { returns(Interface::CodeAction) }
131
+ #: -> Interface::CodeAction
140
132
  def disable_line_action
141
133
  Interface::CodeAction.new(
142
134
  title: "Disable #{@offense.cop_name} for this line",
@@ -155,7 +147,7 @@ module RubyLsp
155
147
  )
156
148
  end
157
149
 
158
- sig { returns(T::Array[Interface::TextEdit]) }
150
+ #: -> Array[Interface::TextEdit]
159
151
  def line_disable_comment
160
152
  new_text = if @offense.source_line.include?(" # rubocop:disable ")
161
153
  ",#{@offense.cop_name}"
@@ -178,7 +170,7 @@ module RubyLsp
178
170
  [inline_comment]
179
171
  end
180
172
 
181
- sig { params(line: String).returns(Integer) }
173
+ #: (String line) -> Integer
182
174
  def length_of_line(line)
183
175
  if @document.encoding == Encoding::UTF_16LE
184
176
  line_length = 0
@@ -197,7 +189,7 @@ module RubyLsp
197
189
  # When `RuboCop::LSP.enable` is called, contextual autocorrect will not offer itself
198
190
  # as `correctable?` to prevent annoying changes while typing. Instead check if
199
191
  # a corrector is present. If it is, then that means some code transformation can be applied.
200
- sig { returns(T::Boolean) }
192
+ #: -> bool
201
193
  def correctable?
202
194
  !@offense.corrector.nil?
203
195
  end
@@ -9,41 +9,38 @@ module RubyLsp
9
9
  module Requests
10
10
  module Support
11
11
  class RuboCopFormatter
12
- extend T::Sig
13
12
  include Formatter
14
13
 
15
- sig { void }
14
+ #: -> void
16
15
  def initialize
17
- @diagnostic_runner = T.let(RuboCopRunner.new, RuboCopRunner)
16
+ @diagnostic_runner = RuboCopRunner.new #: RuboCopRunner
18
17
  # -a is for "--auto-correct" (or "--autocorrect" on newer versions of RuboCop)
19
- @format_runner = T.let(RuboCopRunner.new("-a"), RuboCopRunner)
18
+ @format_runner = RuboCopRunner.new("-a") #: RuboCopRunner
20
19
  end
21
20
 
22
- sig { override.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
21
+ # @override
22
+ #: (URI::Generic uri, RubyDocument document) -> String?
23
23
  def run_formatting(uri, document)
24
- filename = T.must(uri.to_standardized_path || uri.opaque)
24
+ filename = uri.to_standardized_path || uri.opaque #: as !nil
25
25
 
26
26
  # Invoke RuboCop with just this file in `paths`
27
- @format_runner.run(filename, document.source)
27
+ @format_runner.run(filename, document.source, document.parse_result)
28
28
  @format_runner.formatted_source
29
29
  end
30
30
 
31
31
  # RuboCop does not support range formatting
32
- sig { override.params(uri: URI::Generic, source: String, base_indentation: Integer).returns(T.nilable(String)) }
32
+ # @override
33
+ #: (URI::Generic uri, String source, Integer base_indentation) -> String?
33
34
  def run_range_formatting(uri, source, base_indentation)
34
35
  nil
35
36
  end
36
37
 
37
- sig do
38
- override.params(
39
- uri: URI::Generic,
40
- document: RubyDocument,
41
- ).returns(T.nilable(T::Array[Interface::Diagnostic]))
42
- end
38
+ # @override
39
+ #: (URI::Generic uri, RubyDocument document) -> Array[Interface::Diagnostic]?
43
40
  def run_diagnostic(uri, document)
44
- filename = T.must(uri.to_standardized_path || uri.opaque)
41
+ filename = uri.to_standardized_path || uri.opaque #: as !nil
45
42
  # Invoke RuboCop with just this file in `paths`
46
- @diagnostic_runner.run(filename, document.source)
43
+ @diagnostic_runner.run(filename, document.source, document.parse_result)
47
44
 
48
45
  @diagnostic_runner.offenses.map do |offense|
49
46
  Support::RuboCopDiagnostic.new(