steep 0.40.0 → 0.44.0

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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/Gemfile +1 -0
  4. data/bin/output_rebaseline.rb +15 -30
  5. data/bin/output_test.rb +23 -57
  6. data/lib/steep.rb +89 -15
  7. data/lib/steep/annotation_parser.rb +10 -2
  8. data/lib/steep/ast/types/class.rb +4 -0
  9. data/lib/steep/cli.rb +31 -6
  10. data/lib/steep/diagnostic/ruby.rb +13 -8
  11. data/lib/steep/diagnostic/signature.rb +152 -2
  12. data/lib/steep/drivers/annotations.rb +18 -36
  13. data/lib/steep/drivers/check.rb +140 -31
  14. data/lib/steep/drivers/diagnostic_printer.rb +20 -11
  15. data/lib/steep/drivers/langserver.rb +4 -8
  16. data/lib/steep/drivers/print_project.rb +10 -9
  17. data/lib/steep/drivers/stats.rb +135 -119
  18. data/lib/steep/drivers/utils/driver_helper.rb +35 -0
  19. data/lib/steep/drivers/utils/jobs_count.rb +9 -0
  20. data/lib/steep/drivers/validate.rb +29 -18
  21. data/lib/steep/drivers/watch.rb +55 -49
  22. data/lib/steep/drivers/worker.rb +11 -8
  23. data/lib/steep/expectations.rb +159 -0
  24. data/lib/steep/index/signature_symbol_provider.rb +23 -1
  25. data/lib/steep/index/source_index.rb +55 -5
  26. data/lib/steep/interface/block.rb +4 -0
  27. data/lib/steep/project.rb +0 -30
  28. data/lib/steep/project/dsl.rb +5 -3
  29. data/lib/steep/project/pattern.rb +56 -0
  30. data/lib/steep/project/target.rb +11 -227
  31. data/lib/steep/server/base_worker.rb +1 -3
  32. data/lib/steep/server/change_buffer.rb +63 -0
  33. data/lib/steep/server/interaction_worker.rb +72 -57
  34. data/lib/steep/server/master.rb +652 -234
  35. data/lib/steep/server/type_check_worker.rb +304 -0
  36. data/lib/steep/server/worker_process.rb +16 -11
  37. data/lib/steep/{project → services}/completion_provider.rb +5 -5
  38. data/lib/steep/services/content_change.rb +61 -0
  39. data/lib/steep/services/file_loader.rb +48 -0
  40. data/lib/steep/services/goto_service.rb +321 -0
  41. data/lib/steep/{project → services}/hover_content.rb +19 -20
  42. data/lib/steep/services/path_assignment.rb +27 -0
  43. data/lib/steep/services/signature_service.rb +403 -0
  44. data/lib/steep/services/stats_calculator.rb +69 -0
  45. data/lib/steep/services/type_check_service.rb +413 -0
  46. data/lib/steep/signature/validator.rb +187 -85
  47. data/lib/steep/source.rb +21 -18
  48. data/lib/steep/subtyping/check.rb +246 -45
  49. data/lib/steep/subtyping/constraints.rb +4 -4
  50. data/lib/steep/type_construction.rb +428 -193
  51. data/lib/steep/type_inference/block_params.rb +1 -1
  52. data/lib/steep/type_inference/context.rb +22 -0
  53. data/lib/steep/type_inference/local_variable_type_env.rb +26 -12
  54. data/lib/steep/type_inference/logic.rb +1 -1
  55. data/lib/steep/type_inference/logic_type_interpreter.rb +4 -4
  56. data/lib/steep/type_inference/type_env.rb +43 -17
  57. data/lib/steep/version.rb +1 -1
  58. data/smoke/alias/test_expectations.yml +96 -0
  59. data/smoke/and/test_expectations.yml +31 -0
  60. data/smoke/array/test_expectations.yml +103 -0
  61. data/smoke/block/test_expectations.yml +125 -0
  62. data/smoke/case/test_expectations.yml +47 -0
  63. data/smoke/class/test_expectations.yml +120 -0
  64. data/smoke/const/test_expectations.yml +129 -0
  65. data/smoke/diagnostics-rbs-duplicated/test_expectations.yml +13 -0
  66. data/smoke/diagnostics-rbs/Steepfile +7 -4
  67. data/smoke/diagnostics-rbs/test_expectations.yml +231 -0
  68. data/smoke/diagnostics-rbs/unknown-type-name-2.rbs +5 -0
  69. data/smoke/{broken → diagnostics-ruby-unsat}/Steepfile +0 -0
  70. data/smoke/diagnostics-ruby-unsat/a.rbs +3 -0
  71. data/smoke/diagnostics-ruby-unsat/test_expectations.yml +27 -0
  72. data/smoke/{diagnostics → diagnostics-ruby-unsat}/unsatisfiable_constraint.rb +0 -1
  73. data/smoke/diagnostics/a.rbs +0 -4
  74. data/smoke/diagnostics/test_expectations.yml +451 -0
  75. data/smoke/dstr/test_expectations.yml +13 -0
  76. data/smoke/ensure/test_expectations.yml +62 -0
  77. data/smoke/enumerator/test_expectations.yml +135 -0
  78. data/smoke/extension/f.rb +2 -0
  79. data/smoke/extension/f.rbs +3 -0
  80. data/smoke/extension/test_expectations.yml +73 -0
  81. data/smoke/hash/test_expectations.yml +81 -0
  82. data/smoke/hello/test_expectations.yml +25 -0
  83. data/smoke/if/test_expectations.yml +34 -0
  84. data/smoke/implements/b.rb +13 -0
  85. data/smoke/implements/b.rbs +12 -0
  86. data/smoke/implements/test_expectations.yml +23 -0
  87. data/smoke/initialize/test_expectations.yml +1 -0
  88. data/smoke/integer/test_expectations.yml +101 -0
  89. data/smoke/interface/test_expectations.yml +23 -0
  90. data/smoke/kwbegin/test_expectations.yml +17 -0
  91. data/smoke/lambda/test_expectations.yml +39 -0
  92. data/smoke/literal/test_expectations.yml +106 -0
  93. data/smoke/map/test_expectations.yml +1 -0
  94. data/smoke/method/test_expectations.yml +90 -0
  95. data/smoke/module/test_expectations.yml +75 -0
  96. data/smoke/regexp/test_expectations.yml +615 -0
  97. data/smoke/regression/issue_328.rb +1 -0
  98. data/smoke/regression/issue_328.rbs +0 -0
  99. data/smoke/regression/issue_332.rb +11 -0
  100. data/smoke/regression/issue_332.rbs +19 -0
  101. data/smoke/regression/issue_372.rb +8 -0
  102. data/smoke/regression/issue_372.rbs +4 -0
  103. data/smoke/regression/masgn.rb +4 -0
  104. data/smoke/regression/test_expectations.yml +60 -0
  105. data/smoke/regression/thread.rb +7 -0
  106. data/smoke/rescue/test_expectations.yml +79 -0
  107. data/smoke/self/test_expectations.yml +23 -0
  108. data/smoke/skip/test_expectations.yml +23 -0
  109. data/smoke/stdout/test_expectations.yml +1 -0
  110. data/smoke/super/test_expectations.yml +69 -0
  111. data/smoke/toplevel/test_expectations.yml +15 -0
  112. data/smoke/tsort/Steepfile +2 -0
  113. data/smoke/tsort/test_expectations.yml +63 -0
  114. data/smoke/type_case/test_expectations.yml +48 -0
  115. data/smoke/unexpected/Steepfile +5 -0
  116. data/smoke/unexpected/test_expectations.yml +25 -0
  117. data/smoke/unexpected/unexpected.rb +1 -0
  118. data/smoke/unexpected/unexpected.rbs +3 -0
  119. data/smoke/yield/test_expectations.yml +68 -0
  120. data/steep.gemspec +4 -3
  121. metadata +127 -80
  122. data/lib/steep/project/file_loader.rb +0 -68
  123. data/lib/steep/project/signature_file.rb +0 -39
  124. data/lib/steep/project/source_file.rb +0 -129
  125. data/lib/steep/project/stats_calculator.rb +0 -80
  126. data/lib/steep/server/code_worker.rb +0 -150
  127. data/lib/steep/server/signature_worker.rb +0 -157
  128. data/lib/steep/server/utils.rb +0 -69
  129. data/smoke/alias/test.yaml +0 -73
  130. data/smoke/and/test.yaml +0 -24
  131. data/smoke/array/test.yaml +0 -80
  132. data/smoke/block/test.yaml +0 -96
  133. data/smoke/broken/broken.rb +0 -0
  134. data/smoke/broken/broken.rbs +0 -0
  135. data/smoke/broken/test.yaml +0 -6
  136. data/smoke/case/test.yaml +0 -36
  137. data/smoke/class/test.yaml +0 -89
  138. data/smoke/const/test.yaml +0 -96
  139. data/smoke/diagnostics-rbs-duplicated/test.yaml +0 -10
  140. data/smoke/diagnostics-rbs/test.yaml +0 -142
  141. data/smoke/diagnostics/test.yaml +0 -333
  142. data/smoke/dstr/test.yaml +0 -10
  143. data/smoke/ensure/test.yaml +0 -47
  144. data/smoke/enumerator/test.yaml +0 -100
  145. data/smoke/extension/test.yaml +0 -50
  146. data/smoke/hash/test.yaml +0 -62
  147. data/smoke/hello/test.yaml +0 -18
  148. data/smoke/if/test.yaml +0 -27
  149. data/smoke/implements/test.yaml +0 -16
  150. data/smoke/initialize/test.yaml +0 -4
  151. data/smoke/integer/test.yaml +0 -66
  152. data/smoke/interface/test.yaml +0 -16
  153. data/smoke/kwbegin/test.yaml +0 -14
  154. data/smoke/lambda/test.yaml +0 -28
  155. data/smoke/literal/test.yaml +0 -79
  156. data/smoke/map/test.yaml +0 -4
  157. data/smoke/method/test.yaml +0 -71
  158. data/smoke/module/test.yaml +0 -51
  159. data/smoke/regexp/test.yaml +0 -372
  160. data/smoke/regression/test.yaml +0 -38
  161. data/smoke/rescue/test.yaml +0 -60
  162. data/smoke/self/test.yaml +0 -16
  163. data/smoke/skip/test.yaml +0 -16
  164. data/smoke/stdout/test.yaml +0 -4
  165. data/smoke/super/test.yaml +0 -52
  166. data/smoke/toplevel/test.yaml +0 -12
  167. data/smoke/tsort/test.yaml +0 -32
  168. data/smoke/type_case/test.yaml +0 -33
  169. data/smoke/yield/test.yaml +0 -49
@@ -220,10 +220,6 @@ module Steep
220
220
  def header_line
221
221
  "The method cannot be called with a block"
222
222
  end
223
-
224
- def to_s
225
- format_message "method_type=#{method_type}"
226
- end
227
223
  end
228
224
 
229
225
  class RequiredBlockMissing < Base
@@ -395,8 +391,8 @@ module Steep
395
391
  @method = method
396
392
  end
397
393
 
398
- def to_s
399
- format_message "method=#{method}"
394
+ def header_line
395
+ "No superclass method `#{method}` defined"
400
396
  end
401
397
  end
402
398
 
@@ -613,9 +609,18 @@ module Steep
613
609
  def header_line
614
610
  "UnexpectedError: #{error.message}"
615
611
  end
612
+ end
616
613
 
617
- def detail_lines
618
- error.backtrace.join("\n")
614
+ class SyntaxError < Base
615
+ attr_reader :message
616
+
617
+ def initialize(message: ,location:)
618
+ super(node: nil, location: location)
619
+ @message = message
620
+ end
621
+
622
+ def header_line
623
+ "SyntaxError: #{message}"
619
624
  end
620
625
  end
621
626
  end
@@ -21,7 +21,7 @@ module Steep
21
21
  end
22
22
 
23
23
  def diagnostic_code
24
- "Ruby::#{error_name}"
24
+ "RBS::#{error_name}"
25
25
  end
26
26
 
27
27
  def path
@@ -37,8 +37,26 @@ module Steep
37
37
  @exception = exception
38
38
  end
39
39
 
40
+ def self.parser_syntax_error_message(exception)
41
+ value = if exception.error_value.is_a?(RBS::Parser::LocatedValue)
42
+ exception.error_value.value
43
+ else
44
+ exception.error_value
45
+ end
46
+ string = value.to_s
47
+ unless string.empty?
48
+ string = " (#{string})"
49
+ end
50
+
51
+ "Syntax error caused by token `#{exception.token_str}`#{string}"
52
+ end
53
+
40
54
  def header_line
41
- "Syntax error: #{exception.message}"
55
+ if exception.is_a?(RBS::Parser::SyntaxError)
56
+ SyntaxError.parser_syntax_error_message(exception)
57
+ else
58
+ exception.message
59
+ end
42
60
  end
43
61
  end
44
62
 
@@ -219,6 +237,138 @@ module Steep
219
237
  "The variance of type parameter `#{param.name}` is #{param.variance}, but used in incompatible position here"
220
238
  end
221
239
  end
240
+
241
+ class ModuleSelfTypeError < Base
242
+ attr_reader :name
243
+ attr_reader :ancestor
244
+ attr_reader :relation
245
+
246
+ def initialize(name:, ancestor:, relation:, location:)
247
+ super(location: location)
248
+
249
+ @name = name
250
+ @ancestor = ancestor
251
+ @relation = relation
252
+ end
253
+
254
+ def header_line
255
+ "Module self type constraint in type `#{name}` doesn't satisfy: `#{relation}`"
256
+ end
257
+ end
258
+
259
+ class InstanceVariableTypeError < Base
260
+ attr_reader :name
261
+ attr_reader :variable
262
+ attr_reader :var_type
263
+ attr_reader :parent_type
264
+
265
+ def initialize(name:, location:, var_type:, parent_type:)
266
+ super(location: location)
267
+
268
+ @name = name
269
+ @var_type = var_type
270
+ @parent_type = parent_type
271
+ end
272
+
273
+ def header_line
274
+ "Instance variable cannot have different type with parents: #{var_type} <=> #{parent_type}"
275
+ end
276
+ end
277
+
278
+ class UnexpectedError < Base
279
+ attr_reader :message
280
+
281
+ def initialize(message:, location:)
282
+ @message = message
283
+ @location = location
284
+ end
285
+
286
+ def header_line
287
+ "Unexpected error: #{message}"
288
+ end
289
+ end
290
+
291
+ def self.from_rbs_error(error, factory:)
292
+ case error
293
+ when RBS::Parser::SemanticsError, RBS::Parser::LexerError
294
+ Diagnostic::Signature::SyntaxError.new(error, location: error.location)
295
+ when RBS::Parser::SyntaxError
296
+ Diagnostic::Signature::SyntaxError.new(error, location: error.error_value.location)
297
+ when RBS::DuplicatedDeclarationError
298
+ Diagnostic::Signature::DuplicatedDeclaration.new(
299
+ type_name: error.name,
300
+ location: error.decls[0].location
301
+ )
302
+ when RBS::GenericParameterMismatchError
303
+ Diagnostic::Signature::GenericParameterMismatch.new(
304
+ name: error.name,
305
+ location: error.decl.location
306
+ )
307
+ when RBS::InvalidTypeApplicationError
308
+ Diagnostic::Signature::InvalidTypeApplication.new(
309
+ name: error.type_name,
310
+ args: error.args.map {|ty| factory.type(ty) },
311
+ params: error.params,
312
+ location: error.location
313
+ )
314
+ when RBS::NoTypeFoundError,
315
+ RBS::NoSuperclassFoundError,
316
+ RBS::NoMixinFoundError,
317
+ RBS::NoSelfTypeFoundError
318
+ Diagnostic::Signature::UnknownTypeName.new(
319
+ name: error.type_name,
320
+ location: error.location
321
+ )
322
+ when RBS::InvalidOverloadMethodError
323
+ Diagnostic::Signature::InvalidMethodOverload.new(
324
+ class_name: error.type_name,
325
+ method_name: error.method_name,
326
+ location: error.members[0].location
327
+ )
328
+ when RBS::DuplicatedMethodDefinitionError
329
+ Diagnostic::Signature::DuplicatedMethodDefinition.new(
330
+ class_name: error.type_name,
331
+ method_name: error.method_name,
332
+ location: error.location
333
+ )
334
+ when RBS::DuplicatedInterfaceMethodDefinitionError
335
+ Diagnostic::Signature::DuplicatedMethodDefinition.new(
336
+ class_name: error.type_name,
337
+ method_name: error.method_name,
338
+ location: error.member.location
339
+ )
340
+ when RBS::UnknownMethodAliasError
341
+ Diagnostic::Signature::UnknownMethodAlias.new(
342
+ class_name: error.type_name,
343
+ method_name: error.original_name,
344
+ location: error.location
345
+ )
346
+ when RBS::RecursiveAliasDefinitionError
347
+ Diagnostic::Signature::RecursiveAlias.new(
348
+ class_name: error.type.name,
349
+ names: error.defs.map(&:name),
350
+ location: error.defs[0].original.location
351
+ )
352
+ when RBS::RecursiveAncestorError
353
+ Diagnostic::Signature::RecursiveAncestor.new(
354
+ ancestors: error.ancestors,
355
+ location: error.location
356
+ )
357
+ when RBS::SuperclassMismatchError
358
+ Diagnostic::Signature::SuperclassMismatch.new(
359
+ name: error.name,
360
+ location: error.entry.primary.decl.location
361
+ )
362
+ when RBS::InvalidVarianceAnnotationError
363
+ Diagnostic::Signature::InvalidVarianceAnnotation.new(
364
+ name: error.type_name,
365
+ param: error.param,
366
+ location: error.location
367
+ )
368
+ else
369
+ raise error
370
+ end
371
+ end
222
372
  end
223
373
  end
224
374
  end
@@ -4,7 +4,6 @@ module Steep
4
4
  attr_reader :command_line_patterns
5
5
  attr_reader :stdout
6
6
  attr_reader :stderr
7
- attr_reader :labeling
8
7
 
9
8
  include Utils::DriverHelper
10
9
 
@@ -13,56 +12,39 @@ module Steep
13
12
  @stderr = stderr
14
13
 
15
14
  @command_line_patterns = []
16
- @labeling = ASTUtils::Labeling.new
17
15
  end
18
16
 
19
17
  def run
20
18
  project = load_config()
21
19
 
22
- loader = Project::FileLoader.new(project: project)
23
- loader.load_sources(command_line_patterns)
24
- loader.load_signatures()
20
+ loader = Services::FileLoader.new(base_dir: project.base_dir)
25
21
 
26
22
  project.targets.each do |target|
27
23
  Steep.logger.tagged "target=#{target.name}" do
28
- target.load_signatures(validate: false) do |_, subtyping, _|
29
- case (status = target.status)
30
- when nil # status set on error cases
31
- target.source_files.each_value do |file|
32
- file.parse(subtyping.factory) do |source|
33
- source.each_annotation.sort_by {|node, _| [node.loc.expression.begin_pos, node.loc.expression.end_pos] }.each do |node, annotations|
34
- loc = node.loc
35
- stdout.puts "#{file.path}:#{loc.line}:#{loc.column}:#{node.type}:\t#{node.loc.expression.source.lines.first}"
36
- annotations.each do |annotation|
37
- stdout.puts " #{annotation.location.source}"
38
- end
39
- end
40
- end
41
- end
42
- when Project::Target::SignatureErrorStatus
43
- formatter = Diagnostic::LSPFormatter.new
44
- diagnostics = status.errors.group_by {|e| e.location.buffer }.transform_values do |errors|
45
- errors.map {|error| formatter.format(error) }
46
- end
24
+ service = Services::SignatureService.load_from(target.new_env_loader(project: project))
25
+
26
+ sigs = loader.load_changes(target.signature_pattern, changes: {})
27
+ service.update(sigs)
28
+
29
+ factory = AST::Types::Factory.new(builder: service.latest_builder)
30
+
31
+ srcs = loader.load_changes(target.source_pattern, command_line_patterns, changes: {})
32
+ srcs.each do |path, changes|
33
+ text = changes.inject("") {|text, change| change.apply_to(text) }
34
+ source = Source.parse(text, path: path, factory: factory)
47
35
 
48
- diagnostics.each do |buffer, ds|
49
- printer = DiagnosticPrinter.new(stdout: stdout, buffer: buffer)
50
- ds.each do |d|
51
- printer.print(d)
52
- stdout.puts
53
- end
36
+ source.each_annotation.sort_by {|node, _| [node.loc.expression.begin_pos, node.loc.expression.end_pos] }.each do |node, annotations|
37
+ loc = node.loc
38
+ stdout.puts "#{path}:#{loc.line}:#{loc.column}:#{node.type}:\t#{node.loc.expression.source.lines.first}"
39
+ annotations.each do |annotation|
40
+ stdout.puts " #{annotation.location.source}"
54
41
  end
55
42
  end
56
43
  end
57
44
  end
58
45
  end
59
46
 
60
- project.targets.each do |target|
61
- Steep.logger.tagged "target=#{target.name}" do
62
- end
63
- end
64
-
65
- project.targets.all? {|target| !target.status } ? 0 : 1
47
+ 0
66
48
  end
67
49
  end
68
50
  end
@@ -6,8 +6,11 @@ module Steep
6
6
  attr_reader :stdout
7
7
  attr_reader :stderr
8
8
  attr_reader :command_line_patterns
9
+ attr_accessor :with_expectations_path
10
+ attr_accessor :save_expectations_path
9
11
 
10
12
  include Utils::DriverHelper
13
+ include Utils::JobsCount
11
14
 
12
15
  def initialize(stdout:, stderr:)
13
16
  @stdout = stdout
@@ -18,10 +21,6 @@ module Steep
18
21
  def run
19
22
  project = load_config()
20
23
 
21
- loader = Project::FileLoader.new(project: project)
22
- loader.load_sources(command_line_patterns)
23
- loader.load_signatures()
24
-
25
24
  stdout.puts Rainbow("# Type checking files:").bold
26
25
  stdout.puts
27
26
 
@@ -34,30 +33,38 @@ module Steep
34
33
  server_reader = LanguageServer::Protocol::Transport::Io::Reader.new(server_read)
35
34
  server_writer = LanguageServer::Protocol::Transport::Io::Writer.new(server_write)
36
35
 
37
- interaction_worker = Server::WorkerProcess.spawn_worker(:interaction, name: "interaction", steepfile: project.steepfile_path, delay_shutdown: true)
38
- signature_worker = Server::WorkerProcess.spawn_worker(:signature, name: "signature", steepfile: project.steepfile_path, delay_shutdown: true)
39
- code_workers = Server::WorkerProcess.spawn_code_workers(steepfile: project.steepfile_path, delay_shutdown: true)
36
+ typecheck_workers = Server::WorkerProcess.spawn_typecheck_workers(
37
+ steepfile: project.steepfile_path,
38
+ args: command_line_patterns,
39
+ delay_shutdown: true,
40
+ count: jobs_count
41
+ )
40
42
 
41
43
  master = Server::Master.new(
42
44
  project: project,
43
45
  reader: server_reader,
44
46
  writer: server_writer,
45
- interaction_worker: interaction_worker,
46
- signature_worker: signature_worker,
47
- code_workers: code_workers
47
+ interaction_worker: nil,
48
+ typecheck_workers: typecheck_workers
48
49
  )
50
+ master.typecheck_automatically = false
51
+ master.commandline_args.push(*command_line_patterns)
49
52
 
50
53
  main_thread = Thread.start do
51
54
  master.start()
52
55
  end
53
56
  main_thread.abort_on_exception = true
54
57
 
55
- client_writer.write({ method: :initialize, id: 0 })
58
+ Steep.logger.info { "Initializing server" }
59
+ initialize_id = request_id()
60
+ client_writer.write({ method: :initialize, id: initialize_id, params: {} })
61
+ wait_for_response_id(reader: client_reader, id: initialize_id)
56
62
 
57
- shutdown_id = -1
58
- client_writer.write({ method: :shutdown, id: shutdown_id })
63
+ request_guid = SecureRandom.uuid
64
+ Steep.logger.info { "Starting type checking: #{request_guid}" }
65
+ client_writer.write({ method: "$/typecheck", params: { guid: request_guid } })
59
66
 
60
- responses = []
67
+ diagnostic_notifications = []
61
68
  error_messages = []
62
69
  client_reader.read do |response|
63
70
  case
@@ -68,7 +75,7 @@ module Steep
68
75
  else
69
76
  stdout.print "F"
70
77
  end
71
- responses << response[:params]
78
+ diagnostic_notifications << response[:params]
72
79
  stdout.flush
73
80
  when response[:method] == "window/showMessage"
74
81
  # Assuming ERROR message means unrecoverable error.
@@ -76,43 +83,145 @@ module Steep
76
83
  if message[:type] == LSP::Constant::MessageType::ERROR
77
84
  error_messages << message[:message]
78
85
  end
79
- when response[:id] == shutdown_id
80
- break
86
+ when response[:method] == "$/progress"
87
+ if response[:params][:token] == request_guid
88
+ if response[:params][:value][:kind] == "end"
89
+ break
90
+ end
91
+ end
81
92
  end
82
93
  end
83
94
 
84
- client_writer.write({ method: :exit })
85
- client_writer.io.close()
95
+ Steep.logger.info { "Shutting down..." }
86
96
 
97
+ shutdown_exit(reader: client_reader, writer: client_writer)
87
98
  main_thread.join()
88
99
 
89
100
  stdout.puts
90
101
  stdout.puts
91
102
 
92
- case
93
- when responses.all? {|res| res[:diagnostics].empty? } && error_messages.empty?
103
+ if error_messages.empty?
104
+ loader = Services::FileLoader.new(base_dir: project.base_dir)
105
+ all_files = project.targets.each.with_object(Set[]) do |target, set|
106
+ set.merge(loader.load_changes(target.source_pattern, command_line_patterns, changes: {}).each_key)
107
+ set.merge(loader.load_changes(target.signature_pattern, changes: {}).each_key)
108
+ end.to_a
109
+
110
+ case
111
+ when with_expectations_path
112
+ print_expectations(project: project,
113
+ all_files: all_files,
114
+ expectations_path: with_expectations_path,
115
+ notifications: diagnostic_notifications)
116
+ when save_expectations_path
117
+ save_expectations(project: project,
118
+ all_files: all_files,
119
+ expectations_path: save_expectations_path,
120
+ notifications: diagnostic_notifications)
121
+ else
122
+ print_result(project: project, notifications: diagnostic_notifications)
123
+ end
124
+ else
125
+ stdout.puts Rainbow("Unexpected error reported. 🚨").red.bold
126
+ 1
127
+ end
128
+ end
129
+
130
+ def print_expectations(project:, all_files:, expectations_path:, notifications:)
131
+ expectations = Expectations.load(path: expectations_path, content: expectations_path.read)
132
+
133
+ expected_count = 0
134
+ unexpected_count = 0
135
+ missing_count = 0
136
+
137
+ ns = notifications.each.with_object({}) do |notification, hash|
138
+ path = project.relative_path(Pathname(URI.parse(notification[:uri]).path))
139
+ hash[path] = notification[:diagnostics]
140
+ end
141
+
142
+ all_files.sort.each do |path|
143
+ test = expectations.test(path: path, diagnostics: ns[path] || [])
144
+
145
+ buffer = RBS::Buffer.new(name: path, content: path.read)
146
+ printer = DiagnosticPrinter.new(buffer: buffer, stdout: stdout)
147
+
148
+ test.each_diagnostics.each do |type, diag|
149
+ case type
150
+ when :expected
151
+ expected_count += 1
152
+ when :unexpected
153
+ unexpected_count += 1
154
+ printer.print(diag, prefix: Rainbow("+ ").green)
155
+ when :missing
156
+ missing_count += 1
157
+ printer.print(diag, prefix: Rainbow("- ").red, source: false)
158
+ end
159
+ end
160
+ end
161
+
162
+ if unexpected_count > 0 || missing_count > 0
163
+ stdout.puts
164
+
165
+ stdout.puts Rainbow("Expectations unsatisfied:").bold.red
166
+ stdout.puts " #{expected_count} expected #{"diagnostic".pluralize(expected_count)}"
167
+ stdout.puts Rainbow(" + #{unexpected_count} unexpected #{"diagnostic".pluralize(unexpected_count)}").green
168
+ stdout.puts Rainbow(" - #{missing_count} missing #{"diagnostic".pluralize(missing_count)}").red
169
+ 1
170
+ else
171
+ stdout.puts Rainbow("Expectations satisfied:").bold.green
172
+ stdout.puts " #{expected_count} expected #{"diagnostic".pluralize(expected_count)}"
173
+ 0
174
+ end
175
+ end
176
+
177
+ def save_expectations(project:, all_files:, expectations_path:, notifications:)
178
+ expectations = if expectations_path.file?
179
+ Expectations.load(path: expectations_path, content: expectations_path.read)
180
+ else
181
+ Expectations.empty()
182
+ end
183
+
184
+ ns = notifications.each.with_object({}) do |notification, hash|
185
+ path = project.relative_path(Pathname(URI.parse(notification[:uri]).path))
186
+ hash[path] = notification[:diagnostics]
187
+ end
188
+
189
+ all_files.sort.each do |path|
190
+ ds = ns[path] || []
191
+
192
+ if ds.empty?
193
+ expectations.diagnostics.delete(path)
194
+ else
195
+ expectations.diagnostics[path] = ds
196
+ end
197
+ end
198
+
199
+ expectations_path.write(expectations.to_yaml)
200
+ stdout.puts Rainbow("Saved expectations in #{expectations_path}...").bold
201
+ 0
202
+ end
203
+
204
+ def print_result(project:, notifications:)
205
+ if notifications.all? {|notification| notification[:diagnostics].empty? }
94
206
  emoji = %w(🫖 🫖 🫖 🫖 🫖 🫖 🫖 🫖 🍵 🧋 🧉).sample
95
207
  stdout.puts Rainbow("No type error detected. #{emoji}").green.bold
96
208
  0
97
- when !error_messages.empty?
98
- stdout.puts Rainbow("Unexpected error reported. 🚨").red.bold
99
- 1
100
209
  else
101
- errors = responses.reject {|res| res[:diagnostics].empty? }
102
- total = errors.sum {|res| res[:diagnostics].size }
103
- stdout.puts Rainbow("Detected #{total} problems from #{errors.size} files").red.bold
104
- stdout.puts
210
+ errors = notifications.reject {|notification| notification[:diagnostics].empty? }
211
+ total = errors.sum {|notification| notification[:diagnostics].size }
105
212
 
106
- errors.each do |resp|
107
- path = project.relative_path(Pathname(URI.parse(resp[:uri]).path))
213
+ errors.each do |notification|
214
+ path = project.relative_path(Pathname(URI.parse(notification[:uri]).path))
108
215
  buffer = RBS::Buffer.new(name: path, content: path.read)
109
216
  printer = DiagnosticPrinter.new(buffer: buffer, stdout: stdout)
110
217
 
111
- resp[:diagnostics].each do |diag|
218
+ notification[:diagnostics].each do |diag|
112
219
  printer.print(diag)
113
220
  stdout.puts
114
221
  end
115
222
  end
223
+
224
+ stdout.puts Rainbow("Detected #{total} #{"problem".pluralize(total)} from #{errors.size} #{"file".pluralize(errors.size)}").red.bold
116
225
  1
117
226
  end
118
227
  end