ruby-lsp 0.8.0 → 0.9.0

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.
@@ -6,6 +6,7 @@ require "bundler"
6
6
  require "fileutils"
7
7
  require "pathname"
8
8
  require "digest"
9
+ require "time"
9
10
 
10
11
  # This file is a script that will configure a custom bundle for the Ruby LSP. The custom bundle allows developers to use
11
12
  # the Ruby LSP without including the gem in their application's Gemfile while at the same time giving us access to the
@@ -17,15 +18,19 @@ module RubyLsp
17
18
 
18
19
  class BundleNotLocked < StandardError; end
19
20
 
20
- sig { params(project_path: String).void }
21
- def initialize(project_path)
21
+ FOUR_HOURS = T.let(4 * 60 * 60, Integer)
22
+
23
+ sig { params(project_path: String, branch: T.nilable(String)).void }
24
+ def initialize(project_path, branch: nil)
22
25
  @project_path = project_path
26
+ @branch = branch
23
27
 
24
28
  # Custom bundle paths
25
29
  @custom_dir = T.let(Pathname.new(".ruby-lsp").expand_path(Dir.pwd), Pathname)
26
30
  @custom_gemfile = T.let(@custom_dir + "Gemfile", Pathname)
27
31
  @custom_lockfile = T.let(@custom_dir + "Gemfile.lock", Pathname)
28
32
  @lockfile_hash_path = T.let(@custom_dir + "main_lockfile_hash", Pathname)
33
+ @last_updated_path = T.let(@custom_dir + "last_updated", Pathname)
29
34
 
30
35
  # Regular bundle paths
31
36
  @gemfile = T.let(
@@ -119,7 +124,9 @@ module RubyLsp
119
124
  end
120
125
 
121
126
  unless @dependencies["ruby-lsp"]
122
- parts << 'gem "ruby-lsp", require: false, group: :development'
127
+ ruby_lsp_entry = +'gem "ruby-lsp", require: false, group: :development'
128
+ ruby_lsp_entry << ", github: \"Shopify/ruby-lsp\", branch: \"#{@branch}\"" if @branch
129
+ parts << ruby_lsp_entry
123
130
  end
124
131
 
125
132
  unless @dependencies["debug"]
@@ -156,11 +163,12 @@ module RubyLsp
156
163
  # `.ruby-lsp` folder, which is not the user's intention. For example, if the path is configured as `vendor`, we
157
164
  # want to install it in the top level `vendor` and not `.ruby-lsp/vendor`
158
165
  path = Bundler.settings["path"]
166
+ expanded_path = File.expand_path(path, Dir.pwd) if path
159
167
 
160
168
  # Use the absolute `BUNDLE_PATH` to prevent accidentally creating unwanted folders under `.ruby-lsp`
161
169
  env = {}
162
170
  env["BUNDLE_GEMFILE"] = bundle_gemfile.to_s
163
- env["BUNDLE_PATH"] = File.expand_path(path, Dir.pwd) if path
171
+ env["BUNDLE_PATH"] = expanded_path if expanded_path
164
172
 
165
173
  # If both `ruby-lsp` and `debug` are already in the Gemfile, then we shouldn't try to upgrade them or else we'll
166
174
  # produce undesired source control changes. If the custom bundle was just created and either `ruby-lsp` or `debug`
@@ -169,15 +177,16 @@ module RubyLsp
169
177
  # custom `.ruby-lsp/Gemfile.lock` already exists and includes both gems
170
178
  command = +""
171
179
 
172
- if (@dependencies["ruby-lsp"] && @dependencies["debug"]) ||
173
- custom_bundle_dependencies["ruby-lsp"].nil? || custom_bundle_dependencies["debug"].nil?
180
+ if should_bundle_install?
174
181
  # Install gems using the custom bundle
175
- command << "bundle install "
182
+ command << "bundle check || bundle install "
176
183
  else
177
184
  # If ruby-lsp or debug are not in the Gemfile, try to update them to the latest version
178
185
  command << "bundle update "
179
186
  command << "ruby-lsp " unless @dependencies["ruby-lsp"]
180
187
  command << "debug " unless @dependencies["debug"]
188
+
189
+ @last_updated_path.write(Time.now.iso8601)
181
190
  end
182
191
 
183
192
  # Redirect stdout to stderr to prevent going into an infinite loop. The extension might confuse stdout output with
@@ -187,7 +196,14 @@ module RubyLsp
187
196
  # Add bundle update
188
197
  warn("Ruby LSP> Running bundle install for the custom bundle. This may take a while...")
189
198
  system(env, command)
190
- [bundle_gemfile.to_s, path]
199
+ [bundle_gemfile.to_s, expanded_path]
200
+ end
201
+
202
+ sig { returns(T::Boolean) }
203
+ def should_bundle_install?
204
+ (!@dependencies["ruby-lsp"].nil? && !@dependencies["debug"].nil?) ||
205
+ custom_bundle_dependencies["ruby-lsp"].nil? || custom_bundle_dependencies["debug"].nil? ||
206
+ (@last_updated_path.exist? && Time.parse(@last_updated_path.read) > (Time.now - FOUR_HOURS))
191
207
  end
192
208
  end
193
209
  end
@@ -13,34 +13,40 @@ module RubyLsp
13
13
  sig { returns(String) }
14
14
  attr_accessor :formatter
15
15
 
16
+ sig { returns(T::Boolean) }
17
+ attr_accessor :supports_progress
18
+
19
+ sig { returns(T::Boolean) }
20
+ attr_accessor :experimental_features
21
+
16
22
  sig { void }
17
23
  def initialize
18
24
  @state = T.let({}, T::Hash[String, Document])
19
25
  @encoding = T.let(Constant::PositionEncodingKind::UTF8, String)
20
26
  @formatter = T.let("auto", String)
27
+ @supports_progress = T.let(true, T::Boolean)
28
+ @experimental_features = T.let(false, T::Boolean)
21
29
  end
22
30
 
23
31
  sig { params(uri: URI::Generic).returns(Document) }
24
32
  def get(uri)
25
- path = uri.to_standardized_path
26
- return T.must(@state[T.must(uri.opaque)]) unless path
27
-
28
- document = @state[path]
33
+ document = @state[uri.to_s]
29
34
  return document unless document.nil?
30
35
 
31
- set(uri: uri, source: File.binread(CGI.unescape(path)), version: 0)
32
- T.must(@state[path])
36
+ path = T.must(uri.to_standardized_path)
37
+ set(uri: uri, source: File.binread(path), version: 0)
38
+ T.must(@state[uri.to_s])
33
39
  end
34
40
 
35
41
  sig { params(uri: URI::Generic, source: String, version: Integer).void }
36
42
  def set(uri:, source:, version:)
37
43
  document = Document.new(source: source, version: version, uri: uri, encoding: @encoding)
38
- @state[uri.storage_key] = document
44
+ @state[uri.to_s] = document
39
45
  end
40
46
 
41
47
  sig { params(uri: URI::Generic, edits: T::Array[Document::EditShape], version: Integer).void }
42
48
  def push_edits(uri:, edits:, version:)
43
- T.must(@state[uri.storage_key]).push_edits(edits, version: version)
49
+ T.must(@state[uri.to_s]).push_edits(edits, version: version)
44
50
  end
45
51
 
46
52
  sig { void }
@@ -55,7 +61,7 @@ module RubyLsp
55
61
 
56
62
  sig { params(uri: URI::Generic).void }
57
63
  def delete(uri)
58
- @state.delete(uri.storage_key)
64
+ @state.delete(uri.to_s)
59
65
  end
60
66
 
61
67
  sig do
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.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-08-08 00:00:00.000000000 Z
11
+ date: 2023-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -58,6 +58,26 @@ dependencies:
58
58
  - - "<"
59
59
  - !ruby/object:Gem::Version
60
60
  version: '7'
61
+ - !ruby/object:Gem::Dependency
62
+ name: yarp
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0.9'
68
+ - - "<"
69
+ - !ruby/object:Gem::Version
70
+ version: '0.10'
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0.9'
78
+ - - "<"
79
+ - !ruby/object:Gem::Version
80
+ version: '0.10'
61
81
  description: An opinionated language server for Ruby
62
82
  email:
63
83
  - ruby@shopify.com
@@ -75,6 +95,15 @@ files:
75
95
  - lib/core_ext/uri.rb
76
96
  - lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
77
97
  - lib/ruby-lsp.rb
98
+ - lib/ruby_indexer/lib/ruby_indexer/configuration.rb
99
+ - lib/ruby_indexer/lib/ruby_indexer/index.rb
100
+ - lib/ruby_indexer/lib/ruby_indexer/visitor.rb
101
+ - lib/ruby_indexer/ruby_indexer.rb
102
+ - lib/ruby_indexer/test/classes_and_modules_test.rb
103
+ - lib/ruby_indexer/test/configuration_test.rb
104
+ - lib/ruby_indexer/test/constant_test.rb
105
+ - lib/ruby_indexer/test/index_test.rb
106
+ - lib/ruby_indexer/test/test_case.rb
78
107
  - lib/ruby_lsp/check_docs.rb
79
108
  - lib/ruby_lsp/document.rb
80
109
  - lib/ruby_lsp/event_emitter.rb
@@ -107,7 +136,6 @@ files:
107
136
  - lib/ruby_lsp/requests/support/formatter_runner.rb
108
137
  - lib/ruby_lsp/requests/support/highlight_target.rb
109
138
  - lib/ruby_lsp/requests/support/prefix_tree.rb
110
- - lib/ruby_lsp/requests/support/rails_document_client.rb
111
139
  - lib/ruby_lsp/requests/support/rubocop_diagnostic.rb
112
140
  - lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb
113
141
  - lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb
@@ -117,6 +145,7 @@ files:
117
145
  - lib/ruby_lsp/requests/support/sorbet.rb
118
146
  - lib/ruby_lsp/requests/support/source_uri.rb
119
147
  - lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb
148
+ - lib/ruby_lsp/requests/workspace_symbol.rb
120
149
  - lib/ruby_lsp/server.rb
121
150
  - lib/ruby_lsp/setup_bundler.rb
122
151
  - lib/ruby_lsp/store.rb
@@ -141,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
170
  - !ruby/object:Gem::Version
142
171
  version: '0'
143
172
  requirements: []
144
- rubygems_version: 3.4.17
173
+ rubygems_version: 3.4.18
145
174
  signing_key:
146
175
  specification_version: 4
147
176
  summary: An opinionated language server for Ruby
@@ -1,122 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require "net/http"
5
-
6
- module RubyLsp
7
- module Requests
8
- module Support
9
- class RailsDocumentClient
10
- RAILS_DOC_HOST = "https://api.rubyonrails.org"
11
- SUPPORTED_RAILS_DOC_NAMESPACES = T.let(
12
- Regexp.union(
13
- /ActionDispatch/,
14
- /ActionController/,
15
- /AbstractController/,
16
- /ActiveRecord/,
17
- /ActiveModel/,
18
- /ActiveStorage/,
19
- /ActionText/,
20
- /ActiveJob/,
21
- ).freeze,
22
- Regexp,
23
- )
24
-
25
- RAILTIES_VERSION = T.let(
26
- [*::Gem::Specification.default_stubs, *::Gem::Specification.stubs].find do |s|
27
- s.name == "railties"
28
- end&.version&.to_s,
29
- T.nilable(String),
30
- )
31
-
32
- class << self
33
- extend T::Sig
34
- sig do
35
- params(name: String).returns(T::Array[String])
36
- end
37
- def generate_rails_document_urls(name)
38
- docs = search_index&.fetch(name, nil)
39
-
40
- return [] unless docs
41
-
42
- docs.map do |doc|
43
- owner = doc[:owner]
44
-
45
- link_name =
46
- # class/module name
47
- if owner == name
48
- name
49
- else
50
- "#{owner}##{name}"
51
- end
52
-
53
- "[Rails Document: `#{link_name}`](#{doc[:url]})"
54
- end
55
- end
56
-
57
- sig { returns(T.nilable(T::Hash[String, T::Array[T::Hash[Symbol, String]]])) }
58
- private def search_index
59
- @rails_documents ||= T.let(
60
- build_search_index,
61
- T.nilable(T::Hash[String, T::Array[T::Hash[Symbol, String]]]),
62
- )
63
- end
64
-
65
- sig { returns(T.nilable(T::Hash[String, T::Array[T::Hash[Symbol, String]]])) }
66
- private def build_search_index
67
- return unless RAILTIES_VERSION
68
-
69
- warn("Fetching Rails Documents...")
70
-
71
- response = Net::HTTP.get_response(URI("#{RAILS_DOC_HOST}/v#{RAILTIES_VERSION}/js/search_index.js"))
72
-
73
- if response.code == "302"
74
- # If the version's doc is not found, e.g. Rails main, it'll be redirected
75
- # In this case, we just fetch the latest doc
76
- response = Net::HTTP.get_response(URI("#{RAILS_DOC_HOST}/js/search_index.js"))
77
- end
78
-
79
- if response.code == "200"
80
- process_search_index(response.body)
81
- else
82
- warn("Response failed: #{response.inspect}")
83
- nil
84
- end
85
- rescue StandardError => e
86
- warn("Exception occurred when fetching Rails document index: #{e.inspect}")
87
- end
88
-
89
- sig { params(js: String).returns(T::Hash[String, T::Array[T::Hash[Symbol, String]]]) }
90
- private def process_search_index(js)
91
- raw_data = js.sub("var search_data = ", "")
92
- info = JSON.parse(raw_data).dig("index", "info")
93
-
94
- # An entry looks like this:
95
- #
96
- # ["belongs_to", # method or module/class
97
- # "ActiveRecord::Associations::ClassMethods", # method owner
98
- # "classes/ActiveRecord/Associations/ClassMethods.html#method-i-belongs_to", # path to the document
99
- # "(name, scope = nil, **options)", # method's parameters
100
- # "<p>Specifies a one-to-one association with another class..."] # document preview
101
- #
102
- info.each_with_object({}) do |(method_or_class, method_owner, doc_path, _, doc_preview), table|
103
- # If a method doesn't have documentation, there's no need to generate the link to it.
104
- next if doc_preview.nil? || doc_preview.empty?
105
-
106
- # If the method or class/module is not from the supported namespace, reject it
107
- next unless [method_or_class, method_owner].any? do |elem|
108
- elem.match?(SUPPORTED_RAILS_DOC_NAMESPACES)
109
- end
110
-
111
- owner = method_owner.empty? ? method_or_class : method_owner
112
- table[method_or_class] ||= []
113
- # It's possible to have multiple modules defining the same method name. For example,
114
- # both `ActiveRecord::FinderMethods` and `ActiveRecord::Associations::CollectionProxy` defines `#find`
115
- table[method_or_class] << { owner: owner, url: "#{RAILS_DOC_HOST}/v#{RAILTIES_VERSION}/#{doc_path}" }
116
- end
117
- end
118
- end
119
- end
120
- end
121
- end
122
- end