refined-steep-server 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.
- checksums.yaml +7 -0
- data/.claude/commands/add-stub.md +7 -0
- data/.claude/commands/check.md +7 -0
- data/.github/workflows/ci.yml +55 -0
- data/.rspec +3 -0
- data/CLAUDE.md +57 -0
- data/README.md +147 -0
- data/Rakefile +12 -0
- data/Steepfile +5 -0
- data/exe/refined-steep-server +36 -0
- data/lib/refined/steep/server/base_server.rb +171 -0
- data/lib/refined/steep/server/io.rb +54 -0
- data/lib/refined/steep/server/lsp_server.rb +851 -0
- data/lib/refined/steep/server/message.rb +150 -0
- data/lib/refined/steep/server/steep_state.rb +80 -0
- data/lib/refined/steep/server/store.rb +113 -0
- data/lib/refined/steep/server/version.rb +9 -0
- data/lib/refined/steep/server.rb +22 -0
- data/rbs_collection.yaml +19 -0
- data/sig/external/steep.rbs +376 -0
- data/sig/refined/steep/server.rbs +8 -0
- data/spec/refined/steep/server/base_server_spec.rb +116 -0
- data/spec/refined/steep/server/io_spec.rb +54 -0
- data/spec/refined/steep/server/lsp_server_spec.rb +317 -0
- data/spec/refined/steep/server/message_spec.rb +80 -0
- data/spec/refined/steep/server/steep_state_spec.rb +46 -0
- data/spec/refined/steep/server/store_spec.rb +82 -0
- data/spec/spec_helper.rb +87 -0
- metadata +98 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
module Steep
|
|
2
|
+
module PathHelper
|
|
3
|
+
def self.to_pathname: (String uri) -> Pathname?
|
|
4
|
+
def self.to_uri: (Pathname path) -> URI::Generic
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module Server
|
|
8
|
+
module LSPFormatter
|
|
9
|
+
def self.format_hover_content: (untyped content) -> untyped
|
|
10
|
+
def self.format_completion_docs: (untyped item) -> String?
|
|
11
|
+
def self.declaration_summary: (untyped decl) -> String?
|
|
12
|
+
def self.markup_content: (?String? string) { () -> String? } -> untyped
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class Source
|
|
17
|
+
def self.parse: (String content, path: Pathname, factory: untyped) -> Source
|
|
18
|
+
def without_unrelated_defs: (line: Integer, column: Integer) -> Source
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Typing
|
|
22
|
+
class UnknownNodeError < StandardError
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class Project
|
|
27
|
+
attr_reader targets: Array[Target]
|
|
28
|
+
attr_reader steepfile_path: Pathname
|
|
29
|
+
attr_reader base_dir: Pathname
|
|
30
|
+
attr_accessor global_options: untyped
|
|
31
|
+
|
|
32
|
+
def initialize: (steepfile_path: Pathname, ?base_dir: Pathname?) -> void
|
|
33
|
+
def relative_path: (Pathname path) -> Pathname
|
|
34
|
+
def absolute_path: (Pathname path) -> Pathname
|
|
35
|
+
def target_for_source_path: (Pathname path) -> Target?
|
|
36
|
+
def target_for_signature_path: (Pathname path) -> Target?
|
|
37
|
+
|
|
38
|
+
class Target
|
|
39
|
+
attr_reader name: Symbol
|
|
40
|
+
def possible_source_file?: (Pathname path) -> untyped
|
|
41
|
+
def code_diagnostics_config: () -> Hash[untyped, untyped]?
|
|
42
|
+
def new_env_loader: () -> untyped
|
|
43
|
+
def implicitly_returns_nil: () -> bool
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class DSL
|
|
47
|
+
def self.parse: (Project project, String code, ?filename: String) -> void
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
module Services
|
|
52
|
+
class TypeCheckService
|
|
53
|
+
attr_reader project: Project
|
|
54
|
+
attr_reader source_files: Hash[Pathname, SourceFile]
|
|
55
|
+
attr_reader signature_services: Hash[Symbol, SignatureService]
|
|
56
|
+
attr_reader signature_validation_diagnostics: untyped
|
|
57
|
+
|
|
58
|
+
def initialize: (project: Project) -> void
|
|
59
|
+
def update: (changes: Hash[Pathname, Array[ContentChange]]) -> void
|
|
60
|
+
def typecheck_source: (path: Pathname, target: Project::Target) -> Array[untyped]?
|
|
61
|
+
def validate_signature: (path: Pathname, target: Project::Target) -> Array[untyped]?
|
|
62
|
+
|
|
63
|
+
class SourceFile
|
|
64
|
+
attr_reader path: Pathname
|
|
65
|
+
attr_reader content: String
|
|
66
|
+
attr_reader node: untyped
|
|
67
|
+
attr_reader typing: untyped
|
|
68
|
+
attr_reader errors: Array[untyped]?
|
|
69
|
+
attr_reader ignores: untyped
|
|
70
|
+
|
|
71
|
+
def diagnostics: () -> Array[untyped]?
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class SignatureService
|
|
76
|
+
def current_subtyping: () -> untyped
|
|
77
|
+
def latest_rbs_index: () -> untyped
|
|
78
|
+
def latest_env: () -> untyped
|
|
79
|
+
def env_rbs_paths: () -> Set[Pathname]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
class ContentChange
|
|
83
|
+
attr_reader range: [Position, Position]?
|
|
84
|
+
attr_reader text: String
|
|
85
|
+
|
|
86
|
+
def initialize: (?range: [Position, Position]?, text: String) -> void
|
|
87
|
+
|
|
88
|
+
class Position
|
|
89
|
+
attr_reader line: Integer
|
|
90
|
+
attr_reader column: Integer
|
|
91
|
+
|
|
92
|
+
def initialize: (line: Integer, column: Integer) -> void
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
class PathAssignment
|
|
97
|
+
def self.all: () -> PathAssignment
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
class FileLoader
|
|
101
|
+
def initialize: (base_dir: Pathname) -> void
|
|
102
|
+
def each_path_in_target: (Project::Target target, ?Array[String] command_line_args) { (Pathname) -> void } -> void
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
module HoverProvider
|
|
106
|
+
def self.content_for: (service: TypeCheckService, path: Pathname, line: Integer, column: Integer) -> untyped
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
class GotoService
|
|
110
|
+
def initialize: (type_check: TypeCheckService, assignment: PathAssignment) -> void
|
|
111
|
+
def definition: (path: Pathname, line: Integer, column: Integer) -> Array[untyped]
|
|
112
|
+
def implementation: (path: Pathname, line: Integer, column: Integer) -> Array[untyped]
|
|
113
|
+
def type_definition: (path: Pathname, line: Integer, column: Integer) -> Array[untyped]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
class SignatureHelpProvider
|
|
117
|
+
def initialize: (source: Source, subtyping: untyped) -> void
|
|
118
|
+
def run: (line: Integer, column: Integer) -> [Array[untyped], Integer]?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
class CompletionProvider
|
|
122
|
+
def initialize: (source_text: String, path: Pathname, subtyping: untyped) -> void
|
|
123
|
+
def run: (line: Integer, column: Integer) -> Array[untyped]
|
|
124
|
+
|
|
125
|
+
class LocalVariableItem
|
|
126
|
+
attr_reader identifier: Symbol
|
|
127
|
+
attr_reader type: untyped
|
|
128
|
+
attr_reader range: untyped
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
class ConstantItem
|
|
132
|
+
attr_reader identifier: Symbol
|
|
133
|
+
attr_reader range: untyped
|
|
134
|
+
attr_reader env: untyped
|
|
135
|
+
attr_reader full_name: untyped
|
|
136
|
+
def class?: () -> bool
|
|
137
|
+
def module?: () -> bool
|
|
138
|
+
def decl: () -> untyped
|
|
139
|
+
def comments: () -> Array[untyped]
|
|
140
|
+
def deprecated?: () -> bool
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class SimpleMethodNameItem
|
|
144
|
+
attr_reader identifier: Symbol
|
|
145
|
+
attr_reader method_name: untyped
|
|
146
|
+
attr_reader range: untyped
|
|
147
|
+
attr_reader deprecated: bool
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
class ComplexMethodNameItem
|
|
151
|
+
attr_reader identifier: Symbol
|
|
152
|
+
attr_reader method_names: Array[untyped]
|
|
153
|
+
attr_reader range: untyped
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
class GeneratedMethodNameItem
|
|
157
|
+
attr_reader identifier: Symbol
|
|
158
|
+
attr_reader range: untyped
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
class InstanceVariableItem
|
|
162
|
+
attr_reader identifier: Symbol
|
|
163
|
+
attr_reader type: untyped
|
|
164
|
+
attr_reader range: untyped
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
class KeywordArgumentItem
|
|
168
|
+
attr_reader identifier: Symbol
|
|
169
|
+
attr_reader range: untyped
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
class TypeNameItem
|
|
173
|
+
attr_reader absolute_type_name: untyped
|
|
174
|
+
attr_reader relative_type_name: untyped
|
|
175
|
+
attr_reader range: untyped
|
|
176
|
+
attr_reader env: untyped
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
class TextItem
|
|
180
|
+
attr_reader label: String
|
|
181
|
+
attr_reader text: String
|
|
182
|
+
attr_reader help_text: String?
|
|
183
|
+
attr_reader range: untyped
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
module Diagnostic
|
|
189
|
+
class LSPFormatter
|
|
190
|
+
def initialize: (?Hash[untyped, untyped]? config, ?default_severity: Symbol) -> void
|
|
191
|
+
def format: (untyped diagnostic) -> untyped
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
module Index
|
|
196
|
+
class SignatureSymbolProvider
|
|
197
|
+
attr_reader indexes: Hash[untyped, untyped]
|
|
198
|
+
|
|
199
|
+
def initialize: (project: Project, assignment: Services::PathAssignment) -> void
|
|
200
|
+
def query_symbol: (String query) -> Array[untyped]
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
module LanguageServer
|
|
206
|
+
module Protocol
|
|
207
|
+
module Interface
|
|
208
|
+
# All interface classes return hash-like objects
|
|
209
|
+
class InitializeResult
|
|
210
|
+
def initialize: (**untyped) -> void
|
|
211
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
class ServerCapabilities
|
|
215
|
+
def initialize: (**untyped) -> void
|
|
216
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
class TextDocumentSyncOptions
|
|
220
|
+
def initialize: (**untyped) -> void
|
|
221
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
class SaveOptions
|
|
225
|
+
def initialize: (**untyped) -> void
|
|
226
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
class CompletionOptions
|
|
230
|
+
def initialize: (**untyped) -> void
|
|
231
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
class SignatureHelpOptions
|
|
235
|
+
def initialize: (**untyped) -> void
|
|
236
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
class Hover
|
|
240
|
+
def initialize: (**untyped) -> void
|
|
241
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
class MarkupContent
|
|
245
|
+
def initialize: (**untyped) -> void
|
|
246
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
class Range
|
|
250
|
+
def initialize: (**untyped) -> void
|
|
251
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
class Position
|
|
255
|
+
def initialize: (**untyped) -> void
|
|
256
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
class CompletionList
|
|
260
|
+
def initialize: (**untyped) -> void
|
|
261
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
class CompletionItem
|
|
265
|
+
def initialize: (**untyped) -> void
|
|
266
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
class CompletionItemLabelDetails
|
|
270
|
+
def initialize: (**untyped) -> void
|
|
271
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
class TextEdit
|
|
275
|
+
def initialize: (**untyped) -> void
|
|
276
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
class SignatureHelp
|
|
280
|
+
def initialize: (**untyped) -> void
|
|
281
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
class SignatureInformation
|
|
285
|
+
def initialize: (**untyped) -> void
|
|
286
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
class ParameterInformation
|
|
290
|
+
def initialize: (**untyped) -> void
|
|
291
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
class SymbolInformation
|
|
295
|
+
def initialize: (**untyped) -> void
|
|
296
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
class ShowMessageParams
|
|
300
|
+
def initialize: (**untyped) -> void
|
|
301
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
class LogMessageParams
|
|
305
|
+
def initialize: (**untyped) -> void
|
|
306
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
class PublishDiagnosticsParams
|
|
310
|
+
def initialize: (**untyped) -> void
|
|
311
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
class ProgressParams
|
|
315
|
+
def initialize: (**untyped) -> void
|
|
316
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
class WorkDoneProgressBegin
|
|
320
|
+
def initialize: (**untyped) -> void
|
|
321
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
class WorkDoneProgressCreateParams
|
|
325
|
+
def initialize: (**untyped) -> void
|
|
326
|
+
def to_hash: () -> Hash[Symbol, untyped]
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
module Constant
|
|
331
|
+
module TextDocumentSyncKind
|
|
332
|
+
INCREMENTAL: Integer
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
module MessageType
|
|
336
|
+
LOG: Integer
|
|
337
|
+
INFO: Integer
|
|
338
|
+
WARNING: Integer
|
|
339
|
+
ERROR: Integer
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
module ErrorCodes
|
|
343
|
+
REQUEST_CANCELLED: Integer
|
|
344
|
+
INTERNAL_ERROR: Integer
|
|
345
|
+
REQUEST_FAILED: Integer
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
module CompletionItemKind
|
|
349
|
+
TEXT: Integer
|
|
350
|
+
FUNCTION: Integer
|
|
351
|
+
VARIABLE: Integer
|
|
352
|
+
CLASS: Integer
|
|
353
|
+
INTERFACE: Integer
|
|
354
|
+
FIELD: Integer
|
|
355
|
+
CONSTANT: Integer
|
|
356
|
+
SNIPPET: Integer
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
module InsertTextFormat
|
|
360
|
+
SNIPPET: Integer
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
module MarkupKind
|
|
364
|
+
MARKDOWN: String
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
module Transport
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
module Parser
|
|
374
|
+
class SyntaxError < StandardError
|
|
375
|
+
end
|
|
376
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Refined::Steep::Server::BaseServer do
|
|
4
|
+
def build_message(method:, id: nil, params: {})
|
|
5
|
+
msg = { method: method, params: params }
|
|
6
|
+
msg[:id] = id if id
|
|
7
|
+
msg
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def encode_message(hash)
|
|
11
|
+
json = JSON.generate(hash)
|
|
12
|
+
"Content-Length: #{json.bytesize}\r\n\r\n#{json}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create_server(messages)
|
|
16
|
+
raw = messages.map { |m| encode_message(m) }.join
|
|
17
|
+
reader = StringIO.new(raw)
|
|
18
|
+
writer = StringIO.new
|
|
19
|
+
|
|
20
|
+
server = TestServer.new(reader: reader, writer: writer)
|
|
21
|
+
[server, writer]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Concrete subclass for testing
|
|
25
|
+
before do
|
|
26
|
+
stub_const("TestServer", Class.new(Refined::Steep::Server::BaseServer) do
|
|
27
|
+
attr_reader :processed_messages
|
|
28
|
+
|
|
29
|
+
def initialize(**options)
|
|
30
|
+
@processed_messages = []
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def process_message(message)
|
|
35
|
+
@processed_messages << message
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def shutdown
|
|
41
|
+
# no-op
|
|
42
|
+
end
|
|
43
|
+
end)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe "#start" do
|
|
47
|
+
it "processes initialize message synchronously" do
|
|
48
|
+
messages = [build_message(method: "initialize", id: 1, params: { capabilities: {} })]
|
|
49
|
+
server, = create_server(messages)
|
|
50
|
+
|
|
51
|
+
server.start
|
|
52
|
+
|
|
53
|
+
expect(server.processed_messages.size).to eq(1)
|
|
54
|
+
expect(server.processed_messages[0][:method]).to eq("initialize")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "processes initialized notification synchronously" do
|
|
58
|
+
messages = [build_message(method: "initialized")]
|
|
59
|
+
server, = create_server(messages)
|
|
60
|
+
|
|
61
|
+
server.start
|
|
62
|
+
|
|
63
|
+
expect(server.processed_messages.size).to eq(1)
|
|
64
|
+
expect(server.processed_messages[0][:method]).to eq("initialized")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "queues other messages for worker thread" do
|
|
68
|
+
messages = [build_message(method: "textDocument/hover", id: 1)]
|
|
69
|
+
server, = create_server(messages)
|
|
70
|
+
|
|
71
|
+
server.start
|
|
72
|
+
# Give the worker thread time to process
|
|
73
|
+
sleep 0.1
|
|
74
|
+
|
|
75
|
+
expect(server.processed_messages.size).to eq(1)
|
|
76
|
+
expect(server.processed_messages[0][:method]).to eq("textDocument/hover")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "handles shutdown and exit sequence" do
|
|
80
|
+
messages = [
|
|
81
|
+
build_message(method: "shutdown", id: 1),
|
|
82
|
+
build_message(method: "exit"),
|
|
83
|
+
]
|
|
84
|
+
server, writer = create_server(messages)
|
|
85
|
+
|
|
86
|
+
expect { server.start }.to raise_error(SystemExit) do |error|
|
|
87
|
+
expect(error.status).to eq(0)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Verify shutdown response was written
|
|
91
|
+
output = writer.string
|
|
92
|
+
expect(output).to include('"id":1')
|
|
93
|
+
expect(output).to include('"result":null')
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
describe "#send_message" do
|
|
98
|
+
it "sends result message through outgoing queue" do
|
|
99
|
+
messages = [
|
|
100
|
+
build_message(method: "initialize", id: 1, params: { capabilities: {} }),
|
|
101
|
+
]
|
|
102
|
+
server, writer = create_server(messages)
|
|
103
|
+
|
|
104
|
+
# Override process_message to send a result
|
|
105
|
+
allow(server).to receive(:process_message) do |msg|
|
|
106
|
+
server.send(:send_message, Refined::Steep::Server::Result.new(id: msg[:id], response: { capabilities: {} }))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
server.start
|
|
110
|
+
sleep 0.1
|
|
111
|
+
|
|
112
|
+
output = writer.string
|
|
113
|
+
expect(output).to include('"capabilities"')
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Refined::Steep::Server::MessageReader do
|
|
4
|
+
it "reads JSON-RPC messages from IO" do
|
|
5
|
+
json = '{"method":"initialize","params":{}}'
|
|
6
|
+
raw = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
|
|
7
|
+
io = StringIO.new(raw)
|
|
8
|
+
|
|
9
|
+
reader = Refined::Steep::Server::MessageReader.new(io)
|
|
10
|
+
messages = []
|
|
11
|
+
reader.each_message { |msg| messages << msg }
|
|
12
|
+
|
|
13
|
+
expect(messages.size).to eq(1)
|
|
14
|
+
expect(messages[0][:method]).to eq("initialize")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "reads multiple messages" do
|
|
18
|
+
messages_data = [
|
|
19
|
+
{ method: "initialize", params: {} },
|
|
20
|
+
{ method: "initialized", params: {} },
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
raw = messages_data.map do |data|
|
|
24
|
+
json = JSON.generate(data)
|
|
25
|
+
"Content-Length: #{json.bytesize}\r\n\r\n#{json}"
|
|
26
|
+
end.join
|
|
27
|
+
|
|
28
|
+
io = StringIO.new(raw)
|
|
29
|
+
reader = Refined::Steep::Server::MessageReader.new(io)
|
|
30
|
+
messages = []
|
|
31
|
+
reader.each_message { |msg| messages << msg }
|
|
32
|
+
|
|
33
|
+
expect(messages.size).to eq(2)
|
|
34
|
+
expect(messages[0][:method]).to eq("initialize")
|
|
35
|
+
expect(messages[1][:method]).to eq("initialized")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
RSpec.describe Refined::Steep::Server::MessageWriter do
|
|
40
|
+
it "writes JSON-RPC messages to IO" do
|
|
41
|
+
io = StringIO.new
|
|
42
|
+
writer = Refined::Steep::Server::MessageWriter.new(io)
|
|
43
|
+
|
|
44
|
+
writer.write({ id: 1, result: nil })
|
|
45
|
+
|
|
46
|
+
output = io.string
|
|
47
|
+
expect(output).to match(/Content-Length: \d+\r\n\r\n/)
|
|
48
|
+
json_part = output.sub(/Content-Length: \d+\r\n\r\n/, "")
|
|
49
|
+
parsed = JSON.parse(json_part, symbolize_names: true)
|
|
50
|
+
expect(parsed[:jsonrpc]).to eq("2.0")
|
|
51
|
+
expect(parsed[:id]).to eq(1)
|
|
52
|
+
expect(parsed[:result]).to be_nil
|
|
53
|
+
end
|
|
54
|
+
end
|