ruby-lsp 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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