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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.gitmodules +0 -0
- data/CHANGELOG.md +1032 -0
- data/LICENSE +21 -0
- data/README.md +260 -0
- data/Rakefile +227 -0
- data/STDGEM_DEPENDENCIES.txt +59 -0
- data/Steepfile +68 -0
- data/bin/console +14 -0
- data/bin/generate-diagnostics-docs.rb +112 -0
- data/bin/mem_graph.rb +67 -0
- data/bin/mem_prof.rb +102 -0
- data/bin/output_rebaseline.rb +34 -0
- data/bin/output_test.rb +60 -0
- data/bin/rbs +20 -0
- data/bin/rbs-inline +19 -0
- data/bin/setup +9 -0
- data/bin/stackprof_test.rb +19 -0
- data/bin/steep +19 -0
- data/bin/steep-check.rb +251 -0
- data/bin/steep-prof +16 -0
- data/doc/narrowing.md +195 -0
- data/doc/shape.md +194 -0
- data/exe/steep +18 -0
- data/guides/README.md +5 -0
- data/guides/src/gem-rbs-collection/gem-rbs-collection.md +126 -0
- data/guides/src/getting-started/getting-started.md +163 -0
- data/guides/src/nil-optional/nil-optional.md +195 -0
- data/lib/steep/annotation_parser.rb +199 -0
- data/lib/steep/ast/annotation/collection.rb +172 -0
- data/lib/steep/ast/annotation.rb +137 -0
- data/lib/steep/ast/builtin.rb +104 -0
- data/lib/steep/ast/ignore.rb +148 -0
- data/lib/steep/ast/node/type_application.rb +88 -0
- data/lib/steep/ast/node/type_assertion.rb +81 -0
- data/lib/steep/ast/types/any.rb +35 -0
- data/lib/steep/ast/types/boolean.rb +45 -0
- data/lib/steep/ast/types/bot.rb +35 -0
- data/lib/steep/ast/types/class.rb +43 -0
- data/lib/steep/ast/types/factory.rb +557 -0
- data/lib/steep/ast/types/helper.rb +40 -0
- data/lib/steep/ast/types/instance.rb +42 -0
- data/lib/steep/ast/types/intersection.rb +93 -0
- data/lib/steep/ast/types/literal.rb +59 -0
- data/lib/steep/ast/types/logic.rb +84 -0
- data/lib/steep/ast/types/name.rb +128 -0
- data/lib/steep/ast/types/nil.rb +41 -0
- data/lib/steep/ast/types/proc.rb +117 -0
- data/lib/steep/ast/types/record.rb +79 -0
- data/lib/steep/ast/types/self.rb +43 -0
- data/lib/steep/ast/types/shared_instance.rb +11 -0
- data/lib/steep/ast/types/top.rb +35 -0
- data/lib/steep/ast/types/tuple.rb +60 -0
- data/lib/steep/ast/types/union.rb +97 -0
- data/lib/steep/ast/types/var.rb +65 -0
- data/lib/steep/ast/types/void.rb +35 -0
- data/lib/steep/cli.rb +401 -0
- data/lib/steep/diagnostic/deprecated/else_on_exhaustive_case.rb +20 -0
- data/lib/steep/diagnostic/deprecated/unknown_constant_assigned.rb +28 -0
- data/lib/steep/diagnostic/helper.rb +18 -0
- data/lib/steep/diagnostic/lsp_formatter.rb +78 -0
- data/lib/steep/diagnostic/result_printer2.rb +48 -0
- data/lib/steep/diagnostic/ruby.rb +1221 -0
- data/lib/steep/diagnostic/signature.rb +570 -0
- data/lib/steep/drivers/annotations.rb +52 -0
- data/lib/steep/drivers/check.rb +339 -0
- data/lib/steep/drivers/checkfile.rb +210 -0
- data/lib/steep/drivers/diagnostic_printer.rb +105 -0
- data/lib/steep/drivers/init.rb +66 -0
- data/lib/steep/drivers/langserver.rb +56 -0
- data/lib/steep/drivers/print_project.rb +113 -0
- data/lib/steep/drivers/stats.rb +203 -0
- data/lib/steep/drivers/utils/driver_helper.rb +143 -0
- data/lib/steep/drivers/utils/jobs_option.rb +26 -0
- data/lib/steep/drivers/vendor.rb +27 -0
- data/lib/steep/drivers/watch.rb +194 -0
- data/lib/steep/drivers/worker.rb +58 -0
- data/lib/steep/equatable.rb +23 -0
- data/lib/steep/expectations.rb +228 -0
- data/lib/steep/index/rbs_index.rb +350 -0
- data/lib/steep/index/signature_symbol_provider.rb +185 -0
- data/lib/steep/index/source_index.rb +167 -0
- data/lib/steep/interface/block.rb +103 -0
- data/lib/steep/interface/builder.rb +843 -0
- data/lib/steep/interface/function.rb +1090 -0
- data/lib/steep/interface/method_type.rb +330 -0
- data/lib/steep/interface/shape.rb +239 -0
- data/lib/steep/interface/substitution.rb +159 -0
- data/lib/steep/interface/type_param.rb +115 -0
- data/lib/steep/located_value.rb +20 -0
- data/lib/steep/method_name.rb +42 -0
- data/lib/steep/module_helper.rb +24 -0
- data/lib/steep/node_helper.rb +273 -0
- data/lib/steep/path_helper.rb +30 -0
- data/lib/steep/project/dsl.rb +268 -0
- data/lib/steep/project/group.rb +31 -0
- data/lib/steep/project/options.rb +63 -0
- data/lib/steep/project/pattern.rb +59 -0
- data/lib/steep/project/target.rb +92 -0
- data/lib/steep/project.rb +78 -0
- data/lib/steep/rake_task.rb +132 -0
- data/lib/steep/range_extension.rb +29 -0
- data/lib/steep/server/base_worker.rb +97 -0
- data/lib/steep/server/change_buffer.rb +73 -0
- data/lib/steep/server/custom_methods.rb +77 -0
- data/lib/steep/server/delay_queue.rb +45 -0
- data/lib/steep/server/interaction_worker.rb +492 -0
- data/lib/steep/server/lsp_formatter.rb +455 -0
- data/lib/steep/server/master.rb +922 -0
- data/lib/steep/server/target_group_files.rb +205 -0
- data/lib/steep/server/type_check_controller.rb +366 -0
- data/lib/steep/server/type_check_worker.rb +303 -0
- data/lib/steep/server/work_done_progress.rb +64 -0
- data/lib/steep/server/worker_process.rb +176 -0
- data/lib/steep/services/completion_provider.rb +802 -0
- data/lib/steep/services/content_change.rb +61 -0
- data/lib/steep/services/file_loader.rb +74 -0
- data/lib/steep/services/goto_service.rb +441 -0
- data/lib/steep/services/hover_provider/rbs.rb +88 -0
- data/lib/steep/services/hover_provider/ruby.rb +221 -0
- data/lib/steep/services/hover_provider/singleton_methods.rb +20 -0
- data/lib/steep/services/path_assignment.rb +46 -0
- data/lib/steep/services/signature_help_provider.rb +202 -0
- data/lib/steep/services/signature_service.rb +428 -0
- data/lib/steep/services/stats_calculator.rb +68 -0
- data/lib/steep/services/type_check_service.rb +394 -0
- data/lib/steep/services/type_name_completion.rb +236 -0
- data/lib/steep/signature/validator.rb +651 -0
- data/lib/steep/source/ignore_ranges.rb +69 -0
- data/lib/steep/source.rb +691 -0
- data/lib/steep/subtyping/cache.rb +30 -0
- data/lib/steep/subtyping/check.rb +1113 -0
- data/lib/steep/subtyping/constraints.rb +341 -0
- data/lib/steep/subtyping/relation.rb +101 -0
- data/lib/steep/subtyping/result.rb +324 -0
- data/lib/steep/subtyping/variable_variance.rb +89 -0
- data/lib/steep/test.rb +9 -0
- data/lib/steep/thread_waiter.rb +43 -0
- data/lib/steep/type_construction.rb +5183 -0
- data/lib/steep/type_inference/block_params.rb +416 -0
- data/lib/steep/type_inference/case_when.rb +303 -0
- data/lib/steep/type_inference/constant_env.rb +56 -0
- data/lib/steep/type_inference/context.rb +195 -0
- data/lib/steep/type_inference/logic_type_interpreter.rb +613 -0
- data/lib/steep/type_inference/method_call.rb +193 -0
- data/lib/steep/type_inference/method_params.rb +531 -0
- data/lib/steep/type_inference/multiple_assignment.rb +194 -0
- data/lib/steep/type_inference/send_args.rb +712 -0
- data/lib/steep/type_inference/type_env.rb +341 -0
- data/lib/steep/type_inference/type_env_builder.rb +138 -0
- data/lib/steep/typing.rb +321 -0
- data/lib/steep/version.rb +3 -0
- data/lib/steep.rb +369 -0
- data/manual/annotations.md +181 -0
- data/manual/ignore.md +20 -0
- data/manual/ruby-diagnostics.md +1879 -0
- data/sample/Steepfile +22 -0
- data/sample/lib/conference.rb +49 -0
- data/sample/lib/length.rb +35 -0
- data/sample/sig/conference.rbs +42 -0
- data/sample/sig/generics.rbs +15 -0
- data/sample/sig/length.rbs +34 -0
- data/steep-relaxed.gemspec +56 -0
- 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
|