ruby-lsp 0.12.5 → 0.13.1

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