steep-relaxed 1.9.3.3

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 (165) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.gitmodules +0 -0
  4. data/CHANGELOG.md +1032 -0
  5. data/LICENSE +21 -0
  6. data/README.md +260 -0
  7. data/Rakefile +227 -0
  8. data/STDGEM_DEPENDENCIES.txt +59 -0
  9. data/Steepfile +68 -0
  10. data/bin/console +14 -0
  11. data/bin/generate-diagnostics-docs.rb +112 -0
  12. data/bin/mem_graph.rb +67 -0
  13. data/bin/mem_prof.rb +102 -0
  14. data/bin/output_rebaseline.rb +34 -0
  15. data/bin/output_test.rb +60 -0
  16. data/bin/rbs +20 -0
  17. data/bin/rbs-inline +19 -0
  18. data/bin/setup +9 -0
  19. data/bin/stackprof_test.rb +19 -0
  20. data/bin/steep +19 -0
  21. data/bin/steep-check.rb +251 -0
  22. data/bin/steep-prof +16 -0
  23. data/doc/narrowing.md +195 -0
  24. data/doc/shape.md +194 -0
  25. data/exe/steep +18 -0
  26. data/guides/README.md +5 -0
  27. data/guides/src/gem-rbs-collection/gem-rbs-collection.md +126 -0
  28. data/guides/src/getting-started/getting-started.md +163 -0
  29. data/guides/src/nil-optional/nil-optional.md +195 -0
  30. data/lib/steep/annotation_parser.rb +199 -0
  31. data/lib/steep/ast/annotation/collection.rb +172 -0
  32. data/lib/steep/ast/annotation.rb +137 -0
  33. data/lib/steep/ast/builtin.rb +104 -0
  34. data/lib/steep/ast/ignore.rb +148 -0
  35. data/lib/steep/ast/node/type_application.rb +88 -0
  36. data/lib/steep/ast/node/type_assertion.rb +81 -0
  37. data/lib/steep/ast/types/any.rb +35 -0
  38. data/lib/steep/ast/types/boolean.rb +45 -0
  39. data/lib/steep/ast/types/bot.rb +35 -0
  40. data/lib/steep/ast/types/class.rb +43 -0
  41. data/lib/steep/ast/types/factory.rb +557 -0
  42. data/lib/steep/ast/types/helper.rb +40 -0
  43. data/lib/steep/ast/types/instance.rb +42 -0
  44. data/lib/steep/ast/types/intersection.rb +93 -0
  45. data/lib/steep/ast/types/literal.rb +59 -0
  46. data/lib/steep/ast/types/logic.rb +84 -0
  47. data/lib/steep/ast/types/name.rb +128 -0
  48. data/lib/steep/ast/types/nil.rb +41 -0
  49. data/lib/steep/ast/types/proc.rb +117 -0
  50. data/lib/steep/ast/types/record.rb +79 -0
  51. data/lib/steep/ast/types/self.rb +43 -0
  52. data/lib/steep/ast/types/shared_instance.rb +11 -0
  53. data/lib/steep/ast/types/top.rb +35 -0
  54. data/lib/steep/ast/types/tuple.rb +60 -0
  55. data/lib/steep/ast/types/union.rb +97 -0
  56. data/lib/steep/ast/types/var.rb +65 -0
  57. data/lib/steep/ast/types/void.rb +35 -0
  58. data/lib/steep/cli.rb +401 -0
  59. data/lib/steep/diagnostic/deprecated/else_on_exhaustive_case.rb +20 -0
  60. data/lib/steep/diagnostic/deprecated/unknown_constant_assigned.rb +28 -0
  61. data/lib/steep/diagnostic/helper.rb +18 -0
  62. data/lib/steep/diagnostic/lsp_formatter.rb +78 -0
  63. data/lib/steep/diagnostic/result_printer2.rb +48 -0
  64. data/lib/steep/diagnostic/ruby.rb +1221 -0
  65. data/lib/steep/diagnostic/signature.rb +570 -0
  66. data/lib/steep/drivers/annotations.rb +52 -0
  67. data/lib/steep/drivers/check.rb +339 -0
  68. data/lib/steep/drivers/checkfile.rb +210 -0
  69. data/lib/steep/drivers/diagnostic_printer.rb +105 -0
  70. data/lib/steep/drivers/init.rb +66 -0
  71. data/lib/steep/drivers/langserver.rb +56 -0
  72. data/lib/steep/drivers/print_project.rb +113 -0
  73. data/lib/steep/drivers/stats.rb +203 -0
  74. data/lib/steep/drivers/utils/driver_helper.rb +143 -0
  75. data/lib/steep/drivers/utils/jobs_option.rb +26 -0
  76. data/lib/steep/drivers/vendor.rb +27 -0
  77. data/lib/steep/drivers/watch.rb +194 -0
  78. data/lib/steep/drivers/worker.rb +58 -0
  79. data/lib/steep/equatable.rb +23 -0
  80. data/lib/steep/expectations.rb +228 -0
  81. data/lib/steep/index/rbs_index.rb +350 -0
  82. data/lib/steep/index/signature_symbol_provider.rb +185 -0
  83. data/lib/steep/index/source_index.rb +167 -0
  84. data/lib/steep/interface/block.rb +103 -0
  85. data/lib/steep/interface/builder.rb +843 -0
  86. data/lib/steep/interface/function.rb +1090 -0
  87. data/lib/steep/interface/method_type.rb +330 -0
  88. data/lib/steep/interface/shape.rb +239 -0
  89. data/lib/steep/interface/substitution.rb +159 -0
  90. data/lib/steep/interface/type_param.rb +115 -0
  91. data/lib/steep/located_value.rb +20 -0
  92. data/lib/steep/method_name.rb +42 -0
  93. data/lib/steep/module_helper.rb +24 -0
  94. data/lib/steep/node_helper.rb +273 -0
  95. data/lib/steep/path_helper.rb +30 -0
  96. data/lib/steep/project/dsl.rb +268 -0
  97. data/lib/steep/project/group.rb +31 -0
  98. data/lib/steep/project/options.rb +63 -0
  99. data/lib/steep/project/pattern.rb +59 -0
  100. data/lib/steep/project/target.rb +92 -0
  101. data/lib/steep/project.rb +78 -0
  102. data/lib/steep/rake_task.rb +132 -0
  103. data/lib/steep/range_extension.rb +29 -0
  104. data/lib/steep/server/base_worker.rb +97 -0
  105. data/lib/steep/server/change_buffer.rb +73 -0
  106. data/lib/steep/server/custom_methods.rb +77 -0
  107. data/lib/steep/server/delay_queue.rb +45 -0
  108. data/lib/steep/server/interaction_worker.rb +492 -0
  109. data/lib/steep/server/lsp_formatter.rb +455 -0
  110. data/lib/steep/server/master.rb +922 -0
  111. data/lib/steep/server/target_group_files.rb +205 -0
  112. data/lib/steep/server/type_check_controller.rb +366 -0
  113. data/lib/steep/server/type_check_worker.rb +303 -0
  114. data/lib/steep/server/work_done_progress.rb +64 -0
  115. data/lib/steep/server/worker_process.rb +176 -0
  116. data/lib/steep/services/completion_provider.rb +802 -0
  117. data/lib/steep/services/content_change.rb +61 -0
  118. data/lib/steep/services/file_loader.rb +74 -0
  119. data/lib/steep/services/goto_service.rb +441 -0
  120. data/lib/steep/services/hover_provider/rbs.rb +88 -0
  121. data/lib/steep/services/hover_provider/ruby.rb +221 -0
  122. data/lib/steep/services/hover_provider/singleton_methods.rb +20 -0
  123. data/lib/steep/services/path_assignment.rb +46 -0
  124. data/lib/steep/services/signature_help_provider.rb +202 -0
  125. data/lib/steep/services/signature_service.rb +428 -0
  126. data/lib/steep/services/stats_calculator.rb +68 -0
  127. data/lib/steep/services/type_check_service.rb +394 -0
  128. data/lib/steep/services/type_name_completion.rb +236 -0
  129. data/lib/steep/signature/validator.rb +651 -0
  130. data/lib/steep/source/ignore_ranges.rb +69 -0
  131. data/lib/steep/source.rb +691 -0
  132. data/lib/steep/subtyping/cache.rb +30 -0
  133. data/lib/steep/subtyping/check.rb +1113 -0
  134. data/lib/steep/subtyping/constraints.rb +341 -0
  135. data/lib/steep/subtyping/relation.rb +101 -0
  136. data/lib/steep/subtyping/result.rb +324 -0
  137. data/lib/steep/subtyping/variable_variance.rb +89 -0
  138. data/lib/steep/test.rb +9 -0
  139. data/lib/steep/thread_waiter.rb +43 -0
  140. data/lib/steep/type_construction.rb +5183 -0
  141. data/lib/steep/type_inference/block_params.rb +416 -0
  142. data/lib/steep/type_inference/case_when.rb +303 -0
  143. data/lib/steep/type_inference/constant_env.rb +56 -0
  144. data/lib/steep/type_inference/context.rb +195 -0
  145. data/lib/steep/type_inference/logic_type_interpreter.rb +613 -0
  146. data/lib/steep/type_inference/method_call.rb +193 -0
  147. data/lib/steep/type_inference/method_params.rb +531 -0
  148. data/lib/steep/type_inference/multiple_assignment.rb +194 -0
  149. data/lib/steep/type_inference/send_args.rb +712 -0
  150. data/lib/steep/type_inference/type_env.rb +341 -0
  151. data/lib/steep/type_inference/type_env_builder.rb +138 -0
  152. data/lib/steep/typing.rb +321 -0
  153. data/lib/steep/version.rb +3 -0
  154. data/lib/steep.rb +369 -0
  155. data/manual/annotations.md +181 -0
  156. data/manual/ignore.md +20 -0
  157. data/manual/ruby-diagnostics.md +1879 -0
  158. data/sample/Steepfile +22 -0
  159. data/sample/lib/conference.rb +49 -0
  160. data/sample/lib/length.rb +35 -0
  161. data/sample/sig/conference.rbs +42 -0
  162. data/sample/sig/generics.rbs +15 -0
  163. data/sample/sig/length.rbs +34 -0
  164. data/steep-relaxed.gemspec +56 -0
  165. metadata +340 -0
@@ -0,0 +1,922 @@
1
+ module Steep
2
+ module Server
3
+ class Master
4
+ LSP = LanguageServer::Protocol
5
+
6
+ class ResultHandler
7
+ attr_reader :request
8
+ attr_reader :completion_handler
9
+ attr_reader :response
10
+
11
+ def initialize(request:)
12
+ @request = request
13
+ @response = nil
14
+ @completion_handler = nil
15
+ @completed = false
16
+ end
17
+
18
+ def process_response(message)
19
+ if request[:id] == message[:id]
20
+ completion_handler&.call(message)
21
+ @response = message
22
+ true
23
+ else
24
+ false
25
+ end
26
+ end
27
+
28
+ def result
29
+ response&.dig(:result)
30
+ end
31
+
32
+ def completed?
33
+ !!@response
34
+ end
35
+
36
+ def on_completion(&block)
37
+ @completion_handler = block
38
+ end
39
+ end
40
+
41
+ class GroupHandler
42
+ attr_reader :request
43
+ attr_reader :handlers
44
+ attr_reader :completion_handler
45
+
46
+ def initialize()
47
+ @handlers = {}
48
+ @completion_handler = nil
49
+ end
50
+
51
+ def process_response(message)
52
+ if handler = handlers[message[:id]]
53
+ handler.process_response(message)
54
+
55
+ if completed?
56
+ completion_handler&.call(handlers.values)
57
+ end
58
+
59
+ true
60
+ else
61
+ false
62
+ end
63
+ end
64
+
65
+ def completed?
66
+ handlers.each_value.all? {|handler| handler.completed? }
67
+ end
68
+
69
+ def <<(handler)
70
+ handlers[handler.request[:id]] = handler
71
+ end
72
+
73
+ def on_completion(&block)
74
+ @completion_handler = block
75
+ end
76
+ end
77
+
78
+ class ResultController
79
+ attr_reader :handlers
80
+
81
+ def initialize()
82
+ @handlers = []
83
+ end
84
+
85
+ def <<(handler)
86
+ @handlers << handler
87
+ end
88
+
89
+ def request_group()
90
+ group = GroupHandler.new()
91
+ yield group
92
+ group
93
+ end
94
+
95
+ def process_response(message)
96
+ handlers.each do |handler|
97
+ return true if handler.process_response(message)
98
+ end
99
+ false
100
+ ensure
101
+ handlers.reject!(&:completed?)
102
+ end
103
+ end
104
+
105
+ module MessageUtils
106
+ def request?
107
+ if method && id
108
+ true
109
+ else
110
+ false
111
+ end
112
+ end
113
+
114
+ def response?
115
+ if id && !method
116
+ true
117
+ else
118
+ false
119
+ end
120
+ end
121
+
122
+ def notification?
123
+ if method && !id
124
+ true
125
+ else
126
+ false
127
+ end
128
+ end
129
+
130
+ def method
131
+ message[:method]
132
+ end
133
+
134
+ def id
135
+ message[:id]
136
+ end
137
+
138
+ def result
139
+ message[:result]
140
+ end
141
+
142
+ def params
143
+ message[:params]
144
+ end
145
+ end
146
+
147
+ ReceiveMessageJob = _ = Struct.new(:source, :message, keyword_init: true) do
148
+ # @implements ReceiveMessageJob
149
+
150
+ def response?
151
+ message.key?(:id) && !message.key?(:method)
152
+ end
153
+
154
+ include MessageUtils
155
+ end
156
+
157
+ class SendMessageJob < Struct.new(:dest, :message, keyword_init: true)
158
+ # @implements SendMessageJob
159
+
160
+ def self.to_worker(worker, message:)
161
+ new(dest: worker, message: message)
162
+ end
163
+
164
+ def self.to_client(message:)
165
+ new(dest: :client, message: message)
166
+ end
167
+
168
+ include MessageUtils
169
+ end
170
+
171
+ attr_reader :project
172
+ attr_reader :reader, :writer
173
+ attr_reader :commandline_args
174
+
175
+ attr_reader :interaction_worker
176
+ attr_reader :typecheck_workers
177
+
178
+ attr_reader :job_queue, :write_queue
179
+
180
+ attr_reader :current_type_check_request
181
+ attr_reader :controller
182
+ attr_reader :result_controller
183
+
184
+ attr_reader :initialize_params
185
+ attr_accessor :typecheck_automatically
186
+ attr_reader :start_type_checking_queue
187
+
188
+ def initialize(project:, reader:, writer:, interaction_worker:, typecheck_workers:, queue: Queue.new)
189
+ @project = project
190
+ @reader = reader
191
+ @writer = writer
192
+ @interaction_worker = interaction_worker
193
+ @typecheck_workers = typecheck_workers
194
+ @current_type_check_request = nil
195
+ @typecheck_automatically = true
196
+ @commandline_args = []
197
+ @job_queue = queue
198
+ @write_queue = SizedQueue.new(100)
199
+
200
+ @controller = TypeCheckController.new(project: project)
201
+ @result_controller = ResultController.new()
202
+ @start_type_checking_queue = DelayQueue.new(delay: 0.3)
203
+ end
204
+
205
+ def start
206
+ Steep.logger.tagged "master" do
207
+ tags = Steep.logger.formatter.current_tags.dup
208
+
209
+ # @type var worker_threads: Array[Thread]
210
+ worker_threads = []
211
+
212
+ if interaction_worker
213
+ worker_threads << Thread.new do
214
+ Steep.logger.formatter.push_tags(*tags, "from-worker@interaction")
215
+ interaction_worker.reader.read do |message|
216
+ job_queue << ReceiveMessageJob.new(source: interaction_worker, message: message)
217
+ end
218
+ end
219
+ end
220
+
221
+ typecheck_workers.each do |worker|
222
+ worker_threads << Thread.new do
223
+ Steep.logger.formatter.push_tags(*tags, "from-worker@#{worker.name}")
224
+ worker.reader.read do |message|
225
+ job_queue << ReceiveMessageJob.new(source: worker, message: message)
226
+ end
227
+ end
228
+ end
229
+
230
+ read_client_thread = Thread.new do
231
+ reader.read do |message|
232
+ job_queue << ReceiveMessageJob.new(source: :client, message: message)
233
+ break if message[:method] == "exit"
234
+ end
235
+ end
236
+
237
+ write_thread = Thread.new do
238
+ Steep.logger.formatter.push_tags(*tags)
239
+ Steep.logger.tagged "write" do
240
+ while job = write_queue.deq
241
+ # @type var job: SendMessageJob
242
+ case job.dest
243
+ when :client
244
+ Steep.logger.info { "Processing SendMessageJob: dest=client, method=#{job.message[:method] || "-"}, id=#{job.message[:id] || "-"}" }
245
+ writer.write job.message
246
+ when WorkerProcess
247
+ Steep.logger.info { "Processing SendMessageJob: dest=#{job.dest.name}, method=#{job.message[:method] || "-"}, id=#{job.message[:id] || "-"}" }
248
+ job.dest << job.message
249
+ end
250
+ end
251
+ end
252
+ end
253
+
254
+ loop_thread = Thread.new do
255
+ Steep.logger.formatter.push_tags(*tags)
256
+ Steep.logger.tagged "main" do
257
+ while job = job_queue.deq
258
+ case job
259
+ when ReceiveMessageJob
260
+ src = case job.source
261
+ when :client
262
+ :client
263
+ else
264
+ job.source.name
265
+ end
266
+ Steep.logger.tagged("ReceiveMessageJob(#{src}/#{job.message[:method]}/#{job.message[:id]})") do
267
+ if job.response? && result_controller.process_response(job.message)
268
+ # nop
269
+ Steep.logger.info { "Processed by ResultController" }
270
+ else
271
+ case job.source
272
+ when :client
273
+ process_message_from_client(job.message)
274
+
275
+ if job.message[:method] == "exit"
276
+ job_queue.close()
277
+ end
278
+ when WorkerProcess
279
+ process_message_from_worker(job.message, worker: job.source)
280
+ end
281
+ end
282
+ end
283
+ when Proc
284
+ job.call()
285
+ end
286
+ end
287
+ end
288
+ end
289
+
290
+ waiter = ThreadWaiter.new
291
+ each_worker do |worker|
292
+ waiter << worker.wait_thread
293
+ end
294
+ waiter.wait_one()
295
+
296
+ unless job_queue.closed?
297
+ # Exit by error
298
+ each_worker do |worker|
299
+ worker.kill(force: true)
300
+ end
301
+ raise "Unexpected worker process exit"
302
+ end
303
+
304
+ write_queue.close()
305
+ write_thread.join
306
+
307
+ read_client_thread.join()
308
+ worker_threads.each do |thread|
309
+ thread.join
310
+ end
311
+
312
+ loop_thread.join
313
+ end
314
+ end
315
+
316
+ def each_worker(&block)
317
+ if block
318
+ yield interaction_worker if interaction_worker
319
+ typecheck_workers.each(&block)
320
+ else
321
+ enum_for :each_worker
322
+ end
323
+ end
324
+
325
+ def pathname(uri)
326
+ Steep::PathHelper.to_pathname(uri)
327
+ end
328
+
329
+ def assign_initialize_params(params)
330
+ @initialize_params = params
331
+ end
332
+
333
+ def work_done_progress_supported?
334
+ initialize_params or raise "`initialize` request is not receiged yet"
335
+ initialize_params.dig(:capabilities, :window, :workDoneProgress) ? true : false
336
+ end
337
+
338
+ def file_system_watcher_supported?
339
+ initialize_params or raise "`initialize` request is not receiged yet"
340
+ initialize_params.dig(:capabilities, :workspace, :didChangeWatchedFiles, :dynamicRegistration) || false
341
+ end
342
+
343
+ def process_message_from_client(message)
344
+ Steep.logger.info "Processing message from client: method=#{message[:method]}, id=#{message[:id]}"
345
+ id = message[:id]
346
+
347
+ case message[:method]
348
+ when "initialize"
349
+ assign_initialize_params(message[:params])
350
+
351
+ result_controller << group_request do |group|
352
+ each_worker do |worker|
353
+ group << send_request(method: "initialize", params: message[:params], worker: worker)
354
+ end
355
+
356
+ group.on_completion do
357
+ enqueue_write_job SendMessageJob.to_client(
358
+ message: {
359
+ id: id,
360
+ result: LSP::Interface::InitializeResult.new(
361
+ capabilities: LSP::Interface::ServerCapabilities.new(
362
+ text_document_sync: LSP::Interface::TextDocumentSyncOptions.new(
363
+ change: LSP::Constant::TextDocumentSyncKind::INCREMENTAL,
364
+ open_close: true
365
+ ),
366
+ hover_provider: {
367
+ workDoneProgress: true,
368
+ partialResults: true,
369
+ partialResult: true
370
+ },
371
+ completion_provider: LSP::Interface::CompletionOptions.new(
372
+ trigger_characters: [".", "@", ":"],
373
+ work_done_progress: true
374
+ ),
375
+ signature_help_provider: {
376
+ triggerCharacters: ["("]
377
+ },
378
+ workspace_symbol_provider: true,
379
+ definition_provider: true,
380
+ declaration_provider: false,
381
+ implementation_provider: true,
382
+ type_definition_provider: true
383
+ ),
384
+ server_info: {
385
+ name: "steep",
386
+ version: VERSION
387
+ }
388
+ )
389
+ }
390
+ )
391
+
392
+ progress = work_done_progress(SecureRandom.uuid)
393
+ if typecheck_automatically
394
+ progress.begin("Type checking", "loading projects...", request_id: fresh_request_id)
395
+ end
396
+
397
+ Steep.measure("Load files from disk...") do
398
+ controller.load(command_line_args: commandline_args) do |input|
399
+ input.transform_values! do |content|
400
+ content.is_a?(String) or raise
401
+ if content.valid_encoding?
402
+ content
403
+ else
404
+ { text: Base64.encode64(content), binary: true }
405
+ end
406
+ end
407
+ broadcast_notification(CustomMethods::FileLoad.notification({ content: input }))
408
+ end
409
+ end
410
+
411
+ if typecheck_automatically
412
+ progress.end()
413
+ end
414
+
415
+ if file_system_watcher_supported?
416
+ setup_file_system_watcher()
417
+ end
418
+
419
+ controller.changed_paths.clear()
420
+
421
+ # if typecheck_automatically
422
+ # if request = controller.make_request(guid: progress.guid, include_unchanged: true, progress: progress)
423
+ # start_type_check(request: request, last_request: nil)
424
+ # end
425
+ # end
426
+ end
427
+ end
428
+
429
+ when "workspace/didChangeWatchedFiles"
430
+ updated_watched_files = [] #: Array[Pathname]
431
+
432
+ message[:params][:changes].each do |change|
433
+ uri = change[:uri]
434
+ type = change[:type]
435
+
436
+ path = PathHelper.to_pathname!(uri)
437
+
438
+ unless controller.priority_paths.include?(path)
439
+ updated_watched_files << path
440
+
441
+ controller.push_changes(path)
442
+
443
+ case type
444
+ when LSP::Constant::FileChangeType::CREATED, LSP::Constant::FileChangeType::CHANGED
445
+ content = path.read
446
+ when LSP::Constant::FileChangeType::DELETED
447
+ content = ""
448
+ end
449
+
450
+ content or raise
451
+
452
+ broadcast_notification(CustomMethods::FileReset.notification({ uri: uri, content: content }))
453
+ end
454
+ end
455
+
456
+ if updated_watched_files.empty?
457
+ Steep.logger.info { "Exit from workspace/didChangeWatchedFiles notification because all of the changed files are already open" }
458
+ return
459
+ end
460
+
461
+ if typecheck_automatically
462
+ start_type_checking_queue.execute do
463
+ job_queue.push(
464
+ -> do
465
+ last_request = current_type_check_request
466
+ guid = SecureRandom.uuid
467
+
468
+ start_type_check(
469
+ last_request: last_request,
470
+ progress: work_done_progress(guid),
471
+ needs_response: false
472
+ )
473
+ end
474
+ )
475
+ end
476
+ end
477
+
478
+ when "textDocument/didChange"
479
+ if path = pathname(message[:params][:textDocument][:uri])
480
+ broadcast_notification(message)
481
+ controller.push_changes(path)
482
+
483
+ if typecheck_automatically
484
+ start_type_checking_queue.execute do
485
+ job_queue.push(
486
+ -> do
487
+ Steep.logger.info { "Starting type check from textDocument/didChange notification..." }
488
+
489
+ last_request = current_type_check_request
490
+ guid = SecureRandom.uuid
491
+
492
+ start_type_check(
493
+ last_request: last_request,
494
+ progress: work_done_progress(guid),
495
+ needs_response: false
496
+ )
497
+ end
498
+ )
499
+ end
500
+ end
501
+ end
502
+
503
+ when "textDocument/didOpen"
504
+ uri = message[:params][:textDocument][:uri]
505
+ text = message[:params][:textDocument][:text]
506
+
507
+ if path = pathname(uri)
508
+ if target = project.group_for_path(path)
509
+ controller.update_priority(open: path)
510
+ # broadcast_notification(CustomMethods::FileReset.notification({ uri: uri, content: text }))
511
+
512
+ start_type_checking_queue.execute do
513
+ guid = SecureRandom.uuid
514
+ start_type_check(last_request: current_type_check_request, progress: work_done_progress(guid), needs_response: true)
515
+ end
516
+ end
517
+ end
518
+
519
+ when "textDocument/didClose"
520
+ if path = pathname(message[:params][:textDocument][:uri])
521
+ controller.update_priority(close: path)
522
+ end
523
+
524
+ when "textDocument/hover", "textDocument/completion", "textDocument/signatureHelp"
525
+ if interaction_worker
526
+ if path = pathname(message[:params][:textDocument][:uri])
527
+ result_controller << send_request(method: message[:method], params: message[:params], worker: interaction_worker) do |handler|
528
+ handler.on_completion do |response|
529
+ enqueue_write_job SendMessageJob.to_client(
530
+ message: {
531
+ id: message[:id],
532
+ result: response[:result]
533
+ }
534
+ )
535
+ end
536
+ end
537
+ else
538
+ enqueue_write_job SendMessageJob.to_client(
539
+ message: {
540
+ id: message[:id],
541
+ result: nil
542
+ }
543
+ )
544
+ end
545
+ end
546
+
547
+ when "workspace/symbol"
548
+ result_controller << group_request do |group|
549
+ typecheck_workers.each do |worker|
550
+ group << send_request(method: "workspace/symbol", params: message[:params], worker: worker)
551
+ end
552
+
553
+ group.on_completion do |handlers|
554
+ result = handlers.flat_map(&:result)
555
+ result.uniq!
556
+ enqueue_write_job SendMessageJob.to_client(message: { id: message[:id], result: result })
557
+ end
558
+ end
559
+
560
+ when CustomMethods::Stats::METHOD
561
+ result_controller << group_request do |group|
562
+ typecheck_workers.each do |worker|
563
+ group << send_request(method: CustomMethods::Stats::METHOD, params: nil, worker: worker)
564
+ end
565
+
566
+ group.on_completion do |handlers|
567
+ stats = handlers.flat_map(&:result) #: Server::CustomMethods::Stats::result
568
+ enqueue_write_job SendMessageJob.to_client(
569
+ message: CustomMethods::Stats.response(message[:id], stats)
570
+ )
571
+ end
572
+ end
573
+
574
+ when "textDocument/definition", "textDocument/implementation", "textDocument/typeDefinition"
575
+ if path = pathname(message[:params][:textDocument][:uri])
576
+ result_controller << group_request do |group|
577
+ typecheck_workers.each do |worker|
578
+ group << send_request(method: message[:method], params: message[:params], worker: worker)
579
+ end
580
+
581
+ group.on_completion do |handlers|
582
+ links = handlers.flat_map(&:result)
583
+ links.uniq!
584
+ enqueue_write_job SendMessageJob.to_client(
585
+ message: {
586
+ id: message[:id],
587
+ result: links
588
+ }
589
+ )
590
+ end
591
+ end
592
+ else
593
+ enqueue_write_job SendMessageJob.to_client(
594
+ message: {
595
+ id: message[:id],
596
+ result: [] #: Array[untyped]
597
+ }
598
+ )
599
+ end
600
+
601
+ when CustomMethods::TypeCheck::METHOD
602
+ id = message[:id]
603
+ params = message[:params] #: CustomMethods::TypeCheck::params
604
+
605
+ request = TypeCheckController::Request.new(guid: id, progress: work_done_progress(id))
606
+ request.needs_response = true
607
+
608
+ params[:code_paths].each do |target_name, path|
609
+ request.code_paths << [target_name.to_sym, Pathname(path)]
610
+ end
611
+ params[:signature_paths].each do |target_name, path|
612
+ request.signature_paths << [target_name.to_sym, Pathname(path)]
613
+ end
614
+ params[:library_paths].each do |target_name, path|
615
+ request.library_paths << [target_name.to_sym, Pathname(path)]
616
+ end
617
+
618
+ start_type_check(request: request, last_request: nil)
619
+
620
+ when CustomMethods::TypeCheckGroups::METHOD
621
+ params = message[:params] #: CustomMethods::TypeCheckGroups::params
622
+
623
+ groups = params.fetch(:groups)
624
+
625
+ progress = work_done_progress(SecureRandom.uuid)
626
+ progress.begin("Type checking #{groups.empty? ? "project" : groups.join(", ")}", request_id: fresh_request_id)
627
+
628
+ request = controller.make_group_request(groups, progress: progress)
629
+ request.needs_response = false
630
+ start_type_check(request: request, last_request: current_type_check_request, report_progress_threshold: 0)
631
+
632
+ when "$/ping"
633
+ enqueue_write_job SendMessageJob.to_client(
634
+ message: {
635
+ id: message[:id],
636
+ result: message[:params]
637
+ }
638
+ )
639
+
640
+ when CustomMethods::Groups::METHOD
641
+ groups = [] #: Array[String]
642
+
643
+ project.targets.each do |target|
644
+ unless target.source_pattern.empty? && target.signature_pattern.empty?
645
+ groups << target.name.to_s
646
+ end
647
+
648
+ target.groups.each do |group|
649
+ unless group.source_pattern.empty? && group.signature_pattern.empty?
650
+ groups << "#{target.name}.#{group.name}"
651
+ end
652
+ end
653
+ end
654
+
655
+ enqueue_write_job(SendMessageJob.to_client(
656
+ message: CustomMethods::Groups.response(message[:id], groups)
657
+ ))
658
+
659
+ when "shutdown"
660
+ start_type_checking_queue.cancel
661
+
662
+ result_controller << group_request do |group|
663
+ each_worker do |worker|
664
+ group << send_request(method: "shutdown", worker: worker)
665
+ end
666
+
667
+ group.on_completion do
668
+ enqueue_write_job SendMessageJob.to_client(message: { id: message[:id], result: nil })
669
+ end
670
+ end
671
+
672
+ when "exit"
673
+ broadcast_notification(message)
674
+ end
675
+ end
676
+
677
+ def process_message_from_worker(message, worker:)
678
+ Steep.logger.tagged "#process_message_from_worker (worker=#{worker.name})" do
679
+ Steep.logger.info { "Processing message from worker: method=#{message[:method] || "-"}, id=#{message[:id] || "*"}" }
680
+
681
+ case
682
+ when message.key?(:id) && !message.key?(:method)
683
+ Steep.logger.tagged "response(id=#{message[:id]})" do
684
+ Steep.logger.error { "Received unexpected response" }
685
+ Steep.logger.debug { "result = #{message[:result].inspect}" }
686
+ end
687
+ when message.key?(:method) && !message.key?(:id)
688
+ case message[:method]
689
+ when CustomMethods::TypeCheck__Progress::METHOD
690
+ params = message[:params] #: CustomMethods::TypeCheck__Progress::params
691
+ target = project.targets.find {|target| target.name.to_s == params[:target] } or raise
692
+ on_type_check_update(
693
+ guid: params[:guid],
694
+ path: Pathname(params[:path]),
695
+ target: target,
696
+ diagnostics: params[:diagnostics]
697
+ )
698
+ else
699
+ # Forward other notifications
700
+ enqueue_write_job SendMessageJob.to_client(message: message)
701
+ end
702
+ end
703
+ end
704
+ end
705
+
706
+ def finish_type_check(request)
707
+ request.work_done_progress.end()
708
+
709
+ finished_at = Time.now
710
+ duration = finished_at - request.started_at
711
+
712
+ if request.needs_response
713
+ enqueue_write_job(
714
+ SendMessageJob.to_client(
715
+ message: CustomMethods::TypeCheck.response(
716
+ request.guid,
717
+ {
718
+ guid: request.guid,
719
+ completed: request.finished?,
720
+ started_at: request.started_at.iso8601,
721
+ finished_at: finished_at.iso8601,
722
+ duration: duration.to_i
723
+ }
724
+ )
725
+ )
726
+ )
727
+ else
728
+ Steep.logger.debug { "Skip sending response to #{CustomMethods::TypeCheck::METHOD} request" }
729
+ end
730
+ end
731
+
732
+ def start_type_check(request: nil, last_request:, progress: nil, include_unchanged: false, report_progress_threshold: 10, needs_response: nil)
733
+ Steep.logger.tagged "#start_type_check(#{progress&.guid || request&.guid}, #{last_request&.guid}" do
734
+ if last_request
735
+ finish_type_check(last_request)
736
+ end
737
+
738
+ unless request
739
+ progress or raise
740
+ request = controller.make_request(guid: progress.guid, include_unchanged: include_unchanged, progress: progress) or return
741
+ request.needs_response = needs_response ? true : false
742
+ end
743
+
744
+ if last_request
745
+ request.merge!(last_request)
746
+ end
747
+
748
+ if request.total > report_progress_threshold
749
+ request.report_progress!
750
+ end
751
+
752
+ if request.each_unchecked_target_path.to_a.empty?
753
+ finish_type_check(request)
754
+ @current_type_check_request = nil
755
+ return
756
+ end
757
+
758
+ Steep.logger.info "Starting new progress..."
759
+
760
+ @current_type_check_request = request
761
+
762
+ if progress
763
+ # If `request:` keyword arg is not given
764
+ request.work_done_progress.begin("Type checking", request_id: fresh_request_id)
765
+ end
766
+
767
+ Steep.logger.info "Sending $/typecheck/start notifications"
768
+ typecheck_workers.each do |worker|
769
+ assignment = Services::PathAssignment.new(
770
+ max_index: typecheck_workers.size,
771
+ index: worker.index || raise
772
+ )
773
+
774
+ enqueue_write_job SendMessageJob.to_worker(
775
+ worker,
776
+ message: CustomMethods::TypeCheck__Start.notification(request.as_json(assignment: assignment))
777
+ )
778
+ end
779
+ end
780
+ end
781
+
782
+ def on_type_check_update(guid:, path:, target:, diagnostics:)
783
+ if current = current_type_check_request()
784
+ if current.guid == guid
785
+ current.checked(path, target)
786
+
787
+ Steep.logger.info { "Request updated: checked=#{path}, unchecked=#{current.each_unchecked_code_target_path.size}, diagnostics=#{diagnostics&.size}" }
788
+
789
+ percentage = current.percentage
790
+ current.work_done_progress.report(percentage, "#{current.checked_paths.size}/#{current.total}") if current.report_progress
791
+
792
+ push_diagnostics(path, diagnostics)
793
+
794
+ if current.finished?
795
+ finish_type_check(current)
796
+ @current_type_check_request = nil
797
+ end
798
+ end
799
+ end
800
+ end
801
+
802
+ def broadcast_notification(message)
803
+ Steep.logger.info "Broadcasting notification #{message[:method]}"
804
+ each_worker do |worker|
805
+ enqueue_write_job SendMessageJob.new(dest: worker, message: message)
806
+ end
807
+ end
808
+
809
+ def send_notification(message, worker:)
810
+ Steep.logger.info "Sending notification #{message[:method]} to #{worker.name}"
811
+ enqueue_write_job SendMessageJob.new(dest: worker, message: message)
812
+ end
813
+
814
+ def fresh_request_id
815
+ SecureRandom.alphanumeric(10)
816
+ end
817
+
818
+ def send_request(method:, id: fresh_request_id(), params: nil, worker:, &block)
819
+ Steep.logger.info "Sending request #{method}(#{id}) to #{worker.name}"
820
+
821
+ # @type var message: lsp_request
822
+ message = { method: method, id: id, params: params }
823
+ ResultHandler.new(request: message).tap do |handler|
824
+ yield handler if block
825
+ enqueue_write_job SendMessageJob.to_worker(worker, message: message)
826
+ end
827
+ end
828
+
829
+ def group_request()
830
+ GroupHandler.new().tap do |group|
831
+ yield group
832
+ end
833
+ end
834
+
835
+ def kill
836
+ each_worker do |worker|
837
+ worker.kill
838
+ end
839
+ end
840
+
841
+ def enqueue_write_job(job)
842
+ Steep.logger.info { "Write_queue has #{write_queue.size} items"}
843
+ write_queue.push(job) # steep:ignore InsufficientKeywordArguments
844
+ end
845
+
846
+ def work_done_progress(guid)
847
+ if work_done_progress_supported?
848
+ WorkDoneProgress.new(guid) do |message|
849
+ enqueue_write_job SendMessageJob.to_client(message: message)
850
+ end
851
+ else
852
+ WorkDoneProgress.new(guid) do |message|
853
+ # nop
854
+ end
855
+ end
856
+ end
857
+
858
+ def push_diagnostics(path, diagnostics)
859
+ if diagnostics
860
+ write_queue.push SendMessageJob.to_client(
861
+ message: {
862
+ method: :"textDocument/publishDiagnostics",
863
+ params: { uri: Steep::PathHelper.to_uri(path).to_s, diagnostics: diagnostics }
864
+ }
865
+ )
866
+ end
867
+ end
868
+
869
+ def setup_file_system_watcher()
870
+ patterns = [] #: Array[String]
871
+
872
+ project.targets.each do |target|
873
+ patterns.concat(paths_to_watch(target.source_pattern, extname: ".rb"))
874
+ patterns.concat(paths_to_watch(target.signature_pattern, extname: ".rbs"))
875
+ target.groups.each do |group|
876
+ patterns.concat(paths_to_watch(group.source_pattern, extname: ".rb"))
877
+ patterns.concat(paths_to_watch(group.signature_pattern, extname: ".rbs"))
878
+ end
879
+ end
880
+ patterns.sort!
881
+ patterns.uniq!
882
+
883
+ Steep.logger.info { "Setting up didChangeWatchedFiles with pattern: #{patterns.inspect}" }
884
+
885
+ enqueue_write_job SendMessageJob.to_client(
886
+ message: {
887
+ id: SecureRandom.uuid,
888
+ method: "client/registerCapability",
889
+ params: {
890
+ registrations: [
891
+ {
892
+ id: SecureRandom.uuid,
893
+ method: "workspace/didChangeWatchedFiles",
894
+ registerOptions: {
895
+ watchers: patterns.map do |pattern|
896
+ { globPattern: pattern }
897
+ end
898
+ }
899
+ }
900
+ ]
901
+ }
902
+ }
903
+ )
904
+ end
905
+
906
+ def paths_to_watch(pattern, extname:)
907
+ result = [] #: Array[String]
908
+
909
+ pattern.patterns.each do |pat|
910
+ path = project.base_dir + pat
911
+ result << path.to_s unless path.directory?
912
+ end
913
+ pattern.prefixes.each do |pat|
914
+ path = project.base_dir + pat
915
+ result << (path + "**/*#{extname}").to_s unless path.file?
916
+ end
917
+
918
+ result
919
+ end
920
+ end
921
+ end
922
+ end