ruby-lsp 0.0.2 → 0.1.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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +9 -1
  3. data/.github/workflows/publish_docs.yml +32 -0
  4. data/.rubocop.yml +25 -0
  5. data/CHANGELOG.md +23 -0
  6. data/Gemfile +8 -4
  7. data/Gemfile.lock +64 -13
  8. data/README.md +58 -1
  9. data/Rakefile +5 -0
  10. data/VERSION +1 -1
  11. data/bin/tapioca +29 -0
  12. data/dev.yml +3 -0
  13. data/exe/ruby-lsp +19 -3
  14. data/lib/ruby-lsp.rb +2 -0
  15. data/lib/ruby_lsp/cli.rb +23 -7
  16. data/lib/ruby_lsp/document.rb +98 -6
  17. data/lib/ruby_lsp/handler.rb +119 -18
  18. data/lib/ruby_lsp/internal.rb +7 -0
  19. data/lib/ruby_lsp/requests/base_request.rb +19 -5
  20. data/lib/ruby_lsp/requests/code_actions.rb +30 -9
  21. data/lib/ruby_lsp/requests/diagnostics.rb +29 -77
  22. data/lib/ruby_lsp/requests/document_highlight.rb +111 -0
  23. data/lib/ruby_lsp/requests/document_symbol.rb +75 -16
  24. data/lib/ruby_lsp/requests/folding_ranges.rb +63 -19
  25. data/lib/ruby_lsp/requests/formatting.rb +19 -2
  26. data/lib/ruby_lsp/requests/rubocop_request.rb +21 -8
  27. data/lib/ruby_lsp/requests/selection_ranges.rb +114 -0
  28. data/lib/ruby_lsp/requests/semantic_highlighting.rb +132 -61
  29. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +100 -0
  30. data/lib/ruby_lsp/requests/support/selection_range.rb +20 -0
  31. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +70 -0
  32. data/lib/ruby_lsp/requests/support/syntax_error_diagnostic.rb +32 -0
  33. data/lib/ruby_lsp/requests.rb +10 -0
  34. data/lib/ruby_lsp/store.rb +23 -2
  35. data/rakelib/check_docs.rake +57 -0
  36. data/ruby-lsp.gemspec +2 -1
  37. data/sorbet/config +4 -0
  38. data/sorbet/rbi/.rubocop.yml +8 -0
  39. data/sorbet/rbi/gems/ansi@1.5.0.rbi +338 -0
  40. data/sorbet/rbi/gems/ast@2.4.2.rbi +522 -0
  41. data/sorbet/rbi/gems/builder@3.2.4.rbi +418 -0
  42. data/sorbet/rbi/gems/coderay@1.1.3.rbi +8 -0
  43. data/sorbet/rbi/gems/debug@1.5.0.rbi +1273 -0
  44. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +867 -0
  45. data/sorbet/rbi/gems/io-console@0.5.11.rbi +8 -0
  46. data/sorbet/rbi/gems/irb@1.4.1.rbi +376 -0
  47. data/sorbet/rbi/gems/language_server-protocol@3.16.0.3.rbi +7325 -0
  48. data/sorbet/rbi/gems/method_source@1.0.0.rbi +8 -0
  49. data/sorbet/rbi/gems/minitest-reporters@1.5.0.rbi +612 -0
  50. data/sorbet/rbi/gems/minitest@5.15.0.rbi +994 -0
  51. data/sorbet/rbi/gems/parallel@1.22.1.rbi +163 -0
  52. data/sorbet/rbi/gems/parser@3.1.2.0.rbi +3968 -0
  53. data/sorbet/rbi/gems/prettier_print@0.1.0.rbi +734 -0
  54. data/sorbet/rbi/gems/pry@0.14.1.rbi +8 -0
  55. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +227 -0
  56. data/sorbet/rbi/gems/rake@13.0.6.rbi +1853 -0
  57. data/sorbet/rbi/gems/rbi@0.0.14.rbi +2337 -0
  58. data/sorbet/rbi/gems/regexp_parser@2.5.0.rbi +1854 -0
  59. data/sorbet/rbi/gems/reline@0.3.1.rbi +1274 -0
  60. data/sorbet/rbi/gems/rexml@3.2.5.rbi +3852 -0
  61. data/sorbet/rbi/gems/rubocop-ast@1.18.0.rbi +4180 -0
  62. data/sorbet/rbi/gems/rubocop-minitest@0.20.0.rbi +1369 -0
  63. data/sorbet/rbi/gems/rubocop-rake@0.6.0.rbi +246 -0
  64. data/sorbet/rbi/gems/rubocop-shopify@2.6.0.rbi +8 -0
  65. data/sorbet/rbi/gems/rubocop-sorbet@0.6.8.rbi +652 -0
  66. data/sorbet/rbi/gems/rubocop@1.30.0.rbi +36729 -0
  67. data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +732 -0
  68. data/sorbet/rbi/gems/spoom@1.1.11.rbi +1600 -0
  69. data/sorbet/rbi/gems/syntax_tree@2.7.1.rbi +6777 -0
  70. data/sorbet/rbi/gems/tapioca@0.8.1.rbi +1972 -0
  71. data/sorbet/rbi/gems/thor@1.2.1.rbi +2921 -0
  72. data/sorbet/rbi/gems/unicode-display_width@2.1.0.rbi +27 -0
  73. data/sorbet/rbi/gems/unparser@0.6.5.rbi +2789 -0
  74. data/sorbet/rbi/gems/webrick@1.7.0.rbi +1779 -0
  75. data/sorbet/rbi/gems/yard-sorbet@0.6.1.rbi +289 -0
  76. data/sorbet/rbi/gems/yard@0.9.27.rbi +13048 -0
  77. data/sorbet/rbi/shims/fiddle.rbi +4 -0
  78. data/sorbet/rbi/shims/hash.rbi +6 -0
  79. data/sorbet/rbi/shims/rdoc.rbi +4 -0
  80. data/sorbet/tapioca/config.yml +13 -0
  81. data/sorbet/tapioca/require.rb +7 -0
  82. metadata +74 -6
  83. data/shipit.production.yml +0 -1
@@ -1,116 +1,187 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module RubyLsp
4
5
  module Requests
6
+ # The [semantic
7
+ # highlighting](https://microsoft.github.io/language-server-protocol/specification#textDocument_semanticTokens)
8
+ # request informs the editor of the correct token types to provide consistent and accurate highlighting for themes.
9
+ #
10
+ # # Example
11
+ #
12
+ # ```ruby
13
+ # def foo
14
+ # var = 1 # --> semantic highlighting: local variable
15
+ # some_invocation # --> semantic highlighting: method invocation
16
+ # var # --> semantic highlighting: local variable
17
+ # end
18
+ # ```
5
19
  class SemanticHighlighting < BaseRequest
6
- TOKEN_TYPES = [
20
+ extend T::Sig
21
+
22
+ TOKEN_TYPES = T.let([
7
23
  :variable,
8
24
  :method,
9
- ].freeze
10
- TOKEN_MODIFIERS = [].freeze
11
-
12
- def initialize(document)
13
- super
14
-
15
- @tokens = []
16
- @tree = document.tree
17
- @current_row = 0
18
- @current_column = 0
25
+ :namespace,
26
+ ].freeze, T::Array[Symbol])
27
+
28
+ TOKEN_MODIFIERS = T.let({
29
+ declaration: 0,
30
+ definition: 1,
31
+ readonly: 2,
32
+ static: 3,
33
+ deprecated: 4,
34
+ abstract: 5,
35
+ async: 6,
36
+ modification: 7,
37
+ documentation: 8,
38
+ default_library: 9,
39
+ }.freeze, T::Hash[Symbol, Integer])
40
+
41
+ class SemanticToken < T::Struct
42
+ const :location, SyntaxTree::Location
43
+ const :length, Integer
44
+ const :type, Integer
45
+ const :modifier, T::Array[Integer]
19
46
  end
20
47
 
21
- def run
22
- visit(@tree)
23
- LanguageServer::Protocol::Interface::SemanticTokens.new(data: @tokens)
24
- end
48
+ sig { params(document: Document, encoder: T.nilable(Support::SemanticTokenEncoder)).void }
49
+ def initialize(document, encoder: nil)
50
+ super(document)
25
51
 
26
- def visit_m_assign(node)
27
- node.target.parts.each do |var_ref|
28
- add_token(var_ref.value.location, :variable)
29
- end
52
+ @encoder = encoder
53
+ @tokens = T.let([], T::Array[SemanticToken])
54
+ @tree = T.let(document.tree, SyntaxTree::Node)
30
55
  end
31
56
 
32
- def visit_var_field(node)
33
- case node.value
34
- when SyntaxTree::Ident
35
- add_token(node.value.location, :variable)
36
- end
57
+ sig do
58
+ override.returns(
59
+ T.any(
60
+ LanguageServer::Protocol::Interface::SemanticTokens,
61
+ T.all(T::Array[SemanticToken], Object),
62
+ )
63
+ )
37
64
  end
65
+ def run
66
+ visit(@tree)
67
+ return @tokens unless @encoder
38
68
 
39
- def visit_var_ref(node)
40
- case node.value
41
- when SyntaxTree::Ident
42
- add_token(node.value.location, :variable)
43
- end
69
+ @encoder.encode(@tokens)
44
70
  end
45
71
 
72
+ sig { params(node: SyntaxTree::ARefField).void }
46
73
  def visit_a_ref_field(node)
47
74
  add_token(node.collection.value.location, :variable)
48
75
  end
49
76
 
77
+ sig { params(node: SyntaxTree::Call).void }
50
78
  def visit_call(node)
51
79
  visit(node.receiver)
52
80
  add_token(node.message.location, :method)
53
81
  visit(node.arguments)
54
82
  end
55
83
 
84
+ sig { params(node: SyntaxTree::Command).void }
56
85
  def visit_command(node)
57
86
  add_token(node.message.location, :method)
58
87
  visit(node.arguments)
59
88
  end
60
89
 
90
+ sig { params(node: SyntaxTree::CommandCall).void }
61
91
  def visit_command_call(node)
62
92
  visit(node.receiver)
63
93
  add_token(node.message.location, :method)
64
94
  visit(node.arguments)
65
95
  end
66
96
 
67
- def visit_fcall(node)
68
- add_token(node.value.location, :method)
69
- visit(node.arguments)
97
+ sig { params(node: SyntaxTree::Const).void }
98
+ def visit_const(node)
99
+ add_token(node.location, :namespace)
70
100
  end
71
101
 
72
- def visit_vcall(node)
73
- add_token(node.value.location, :method)
102
+ sig { params(node: SyntaxTree::Def).void }
103
+ def visit_def(node)
104
+ add_token(node.name.location, :method, [:declaration])
105
+ visit(node.params)
106
+ visit(node.bodystmt)
74
107
  end
75
108
 
76
- def add_token(location, classification)
77
- length = location.end_char - location.start_char
78
-
79
- compute_delta(location) do |delta_line, delta_column|
80
- @tokens.push(delta_line, delta_column, length, TOKEN_TYPES.index(classification), 0)
81
- end
109
+ sig { params(node: SyntaxTree::DefEndless).void }
110
+ def visit_def_endless(node)
111
+ add_token(node.name.location, :method, [:declaration])
112
+ visit(node.paren)
113
+ visit(node.operator)
114
+ visit(node.statement)
82
115
  end
83
116
 
84
- # The delta array is computed according to the LSP specification:
85
- # > The protocol for the token format relative uses relative
86
- # > positions, because most tokens remain stable relative to
87
- # > each other when edits are made in a file. This simplifies
88
- # > the computation of a delta if a server supports it. So each
89
- # > token is represented using 5 integers.
117
+ sig { params(node: SyntaxTree::Defs).void }
118
+ def visit_defs(node)
119
+ visit(node.target)
120
+ visit(node.operator)
121
+ add_token(node.name.location, :method, [:declaration])
122
+ visit(node.params)
123
+ visit(node.bodystmt)
124
+ end
90
125
 
91
- # For more information on how each number is calculated, read:
92
- # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens
93
- def compute_delta(location)
94
- row = location.start_line - 1
95
- column = location.start_column
126
+ sig { params(node: SyntaxTree::FCall).void }
127
+ def visit_fcall(node)
128
+ add_token(node.value.location, :method)
129
+ visit(node.arguments)
130
+ end
96
131
 
97
- if row < @current_row
98
- raise InvalidTokenRowError, "Invalid token row detected: " \
99
- "Ensure tokens are added in the expected order."
132
+ sig { params(node: SyntaxTree::Kw).void }
133
+ def visit_kw(node)
134
+ case node.value
135
+ when "self"
136
+ add_token(node.location, :variable, [:default_library])
100
137
  end
138
+ end
101
139
 
102
- delta_line = row - @current_row
140
+ sig { params(node: SyntaxTree::MAssign).void }
141
+ def visit_m_assign(node)
142
+ node.target.parts.each do |var_ref|
143
+ add_token(var_ref.value.location, :variable)
144
+ end
145
+ end
103
146
 
104
- delta_column = column
105
- delta_column -= @current_column if delta_line == 0
147
+ sig { params(node: SyntaxTree::VarField).void }
148
+ def visit_var_field(node)
149
+ case node.value
150
+ when SyntaxTree::Ident
151
+ add_token(node.value.location, :variable)
152
+ else
153
+ visit(node.value)
154
+ end
155
+ end
106
156
 
107
- yield delta_line, delta_column
157
+ sig { params(node: SyntaxTree::VarRef).void }
158
+ def visit_var_ref(node)
159
+ case node.value
160
+ when SyntaxTree::Ident
161
+ add_token(node.value.location, :variable)
162
+ else
163
+ visit(node.value)
164
+ end
165
+ end
108
166
 
109
- @current_row = row
110
- @current_column = column
167
+ sig { params(node: SyntaxTree::VCall).void }
168
+ def visit_vcall(node)
169
+ add_token(node.value.location, :method)
111
170
  end
112
171
 
113
- class InvalidTokenRowError < StandardError; end
172
+ sig { params(location: SyntaxTree::Location, type: Symbol, modifiers: T::Array[Symbol]).void }
173
+ def add_token(location, type, modifiers = [])
174
+ length = location.end_char - location.start_char
175
+ modifiers_indices = modifiers.filter_map { |modifier| TOKEN_MODIFIERS[modifier] }
176
+ @tokens.push(
177
+ SemanticToken.new(
178
+ location: location,
179
+ length: length,
180
+ type: T.must(TOKEN_TYPES.index(type)),
181
+ modifier: modifiers_indices
182
+ )
183
+ )
184
+ end
114
185
  end
115
186
  end
116
187
  end
@@ -0,0 +1,100 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ module Support
7
+ class RuboCopDiagnostic
8
+ extend T::Sig
9
+
10
+ RUBOCOP_TO_LSP_SEVERITY = T.let({
11
+ convention: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
12
+ info: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
13
+ refactor: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
14
+ warning: LanguageServer::Protocol::Constant::DiagnosticSeverity::WARNING,
15
+ error: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
16
+ fatal: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
17
+ }.freeze, T::Hash[Symbol, Integer])
18
+
19
+ sig { returns(T::Array[LanguageServer::Protocol::Interface::TextEdit]) }
20
+ attr_reader :replacements
21
+
22
+ sig { params(offense: RuboCop::Cop::Offense, uri: String).void }
23
+ def initialize(offense, uri)
24
+ @offense = offense
25
+ @uri = uri
26
+ @replacements = T.let(
27
+ offense.correctable? ? offense_replacements : [],
28
+ T::Array[LanguageServer::Protocol::Interface::TextEdit]
29
+ )
30
+ end
31
+
32
+ sig { returns(T::Boolean) }
33
+ def correctable?
34
+ @offense.correctable?
35
+ end
36
+
37
+ sig { params(range: T::Range[Integer]).returns(T::Boolean) }
38
+ def in_range?(range)
39
+ range.cover?(@offense.line - 1)
40
+ end
41
+
42
+ sig { returns(LanguageServer::Protocol::Interface::CodeAction) }
43
+ def to_lsp_code_action
44
+ LanguageServer::Protocol::Interface::CodeAction.new(
45
+ title: "Autocorrect #{@offense.cop_name}",
46
+ kind: LanguageServer::Protocol::Constant::CodeActionKind::QUICK_FIX,
47
+ edit: LanguageServer::Protocol::Interface::WorkspaceEdit.new(
48
+ document_changes: [
49
+ LanguageServer::Protocol::Interface::TextDocumentEdit.new(
50
+ text_document: LanguageServer::Protocol::Interface::OptionalVersionedTextDocumentIdentifier.new(
51
+ uri: @uri,
52
+ version: nil
53
+ ),
54
+ edits: @replacements
55
+ ),
56
+ ]
57
+ ),
58
+ is_preferred: true,
59
+ )
60
+ end
61
+
62
+ sig { returns(LanguageServer::Protocol::Interface::Diagnostic) }
63
+ def to_lsp_diagnostic
64
+ LanguageServer::Protocol::Interface::Diagnostic.new(
65
+ message: @offense.message,
66
+ source: "RuboCop",
67
+ code: @offense.cop_name,
68
+ severity: RUBOCOP_TO_LSP_SEVERITY[@offense.severity.name],
69
+ range: LanguageServer::Protocol::Interface::Range.new(
70
+ start: LanguageServer::Protocol::Interface::Position.new(
71
+ line: @offense.line - 1,
72
+ character: @offense.column
73
+ ),
74
+ end: LanguageServer::Protocol::Interface::Position.new(
75
+ line: @offense.last_line - 1,
76
+ character: @offense.last_column
77
+ )
78
+ )
79
+ )
80
+ end
81
+
82
+ private
83
+
84
+ sig { returns(T::Array[LanguageServer::Protocol::Interface::TextEdit]) }
85
+ def offense_replacements
86
+ @offense.corrector.as_replacements.map do |range, replacement|
87
+ LanguageServer::Protocol::Interface::TextEdit.new(
88
+ range: LanguageServer::Protocol::Interface::Range.new(
89
+ start: LanguageServer::Protocol::Interface::Position.new(line: range.line - 1, character: range.column),
90
+ end: LanguageServer::Protocol::Interface::Position.new(line: range.last_line - 1,
91
+ character: range.last_column)
92
+ ),
93
+ new_text: replacement
94
+ )
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,20 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ module Support
7
+ class SelectionRange < LanguageServer::Protocol::Interface::SelectionRange
8
+ extend T::Sig
9
+
10
+ sig { params(position: Document::PositionShape).returns(T::Boolean) }
11
+ def cover?(position)
12
+ line_range = (range.start.line..range.end.line)
13
+ character_range = (range.start.character..range.end.character)
14
+
15
+ line_range.cover?(position[:line]) && character_range.cover?(position[:character])
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,70 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ module Support
7
+ class SemanticTokenEncoder
8
+ extend T::Sig
9
+
10
+ sig { void }
11
+ def initialize
12
+ @current_row = T.let(0, Integer)
13
+ @current_column = T.let(0, Integer)
14
+ end
15
+
16
+ sig do
17
+ params(
18
+ tokens: T::Array[SemanticHighlighting::SemanticToken]
19
+ ).returns(LanguageServer::Protocol::Interface::SemanticTokens)
20
+ end
21
+ def encode(tokens)
22
+ delta = tokens
23
+ .sort_by do |token|
24
+ [token.location.start_line, token.location.start_column]
25
+ end
26
+ .flat_map do |token|
27
+ compute_delta(token)
28
+ end
29
+
30
+ LanguageServer::Protocol::Interface::SemanticTokens.new(data: delta)
31
+ end
32
+
33
+ # The delta array is computed according to the LSP specification:
34
+ # > The protocol for the token format relative uses relative
35
+ # > positions, because most tokens remain stable relative to
36
+ # > each other when edits are made in a file. This simplifies
37
+ # > the computation of a delta if a server supports it. So each
38
+ # > token is represented using 5 integers.
39
+
40
+ # For more information on how each number is calculated, read:
41
+ # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens
42
+ sig { params(token: SemanticHighlighting::SemanticToken).returns(T::Array[Integer]) }
43
+ def compute_delta(token)
44
+ row = token.location.start_line - 1
45
+ column = token.location.start_column
46
+ delta_line = row - @current_row
47
+
48
+ delta_column = column
49
+ delta_column -= @current_column if delta_line == 0
50
+
51
+ [delta_line, delta_column, token.length, token.type, encode_modifiers(token.modifier)]
52
+ ensure
53
+ @current_row = row
54
+ @current_column = column
55
+ end
56
+
57
+ # Encode an array of modifiers to positions onto a bit flag
58
+ # For example, [:default_library] will be encoded as
59
+ # 0b1000000000, as :default_library is the 10th bit according
60
+ # to the token modifiers index map.
61
+ sig { params(modifiers: T::Array[Integer]).returns(Integer) }
62
+ def encode_modifiers(modifiers)
63
+ modifiers.inject(0) do |encoded_modifiers, modifier|
64
+ encoded_modifiers | (1 << modifier)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,32 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ module Support
7
+ class SyntaxErrorDiagnostic
8
+ extend T::Sig
9
+
10
+ sig { params(edit: Document::EditShape).void }
11
+ def initialize(edit)
12
+ @edit = edit
13
+ end
14
+
15
+ sig { returns(FalseClass) }
16
+ def correctable?
17
+ false
18
+ end
19
+
20
+ sig { returns(LanguageServer::Protocol::Interface::Diagnostic) }
21
+ def to_lsp_diagnostic
22
+ LanguageServer::Protocol::Interface::Diagnostic.new(
23
+ message: "Syntax error",
24
+ source: "SyntaxTree",
25
+ severity: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
26
+ range: @edit[:range]
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module RubyLsp
@@ -5,10 +6,19 @@ module RubyLsp
5
6
  autoload :BaseRequest, "ruby_lsp/requests/base_request"
6
7
  autoload :DocumentSymbol, "ruby_lsp/requests/document_symbol"
7
8
  autoload :FoldingRanges, "ruby_lsp/requests/folding_ranges"
9
+ autoload :SelectionRanges, "ruby_lsp/requests/selection_ranges"
8
10
  autoload :SemanticHighlighting, "ruby_lsp/requests/semantic_highlighting"
9
11
  autoload :RuboCopRequest, "ruby_lsp/requests/rubocop_request"
10
12
  autoload :Formatting, "ruby_lsp/requests/formatting"
11
13
  autoload :Diagnostics, "ruby_lsp/requests/diagnostics"
12
14
  autoload :CodeActions, "ruby_lsp/requests/code_actions"
15
+ autoload :DocumentHighlight, "ruby_lsp/requests/document_highlight"
16
+
17
+ module Support
18
+ autoload :RuboCopDiagnostic, "ruby_lsp/requests/support/rubocop_diagnostic"
19
+ autoload :SelectionRange, "ruby_lsp/requests/support/selection_range"
20
+ autoload :SemanticTokenEncoder, "ruby_lsp/requests/support/semantic_token_encoder"
21
+ autoload :SyntaxErrorDiagnostic, "ruby_lsp/requests/support/syntax_error_diagnostic"
22
+ end
13
23
  end
14
24
  end
@@ -1,3 +1,4 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require "cgi"
@@ -6,32 +7,52 @@ require "ruby_lsp/document"
6
7
 
7
8
  module RubyLsp
8
9
  class Store
10
+ extend T::Sig
11
+
12
+ sig { void }
9
13
  def initialize
10
- @state = {}
14
+ @state = T.let({}, T::Hash[String, Document])
11
15
  end
12
16
 
17
+ sig { params(uri: String).returns(Document) }
13
18
  def get(uri)
14
19
  document = @state[uri]
15
20
  return document unless document.nil?
16
21
 
17
22
  set(uri, File.binread(CGI.unescape(URI.parse(uri).path)))
18
- @state[uri]
23
+ T.must(@state[uri])
19
24
  end
20
25
 
26
+ sig { params(uri: String, content: String).void }
21
27
  def set(uri, content)
22
28
  @state[uri] = Document.new(content)
23
29
  rescue SyntaxTree::Parser::ParseError
24
30
  # Do not update the store if there are syntax errors
25
31
  end
26
32
 
33
+ sig { params(uri: String, edits: T::Array[Document::EditShape]).void }
34
+ def push_edits(uri, edits)
35
+ T.must(@state[uri]).push_edits(edits)
36
+ end
37
+
38
+ sig { void }
27
39
  def clear
28
40
  @state.clear
29
41
  end
30
42
 
43
+ sig { params(uri: String).void }
31
44
  def delete(uri)
32
45
  @state.delete(uri)
33
46
  end
34
47
 
48
+ sig do
49
+ type_parameters(:T)
50
+ .params(
51
+ uri: String,
52
+ request_name: Symbol,
53
+ block: T.proc.params(document: Document).returns(T.type_parameter(:T))
54
+ ).returns(T.type_parameter(:T))
55
+ end
35
56
  def cache_fetch(uri, request_name, &block)
36
57
  get(uri).cache_fetch(request_name, &block)
37
58
  end
@@ -0,0 +1,57 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ desc "Check if all LSP requests are documented"
5
+ task :check_docs do
6
+ require "sorbet-runtime"
7
+ require "language_server-protocol"
8
+ require "syntax_tree"
9
+ require "logger"
10
+ require "ruby_lsp/requests/base_request"
11
+ require "ruby_lsp/requests/rubocop_request"
12
+
13
+ Dir["#{Dir.pwd}/lib/ruby_lsp/requests/*.rb"].each do |file|
14
+ require(file)
15
+ YARD.parse(file, [], Logger::Severity::FATAL)
16
+ end
17
+
18
+ spec_matcher = %r{\(https://microsoft.github.io/language-server-protocol/specification#.*\)}
19
+ error_messages = RubyLsp::Requests
20
+ .constants # rubocop:disable Sorbet/ConstantsFromStrings
21
+ .each_with_object(Hash.new { |h, k| h[k] = [] }) do |request, errors|
22
+ full_name = "RubyLsp::Requests::#{request}"
23
+ docs = YARD::Registry.at(full_name).docstring
24
+ next if /:nodoc:/.match?(docs)
25
+
26
+ if docs.empty?
27
+ errors[full_name] << "Missing documentation for request handler class"
28
+ elsif !spec_matcher.match?(docs)
29
+ errors[full_name] << <<~MESSAGE
30
+ Documentation for request handler classes must link to the official LSP specification.
31
+
32
+ For example, if your request handles text document hover, you should add a link to
33
+ https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover.
34
+ MESSAGE
35
+ elsif !/# Example/.match?(docs)
36
+ errors[full_name] << <<~MESSAGE
37
+ Documentation for request handler class must contain an example.
38
+
39
+ = Example
40
+ def my_method # <-- something happens here
41
+ end
42
+ MESSAGE
43
+ end
44
+ end
45
+
46
+ formatted_errors = error_messages.map { |name, errors| "#{name}: #{errors.join(", ")}" }
47
+
48
+ if error_messages.any?
49
+ puts <<~MESSAGE
50
+ The following requests have invalid documentation:
51
+
52
+ #{formatted_errors.join("\n")}
53
+ MESSAGE
54
+
55
+ exit!
56
+ end
57
+ end
data/ruby-lsp.gemspec CHANGED
@@ -21,5 +21,6 @@ Gem::Specification.new do |s|
21
21
 
22
22
  s.add_dependency("language_server-protocol")
23
23
  s.add_dependency("rubocop", ">= 1.0")
24
- s.add_dependency("syntax_tree", ">= 2.3")
24
+ s.add_dependency("sorbet-runtime")
25
+ s.add_dependency("syntax_tree", ">= 2.4")
25
26
  end
data/sorbet/config ADDED
@@ -0,0 +1,4 @@
1
+ --dir
2
+ .
3
+ --ignore=vendor/
4
+ --ignore=test/fixtures/
@@ -0,0 +1,8 @@
1
+ inherit_gem:
2
+ rubocop-sorbet: config/rbi.yml
3
+
4
+ Lint/EmptyFile:
5
+ Enabled: false
6
+
7
+ Sorbet/TypeAliasName:
8
+ Enabled: false