ruby-lsp 0.20.0 → 0.20.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp +7 -1
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +63 -12
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +10 -16
- data/lib/ruby_indexer/test/index_test.rb +89 -0
- data/lib/ruby_lsp/requests/references.rb +2 -0
- data/lib/ruby_lsp/requests/rename.rb +2 -0
- data/lib/ruby_lsp/setup_bundler.rb +30 -8
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 58327a9f9a3d85375cbbf81e3e170a948d5c8dfbdcdcac0a1fdef5f685d20c95
|
4
|
+
data.tar.gz: 45572eb6645bce73d2079bed65f0d92c98921517926b968bba96788769a3aada
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c506eeff4d24060e3afdb4ad9da319c12b725050c8f25a9348ec7dea9082f8adced7671b7b1eb8f7f09412942139e47a2df5059b5672d2b6a75f0ffc5a59885d
|
7
|
+
data.tar.gz: d9a89ce24946e5f9cc47b5a48086f6fd977176d31332a0b634950dc00a41fc9a36e4753eae2896e2da0129e92bff4278265eaa970252a5292c783e5bc82258e9
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.20.
|
1
|
+
0.20.1
|
data/exe/ruby-lsp
CHANGED
@@ -63,7 +63,13 @@ if ENV["BUNDLE_GEMFILE"].nil?
|
|
63
63
|
exit(78)
|
64
64
|
end
|
65
65
|
|
66
|
-
|
66
|
+
base_bundle = if env["BUNDLER_VERSION"]
|
67
|
+
"bundle _#{env["BUNDLER_VERSION"]}_"
|
68
|
+
else
|
69
|
+
"bundle"
|
70
|
+
end
|
71
|
+
|
72
|
+
exit exec(env, "#{base_bundle} exec ruby-lsp #{original_args.join(" ")}")
|
67
73
|
end
|
68
74
|
|
69
75
|
require "ruby_lsp/load_sorbet"
|
@@ -28,7 +28,17 @@ module RubyIndexer
|
|
28
28
|
@encoding = T.let(Encoding::UTF_8, Encoding)
|
29
29
|
@excluded_gems = T.let(initial_excluded_gems, T::Array[String])
|
30
30
|
@included_gems = T.let([], T::Array[String])
|
31
|
-
|
31
|
+
|
32
|
+
@excluded_patterns = T.let(
|
33
|
+
[
|
34
|
+
File.join("**", "*_test.rb"),
|
35
|
+
File.join("node_modules", "**", "*"),
|
36
|
+
File.join("spec", "**", "*"),
|
37
|
+
File.join("test", "**", "*"),
|
38
|
+
File.join("tmp", "**", "*"),
|
39
|
+
],
|
40
|
+
T::Array[String],
|
41
|
+
)
|
32
42
|
|
33
43
|
path = Bundler.settings["path"]
|
34
44
|
if path
|
@@ -56,6 +66,21 @@ module RubyIndexer
|
|
56
66
|
)
|
57
67
|
end
|
58
68
|
|
69
|
+
sig { returns(String) }
|
70
|
+
def merged_excluded_file_pattern
|
71
|
+
# This regex looks for @excluded_patterns that follow the format of "something/**/*", where
|
72
|
+
# "something" is one or more non-"/"
|
73
|
+
#
|
74
|
+
# Returns "/path/to/workspace/{tmp,node_modules}/**/*"
|
75
|
+
@excluded_patterns
|
76
|
+
.filter_map do |pattern|
|
77
|
+
next if File.absolute_path?(pattern)
|
78
|
+
|
79
|
+
pattern.match(%r{\A([^/]+)/\*\*/\*\z})&.captures&.first
|
80
|
+
end
|
81
|
+
.then { |dirs| File.join(@workspace_path, "{#{dirs.join(",")}}/**/*") }
|
82
|
+
end
|
83
|
+
|
59
84
|
sig { returns(T::Array[IndexablePath]) }
|
60
85
|
def indexables
|
61
86
|
excluded_gems = @excluded_gems - @included_gems
|
@@ -64,21 +89,47 @@ module RubyIndexer
|
|
64
89
|
# NOTE: indexing the patterns (both included and excluded) needs to happen before indexing gems, otherwise we risk
|
65
90
|
# having duplicates if BUNDLE_PATH is set to a folder inside the project structure
|
66
91
|
|
92
|
+
flags = File::FNM_PATHNAME | File::FNM_EXTGLOB
|
93
|
+
|
94
|
+
# In order to speed up indexing, only traverse into top-level directories that are not entirely excluded.
|
95
|
+
# For example, if "tmp/**/*" is excluded, we don't need to traverse into "tmp" at all. However, if
|
96
|
+
# "vendor/bundle/**/*" is excluded, we will traverse all of "vendor" and `reject!` out all "vendor/bundle" entries
|
97
|
+
# later.
|
98
|
+
excluded_pattern = merged_excluded_file_pattern
|
99
|
+
included_paths = Dir.glob(File.join(@workspace_path, "*/"), flags)
|
100
|
+
.filter_map do |included_path|
|
101
|
+
next if File.fnmatch?(excluded_pattern, included_path, flags)
|
102
|
+
|
103
|
+
relative_path = included_path
|
104
|
+
.delete_prefix(@workspace_path)
|
105
|
+
.tap { |path| path.delete_prefix!("/") }
|
106
|
+
|
107
|
+
[included_path, relative_path]
|
108
|
+
end
|
109
|
+
|
110
|
+
indexables = T.let([], T::Array[IndexablePath])
|
111
|
+
|
67
112
|
# Add user specified patterns
|
68
|
-
|
113
|
+
@included_patterns.each do |pattern|
|
69
114
|
load_path_entry = T.let(nil, T.nilable(String))
|
70
115
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
# on repositories that define multiple gems, like Rails. All frameworks are defined inside the current
|
76
|
-
# workspace directory, but each one of them belongs to a different $LOAD_PATH entry
|
77
|
-
if load_path_entry.nil? || !path.start_with?(load_path_entry)
|
78
|
-
load_path_entry = $LOAD_PATH.find { |load_path| path.start_with?(load_path) }
|
79
|
-
end
|
116
|
+
included_paths.each do |included_path, relative_path|
|
117
|
+
relative_pattern = pattern.delete_prefix(File.join(relative_path, "/"))
|
118
|
+
|
119
|
+
next unless pattern.start_with?("**") || pattern.start_with?(relative_path)
|
80
120
|
|
81
|
-
|
121
|
+
Dir.glob(File.join(included_path, relative_pattern), flags).each do |path|
|
122
|
+
path = File.expand_path(path)
|
123
|
+
# All entries for the same pattern match the same $LOAD_PATH entry. Since searching the $LOAD_PATH for every
|
124
|
+
# entry is expensive, we memoize it until we find a path that doesn't belong to that $LOAD_PATH. This
|
125
|
+
# happens on repositories that define multiple gems, like Rails. All frameworks are defined inside the
|
126
|
+
# current workspace directory, but each one of them belongs to a different $LOAD_PATH entry
|
127
|
+
if load_path_entry.nil? || !path.start_with?(load_path_entry)
|
128
|
+
load_path_entry = $LOAD_PATH.find { |load_path| path.start_with?(load_path) }
|
129
|
+
end
|
130
|
+
|
131
|
+
indexables << IndexablePath.new(load_path_entry, path)
|
132
|
+
end
|
82
133
|
end
|
83
134
|
end
|
84
135
|
|
@@ -980,29 +980,23 @@ module RubyIndexer
|
|
980
980
|
# nesting
|
981
981
|
sig { params(name: String, nesting: T::Array[String]).returns(String) }
|
982
982
|
def build_non_redundant_full_name(name, nesting)
|
983
|
+
# If there's no nesting, then we can just return the name as is
|
983
984
|
return name if nesting.empty?
|
984
985
|
|
985
|
-
namespace = nesting.join("::")
|
986
|
-
|
987
986
|
# If the name is not qualified, we can just concatenate the nesting and the name
|
988
|
-
return "#{
|
987
|
+
return "#{nesting.join("::")}::#{name}" unless name.include?("::")
|
989
988
|
|
990
989
|
name_parts = name.split("::")
|
990
|
+
first_redundant_part = nesting.index(name_parts[0])
|
991
991
|
|
992
|
-
#
|
993
|
-
|
992
|
+
# If there are no redundant parts between the name and the nesting, then the full name is both combined
|
993
|
+
return "#{nesting.join("::")}::#{name}" unless first_redundant_part
|
994
994
|
|
995
|
-
if
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
# No parts of the nesting are in the name, we can concatenate the namespace and the name
|
1001
|
-
"#{namespace}::#{name}"
|
1002
|
-
else
|
1003
|
-
# The name includes some parts of the nesting. We need to remove the redundant parts
|
1004
|
-
"#{namespace}::#{T.must(name_parts[index..-1]).join("::")}"
|
1005
|
-
end
|
995
|
+
# Otherwise, push all of the leading parts of the nesting that aren't redundant into the name. For example, if we
|
996
|
+
# have a reference to `Foo::Bar` inside the `[Namespace, Foo]` nesting, then only the `Foo` part is redundant, but
|
997
|
+
# we still need to include the `Namespace` part
|
998
|
+
T.unsafe(name_parts).unshift(*nesting[0...first_redundant_part])
|
999
|
+
name_parts.join("::")
|
1006
1000
|
end
|
1007
1001
|
|
1008
1002
|
sig do
|
@@ -1934,5 +1934,94 @@ module RubyIndexer
|
|
1934
1934
|
real_namespace = @index.follow_aliased_namespace("Namespace::Second")
|
1935
1935
|
assert_equal("First::Second", real_namespace)
|
1936
1936
|
end
|
1937
|
+
|
1938
|
+
def test_resolving_alias_to_non_existing_namespace
|
1939
|
+
index(<<~RUBY)
|
1940
|
+
module Namespace
|
1941
|
+
class Foo
|
1942
|
+
module InnerNamespace
|
1943
|
+
Constants = Namespace::Foo::Constants
|
1944
|
+
end
|
1945
|
+
end
|
1946
|
+
end
|
1947
|
+
RUBY
|
1948
|
+
|
1949
|
+
entry = @index.resolve("Constants", ["Namespace", "Foo", "InnerNamespace"])&.first
|
1950
|
+
assert_instance_of(Entry::UnresolvedConstantAlias, entry)
|
1951
|
+
|
1952
|
+
entry = @index.resolve("Namespace::Foo::Constants", ["Namespace", "Foo", "InnerNamespace"])&.first
|
1953
|
+
assert_nil(entry)
|
1954
|
+
end
|
1955
|
+
|
1956
|
+
def test_resolving_alias_to_existing_constant_from_inner_namespace
|
1957
|
+
index(<<~RUBY)
|
1958
|
+
module Parent
|
1959
|
+
CONST = 123
|
1960
|
+
end
|
1961
|
+
|
1962
|
+
module First
|
1963
|
+
module Namespace
|
1964
|
+
class Foo
|
1965
|
+
include Parent
|
1966
|
+
|
1967
|
+
module InnerNamespace
|
1968
|
+
Constants = Namespace::Foo::CONST
|
1969
|
+
end
|
1970
|
+
end
|
1971
|
+
end
|
1972
|
+
end
|
1973
|
+
RUBY
|
1974
|
+
|
1975
|
+
entry = @index.resolve("Namespace::Foo::CONST", ["First", "Namespace", "Foo", "InnerNamespace"])&.first
|
1976
|
+
assert_equal("Parent::CONST", entry.name)
|
1977
|
+
assert_instance_of(Entry::Constant, entry)
|
1978
|
+
end
|
1979
|
+
|
1980
|
+
def test_build_non_redundant_name
|
1981
|
+
assert_equal(
|
1982
|
+
"Namespace::Foo::Constants",
|
1983
|
+
@index.send(
|
1984
|
+
:build_non_redundant_full_name,
|
1985
|
+
"Namespace::Foo::Constants",
|
1986
|
+
["Namespace", "Foo", "InnerNamespace"],
|
1987
|
+
),
|
1988
|
+
)
|
1989
|
+
|
1990
|
+
assert_equal(
|
1991
|
+
"Namespace::Foo::Constants",
|
1992
|
+
@index.send(
|
1993
|
+
:build_non_redundant_full_name,
|
1994
|
+
"Namespace::Foo::Constants",
|
1995
|
+
["Namespace", "Foo"],
|
1996
|
+
),
|
1997
|
+
)
|
1998
|
+
|
1999
|
+
assert_equal(
|
2000
|
+
"Namespace::Foo::Constants",
|
2001
|
+
@index.send(
|
2002
|
+
:build_non_redundant_full_name,
|
2003
|
+
"Foo::Constants",
|
2004
|
+
["Namespace", "Foo"],
|
2005
|
+
),
|
2006
|
+
)
|
2007
|
+
|
2008
|
+
assert_equal(
|
2009
|
+
"Bar::Namespace::Foo::Constants",
|
2010
|
+
@index.send(
|
2011
|
+
:build_non_redundant_full_name,
|
2012
|
+
"Namespace::Foo::Constants",
|
2013
|
+
["Bar"],
|
2014
|
+
),
|
2015
|
+
)
|
2016
|
+
|
2017
|
+
assert_equal(
|
2018
|
+
"First::Namespace::Foo::Constants",
|
2019
|
+
@index.send(
|
2020
|
+
:build_non_redundant_full_name,
|
2021
|
+
"Namespace::Foo::Constants",
|
2022
|
+
["First", "Namespace", "Foo", "InnerNamespace"],
|
2023
|
+
),
|
2024
|
+
)
|
2025
|
+
end
|
1937
2026
|
end
|
1938
2027
|
end
|
@@ -78,6 +78,8 @@ module RubyLsp
|
|
78
78
|
|
79
79
|
parse_result = Prism.parse_file(path)
|
80
80
|
collect_references(reference_target, parse_result, uri)
|
81
|
+
rescue Errno::EISDIR, Errno::ENOENT
|
82
|
+
# If `path` is a directory, just ignore it and continue. If the file doesn't exist, then we also ignore it.
|
81
83
|
end
|
82
84
|
|
83
85
|
@store.each do |_uri, document|
|
@@ -147,6 +147,8 @@ module RubyLsp
|
|
147
147
|
parse_result = Prism.parse_file(path)
|
148
148
|
edits = collect_changes(target, parse_result, name, uri)
|
149
149
|
changes[uri.to_s] = edits unless edits.empty?
|
150
|
+
rescue Errno::EISDIR, Errno::ENOENT
|
151
|
+
# If `path` is a directory, just ignore it and continue. If the file doesn't exist, then we also ignore it.
|
150
152
|
end
|
151
153
|
|
152
154
|
@store.each do |uri, document|
|
@@ -48,7 +48,9 @@ module RubyLsp
|
|
48
48
|
@lockfile_hash_path = T.let(@custom_dir + "main_lockfile_hash", Pathname)
|
49
49
|
@last_updated_path = T.let(@custom_dir + "last_updated", Pathname)
|
50
50
|
|
51
|
-
|
51
|
+
dependencies, bundler_version = load_dependencies
|
52
|
+
@dependencies = T.let(dependencies, T::Hash[String, T.untyped])
|
53
|
+
@bundler_version = T.let(bundler_version, T.nilable(Gem::Version))
|
52
54
|
@rails_app = T.let(rails_app?, T::Boolean)
|
53
55
|
@retry = T.let(false, T::Boolean)
|
54
56
|
end
|
@@ -156,14 +158,15 @@ module RubyLsp
|
|
156
158
|
@custom_gemfile.write(content) unless @custom_gemfile.exist? && @custom_gemfile.read == content
|
157
159
|
end
|
158
160
|
|
159
|
-
sig { returns(T::Hash[String, T.untyped]) }
|
161
|
+
sig { returns([T::Hash[String, T.untyped], T.nilable(Gem::Version)]) }
|
160
162
|
def load_dependencies
|
161
|
-
return {} unless @lockfile&.exist?
|
163
|
+
return [{}, nil] unless @lockfile&.exist?
|
162
164
|
|
163
165
|
# We need to parse the Gemfile.lock manually here. If we try to do `bundler/setup` to use something more
|
164
166
|
# convenient, we may end up with issues when the globally installed `ruby-lsp` version mismatches the one included
|
165
167
|
# in the `Gemfile`
|
166
|
-
|
168
|
+
lockfile_parser = Bundler::LockfileParser.new(@lockfile.read)
|
169
|
+
dependencies = lockfile_parser.dependencies
|
167
170
|
|
168
171
|
# When working on a gem, the `ruby-lsp` might be listed as a dependency in the gemspec. We need to make sure we
|
169
172
|
# check those as well or else we may get version mismatch errors. Notice that bundler allows more than one
|
@@ -172,7 +175,7 @@ module RubyLsp
|
|
172
175
|
dependencies.merge!(Bundler.load_gemspec(path).dependencies.to_h { |dep| [dep.name, dep] })
|
173
176
|
end
|
174
177
|
|
175
|
-
dependencies
|
178
|
+
[dependencies, lockfile_parser.bundler_version]
|
176
179
|
end
|
177
180
|
|
178
181
|
sig { params(bundle_gemfile: T.nilable(Pathname)).returns(T::Hash[String, String]) }
|
@@ -188,6 +191,16 @@ module RubyLsp
|
|
188
191
|
env["BUNDLE_PATH"] = File.expand_path(env["BUNDLE_PATH"], @project_path)
|
189
192
|
end
|
190
193
|
|
194
|
+
# If there's a Bundler version locked, then we need to use that one to run bundle commands, so that the composed
|
195
|
+
# lockfile is also locked to the same version. This avoids Bundler restarts on version mismatches
|
196
|
+
base_bundle = if @bundler_version
|
197
|
+
env["BUNDLER_VERSION"] = @bundler_version.to_s
|
198
|
+
install_bundler_if_needed
|
199
|
+
"bundle _#{@bundler_version}_"
|
200
|
+
else
|
201
|
+
"bundle"
|
202
|
+
end
|
203
|
+
|
191
204
|
# If `ruby-lsp` and `debug` (and potentially `ruby-lsp-rails`) are already in the Gemfile, then we shouldn't try
|
192
205
|
# to upgrade them or else we'll produce undesired source control changes. If the custom bundle was just created
|
193
206
|
# and any of `ruby-lsp`, `ruby-lsp-rails` or `debug` weren't a part of the Gemfile, then we need to run `bundle
|
@@ -196,13 +209,13 @@ module RubyLsp
|
|
196
209
|
|
197
210
|
# When not updating, we run `(bundle check || bundle install)`
|
198
211
|
# When updating, we run `((bundle check && bundle update ruby-lsp debug) || bundle install)`
|
199
|
-
command = +"(
|
212
|
+
command = +"(#{base_bundle} check"
|
200
213
|
|
201
214
|
if should_bundle_update?
|
202
215
|
# If any of `ruby-lsp`, `ruby-lsp-rails` or `debug` are not in the Gemfile, try to update them to the latest
|
203
216
|
# version
|
204
217
|
command.prepend("(")
|
205
|
-
command << " &&
|
218
|
+
command << " && #{base_bundle} update "
|
206
219
|
command << "ruby-lsp " unless @dependencies["ruby-lsp"]
|
207
220
|
command << "debug " unless @dependencies["debug"]
|
208
221
|
command << "ruby-lsp-rails " if @rails_app && !@dependencies["ruby-lsp-rails"]
|
@@ -212,7 +225,7 @@ module RubyLsp
|
|
212
225
|
@last_updated_path.write(Time.now.iso8601)
|
213
226
|
end
|
214
227
|
|
215
|
-
command << " ||
|
228
|
+
command << " || #{base_bundle} install) "
|
216
229
|
|
217
230
|
# Redirect stdout to stderr to prevent going into an infinite loop. The extension might confuse stdout output with
|
218
231
|
# responses
|
@@ -259,6 +272,15 @@ module RubyLsp
|
|
259
272
|
end
|
260
273
|
end
|
261
274
|
|
275
|
+
sig { void }
|
276
|
+
def install_bundler_if_needed
|
277
|
+
# Try to find the bundler version specified in the lockfile in installed gems. If not found, install it
|
278
|
+
requirement = Gem::Requirement.new(@bundler_version.to_s)
|
279
|
+
return if Gem::Specification.any? { |s| s.name == "bundler" && requirement =~ s.version }
|
280
|
+
|
281
|
+
Gem.install("bundler", @bundler_version.to_s)
|
282
|
+
end
|
283
|
+
|
262
284
|
sig { returns(T::Boolean) }
|
263
285
|
def should_bundle_update?
|
264
286
|
# If `ruby-lsp`, `ruby-lsp-rails` and `debug` are in the Gemfile, then we shouldn't try to upgrade them or else it
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.20.
|
4
|
+
version: 0.20.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-10-
|
11
|
+
date: 2024-10-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: language_server-protocol
|