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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d43d431cf15d817d80ebb8564f90a748b871ad3c2c278482d4b1b5c831b7bcd2
4
- data.tar.gz: 28ab582396723d582f7909ad277300430b6e14e6a281408be21a71f4d768930f
3
+ metadata.gz: 58327a9f9a3d85375cbbf81e3e170a948d5c8dfbdcdcac0a1fdef5f685d20c95
4
+ data.tar.gz: 45572eb6645bce73d2079bed65f0d92c98921517926b968bba96788769a3aada
5
5
  SHA512:
6
- metadata.gz: 73a87212da50592a6b201a5f21ea559de74fb12ed795428e06392a4375aa73015a103aedd56734b9b05a2f0a27fcb7366b16107ea9f1a080b3459e2eff1dcd88
7
- data.tar.gz: 7ad77f3dccbabba9cb306c73ccefca4e84e9ff480bc0594d20a1c6e03727948119f7accefc522e5ed25d6de1d21fff03b18c704c7c8908b33eb92cfc5b2c665d
6
+ metadata.gz: c506eeff4d24060e3afdb4ad9da319c12b725050c8f25a9348ec7dea9082f8adced7671b7b1eb8f7f09412942139e47a2df5059b5672d2b6a75f0ffc5a59885d
7
+ data.tar.gz: d9a89ce24946e5f9cc47b5a48086f6fd977176d31332a0b634950dc00a41fc9a36e4753eae2896e2da0129e92bff4278265eaa970252a5292c783e5bc82258e9
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.20.0
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
- exit exec(env, "bundle exec ruby-lsp #{original_args.join(" ")}")
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
- @excluded_patterns = T.let([File.join("**", "*_test.rb"), File.join("tmp", "**", "*")], T::Array[String])
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
- indexables = @included_patterns.flat_map do |pattern|
113
+ @included_patterns.each do |pattern|
69
114
  load_path_entry = T.let(nil, T.nilable(String))
70
115
 
71
- Dir.glob(File.join(@workspace_path, pattern), File::FNM_PATHNAME | File::FNM_EXTGLOB).map! do |path|
72
- path = File.expand_path(path)
73
- # All entries for the same pattern match the same $LOAD_PATH entry. Since searching the $LOAD_PATH for every
74
- # entry is expensive, we memoize it until we find a path that doesn't belong to that $LOAD_PATH. This happens
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
- IndexablePath.new(load_path_entry, path)
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 "#{namespace}::#{name}" unless name.include?("::")
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
- # Find the first part of the name that is not in the nesting
993
- index = name_parts.index { |part| !nesting.include?(part) }
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 index.nil?
996
- # All parts of the nesting are redundant because they are already present in the name. We can return the name
997
- # directly
998
- name
999
- elsif index == 0
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
- @dependencies = T.let(load_dependencies, T::Hash[String, T.untyped])
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
- dependencies = Bundler::LockfileParser.new(@lockfile.read).dependencies
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 = +"(bundle check"
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 << " && bundle update "
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 << " || bundle install) "
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.0
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 00:00:00.000000000 Z
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