ruby-lsp 0.1.0 → 0.2.0

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