ruby-lsp 0.0.2 → 0.1.0

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