steep 0.15.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 498c3c95c9c03e8ef047c774aff47158d8681e2ae485d5b0449a74fae33cd06b
4
- data.tar.gz: 64e9572dc4ec5b5fea6035c1a1fbb7241a758f76c5d328d2ac0011c37e176044
3
+ metadata.gz: b4dda8122216dc9af384ceba2c84f7cb1076df5be7060d2aa32dbaff32140ed6
4
+ data.tar.gz: d4ce7351acc02b06cd608a10639c00bc8bb1bed8ca81596bb1ed718112392a68
5
5
  SHA512:
6
- metadata.gz: acef7a13109ecd0660d72dff4559b4432f96c62f79aa4dcb762722f9a96678bb6f68f0bd288017381c6ebef65f9648a24f2b349d2af28f1a6f0c66560d56aee6
7
- data.tar.gz: 9d31c49a37770c7493cefcde0f608ee6b6c94bd5fa1935c3ce963bae70b3e526b678000ae1b2b9f59d94a775c5103cbabf7effd480262725db476f70e6124dcf
6
+ metadata.gz: 0bc09bf14d4fbfe8cea15d8f939efd2f9b75672a25fdfbbd19011461001786d15e7d84fadfa37cf67a0303f742c6b2a718855095603119276d1154c4b97cc673
7
+ data.tar.gz: 7d3d1d4f8acb18de067228fe38d52a7cb62dbd5049555fa748d96226c6ce1906fc49e034f3e19ae346c215ee89a715e04e0138b985cbedcc89b922499adc0a08
data/.gitmodules CHANGED
@@ -1,3 +1,3 @@
1
1
  [submodule "vendor/ruby-signature"]
2
2
  path = vendor/ruby-signature
3
- url = https://github.com/ruby/ruby-signature.git
3
+ url = https://github.com/ruby/rbs.git
data/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.16.0 (2020-05-19)
6
+
7
+ * Spawn workers for type check performance ([#137](https://github.com/soutaro/steep/pull/137))
8
+ * Fix `check` and `signature` methods in Steepfile ([8f3e4c7](https://github.com/soutaro/steep/pull/137/commits/8f3e4c75b29ac26920f02294be06d6c68dbd4dca))
9
+
5
10
  ## 0.15.0 (2020-05-05)
6
11
 
7
12
  * Add type checking configuration to dsl ([#132](https://github.com/soutaro/steep/pull/132))
data/lib/steep/cli.rb CHANGED
@@ -34,7 +34,7 @@ module Steep
34
34
 
35
35
  def setup_command
36
36
  @command = argv.shift&.to_sym
37
- if CLI.available_commands.include?(@command)
37
+ if CLI.available_commands.include?(@command) || @command == :worker
38
38
  true
39
39
  else
40
40
  stderr.puts "Unknown command: #{command}"
@@ -159,5 +159,20 @@ module Steep
159
159
  stdout.puts Steep::VERSION
160
160
  0
161
161
  end
162
+
163
+ def process_worker
164
+ Drivers::Worker.new(stdout: stdout, stderr: stderr, stdin: stdin).tap do |command|
165
+ OptionParser.new do |opts|
166
+ opts.banner = "Usage: steep worker [options] [dir]"
167
+ handle_logging_options opts
168
+
169
+ opts.on("--interaction") { command.worker_type = :interaction }
170
+ opts.on("--code") { command.worker_type = :code }
171
+ opts.on("--signature") { command.worker_type = :signature }
172
+ opts.on("--steepfile=PATH") {|path| command.steepfile = Pathname(path) }
173
+ opts.on("--name=NAME") {|name| command.worker_name = name }
174
+ end.parse!(argv)
175
+ end.run
176
+ end
162
177
  end
163
178
  end
@@ -25,7 +25,7 @@ module Steep
25
25
 
26
26
  project.targets.each do |target|
27
27
  Steep.logger.tagged "target=#{target.name}" do
28
- target.load_signatures do |_, subtyping, _|
28
+ target.load_signatures(validate: false) do |_, subtyping, _|
29
29
  case (status = target.status)
30
30
  when nil # status set on error cases
31
31
  target.source_files.each_value do |file|
@@ -33,11 +33,6 @@ module Steep
33
33
  @project or raise "Empty #project"
34
34
  end
35
35
 
36
- def enqueue_type_check(version)
37
- @latest_update_version = version
38
- type_check_queue << TypeCheckRequest.new(version: version)
39
- end
40
-
41
36
  def run
42
37
  @project = load_config()
43
38
 
@@ -45,466 +40,22 @@ module Steep
45
40
  loader.load_sources([])
46
41
  loader.load_signatures()
47
42
 
48
- start_type_check()
49
-
50
- reader.read do |request|
51
- Steep.logger.tagged "lsp" do
52
- Steep.logger.debug { "Received a request: request=#{request.to_json}" }
53
- handle_request(request) do |id, result|
54
- if id
55
- write_mutex.synchronize do
56
- Steep.logger.debug { "Writing response to #{id}: #{result.to_json}" }
57
- writer.write(id: id, result: result)
58
- end
59
- end
60
- end
61
- end
62
- end
63
-
64
- 0
65
- end
66
-
67
- def write(method:, params:)
68
- write_mutex.synchronize do
69
- Steep.logger.debug { "Sending request: method=#{method}, params=#{params.to_json}"}
70
- writer.write(method: method, params: params)
71
- end
72
- end
73
-
74
- def handle_request(request)
75
- id = request[:id]
76
- method = request[:method].to_sym
77
-
78
- Steep.logger.tagged "id=#{id}, method=#{method}" do
79
- case method
80
- when :initialize
81
- yield id, LanguageServer::Protocol::Interface::InitializeResult.new(
82
- capabilities: LanguageServer::Protocol::Interface::ServerCapabilities.new(
83
- text_document_sync: LanguageServer::Protocol::Interface::TextDocumentSyncOptions.new(
84
- change: LanguageServer::Protocol::Constant::TextDocumentSyncKind::FULL
85
- ),
86
- hover_provider: true,
87
- completion_provider: LanguageServer::Protocol::Interface::CompletionOptions.new(
88
- trigger_characters: [".", "@"],
89
- )
90
- )
91
- )
92
-
93
- enqueue_type_check nil
94
-
95
- when :"textDocument/completion"
96
- Steep.logger.error request.inspect
97
- begin
98
- params = request[:params]
99
- uri = URI.parse(params[:textDocument][:uri])
100
- path = project.relative_path(Pathname(uri.path))
101
- target = project.targets.find {|target| target.source_file?(path) }
102
- case (status = target&.status)
103
- when Project::Target::TypeCheckStatus
104
- subtyping = status.subtyping
105
- source = target.source_files[path]
106
-
107
- line, column = params[:position].yield_self {|hash| [hash[:line]+1, hash[:character]] }
108
- trigger = params[:context][:triggerCharacter]
109
-
110
- Steep.logger.error "line: #{line}, column: #{column}, trigger: #{trigger}"
111
-
112
- provider = Project::CompletionProvider.new(source_text: source.content, path: path, subtyping: subtyping)
113
- items = begin
114
- provider.run(line: line, column: column)
115
- rescue Parser::SyntaxError
116
- []
117
- end
118
-
119
- completion_items = items.map do |item|
120
- format_completion_item(item)
121
- end
122
-
123
- Steep.logger.debug "items = #{completion_items.inspect}"
124
-
125
- yield id, LanguageServer::Protocol::Interface::CompletionList.new(
126
- is_incomplete: false,
127
- items: completion_items
128
- )
129
- end
130
-
131
- rescue Typing::UnknownNodeError => exn
132
- Steep.log_error exn, message: "Failed to compute completion: #{exn.inspect}"
133
- yield id, nil
134
- end
135
-
136
- when :"textDocument/didChange"
137
- uri = URI.parse(request[:params][:textDocument][:uri])
138
- path = project.relative_path(Pathname(uri.path))
139
- text = request[:params][:contentChanges][0][:text]
140
-
141
- Steep.logger.debug { "path=#{path}, content=#{text.lines.first&.chomp}..." }
142
-
143
- project.targets.each do |target|
144
- Steep.logger.tagged "target=#{target.name}" do
145
- case
146
- when target.source_file?(path)
147
- if text.empty? && !path.file?
148
- Steep.logger.info { "Deleting source file: #{path}..." }
149
- target.remove_source(path)
150
- report_diagnostics path, []
151
- else
152
- Steep.logger.info { "Updating source file: #{path}..." }
153
- target.update_source(path, text)
154
- end
155
- when target.possible_source_file?(path)
156
- Steep.logger.info { "Adding source file: #{path}..." }
157
- target.add_source(path, text)
158
- when target.signature_file?(path)
159
- if text.empty? && !path.file?
160
- Steep.logger.info { "Deleting signature file: #{path}..." }
161
- target.remove_signature(path)
162
- report_diagnostics path, []
163
- else
164
- Steep.logger.info { "Updating signature file: #{path}..." }
165
- target.update_signature(path, text)
166
- end
167
- when target.possible_signature_file?(path)
168
- Steep.logger.info { "Adding signature file: #{path}..." }
169
- target.add_signature(path, text)
170
- end
171
- end
172
- end
173
-
174
- version = request[:params][:textDocument][:version]
175
- enqueue_type_check version
176
- when :"textDocument/hover"
177
- uri = URI.parse(request[:params][:textDocument][:uri])
178
- path = project.relative_path(Pathname(uri.path))
179
- line = request[:params][:position][:line]
180
- column = request[:params][:position][:character]
181
-
182
- yield id, response_to_hover(path: path, line: line, column: column)
183
-
184
- when :shutdown
185
- yield id, nil
186
-
187
- when :exit
188
- type_check_queue << nil
189
- type_check_thread.join
190
- exit
191
- end
192
- end
193
- end
194
-
195
- def start_type_check
196
- @type_check_thread = Thread.start do
197
- while request = type_check_queue.deq
198
- if @latest_update_version == nil || @latest_update_version == request.version
199
- begin
200
- run_type_check()
201
- rescue => exn
202
- Steep.log_error exn
203
- end
204
- end
205
- end
206
- end
207
- end
208
-
209
- def run_type_check()
210
- Steep.logger.tagged "#run_type_check" do
211
- Steep.logger.info { "Running type check..." }
212
- type_check project
213
-
214
- Steep.logger.info { "Sending diagnostics..." }
215
- project.targets.each do |target|
216
- Steep.logger.tagged "target=#{target.name}, status=#{target.status.class}" do
217
- Steep.logger.info { "Clearing signature diagnostics..." }
218
- target.signature_files.each_value do |file|
219
- report_diagnostics file.path, []
220
- end
221
-
222
- case (status = target.status)
223
- when Project::Target::SignatureValidationErrorStatus
224
- Steep.logger.info { "Signature validation error" }
225
- status.errors.group_by(&:path).each do |path, errors|
226
- diagnostics = errors.map {|error| diagnostic_for_validation_error(error) }
227
- report_diagnostics path, diagnostics
228
- end
229
- when Project::Target::TypeCheckStatus
230
- Steep.logger.info { "Type check" }
231
- status.type_check_sources.each do |source|
232
- diagnostics = case source.status
233
- when Project::SourceFile::TypeCheckStatus
234
- source.errors.map {|error| diagnostic_for_type_error(error) }
235
- when Project::SourceFile::AnnotationSyntaxErrorStatus
236
- [diagnostics_raw(source.status.error.message, source.status.location)]
237
- when Project::SourceFile::ParseErrorStatus
238
- []
239
- when Project::SourceFile::TypeCheckErrorStatus
240
- Steep.log_error source.status.error
241
- []
242
- end
243
-
244
- if diagnostics
245
- report_diagnostics source.path, diagnostics
246
- end
247
- end
248
- when Project::Target::SignatureSyntaxErrorStatus
249
- Steep.logger.info { "Signature syntax error" }
250
- end
251
- end
252
- end
253
- end
254
- end
255
-
256
- def report_diagnostics(path, diagnostics)
257
- Steep.logger.info { "Reporting #{diagnostics.size} diagnostics for #{path}..." }
258
- write(
259
- method: :"textDocument/publishDiagnostics",
260
- params: LanguageServer::Protocol::Interface::PublishDiagnosticsParams.new(
261
- uri: URI.parse(project.absolute_path(path).to_s).tap {|uri| uri.scheme = "file"},
262
- diagnostics: diagnostics,
263
- )
264
- )
265
- end
266
-
267
- def diagnostic_for_validation_error(error)
268
- LanguageServer::Protocol::Interface::Diagnostic.new(
269
- message: StringIO.new("").tap {|io| error.puts(io) }.string,
270
- severity: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
271
- range: LanguageServer::Protocol::Interface::Range.new(
272
- start: LanguageServer::Protocol::Interface::Position.new(
273
- line: error.location.start_line - 1,
274
- character: error.location.start_column,
275
- ),
276
- end: LanguageServer::Protocol::Interface::Position.new(
277
- line: error.location.end_line - 1,
278
- character: error.location.end_column,
279
- ),
280
- )
281
- )
282
- end
283
-
284
- def diagnostics_raw(message, loc)
285
- LanguageServer::Protocol::Interface::Diagnostic.new(
286
- message: message,
287
- severity: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
288
- range: LanguageServer::Protocol::Interface::Range.new(
289
- start: LanguageServer::Protocol::Interface::Position.new(
290
- line: loc.start_line - 1,
291
- character: loc.start_column,
292
- ),
293
- end: LanguageServer::Protocol::Interface::Position.new(
294
- line: loc.end_line - 1,
295
- character: loc.end_column,
296
- ),
297
- )
298
- )
299
- end
300
-
301
- def diagnostic_for_type_error(error)
302
- LanguageServer::Protocol::Interface::Diagnostic.new(
303
- message: error.to_s,
304
- severity: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
305
- range: LanguageServer::Protocol::Interface::Range.new(
306
- start: LanguageServer::Protocol::Interface::Position.new(
307
- line: error.node.loc.line - 1,
308
- character: error.node.loc.column,
309
- ),
310
- end: LanguageServer::Protocol::Interface::Position.new(
311
- line: error.node.loc.last_line - 1,
312
- character: error.node.loc.last_column,
313
- ),
314
- )
43
+ interaction_worker = Server::WorkerProcess.spawn_worker(:interaction, name: "interaction", steepfile: project.steepfile_path)
44
+ signature_worker = Server::WorkerProcess.spawn_worker(:signature, name: "signature", steepfile: project.steepfile_path)
45
+ code_workers = Server::WorkerProcess.spawn_code_workers(steepfile: project.steepfile_path)
46
+
47
+ master = Server::Master.new(
48
+ project: project,
49
+ reader: reader,
50
+ writer: writer,
51
+ interaction_worker: interaction_worker,
52
+ signature_worker: signature_worker,
53
+ code_workers: code_workers
315
54
  )
316
- end
317
-
318
- def response_to_hover(path:, line:, column:)
319
- Steep.logger.info { "path=#{path}, line=#{line}, column=#{column}" }
320
-
321
- hover = Project::HoverContent.new(project: project)
322
- content = hover.content_for(path: path, line: line+1, column: column+1)
323
- if content
324
- range = content.location.yield_self do |location|
325
- start_position = { line: location.line - 1, character: location.column }
326
- end_position = { line: location.last_line - 1, character: location.last_column }
327
- { start: start_position, end: end_position }
328
- end
329
55
 
330
- LanguageServer::Protocol::Interface::Hover.new(
331
- contents: { kind: "markdown", value: format_hover(content) },
332
- range: range
333
- )
334
- end
335
- rescue Typing::UnknownNodeError => exn
336
- Steep.log_error exn, message: "Failed to compute hover: #{exn.inspect}"
337
- nil
338
- end
339
-
340
- def format_hover(content)
341
- case content
342
- when Project::HoverContent::VariableContent
343
- "`#{content.name}`: `#{content.type.to_s}`"
344
- when Project::HoverContent::MethodCallContent
345
- method_name = case content.method_name
346
- when Project::HoverContent::InstanceMethodName
347
- "#{content.method_name.class_name}##{content.method_name.method_name}"
348
- when Project::HoverContent::SingletonMethodName
349
- "#{content.method_name.class_name}.#{content.method_name.method_name}"
350
- else
351
- nil
352
- end
353
-
354
- if method_name
355
- string = <<HOVER
356
- ```
357
- #{method_name} ~> #{content.type}
358
- ```
359
- HOVER
360
- if content.definition
361
- if content.definition.comment
362
- string << "\n----\n\n#{content.definition.comment.string}"
363
- end
364
-
365
- string << "\n----\n\n#{content.definition.method_types.map {|x| "- `#{x}`\n" }.join()}"
366
- end
367
- else
368
- "`#{content.type}`"
369
- end
370
- when Project::HoverContent::DefinitionContent
371
- string = <<HOVER
372
- ```
373
- def #{content.method_name}: #{content.method_type}
374
- ```
375
- HOVER
376
- if (comment = content.definition.comment)
377
- string << "\n----\n\n#{comment.string}\n"
378
- end
379
-
380
- if content.definition.method_types.size > 1
381
- string << "\n----\n\n#{content.definition.method_types.map {|x| "- `#{x}`\n" }.join()}"
382
- end
383
-
384
- string
385
- when Project::HoverContent::TypeContent
386
- "`#{content.type}`"
387
- end
388
- end
389
-
390
- def format_completion_item(item)
391
- range = LanguageServer::Protocol::Interface::Range.new(
392
- start: LanguageServer::Protocol::Interface::Position.new(
393
- line: item.range.start.line-1,
394
- character: item.range.start.column
395
- ),
396
- end: LanguageServer::Protocol::Interface::Position.new(
397
- line: item.range.end.line-1,
398
- character: item.range.end.column
399
- )
400
- )
56
+ master.start()
401
57
 
402
- case item
403
- when Project::CompletionProvider::LocalVariableItem
404
- LanguageServer::Protocol::Interface::CompletionItem.new(
405
- label: item.identifier,
406
- kind: LanguageServer::Protocol::Constant::CompletionItemKind::VARIABLE,
407
- detail: "#{item.identifier}: #{item.type}",
408
- text_edit: LanguageServer::Protocol::Interface::TextEdit.new(
409
- range: range,
410
- new_text: "#{item.identifier}"
411
- )
412
- )
413
- when Project::CompletionProvider::MethodNameItem
414
- label = "def #{item.identifier}: #{item.method_type}"
415
- method_type_snippet = method_type_to_snippet(item.method_type)
416
- LanguageServer::Protocol::Interface::CompletionItem.new(
417
- label: label,
418
- kind: LanguageServer::Protocol::Constant::CompletionItemKind::METHOD,
419
- text_edit: LanguageServer::Protocol::Interface::TextEdit.new(
420
- new_text: "#{item.identifier}#{method_type_snippet}",
421
- range: range
422
- ),
423
- documentation: item.definition.comment&.string,
424
- insert_text_format: LanguageServer::Protocol::Constant::InsertTextFormat::SNIPPET
425
- )
426
- when Project::CompletionProvider::InstanceVariableItem
427
- label = "#{item.identifier}: #{item.type}"
428
- LanguageServer::Protocol::Interface::CompletionItem.new(
429
- label: label,
430
- kind: LanguageServer::Protocol::Constant::CompletionItemKind::FIELD,
431
- text_edit: LanguageServer::Protocol::Interface::TextEdit.new(
432
- range: range,
433
- new_text: item.identifier,
434
- ),
435
- insert_text_format: LanguageServer::Protocol::Constant::InsertTextFormat::SNIPPET
436
- )
437
- end
438
- end
439
-
440
- def method_type_to_snippet(method_type)
441
- params = if method_type.type.each_param.count == 0
442
- ""
443
- else
444
- "(#{params_to_snippet(method_type.type)})"
445
- end
446
-
447
-
448
- block = if method_type.block
449
- open, space, close = if method_type.block.type.return_type.is_a?(RBS::Types::Bases::Void)
450
- ["do", " ", "end"]
451
- else
452
- ["{", "", "}"]
453
- end
454
-
455
- if method_type.block.type.each_param.count == 0
456
- " #{open} $0 #{close}"
457
- else
458
- " #{open}#{space}|#{params_to_snippet(method_type.block.type)}| $0 #{close}"
459
- end
460
- else
461
- ""
462
- end
463
-
464
- "#{params}#{block}"
465
- end
466
-
467
- def params_to_snippet(fun)
468
- params = []
469
-
470
- index = 1
471
-
472
- fun.required_positionals.each do |param|
473
- if name = param.name
474
- params << "${#{index}:#{param.type}}"
475
- else
476
- params << "${#{index}:#{param.type}}"
477
- end
478
-
479
- index += 1
480
- end
481
-
482
- if fun.rest_positionals
483
- params << "${#{index}:*#{fun.rest_positionals.type}}"
484
- index += 1
485
- end
486
-
487
- fun.trailing_positionals.each do |param|
488
- if name = param.name
489
- params << "${#{index}:#{param.type}}"
490
- else
491
- params << "${#{index}:#{param.type}}"
492
- end
493
-
494
- index += 1
495
- end
496
-
497
- fun.required_keywords.each do |keyword, param|
498
- if name = param.name
499
- params << "#{keyword}: ${#{index}:#{name}_}"
500
- else
501
- params << "#{keyword}: ${#{index}:#{param.type}_}"
502
- end
503
-
504
- index += 1
505
- end
506
-
507
- params.join(", ")
58
+ 0
508
59
  end
509
60
  end
510
61
  end
@@ -8,7 +8,7 @@ module Steep
8
8
  raise "Cannot find a configuration at #{path}: `steep init` to scaffold" unless path.file?
9
9
 
10
10
  steep_file_path = path.absolute? ? path : Pathname.pwd + path
11
- Project.new(base_dir: steep_file_path.parent).tap do |project|
11
+ Project.new(steepfile_path: steep_file_path).tap do |project|
12
12
  Project::DSL.parse(project, path.read, filename: path.to_s)
13
13
  end
14
14
  end