steep-activesupport-4 1.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) 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/Steepfile +68 -0
  9. data/bin/console +14 -0
  10. data/bin/generate-diagnostics-docs.rb +112 -0
  11. data/bin/mem_graph.rb +67 -0
  12. data/bin/mem_prof.rb +102 -0
  13. data/bin/output_rebaseline.rb +34 -0
  14. data/bin/output_test.rb +60 -0
  15. data/bin/rbs +20 -0
  16. data/bin/rbs-inline +19 -0
  17. data/bin/setup +9 -0
  18. data/bin/stackprof_test.rb +19 -0
  19. data/bin/steep +19 -0
  20. data/bin/steep-check.rb +251 -0
  21. data/bin/steep-prof +16 -0
  22. data/doc/narrowing.md +195 -0
  23. data/doc/shape.md +194 -0
  24. data/exe/steep +18 -0
  25. data/guides/README.md +5 -0
  26. data/guides/src/gem-rbs-collection/gem-rbs-collection.md +126 -0
  27. data/guides/src/getting-started/getting-started.md +163 -0
  28. data/guides/src/nil-optional/nil-optional.md +195 -0
  29. data/lib/steep/annotation_parser.rb +199 -0
  30. data/lib/steep/ast/annotation/collection.rb +172 -0
  31. data/lib/steep/ast/annotation.rb +137 -0
  32. data/lib/steep/ast/builtin.rb +104 -0
  33. data/lib/steep/ast/ignore.rb +148 -0
  34. data/lib/steep/ast/node/type_application.rb +88 -0
  35. data/lib/steep/ast/node/type_assertion.rb +81 -0
  36. data/lib/steep/ast/types/any.rb +35 -0
  37. data/lib/steep/ast/types/boolean.rb +45 -0
  38. data/lib/steep/ast/types/bot.rb +35 -0
  39. data/lib/steep/ast/types/class.rb +43 -0
  40. data/lib/steep/ast/types/factory.rb +557 -0
  41. data/lib/steep/ast/types/helper.rb +40 -0
  42. data/lib/steep/ast/types/instance.rb +42 -0
  43. data/lib/steep/ast/types/intersection.rb +93 -0
  44. data/lib/steep/ast/types/literal.rb +59 -0
  45. data/lib/steep/ast/types/logic.rb +84 -0
  46. data/lib/steep/ast/types/name.rb +128 -0
  47. data/lib/steep/ast/types/nil.rb +41 -0
  48. data/lib/steep/ast/types/proc.rb +117 -0
  49. data/lib/steep/ast/types/record.rb +79 -0
  50. data/lib/steep/ast/types/self.rb +43 -0
  51. data/lib/steep/ast/types/shared_instance.rb +11 -0
  52. data/lib/steep/ast/types/top.rb +35 -0
  53. data/lib/steep/ast/types/tuple.rb +60 -0
  54. data/lib/steep/ast/types/union.rb +97 -0
  55. data/lib/steep/ast/types/var.rb +65 -0
  56. data/lib/steep/ast/types/void.rb +35 -0
  57. data/lib/steep/cli.rb +401 -0
  58. data/lib/steep/diagnostic/deprecated/else_on_exhaustive_case.rb +20 -0
  59. data/lib/steep/diagnostic/deprecated/unknown_constant_assigned.rb +28 -0
  60. data/lib/steep/diagnostic/helper.rb +18 -0
  61. data/lib/steep/diagnostic/lsp_formatter.rb +78 -0
  62. data/lib/steep/diagnostic/result_printer2.rb +48 -0
  63. data/lib/steep/diagnostic/ruby.rb +1221 -0
  64. data/lib/steep/diagnostic/signature.rb +570 -0
  65. data/lib/steep/drivers/annotations.rb +52 -0
  66. data/lib/steep/drivers/check.rb +339 -0
  67. data/lib/steep/drivers/checkfile.rb +210 -0
  68. data/lib/steep/drivers/diagnostic_printer.rb +105 -0
  69. data/lib/steep/drivers/init.rb +66 -0
  70. data/lib/steep/drivers/langserver.rb +56 -0
  71. data/lib/steep/drivers/print_project.rb +113 -0
  72. data/lib/steep/drivers/stats.rb +203 -0
  73. data/lib/steep/drivers/utils/driver_helper.rb +143 -0
  74. data/lib/steep/drivers/utils/jobs_option.rb +26 -0
  75. data/lib/steep/drivers/vendor.rb +27 -0
  76. data/lib/steep/drivers/watch.rb +194 -0
  77. data/lib/steep/drivers/worker.rb +58 -0
  78. data/lib/steep/equatable.rb +23 -0
  79. data/lib/steep/expectations.rb +228 -0
  80. data/lib/steep/index/rbs_index.rb +350 -0
  81. data/lib/steep/index/signature_symbol_provider.rb +185 -0
  82. data/lib/steep/index/source_index.rb +167 -0
  83. data/lib/steep/interface/block.rb +103 -0
  84. data/lib/steep/interface/builder.rb +843 -0
  85. data/lib/steep/interface/function.rb +1090 -0
  86. data/lib/steep/interface/method_type.rb +330 -0
  87. data/lib/steep/interface/shape.rb +239 -0
  88. data/lib/steep/interface/substitution.rb +159 -0
  89. data/lib/steep/interface/type_param.rb +115 -0
  90. data/lib/steep/located_value.rb +20 -0
  91. data/lib/steep/method_name.rb +42 -0
  92. data/lib/steep/module_helper.rb +24 -0
  93. data/lib/steep/node_helper.rb +273 -0
  94. data/lib/steep/path_helper.rb +30 -0
  95. data/lib/steep/project/dsl.rb +268 -0
  96. data/lib/steep/project/group.rb +31 -0
  97. data/lib/steep/project/options.rb +63 -0
  98. data/lib/steep/project/pattern.rb +59 -0
  99. data/lib/steep/project/target.rb +92 -0
  100. data/lib/steep/project.rb +78 -0
  101. data/lib/steep/rake_task.rb +132 -0
  102. data/lib/steep/range_extension.rb +29 -0
  103. data/lib/steep/server/base_worker.rb +97 -0
  104. data/lib/steep/server/change_buffer.rb +73 -0
  105. data/lib/steep/server/custom_methods.rb +77 -0
  106. data/lib/steep/server/delay_queue.rb +45 -0
  107. data/lib/steep/server/interaction_worker.rb +492 -0
  108. data/lib/steep/server/lsp_formatter.rb +455 -0
  109. data/lib/steep/server/master.rb +912 -0
  110. data/lib/steep/server/target_group_files.rb +205 -0
  111. data/lib/steep/server/type_check_controller.rb +366 -0
  112. data/lib/steep/server/type_check_worker.rb +303 -0
  113. data/lib/steep/server/work_done_progress.rb +64 -0
  114. data/lib/steep/server/worker_process.rb +176 -0
  115. data/lib/steep/services/completion_provider.rb +802 -0
  116. data/lib/steep/services/content_change.rb +61 -0
  117. data/lib/steep/services/file_loader.rb +74 -0
  118. data/lib/steep/services/goto_service.rb +441 -0
  119. data/lib/steep/services/hover_provider/rbs.rb +88 -0
  120. data/lib/steep/services/hover_provider/ruby.rb +221 -0
  121. data/lib/steep/services/hover_provider/singleton_methods.rb +20 -0
  122. data/lib/steep/services/path_assignment.rb +46 -0
  123. data/lib/steep/services/signature_help_provider.rb +202 -0
  124. data/lib/steep/services/signature_service.rb +428 -0
  125. data/lib/steep/services/stats_calculator.rb +68 -0
  126. data/lib/steep/services/type_check_service.rb +394 -0
  127. data/lib/steep/services/type_name_completion.rb +236 -0
  128. data/lib/steep/signature/validator.rb +651 -0
  129. data/lib/steep/source/ignore_ranges.rb +69 -0
  130. data/lib/steep/source.rb +691 -0
  131. data/lib/steep/subtyping/cache.rb +30 -0
  132. data/lib/steep/subtyping/check.rb +1113 -0
  133. data/lib/steep/subtyping/constraints.rb +341 -0
  134. data/lib/steep/subtyping/relation.rb +101 -0
  135. data/lib/steep/subtyping/result.rb +324 -0
  136. data/lib/steep/subtyping/variable_variance.rb +89 -0
  137. data/lib/steep/test.rb +9 -0
  138. data/lib/steep/thread_waiter.rb +43 -0
  139. data/lib/steep/type_construction.rb +5183 -0
  140. data/lib/steep/type_inference/block_params.rb +416 -0
  141. data/lib/steep/type_inference/case_when.rb +303 -0
  142. data/lib/steep/type_inference/constant_env.rb +56 -0
  143. data/lib/steep/type_inference/context.rb +195 -0
  144. data/lib/steep/type_inference/logic_type_interpreter.rb +613 -0
  145. data/lib/steep/type_inference/method_call.rb +193 -0
  146. data/lib/steep/type_inference/method_params.rb +531 -0
  147. data/lib/steep/type_inference/multiple_assignment.rb +194 -0
  148. data/lib/steep/type_inference/send_args.rb +712 -0
  149. data/lib/steep/type_inference/type_env.rb +341 -0
  150. data/lib/steep/type_inference/type_env_builder.rb +138 -0
  151. data/lib/steep/typing.rb +321 -0
  152. data/lib/steep/version.rb +3 -0
  153. data/lib/steep.rb +369 -0
  154. data/manual/annotations.md +181 -0
  155. data/manual/ignore.md +20 -0
  156. data/manual/ruby-diagnostics.md +1879 -0
  157. data/sample/Steepfile +22 -0
  158. data/sample/lib/conference.rb +49 -0
  159. data/sample/lib/length.rb +35 -0
  160. data/sample/sig/conference.rbs +42 -0
  161. data/sample/sig/generics.rbs +15 -0
  162. data/sample/sig/length.rbs +34 -0
  163. data/steep-activesupport-4.gemspec +55 -0
  164. metadata +437 -0
@@ -0,0 +1,912 @@
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
+ patterns = [] #: Array[String]
417
+ project.targets.each do |target|
418
+ target.source_pattern.patterns.each do |pat|
419
+ path = project.base_dir + pat
420
+ patterns << path.to_s unless path.directory?
421
+ end
422
+ target.source_pattern.prefixes.each do |pat|
423
+ path = project.base_dir + pat
424
+ patterns << (path + "**/*.rb").to_s unless path.file?
425
+ end
426
+ target.signature_pattern.patterns.each do |pat|
427
+ path = project.base_dir + pat
428
+ patterns << path.to_s unless path.directory?
429
+ end
430
+ target.signature_pattern.prefixes.each do |pat|
431
+ path = project.base_dir + pat
432
+ patterns << (path + "**/*.rbs").to_s unless path.file?
433
+ end
434
+ end
435
+ patterns.sort!
436
+ patterns.uniq!
437
+
438
+ Steep.logger.info { "Setting up didChangeWatchedFiles with pattern: #{patterns.inspect}" }
439
+
440
+ enqueue_write_job SendMessageJob.to_client(
441
+ message: {
442
+ id: SecureRandom.uuid,
443
+ method: "client/registerCapability",
444
+ params: {
445
+ registrations: [
446
+ {
447
+ id: SecureRandom.uuid,
448
+ method: "workspace/didChangeWatchedFiles",
449
+ registerOptions: {
450
+ watchers: patterns.map do |pattern|
451
+ { globPattern: pattern }
452
+ end
453
+ }
454
+ }
455
+ ]
456
+ }
457
+ }
458
+ )
459
+ end
460
+
461
+ controller.changed_paths.clear()
462
+
463
+ # if typecheck_automatically
464
+ # if request = controller.make_request(guid: progress.guid, include_unchanged: true, progress: progress)
465
+ # start_type_check(request: request, last_request: nil)
466
+ # end
467
+ # end
468
+ end
469
+ end
470
+
471
+ when "workspace/didChangeWatchedFiles"
472
+ updated_watched_files = [] #: Array[Pathname]
473
+
474
+ message[:params][:changes].each do |change|
475
+ uri = change[:uri]
476
+ type = change[:type]
477
+
478
+ path = PathHelper.to_pathname!(uri)
479
+
480
+ unless controller.priority_paths.include?(path)
481
+ updated_watched_files << path
482
+
483
+ controller.push_changes(path)
484
+
485
+ case type
486
+ when LSP::Constant::FileChangeType::CREATED, LSP::Constant::FileChangeType::CHANGED
487
+ content = path.read
488
+ when LSP::Constant::FileChangeType::DELETED
489
+ content = ""
490
+ end
491
+
492
+ content or raise
493
+
494
+ broadcast_notification(CustomMethods::FileReset.notification({ uri: uri, content: content }))
495
+ end
496
+ end
497
+
498
+ if updated_watched_files.empty?
499
+ Steep.logger.info { "Exit from workspace/didChangeWatchedFiles notification because all of the changed files are already open" }
500
+ return
501
+ end
502
+
503
+ if typecheck_automatically
504
+ start_type_checking_queue.execute do
505
+ job_queue.push(
506
+ -> do
507
+ last_request = current_type_check_request
508
+ guid = SecureRandom.uuid
509
+
510
+ start_type_check(
511
+ last_request: last_request,
512
+ progress: work_done_progress(guid),
513
+ needs_response: false
514
+ )
515
+ end
516
+ )
517
+ end
518
+ end
519
+
520
+ when "textDocument/didChange"
521
+ if path = pathname(message[:params][:textDocument][:uri])
522
+ broadcast_notification(message)
523
+ controller.push_changes(path)
524
+
525
+ if typecheck_automatically
526
+ start_type_checking_queue.execute do
527
+ job_queue.push(
528
+ -> do
529
+ Steep.logger.info { "Starting type check from textDocument/didChange notification..." }
530
+
531
+ last_request = current_type_check_request
532
+ guid = SecureRandom.uuid
533
+
534
+ start_type_check(
535
+ last_request: last_request,
536
+ progress: work_done_progress(guid),
537
+ needs_response: false
538
+ )
539
+ end
540
+ )
541
+ end
542
+ end
543
+ end
544
+
545
+ when "textDocument/didOpen"
546
+ uri = message[:params][:textDocument][:uri]
547
+ text = message[:params][:textDocument][:text]
548
+
549
+ if path = pathname(uri)
550
+ if target = project.group_for_path(path)
551
+ controller.update_priority(open: path)
552
+ # broadcast_notification(CustomMethods::FileReset.notification({ uri: uri, content: text }))
553
+
554
+ start_type_checking_queue.execute do
555
+ guid = SecureRandom.uuid
556
+ start_type_check(last_request: current_type_check_request, progress: work_done_progress(guid), needs_response: true)
557
+ end
558
+ end
559
+ end
560
+
561
+ when "textDocument/didClose"
562
+ if path = pathname(message[:params][:textDocument][:uri])
563
+ controller.update_priority(close: path)
564
+ end
565
+
566
+ when "textDocument/hover", "textDocument/completion", "textDocument/signatureHelp"
567
+ if interaction_worker
568
+ if path = pathname(message[:params][:textDocument][:uri])
569
+ result_controller << send_request(method: message[:method], params: message[:params], worker: interaction_worker) do |handler|
570
+ handler.on_completion do |response|
571
+ enqueue_write_job SendMessageJob.to_client(
572
+ message: {
573
+ id: message[:id],
574
+ result: response[:result]
575
+ }
576
+ )
577
+ end
578
+ end
579
+ else
580
+ enqueue_write_job SendMessageJob.to_client(
581
+ message: {
582
+ id: message[:id],
583
+ result: nil
584
+ }
585
+ )
586
+ end
587
+ end
588
+
589
+ when "workspace/symbol"
590
+ result_controller << group_request do |group|
591
+ typecheck_workers.each do |worker|
592
+ group << send_request(method: "workspace/symbol", params: message[:params], worker: worker)
593
+ end
594
+
595
+ group.on_completion do |handlers|
596
+ result = handlers.flat_map(&:result)
597
+ result.uniq!
598
+ enqueue_write_job SendMessageJob.to_client(message: { id: message[:id], result: result })
599
+ end
600
+ end
601
+
602
+ when CustomMethods::Stats::METHOD
603
+ result_controller << group_request do |group|
604
+ typecheck_workers.each do |worker|
605
+ group << send_request(method: CustomMethods::Stats::METHOD, params: nil, worker: worker)
606
+ end
607
+
608
+ group.on_completion do |handlers|
609
+ stats = handlers.flat_map(&:result) #: Server::CustomMethods::Stats::result
610
+ enqueue_write_job SendMessageJob.to_client(
611
+ message: CustomMethods::Stats.response(message[:id], stats)
612
+ )
613
+ end
614
+ end
615
+
616
+ when "textDocument/definition", "textDocument/implementation", "textDocument/typeDefinition"
617
+ if path = pathname(message[:params][:textDocument][:uri])
618
+ result_controller << group_request do |group|
619
+ typecheck_workers.each do |worker|
620
+ group << send_request(method: message[:method], params: message[:params], worker: worker)
621
+ end
622
+
623
+ group.on_completion do |handlers|
624
+ links = handlers.flat_map(&:result)
625
+ links.uniq!
626
+ enqueue_write_job SendMessageJob.to_client(
627
+ message: {
628
+ id: message[:id],
629
+ result: links
630
+ }
631
+ )
632
+ end
633
+ end
634
+ else
635
+ enqueue_write_job SendMessageJob.to_client(
636
+ message: {
637
+ id: message[:id],
638
+ result: []
639
+ }
640
+ )
641
+ end
642
+
643
+ when CustomMethods::TypeCheck::METHOD
644
+ id = message[:id]
645
+ params = message[:params] #: CustomMethods::TypeCheck::params
646
+
647
+ request = TypeCheckController::Request.new(guid: id, progress: work_done_progress(id))
648
+ request.needs_response = true
649
+
650
+ params[:code_paths].each do |target_name, path|
651
+ request.code_paths << [target_name.to_sym, Pathname(path)]
652
+ end
653
+ params[:signature_paths].each do |target_name, path|
654
+ request.signature_paths << [target_name.to_sym, Pathname(path)]
655
+ end
656
+ params[:library_paths].each do |target_name, path|
657
+ request.library_paths << [target_name.to_sym, Pathname(path)]
658
+ end
659
+
660
+ start_type_check(request: request, last_request: nil)
661
+
662
+ when CustomMethods::TypeCheckGroups::METHOD
663
+ params = message[:params] #: CustomMethods::TypeCheckGroups::params
664
+
665
+ groups = params.fetch(:groups)
666
+
667
+ progress = work_done_progress(SecureRandom.uuid)
668
+ progress.begin("Type checking #{groups.empty? ? "project" : groups.join(", ")}", request_id: fresh_request_id)
669
+
670
+ request = controller.make_group_request(groups, progress: progress)
671
+ request.needs_response = false
672
+ start_type_check(request: request, last_request: current_type_check_request, report_progress_threshold: 0)
673
+
674
+ when "$/ping"
675
+ enqueue_write_job SendMessageJob.to_client(
676
+ message: {
677
+ id: message[:id],
678
+ result: message[:params]
679
+ }
680
+ )
681
+
682
+ when CustomMethods::Groups::METHOD
683
+ groups = [] #: Array[String]
684
+
685
+ project.targets.each do |target|
686
+ unless target.source_pattern.empty? && target.signature_pattern.empty?
687
+ groups << target.name.to_s
688
+ end
689
+
690
+ target.groups.each do |group|
691
+ unless group.source_pattern.empty? && group.signature_pattern.empty?
692
+ groups << "#{target.name}.#{group.name}"
693
+ end
694
+ end
695
+ end
696
+
697
+ enqueue_write_job(SendMessageJob.to_client(
698
+ message: CustomMethods::Groups.response(message[:id], groups)
699
+ ))
700
+
701
+ when "shutdown"
702
+ start_type_checking_queue.cancel
703
+
704
+ result_controller << group_request do |group|
705
+ each_worker do |worker|
706
+ group << send_request(method: "shutdown", worker: worker)
707
+ end
708
+
709
+ group.on_completion do
710
+ enqueue_write_job SendMessageJob.to_client(message: { id: message[:id], result: nil })
711
+ end
712
+ end
713
+
714
+ when "exit"
715
+ broadcast_notification(message)
716
+ end
717
+ end
718
+
719
+ def process_message_from_worker(message, worker:)
720
+ Steep.logger.tagged "#process_message_from_worker (worker=#{worker.name})" do
721
+ Steep.logger.info { "Processing message from worker: method=#{message[:method] || "-"}, id=#{message[:id] || "*"}" }
722
+
723
+ case
724
+ when message.key?(:id) && !message.key?(:method)
725
+ Steep.logger.tagged "response(id=#{message[:id]})" do
726
+ Steep.logger.error { "Received unexpected response" }
727
+ Steep.logger.debug { "result = #{message[:result].inspect}" }
728
+ end
729
+ when message.key?(:method) && !message.key?(:id)
730
+ case message[:method]
731
+ when CustomMethods::TypeCheck__Progress::METHOD
732
+ params = message[:params] #: CustomMethods::TypeCheck__Progress::params
733
+ target = project.targets.find {|target| target.name.to_s == params[:target] } or raise
734
+ on_type_check_update(
735
+ guid: params[:guid],
736
+ path: Pathname(params[:path]),
737
+ target: target,
738
+ diagnostics: params[:diagnostics]
739
+ )
740
+ else
741
+ # Forward other notifications
742
+ enqueue_write_job SendMessageJob.to_client(message: message)
743
+ end
744
+ end
745
+ end
746
+ end
747
+
748
+ def finish_type_check(request)
749
+ request.work_done_progress.end()
750
+
751
+ finished_at = Time.now
752
+ duration = finished_at - request.started_at
753
+
754
+ if request.needs_response
755
+ enqueue_write_job(
756
+ SendMessageJob.to_client(
757
+ message: CustomMethods::TypeCheck.response(
758
+ request.guid,
759
+ {
760
+ guid: request.guid,
761
+ completed: request.finished?,
762
+ started_at: request.started_at.iso8601,
763
+ finished_at: finished_at.iso8601,
764
+ duration: duration.to_i
765
+ }
766
+ )
767
+ )
768
+ )
769
+ else
770
+ Steep.logger.debug { "Skip sending response to #{CustomMethods::TypeCheck::METHOD} request" }
771
+ end
772
+ end
773
+
774
+ def start_type_check(request: nil, last_request:, progress: nil, include_unchanged: false, report_progress_threshold: 10, needs_response: nil)
775
+ Steep.logger.tagged "#start_type_check(#{progress&.guid || request&.guid}, #{last_request&.guid}" do
776
+ if last_request
777
+ finish_type_check(last_request)
778
+ end
779
+
780
+ unless request
781
+ progress or raise
782
+ request = controller.make_request(guid: progress.guid, include_unchanged: include_unchanged, progress: progress) or return
783
+ request.needs_response = needs_response ? true : false
784
+ end
785
+
786
+ if last_request
787
+ request.merge!(last_request)
788
+ end
789
+
790
+ if request.total > report_progress_threshold
791
+ request.report_progress!
792
+ end
793
+
794
+ if request.each_unchecked_target_path.to_a.empty?
795
+ finish_type_check(request)
796
+ @current_type_check_request = nil
797
+ return
798
+ end
799
+
800
+ Steep.logger.info "Starting new progress..."
801
+
802
+ @current_type_check_request = request
803
+
804
+ if progress
805
+ # If `request:` keyword arg is not given
806
+ request.work_done_progress.begin("Type checking", request_id: fresh_request_id)
807
+ end
808
+
809
+ Steep.logger.info "Sending $/typecheck/start notifications"
810
+ typecheck_workers.each do |worker|
811
+ assignment = Services::PathAssignment.new(
812
+ max_index: typecheck_workers.size,
813
+ index: worker.index || raise
814
+ )
815
+
816
+ enqueue_write_job SendMessageJob.to_worker(
817
+ worker,
818
+ message: CustomMethods::TypeCheck__Start.notification(request.as_json(assignment: assignment))
819
+ )
820
+ end
821
+ end
822
+ end
823
+
824
+ def on_type_check_update(guid:, path:, target:, diagnostics:)
825
+ if current = current_type_check_request()
826
+ if current.guid == guid
827
+ current.checked(path, target)
828
+
829
+ Steep.logger.info { "Request updated: checked=#{path}, unchecked=#{current.each_unchecked_code_target_path.size}, diagnostics=#{diagnostics&.size}" }
830
+
831
+ percentage = current.percentage
832
+ current.work_done_progress.report(percentage, "#{current.checked_paths.size}/#{current.total}") if current.report_progress
833
+
834
+ push_diagnostics(path, diagnostics)
835
+
836
+ if current.finished?
837
+ finish_type_check(current)
838
+ @current_type_check_request = nil
839
+ end
840
+ end
841
+ end
842
+ end
843
+
844
+ def broadcast_notification(message)
845
+ Steep.logger.info "Broadcasting notification #{message[:method]}"
846
+ each_worker do |worker|
847
+ enqueue_write_job SendMessageJob.new(dest: worker, message: message)
848
+ end
849
+ end
850
+
851
+ def send_notification(message, worker:)
852
+ Steep.logger.info "Sending notification #{message[:method]} to #{worker.name}"
853
+ enqueue_write_job SendMessageJob.new(dest: worker, message: message)
854
+ end
855
+
856
+ def fresh_request_id
857
+ SecureRandom.alphanumeric(10)
858
+ end
859
+
860
+ def send_request(method:, id: fresh_request_id(), params: nil, worker:, &block)
861
+ Steep.logger.info "Sending request #{method}(#{id}) to #{worker.name}"
862
+
863
+ # @type var message: lsp_request
864
+ message = { method: method, id: id, params: params }
865
+ ResultHandler.new(request: message).tap do |handler|
866
+ yield handler if block
867
+ enqueue_write_job SendMessageJob.to_worker(worker, message: message)
868
+ end
869
+ end
870
+
871
+ def group_request()
872
+ GroupHandler.new().tap do |group|
873
+ yield group
874
+ end
875
+ end
876
+
877
+ def kill
878
+ each_worker do |worker|
879
+ worker.kill
880
+ end
881
+ end
882
+
883
+ def enqueue_write_job(job)
884
+ Steep.logger.info { "Write_queue has #{write_queue.size} items"}
885
+ write_queue.push(job) # steep:ignore InsufficientKeywordArguments
886
+ end
887
+
888
+ def work_done_progress(guid)
889
+ if work_done_progress_supported?
890
+ WorkDoneProgress.new(guid) do |message|
891
+ enqueue_write_job SendMessageJob.to_client(message: message)
892
+ end
893
+ else
894
+ WorkDoneProgress.new(guid) do |message|
895
+ # nop
896
+ end
897
+ end
898
+ end
899
+
900
+ def push_diagnostics(path, diagnostics)
901
+ if diagnostics
902
+ write_queue.push SendMessageJob.to_client(
903
+ message: {
904
+ method: :"textDocument/publishDiagnostics",
905
+ params: { uri: Steep::PathHelper.to_uri(path).to_s, diagnostics: diagnostics }
906
+ }
907
+ )
908
+ end
909
+ end
910
+ end
911
+ end
912
+ end