ruby-lsp 0.20.0 → 0.20.1
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/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
|