steep-activesupport-4 1.9.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 (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