ruby-lsp 0.6.0 → 0.6.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d7205054d59bb5a292bc8e2d87a984663ce1f0dac527d6d69d3940bc6e86735
4
- data.tar.gz: 9b760d79ca5406fe3e78e4bd1276cb507464d72d2fc2e68cf5abf1f9920df1a6
3
+ metadata.gz: 0da1b154c2c048cdea8d1ca6f87e77b895482cdbea155058550eff983fdbae1c
4
+ data.tar.gz: 7a67502b28e7e525acc6349fafe5de9e9945350d84af72d0a9afaa5d5b0803d8
5
5
  SHA512:
6
- metadata.gz: bf465a2d9af7e109b21ecd861cb88b556ee872e4d46e292e791f8f85ce131af7791254187bcc68216b9df0f1276296b5cf80e78be7193c11fdf16831b8da4f0f
7
- data.tar.gz: 57fa298a96b7cd3eeb2be68d21261ae634f8e70d818a5c68b2e7cdcfa464d7d27b16e90aecbfb8192a4a3ff9fecc4fa8b94baa3b3b51d9c7c887f4fba2b07bd5
6
+ metadata.gz: 77968226990bdcbab7026e962a3b7e1e3459f920939aa19a50331daa7b4a24b180eb46a342b5385412a43b73d1d6e40d6f9de2f1321a512cf65d36046b978cbc
7
+ data.tar.gz: a27e182f7c554d9b231f4ea69b15b52d0c4c32fe0cbb17879480c8b9ada87288c59ffa93fbfd99963c456bf4a6298164c920f720611532609f5e985e3c1e890c
data/README.md CHANGED
@@ -42,6 +42,15 @@ See the [documentation](https://shopify.github.io/ruby-lsp) for more in-depth de
42
42
  For creating rich themes for Ruby using the semantic highlighting information, see the [semantic highlighting
43
43
  documentation](SEMANTIC_HIGHLIGHTING.md).
44
44
 
45
+ ### Extensions
46
+
47
+ The Ruby LSP provides a server extension system that allows other gems to enhance the base functionality with more
48
+ editor features. This is the mechanism that powers extensions like
49
+
50
+ - [Ruby LSP Rails](https://github.com/Shopify/ruby-lsp-rails)
51
+
52
+ For instructions on how to create extensions, see the [server extensions documentation](SERVER_EXTENSIONS.md).
53
+
45
54
  ## Learn More
46
55
 
47
56
  * [RubyConf 2022: Improving the development experience with language servers](https://www.youtube.com/watch?v=kEfXPTm1aCI) ([Vinicius Stock](https://github.com/vinistock))
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.0
1
+ 0.6.2
@@ -6,23 +6,26 @@ require "objspace"
6
6
 
7
7
  module RubyLsp
8
8
  # This rake task checks that all requests or extensions are fully documented. Add the rake task to your Rakefile and
9
- # specify the absolute path for all files that must be required in order to discover all listeners
9
+ # specify the absolute path for all files that must be required in order to discover all listeners and their
10
+ # related GIFs
10
11
  #
11
12
  # # Rakefile
12
13
  # request_files = FileList.new("#{__dir__}/lib/ruby_lsp/requests/*.rb") do |fl|
13
14
  # fl.exclude(/base_request\.rb/)
14
15
  # end
15
- # RubyLsp::CheckDocs.new(request_files)
16
+ # gif_files = FileList.new("#{__dir__}/**/*.gif")
17
+ # RubyLsp::CheckDocs.new(request_files, gif_files)
16
18
  # # Run with bundle exec rake ruby_lsp:check_docs
17
19
  class CheckDocs < Rake::TaskLib
18
20
  extend T::Sig
19
21
 
20
- sig { params(require_files: Rake::FileList).void }
21
- def initialize(require_files)
22
+ sig { params(require_files: Rake::FileList, gif_files: Rake::FileList).void }
23
+ def initialize(require_files, gif_files)
22
24
  super()
23
25
 
24
26
  @name = T.let("ruby_lsp:check_docs", String)
25
27
  @file_list = require_files
28
+ @gif_list = gif_files
26
29
  define_task
27
30
  end
28
31
 
@@ -34,6 +37,13 @@ module RubyLsp
34
37
  task(@name) { run_task }
35
38
  end
36
39
 
40
+ sig { params(request_path: String).returns(T::Boolean) }
41
+ def gif_exists?(request_path)
42
+ request_gif = request_path.gsub(".rb", ".gif").split("/").last
43
+
44
+ @gif_list.any? { |gif_path| gif_path.end_with?(request_gif) }
45
+ end
46
+
37
47
  sig { void }
38
48
  def run_task
39
49
  # Require all files configured to make sure all listeners are loaded
@@ -42,7 +52,7 @@ module RubyLsp
42
52
  # Find all classes that inherit from BaseRequest or Listener, which are the ones we want to make sure are
43
53
  # documented
44
54
  features = ObjectSpace.each_object(Class).filter_map do |k|
45
- klass = T.cast(k, T::Class[T.anything])
55
+ klass = T.unsafe(k)
46
56
  klass if klass < RubyLsp::Requests::BaseRequest || klass < RubyLsp::Listener
47
57
  end
48
58
 
@@ -93,6 +103,14 @@ module RubyLsp
93
103
 
94
104
  # [Inlay hint demo](../../inlay_hint.gif)
95
105
  DOCS
106
+ elsif !gif_exists?(file_path)
107
+ T.must(missing_docs[class_name]) << <<~DOCS
108
+ The GIF for the request documentation does not exist. Make sure to add it,
109
+ with the same naming as the request. For example:
110
+
111
+ # lib/ruby_lsp/requests/code_lens.rb
112
+ # foo/bar/code_lens.gif
113
+ DOCS
96
114
  end
97
115
  end
98
116
 
@@ -53,6 +53,12 @@ module RubyLsp
53
53
 
54
54
  # Visit dispatchers are below. Notice that for nodes that create a new scope (e.g.: classes, modules, method defs)
55
55
  # we need both an `on_*` and `after_*` event. This is because some requests must know when we exit the scope
56
+ sig { override.params(node: T.nilable(SyntaxTree::Node)).void }
57
+ def visit(node)
58
+ @listeners[:on_node]&.each { |l| T.unsafe(l).on_node(node) }
59
+ super
60
+ end
61
+
56
62
  sig { override.params(node: SyntaxTree::ClassDeclaration).void }
57
63
  def visit_class(node)
58
64
  @listeners[:on_class]&.each { |l| T.unsafe(l).on_class(node) }
@@ -130,7 +130,7 @@ module RubyLsp
130
130
  )
131
131
 
132
132
  nil
133
- rescue StandardError => error
133
+ rescue StandardError, LoadError => error
134
134
  @message_queue << Notification.new(
135
135
  message: "window/showMessage",
136
136
  params: Interface::ShowMessageParams.new(
@@ -156,7 +156,7 @@ module RubyLsp
156
156
  when "textDocument/diagnostic"
157
157
  begin
158
158
  diagnostic(uri)
159
- rescue StandardError => error
159
+ rescue StandardError, LoadError => error
160
160
  @message_queue << Notification.new(
161
161
  message: "window/showMessage",
162
162
  params: Interface::ShowMessageParams.new(
@@ -275,10 +275,17 @@ module RubyLsp
275
275
  params(
276
276
  uri: String,
277
277
  position: Document::PositionShape,
278
- ).returns(T::Array[Interface::DocumentHighlight])
278
+ ).returns(T.nilable(T::Array[Interface::DocumentHighlight]))
279
279
  end
280
280
  def document_highlight(uri, position)
281
- Requests::DocumentHighlight.new(@store.get(uri), position).run
281
+ document = @store.get(uri)
282
+ return if document.syntax_error?
283
+
284
+ target, parent = document.locate_node(position)
285
+ emitter = EventEmitter.new
286
+ listener = Requests::DocumentHighlight.new(target, parent, emitter, @message_queue)
287
+ emitter.visit(document.tree)
288
+ listener.response
282
289
  end
283
290
 
284
291
  sig { params(uri: String, range: Document::RangeShape).returns(T.nilable(T::Array[Interface::InlayHint])) }
@@ -29,6 +29,7 @@ module RubyLsp
29
29
 
30
30
  BASE_COMMAND = T.let((File.exist?("Gemfile.lock") ? "bundle exec ruby" : "ruby") + " -Itest ", String)
31
31
  ACCESS_MODIFIERS = T.let(["public", "private", "protected"], T::Array[String])
32
+ SUPPORTED_TEST_LIBRARIES = T.let(["minitest", "test-unit"], T::Array[String])
32
33
 
33
34
  sig { override.returns(ResponseType) }
34
35
  attr_reader :response
@@ -61,8 +62,9 @@ module RubyLsp
61
62
  def on_class(node)
62
63
  @visibility_stack.push(["public", "public"])
63
64
  class_name = node.constant.constant.value
65
+ @class_stack.push(class_name)
66
+
64
67
  if class_name.end_with?("Test")
65
- @class_stack.push(class_name)
66
68
  add_test_code_lens(
67
69
  node,
68
70
  name: class_name,
@@ -80,11 +82,13 @@ module RubyLsp
80
82
 
81
83
  sig { params(node: SyntaxTree::DefNode).void }
82
84
  def on_def(node)
85
+ class_name = @class_stack.last
86
+ return unless class_name&.end_with?("Test")
87
+
83
88
  visibility, _ = @visibility_stack.last
84
89
  if visibility == "public"
85
90
  method_name = node.name.value
86
91
  if method_name.start_with?("test_")
87
- class_name = T.must(@class_stack.last)
88
92
  add_test_code_lens(
89
93
  node,
90
94
  name: method_name,
@@ -154,6 +158,9 @@ module RubyLsp
154
158
 
155
159
  sig { params(node: SyntaxTree::Node, name: String, command: String, kind: Symbol).void }
156
160
  def add_test_code_lens(node, name:, command:, kind:)
161
+ # don't add code lenses if the test library is not supported or unknown
162
+ return unless SUPPORTED_TEST_LIBRARIES.include?(@test_library)
163
+
157
164
  arguments = [
158
165
  @path,
159
166
  name,
@@ -193,8 +200,13 @@ module RubyLsp
193
200
 
194
201
  sig { params(node: SyntaxTree::Command).returns(T.nilable(String)) }
195
202
  def resolve_gem_remote(node)
196
- gem_name = node.arguments.parts.flat_map(&:child_nodes).first.value
197
- spec = Gem::Specification.stubs.find { |gem| gem.name == gem_name }&.to_spec
203
+ gem_statement = node.arguments.parts.first
204
+ return unless gem_statement.is_a?(SyntaxTree::StringLiteral)
205
+
206
+ gem_name = gem_statement.parts.first
207
+ return unless gem_name.is_a?(SyntaxTree::TStringContent)
208
+
209
+ spec = Gem::Specification.stubs.find { |gem| gem.name == gem_name.value }&.to_spec
198
210
  return if spec.nil?
199
211
 
200
212
  [spec.homepage, spec.metadata["source_code_uri"]].compact.find do |page|
@@ -22,34 +22,49 @@ module RubyLsp
22
22
  # FOO # should be highlighted as "read"
23
23
  # end
24
24
  # ```
25
- class DocumentHighlight < BaseRequest
25
+ class DocumentHighlight < Listener
26
26
  extend T::Sig
27
27
 
28
- sig { params(document: Document, position: Document::PositionShape).void }
29
- def initialize(document, position)
30
- super(document)
28
+ ResponseType = type_member { { fixed: T::Array[Interface::DocumentHighlight] } }
31
29
 
32
- @highlights = T.let([], T::Array[Interface::DocumentHighlight])
33
- return unless document.parsed?
30
+ sig { override.returns(ResponseType) }
31
+ attr_reader :response
34
32
 
35
- @target = T.let(find(position), T.nilable(Support::HighlightTarget))
33
+ sig do
34
+ params(
35
+ target: T.nilable(SyntaxTree::Node),
36
+ parent: T.nilable(SyntaxTree::Node),
37
+ emitter: EventEmitter,
38
+ message_queue: Thread::Queue,
39
+ ).void
36
40
  end
41
+ def initialize(target, parent, emitter, message_queue)
42
+ super(emitter, message_queue)
43
+
44
+ @response = T.let([], T::Array[Interface::DocumentHighlight])
45
+
46
+ return unless target && parent
37
47
 
38
- sig { override.returns(T.all(T::Array[Interface::DocumentHighlight], Object)) }
39
- def run
40
- # no @target means the target is not highlightable
41
- visit(@document.tree) if @document.parsed? && @target
42
- @highlights
48
+ highlight_target =
49
+ case target
50
+ when *DIRECT_HIGHLIGHTS
51
+ Support::HighlightTarget.new(target)
52
+ when SyntaxTree::Ident
53
+ relevant_node = parent.is_a?(SyntaxTree::Params) ? target : parent
54
+ Support::HighlightTarget.new(relevant_node)
55
+ end
56
+
57
+ @target = T.let(highlight_target, T.nilable(Support::HighlightTarget))
58
+
59
+ emitter.register(self, :on_node) if @target
43
60
  end
44
61
 
45
- sig { override.params(node: T.nilable(SyntaxTree::Node)).void }
46
- def visit(node)
62
+ sig { params(node: T.nilable(SyntaxTree::Node)).void }
63
+ def on_node(node)
47
64
  return if node.nil?
48
65
 
49
66
  match = T.must(@target).highlight_type(node)
50
67
  add_highlight(match) if match
51
-
52
- super
53
68
  end
54
69
 
55
70
  private
@@ -65,30 +80,10 @@ module RubyLsp
65
80
  T::Array[T.class_of(SyntaxTree::Node)],
66
81
  )
67
82
 
68
- sig do
69
- params(
70
- position: Document::PositionShape,
71
- ).returns(T.nilable(Support::HighlightTarget))
72
- end
73
- def find(position)
74
- matched, parent = @document.locate_node(position)
75
-
76
- return unless matched && parent
77
- return unless matched.is_a?(SyntaxTree::Ident) || DIRECT_HIGHLIGHTS.include?(matched.class)
78
-
79
- case matched
80
- when *DIRECT_HIGHLIGHTS
81
- Support::HighlightTarget.new(matched)
82
- when SyntaxTree::Ident
83
- relevant_node = parent.is_a?(SyntaxTree::Params) ? matched : parent
84
- Support::HighlightTarget.new(relevant_node)
85
- end
86
- end
87
-
88
83
  sig { params(match: Support::HighlightTarget::HighlightMatch).void }
89
84
  def add_highlight(match)
90
85
  range = range_from_syntax_tree_node(match.node)
91
- @highlights << Interface::DocumentHighlight.new(range: range, kind: match.type)
86
+ @response << Interface::DocumentHighlight.new(range: range, kind: match.type)
92
87
  end
93
88
  end
94
89
  end
@@ -3,7 +3,7 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
- # ![Inlay hint demo](../../inlay_hint.gif)
6
+ # ![Inlay hint demo](../../inlay_hints.gif)
7
7
  #
8
8
  # [Inlay hints](https://microsoft.github.io/language-server-protocol/specification#textDocument_inlayHint)
9
9
  # are labels added directly in the code that explicitly show the user something that might
@@ -30,13 +30,11 @@ module RubyLsp
30
30
  def initialize(document, position, trigger_character)
31
31
  super(document)
32
32
 
33
- scanner = document.create_scanner
34
- line_begin = position[:line] == 0 ? 0 : scanner.find_char_position({ line: position[:line] - 1, character: 0 })
35
- @line_end = T.let(scanner.find_char_position(position), Integer)
36
- line = T.must(@document.source[line_begin..@line_end])
33
+ @lines = T.let(@document.source.lines, T::Array[String])
34
+ line = @lines[[position[:line] - 1, 0].max]
37
35
 
38
- @indentation = T.let(find_indentation(line), Integer)
39
- @previous_line = T.let(line.strip.chomp, String)
36
+ @indentation = T.let(line ? find_indentation(line) : 0, Integer)
37
+ @previous_line = T.let(line ? line.strip.chomp : "", String)
40
38
  @position = position
41
39
  @edits = T.let([], T::Array[Interface::TextEdit])
42
40
  @trigger_character = trigger_character
@@ -87,30 +85,15 @@ module RubyLsp
87
85
  return unless END_REGEXES.any? { |regex| regex.match?(@previous_line) }
88
86
 
89
87
  indents = " " * @indentation
88
+ current_line = @lines[@position[:line]]
89
+ next_line = @lines[@position[:line] + 1]
90
90
 
91
- if @previous_line.include?("\n")
92
- # If the previous line has a line break, then it means there's content after the line break that triggered
93
- # this completion. For these cases, we want to add the `end` after the content and move the cursor back to the
94
- # keyword that triggered the completion
95
-
96
- line = @position[:line]
97
-
98
- # If there are enough lines in the document, we want to add the `end` token on the line below the extra
99
- # content. Otherwise, we want to insert and extra line break ourselves
100
- correction = if T.must(@document.source[@line_end..-1]).count("\n") >= 2
101
- line -= 1
102
- "#{indents}end"
103
- else
104
- "#{indents}\nend"
105
- end
106
-
107
- add_edit_with_text(correction, { line: @position[:line] + 1, character: @position[:character] })
108
- move_cursor_to(line, @indentation + 3)
109
- else
110
- # If there's nothing after the new line break that triggered the completion, then we want to add the `end` and
111
- # move the cursor to the body of the statement
91
+ if current_line.nil? || current_line.blank?
112
92
  add_edit_with_text(" \n#{indents}end")
113
93
  move_cursor_to(@position[:line], @indentation + 2)
94
+ elsif next_line.nil? || next_line.blank?
95
+ add_edit_with_text("#{indents}end", { line: @position[:line] + 1, character: @position[:character] })
96
+ move_cursor_to(@position[:line], @indentation + 3)
114
97
  end
115
98
  end
116
99
 
@@ -20,15 +20,19 @@ module RubyLsp
20
20
 
21
21
  sig { returns(String) }
22
22
  def detected_test_library
23
- if direct_dependency?(/^minitest/)
23
+ # A Rails app may have a dependency on minitest, but we would instead want to use the Rails test runner provided
24
+ # by ruby-lsp-rails.
25
+ if direct_dependency?(/^rails$/)
26
+ "rails"
27
+ # NOTE: Intentionally ends with $ to avoid mis-matching minitest-reporters, etc. in a Rails app.
28
+ elsif direct_dependency?(/^minitest$/)
24
29
  "minitest"
25
30
  elsif direct_dependency?(/^test-unit/)
26
31
  "test-unit"
27
32
  elsif direct_dependency?(/^rspec/)
28
33
  "rspec"
29
34
  else
30
- warn("WARNING: No test library detected. Assuming minitest.")
31
- "minitest"
35
+ "unknown"
32
36
  end
33
37
  end
34
38
 
@@ -78,7 +78,7 @@ module URI
78
78
  if URI.respond_to?(:register_scheme)
79
79
  URI.register_scheme("SOURCE", self)
80
80
  else
81
- @@schemes = T.let(@@schemes, T::Hash[String, T::Class[T.anything]]) # rubocop:disable Style/ClassVars
81
+ @@schemes = T.let(@@schemes, T::Hash[String, T.untyped]) # rubocop:disable Style/ClassVars
82
82
  @@schemes["SOURCE"] = self
83
83
  end
84
84
  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.6.0
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-06-16 00:00:00.000000000 Z
11
+ date: 2023-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol