ruby-lsp 0.4.5 → 0.5.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/README.md +5 -86
  3. data/VERSION +1 -1
  4. data/lib/ruby_lsp/check_docs.rb +112 -0
  5. data/lib/ruby_lsp/document.rb +13 -2
  6. data/lib/ruby_lsp/event_emitter.rb +84 -18
  7. data/lib/ruby_lsp/executor.rb +126 -48
  8. data/lib/ruby_lsp/internal.rb +2 -0
  9. data/lib/ruby_lsp/listener.rb +6 -13
  10. data/lib/ruby_lsp/requests/base_request.rb +0 -5
  11. data/lib/ruby_lsp/requests/code_action_resolve.rb +1 -1
  12. data/lib/ruby_lsp/requests/code_actions.rb +1 -1
  13. data/lib/ruby_lsp/requests/code_lens.rb +82 -66
  14. data/lib/ruby_lsp/requests/diagnostics.rb +2 -2
  15. data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
  16. data/lib/ruby_lsp/requests/document_link.rb +17 -15
  17. data/lib/ruby_lsp/requests/document_symbol.rb +51 -31
  18. data/lib/ruby_lsp/requests/folding_ranges.rb +1 -1
  19. data/lib/ruby_lsp/requests/formatting.rb +10 -11
  20. data/lib/ruby_lsp/requests/hover.rb +20 -20
  21. data/lib/ruby_lsp/requests/inlay_hints.rb +1 -1
  22. data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
  23. data/lib/ruby_lsp/requests/path_completion.rb +21 -57
  24. data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
  25. data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -1
  26. data/lib/ruby_lsp/requests/support/common.rb +36 -0
  27. data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +0 -1
  28. data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +0 -1
  29. data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +5 -2
  30. data/lib/ruby_lsp/requests.rb +15 -15
  31. data/lib/ruby_lsp/server.rb +44 -20
  32. data/lib/ruby_lsp/store.rb +1 -1
  33. data/lib/ruby_lsp/utils.rb +2 -7
  34. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 437c2048862bed450fd70057489636117c99943dd7bb68df35c4e161a29f8385
4
- data.tar.gz: 30d26e8455f9086bc934ffe23b5e37816f18f5904e3422495c316a980a860cfc
3
+ metadata.gz: eadf22b9ba9191df338c0da93b3ce6b98ddf61a4831cf204f79ad7c636695fcd
4
+ data.tar.gz: c0f7fc6ec73638ffc2075e86e40925a14a2e629d9ee79e10a0ed5039f9fee3f2
5
5
  SHA512:
6
- metadata.gz: 366dd4b756ded58762b598545e398059fe0eae7578b13c92cc0a90dcc9aba2d58106dbf2dc0e9867a9db7de5007e6114ebbbf7cf191bcdebb843b077de945ba9
7
- data.tar.gz: 1f798c1462825cccafd48bff23baecb8ba8ef36f9b5733502d79c5d8e6e52550c7ab722a99678eec04ce2a8b42c4d84aab912acb2100ff7d26e422b02f2c185b
6
+ metadata.gz: b3ceb8e427dabbb4819d105b29a82fb719c61c4d2bd69b48c215b7010b6ad79f06bf71f57dc936f8ef74440fd0678b5e3640880008e8d76fe3be50091b8e0da1
7
+ data.tar.gz: 855d9cff0ee7a3db7fe2699b08fe8d3cb7fc7edb39448fb9821ab58b4c234763f2489388a995de216e90f05ecbc4e4b22b7ef23b266bdff9654efbc0a1dc6bcd
data/README.md CHANGED
@@ -2,7 +2,6 @@
2
2
  [![Ruby LSP extension](https://img.shields.io/badge/VS%20Code-Ruby%20LSP-success?logo=visual-studio-code)](https://marketplace.visualstudio.com/items?itemName=Shopify.ruby-lsp)
3
3
  [![Ruby DX Slack](https://img.shields.io/badge/Slack-Ruby%20DX-success?logo=slack)](https://join.slack.com/t/ruby-dx/shared_invite/zt-1s6f4y15t-v9jedZ9YUPQLM91TEJ4Gew)
4
4
 
5
-
6
5
  # Ruby LSP
7
6
 
8
7
  The Ruby LSP is an implementation of the [language server protocol](https://microsoft.github.io/language-server-protocol/)
@@ -21,8 +20,7 @@ get the extra features in the editor. Do not install this gem manually.
21
20
 
22
21
  ### With other editors
23
22
 
24
- See [editors](https://github.com/Shopify/ruby-lsp/blob/main/EDITORS.md) for community instructions on setting up the
25
- Ruby LSP.
23
+ See [editors](EDITORS.md) for community instructions on setting up the Ruby LSP.
26
24
 
27
25
  The gem can be installed by doing
28
26
  ```shell
@@ -51,92 +49,13 @@ See the [documentation](https://shopify.github.io/ruby-lsp) for more in-depth de
51
49
  Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/ruby-lsp.
52
50
  This project is intended to be a safe, welcoming space for collaboration, and contributors
53
51
  are expected to adhere to the
54
- [Contributor Covenant](https://github.com/Shopify/ruby-lsp/blob/main/CODE_OF_CONDUCT.md)
52
+ [Contributor Covenant](CODE_OF_CONDUCT.md)
55
53
  code of conduct.
56
54
 
57
- ### Running the test suite
58
-
59
- Run the test suite with `bin/test`.
60
-
61
- For more visibility into which tests are running, use the `SpecReporter`:
62
-
63
- `SPEC_REPORTER=1 bin/test`
64
-
65
- By default the tests run with warnings disabled to reduce noise. To enable warnings, pass `VERBOSE=1`.
66
- Warnings are always shown when running in CI.
67
-
68
- ### Expectation testing
69
-
70
- To simplify the way we run tests over different pieces of Ruby code, we use a custom expectations test framework against a set of Ruby fixtures.
71
-
72
- We define expectations as `.exp` files, of which there are two variants:
73
- * `.exp.rb`, to indicate the resulting code after an operation.
74
- * `.exp.json`, consisting of a `result`, and an optional set of input `params`.
75
-
76
- To add a new fixture to the expectations test suite:
77
-
78
- 1. Add a new fixture `my_fixture.rb` file under `test/fixtures`
79
- 2. (optional) Add new expectations under `test/expectations/$HANDLER` for the request handlers you're concerned by
80
- 3. Profit by running `bin/test test/requests/$HANDLER_expectations_test my_fixture`
81
- * Handlers for which you added expectations will be checked with `assert_expectations`
82
- * Handlers without expectations will be ran against your new test to check that nothing breaks
83
-
84
- To add a new expectations test runner for a new request handler:
85
-
86
- 1. Add a new file under `test/requests/$HANDLER_expectations_test.rb` that subclasses `ExpectationsTestRunner` and calls `expectations_tests $HANDLER, "$EXPECTATIONS_DIR"` where: `$HANDLER` is the fully qualified name or your handler class and `$EXPECTATIONS_DIR` is the directory name where you want to store the expectation files.
87
-
88
- ```rb
89
- # frozen_string_literal: true
90
-
91
- require "test_helper"
92
- require "expectations/expectations_test_runner"
93
-
94
- class $HANDLERExpectationsTest < ExpectationsTestRunner
95
- expectations_tests RubyLsp::Requests::$HANDLER, "$EXPECTATIONS_DIR"
96
- end
97
- ```
98
-
99
- 2. (optional) Override the `run_expectations` and `assert_expectations` methods if needed. See the different request handler expectations runners under `test/requests/*_expectations_test.rb` for examples.
100
-
101
- 4. (optional) Add new fixtures for your handler under `test/fixtures`
102
-
103
- 5. (optional) Add new expectations under `test/expectations/$HANDLER`
104
- * No need to write the expectations by hand, just run the test with an empty expectation file and copy from the output.
105
-
106
- 7. Profit by running, `bin/test test/expectations_test $HANDLER`
107
- * Tests with expectations will be checked with `assert_expectations`
108
- * Tests without expectations will be ran against your new $HANDLER to check that nothing breaks
109
-
110
- ### Debugging
111
-
112
- ### Debugging Tests
113
-
114
- 1. Open the test file.
115
- 2. Set a breakpoint(s) on lines by clicking next to their numbers.
116
- 3. Open VS Code's `Run and Debug` panel.
117
- 4. At the top of the panel, select `Minitset - current file` and click the green triangle (or press F5).
118
-
119
- ### Debugging Running Ruby LSP Process
120
-
121
- 1. Open the `vscode-ruby-lsp` project in VS Code.
122
- 2. [`vscode-ruby-lsp`] Open VS Code's `Run and Debug` panel.
123
- 3. [`vscode-ruby-lsp`] Select `Run Extension` and click the green triangle (or press F5).
124
- 4. [`vscode-ruby-lsp`] Now VS Code will:
125
- - Open another workspace as the `Extension Development Host`.
126
- - Run `vscode-ruby-lsp` extension in debug mode, which will start a new `ruby-lsp` process with the `--debug` flag. Note that debugging is not available on Windows.
127
- 5. Open `ruby-lsp` in VS Code.
128
- 6. [`ruby-lsp`] Run `bin/rdbg -A` to connect to the running `ruby-lsp` process.
129
- 7. [`ruby-lsp`] Use commands like `b <file>:<line>` or `b Class#method` to set breakpoints and type `c` to continue the process.
130
- 8. In your `Extension Development Host` project (e.g. [`Tapioca`](https://github.com/Shopify/tapioca)), trigger the request that will hit the breakpoint.
131
-
132
- ### Spell Checking
133
-
134
- VS Code users will be prompted to enable the [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) extension.
135
- By default this will be enabled for all workspaces, but you can choose to selectively enable or disable it per workspace.
136
-
137
- If you introduce a word which the spell checker does not recognize, you can add it to the `cspell.json` configuration alongside your PR.
55
+ If you wish to contribute, see [CONTRIBUTING](CONTRIBUTING.md) for development instructions and check out our pinned
56
+ [roadmap issue](https://github.com/Shopify/ruby-lsp/issues) for a list of tasks to get started.
138
57
 
139
58
  ## License
140
59
 
141
60
  The gem is available as open source under the terms of the
142
- [MIT License](https://github.com/Shopify/ruby-lsp/blob/main/LICENSE.txt).
61
+ [MIT License](LICENSE.txt).
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.5
1
+ 0.5.1
@@ -0,0 +1,112 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "ruby_lsp/internal"
5
+ require "objspace"
6
+
7
+ module RubyLsp
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
10
+ #
11
+ # # Rakefile
12
+ # request_files = FileList.new("#{__dir__}/lib/ruby_lsp/requests/*.rb") do |fl|
13
+ # fl.exclude(/base_request\.rb/)
14
+ # end
15
+ # RubyLsp::CheckDocs.new(request_files)
16
+ # # Run with bundle exec rake ruby_lsp:check_docs
17
+ class CheckDocs < Rake::TaskLib
18
+ extend T::Sig
19
+
20
+ sig { params(require_files: Rake::FileList).void }
21
+ def initialize(require_files)
22
+ super()
23
+
24
+ @name = T.let("ruby_lsp:check_docs", String)
25
+ @file_list = require_files
26
+ define_task
27
+ end
28
+
29
+ private
30
+
31
+ sig { void }
32
+ def define_task
33
+ desc("Checks if all Ruby LSP listeners are documented")
34
+ task(@name) { run_task }
35
+ end
36
+
37
+ sig { void }
38
+ def run_task
39
+ # Require all files configured to make sure all listeners are loaded
40
+ @file_list.each { |f| require(f.delete_suffix(".rb")) }
41
+
42
+ # Find all classes that inherit from BaseRequest or Listener, which are the ones we want to make sure are
43
+ # documented
44
+ features = ObjectSpace.each_object(Class).filter_map do |k|
45
+ klass = T.cast(k, Class)
46
+ klass if klass < RubyLsp::Requests::BaseRequest || klass < RubyLsp::Listener
47
+ end
48
+
49
+ missing_docs = T.let(Hash.new { |h, k| h[k] = [] }, T::Hash[String, T::Array[String]])
50
+
51
+ features.each do |klass|
52
+ class_name = T.must(klass.name)
53
+ file_path, line_number = Module.const_source_location(class_name)
54
+ next unless file_path && line_number
55
+
56
+ # Adjust the line number to start searching right above the class definition
57
+ line_number -= 2
58
+
59
+ lines = File.readlines(file_path)
60
+ docs = []
61
+
62
+ # Extract the documentation on top of the listener constant
63
+ while (line = lines[line_number]&.strip) && line.start_with?("#")
64
+ docs.unshift(line)
65
+ line_number -= 1
66
+ end
67
+
68
+ documentation = docs.join("\n")
69
+
70
+ if docs.empty?
71
+ T.must(missing_docs[class_name]) << "No documentation found"
72
+ elsif !%r{\(https://microsoft.github.io/language-server-protocol/specification#.*\)}.match?(documentation)
73
+ T.must(missing_docs[class_name]) << <<~DOCS
74
+ Missing specification link. Requests and extensions should include a link to the LSP specification for the
75
+ related feature. For example:
76
+
77
+ [Inlay hint](https://microsoft.github.io/language-server-protocol/specification#textDocument_inlayHint)
78
+ DOCS
79
+ elsif !documentation.include?("# Example")
80
+ T.must(missing_docs[class_name]) << <<~DOCS
81
+ Missing example. Requests and extensions should include a code example that explains what the feature does.
82
+
83
+ # # Example
84
+ # ```ruby
85
+ # class Foo # <- information is shown here
86
+ # end
87
+ # ```
88
+ DOCS
89
+ elsif !/\[.* demo\]\(.*\.gif\)/.match?(documentation)
90
+ T.must(missing_docs[class_name]) << <<~DOCS
91
+ Missing demonstration GIF. Each request and extension must be documented with a GIF that shows the feature
92
+ working. For example:
93
+
94
+ # [Inlay hint demo](../../inlay_hint.gif)
95
+ DOCS
96
+ end
97
+ end
98
+
99
+ if missing_docs.any?
100
+ warn(<<~WARN)
101
+ The following listeners are missing documentation:
102
+
103
+ #{missing_docs.map { |k, v| "#{k}\n\n#{v.join("\n")}" }.join("\n\n")}
104
+ WARN
105
+
106
+ abort
107
+ end
108
+
109
+ puts "All listeners are documented!"
110
+ end
111
+ end
112
+ end
@@ -23,7 +23,7 @@ module RubyLsp
23
23
 
24
24
  sig { params(source: String, version: Integer, uri: String, encoding: String).void }
25
25
  def initialize(source:, version:, uri:, encoding: Constant::PositionEncodingKind::UTF8)
26
- @cache = T.let({}, T::Hash[Symbol, T.untyped])
26
+ @cache = T.let({}, T::Hash[String, T.untyped])
27
27
  @encoding = T.let(encoding, String)
28
28
  @source = T.let(source, String)
29
29
  @version = T.let(version, Integer)
@@ -40,10 +40,11 @@ module RubyLsp
40
40
  @source == other.source
41
41
  end
42
42
 
43
+ # TODO: remove this method once all nonpositional requests have been migrated to the listener pattern
43
44
  sig do
44
45
  type_parameters(:T)
45
46
  .params(
46
- request_name: Symbol,
47
+ request_name: String,
47
48
  block: T.proc.params(document: Document).returns(T.type_parameter(:T)),
48
49
  ).returns(T.type_parameter(:T))
49
50
  end
@@ -56,6 +57,16 @@ module RubyLsp
56
57
  result
57
58
  end
58
59
 
60
+ sig { type_parameters(:T).params(request_name: String, value: T.type_parameter(:T)).returns(T.type_parameter(:T)) }
61
+ def cache_set(request_name, value)
62
+ @cache[request_name] = value
63
+ end
64
+
65
+ sig { params(request_name: String).returns(T.untyped) }
66
+ def cache_get(request_name)
67
+ @cache[request_name]
68
+ end
69
+
59
70
  sig { params(edits: T::Array[EditShape], version: Integer).void }
60
71
  def push_edits(edits, version:)
61
72
  edits.each do |edit|
@@ -13,42 +13,108 @@ module RubyLsp
13
13
  #
14
14
  # ```ruby
15
15
  # target_node = document.locate_node(position)
16
- # listener = Requests::Hover.new
17
- # EventEmitter.new(listener).emit_for_target(target_node)
16
+ # emitter = EventEmitter.new
17
+ # listener = Requests::Hover.new(emitter, @message_queue)
18
+ # emitter.emit_for_target(target_node)
18
19
  # listener.response
19
20
  # ```
20
21
  class EventEmitter < SyntaxTree::Visitor
21
22
  extend T::Sig
22
23
 
23
- sig { params(listeners: Listener[T.untyped]).void }
24
- def initialize(*listeners)
25
- @listeners = listeners
26
-
27
- # Create a map of event name to listeners that have registered it, so that we avoid unnecessary invocations
28
- @event_to_listener_map = T.let(
29
- listeners.each_with_object(Hash.new { |h, k| h[k] = [] }) do |listener, hash|
30
- listener.class.events&.each { |event| hash[event] << listener }
31
- end,
32
- T::Hash[Symbol, T::Array[Listener[T.untyped]]],
33
- )
34
-
24
+ sig { void }
25
+ def initialize
26
+ @listeners = T.let(Hash.new { |h, k| h[k] = [] }, T::Hash[Symbol, T::Array[Listener[T.untyped]]])
35
27
  super()
36
28
  end
37
29
 
30
+ sig { params(listener: Listener[T.untyped], events: Symbol).void }
31
+ def register(listener, *events)
32
+ events.each { |event| T.must(@listeners[event]) << listener }
33
+ end
34
+
38
35
  # Emit events for a specific node. This is similar to the regular `visit` method, but avoids going deeper into the
39
36
  # tree for performance
40
37
  sig { params(node: T.nilable(SyntaxTree::Node)).void }
41
38
  def emit_for_target(node)
42
39
  case node
43
40
  when SyntaxTree::Command
44
- @event_to_listener_map[:on_command]&.each { |listener| T.unsafe(listener).on_command(node) }
41
+ @listeners[:on_command]&.each { |l| T.unsafe(l).on_command(node) }
45
42
  when SyntaxTree::CallNode
46
- @event_to_listener_map[:on_call]&.each { |listener| T.unsafe(listener).on_call(node) }
43
+ @listeners[:on_call]&.each { |l| T.unsafe(l).on_call(node) }
44
+ when SyntaxTree::TStringContent
45
+ @listeners[:on_tstring_content]&.each { |l| T.unsafe(l).on_tstring_content(node) }
47
46
  when SyntaxTree::ConstPathRef
48
- @event_to_listener_map[:on_const_path_ref]&.each { |listener| T.unsafe(listener).on_const_path_ref(node) }
47
+ @listeners[:on_const_path_ref]&.each { |l| T.unsafe(l).on_const_path_ref(node) }
49
48
  when SyntaxTree::Const
50
- @event_to_listener_map[:on_const]&.each { |listener| T.unsafe(listener).on_const(node) }
49
+ @listeners[:on_const]&.each { |l| T.unsafe(l).on_const(node) }
51
50
  end
52
51
  end
52
+
53
+ # Visit dispatchers are below. Notice that for nodes that create a new scope (e.g.: classes, modules, method defs)
54
+ # we need both an `on_*` and `after_*` event. This is because some requests must know when we exit the scope
55
+ sig { override.params(node: SyntaxTree::ClassDeclaration).void }
56
+ def visit_class(node)
57
+ @listeners[:on_class]&.each { |l| T.unsafe(l).on_class(node) }
58
+ super
59
+ @listeners[:after_class]&.each { |l| T.unsafe(l).after_class(node) }
60
+ end
61
+
62
+ sig { override.params(node: SyntaxTree::ModuleDeclaration).void }
63
+ def visit_module(node)
64
+ @listeners[:on_module]&.each { |l| T.unsafe(l).on_module(node) }
65
+ super
66
+ @listeners[:after_module]&.each { |l| T.unsafe(l).after_module(node) }
67
+ end
68
+
69
+ sig { override.params(node: SyntaxTree::Command).void }
70
+ def visit_command(node)
71
+ @listeners[:on_command]&.each { |l| T.unsafe(l).on_command(node) }
72
+ super
73
+ @listeners[:after_command]&.each { |l| T.unsafe(l).after_command(node) }
74
+ end
75
+
76
+ sig { override.params(node: SyntaxTree::CallNode).void }
77
+ def visit_call(node)
78
+ @listeners[:on_call]&.each { |l| T.unsafe(l).on_call(node) }
79
+ super
80
+ @listeners[:after_call]&.each { |l| T.unsafe(l).after_call(node) }
81
+ end
82
+
83
+ sig { override.params(node: SyntaxTree::VCall).void }
84
+ def visit_vcall(node)
85
+ @listeners[:on_vcall]&.each { |l| T.unsafe(l).on_vcall(node) }
86
+ super
87
+ end
88
+
89
+ sig { override.params(node: SyntaxTree::ConstPathField).void }
90
+ def visit_const_path_field(node)
91
+ @listeners[:on_const_path_field]&.each { |l| T.unsafe(l).on_const_path_field(node) }
92
+ super
93
+ end
94
+
95
+ sig { override.params(node: SyntaxTree::TopConstField).void }
96
+ def visit_top_const_field(node)
97
+ @listeners[:on_top_const_field]&.each { |l| T.unsafe(l).on_top_const_field(node) }
98
+ super
99
+ end
100
+
101
+ sig { override.params(node: SyntaxTree::DefNode).void }
102
+ def visit_def(node)
103
+ @listeners[:on_def]&.each { |l| T.unsafe(l).on_def(node) }
104
+ super
105
+ @listeners[:after_def]&.each { |l| T.unsafe(l).after_def(node) }
106
+ end
107
+
108
+ sig { override.params(node: SyntaxTree::VarField).void }
109
+ def visit_var_field(node)
110
+ @listeners[:on_var_field]&.each { |l| T.unsafe(l).on_var_field(node) }
111
+ super
112
+ end
113
+
114
+ sig { override.params(node: SyntaxTree::Comment).void }
115
+ def visit_comment(node)
116
+ @listeners[:on_comment]&.each { |l| T.unsafe(l).on_comment(node) }
117
+ super
118
+ end
53
119
  end
54
120
  end