ruby-lsp 0.12.5 → 0.13.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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp-check +20 -4
  4. data/lib/ruby_indexer/lib/ruby_indexer/collector.rb +36 -2
  5. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +1 -1
  6. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +132 -13
  7. data/lib/ruby_indexer/test/configuration_test.rb +10 -0
  8. data/lib/ruby_indexer/test/index_test.rb +11 -0
  9. data/lib/ruby_indexer/test/method_test.rb +202 -0
  10. data/lib/ruby_lsp/addon.rb +9 -13
  11. data/lib/ruby_lsp/document.rb +3 -0
  12. data/lib/ruby_lsp/executor.rb +33 -28
  13. data/lib/ruby_lsp/listener.rb +4 -5
  14. data/lib/ruby_lsp/requests/code_actions.rb +2 -16
  15. data/lib/ruby_lsp/requests/code_lens.rb +29 -7
  16. data/lib/ruby_lsp/requests/completion.rb +11 -8
  17. data/lib/ruby_lsp/requests/definition.rb +3 -4
  18. data/lib/ruby_lsp/requests/diagnostics.rb +0 -5
  19. data/lib/ruby_lsp/requests/document_highlight.rb +2 -3
  20. data/lib/ruby_lsp/requests/document_link.rb +2 -3
  21. data/lib/ruby_lsp/requests/document_symbol.rb +3 -3
  22. data/lib/ruby_lsp/requests/folding_ranges.rb +12 -15
  23. data/lib/ruby_lsp/requests/formatting.rb +0 -5
  24. data/lib/ruby_lsp/requests/hover.rb +3 -4
  25. data/lib/ruby_lsp/requests/inlay_hints.rb +20 -3
  26. data/lib/ruby_lsp/requests/on_type_formatting.rb +31 -4
  27. data/lib/ruby_lsp/requests/semantic_highlighting.rb +28 -11
  28. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +92 -21
  29. data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +1 -1
  30. data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +3 -8
  31. data/lib/ruby_lsp/requests/workspace_symbol.rb +2 -0
  32. data/lib/ruby_lsp/store.rb +21 -0
  33. data/lib/ruby_lsp/utils.rb +18 -4
  34. metadata +7 -7
@@ -20,8 +20,8 @@ module RubyLsp
20
20
 
21
21
  END_REGEXES = T.let(
22
22
  [
23
- /(if|unless|for|while|class|module|until|def|case).*/,
24
- /.*\sdo/,
23
+ /\b(if|unless|for|while|class|module|until|def|case)\b.*/,
24
+ /.*\s\bdo\b/,
25
25
  ],
26
26
  T::Array[Regexp],
27
27
  )
@@ -60,6 +60,8 @@ module RubyLsp
60
60
  handle_statement_end
61
61
  end
62
62
  end
63
+ when "d"
64
+ auto_indent_after_end_keyword
63
65
  end
64
66
 
65
67
  @edits
@@ -118,7 +120,7 @@ module RubyLsp
118
120
  current_line = @lines[@position[:line]]
119
121
  next_line = @lines[@position[:line] + 1]
120
122
 
121
- if current_line.nil? || current_line.strip.empty?
123
+ if current_line.nil? || current_line.strip.empty? || current_line.include?(")") || current_line.include?("]")
122
124
  add_edit_with_text("\n")
123
125
  add_edit_with_text("#{indents}end")
124
126
  move_cursor_to(@position[:line], @indentation + 2)
@@ -139,7 +141,6 @@ module RubyLsp
139
141
  sig { params(spaces: String).void }
140
142
  def handle_comment_line(spaces)
141
143
  add_edit_with_text("##{spaces}")
142
- move_cursor_to(@position[:line], @indentation + spaces.size + 1)
143
144
  end
144
145
 
145
146
  sig { params(text: String, position: Document::PositionShape).void }
@@ -186,6 +187,32 @@ module RubyLsp
186
187
 
187
188
  count
188
189
  end
190
+
191
+ sig { void }
192
+ def auto_indent_after_end_keyword
193
+ current_line = @lines[@position[:line]]
194
+ return unless current_line&.strip == "end"
195
+
196
+ target, _parent, _nesting = @document.locate_node({
197
+ line: @position[:line],
198
+ character: @position[:character] - 1,
199
+ })
200
+
201
+ statements = case target
202
+ when Prism::IfNode, Prism::UnlessNode, Prism::ForNode, Prism::WhileNode, Prism::UntilNode
203
+ target.statements
204
+ end
205
+ return unless statements
206
+
207
+ statements.body.each do |node|
208
+ loc = node.location
209
+ next unless loc.start_column == @indentation
210
+
211
+ add_edit_with_text(" ", { line: loc.start_line - 1, character: 0 })
212
+ end
213
+
214
+ move_cursor_to(@position[:line], @position[:character])
215
+ end
189
216
  end
190
217
  end
191
218
  end
@@ -107,20 +107,15 @@ module RubyLsp
107
107
  sig { override.returns(ResponseType) }
108
108
  attr_reader :_response
109
109
 
110
- sig do
111
- params(
112
- dispatcher: Prism::Dispatcher,
113
- message_queue: Thread::Queue,
114
- range: T.nilable(T::Range[Integer]),
115
- ).void
116
- end
117
- def initialize(dispatcher, message_queue, range: nil)
118
- super(dispatcher, message_queue)
110
+ sig { params(dispatcher: Prism::Dispatcher, range: T.nilable(T::Range[Integer])).void }
111
+ def initialize(dispatcher, range: nil)
112
+ super(dispatcher)
119
113
 
120
114
  @_response = T.let([], ResponseType)
121
115
  @range = range
122
116
  @special_methods = T.let(nil, T.nilable(T::Array[String]))
123
117
  @current_scope = T.let(ParameterScope.new, ParameterScope)
118
+ @inside_regex_capture = T.let(false, T::Boolean)
124
119
 
125
120
  dispatcher.register(
126
121
  self,
@@ -152,6 +147,8 @@ module RubyLsp
152
147
  :on_local_variable_or_write_node_enter,
153
148
  :on_local_variable_target_node_enter,
154
149
  :on_block_local_variable_node_enter,
150
+ :on_match_write_node_enter,
151
+ :on_match_write_node_leave,
155
152
  )
156
153
  end
157
154
 
@@ -165,14 +162,28 @@ module RubyLsp
165
162
  # We can't push a semantic token for [] and []= because the argument inside the brackets is a part of
166
163
  # the message_loc
167
164
  return if message.start_with?("[") && (message.end_with?("]") || message.end_with?("]="))
168
-
169
- return process_regexp_locals(node) if message == "=~"
165
+ return if message == "=~"
170
166
  return if special_method?(message)
171
167
 
172
168
  type = Support::Sorbet.annotation?(node) ? :type : :method
173
169
  add_token(T.must(node.message_loc), type)
174
170
  end
175
171
 
172
+ sig { params(node: Prism::MatchWriteNode).void }
173
+ def on_match_write_node_enter(node)
174
+ call = node.call
175
+
176
+ if call.message == "=~"
177
+ @inside_regex_capture = true
178
+ process_regexp_locals(call)
179
+ end
180
+ end
181
+
182
+ sig { params(node: Prism::MatchWriteNode).void }
183
+ def on_match_write_node_leave(node)
184
+ @inside_regex_capture = true if node.call.message == "=~"
185
+ end
186
+
176
187
  sig { params(node: Prism::ConstantReadNode).void }
177
188
  def on_constant_read_node_enter(node)
178
189
  return unless visible?(node, @range)
@@ -359,6 +370,12 @@ module RubyLsp
359
370
 
360
371
  sig { params(node: Prism::LocalVariableTargetNode).void }
361
372
  def on_local_variable_target_node_enter(node)
373
+ # If we're inside a regex capture, Prism will add LocalVariableTarget nodes for each captured variable.
374
+ # Unfortunately, if the regex contains a backslash, the location will be incorrect and we'll end up highlighting
375
+ # the entire regex as a local variable. We process these captures in process_regexp_locals instead and then
376
+ # prevent pushing local variable target tokens. See https://github.com/ruby/prism/issues/1912
377
+ return if @inside_regex_capture
378
+
362
379
  return unless visible?(node, @range)
363
380
 
364
381
  add_token(node.location, @current_scope.type_for(node.name))
@@ -19,30 +19,23 @@ module RubyLsp
19
19
  T::Hash[Symbol, Integer],
20
20
  )
21
21
 
22
- sig { params(offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
23
- def initialize(offense, uri)
22
+ # TODO: avoid passing document once we have alternative ways to get at
23
+ # encoding and file source
24
+ sig { params(document: Document, offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
25
+ def initialize(document, offense, uri)
26
+ @document = document
24
27
  @offense = offense
25
28
  @uri = uri
26
29
  end
27
30
 
28
- sig { returns(Interface::CodeAction) }
29
- def to_lsp_code_action
30
- Interface::CodeAction.new(
31
- title: "Autocorrect #{@offense.cop_name}",
32
- kind: Constant::CodeActionKind::QUICK_FIX,
33
- edit: Interface::WorkspaceEdit.new(
34
- document_changes: [
35
- Interface::TextDocumentEdit.new(
36
- text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
37
- uri: @uri.to_s,
38
- version: nil,
39
- ),
40
- edits: @offense.correctable? ? offense_replacements : [],
41
- ),
42
- ],
43
- ),
44
- is_preferred: true,
45
- )
31
+ sig { returns(T::Array[Interface::CodeAction]) }
32
+ def to_lsp_code_actions
33
+ code_actions = []
34
+
35
+ code_actions << autocorrect_action if @offense.correctable?
36
+ code_actions << disable_line_action
37
+
38
+ code_actions
46
39
  end
47
40
 
48
41
  sig { returns(Interface::Diagnostic) }
@@ -65,7 +58,7 @@ module RubyLsp
65
58
  ),
66
59
  data: {
67
60
  correctable: @offense.correctable?,
68
- code_action: to_lsp_code_action,
61
+ code_actions: to_lsp_code_actions,
69
62
  },
70
63
  )
71
64
  end
@@ -90,6 +83,26 @@ module RubyLsp
90
83
  Interface::CodeDescription.new(href: doc_url) if doc_url
91
84
  end
92
85
 
86
+ sig { returns(Interface::CodeAction) }
87
+ def autocorrect_action
88
+ Interface::CodeAction.new(
89
+ title: "Autocorrect #{@offense.cop_name}",
90
+ kind: Constant::CodeActionKind::QUICK_FIX,
91
+ edit: Interface::WorkspaceEdit.new(
92
+ document_changes: [
93
+ Interface::TextDocumentEdit.new(
94
+ text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
95
+ uri: @uri.to_s,
96
+ version: nil,
97
+ ),
98
+ edits: @offense.correctable? ? offense_replacements : [],
99
+ ),
100
+ ],
101
+ ),
102
+ is_preferred: true,
103
+ )
104
+ end
105
+
93
106
  sig { returns(T::Array[Interface::TextEdit]) }
94
107
  def offense_replacements
95
108
  @offense.corrector.as_replacements.map do |range, replacement|
@@ -102,6 +115,64 @@ module RubyLsp
102
115
  )
103
116
  end
104
117
  end
118
+
119
+ sig { returns(Interface::CodeAction) }
120
+ def disable_line_action
121
+ Interface::CodeAction.new(
122
+ title: "Disable #{@offense.cop_name} for this line",
123
+ kind: Constant::CodeActionKind::QUICK_FIX,
124
+ edit: Interface::WorkspaceEdit.new(
125
+ document_changes: [
126
+ Interface::TextDocumentEdit.new(
127
+ text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
128
+ uri: @uri.to_s,
129
+ version: nil,
130
+ ),
131
+ edits: line_disable_comment,
132
+ ),
133
+ ],
134
+ ),
135
+ )
136
+ end
137
+
138
+ sig { returns(T::Array[Interface::TextEdit]) }
139
+ def line_disable_comment
140
+ new_text = if @offense.source_line.include?(" # rubocop:disable ")
141
+ ",#{@offense.cop_name}"
142
+ else
143
+ " # rubocop:disable #{@offense.cop_name}"
144
+ end
145
+
146
+ eol = Interface::Position.new(
147
+ line: @offense.line - 1,
148
+ character: length_of_line(@offense.source_line),
149
+ )
150
+
151
+ # TODO: fails for multiline strings - may be preferable to use block
152
+ # comments to disable some offenses
153
+ inline_comment = Interface::TextEdit.new(
154
+ range: Interface::Range.new(start: eol, end: eol),
155
+ new_text: new_text,
156
+ )
157
+
158
+ [inline_comment]
159
+ end
160
+
161
+ sig { params(line: String).returns(Integer) }
162
+ def length_of_line(line)
163
+ if @document.encoding == Constant::PositionEncodingKind::UTF16
164
+ line_length = 0
165
+ line.codepoints.each do |codepoint|
166
+ line_length += 1
167
+ if codepoint > RubyLsp::Document::Scanner::SURROGATE_PAIR_START
168
+ line_length += 1
169
+ end
170
+ end
171
+ line_length
172
+ else
173
+ line.length
174
+ end
175
+ end
105
176
  end
106
177
  end
107
178
  end
@@ -25,7 +25,7 @@ module RubyLsp
25
25
  @runner.run(filename, document.source)
26
26
 
27
27
  @runner.offenses.map do |offense|
28
- Support::RuboCopDiagnostic.new(offense, uri)
28
+ Support::RuboCopDiagnostic.new(document, offense, uri)
29
29
  end
30
30
  end
31
31
  end
@@ -34,15 +34,10 @@ module RubyLsp
34
34
 
35
35
  sig { override.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
36
36
  def run(uri, document)
37
- relative_path = Pathname.new(T.must(uri.to_standardized_path || uri.opaque))
38
- .relative_path_from(T.must(WORKSPACE_URI.to_standardized_path))
39
- return if @options.ignore_files.any? { |pattern| File.fnmatch(pattern, relative_path) }
37
+ path = uri.to_standardized_path
38
+ return if path && @options.ignore_files.any? { |pattern| File.fnmatch?("*/#{pattern}", path) }
40
39
 
41
- SyntaxTree.format(
42
- document.source,
43
- @options.print_width,
44
- options: @options.formatter_options,
45
- )
40
+ SyntaxTree.format(document.source, @options.print_width, options: @options.formatter_options)
46
41
  end
47
42
  end
48
43
  end
@@ -75,6 +75,8 @@ module RubyLsp
75
75
  Constant::SymbolKind::CONSTANT
76
76
  when RubyIndexer::Entry::Method
77
77
  entry.name == "initialize" ? Constant::SymbolKind::CONSTRUCTOR : Constant::SymbolKind::METHOD
78
+ when RubyIndexer::Entry::Accessor
79
+ Constant::SymbolKind::PROPERTY
78
80
  end
79
81
  end
80
82
  end
@@ -17,6 +17,12 @@ module RubyLsp
17
17
  sig { returns(T::Boolean) }
18
18
  attr_accessor :experimental_features
19
19
 
20
+ sig { returns(URI::Generic) }
21
+ attr_accessor :workspace_uri
22
+
23
+ sig { returns(T::Hash[Symbol, RequestConfig]) }
24
+ attr_accessor :features_configuration
25
+
20
26
  sig { void }
21
27
  def initialize
22
28
  @state = T.let({}, T::Hash[String, Document])
@@ -24,6 +30,21 @@ module RubyLsp
24
30
  @formatter = T.let("auto", String)
25
31
  @supports_progress = T.let(true, T::Boolean)
26
32
  @experimental_features = T.let(false, T::Boolean)
33
+ @workspace_uri = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
34
+ @features_configuration = T.let(
35
+ {
36
+ codeLens: RequestConfig.new({
37
+ enableAll: false,
38
+ gemfileLinks: true,
39
+ }),
40
+ inlayHint: RequestConfig.new({
41
+ enableAll: false,
42
+ implicitRescue: false,
43
+ implicitHashValue: false,
44
+ }),
45
+ },
46
+ T::Hash[Symbol, RequestConfig],
47
+ )
27
48
  end
28
49
 
29
50
  sig { params(uri: URI::Generic).returns(Document) }
@@ -4,10 +4,6 @@
4
4
  module RubyLsp
5
5
  # Used to indicate that a request shouldn't return a response
6
6
  VOID = T.let(Object.new.freeze, Object)
7
-
8
- # This freeze is not redundant since the interpolated string is mutable
9
- WORKSPACE_URI = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
10
-
11
7
  BUNDLE_PATH = T.let(
12
8
  begin
13
9
  Bundler.bundle_path.to_s
@@ -78,4 +74,22 @@ module RubyLsp
78
74
  @cancelled = true
79
75
  end
80
76
  end
77
+
78
+ # A request configuration, to turn on/off features
79
+ class RequestConfig
80
+ extend T::Sig
81
+
82
+ sig { returns(T::Hash[Symbol, T::Boolean]) }
83
+ attr_accessor :configuration
84
+
85
+ sig { params(configuration: T::Hash[Symbol, T::Boolean]).void }
86
+ def initialize(configuration)
87
+ @configuration = configuration
88
+ end
89
+
90
+ sig { params(feature: Symbol).returns(T.nilable(T::Boolean)) }
91
+ def enabled?(feature)
92
+ @configuration[:enableAll] || @configuration[feature]
93
+ end
94
+ end
81
95
  end
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.12.5
4
+ version: 0.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-11-24 00:00:00.000000000 Z
11
+ date: 2023-12-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -30,20 +30,20 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.17.1
33
+ version: 0.18.0
34
34
  - - "<"
35
35
  - !ruby/object:Gem::Version
36
- version: '0.18'
36
+ version: '0.19'
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
- version: 0.17.1
43
+ version: 0.18.0
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
- version: '0.18'
46
+ version: '0.19'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: sorbet-runtime
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -157,7 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
157
157
  - !ruby/object:Gem::Version
158
158
  version: '0'
159
159
  requirements: []
160
- rubygems_version: 3.4.21
160
+ rubygems_version: 3.4.22
161
161
  signing_key:
162
162
  specification_version: 4
163
163
  summary: An opinionated language server for Ruby