ruby-lsp 0.26.2 → 0.26.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
- data/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +6 -2
- data/lib/ruby_lsp/base_server.rb +21 -12
- data/lib/ruby_lsp/listeners/document_link.rb +50 -18
- data/lib/ruby_lsp/requests/support/package_url.rb +414 -0
- data/lib/ruby_lsp/requests/workspace_symbol.rb +20 -12
- data/lib/ruby_lsp/setup_bundler.rb +11 -2
- data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +10 -2
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 90c12f93d4750216ef84e046fe970f0ccc8edd7567943b9f4be02dc3828fd151
|
|
4
|
+
data.tar.gz: ef810d7d1599f9539474e87067c5972ef2aa3a6143452494e9d03857d64771e7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4a6ff236ecaabfd5e927a6f57f47d97ad6822dc647ce3531c57565adfc05fd592167decbabfa2ac3420d8294f193188103c7aa3a468b5f92dca7b18a92bb1b87
|
|
7
|
+
data.tar.gz: e34f78b58b73536faecf0bafb0f7a620bbbc3122c8fb8d1b526112b000055b78fe6d153ff0bf1bbd2300aaaffb3f5a6327fd7ef4248332b39ebb5a7c13fe94d5
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.26.
|
|
1
|
+
0.26.3
|
|
@@ -195,12 +195,13 @@ module RubyIndexer
|
|
|
195
195
|
end
|
|
196
196
|
|
|
197
197
|
# Fuzzy searches index entries based on Jaro-Winkler similarity. If no query is provided, all entries are returned
|
|
198
|
-
#: (String? query) -> Array[Entry]
|
|
199
|
-
def fuzzy_search(query)
|
|
198
|
+
#: (String? query) ?{ (Entry) -> bool? } -> Array[Entry]
|
|
199
|
+
def fuzzy_search(query, &condition)
|
|
200
200
|
unless query
|
|
201
201
|
entries = @entries.filter_map do |_name, entries|
|
|
202
202
|
next if entries.first.is_a?(Entry::SingletonClass)
|
|
203
203
|
|
|
204
|
+
entries = entries.select(&condition) if condition
|
|
204
205
|
entries
|
|
205
206
|
end
|
|
206
207
|
|
|
@@ -212,6 +213,9 @@ module RubyIndexer
|
|
|
212
213
|
results = @entries.filter_map do |name, entries|
|
|
213
214
|
next if entries.first.is_a?(Entry::SingletonClass)
|
|
214
215
|
|
|
216
|
+
entries = entries.select(&condition) if condition
|
|
217
|
+
next if entries.empty?
|
|
218
|
+
|
|
215
219
|
similarity = DidYouMean::JaroWinkler.distance(name.gsub("::", "").downcase, normalized_query)
|
|
216
220
|
[entries, -similarity] if similarity > ENTRY_SIMILARITY_THRESHOLD
|
|
217
221
|
end
|
data/lib/ruby_lsp/base_server.rb
CHANGED
|
@@ -83,7 +83,7 @@ module RubyLsp
|
|
|
83
83
|
# The following requests need to be executed in the main thread directly to avoid concurrency issues. Everything
|
|
84
84
|
# else is pushed into the incoming queue
|
|
85
85
|
case method
|
|
86
|
-
when "initialize", "initialized", "rubyLsp/diagnoseState"
|
|
86
|
+
when "initialize", "initialized", "rubyLsp/diagnoseState", "$/cancelRequest"
|
|
87
87
|
process_message(message)
|
|
88
88
|
when "shutdown"
|
|
89
89
|
@global_state.synchronize do
|
|
@@ -154,20 +154,29 @@ module RubyLsp
|
|
|
154
154
|
def new_worker
|
|
155
155
|
Thread.new do
|
|
156
156
|
while (message = @incoming_queue.pop)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if id && @cancelled_requests.include?(id)
|
|
162
|
-
send_message(Result.new(id: id, response: nil))
|
|
163
|
-
@cancelled_requests.delete(id)
|
|
164
|
-
next
|
|
165
|
-
end
|
|
166
|
-
end
|
|
157
|
+
handle_incoming_message(message)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
167
161
|
|
|
168
|
-
|
|
162
|
+
#: (Hash[Symbol, untyped]) -> void
|
|
163
|
+
def handle_incoming_message(message)
|
|
164
|
+
id = message[:id]
|
|
165
|
+
|
|
166
|
+
# Check if the request was cancelled before trying to process it
|
|
167
|
+
@global_state.synchronize do
|
|
168
|
+
if id && @cancelled_requests.include?(id)
|
|
169
|
+
send_message(Error.new(
|
|
170
|
+
id: id,
|
|
171
|
+
code: Constant::ErrorCodes::REQUEST_CANCELLED,
|
|
172
|
+
message: "Request #{id} was cancelled",
|
|
173
|
+
))
|
|
174
|
+
@cancelled_requests.delete(id)
|
|
175
|
+
return
|
|
169
176
|
end
|
|
170
177
|
end
|
|
178
|
+
|
|
179
|
+
process_message(message)
|
|
171
180
|
end
|
|
172
181
|
|
|
173
182
|
#: ((Result | Error | Notification | Request) message) -> void
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "ruby_lsp/requests/support/source_uri"
|
|
5
|
+
require "ruby_lsp/requests/support/package_url"
|
|
5
6
|
|
|
6
7
|
module RubyLsp
|
|
7
8
|
module Listeners
|
|
@@ -102,19 +103,58 @@ module RubyLsp
|
|
|
102
103
|
comment = @lines_to_comments[node.location.start_line - 1]
|
|
103
104
|
return unless comment
|
|
104
105
|
|
|
105
|
-
match = comment.location.slice.match(%r{source://.*#\d
|
|
106
|
+
match = comment.location.slice.match(%r{(source://.*#\d+|pkg:gem/.*#.*)$})
|
|
106
107
|
return unless match
|
|
107
108
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
)
|
|
109
|
+
uri_string = match[0] #: as !nil
|
|
110
|
+
|
|
111
|
+
file_path, line_number = if uri_string.start_with?("pkg:gem/")
|
|
112
|
+
parse_package_url(uri_string)
|
|
113
|
+
else
|
|
114
|
+
parse_source_uri(uri_string)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
return unless file_path
|
|
118
|
+
|
|
119
|
+
@response_builder << Interface::DocumentLink.new(
|
|
120
|
+
range: range_from_location(comment.location),
|
|
121
|
+
target: "file://#{file_path}##{line_number}",
|
|
122
|
+
tooltip: "Jump to #{file_path}##{line_number}",
|
|
123
|
+
)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
#: (String uri_string) -> [String, String]?
|
|
127
|
+
def parse_package_url(uri_string)
|
|
128
|
+
purl = PackageURL.parse(uri_string) #: as PackageURL?
|
|
129
|
+
return unless purl
|
|
130
|
+
|
|
131
|
+
gem_version = resolve_version(purl.version, purl.name)
|
|
132
|
+
return if gem_version.nil?
|
|
133
|
+
|
|
134
|
+
path, line_number = purl.subpath.split(":", 2)
|
|
135
|
+
return unless path
|
|
136
|
+
|
|
137
|
+
gem_name = purl.name
|
|
138
|
+
return unless gem_name
|
|
139
|
+
|
|
140
|
+
file_path = self.class.gem_paths.dig(gem_name, gem_version, CGI.unescape(path))
|
|
141
|
+
return if file_path.nil?
|
|
142
|
+
|
|
143
|
+
[file_path, line_number]
|
|
144
|
+
rescue PackageURL::InvalidPackageURL
|
|
145
|
+
nil
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
#: (String uri_string) -> [String, String]?
|
|
149
|
+
def parse_source_uri(uri_string)
|
|
150
|
+
uri = begin
|
|
151
|
+
URI(uri_string)
|
|
112
152
|
rescue URI::Error
|
|
113
153
|
nil
|
|
114
154
|
end #: as URI::Source?
|
|
115
155
|
return unless uri
|
|
116
156
|
|
|
117
|
-
gem_version = resolve_version(uri)
|
|
157
|
+
gem_version = resolve_version(uri.gem_version, uri.gem_name)
|
|
118
158
|
return if gem_version.nil?
|
|
119
159
|
|
|
120
160
|
path = uri.path
|
|
@@ -126,28 +166,20 @@ module RubyLsp
|
|
|
126
166
|
file_path = self.class.gem_paths.dig(gem_name, gem_version, CGI.unescape(path))
|
|
127
167
|
return if file_path.nil?
|
|
128
168
|
|
|
129
|
-
|
|
130
|
-
range: range_from_location(comment.location),
|
|
131
|
-
target: "file://#{file_path}##{uri.line_number}",
|
|
132
|
-
tooltip: "Jump to #{file_path}##{uri.line_number}",
|
|
133
|
-
)
|
|
169
|
+
[file_path, uri.line_number || "0"]
|
|
134
170
|
end
|
|
135
171
|
|
|
136
172
|
# Try to figure out the gem version for a source:// link. The order of precedence is:
|
|
137
173
|
# 1. The version in the URI
|
|
138
174
|
# 2. The version in the RBI file name
|
|
139
175
|
# 3. The version from the gemspec
|
|
140
|
-
#: (
|
|
141
|
-
def resolve_version(
|
|
142
|
-
version = uri.gem_version
|
|
176
|
+
#: (String? version, String? gem_name) -> String?
|
|
177
|
+
def resolve_version(version, gem_name)
|
|
143
178
|
return version unless version.nil? || version.empty?
|
|
144
179
|
|
|
145
180
|
return @gem_version unless @gem_version.nil? || @gem_version.empty?
|
|
146
181
|
|
|
147
|
-
gem_name
|
|
148
|
-
return unless gem_name
|
|
149
|
-
|
|
150
|
-
GEM_TO_VERSION_MAP[gem_name]
|
|
182
|
+
GEM_TO_VERSION_MAP[gem_name.to_s]
|
|
151
183
|
end
|
|
152
184
|
end
|
|
153
185
|
end
|
|
@@ -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
|
|
@@ -20,17 +20,7 @@ module RubyLsp
|
|
|
20
20
|
# @override
|
|
21
21
|
#: -> Array[Interface::WorkspaceSymbol]
|
|
22
22
|
def perform
|
|
23
|
-
|
|
24
|
-
uri = entry.uri
|
|
25
|
-
file_path = uri.full_path
|
|
26
|
-
|
|
27
|
-
# We only show symbols declared in the workspace
|
|
28
|
-
in_dependencies = file_path && !not_in_dependencies?(file_path)
|
|
29
|
-
next if in_dependencies
|
|
30
|
-
|
|
31
|
-
# We should never show private symbols when searching the entire workspace
|
|
32
|
-
next if entry.private?
|
|
33
|
-
|
|
23
|
+
fuzzy_search.filter_map do |entry|
|
|
34
24
|
kind = kind_for_entry(entry)
|
|
35
25
|
loc = entry.location
|
|
36
26
|
|
|
@@ -44,7 +34,7 @@ module RubyLsp
|
|
|
44
34
|
container_name: container.join("::"),
|
|
45
35
|
kind: kind,
|
|
46
36
|
location: Interface::Location.new(
|
|
47
|
-
uri: uri.to_s,
|
|
37
|
+
uri: entry.uri.to_s,
|
|
48
38
|
range: Interface::Range.new(
|
|
49
39
|
start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column),
|
|
50
40
|
end: Interface::Position.new(line: loc.end_line - 1, character: loc.end_column),
|
|
@@ -53,6 +43,24 @@ module RubyLsp
|
|
|
53
43
|
)
|
|
54
44
|
end
|
|
55
45
|
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
#: -> Array[RubyIndexer::Entry]
|
|
50
|
+
def fuzzy_search
|
|
51
|
+
@index.fuzzy_search(@query) do |entry|
|
|
52
|
+
file_path = entry.uri.full_path
|
|
53
|
+
|
|
54
|
+
# We only show symbols declared in the workspace
|
|
55
|
+
in_dependencies = file_path && !not_in_dependencies?(file_path)
|
|
56
|
+
next if in_dependencies
|
|
57
|
+
|
|
58
|
+
# We should never show private symbols when searching the entire workspace
|
|
59
|
+
next if entry.private?
|
|
60
|
+
|
|
61
|
+
true
|
|
62
|
+
end
|
|
63
|
+
end
|
|
56
64
|
end
|
|
57
65
|
end
|
|
58
66
|
end
|
|
@@ -270,12 +270,21 @@ module RubyLsp
|
|
|
270
270
|
#: (Hash[String, String] env, ?force_install: bool) -> Hash[String, String]
|
|
271
271
|
def run_bundle_install_directly(env, force_install: false)
|
|
272
272
|
RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
|
|
273
|
+
|
|
274
|
+
# The should_bundle_update? check needs to run on the original Bundler environment, but everything else (like
|
|
275
|
+
# updating or running install) requires the modified environment. Here we compute the check ahead of time and
|
|
276
|
+
# merge the environment to ensure correct results.
|
|
277
|
+
#
|
|
278
|
+
# The symptoms of having these operations in the wrong order is seeing unwanted modifications in the application's
|
|
279
|
+
# main lockfile because we accidentally run update on the main bundle instead of the composed one.
|
|
280
|
+
needs_update = should_bundle_update?
|
|
281
|
+
ENV.merge!(env)
|
|
282
|
+
|
|
273
283
|
return update(env) if @needs_update_path.exist?
|
|
274
284
|
|
|
275
285
|
# The ENV can only be merged after checking if an update is required because we depend on the original value of
|
|
276
286
|
# ENV["BUNDLE_GEMFILE"], which gets overridden after the merge
|
|
277
|
-
FileUtils.touch(@needs_update_path) if
|
|
278
|
-
ENV.merge!(env)
|
|
287
|
+
FileUtils.touch(@needs_update_path) if needs_update
|
|
279
288
|
|
|
280
289
|
$stderr.puts("Ruby LSP> Checking if the composed bundle is satisfied...")
|
|
281
290
|
missing_gems = bundle_check
|
|
@@ -35,10 +35,10 @@ module RubyLsp
|
|
|
35
35
|
@io = begin
|
|
36
36
|
# The environment variable is only used for tests. The extension always writes to the temporary file
|
|
37
37
|
if port
|
|
38
|
-
|
|
38
|
+
socket(port)
|
|
39
39
|
elsif File.exist?(port_db_path)
|
|
40
40
|
db = JSON.load_file(port_db_path)
|
|
41
|
-
|
|
41
|
+
socket(db[Dir.pwd])
|
|
42
42
|
else
|
|
43
43
|
# For tests that don't spawn the TCP server
|
|
44
44
|
require "stringio"
|
|
@@ -209,6 +209,14 @@ module RubyLsp
|
|
|
209
209
|
|
|
210
210
|
private
|
|
211
211
|
|
|
212
|
+
#: (String) -> TCPSocket
|
|
213
|
+
def socket(port)
|
|
214
|
+
socket = TCPSocket.new("localhost", port)
|
|
215
|
+
socket.binmode
|
|
216
|
+
socket.sync = true
|
|
217
|
+
socket
|
|
218
|
+
end
|
|
219
|
+
|
|
212
220
|
#: (String?, **untyped) -> void
|
|
213
221
|
def send_message(method_name, **params)
|
|
214
222
|
json_message = { method: method_name, params: params }.to_json
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby-lsp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.26.
|
|
4
|
+
version: 0.26.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shopify
|
|
@@ -163,6 +163,7 @@ files:
|
|
|
163
163
|
- lib/ruby_lsp/requests/support/annotation.rb
|
|
164
164
|
- lib/ruby_lsp/requests/support/common.rb
|
|
165
165
|
- lib/ruby_lsp/requests/support/formatter.rb
|
|
166
|
+
- lib/ruby_lsp/requests/support/package_url.rb
|
|
166
167
|
- lib/ruby_lsp/requests/support/rubocop_diagnostic.rb
|
|
167
168
|
- lib/ruby_lsp/requests/support/rubocop_formatter.rb
|
|
168
169
|
- lib/ruby_lsp/requests/support/rubocop_runner.rb
|