ruby-lsp 0.17.13 → 0.17.14

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 042725d7afce428b5c024933a7101151f2f69c57cbb40b4f938384c13d6c974b
4
- data.tar.gz: bec8636d402451e1009e87ddd98e6fdedc062643e614b596a0698ffcfcc9b271
3
+ metadata.gz: 17c576dbc88c8c4012eb777b15df7df630a827d7692f6bceec06b68178b25357
4
+ data.tar.gz: f023f22a818616b1c8c6fb1de77d0637e7bdae37b60b46e941b297aaa95729a9
5
5
  SHA512:
6
- metadata.gz: 6a6adb40a9ccaaf916f46c041f3eb2cc7a81be73c5d3bf7c05a45472cd9d08e5a9dd04d804b8e15f091645d420037e324368ccbcc619311a1c60c81f241c1b89
7
- data.tar.gz: d6b19c8d02cfab8dca9e4d675e8ee549e8b392631a6080757c0413da29a07d1db411d9360b7553a798805d9e6faac7318863e35ba664488c5eb41c869a7e734a
6
+ metadata.gz: e0848d63fef1677ccf276182cdea5a8304e4a1e04a7ec4936f8f083c27bc62f575fe3f0a207f75805cf3c8eb106e43d5d1afe2df366028980cb5eee6ed97c14b
7
+ data.tar.gz: d57869c1ee7dcec65becd9ee4fc225c91e946e697c3aab8a42d2453c1a85bcc91cd8e0a6364d810f8109c2b97102f101d24bf8986d2e4b3905b56368e260b0d3
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.13
1
+ 0.17.14
data/exe/ruby-lsp CHANGED
@@ -81,6 +81,8 @@ require "ruby_lsp/load_sorbet"
81
81
  $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
82
82
  require "ruby_lsp/internal"
83
83
 
84
+ T::Utils.run_all_sig_blocks
85
+
84
86
  if options[:debug]
85
87
  if ["x64-mingw-ucrt", "x64-mingw32"].include?(RUBY_PLATFORM)
86
88
  $stderr.puts "Debugging is not supported on Windows"
@@ -7,15 +7,19 @@ module RubyLsp
7
7
  enums do
8
8
  Ruby = new("ruby")
9
9
  ERB = new("erb")
10
+ RBS = new("rbs")
10
11
  end
11
12
  end
12
13
 
13
14
  extend T::Sig
14
15
  extend T::Helpers
16
+ extend T::Generic
17
+
18
+ ParseResultType = type_member
15
19
 
16
20
  abstract!
17
21
 
18
- sig { returns(Prism::ParseResult) }
22
+ sig { returns(ParseResultType) }
19
23
  attr_reader :parse_result
20
24
 
21
25
  sig { returns(String) }
@@ -38,10 +42,10 @@ module RubyLsp
38
42
  @version = T.let(version, Integer)
39
43
  @uri = T.let(uri, URI::Generic)
40
44
  @needs_parsing = T.let(true, T::Boolean)
41
- @parse_result = T.let(parse, Prism::ParseResult)
45
+ @parse_result = T.let(parse, ParseResultType)
42
46
  end
43
47
 
44
- sig { params(other: Document).returns(T::Boolean) }
48
+ sig { params(other: Document[T.untyped]).returns(T::Boolean) }
45
49
  def ==(other)
46
50
  self.class == other.class && uri == other.uri && @source == other.source
47
51
  end
@@ -54,7 +58,7 @@ module RubyLsp
54
58
  type_parameters(:T)
55
59
  .params(
56
60
  request_name: String,
57
- block: T.proc.params(document: Document).returns(T.type_parameter(:T)),
61
+ block: T.proc.params(document: Document[ParseResultType]).returns(T.type_parameter(:T)),
58
62
  ).returns(T.type_parameter(:T))
59
63
  end
60
64
  def cache_fetch(request_name, &block)
@@ -93,7 +97,7 @@ module RubyLsp
93
97
  @cache.clear
94
98
  end
95
99
 
96
- sig { abstract.returns(Prism::ParseResult) }
100
+ sig { abstract.returns(ParseResultType) }
97
101
  def parse; end
98
102
 
99
103
  sig { abstract.returns(T::Boolean) }
@@ -104,115 +108,6 @@ module RubyLsp
104
108
  Scanner.new(@source, @encoding)
105
109
  end
106
110
 
107
- sig do
108
- params(
109
- position: T::Hash[Symbol, T.untyped],
110
- node_types: T::Array[T.class_of(Prism::Node)],
111
- ).returns(NodeContext)
112
- end
113
- def locate_node(position, node_types: [])
114
- locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
115
- end
116
-
117
- sig do
118
- params(
119
- node: Prism::Node,
120
- char_position: Integer,
121
- node_types: T::Array[T.class_of(Prism::Node)],
122
- ).returns(NodeContext)
123
- end
124
- def locate(node, char_position, node_types: [])
125
- queue = T.let(node.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
126
- closest = node
127
- parent = T.let(nil, T.nilable(Prism::Node))
128
- nesting_nodes = T.let(
129
- [],
130
- T::Array[T.any(
131
- Prism::ClassNode,
132
- Prism::ModuleNode,
133
- Prism::SingletonClassNode,
134
- Prism::DefNode,
135
- Prism::BlockNode,
136
- Prism::LambdaNode,
137
- Prism::ProgramNode,
138
- )],
139
- )
140
-
141
- nesting_nodes << node if node.is_a?(Prism::ProgramNode)
142
- call_node = T.let(nil, T.nilable(Prism::CallNode))
143
-
144
- until queue.empty?
145
- candidate = queue.shift
146
-
147
- # Skip nil child nodes
148
- next if candidate.nil?
149
-
150
- # Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
151
- # same order as the visiting mechanism, which means searching the child nodes before moving on to the next
152
- # sibling
153
- T.unsafe(queue).unshift(*candidate.child_nodes)
154
-
155
- # Skip if the current node doesn't cover the desired position
156
- loc = candidate.location
157
- next unless (loc.start_offset...loc.end_offset).cover?(char_position)
158
-
159
- # If the node's start character is already past the position, then we should've found the closest node
160
- # already
161
- break if char_position < loc.start_offset
162
-
163
- # If the candidate starts after the end of the previous nesting level, then we've exited that nesting level and
164
- # need to pop the stack
165
- previous_level = nesting_nodes.last
166
- nesting_nodes.pop if previous_level && loc.start_offset > previous_level.location.end_offset
167
-
168
- # Keep track of the nesting where we found the target. This is used to determine the fully qualified name of the
169
- # target when it is a constant
170
- case candidate
171
- when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode, Prism::BlockNode,
172
- Prism::LambdaNode
173
- nesting_nodes << candidate
174
- end
175
-
176
- if candidate.is_a?(Prism::CallNode)
177
- arg_loc = candidate.arguments&.location
178
- blk_loc = candidate.block&.location
179
- if (arg_loc && (arg_loc.start_offset...arg_loc.end_offset).cover?(char_position)) ||
180
- (blk_loc && (blk_loc.start_offset...blk_loc.end_offset).cover?(char_position))
181
- call_node = candidate
182
- end
183
- end
184
-
185
- # If there are node types to filter by, and the current node is not one of those types, then skip it
186
- next if node_types.any? && node_types.none? { |type| candidate.class == type }
187
-
188
- # If the current node is narrower than or equal to the previous closest node, then it is more precise
189
- closest_loc = closest.location
190
- if loc.end_offset - loc.start_offset <= closest_loc.end_offset - closest_loc.start_offset
191
- parent = closest
192
- closest = candidate
193
- end
194
- end
195
-
196
- # When targeting the constant part of a class/module definition, we do not want the nesting to be duplicated. That
197
- # is, when targeting Bar in the following example:
198
- #
199
- # ```ruby
200
- # class Foo::Bar; end
201
- # ```
202
- # The correct target is `Foo::Bar` with an empty nesting. `Foo::Bar` should not appear in the nesting stack, even
203
- # though the class/module node does indeed enclose the target, because it would lead to incorrect behavior
204
- if closest.is_a?(Prism::ConstantReadNode) || closest.is_a?(Prism::ConstantPathNode)
205
- last_level = nesting_nodes.last
206
-
207
- if (last_level.is_a?(Prism::ModuleNode) || last_level.is_a?(Prism::ClassNode)) &&
208
- last_level.constant_path == closest
209
- nesting_nodes.pop
210
- end
211
- end
212
-
213
- NodeContext.new(closest, parent, nesting_nodes, call_node)
214
- end
215
-
216
111
  class Scanner
217
112
  extend T::Sig
218
113
 
@@ -4,15 +4,19 @@
4
4
  module RubyLsp
5
5
  class ERBDocument < Document
6
6
  extend T::Sig
7
+ extend T::Generic
7
8
 
8
- sig { override.returns(Prism::ParseResult) }
9
+ ParseResultType = type_member { { fixed: Prism::ParseResult } }
10
+
11
+ sig { override.returns(ParseResultType) }
9
12
  def parse
10
13
  return @parse_result unless @needs_parsing
11
14
 
12
15
  @needs_parsing = false
13
16
  scanner = ERBScanner.new(@source)
14
17
  scanner.scan
15
- @parse_result = Prism.parse(scanner.ruby)
18
+ # assigning empty scopes to turn Prism into eval mode
19
+ @parse_result = Prism.parse(scanner.ruby, scopes: [[]])
16
20
  end
17
21
 
18
22
  sig { override.returns(T::Boolean) }
@@ -25,6 +29,16 @@ module RubyLsp
25
29
  LanguageId::ERB
26
30
  end
27
31
 
32
+ sig do
33
+ params(
34
+ position: T::Hash[Symbol, T.untyped],
35
+ node_types: T::Array[T.class_of(Prism::Node)],
36
+ ).returns(NodeContext)
37
+ end
38
+ def locate_node(position, node_types: [])
39
+ RubyDocument.locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
40
+ end
41
+
28
42
  class ERBScanner
29
43
  extend T::Sig
30
44
 
@@ -93,7 +93,7 @@ module RubyLsp
93
93
  @test_library = detect_test_library(direct_dependencies)
94
94
  notifications << Notification.window_log_message("Detected test library: #{@test_library}")
95
95
 
96
- @has_type_checker = detect_typechecker(direct_dependencies)
96
+ @has_type_checker = detect_typechecker(all_dependencies)
97
97
  if @has_type_checker
98
98
  notifications << Notification.window_log_message(
99
99
  "Ruby LSP detected this is a Sorbet project and will defer to the Sorbet LSP for some functionality",
@@ -36,6 +36,7 @@ require "ruby_lsp/node_context"
36
36
  require "ruby_lsp/document"
37
37
  require "ruby_lsp/ruby_document"
38
38
  require "ruby_lsp/erb_document"
39
+ require "ruby_lsp/rbs_document"
39
40
  require "ruby_lsp/store"
40
41
  require "ruby_lsp/addon"
41
42
  require "ruby_lsp/requests/support/rubocop_runner"
@@ -0,0 +1,41 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ class RBSDocument < Document
6
+ extend T::Sig
7
+ extend T::Generic
8
+
9
+ ParseResultType = type_member { { fixed: T::Array[RBS::AST::Declarations::Base] } }
10
+
11
+ sig { params(source: String, version: Integer, uri: URI::Generic, encoding: Encoding).void }
12
+ def initialize(source:, version:, uri:, encoding: Encoding::UTF_8)
13
+ @syntax_error = T.let(false, T::Boolean)
14
+ super
15
+ end
16
+
17
+ sig { override.returns(ParseResultType) }
18
+ def parse
19
+ return @parse_result unless @needs_parsing
20
+
21
+ @needs_parsing = false
22
+
23
+ _, _, declarations = RBS::Parser.parse_signature(@source)
24
+ @syntax_error = false
25
+ @parse_result = declarations
26
+ rescue RBS::ParsingError
27
+ @syntax_error = true
28
+ @parse_result
29
+ end
30
+
31
+ sig { override.returns(T::Boolean) }
32
+ def syntax_error?
33
+ @syntax_error
34
+ end
35
+
36
+ sig { override.returns(LanguageId) }
37
+ def language_id
38
+ LanguageId::RBS
39
+ end
40
+ end
41
+ end
@@ -112,7 +112,7 @@ module RubyLsp
112
112
  extracted_source = T.must(@document.source[start_index...end_index])
113
113
 
114
114
  # Find the closest statements node, so that we place the refactor in a valid position
115
- node_context = @document
115
+ node_context = RubyDocument
116
116
  .locate(@document.parse_result.value, start_index, node_types: [Prism::StatementsNode, Prism::BlockNode])
117
117
 
118
118
  closest_statements = node_context.node
@@ -206,28 +206,44 @@ module RubyLsp
206
206
  extracted_source = T.must(@document.source[start_index...end_index])
207
207
 
208
208
  # Find the closest method declaration node, so that we place the refactor in a valid position
209
- node_context = @document.locate(@document.parse_result.value, start_index, node_types: [Prism::DefNode])
210
- closest_def = T.cast(node_context.node, Prism::DefNode)
211
- return Error::InvalidTargetRange if closest_def.nil?
209
+ node_context = RubyDocument.locate(@document.parse_result.value, start_index, node_types: [Prism::DefNode])
210
+ closest_node = node_context.node
211
+ return Error::InvalidTargetRange unless closest_node
212
212
 
213
- end_keyword_loc = closest_def.end_keyword_loc
214
- return Error::InvalidTargetRange if end_keyword_loc.nil?
213
+ target_range = if closest_node.is_a?(Prism::DefNode)
214
+ end_keyword_loc = closest_node.end_keyword_loc
215
+ return Error::InvalidTargetRange unless end_keyword_loc
215
216
 
216
- end_line = end_keyword_loc.end_line - 1
217
- character = end_keyword_loc.end_column
218
- indentation = " " * end_keyword_loc.start_column
219
- target_range = {
220
- start: { line: end_line, character: character },
221
- end: { line: end_line, character: character },
222
- }
217
+ end_line = end_keyword_loc.end_line - 1
218
+ character = end_keyword_loc.end_column
219
+ indentation = " " * end_keyword_loc.start_column
223
220
 
224
- new_method_source = <<~RUBY.chomp
221
+ new_method_source = <<~RUBY.chomp
225
222
 
226
223
 
227
- #{indentation}def #{NEW_METHOD_NAME}
228
- #{indentation} #{extracted_source}
229
- #{indentation}end
230
- RUBY
224
+ #{indentation}def #{NEW_METHOD_NAME}
225
+ #{indentation} #{extracted_source}
226
+ #{indentation}end
227
+ RUBY
228
+
229
+ {
230
+ start: { line: end_line, character: character },
231
+ end: { line: end_line, character: character },
232
+ }
233
+ else
234
+ new_method_source = <<~RUBY
235
+ #{indentation}def #{NEW_METHOD_NAME}
236
+ #{indentation} #{extracted_source.gsub("\n", "\n ")}
237
+ #{indentation}end
238
+
239
+ RUBY
240
+
241
+ line = [0, source_range.dig(:start, :line) - 1].max
242
+ {
243
+ start: { line: line, character: source_range.dig(:start, :character) },
244
+ end: { line: line, character: source_range.dig(:start, :character) },
245
+ }
246
+ end
231
247
 
232
248
  Interface::CodeAction.new(
233
249
  title: CodeActions::EXTRACT_TO_METHOD_TITLE,
@@ -37,7 +37,7 @@ module RubyLsp
37
37
 
38
38
  sig do
39
39
  params(
40
- document: Document,
40
+ document: T.any(RubyDocument, ERBDocument),
41
41
  range: T::Hash[Symbol, T.untyped],
42
42
  context: T::Hash[Symbol, T.untyped],
43
43
  ).void
@@ -46,7 +46,7 @@ module RubyLsp
46
46
 
47
47
  sig do
48
48
  params(
49
- document: Document,
49
+ document: T.any(RubyDocument, ERBDocument),
50
50
  global_state: GlobalState,
51
51
  params: T::Hash[Symbol, T.untyped],
52
52
  sorbet_level: RubyDocument::SorbetLevel,
@@ -60,7 +60,7 @@ module RubyLsp
60
60
  # Completion always receives the position immediately after the character that was just typed. Here we adjust it
61
61
  # back by 1, so that we find the right node
62
62
  char_position = document.create_scanner.find_char_position(params[:position]) - 1
63
- node_context = document.locate(
63
+ node_context = RubyDocument.locate(
64
64
  document.parse_result.value,
65
65
  char_position,
66
66
  node_types: [
@@ -38,7 +38,7 @@ module RubyLsp
38
38
 
39
39
  sig do
40
40
  params(
41
- document: Document,
41
+ document: T.any(RubyDocument, ERBDocument),
42
42
  global_state: GlobalState,
43
43
  position: T::Hash[Symbol, T.untyped],
44
44
  dispatcher: Prism::Dispatcher,
@@ -32,7 +32,7 @@ module RubyLsp
32
32
  end
33
33
  end
34
34
 
35
- sig { params(global_state: GlobalState, document: Document).void }
35
+ sig { params(global_state: GlobalState, document: RubyDocument).void }
36
36
  def initialize(global_state, document)
37
37
  super()
38
38
  @active_linters = T.let(global_state.active_linters, T::Array[Support::Formatter])
@@ -29,7 +29,7 @@ module RubyLsp
29
29
 
30
30
  sig do
31
31
  params(
32
- document: Document,
32
+ document: T.any(RubyDocument, ERBDocument),
33
33
  position: T::Hash[Symbol, T.untyped],
34
34
  dispatcher: Prism::Dispatcher,
35
35
  ).void
@@ -40,7 +40,7 @@ module RubyLsp
40
40
  end
41
41
  end
42
42
 
43
- sig { params(global_state: GlobalState, document: Document).void }
43
+ sig { params(global_state: GlobalState, document: RubyDocument).void }
44
44
  def initialize(global_state, document)
45
45
  super()
46
46
  @document = document
@@ -32,7 +32,7 @@ module RubyLsp
32
32
 
33
33
  sig do
34
34
  params(
35
- document: Document,
35
+ document: T.any(RubyDocument, ERBDocument),
36
36
  global_state: GlobalState,
37
37
  position: T::Hash[Symbol, T.untyped],
38
38
  dispatcher: Prism::Dispatcher,
@@ -52,7 +52,7 @@ module RubyLsp
52
52
 
53
53
  sig do
54
54
  params(
55
- document: Document,
55
+ document: T.any(RubyDocument, ERBDocument),
56
56
  range: T::Hash[Symbol, T.untyped],
57
57
  hints_configuration: RequestConfig,
58
58
  dispatcher: Prism::Dispatcher,
@@ -42,7 +42,7 @@ module RubyLsp
42
42
 
43
43
  sig do
44
44
  params(
45
- document: Document,
45
+ document: RubyDocument,
46
46
  position: T::Hash[Symbol, T.untyped],
47
47
  trigger_character: String,
48
48
  client_name: String,
@@ -35,7 +35,7 @@ module RubyLsp
35
35
 
36
36
  sig do
37
37
  params(
38
- document: Document,
38
+ document: T.any(RubyDocument, ERBDocument),
39
39
  index: RubyIndexer::Index,
40
40
  position: T::Hash[Symbol, T.untyped],
41
41
  ).void
@@ -23,7 +23,8 @@ module RubyLsp
23
23
  class SelectionRanges < Request
24
24
  extend T::Sig
25
25
  include Support::Common
26
- sig { params(document: Document).void }
26
+
27
+ sig { params(document: T.any(RubyDocument, ERBDocument)).void }
27
28
  def initialize(document)
28
29
  super()
29
30
  @document = document
@@ -20,7 +20,7 @@ module RubyLsp
20
20
  class ShowSyntaxTree < Request
21
21
  extend T::Sig
22
22
 
23
- sig { params(document: Document, range: T.nilable(T::Hash[Symbol, T.untyped])).void }
23
+ sig { params(document: RubyDocument, range: T.nilable(T::Hash[Symbol, T.untyped])).void }
24
24
  def initialize(document, range)
25
25
  super()
26
26
  @document = document
@@ -41,7 +41,7 @@ module RubyLsp
41
41
 
42
42
  sig do
43
43
  params(
44
- document: Document,
44
+ document: T.any(RubyDocument, ERBDocument),
45
45
  global_state: GlobalState,
46
46
  position: T::Hash[Symbol, T.untyped],
47
47
  context: T.nilable(T::Hash[Symbol, T.untyped]),
@@ -10,13 +10,13 @@ module RubyLsp
10
10
 
11
11
  interface!
12
12
 
13
- sig { abstract.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
13
+ sig { abstract.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
14
14
  def run_formatting(uri, document); end
15
15
 
16
16
  sig do
17
17
  abstract.params(
18
18
  uri: URI::Generic,
19
- document: Document,
19
+ document: RubyDocument,
20
20
  ).returns(T.nilable(T::Array[Interface::Diagnostic]))
21
21
  end
22
22
  def run_diagnostic(uri, document); end
@@ -31,7 +31,7 @@ module RubyLsp
31
31
 
32
32
  # TODO: avoid passing document once we have alternative ways to get at
33
33
  # encoding and file source
34
- sig { params(document: Document, offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
34
+ sig { params(document: RubyDocument, offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
35
35
  def initialize(document, offense, uri)
36
36
  @document = document
37
37
  @offense = offense
@@ -17,7 +17,7 @@ module RubyLsp
17
17
  @format_runner = T.let(RuboCopRunner.new("-a"), RuboCopRunner)
18
18
  end
19
19
 
20
- sig { override.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
20
+ sig { override.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
21
21
  def run_formatting(uri, document)
22
22
  filename = T.must(uri.to_standardized_path || uri.opaque)
23
23
 
@@ -29,7 +29,7 @@ module RubyLsp
29
29
  sig do
30
30
  override.params(
31
31
  uri: URI::Generic,
32
- document: Document,
32
+ document: RubyDocument,
33
33
  ).returns(T.nilable(T::Array[Interface::Diagnostic]))
34
34
  end
35
35
  def run_diagnostic(uri, document)
@@ -29,7 +29,7 @@ module RubyLsp
29
29
  )
30
30
  end
31
31
 
32
- sig { override.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
32
+ sig { override.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
33
33
  def run_formatting(uri, document)
34
34
  path = uri.to_standardized_path
35
35
  return if path && @options.ignore_files.any? { |pattern| File.fnmatch?("*/#{pattern}", path) }
@@ -40,7 +40,7 @@ module RubyLsp
40
40
  sig do
41
41
  override.params(
42
42
  uri: URI::Generic,
43
- document: Document,
43
+ document: RubyDocument,
44
44
  ).returns(T.nilable(T::Array[Interface::Diagnostic]))
45
45
  end
46
46
  def run_diagnostic(uri, document)
@@ -3,6 +3,11 @@
3
3
 
4
4
  module RubyLsp
5
5
  class RubyDocument < Document
6
+ extend T::Sig
7
+ extend T::Generic
8
+
9
+ ParseResultType = type_member { { fixed: Prism::ParseResult } }
10
+
6
11
  class SorbetLevel < T::Enum
7
12
  enums do
8
13
  None = new("none")
@@ -13,7 +18,110 @@ module RubyLsp
13
18
  end
14
19
  end
15
20
 
16
- sig { override.returns(Prism::ParseResult) }
21
+ class << self
22
+ extend T::Sig
23
+
24
+ sig do
25
+ params(
26
+ node: Prism::Node,
27
+ char_position: Integer,
28
+ node_types: T::Array[T.class_of(Prism::Node)],
29
+ ).returns(NodeContext)
30
+ end
31
+ def locate(node, char_position, node_types: [])
32
+ queue = T.let(node.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
33
+ closest = node
34
+ parent = T.let(nil, T.nilable(Prism::Node))
35
+ nesting_nodes = T.let(
36
+ [],
37
+ T::Array[T.any(
38
+ Prism::ClassNode,
39
+ Prism::ModuleNode,
40
+ Prism::SingletonClassNode,
41
+ Prism::DefNode,
42
+ Prism::BlockNode,
43
+ Prism::LambdaNode,
44
+ Prism::ProgramNode,
45
+ )],
46
+ )
47
+
48
+ nesting_nodes << node if node.is_a?(Prism::ProgramNode)
49
+ call_node = T.let(nil, T.nilable(Prism::CallNode))
50
+
51
+ until queue.empty?
52
+ candidate = queue.shift
53
+
54
+ # Skip nil child nodes
55
+ next if candidate.nil?
56
+
57
+ # Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
58
+ # same order as the visiting mechanism, which means searching the child nodes before moving on to the next
59
+ # sibling
60
+ T.unsafe(queue).unshift(*candidate.child_nodes)
61
+
62
+ # Skip if the current node doesn't cover the desired position
63
+ loc = candidate.location
64
+ next unless (loc.start_offset...loc.end_offset).cover?(char_position)
65
+
66
+ # If the node's start character is already past the position, then we should've found the closest node
67
+ # already
68
+ break if char_position < loc.start_offset
69
+
70
+ # If the candidate starts after the end of the previous nesting level, then we've exited that nesting level
71
+ # and need to pop the stack
72
+ previous_level = nesting_nodes.last
73
+ nesting_nodes.pop if previous_level && loc.start_offset > previous_level.location.end_offset
74
+
75
+ # Keep track of the nesting where we found the target. This is used to determine the fully qualified name of
76
+ # the target when it is a constant
77
+ case candidate
78
+ when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode, Prism::BlockNode,
79
+ Prism::LambdaNode
80
+ nesting_nodes << candidate
81
+ end
82
+
83
+ if candidate.is_a?(Prism::CallNode)
84
+ arg_loc = candidate.arguments&.location
85
+ blk_loc = candidate.block&.location
86
+ if (arg_loc && (arg_loc.start_offset...arg_loc.end_offset).cover?(char_position)) ||
87
+ (blk_loc && (blk_loc.start_offset...blk_loc.end_offset).cover?(char_position))
88
+ call_node = candidate
89
+ end
90
+ end
91
+
92
+ # If there are node types to filter by, and the current node is not one of those types, then skip it
93
+ next if node_types.any? && node_types.none? { |type| candidate.class == type }
94
+
95
+ # If the current node is narrower than or equal to the previous closest node, then it is more precise
96
+ closest_loc = closest.location
97
+ if loc.end_offset - loc.start_offset <= closest_loc.end_offset - closest_loc.start_offset
98
+ parent = closest
99
+ closest = candidate
100
+ end
101
+ end
102
+
103
+ # When targeting the constant part of a class/module definition, we do not want the nesting to be duplicated.
104
+ # That is, when targeting Bar in the following example:
105
+ #
106
+ # ```ruby
107
+ # class Foo::Bar; end
108
+ # ```
109
+ # The correct target is `Foo::Bar` with an empty nesting. `Foo::Bar` should not appear in the nesting stack,
110
+ # even though the class/module node does indeed enclose the target, because it would lead to incorrect behavior
111
+ if closest.is_a?(Prism::ConstantReadNode) || closest.is_a?(Prism::ConstantPathNode)
112
+ last_level = nesting_nodes.last
113
+
114
+ if (last_level.is_a?(Prism::ModuleNode) || last_level.is_a?(Prism::ClassNode)) &&
115
+ last_level.constant_path == closest
116
+ nesting_nodes.pop
117
+ end
118
+ end
119
+
120
+ NodeContext.new(closest, parent, nesting_nodes, call_node)
121
+ end
122
+ end
123
+
124
+ sig { override.returns(ParseResultType) }
17
125
  def parse
18
126
  return @parse_result unless @needs_parsing
19
127
 
@@ -84,5 +192,15 @@ module RubyLsp
84
192
  end
85
193
  end
86
194
  end
195
+
196
+ sig do
197
+ params(
198
+ position: T::Hash[Symbol, T.untyped],
199
+ node_types: T::Array[T.class_of(Prism::Node)],
200
+ ).returns(NodeContext)
201
+ end
202
+ def locate_node(position, node_types: [])
203
+ RubyDocument.locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
204
+ end
87
205
  end
88
206
  end
@@ -298,9 +298,12 @@ module RubyLsp
298
298
  language_id = case text_document[:languageId]
299
299
  when "erb", "eruby"
300
300
  Document::LanguageId::ERB
301
+ when "rbs"
302
+ Document::LanguageId::RBS
301
303
  else
302
304
  Document::LanguageId::Ruby
303
305
  end
306
+
304
307
  @store.set(
305
308
  uri: text_document[:uri],
306
309
  source: text_document[:text],
@@ -341,7 +344,12 @@ module RubyLsp
341
344
  def text_document_selection_range(message)
342
345
  uri = message.dig(:params, :textDocument, :uri)
343
346
  ranges = @store.cache_fetch(uri, "textDocument/selectionRange") do |document|
344
- Requests::SelectionRanges.new(document).perform
347
+ case document
348
+ when RubyDocument, ERBDocument
349
+ Requests::SelectionRanges.new(document).perform
350
+ else
351
+ []
352
+ end
345
353
  end
346
354
 
347
355
  # Per the selection range request spec (https://microsoft.github.io/language-server-protocol/specification#textDocument_selectionRange),
@@ -363,6 +371,11 @@ module RubyLsp
363
371
  uri = URI(message.dig(:params, :textDocument, :uri))
364
372
  document = @store.get(uri)
365
373
 
374
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
375
+ send_empty_response(message[:id])
376
+ return
377
+ end
378
+
366
379
  # If the response has already been cached by another request, return it
367
380
  cached_response = document.cache_get(message[:method])
368
381
  if cached_response
@@ -407,6 +420,12 @@ module RubyLsp
407
420
  range = params[:range]
408
421
  uri = params.dig(:textDocument, :uri)
409
422
  document = @store.get(uri)
423
+
424
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
425
+ send_empty_response(message[:id])
426
+ return
427
+ end
428
+
410
429
  start_line = range.dig(:start, :line)
411
430
  end_line = range.dig(:end, :line)
412
431
 
@@ -436,6 +455,10 @@ module RubyLsp
436
455
  end
437
456
 
438
457
  document = @store.get(uri)
458
+ unless document.is_a?(RubyDocument)
459
+ send_empty_response(message[:id])
460
+ return
461
+ end
439
462
 
440
463
  response = Requests::Formatting.new(@global_state, document).perform
441
464
  send_message(Result.new(id: message[:id], response: response))
@@ -452,6 +475,12 @@ module RubyLsp
452
475
  params = message[:params]
453
476
  dispatcher = Prism::Dispatcher.new
454
477
  document = @store.get(params.dig(:textDocument, :uri))
478
+
479
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
480
+ send_empty_response(message[:id])
481
+ return
482
+ end
483
+
455
484
  request = Requests::DocumentHighlight.new(document, params[:position], dispatcher)
456
485
  dispatcher.dispatch(document.parse_result.value)
457
486
  send_message(Result.new(id: message[:id], response: request.perform))
@@ -462,6 +491,11 @@ module RubyLsp
462
491
  params = message[:params]
463
492
  document = @store.get(params.dig(:textDocument, :uri))
464
493
 
494
+ unless document.is_a?(RubyDocument)
495
+ send_empty_response(message[:id])
496
+ return
497
+ end
498
+
465
499
  send_message(
466
500
  Result.new(
467
501
  id: message[:id],
@@ -481,6 +515,11 @@ module RubyLsp
481
515
  dispatcher = Prism::Dispatcher.new
482
516
  document = @store.get(params.dig(:textDocument, :uri))
483
517
 
518
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
519
+ send_empty_response(message[:id])
520
+ return
521
+ end
522
+
484
523
  send_message(
485
524
  Result.new(
486
525
  id: message[:id],
@@ -495,7 +534,7 @@ module RubyLsp
495
534
  )
496
535
  end
497
536
 
498
- sig { params(document: Document).returns(RubyDocument::SorbetLevel) }
537
+ sig { params(document: Document[T.untyped]).returns(RubyDocument::SorbetLevel) }
499
538
  def sorbet_level(document)
500
539
  return RubyDocument::SorbetLevel::Ignore unless @global_state.has_type_checker
501
540
  return RubyDocument::SorbetLevel::Ignore unless document.is_a?(RubyDocument)
@@ -509,6 +548,12 @@ module RubyLsp
509
548
  hints_configurations = T.must(@store.features_configuration.dig(:inlayHint))
510
549
  dispatcher = Prism::Dispatcher.new
511
550
  document = @store.get(params.dig(:textDocument, :uri))
551
+
552
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
553
+ send_empty_response(message[:id])
554
+ return
555
+ end
556
+
512
557
  request = Requests::InlayHints.new(document, params[:range], hints_configurations, dispatcher)
513
558
  dispatcher.visit(document.parse_result.value)
514
559
  send_message(Result.new(id: message[:id], response: request.perform))
@@ -519,6 +564,11 @@ module RubyLsp
519
564
  params = message[:params]
520
565
  document = @store.get(params.dig(:textDocument, :uri))
521
566
 
567
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
568
+ send_empty_response(message[:id])
569
+ return
570
+ end
571
+
522
572
  send_message(
523
573
  Result.new(
524
574
  id: message[:id],
@@ -581,7 +631,10 @@ module RubyLsp
581
631
  document = @store.get(uri)
582
632
 
583
633
  response = document.cache_fetch("textDocument/diagnostic") do |document|
584
- Requests::Diagnostics.new(@global_state, document).perform
634
+ case document
635
+ when RubyDocument
636
+ Requests::Diagnostics.new(@global_state, document).perform
637
+ end
585
638
  end
586
639
 
587
640
  send_message(
@@ -604,6 +657,11 @@ module RubyLsp
604
657
  dispatcher = Prism::Dispatcher.new
605
658
  document = @store.get(params.dig(:textDocument, :uri))
606
659
 
660
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
661
+ send_empty_response(message[:id])
662
+ return
663
+ end
664
+
607
665
  send_message(
608
666
  Result.new(
609
667
  id: message[:id],
@@ -632,6 +690,11 @@ module RubyLsp
632
690
  dispatcher = Prism::Dispatcher.new
633
691
  document = @store.get(params.dig(:textDocument, :uri))
634
692
 
693
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
694
+ send_empty_response(message[:id])
695
+ return
696
+ end
697
+
635
698
  send_message(
636
699
  Result.new(
637
700
  id: message[:id],
@@ -653,6 +716,11 @@ module RubyLsp
653
716
  dispatcher = Prism::Dispatcher.new
654
717
  document = @store.get(params.dig(:textDocument, :uri))
655
718
 
719
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
720
+ send_empty_response(message[:id])
721
+ return
722
+ end
723
+
656
724
  send_message(
657
725
  Result.new(
658
726
  id: message[:id],
@@ -710,9 +778,16 @@ module RubyLsp
710
778
  sig { params(message: T::Hash[Symbol, T.untyped]).void }
711
779
  def text_document_show_syntax_tree(message)
712
780
  params = message[:params]
781
+ document = @store.get(params.dig(:textDocument, :uri))
782
+
783
+ unless document.is_a?(RubyDocument)
784
+ send_empty_response(message[:id])
785
+ return
786
+ end
787
+
713
788
  response = {
714
789
  ast: Requests::ShowSyntaxTree.new(
715
- @store.get(params.dig(:textDocument, :uri)),
790
+ document,
716
791
  params[:range],
717
792
  ).perform,
718
793
  }
@@ -722,11 +797,19 @@ module RubyLsp
722
797
  sig { params(message: T::Hash[Symbol, T.untyped]).void }
723
798
  def text_document_prepare_type_hierarchy(message)
724
799
  params = message[:params]
800
+ document = @store.get(params.dig(:textDocument, :uri))
801
+
802
+ unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
803
+ send_empty_response(message[:id])
804
+ return
805
+ end
806
+
725
807
  response = Requests::PrepareTypeHierarchy.new(
726
- @store.get(params.dig(:textDocument, :uri)),
808
+ document,
727
809
  @global_state.index,
728
810
  params[:position],
729
811
  ).perform
812
+
730
813
  send_message(Result.new(id: message[:id], response: response))
731
814
  end
732
815
 
@@ -793,6 +876,11 @@ module RubyLsp
793
876
  send_message(Notification.window_show_error("Error while indexing: #{error.message}"))
794
877
  end
795
878
 
879
+ # Indexing produces a high number of short lived object allocations. That might lead to some fragmentation and
880
+ # an unnecessarily expanded heap. Compacting ensures that the heap is as small as possible and that future
881
+ # allocations and garbage collections are faster
882
+ GC.compact
883
+
796
884
  # Always end the progress notification even if indexing failed or else it never goes away and the user has no
797
885
  # way of dismissing it
798
886
  end_progress("indexing-progress")
@@ -50,6 +50,7 @@ module RubyLsp
50
50
  @last_updated_path = T.let(@custom_dir + "last_updated", Pathname)
51
51
 
52
52
  @dependencies = T.let(load_dependencies, T::Hash[String, T.untyped])
53
+ @rails_app = T.let(rails_app?, T::Boolean)
53
54
  @retry = T.let(false, T::Boolean)
54
55
  end
55
56
 
@@ -62,7 +63,7 @@ module RubyLsp
62
63
  # Do not set up a custom bundle if LSP dependencies are already in the Gemfile
63
64
  if @dependencies["ruby-lsp"] &&
64
65
  @dependencies["debug"] &&
65
- (@dependencies["rails"] ? @dependencies["ruby-lsp-rails"] : true)
66
+ (@rails_app ? @dependencies["ruby-lsp-rails"] : true)
66
67
  $stderr.puts(
67
68
  "Ruby LSP> Skipping custom bundle setup since LSP dependencies are already in #{@gemfile}",
68
69
  )
@@ -148,7 +149,7 @@ module RubyLsp
148
149
  parts << 'gem "debug", require: false, group: :development, platforms: :mri'
149
150
  end
150
151
 
151
- if @dependencies["rails"] && !@dependencies["ruby-lsp-rails"]
152
+ if @rails_app && !@dependencies["ruby-lsp-rails"]
152
153
  parts << 'gem "ruby-lsp-rails", require: false, group: :development'
153
154
  end
154
155
 
@@ -209,7 +210,7 @@ module RubyLsp
209
210
  command << " && bundle update "
210
211
  command << "ruby-lsp " unless @dependencies["ruby-lsp"]
211
212
  command << "debug " unless @dependencies["debug"]
212
- command << "ruby-lsp-rails " if @dependencies["rails"] && !@dependencies["ruby-lsp-rails"]
213
+ command << "ruby-lsp-rails " if @rails_app && !@dependencies["ruby-lsp-rails"]
213
214
  command << "--pre" if @experimental
214
215
  command.delete_suffix!(" ")
215
216
  command << ")"
@@ -244,7 +245,7 @@ module RubyLsp
244
245
  def should_bundle_update?
245
246
  # If `ruby-lsp`, `ruby-lsp-rails` and `debug` are in the Gemfile, then we shouldn't try to upgrade them or else it
246
247
  # will produce version control changes
247
- if @dependencies["rails"]
248
+ if @rails_app
248
249
  return false if @dependencies.values_at("ruby-lsp", "ruby-lsp-rails", "debug").all?
249
250
 
250
251
  # If the custom lockfile doesn't include `ruby-lsp`, `ruby-lsp-rails` or `debug`, we need to run bundle install
@@ -280,5 +281,15 @@ module RubyLsp
280
281
 
281
282
  @custom_lockfile.write(content)
282
283
  end
284
+
285
+ # Detects if the project is a Rails app by looking if the superclass of the main class is `Rails::Application`
286
+ sig { returns(T::Boolean) }
287
+ def rails_app?
288
+ config = Pathname.new("config/application.rb").expand_path
289
+ application_contents = config.read if config.exist?
290
+ return false unless application_contents
291
+
292
+ /class .* < (::)?Rails::Application/.match?(application_contents)
293
+ end
283
294
  end
284
295
  end
@@ -18,7 +18,7 @@ module RubyLsp
18
18
 
19
19
  sig { void }
20
20
  def initialize
21
- @state = T.let({}, T::Hash[String, Document])
21
+ @state = T.let({}, T::Hash[String, Document[T.untyped]])
22
22
  @supports_progress = T.let(true, T::Boolean)
23
23
  @features_configuration = T.let(
24
24
  {
@@ -33,7 +33,7 @@ module RubyLsp
33
33
  @client_name = T.let("Unknown", String)
34
34
  end
35
35
 
36
- sig { params(uri: URI::Generic).returns(Document) }
36
+ sig { params(uri: URI::Generic).returns(Document[T.untyped]) }
37
37
  def get(uri)
38
38
  document = @state[uri.to_s]
39
39
  return document unless document.nil?
@@ -44,8 +44,11 @@ module RubyLsp
44
44
  raise NonExistingDocumentError, uri.to_s unless path
45
45
 
46
46
  ext = File.extname(path)
47
- language_id = if ext == ".erb" || ext == ".rhtml"
47
+ language_id = case ext
48
+ when ".erb", ".rhtml"
48
49
  Document::LanguageId::ERB
50
+ when ".rbs"
51
+ Document::LanguageId::RBS
49
52
  else
50
53
  Document::LanguageId::Ruby
51
54
  end
@@ -66,13 +69,14 @@ module RubyLsp
66
69
  ).void
67
70
  end
68
71
  def set(uri:, source:, version:, language_id:, encoding: Encoding::UTF_8)
69
- document = case language_id
72
+ @state[uri.to_s] = case language_id
70
73
  when Document::LanguageId::ERB
71
74
  ERBDocument.new(source: source, version: version, uri: uri, encoding: encoding)
75
+ when Document::LanguageId::RBS
76
+ RBSDocument.new(source: source, version: version, uri: uri, encoding: encoding)
72
77
  else
73
78
  RubyDocument.new(source: source, version: version, uri: uri, encoding: encoding)
74
79
  end
75
- @state[uri.to_s] = document
76
80
  end
77
81
 
78
82
  sig { params(uri: URI::Generic, edits: T::Array[T::Hash[Symbol, T.untyped]], version: Integer).void }
@@ -100,7 +104,7 @@ module RubyLsp
100
104
  .params(
101
105
  uri: URI::Generic,
102
106
  request_name: String,
103
- block: T.proc.params(document: Document).returns(T.type_parameter(:T)),
107
+ block: T.proc.params(document: Document[T.untyped]).returns(T.type_parameter(:T)),
104
108
  ).returns(T.type_parameter(:T))
105
109
  end
106
110
  def cache_fetch(uri, request_name, &block)
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.13
4
+ version: 0.17.14
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-14 00:00:00.000000000 Z
11
+ date: 2024-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -137,6 +137,7 @@ files:
137
137
  - lib/ruby_lsp/load_sorbet.rb
138
138
  - lib/ruby_lsp/node_context.rb
139
139
  - lib/ruby_lsp/parameter_scope.rb
140
+ - lib/ruby_lsp/rbs_document.rb
140
141
  - lib/ruby_lsp/requests.rb
141
142
  - lib/ruby_lsp/requests/code_action_resolve.rb
142
143
  - lib/ruby_lsp/requests/code_actions.rb