ruby-lsp 0.1.0 → 0.2.0

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: 2b072ffa3ea73a83aba6d6c32b931c4de30b0de06fd70655d620c3ed95bd8aa4
4
- data.tar.gz: 24568688b20ffed38defcbc0b00708eb22a7dad20a9b5f397348242a64359bfd
3
+ metadata.gz: f81baa8f09011e5130c955c4c5d2a33583379b0b073caee556719b874efc320e
4
+ data.tar.gz: 55a664f923d93bda9ddb06624303f889123496c672c46b31ea71c629cfa96a81
5
5
  SHA512:
6
- metadata.gz: ea1830f0be08cb291e67d80bbe21dbe17f3e4b4d285b1d9112e004ca2feff32eaca85af755bc2635e3341ab4e64491710c3ba13c5bed617e34495534e1d7a5f8
7
- data.tar.gz: f84b1b27c5c17455d1e8423f1e52f98a754ed123e1722bd536ee7bfdc754952e5d6619c0f4402664a33dc278bdc2422a3e7a37c8de5f172e4456e7a6c08b7a67
6
+ metadata.gz: 8a872f34699bfaa4f955c714c4e49eed52e87b009afc0a642d0ad434956f6a14690188ad9c359cc7aadd53e96fbd0d5d320e00aa263265e1ce3d3e369e8bd124
7
+ data.tar.gz: 06444ea3e08b961445db6cb1a9093fc322b40ddf47daa00deb09a77f3b114014be8f525d7d391430b578b7916042bf345759d113c74e41a8ea51d65791abe69c
data/CHANGELOG.md CHANGED
@@ -6,6 +6,18 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.0]
10
+
11
+ - Add semantic token for keyword and keyword rest params (https://github.com/Shopify/ruby-lsp/pull/142)
12
+ - Return error responses on exceptions (https://github.com/Shopify/ruby-lsp/pull/160)
13
+ - Sanitize home directory for telemetry (https://github.com/Shopify/ruby-lsp/pull/171)
14
+ - Avoid adding semantic tokens for special methods (https://github.com/Shopify/ruby-lsp/pull/162)
15
+ - Properly respect excluded files in RuboCop requests (https://github.com/Shopify/ruby-lsp/pull/173)
16
+ - Clear diagnostics when closing files (https://github.com/Shopify/ruby-lsp/pull/174)
17
+ - Avoid pushing ranges for single line partial ranges (https://github.com/Shopify/ruby-lsp/pull/185)
18
+ - Change folding ranges to include closing tokens (https://github.com/Shopify/ruby-lsp/pull/181)
19
+ - Remove RuboCop dependency and fallback to SyntaxTree formatting (https://github.com/Shopify/ruby-lsp/pull/184)
20
+
9
21
  ## [0.1.0]
10
22
 
11
23
  - Implement token modifiers in SemanticTokenEncoder ([#112](https://github.com/Shopify/ruby-lsp/pull/112))
data/Gemfile CHANGED
@@ -5,10 +5,11 @@ source "https://rubygems.org"
5
5
  gemspec
6
6
 
7
7
  gem "debug", "~> 1.5", require: false
8
- gem "minitest", "~> 5.15"
8
+ gem "minitest", "~> 5.16"
9
9
  gem "minitest-reporters", "~> 1.5"
10
10
  gem "rake", "~> 13.0"
11
- gem "rubocop-shopify", "~> 2.7", require: false
11
+ gem "rubocop", ">= 1.0.0"
12
+ gem "rubocop-shopify", "~> 2.8", require: false
12
13
  gem "rubocop-minitest", "~> 0.20.1", require: false
13
14
  gem "rubocop-rake", "~> 0.6.0", require: false
14
15
  gem "rubocop-sorbet", "~> 0.6", require: false
data/Gemfile.lock CHANGED
@@ -1,9 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby-lsp (0.1.0)
4
+ ruby-lsp (0.2.0)
5
5
  language_server-protocol
6
- rubocop (>= 1.0)
7
6
  sorbet-runtime
8
7
  syntax_tree (>= 2.4)
9
8
 
@@ -21,9 +20,10 @@ GEM
21
20
  io-console (0.5.11)
22
21
  irb (1.4.1)
23
22
  reline (>= 0.3.0)
23
+ json (2.6.2)
24
24
  language_server-protocol (3.16.0.3)
25
25
  method_source (1.0.0)
26
- minitest (5.15.0)
26
+ minitest (5.16.2)
27
27
  minitest-reporters (1.5.0)
28
28
  ansi
29
29
  builder
@@ -38,7 +38,7 @@ GEM
38
38
  method_source (~> 1.0)
39
39
  rainbow (3.1.1)
40
40
  rake (13.0.6)
41
- rbi (0.0.14)
41
+ rbi (0.0.15)
42
42
  ast
43
43
  parser (>= 2.6.4.0)
44
44
  sorbet-runtime (>= 0.5.9204)
@@ -47,7 +47,8 @@ GEM
47
47
  reline (0.3.1)
48
48
  io-console (~> 0.5)
49
49
  rexml (3.2.5)
50
- rubocop (1.30.1)
50
+ rubocop (1.31.1)
51
+ json (~> 2.3)
51
52
  parallel (~> 1.10)
52
53
  parser (>= 3.1.0.0)
53
54
  rainbow (>= 2.2.2, < 4.0)
@@ -62,26 +63,26 @@ GEM
62
63
  rubocop (>= 0.90, < 2.0)
63
64
  rubocop-rake (0.6.0)
64
65
  rubocop (~> 1.0)
65
- rubocop-shopify (2.7.0)
66
- rubocop (~> 1.30)
67
- rubocop-sorbet (0.6.8)
66
+ rubocop-shopify (2.8.0)
67
+ rubocop (~> 1.31)
68
+ rubocop-sorbet (0.6.11)
68
69
  rubocop (>= 0.90.0)
69
70
  ruby-progressbar (1.11.0)
70
- sorbet (0.5.10092)
71
- sorbet-static (= 0.5.10092)
72
- sorbet-runtime (0.5.10092)
73
- sorbet-static (0.5.10092-universal-darwin-21)
74
- sorbet-static (0.5.10092-x86_64-linux)
75
- sorbet-static-and-runtime (0.5.10092)
76
- sorbet (= 0.5.10092)
77
- sorbet-runtime (= 0.5.10092)
71
+ sorbet (0.5.10139)
72
+ sorbet-static (= 0.5.10139)
73
+ sorbet-runtime (0.5.10139)
74
+ sorbet-static (0.5.10139-universal-darwin-21)
75
+ sorbet-static (0.5.10139-x86_64-linux)
76
+ sorbet-static-and-runtime (0.5.10139)
77
+ sorbet (= 0.5.10139)
78
+ sorbet-runtime (= 0.5.10139)
78
79
  spoom (1.1.11)
79
80
  sorbet (>= 0.5.9204)
80
81
  sorbet-runtime (>= 0.5.9204)
81
82
  thor (>= 0.19.2)
82
- syntax_tree (2.7.1)
83
+ syntax_tree (2.8.0)
83
84
  prettier_print
84
- tapioca (0.8.1)
85
+ tapioca (0.8.3)
85
86
  bundler (>= 1.17.3)
86
87
  parallel (>= 1.21.0)
87
88
  pry (>= 0.12.2)
@@ -91,7 +92,7 @@ GEM
91
92
  thor (>= 1.2.0)
92
93
  yard-sorbet
93
94
  thor (1.2.1)
94
- unicode-display_width (2.1.0)
95
+ unicode-display_width (2.2.0)
95
96
  unparser (0.6.5)
96
97
  diff-lcs (~> 1.3)
97
98
  parser (>= 3.1.0)
@@ -108,12 +109,13 @@ PLATFORMS
108
109
 
109
110
  DEPENDENCIES
110
111
  debug (~> 1.5)
111
- minitest (~> 5.15)
112
+ minitest (~> 5.16)
112
113
  minitest-reporters (~> 1.5)
113
114
  rake (~> 13.0)
115
+ rubocop (>= 1.0.0)
114
116
  rubocop-minitest (~> 0.20.1)
115
117
  rubocop-rake (~> 0.6.0)
116
- rubocop-shopify (~> 2.7)
118
+ rubocop-shopify (~> 2.8)
117
119
  rubocop-sorbet (~> 0.6)
118
120
  ruby-lsp!
119
121
  sorbet-static-and-runtime
data/README.md CHANGED
@@ -17,7 +17,8 @@ end
17
17
  If using VS Code, install the [Ruby LSP plugin](https://github.com/Shopify/vscode-ruby-lsp) to get the extra features in
18
18
  the editor.
19
19
 
20
- See the [documentation](https://shopify.github.io/ruby-lsp) for supported features.
20
+ See the [documentation](https://shopify.github.io/ruby-lsp) for
21
+ [supported features](https://shopify.github.io/ruby-lsp/RubyLsp/Requests.html).
21
22
 
22
23
  ## Contributing
23
24
 
data/Rakefile CHANGED
@@ -11,7 +11,14 @@ Rake::TestTask.new(:test) do |t|
11
11
  end
12
12
 
13
13
  YARD::Rake::YardocTask.new do |t|
14
- t.options = ["--markup", "markdown", "--output-dir", "docs"]
14
+ t.options = [
15
+ "--markup",
16
+ "markdown",
17
+ "--output-dir",
18
+ "docs",
19
+ "--asset",
20
+ "misc",
21
+ ]
15
22
  end
16
23
 
17
24
  require "rubocop/rake_task"
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
data/bin/console ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ # typed: strict
3
+ # frozen_string_literal: true
4
+
5
+ require "bundler/setup"
6
+ require_relative "../lib/ruby_lsp/internal"
7
+ require "irb"
8
+
9
+ extend T::Sig
10
+
11
+ sig { params(source: String).returns(RubyLsp::Document) }
12
+ def new_doc(source)
13
+ RubyLsp::Document.new(source)
14
+ end
15
+
16
+ @source = T.let(File.read(File.expand_path("../lib/ruby_lsp/handler.rb", __dir__)), String)
17
+ @document = T.let(new_doc(@source), RubyLsp::Document)
18
+
19
+ IRB.start(__FILE__)
data/lib/ruby_lsp/cli.rb CHANGED
@@ -41,6 +41,7 @@ module RubyLsp
41
41
  on("textDocument/didClose") do |request|
42
42
  uri = request.dig(:params, :textDocument, :uri)
43
43
  store.delete(uri)
44
+ clear_diagnostics(uri)
44
45
 
45
46
  RubyLsp::Handler::VOID
46
47
  end
@@ -62,7 +62,16 @@ module RubyLsp
62
62
  error = e
63
63
  end
64
64
 
65
- @writer.write(id: request[:id], result: result) unless result == VOID
65
+ if error
66
+ @writer.write(
67
+ {
68
+ id: request[:id],
69
+ error: { code: Constant::ErrorCodes::INTERNAL_ERROR, message: error.inspect, data: request.to_json },
70
+ }
71
+ )
72
+ elsif result != VOID
73
+ @writer.write(id: request[:id], result: result)
74
+ end
66
75
  end
67
76
  end
68
77
 
@@ -94,7 +103,7 @@ module RubyLsp
94
103
  Interface::SemanticTokensRegistrationOptions.new(
95
104
  document_selector: { scheme: "file", language: "ruby" },
96
105
  legend: Interface::SemanticTokensLegend.new(
97
- token_types: Requests::SemanticHighlighting::TOKEN_TYPES,
106
+ token_types: Requests::SemanticHighlighting::TOKEN_TYPES.keys,
98
107
  token_modifiers: Requests::SemanticHighlighting::TOKEN_MODIFIERS.keys
99
108
  ),
100
109
  range: false,
@@ -187,6 +196,14 @@ module RubyLsp
187
196
  )
188
197
  end
189
198
 
199
+ sig { params(uri: String).void }
200
+ def clear_diagnostics(uri)
201
+ @writer.write(
202
+ method: "textDocument/publishDiagnostics",
203
+ params: Interface::PublishDiagnosticsParams.new(uri: uri, diagnostics: [])
204
+ )
205
+ end
206
+
190
207
  sig do
191
208
  params(uri: String, range: T::Range[Integer]).returns(T::Array[LanguageServer::Protocol::Interface::CodeAction])
192
209
  end
@@ -227,7 +244,7 @@ module RubyLsp
227
244
  params[:errorMessage] = error.message
228
245
  end
229
246
 
230
- params[:uri] = uri if uri
247
+ params[:uri] = uri.sub(%r{.*://#{Dir.home}}, "~") if uri
231
248
  params
232
249
  end
233
250
  end
@@ -3,6 +3,8 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
+ # ![Code actions demo](../../misc/code_actions.gif)
7
+ #
6
8
  # The [code actions](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction)
7
9
  # request informs the editor of RuboCop quick fixes that can be applied. These are accesible by hovering over a
8
10
  # specific diagnostic.
@@ -1,8 +1,12 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "ruby_lsp/requests/support/rubocop_diagnostics_runner"
5
+
4
6
  module RubyLsp
5
7
  module Requests
8
+ # ![Diagnostics demo](../../misc/diagnostics.gif)
9
+ #
6
10
  # The
7
11
  # [diagnostics](https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics)
8
12
  # request informs the editor of RuboCop offenses for a given file.
@@ -14,9 +18,16 @@ module RubyLsp
14
18
  # puts "Hello" # --> diagnostics: incorrect indentantion
15
19
  # end
16
20
  # ```
17
- class Diagnostics < RuboCopRequest
21
+ class Diagnostics < BaseRequest
18
22
  extend T::Sig
19
23
 
24
+ sig { params(uri: String, document: Document).void }
25
+ def initialize(uri, document)
26
+ super(document)
27
+
28
+ @uri = uri
29
+ end
30
+
20
31
  sig do
21
32
  override.returns(
22
33
  T.any(
@@ -27,15 +38,9 @@ module RubyLsp
27
38
  end
28
39
  def run
29
40
  return syntax_error_diagnostics if @document.syntax_errors?
41
+ return [] unless defined?(Support::RuboCopDiagnosticsRunner)
30
42
 
31
- super
32
-
33
- @diagnostics
34
- end
35
-
36
- sig { params(_file: String, offenses: T::Array[RuboCop::Cop::Offense]).void }
37
- def file_finished(_file, offenses)
38
- @diagnostics = offenses.map { |offense| Support::RuboCopDiagnostic.new(offense, @uri) }
43
+ Support::RuboCopDiagnosticsRunner.instance.run(@uri, @document)
39
44
  end
40
45
 
41
46
  private
@@ -3,6 +3,8 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
+ # ![Document highlight demo](../../misc/document_highlight.gif)
7
+ #
6
8
  # The [document highlight](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentHighlight)
7
9
  # informs the editor all relevant elements of the currently pointed item for highlighting. For example, when
8
10
  # the cursor is on the `F` of the constant `FOO`, the editor should identify other occurences of `FOO`
@@ -3,6 +3,8 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
+ # ![Document symbol demo](../../misc/document_symbol.gif)
7
+ #
6
8
  # The [document
7
9
  # symbol](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol) request
8
10
  # informs the editor of all the important symbols, such as classes, variables, and methods, defined in a file. With
@@ -3,10 +3,13 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
+ # ![Folding ranges demo](../../misc/folding_ranges.gif)
7
+ #
6
8
  # The [folding ranges](https://microsoft.github.io/language-server-protocol/specification#textDocument_foldingRange)
7
- # request informs the editor of the ranges where code can be folded.
9
+ # request informs the editor of the ranges where and how code can be folded.
8
10
  #
9
11
  # # Example
12
+ #
10
13
  # ```ruby
11
14
  # def say_hello # <-- folding range start
12
15
  # puts "Hello"
@@ -32,12 +35,13 @@ module RubyLsp
32
35
  SyntaxTree::Unless,
33
36
  SyntaxTree::Until,
34
37
  SyntaxTree::While,
38
+ SyntaxTree::Else,
39
+ SyntaxTree::Ensure,
40
+ SyntaxTree::Begin,
35
41
  ].freeze, T::Array[T.class_of(SyntaxTree::Node)])
36
42
 
37
43
  NODES_WITH_STATEMENTS = T.let([
38
- SyntaxTree::Else,
39
44
  SyntaxTree::Elsif,
40
- SyntaxTree::Ensure,
41
45
  SyntaxTree::In,
42
46
  SyntaxTree::Rescue,
43
47
  SyntaxTree::When,
@@ -45,9 +49,7 @@ module RubyLsp
45
49
 
46
50
  StatementNode = T.type_alias do
47
51
  T.any(
48
- SyntaxTree::Else,
49
52
  SyntaxTree::Elsif,
50
- SyntaxTree::Ensure,
51
53
  SyntaxTree::In,
52
54
  SyntaxTree::Rescue,
53
55
  SyntaxTree::When,
@@ -77,11 +79,10 @@ module RubyLsp
77
79
 
78
80
  case node
79
81
  when *SIMPLE_FOLDABLES
80
- add_node_range(T.must(node))
82
+ location = T.must(node).location
83
+ add_lines_range(location.start_line, location.end_line - 1)
81
84
  when *NODES_WITH_STATEMENTS
82
85
  add_statements_range(T.must(node), T.cast(node, StatementNode).statements)
83
- when SyntaxTree::Begin
84
- add_statements_range(node, node.bodystmt.statements)
85
86
  when SyntaxTree::Call, SyntaxTree::CommandCall
86
87
  add_call_range(node)
87
88
  return
@@ -135,6 +136,11 @@ module RubyLsp
135
136
  kind: @kind
136
137
  )
137
138
  end
139
+
140
+ sig { returns(T::Boolean) }
141
+ def multiline?
142
+ @end_line > @start_line
143
+ end
138
144
  end
139
145
 
140
146
  sig { params(node: T.nilable(SyntaxTree::Node)).returns(T::Boolean) }
@@ -175,7 +181,7 @@ module RubyLsp
175
181
  def emit_partial_range
176
182
  return if @partial_range.nil?
177
183
 
178
- @ranges << @partial_range.to_range
184
+ @ranges << @partial_range.to_range if @partial_range.multiline?
179
185
  @partial_range = nil
180
186
  end
181
187
 
@@ -195,7 +201,7 @@ module RubyLsp
195
201
  end
196
202
  end
197
203
 
198
- add_lines_range(receiver.location.start_line, node.location.end_line)
204
+ add_lines_range(receiver.location.start_line, node.location.end_line - 1)
199
205
 
200
206
  visit(node.arguments)
201
207
  end
@@ -205,9 +211,10 @@ module RubyLsp
205
211
  params_location = node.params.location
206
212
 
207
213
  if params_location.start_line < params_location.end_line
208
- add_lines_range(params_location.end_line, node.location.end_line)
214
+ add_lines_range(params_location.end_line, node.location.end_line - 1)
209
215
  else
210
- add_node_range(node)
216
+ location = node.location
217
+ add_lines_range(location.start_line, location.end_line - 1)
211
218
  end
212
219
 
213
220
  visit(node.bodystmt.statements)
@@ -215,7 +222,7 @@ module RubyLsp
215
222
 
216
223
  sig { params(node: SyntaxTree::Node, statements: SyntaxTree::Statements).void }
217
224
  def add_statements_range(node, statements)
218
- add_lines_range(node.location.start_line, statements.location.end_line) unless statements.empty?
225
+ add_lines_range(node.location.start_line, statements.body.last.location.end_line) unless statements.empty?
219
226
  end
220
227
 
221
228
  sig { params(node: SyntaxTree::StringConcat).void }
@@ -223,13 +230,7 @@ module RubyLsp
223
230
  left = T.let(node.left, SyntaxTree::Node)
224
231
  left = left.left while left.is_a?(SyntaxTree::StringConcat)
225
232
 
226
- add_lines_range(left.location.start_line, node.right.location.end_line)
227
- end
228
-
229
- sig { params(node: SyntaxTree::Node).void }
230
- def add_node_range(node)
231
- location = node.location
232
- add_lines_range(location.start_line, location.end_line)
233
+ add_lines_range(left.location.start_line, node.right.location.end_line - 1)
233
234
  end
234
235
 
235
236
  sig { params(start_line: Integer, end_line: Integer).void }
@@ -1,8 +1,12 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "ruby_lsp/requests/support/rubocop_formatting_runner"
5
+
4
6
  module RubyLsp
5
7
  module Requests
8
+ # ![Formatting symbol demo](../../misc/formatting.gif)
9
+ #
6
10
  # The [formatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting)
7
11
  # request uses RuboCop to fix auto-correctable offenses in the document. This requires enabling format on save and
8
12
  # registering the ruby-lsp as the Ruby formatter.
@@ -14,43 +18,43 @@ module RubyLsp
14
18
  # puts "Hello" # --> formatting: fixes the indentation on save
15
19
  # end
16
20
  # ```
17
- class Formatting < RuboCopRequest
21
+ class Formatting < BaseRequest
18
22
  extend T::Sig
19
23
 
20
- RUBOCOP_FLAGS = T.let((COMMON_RUBOCOP_FLAGS + ["--auto-correct"]).freeze, T::Array[String])
21
-
22
24
  sig { params(uri: String, document: Document).void }
23
25
  def initialize(uri, document)
24
- super
25
- @formatted_text = T.let(nil, T.nilable(String))
26
+ super(document)
27
+
28
+ @uri = uri
26
29
  end
27
30
 
28
31
  sig { override.returns(T.nilable(T.all(T::Array[LanguageServer::Protocol::Interface::TextEdit], Object))) }
29
32
  def run
30
- super
33
+ formatted_text = formatted_file
34
+ return unless formatted_text
31
35
 
32
- @formatted_text = @options[:stdin] # Rubocop applies the corrections on stdin
33
- return unless @formatted_text
36
+ size = @document.source.size
34
37
 
35
38
  [
36
39
  LanguageServer::Protocol::Interface::TextEdit.new(
37
40
  range: LanguageServer::Protocol::Interface::Range.new(
38
41
  start: LanguageServer::Protocol::Interface::Position.new(line: 0, character: 0),
39
- end: LanguageServer::Protocol::Interface::Position.new(
40
- line: text.size,
41
- character: text.size
42
- )
42
+ end: LanguageServer::Protocol::Interface::Position.new(line: size, character: size)
43
43
  ),
44
- new_text: @formatted_text
44
+ new_text: formatted_text
45
45
  ),
46
46
  ]
47
47
  end
48
48
 
49
49
  private
50
50
 
51
- sig { returns(T::Array[String]) }
52
- def rubocop_flags
53
- RUBOCOP_FLAGS
51
+ sig { returns(T.nilable(String)) }
52
+ def formatted_file
53
+ if defined?(Support::RuboCopFormattingRunner)
54
+ Support::RuboCopFormattingRunner.instance.run(@uri, @document)
55
+ else
56
+ SyntaxTree.format(@document.source)
57
+ end
54
58
  end
55
59
  end
56
60
  end
@@ -3,6 +3,8 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
+ # ![Selection ranges demo](../../misc/selection_ranges.gif)
7
+ #
6
8
  # The [selection ranges](https://microsoft.github.io/language-server-protocol/specification#textDocument_selectionRange)
7
9
  # request informs the editor of ranges that the user may want to select based on the location(s)
8
10
  # of their cursor(s).
@@ -3,6 +3,8 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
+ # ![Semantic highlighting demo](../../misc/semantic_highlighting.gif)
7
+ #
6
8
  # The [semantic
7
9
  # highlighting](https://microsoft.github.io/language-server-protocol/specification#textDocument_semanticTokens)
8
10
  # request informs the editor of the correct token types to provide consistent and accurate highlighting for themes.
@@ -19,11 +21,31 @@ module RubyLsp
19
21
  class SemanticHighlighting < BaseRequest
20
22
  extend T::Sig
21
23
 
22
- TOKEN_TYPES = T.let([
23
- :variable,
24
- :method,
25
- :namespace,
26
- ].freeze, T::Array[Symbol])
24
+ TOKEN_TYPES = T.let({
25
+ namespace: 0,
26
+ type: 1,
27
+ class: 2,
28
+ enum: 3,
29
+ interface: 4,
30
+ struct: 5,
31
+ typeParameter: 6,
32
+ parameter: 7,
33
+ variable: 8,
34
+ property: 9,
35
+ enumMember: 10,
36
+ event: 11,
37
+ function: 12,
38
+ method: 13,
39
+ macro: 14,
40
+ keyword: 15,
41
+ modifier: 16,
42
+ comment: 17,
43
+ string: 18,
44
+ number: 19,
45
+ regexp: 20,
46
+ operator: 21,
47
+ decorator: 22,
48
+ }.freeze, T::Hash[Symbol, Integer])
27
49
 
28
50
  TOKEN_MODIFIERS = T.let({
29
51
  declaration: 0,
@@ -38,6 +60,11 @@ module RubyLsp
38
60
  default_library: 9,
39
61
  }.freeze, T::Hash[Symbol, Integer])
40
62
 
63
+ SPECIAL_RUBY_METHODS = T.let((Module.instance_methods(false) +
64
+ Kernel.methods(false) + Bundler::Dsl.instance_methods(false) +
65
+ Module.private_instance_methods(false))
66
+ .map(&:to_s), T::Array[String])
67
+
41
68
  class SemanticToken < T::Struct
42
69
  const :location, SyntaxTree::Location
43
70
  const :length, Integer
@@ -52,6 +79,7 @@ module RubyLsp
52
79
  @encoder = encoder
53
80
  @tokens = T.let([], T::Array[SemanticToken])
54
81
  @tree = T.let(document.tree, SyntaxTree::Node)
82
+ @special_methods = T.let(nil, T.nilable(T::Array[String]))
55
83
  end
56
84
 
57
85
  sig do
@@ -83,7 +111,7 @@ module RubyLsp
83
111
 
84
112
  sig { params(node: SyntaxTree::Command).void }
85
113
  def visit_command(node)
86
- add_token(node.message.location, :method)
114
+ add_token(node.message.location, :method) unless special_method?(node.message.value)
87
115
  visit(node.arguments)
88
116
  end
89
117
 
@@ -125,7 +153,7 @@ module RubyLsp
125
153
 
126
154
  sig { params(node: SyntaxTree::FCall).void }
127
155
  def visit_fcall(node)
128
- add_token(node.value.location, :method)
156
+ add_token(node.value.location, :method) unless special_method?(node.value.value)
129
157
  visit(node.arguments)
130
158
  end
131
159
 
@@ -144,6 +172,16 @@ module RubyLsp
144
172
  end
145
173
  end
146
174
 
175
+ sig { params(node: SyntaxTree::Params).void }
176
+ def visit_params(node)
177
+ node.keywords.each do |keyword,|
178
+ location = keyword.location
179
+ add_token(location_without_colon(location), :variable)
180
+ end
181
+
182
+ add_token(node.keyword_rest.name.location, :variable) if node.keyword_rest
183
+ end
184
+
147
185
  sig { params(node: SyntaxTree::VarField).void }
148
186
  def visit_var_field(node)
149
187
  case node.value
@@ -166,7 +204,7 @@ module RubyLsp
166
204
 
167
205
  sig { params(node: SyntaxTree::VCall).void }
168
206
  def visit_vcall(node)
169
- add_token(node.value.location, :method)
207
+ add_token(node.value.location, :method) unless special_method?(node.value.value)
170
208
  end
171
209
 
172
210
  sig { params(location: SyntaxTree::Location, type: Symbol, modifiers: T::Array[Symbol]).void }
@@ -177,11 +215,37 @@ module RubyLsp
177
215
  SemanticToken.new(
178
216
  location: location,
179
217
  length: length,
180
- type: T.must(TOKEN_TYPES.index(type)),
218
+ type: T.must(TOKEN_TYPES[type]),
181
219
  modifier: modifiers_indices
182
220
  )
183
221
  )
184
222
  end
223
+
224
+ private
225
+
226
+ # Exclude the ":" symbol at the end of a location
227
+ # We use it on keyword parameters to be consistent
228
+ # with the rest of the parameters
229
+ sig { params(location: T.untyped).returns(SyntaxTree::Location) }
230
+ def location_without_colon(location)
231
+ SyntaxTree::Location.new(
232
+ start_line: location.start_line,
233
+ start_column: location.start_column,
234
+ start_char: location.start_char,
235
+ end_char: location.end_char - 1,
236
+ end_column: location.end_column - 1,
237
+ end_line: location.end_line,
238
+ )
239
+ end
240
+
241
+ # Textmate provides highlighting for a subset
242
+ # of these special Ruby-specific methods.
243
+ # We want to utilize that highlighting, so we
244
+ # avoid making a semantic token for it.
245
+ sig { params(method_name: String).returns(T::Boolean) }
246
+ def special_method?(method_name)
247
+ SPECIAL_RUBY_METHODS.include?(method_name)
248
+ end
185
249
  end
186
250
  end
187
251
  end
@@ -0,0 +1,61 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "rubocop"
6
+ rescue LoadError
7
+ return
8
+ end
9
+
10
+ require "cgi"
11
+ require "singleton"
12
+
13
+ module RubyLsp
14
+ module Requests
15
+ module Support
16
+ # :nodoc:
17
+ class RuboCopDiagnosticsRunner < RuboCop::Runner
18
+ extend T::Sig
19
+ include Singleton
20
+
21
+ sig { void }
22
+ def initialize
23
+ @options = T.let({}, T::Hash[Symbol, T.untyped])
24
+ @uri = T.let(nil, T.nilable(String))
25
+ @diagnostics = T.let([], T::Array[Support::RuboCopDiagnostic])
26
+
27
+ super(
28
+ ::RuboCop::Options.new.parse([
29
+ "--stderr", # Print any output to stderr so that our stdout does not get polluted
30
+ "--force-exclusion",
31
+ "--format",
32
+ "RuboCop::Formatter::BaseFormatter", # Suppress any output by using the base formatter
33
+ ]).first,
34
+ ::RuboCop::ConfigStore.new
35
+ )
36
+ end
37
+
38
+ sig { params(uri: String, document: Document).returns(T::Array[Support::RuboCopDiagnostic]) }
39
+ def run(uri, document)
40
+ @diagnostics.clear
41
+ @uri = uri
42
+
43
+ file = CGI.unescape(URI.parse(uri).path)
44
+ # We communicate with Rubocop via stdin
45
+ @options[:stdin] = document.source
46
+
47
+ # Invoke RuboCop with just this file in `paths`
48
+ super([file])
49
+ @diagnostics
50
+ end
51
+
52
+ private
53
+
54
+ sig { params(_file: String, offenses: T::Array[RuboCop::Cop::Offense]).void }
55
+ def file_finished(_file, offenses)
56
+ @diagnostics = offenses.map { |offense| Support::RuboCopDiagnostic.new(offense, T.must(@uri)) }
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,50 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "rubocop"
6
+ rescue LoadError
7
+ return
8
+ end
9
+
10
+ require "cgi"
11
+ require "singleton"
12
+
13
+ module RubyLsp
14
+ module Requests
15
+ module Support
16
+ # :nodoc:
17
+ class RuboCopFormattingRunner < RuboCop::Runner
18
+ extend T::Sig
19
+ include Singleton
20
+
21
+ sig { void }
22
+ def initialize
23
+ @options = T.let({}, T::Hash[Symbol, T.untyped])
24
+
25
+ super(
26
+ ::RuboCop::Options.new.parse([
27
+ "--stderr", # Print any output to stderr so that our stdout does not get polluted
28
+ "--force-exclusion",
29
+ "--format",
30
+ "RuboCop::Formatter::BaseFormatter", # Suppress any output by using the base formatter
31
+ "-a", # --auto-correct
32
+ ]).first,
33
+ ::RuboCop::ConfigStore.new
34
+ )
35
+ end
36
+
37
+ sig { params(uri: String, document: Document).returns(T.nilable(String)) }
38
+ def run(uri, document)
39
+ file = CGI.unescape(URI.parse(uri).path)
40
+ # We communicate with Rubocop via stdin
41
+ @options[:stdin] = document.source
42
+
43
+ # Invoke RuboCop with just this file in `paths`
44
+ super([file])
45
+ @options[:stdin]
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -2,18 +2,28 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyLsp
5
+ # Supported features
6
+ #
7
+ # - {RubyLsp::Requests::DocumentSymbol}
8
+ # - {RubyLsp::Requests::FoldingRanges}
9
+ # - {RubyLsp::Requests::SelectionRanges}
10
+ # - {RubyLsp::Requests::SemanticHighlighting}
11
+ # - {RubyLsp::Requests::Formatting}
12
+ # - {RubyLsp::Requests::Diagnostics}
13
+ # - {RubyLsp::Requests::CodeActions}
14
+ # - {RubyLsp::Requests::DocumentHighlight}
5
15
  module Requests
6
16
  autoload :BaseRequest, "ruby_lsp/requests/base_request"
7
17
  autoload :DocumentSymbol, "ruby_lsp/requests/document_symbol"
8
18
  autoload :FoldingRanges, "ruby_lsp/requests/folding_ranges"
9
19
  autoload :SelectionRanges, "ruby_lsp/requests/selection_ranges"
10
20
  autoload :SemanticHighlighting, "ruby_lsp/requests/semantic_highlighting"
11
- autoload :RuboCopRequest, "ruby_lsp/requests/rubocop_request"
12
21
  autoload :Formatting, "ruby_lsp/requests/formatting"
13
22
  autoload :Diagnostics, "ruby_lsp/requests/diagnostics"
14
23
  autoload :CodeActions, "ruby_lsp/requests/code_actions"
15
24
  autoload :DocumentHighlight, "ruby_lsp/requests/document_highlight"
16
25
 
26
+ # :nodoc:
17
27
  module Support
18
28
  autoload :RuboCopDiagnostic, "ruby_lsp/requests/support/rubocop_diagnostic"
19
29
  autoload :SelectionRange, "ruby_lsp/requests/support/selection_range"
@@ -8,9 +8,13 @@ task :check_docs do
8
8
  require "syntax_tree"
9
9
  require "logger"
10
10
  require "ruby_lsp/requests/base_request"
11
- require "ruby_lsp/requests/rubocop_request"
11
+ require "ruby_lsp/requests/support/rubocop_diagnostics_runner"
12
+ require "ruby_lsp/requests/support/rubocop_formatting_runner"
12
13
 
13
- Dir["#{Dir.pwd}/lib/ruby_lsp/requests/*.rb"].each do |file|
14
+ request_doc_files = Dir["#{Dir.pwd}/lib/ruby_lsp/requests/*.rb"]
15
+ request_doc_files << "#{Dir.pwd}/lib/ruby_lsp/requests.rb"
16
+
17
+ request_doc_files.each do |file|
14
18
  require(file)
15
19
  YARD.parse(file, [], Logger::Severity::FATAL)
16
20
  end
@@ -19,6 +23,8 @@ task :check_docs do
19
23
  error_messages = RubyLsp::Requests
20
24
  .constants # rubocop:disable Sorbet/ConstantsFromStrings
21
25
  .each_with_object(Hash.new { |h, k| h[k] = [] }) do |request, errors|
26
+ next if request == :Support
27
+
22
28
  full_name = "RubyLsp::Requests::#{request}"
23
29
  docs = YARD::Registry.at(full_name).docstring
24
30
  next if /:nodoc:/.match?(docs)
@@ -32,15 +38,33 @@ task :check_docs do
32
38
  For example, if your request handles text document hover, you should add a link to
33
39
  https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover.
34
40
  MESSAGE
41
+ elsif !%r{!\[.* demo\]\(\.\./\.\./misc/.*\.gif\)}.match?(docs)
42
+ errors[full_name] << <<~MESSAGE
43
+ Documentation for request handler class must contain a demonstration GIF, in the following format:
44
+
45
+ ![Request name demo](../../misc/request_name.gif)
46
+
47
+ See the misc/ folder for examples.
48
+ MESSAGE
35
49
  elsif !/# Example/.match?(docs)
36
50
  errors[full_name] << <<~MESSAGE
37
51
  Documentation for request handler class must contain an example.
38
52
 
39
- = Example
40
- def my_method # <-- something happens here
41
- end
53
+ # # Example
54
+ #
55
+ # ```ruby
56
+ # def my_method # <-- something happens here
57
+ # end
58
+ # ```
42
59
  MESSAGE
43
60
  end
61
+
62
+ supported_features = YARD::Registry.at("RubyLsp::Requests").docstring
63
+ next if /- {#{full_name}}/.match?(supported_features)
64
+
65
+ errors[full_name] << <<~MESSAGE
66
+ Documentation for request handler class must be listed in the RubyLsp::Requests module documentation.
67
+ MESSAGE
44
68
  end
45
69
 
46
70
  formatted_errors = error_messages.map { |name, errors| "#{name}: #{errors.join(", ")}" }
data/ruby-lsp.gemspec CHANGED
@@ -7,20 +7,21 @@ Gem::Specification.new do |s|
7
7
  s.email = ["ruby@shopify.com"]
8
8
  s.metadata["allowed_push_host"] = "https://rubygems.org"
9
9
 
10
- s.summary = "A simple language server for ruby"
11
- s.description = "A simple language server for ruby"
10
+ s.summary = "An opinionated language server for Ruby"
11
+ s.description = "An opinionated language server for Ruby"
12
12
  s.homepage = "https://github.com/Shopify/ruby-lsp"
13
13
  s.license = "MIT"
14
14
 
15
15
  s.files = Dir.chdir(File.expand_path(__dir__)) do
16
- %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
16
+ %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features|misc)/}) }
17
17
  end
18
18
  s.bindir = "exe"
19
19
  s.executables = s.files.grep(/\Aexe/) { |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
21
 
22
22
  s.add_dependency("language_server-protocol")
23
- s.add_dependency("rubocop", ">= 1.0")
24
23
  s.add_dependency("sorbet-runtime")
25
24
  s.add_dependency("syntax_tree", ">= 2.4")
25
+
26
+ s.required_ruby_version = ">= 2.7.3"
26
27
  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.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-06-15 00:00:00.000000000 Z
11
+ date: 2022-07-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -24,20 +24,6 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: rubocop
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '1.0'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '1.0'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: sorbet-runtime
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -66,7 +52,7 @@ dependencies:
66
52
  - - ">="
67
53
  - !ruby/object:Gem::Version
68
54
  version: '2.4'
69
- description: A simple language server for ruby
55
+ description: An opinionated language server for Ruby
70
56
  email:
71
57
  - ruby@shopify.com
72
58
  executables:
@@ -92,6 +78,7 @@ files:
92
78
  - README.md
93
79
  - Rakefile
94
80
  - VERSION
81
+ - bin/console
95
82
  - bin/rubocop
96
83
  - bin/tapioca
97
84
  - bin/test
@@ -110,10 +97,11 @@ files:
110
97
  - lib/ruby_lsp/requests/document_symbol.rb
111
98
  - lib/ruby_lsp/requests/folding_ranges.rb
112
99
  - lib/ruby_lsp/requests/formatting.rb
113
- - lib/ruby_lsp/requests/rubocop_request.rb
114
100
  - lib/ruby_lsp/requests/selection_ranges.rb
115
101
  - lib/ruby_lsp/requests/semantic_highlighting.rb
116
102
  - lib/ruby_lsp/requests/support/rubocop_diagnostic.rb
103
+ - lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb
104
+ - lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb
117
105
  - lib/ruby_lsp/requests/support/selection_range.rb
118
106
  - lib/ruby_lsp/requests/support/semantic_token_encoder.rb
119
107
  - lib/ruby_lsp/requests/support/syntax_error_diagnostic.rb
@@ -179,7 +167,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
179
167
  requirements:
180
168
  - - ">="
181
169
  - !ruby/object:Gem::Version
182
- version: '0'
170
+ version: 2.7.3
183
171
  required_rubygems_version: !ruby/object:Gem::Requirement
184
172
  requirements:
185
173
  - - ">="
@@ -189,5 +177,5 @@ requirements: []
189
177
  rubygems_version: 3.3.3
190
178
  signing_key:
191
179
  specification_version: 4
192
- summary: A simple language server for ruby
180
+ summary: An opinionated language server for Ruby
193
181
  test_files: []
@@ -1,60 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require "rubocop"
5
- require "cgi"
6
-
7
- module RubyLsp
8
- module Requests
9
- # :nodoc:
10
- class RuboCopRequest < RuboCop::Runner
11
- extend T::Sig
12
- extend T::Helpers
13
-
14
- abstract!
15
-
16
- COMMON_RUBOCOP_FLAGS = T.let([
17
- "--stderr", # Print any output to stderr so that our stdout does not get polluted
18
- "--format",
19
- "RuboCop::Formatter::BaseFormatter", # Suppress any output by using the base formatter
20
- ].freeze, T::Array[String])
21
-
22
- sig { returns(String) }
23
- attr_reader :file
24
-
25
- sig { returns(String) }
26
- attr_reader :text
27
-
28
- sig { params(uri: String, document: Document).void }
29
- def initialize(uri, document)
30
- @file = T.let(CGI.unescape(URI.parse(uri).path), String)
31
- @document = document
32
- @text = T.let(document.source, String)
33
- @uri = uri
34
- @options = T.let({}, T::Hash[Symbol, T.untyped])
35
- @diagnostics = T.let([], T::Array[Support::RuboCopDiagnostic])
36
-
37
- super(
38
- ::RuboCop::Options.new.parse(rubocop_flags).first,
39
- ::RuboCop::ConfigStore.new
40
- )
41
- end
42
-
43
- sig { overridable.returns(Object) }
44
- def run
45
- # We communicate with Rubocop via stdin
46
- @options[:stdin] = text
47
-
48
- # Invoke the actual run method with just this file in `paths`
49
- super([file])
50
- end
51
-
52
- private
53
-
54
- sig { returns(T::Array[String]) }
55
- def rubocop_flags
56
- COMMON_RUBOCOP_FLAGS
57
- end
58
- end
59
- end
60
- end