ruby-lsp 0.17.14 → 0.17.15
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/configuration.rb +28 -9
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +5 -3
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +21 -0
- data/lib/ruby_indexer/test/configuration_test.rb +41 -7
- data/lib/ruby_lsp/listeners/definition.rb +8 -5
- data/lib/ruby_lsp/server.rb +4 -4
- data/lib/ruby_lsp/setup_bundler.rb +4 -4
- 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: 43d715172510bba67801670dbfc574eed9851b9852aaea3f6e210acd74734af4
|
4
|
+
data.tar.gz: b35b3a55c3d65987390e4b125a090ff8f6da902bfa7ab9c08eb9eb8c72d5157f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef8a2ca24755eef8c5ed1146f1f6c40ec725988e9dd6a51efb326583e5606a221f733625f08d4dad5bb6091675c39fb18a485ff8c0bed1dc0b2e221a4390c585
|
7
|
+
data.tar.gz: 16101462585105a9a201f6341908f0b238f77f4acaa6dac723c95e35fcddc7c7b68d937bead5beecc14ae6bcac276b2f813523304f7eaded3132d3aa07eb943b
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.17.
|
1
|
+
0.17.15
|
@@ -16,15 +16,24 @@ module RubyIndexer
|
|
16
16
|
T::Hash[String, T.untyped],
|
17
17
|
)
|
18
18
|
|
19
|
+
sig { params(workspace_path: String).void }
|
20
|
+
attr_writer :workspace_path
|
21
|
+
|
19
22
|
sig { void }
|
20
23
|
def initialize
|
24
|
+
@workspace_path = T.let(Dir.pwd, String)
|
21
25
|
@excluded_gems = T.let(initial_excluded_gems, T::Array[String])
|
22
26
|
@included_gems = T.let([], T::Array[String])
|
23
|
-
@excluded_patterns = T.let([File.join("**", "*_test.rb"), File.join("
|
27
|
+
@excluded_patterns = T.let([File.join("**", "*_test.rb"), File.join("tmp", "**", "*")], T::Array[String])
|
28
|
+
|
24
29
|
path = Bundler.settings["path"]
|
25
|
-
|
30
|
+
if path
|
31
|
+
# Substitute Windows backslashes into forward slashes, which are used in glob patterns
|
32
|
+
glob = path.gsub(/[\\]+/, "/")
|
33
|
+
@excluded_patterns << File.join(glob, "**", "*.rb")
|
34
|
+
end
|
26
35
|
|
27
|
-
@included_patterns = T.let([File.join(
|
36
|
+
@included_patterns = T.let([File.join("**", "*.rb")], T::Array[String])
|
28
37
|
@excluded_magic_comments = T.let(
|
29
38
|
[
|
30
39
|
"frozen_string_literal:",
|
@@ -55,12 +64,12 @@ module RubyIndexer
|
|
55
64
|
indexables = @included_patterns.flat_map do |pattern|
|
56
65
|
load_path_entry = T.let(nil, T.nilable(String))
|
57
66
|
|
58
|
-
Dir.glob(pattern, File::FNM_PATHNAME | File::FNM_EXTGLOB).map! do |path|
|
67
|
+
Dir.glob(File.join(@workspace_path, pattern), File::FNM_PATHNAME | File::FNM_EXTGLOB).map! do |path|
|
59
68
|
path = File.expand_path(path)
|
60
69
|
# All entries for the same pattern match the same $LOAD_PATH entry. Since searching the $LOAD_PATH for every
|
61
70
|
# entry is expensive, we memoize it until we find a path that doesn't belong to that $LOAD_PATH. This happens
|
62
|
-
# on repositories that define multiple gems, like Rails. All frameworks are defined inside the
|
63
|
-
# each one of them belongs to a different $LOAD_PATH entry
|
71
|
+
# on repositories that define multiple gems, like Rails. All frameworks are defined inside the current
|
72
|
+
# workspace directory, but each one of them belongs to a different $LOAD_PATH entry
|
64
73
|
if load_path_entry.nil? || !path.start_with?(load_path_entry)
|
65
74
|
load_path_entry = $LOAD_PATH.find { |load_path| path.start_with?(load_path) }
|
66
75
|
end
|
@@ -69,9 +78,19 @@ module RubyIndexer
|
|
69
78
|
end
|
70
79
|
end
|
71
80
|
|
81
|
+
# If the patterns are relative, we make it relative to the workspace path. If they are absolute, then we shouldn't
|
82
|
+
# concatenate anything
|
83
|
+
excluded_patterns = @excluded_patterns.map do |pattern|
|
84
|
+
if File.absolute_path?(pattern)
|
85
|
+
pattern
|
86
|
+
else
|
87
|
+
File.join(@workspace_path, pattern)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
72
91
|
# Remove user specified patterns
|
73
92
|
indexables.reject! do |indexable|
|
74
|
-
|
93
|
+
excluded_patterns.any? do |pattern|
|
75
94
|
File.fnmatch?(pattern, indexable.full_path, File::FNM_PATHNAME | File::FNM_EXTGLOB)
|
76
95
|
end
|
77
96
|
end
|
@@ -122,7 +141,7 @@ module RubyIndexer
|
|
122
141
|
# When working on a gem, it will be included in the locked_gems list. Since these are the project's own files,
|
123
142
|
# we have already included and handled exclude patterns for it and should not re-include or it'll lead to
|
124
143
|
# duplicates or accidentally ignoring exclude patterns
|
125
|
-
next if spec.full_gem_path ==
|
144
|
+
next if spec.full_gem_path == @workspace_path
|
126
145
|
|
127
146
|
indexables.concat(
|
128
147
|
spec.require_paths.flat_map do |require_path|
|
@@ -185,7 +204,7 @@ module RubyIndexer
|
|
185
204
|
# If the dependency is prerelease, `to_spec` may return `nil` due to a bug in older version of Bundler/RubyGems:
|
186
205
|
# https://github.com/Shopify/ruby-lsp/issues/1246
|
187
206
|
this_gem = Bundler.definition.dependencies.find do |d|
|
188
|
-
d.to_spec&.full_gem_path ==
|
207
|
+
d.to_spec&.full_gem_path == @workspace_path
|
189
208
|
rescue Gem::MissingSpecError
|
190
209
|
false
|
191
210
|
end
|
@@ -152,16 +152,17 @@ module RubyIndexer
|
|
152
152
|
|
153
153
|
if current_owner
|
154
154
|
expression = node.expression
|
155
|
-
|
155
|
+
name = (expression.is_a?(Prism::SelfNode) ? "<Class:#{@stack.last}>" : "<Class:#{expression.slice}>")
|
156
|
+
real_nesting = actual_nesting(name)
|
156
157
|
|
157
|
-
existing_entries = T.cast(@index[
|
158
|
+
existing_entries = T.cast(@index[real_nesting.join("::")], T.nilable(T::Array[Entry::SingletonClass]))
|
158
159
|
|
159
160
|
if existing_entries
|
160
161
|
entry = T.must(existing_entries.first)
|
161
162
|
entry.update_singleton_information(node.location, expression.location, collect_comments(node))
|
162
163
|
else
|
163
164
|
entry = Entry::SingletonClass.new(
|
164
|
-
|
165
|
+
real_nesting,
|
165
166
|
@file_path,
|
166
167
|
node.location,
|
167
168
|
expression.location,
|
@@ -172,6 +173,7 @@ module RubyIndexer
|
|
172
173
|
end
|
173
174
|
|
174
175
|
@owner_stack << entry
|
176
|
+
@stack << name
|
175
177
|
end
|
176
178
|
end
|
177
179
|
|
@@ -564,6 +564,27 @@ module RubyIndexer
|
|
564
564
|
assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:1-2:2-5")
|
565
565
|
end
|
566
566
|
|
567
|
+
def test_indexing_singletons_inside_top_level_references
|
568
|
+
index(<<~RUBY)
|
569
|
+
module ::Foo
|
570
|
+
class Bar
|
571
|
+
class << self
|
572
|
+
end
|
573
|
+
end
|
574
|
+
end
|
575
|
+
RUBY
|
576
|
+
|
577
|
+
# We want to explicitly verify that we didn't introduce the leading `::` by accident, but `Index#[]` deletes the
|
578
|
+
# prefix when we use `refute_entry`
|
579
|
+
entries = @index.instance_variable_get(:@entries)
|
580
|
+
refute(entries.key?("::Foo"))
|
581
|
+
refute(entries.key?("::Foo::Bar"))
|
582
|
+
refute(entries.key?("::Foo::Bar::<Class:Bar>"))
|
583
|
+
assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:5-3")
|
584
|
+
assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:1-2:4-5")
|
585
|
+
assert_entry("Foo::Bar::<Class:Bar>", Entry::SingletonClass, "/fake/path/foo.rb:2-4:3-7")
|
586
|
+
end
|
587
|
+
|
567
588
|
def test_indexing_namespaces_inside_nested_top_level_references
|
568
589
|
index(<<~RUBY)
|
569
590
|
class Baz
|
@@ -7,10 +7,12 @@ module RubyIndexer
|
|
7
7
|
class ConfigurationTest < Minitest::Test
|
8
8
|
def setup
|
9
9
|
@config = Configuration.new
|
10
|
+
@workspace_path = File.expand_path(File.join("..", "..", ".."), __dir__)
|
11
|
+
@config.workspace_path = @workspace_path
|
10
12
|
end
|
11
13
|
|
12
14
|
def test_load_configuration_executes_configure_block
|
13
|
-
@config.apply_config({ "excluded_patterns" => ["**/
|
15
|
+
@config.apply_config({ "excluded_patterns" => ["**/fixtures/**/*.rb"] })
|
14
16
|
indexables = @config.indexables
|
15
17
|
|
16
18
|
assert(indexables.none? { |indexable| indexable.full_path.include?("test/fixtures") })
|
@@ -25,7 +27,7 @@ module RubyIndexer
|
|
25
27
|
indexables = @config.indexables
|
26
28
|
|
27
29
|
# All paths should be expanded
|
28
|
-
assert(indexables.
|
30
|
+
assert(indexables.all? { |indexable| File.absolute_path?(indexable.full_path) })
|
29
31
|
end
|
30
32
|
|
31
33
|
def test_indexables_only_includes_gem_require_paths
|
@@ -71,17 +73,20 @@ module RubyIndexer
|
|
71
73
|
Bundler.settings.temporary(path: "vendor/bundle") do
|
72
74
|
config = Configuration.new
|
73
75
|
|
74
|
-
assert_includes(config.instance_variable_get(:@excluded_patterns), "
|
76
|
+
assert_includes(config.instance_variable_get(:@excluded_patterns), "vendor/bundle/**/*.rb")
|
75
77
|
end
|
76
78
|
end
|
77
79
|
|
78
80
|
def test_indexables_does_not_include_gems_own_installed_files
|
79
81
|
indexables = @config.indexables
|
82
|
+
indexables_inside_bundled_lsp = indexables.select do |indexable|
|
83
|
+
indexable.full_path.start_with?(Bundler.bundle_path.join("gems", "ruby-lsp").to_s)
|
84
|
+
end
|
80
85
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
86
|
+
assert_empty(
|
87
|
+
indexables_inside_bundled_lsp,
|
88
|
+
"Indexables should not include files from the gem currently being worked on. " \
|
89
|
+
"Included: #{indexables_inside_bundled_lsp.map(&:full_path)}",
|
85
90
|
)
|
86
91
|
end
|
87
92
|
|
@@ -126,5 +131,34 @@ module RubyIndexer
|
|
126
131
|
assert_match(regex, comment)
|
127
132
|
end
|
128
133
|
end
|
134
|
+
|
135
|
+
def test_indexables_respect_given_workspace_path
|
136
|
+
Dir.mktmpdir do |dir|
|
137
|
+
FileUtils.mkdir(File.join(dir, "ignore"))
|
138
|
+
FileUtils.touch(File.join(dir, "ignore", "file0.rb"))
|
139
|
+
FileUtils.touch(File.join(dir, "file1.rb"))
|
140
|
+
FileUtils.touch(File.join(dir, "file2.rb"))
|
141
|
+
|
142
|
+
@config.apply_config({ "excluded_patterns" => ["ignore/**/*.rb"] })
|
143
|
+
@config.workspace_path = dir
|
144
|
+
indexables = @config.indexables
|
145
|
+
|
146
|
+
assert(indexables.none? { |indexable| indexable.full_path.start_with?(File.join(dir, "ignore")) })
|
147
|
+
|
148
|
+
# After switching the workspace path, all indexables will be found in one of these places:
|
149
|
+
# - The new workspace path
|
150
|
+
# - The Ruby LSP's own code (because Bundler is requiring the dependency from source)
|
151
|
+
# - Bundled gems
|
152
|
+
# - Default gems
|
153
|
+
assert(
|
154
|
+
indexables.all? do |i|
|
155
|
+
i.full_path.start_with?(dir) ||
|
156
|
+
i.full_path.start_with?(File.join(Dir.pwd, "lib")) ||
|
157
|
+
i.full_path.start_with?(Bundler.bundle_path.to_s) ||
|
158
|
+
i.full_path.start_with?(RbConfig::CONFIG["rubylibdir"])
|
159
|
+
end,
|
160
|
+
)
|
161
|
+
end
|
162
|
+
end
|
129
163
|
end
|
130
164
|
end
|
@@ -208,10 +208,13 @@ module RubyLsp
|
|
208
208
|
def handle_method_definition(message, receiver_type, inherited_only: false)
|
209
209
|
methods = if receiver_type
|
210
210
|
@index.resolve_method(message, receiver_type.name, inherited_only: inherited_only)
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
211
|
+
end
|
212
|
+
|
213
|
+
# If the method doesn't have a receiver, or the guessed receiver doesn't have any matched candidates,
|
214
|
+
# then we provide a few candidates to jump to
|
215
|
+
# But we don't want to provide too many candidates, as it can be overwhelming
|
216
|
+
if receiver_type.nil? || (receiver_type.is_a?(TypeInferrer::GuessedType) && methods.nil?)
|
217
|
+
methods = @index[message]&.take(MAX_NUMBER_OF_DEFINITION_CANDIDATES_WITHOUT_RECEIVER)
|
215
218
|
end
|
216
219
|
|
217
220
|
return unless methods
|
@@ -250,7 +253,7 @@ module RubyLsp
|
|
250
253
|
when :require_relative
|
251
254
|
required_file = "#{node.content}.rb"
|
252
255
|
path = @uri.to_standardized_path
|
253
|
-
current_folder = path ? Pathname.new(CGI.unescape(path)).dirname :
|
256
|
+
current_folder = path ? Pathname.new(CGI.unescape(path)).dirname : @global_state.workspace_path
|
254
257
|
candidate = File.expand_path(File.join(current_folder, required_file))
|
255
258
|
|
256
259
|
@response_builder << Interface::Location.new(
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -879,7 +879,7 @@ module RubyLsp
|
|
879
879
|
# Indexing produces a high number of short lived object allocations. That might lead to some fragmentation and
|
880
880
|
# an unnecessarily expanded heap. Compacting ensures that the heap is as small as possible and that future
|
881
881
|
# allocations and garbage collections are faster
|
882
|
-
GC.compact
|
882
|
+
GC.compact unless @test_mode
|
883
883
|
|
884
884
|
# Always end the progress notification even if indexing failed or else it never goes away and the user has no
|
885
885
|
# way of dismissing it
|
@@ -1000,10 +1000,10 @@ module RubyLsp
|
|
1000
1000
|
|
1001
1001
|
return unless indexing_options
|
1002
1002
|
|
1003
|
+
configuration = @global_state.index.configuration
|
1004
|
+
configuration.workspace_path = @global_state.workspace_path
|
1003
1005
|
# The index expects snake case configurations, but VS Code standardizes on camel case settings
|
1004
|
-
|
1005
|
-
indexing_options.transform_keys { |key| key.to_s.gsub(/([A-Z])/, "_\\1").downcase },
|
1006
|
-
)
|
1006
|
+
configuration.apply_config(indexing_options.transform_keys { |key| key.to_s.gsub(/([A-Z])/, "_\\1").downcase })
|
1007
1007
|
end
|
1008
1008
|
end
|
1009
1009
|
end
|
@@ -43,7 +43,7 @@ module RubyLsp
|
|
43
43
|
@gemfile_name = T.let(@gemfile&.basename&.to_s || "Gemfile", String)
|
44
44
|
|
45
45
|
# Custom bundle paths
|
46
|
-
@custom_dir = T.let(Pathname.new(".ruby-lsp").expand_path(
|
46
|
+
@custom_dir = T.let(Pathname.new(".ruby-lsp").expand_path(@project_path), Pathname)
|
47
47
|
@custom_gemfile = T.let(@custom_dir + @gemfile_name, Pathname)
|
48
48
|
@custom_lockfile = T.let(@custom_dir + (@lockfile&.basename || "Gemfile.lock"), Pathname)
|
49
49
|
@lockfile_hash_path = T.let(@custom_dir + "main_lockfile_hash", Pathname)
|
@@ -183,14 +183,14 @@ module RubyLsp
|
|
183
183
|
# `.ruby-lsp` folder, which is not the user's intention. For example, if the path is configured as `vendor`, we
|
184
184
|
# want to install it in the top level `vendor` and not `.ruby-lsp/vendor`
|
185
185
|
path = Bundler.settings["path"]
|
186
|
-
expanded_path = File.expand_path(path,
|
186
|
+
expanded_path = File.expand_path(path, @project_path) if path
|
187
187
|
|
188
188
|
# Use the absolute `BUNDLE_PATH` to prevent accidentally creating unwanted folders under `.ruby-lsp`
|
189
189
|
env = {}
|
190
190
|
env["BUNDLE_GEMFILE"] = bundle_gemfile.to_s
|
191
191
|
env["BUNDLE_PATH"] = expanded_path if expanded_path
|
192
192
|
|
193
|
-
local_config_path = File.join(
|
193
|
+
local_config_path = File.join(@project_path, ".bundle")
|
194
194
|
env["BUNDLE_APP_CONFIG"] = local_config_path if Dir.exist?(local_config_path)
|
195
195
|
|
196
196
|
# If `ruby-lsp` and `debug` (and potentially `ruby-lsp-rails`) are already in the Gemfile, then we shouldn't try
|
@@ -286,7 +286,7 @@ module RubyLsp
|
|
286
286
|
sig { returns(T::Boolean) }
|
287
287
|
def rails_app?
|
288
288
|
config = Pathname.new("config/application.rb").expand_path
|
289
|
-
application_contents = config.read if config.exist?
|
289
|
+
application_contents = config.read(external_encoding: Encoding::UTF_8) if config.exist?
|
290
290
|
return false unless application_contents
|
291
291
|
|
292
292
|
/class .* < (::)?Rails::Application/.match?(application_contents)
|
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.17.
|
4
|
+
version: 0.17.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-08-
|
11
|
+
date: 2024-08-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: language_server-protocol
|