steep 0.39.0 → 0.40.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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +1 -1
  3. data/CHANGELOG.md +6 -0
  4. data/Rakefile +5 -2
  5. data/bin/output_rebaseline.rb +49 -0
  6. data/bin/output_test.rb +93 -0
  7. data/lib/steep.rb +8 -3
  8. data/lib/steep/cli.rb +1 -1
  9. data/lib/steep/diagnostic/helper.rb +17 -0
  10. data/lib/steep/diagnostic/lsp_formatter.rb +16 -0
  11. data/lib/steep/diagnostic/ruby.rb +623 -0
  12. data/lib/steep/diagnostic/signature.rb +224 -0
  13. data/lib/steep/drivers/annotations.rb +13 -6
  14. data/lib/steep/drivers/check.rb +83 -60
  15. data/lib/steep/drivers/diagnostic_printer.rb +94 -0
  16. data/lib/steep/drivers/stats.rb +125 -29
  17. data/lib/steep/drivers/trace_printer.rb +5 -1
  18. data/lib/steep/drivers/validate.rb +13 -6
  19. data/lib/steep/drivers/watch.rb +26 -9
  20. data/lib/steep/drivers/worker.rb +5 -0
  21. data/lib/steep/project/options.rb +4 -4
  22. data/lib/steep/project/signature_file.rb +8 -2
  23. data/lib/steep/project/stats_calculator.rb +80 -0
  24. data/lib/steep/project/target.rb +64 -53
  25. data/lib/steep/range_extension.rb +29 -0
  26. data/lib/steep/server/base_worker.rb +42 -4
  27. data/lib/steep/server/code_worker.rb +37 -24
  28. data/lib/steep/server/interaction_worker.rb +1 -0
  29. data/lib/steep/server/master.rb +268 -82
  30. data/lib/steep/server/signature_worker.rb +7 -59
  31. data/lib/steep/server/worker_process.rb +9 -9
  32. data/lib/steep/signature/validator.rb +33 -9
  33. data/lib/steep/type_construction.rb +276 -194
  34. data/lib/steep/version.rb +1 -1
  35. data/smoke/alias/a.rb +0 -3
  36. data/smoke/alias/b.rb +0 -1
  37. data/smoke/alias/c.rb +0 -2
  38. data/smoke/alias/test.yaml +73 -0
  39. data/smoke/and/a.rb +0 -3
  40. data/smoke/and/test.yaml +24 -0
  41. data/smoke/array/a.rb +0 -3
  42. data/smoke/array/b.rb +0 -2
  43. data/smoke/array/c.rb +0 -1
  44. data/smoke/array/test.yaml +80 -0
  45. data/smoke/block/a.rb +0 -2
  46. data/smoke/block/b.rb +0 -2
  47. data/smoke/block/d.rb +0 -4
  48. data/smoke/block/test.yaml +96 -0
  49. data/smoke/broken/Steepfile +5 -0
  50. data/smoke/broken/broken.rb +0 -0
  51. data/smoke/broken/broken.rbs +0 -0
  52. data/smoke/broken/test.yaml +6 -0
  53. data/smoke/case/a.rb +0 -3
  54. data/smoke/case/test.yaml +36 -0
  55. data/smoke/class/a.rb +0 -3
  56. data/smoke/class/c.rb +0 -1
  57. data/smoke/class/f.rb +0 -1
  58. data/smoke/class/g.rb +0 -2
  59. data/smoke/class/i.rb +0 -2
  60. data/smoke/class/test.yaml +89 -0
  61. data/smoke/const/a.rb +0 -3
  62. data/smoke/const/b.rb +7 -0
  63. data/smoke/const/b.rbs +5 -0
  64. data/smoke/const/test.yaml +96 -0
  65. data/smoke/diagnostics-rbs-duplicated/Steepfile +5 -0
  66. data/smoke/diagnostics-rbs-duplicated/a.rbs +5 -0
  67. data/smoke/diagnostics-rbs-duplicated/test.yaml +10 -0
  68. data/smoke/diagnostics-rbs/Steepfile +5 -0
  69. data/smoke/diagnostics-rbs/duplicated-method-definition.rbs +20 -0
  70. data/smoke/diagnostics-rbs/generic-parameter-mismatch.rbs +7 -0
  71. data/smoke/diagnostics-rbs/invalid-method-overload.rbs +3 -0
  72. data/smoke/diagnostics-rbs/invalid-type-application.rbs +7 -0
  73. data/smoke/diagnostics-rbs/invalid_variance_annotation.rbs +3 -0
  74. data/smoke/diagnostics-rbs/recursive-alias.rbs +5 -0
  75. data/smoke/diagnostics-rbs/recursive-class.rbs +8 -0
  76. data/smoke/diagnostics-rbs/superclass-mismatch.rbs +7 -0
  77. data/smoke/diagnostics-rbs/test.yaml +142 -0
  78. data/smoke/diagnostics-rbs/unknown-method-alias.rbs +3 -0
  79. data/smoke/diagnostics-rbs/unknown-type-name.rbs +13 -0
  80. data/smoke/diagnostics/Steepfile +5 -0
  81. data/smoke/diagnostics/a.rbs +26 -0
  82. data/smoke/diagnostics/argument_type_mismatch.rb +1 -0
  83. data/smoke/diagnostics/block_body_type_mismatch.rb +1 -0
  84. data/smoke/diagnostics/block_type_mismatch.rb +3 -0
  85. data/smoke/diagnostics/break_type_mismatch.rb +1 -0
  86. data/smoke/diagnostics/else_on_exhaustive_case.rb +12 -0
  87. data/smoke/diagnostics/incompatible_annotation.rb +6 -0
  88. data/smoke/diagnostics/incompatible_argument.rb +1 -0
  89. data/smoke/diagnostics/incompatible_assignment.rb +8 -0
  90. data/smoke/diagnostics/method_arity_mismatch.rb +11 -0
  91. data/smoke/diagnostics/method_body_type_mismatch.rb +6 -0
  92. data/smoke/diagnostics/method_definition_missing.rb +2 -0
  93. data/smoke/diagnostics/method_return_type_annotation_mismatch.rb +7 -0
  94. data/smoke/diagnostics/missing_keyword.rb +1 -0
  95. data/smoke/diagnostics/no_method.rb +1 -0
  96. data/smoke/diagnostics/required_block_missing.rb +1 -0
  97. data/smoke/diagnostics/return_type_mismatch.rb +6 -0
  98. data/smoke/diagnostics/test.yaml +333 -0
  99. data/smoke/diagnostics/unexpected_block_given.rb +1 -0
  100. data/smoke/diagnostics/unexpected_dynamic_method.rb +3 -0
  101. data/smoke/diagnostics/unexpected_jump.rb +4 -0
  102. data/smoke/diagnostics/unexpected_jump_value.rb +3 -0
  103. data/smoke/diagnostics/unexpected_keyword.rb +1 -0
  104. data/smoke/diagnostics/unexpected_splat.rb +1 -0
  105. data/smoke/diagnostics/unexpected_yield.rb +6 -0
  106. data/smoke/diagnostics/unknown_constant_assigned.rb +7 -0
  107. data/smoke/diagnostics/unresolved_overloading.rb +1 -0
  108. data/smoke/diagnostics/unsatisfiable_constraint.rb +7 -0
  109. data/smoke/diagnostics/unsupported_syntax.rb +2 -0
  110. data/smoke/dstr/a.rb +0 -1
  111. data/smoke/dstr/test.yaml +10 -0
  112. data/smoke/ensure/a.rb +0 -4
  113. data/smoke/ensure/test.yaml +47 -0
  114. data/smoke/enumerator/a.rb +0 -6
  115. data/smoke/enumerator/b.rb +0 -3
  116. data/smoke/enumerator/test.yaml +100 -0
  117. data/smoke/extension/a.rb +0 -1
  118. data/smoke/extension/b.rb +0 -2
  119. data/smoke/extension/c.rb +0 -1
  120. data/smoke/extension/test.yaml +50 -0
  121. data/smoke/hash/b.rb +0 -1
  122. data/smoke/hash/c.rb +0 -3
  123. data/smoke/hash/d.rb +0 -1
  124. data/smoke/hash/e.rb +0 -1
  125. data/smoke/hash/test.yaml +62 -0
  126. data/smoke/hello/hello.rb +0 -2
  127. data/smoke/hello/test.yaml +18 -0
  128. data/smoke/if/a.rb +0 -2
  129. data/smoke/if/test.yaml +27 -0
  130. data/smoke/implements/a.rb +0 -2
  131. data/smoke/implements/test.yaml +16 -0
  132. data/smoke/initialize/test.yaml +4 -0
  133. data/smoke/integer/a.rb +0 -7
  134. data/smoke/integer/test.yaml +66 -0
  135. data/smoke/interface/a.rb +0 -2
  136. data/smoke/interface/test.yaml +16 -0
  137. data/smoke/kwbegin/a.rb +0 -1
  138. data/smoke/kwbegin/test.yaml +14 -0
  139. data/smoke/lambda/a.rb +1 -4
  140. data/smoke/lambda/test.yaml +28 -0
  141. data/smoke/literal/a.rb +0 -5
  142. data/smoke/literal/b.rb +0 -2
  143. data/smoke/literal/test.yaml +79 -0
  144. data/smoke/map/test.yaml +4 -0
  145. data/smoke/method/a.rb +0 -5
  146. data/smoke/method/b.rb +0 -1
  147. data/smoke/method/test.yaml +71 -0
  148. data/smoke/module/a.rb +0 -2
  149. data/smoke/module/b.rb +0 -2
  150. data/smoke/module/c.rb +0 -1
  151. data/smoke/module/d.rb +0 -1
  152. data/smoke/module/f.rb +0 -2
  153. data/smoke/module/test.yaml +51 -0
  154. data/smoke/regexp/a.rb +0 -38
  155. data/smoke/regexp/b.rb +0 -26
  156. data/smoke/regexp/test.yaml +372 -0
  157. data/smoke/regression/set_divide.rb +0 -4
  158. data/smoke/regression/test.yaml +38 -0
  159. data/smoke/rescue/a.rb +0 -5
  160. data/smoke/rescue/test.yaml +60 -0
  161. data/smoke/self/a.rb +0 -2
  162. data/smoke/self/test.yaml +16 -0
  163. data/smoke/skip/skip.rb +0 -2
  164. data/smoke/skip/test.yaml +16 -0
  165. data/smoke/stdout/test.yaml +4 -0
  166. data/smoke/super/a.rb +0 -4
  167. data/smoke/super/test.yaml +52 -0
  168. data/smoke/toplevel/a.rb +0 -1
  169. data/smoke/toplevel/test.yaml +12 -0
  170. data/smoke/tsort/a.rb +0 -3
  171. data/smoke/tsort/test.yaml +32 -0
  172. data/smoke/type_case/a.rb +0 -4
  173. data/smoke/type_case/test.yaml +33 -0
  174. data/smoke/yield/a.rb +0 -3
  175. data/smoke/yield/b.rb +6 -0
  176. data/smoke/yield/test.yaml +49 -0
  177. data/steep.gemspec +3 -3
  178. metadata +108 -17
  179. data/bin/smoke_runner.rb +0 -139
  180. data/lib/steep/drivers/signature_error_printer.rb +0 -25
  181. data/lib/steep/errors.rb +0 -594
  182. data/lib/steep/signature/errors.rb +0 -128
  183. data/lib/steep/type_assignability.rb +0 -367
@@ -22,45 +22,141 @@ module Steep
22
22
  loader.load_sources(command_line_patterns)
23
23
  loader.load_signatures()
24
24
 
25
- type_check(project)
25
+ stderr.puts Rainbow("# Calculating stats:").bold
26
+ stderr.puts
27
+
28
+ client_read, server_write = IO.pipe
29
+ server_read, client_write = IO.pipe
30
+
31
+ client_reader = LanguageServer::Protocol::Transport::Io::Reader.new(client_read)
32
+ client_writer = LanguageServer::Protocol::Transport::Io::Writer.new(client_write)
33
+
34
+ server_reader = LanguageServer::Protocol::Transport::Io::Reader.new(server_read)
35
+ server_writer = LanguageServer::Protocol::Transport::Io::Writer.new(server_write)
36
+
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)
40
+
41
+ master = Server::Master.new(
42
+ project: project,
43
+ reader: server_reader,
44
+ writer: server_writer,
45
+ interaction_worker: interaction_worker,
46
+ signature_worker: signature_worker,
47
+ code_workers: code_workers
48
+ )
49
+
50
+ main_thread = Thread.start do
51
+ master.start()
52
+ end
53
+ main_thread.abort_on_exception = true
54
+
55
+ client_writer.write({ method: :initialize, id: 0 })
56
+
57
+ stats_id = -1
58
+ client_writer.write(
59
+ {
60
+ id: stats_id,
61
+ method: "workspace/executeCommand",
62
+ params: {
63
+ command: "steep/stats",
64
+ arguments: project.all_source_files.map {|path| project.absolute_path(path) }
65
+ }
66
+ })
67
+
68
+ stats_result = []
69
+ client_reader.read do |response|
70
+ if response[:id] == stats_id
71
+ stats_result.push(*response[:result])
72
+ break
73
+ end
74
+ end
75
+
76
+ shutdown_id = -2
77
+ client_writer.write({ method: :shutdown, id: shutdown_id })
78
+
79
+ client_reader.read do |response|
80
+ if response[:id] == shutdown_id
81
+ break
82
+ end
83
+ end
84
+
85
+ client_writer.write({ method: "exit" })
86
+ main_thread.join()
26
87
 
27
88
  stdout.puts(
28
89
  CSV.generate do |csv|
29
90
  csv << ["Target", "File", "Status", "Typed calls", "Untyped calls", "All calls", "Typed %"]
30
-
31
- project.targets.each do |target|
32
- case (status = target.status)
33
- when Project::Target::TypeCheckStatus
34
- status.type_check_sources.each do |source_file|
35
- case source_file.status
36
- when Project::SourceFile::TypeCheckStatus
37
- typing = source_file.status.typing
38
-
39
- typed = 0
40
- untyped = 0
41
- total = 0
42
- typing.method_calls.each_value do |call|
43
- case call
44
- when TypeInference::MethodCall::Typed
45
- typed += 1
46
- when TypeInference::MethodCall::Untyped
47
- untyped += 1
48
- end
49
-
50
- total += 1
51
- end
52
-
53
- csv << format_stats(target, source_file.path, "success", typed, untyped, total)
54
- when Project::SourceFile::TypeCheckErrorStatus
55
- csv << format_stats(target, source_file.path, "error", 0, 0, 0)
91
+ stats_result.each do |row|
92
+ if row[:type] == "success"
93
+ csv << [
94
+ row[:target],
95
+ row[:path],
96
+ row[:type],
97
+ row[:typed_calls],
98
+ row[:untyped_calls],
99
+ row[:total_calls],
100
+ if row[:total_calls].nonzero?
101
+ (row[:typed_calls].to_f / row[:total_calls] * 100).to_i
56
102
  else
57
- csv << format_stats(target, source_file.path, "unknown (#{source_file.status.class.to_s.split(/::/).last})", 0, 0, 0)
103
+ 100
58
104
  end
59
- end
105
+ ]
106
+ else
107
+ csv << [
108
+ row[:target],
109
+ row[:path],
110
+ row[:type],
111
+ 0,
112
+ 0,
113
+ 0,
114
+ 0
115
+ ]
60
116
  end
61
117
  end
62
118
  end
63
119
  )
120
+ #
121
+ # type_check(project)
122
+ #
123
+ # stdout.puts(
124
+ # CSV.generate do |csv|
125
+ # csv << ["Target", "File", "Status", "Typed calls", "Untyped calls", "All calls", "Typed %"]
126
+ #
127
+ # project.targets.each do |target|
128
+ # case (status = target.status)
129
+ # when Project::Target::TypeCheckStatus
130
+ # status.type_check_sources.each do |source_file|
131
+ # case source_file.status
132
+ # when Project::SourceFile::TypeCheckStatus
133
+ # typing = source_file.status.typing
134
+ #
135
+ # typed = 0
136
+ # untyped = 0
137
+ # total = 0
138
+ # typing.method_calls.each_value do |call|
139
+ # case call
140
+ # when TypeInference::MethodCall::Typed
141
+ # typed += 1
142
+ # when TypeInference::MethodCall::Untyped
143
+ # untyped += 1
144
+ # end
145
+ #
146
+ # total += 1
147
+ # end
148
+ #
149
+ # csv << format_stats(target, source_file.path, "success", typed, untyped, total)
150
+ # when Project::SourceFile::TypeCheckErrorStatus
151
+ # csv << format_stats(target, source_file.path, "error", 0, 0, 0)
152
+ # else
153
+ # csv << format_stats(target, source_file.path, "unknown (#{source_file.status.class.to_s.split(/::/).last})", 0, 0, 0)
154
+ # end
155
+ # end
156
+ # end
157
+ # end
158
+ # end
159
+ # )
64
160
 
65
161
  0
66
162
  end
@@ -9,7 +9,7 @@ module Steep
9
9
 
10
10
  def print(trace, level: 0)
11
11
  trace.each.with_index do |t, i|
12
- prefix = " " * (i + level)
12
+ prefix = " " * (i + level)
13
13
  case t[0]
14
14
  when :type
15
15
  io.puts "#{prefix}#{t[1]} <: #{t[2]}"
@@ -17,6 +17,10 @@ module Steep
17
17
  io.puts "#{prefix}(#{t[3]}) #{t[1]} <: #{t[2]}"
18
18
  when :method_type
19
19
  io.puts "#{prefix}#{t[1]} <: #{t[2]}"
20
+ when :interface
21
+ # nop
22
+ else
23
+ Steep.logger.error { "Unexpected trace item: #{t[0]}" }
20
24
  end
21
25
  end
22
26
  end
@@ -22,12 +22,19 @@ module Steep
22
22
  project.targets.each do |target|
23
23
  Steep.logger.tagged "target=#{target.name}" do
24
24
  case (status = target.status)
25
- when Project::Target::SignatureSyntaxErrorStatus
26
- printer = SignatureErrorPrinter.new(stdout: stdout, stderr: stderr)
27
- printer.print_syntax_errors(status.errors)
28
- when Project::Target::SignatureValidationErrorStatus
29
- printer = SignatureErrorPrinter.new(stdout: stdout, stderr: stderr)
30
- printer.print_semantic_errors(status.errors)
25
+ when Project::Target::SignatureErrorStatus
26
+ formatter = Diagnostic::LSPFormatter.new
27
+ diagnostics = status.errors.group_by {|e| e.location.buffer }.transform_values do |errors|
28
+ errors.map {|error| formatter.format(error) }
29
+ end
30
+
31
+ diagnostics.each do |buffer, ds|
32
+ printer = DiagnosticPrinter.new(stdout: stdout, buffer: buffer)
33
+ ds.each do |d|
34
+ printer.print(d)
35
+ stdout.puts
36
+ end
37
+ end
31
38
  end
32
39
  end
33
40
  end
@@ -8,6 +8,8 @@ module Steep
8
8
 
9
9
  include Utils::DriverHelper
10
10
 
11
+ LSP = LanguageServer::Protocol
12
+
11
13
  def initialize(stdout:, stderr:)
12
14
  @dirs = []
13
15
  @stdout = stdout
@@ -58,7 +60,7 @@ module Steep
58
60
 
59
61
  Steep.logger.info "Watching #{dirs.join(", ")}..."
60
62
  listener = Listen.to(*dirs.map(&:to_s)) do |modified, added, removed|
61
- stdout.puts "🔬 Type checking updated files..."
63
+ stdout.puts Rainbow("🔬 Type checking updated files...").bold
62
64
 
63
65
  version = Time.now.to_i
64
66
  Steep.logger.tagged "watch" do
@@ -101,35 +103,50 @@ module Steep
101
103
  end.tap(&:start)
102
104
 
103
105
  begin
104
- stdout.puts "👀 Watching directories, Ctrl-C to stop."
106
+ stdout.puts Rainbow("👀 Watching directories, Ctrl-C to stop.").bold
105
107
  client_reader.read do |response|
106
108
  case response[:method]
107
109
  when "textDocument/publishDiagnostics"
108
110
  uri = URI.parse(response[:params][:uri])
109
111
  path = project.relative_path(Pathname(uri.path))
112
+ buffer = RBS::Buffer.new(content: path.read, name: path)
113
+ printer = DiagnosticPrinter.new(stdout: stdout, buffer: buffer)
110
114
 
111
115
  diagnostics = response[:params][:diagnostics]
112
116
 
113
117
  unless diagnostics.empty?
114
118
  diagnostics.each do |diagnostic|
115
- start = diagnostic[:range][:start]
116
- loc = "#{start[:line]+1}:#{start[:character]}"
117
- message = diagnostic[:message].chomp.lines.join(" ")
118
-
119
- stdout.puts "#{path}:#{loc}: #{message}"
119
+ printer.print(diagnostic)
120
120
  end
121
121
  end
122
+ when "window/showMessage"
123
+ # Assuming ERROR message means unrecoverable error.
124
+ message = response[:params]
125
+ if message[:type] == LSP::Constant::MessageType::ERROR
126
+ stdout.puts "Unexpected error reported... 🚨"
127
+ end
122
128
  end
123
129
  end
124
130
  rescue Interrupt
131
+ shutdown_id = -1
125
132
  stdout.puts "Shutting down workers..."
126
- client_writer.write({ method: :shutdown, id: 10000 })
133
+ client_writer.write({ method: :shutdown, id: shutdown_id })
134
+ client_reader.read do |response|
135
+ if response[:id] == shutdown_id
136
+ break
137
+ end
138
+ end
127
139
  client_writer.write({ method: :exit })
128
140
  client_writer.io.close()
129
141
  end
130
142
 
131
143
  listener.stop
132
- main_thread.join
144
+ begin
145
+ main_thread.join
146
+ rescue Interrupt
147
+ master.kill
148
+ main_thread.join
149
+ end
133
150
 
134
151
  0
135
152
  end
@@ -6,6 +6,7 @@ module Steep
6
6
  attr_accessor :steepfile_path
7
7
  attr_accessor :worker_type
8
8
  attr_accessor :worker_name
9
+ attr_accessor :delay_shutdown
9
10
 
10
11
  include Utils::DriverHelper
11
12
 
@@ -37,6 +38,10 @@ module Steep
37
38
  raise "Unknown worker type: #{worker_type}"
38
39
  end
39
40
 
41
+ unless delay_shutdown
42
+ worker.skip_jobs_after_shutdown!
43
+ end
44
+
40
45
  Steep.logger.info "Starting #{worker_type} worker..."
41
46
 
42
47
  worker.run()
@@ -40,13 +40,13 @@ module Steep
40
40
 
41
41
  def error_to_report?(error)
42
42
  case
43
- when error.is_a?(Errors::FallbackAny)
43
+ when error.is_a?(Diagnostic::Ruby::FallbackAny)
44
44
  !allow_fallback_any
45
- when error.is_a?(Errors::MethodDefinitionMissing)
45
+ when error.is_a?(Diagnostic::Ruby::MethodDefinitionMissing)
46
46
  !allow_missing_definitions
47
- when error.is_a?(Errors::NoMethod)
47
+ when error.is_a?(Diagnostic::Ruby::NoMethod)
48
48
  !allow_unknown_method_calls
49
- when error.is_a?(Errors::UnknownConstantAssigned)
49
+ when error.is_a?(Diagnostic::Ruby::UnknownConstantAssigned)
50
50
  !allow_unknown_constant_assignment
51
51
  else
52
52
  true
@@ -5,8 +5,6 @@ module Steep
5
5
  attr_reader :content
6
6
  attr_reader :content_updated_at
7
7
 
8
- attr_reader :status
9
-
10
8
  ParseErrorStatus = Struct.new(:error, :timestamp, keyword_init: true)
11
9
  DeclarationsStatus = Struct.new(:declarations, :timestamp, keyword_init: true)
12
10
 
@@ -21,6 +19,14 @@ module Steep
21
19
  @status = nil
22
20
  end
23
21
 
22
+ def status
23
+ unless @status
24
+ @status = DeclarationsStatus.new(declarations: [], timestamp: Time.now)
25
+ end
26
+
27
+ @status
28
+ end
29
+
24
30
  def load!
25
31
  buffer = RBS::Buffer.new(name: path, content: content)
26
32
  decls = RBS::Parser.parse_signature(buffer)
@@ -0,0 +1,80 @@
1
+ module Steep
2
+ class Project
3
+ class StatsCalculator
4
+ SuccessStats = Struct.new(:target, :path, :typed_calls_count, :untyped_calls_count, :error_calls_count, keyword_init: true) do
5
+ def as_json
6
+ {
7
+ type: "success",
8
+ target: target.name.to_s,
9
+ path: path.to_s,
10
+ typed_calls: typed_calls_count,
11
+ untyped_calls: untyped_calls_count,
12
+ error_calls: error_calls_count,
13
+ total_calls: typed_calls_count + untyped_calls_count + error_calls_count
14
+ }
15
+ end
16
+ end
17
+ ErrorStats = Struct.new(:target, :path, :status, keyword_init: true) do
18
+ def as_json
19
+ {
20
+ type: "error",
21
+ target: target.name.to_s,
22
+ path: path.to_s,
23
+ status: status.class.to_s
24
+ }
25
+ end
26
+ end
27
+
28
+ attr_reader :project
29
+
30
+ def initialize(project:)
31
+ @project = project
32
+ end
33
+
34
+ def calc_stats(target, path)
35
+ source_file = target.source_files[path] or raise
36
+
37
+ target.type_check(
38
+ target_sources: [source_file],
39
+ validate_signatures: false
40
+ )
41
+
42
+ if target.status.is_a?(Target::TypeCheckStatus)
43
+ case source_file.status
44
+ when SourceFile::TypeCheckStatus
45
+ typing = source_file.status.typing
46
+
47
+ typed = 0
48
+ untyped = 0
49
+ errors = 0
50
+ total = 0
51
+ typing.method_calls.each_value do |call|
52
+ case call
53
+ when TypeInference::MethodCall::Typed
54
+ typed += 1
55
+ when TypeInference::MethodCall::Untyped
56
+ untyped += 1
57
+ when TypeInference::MethodCall::Error, TypeInference::MethodCall::NoMethodError
58
+ errors += 1
59
+ else
60
+ raise
61
+ end
62
+ end
63
+
64
+ SuccessStats.new(
65
+ target: target,
66
+ path: path,
67
+ typed_calls_count: typed,
68
+ untyped_calls_count: untyped,
69
+ error_calls_count: errors
70
+ )
71
+ when SourceFile::TypeCheckErrorStatus, SourceFile::AnnotationSyntaxErrorStatus, SourceFile::ParseErrorStatus
72
+ ErrorStats.new(target: target, path: path, status: source_file.status)
73
+ end
74
+ else
75
+ ErrorStats.new(target: target, path: path, status: target.status)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -13,9 +13,7 @@ module Steep
13
13
 
14
14
  attr_reader :status
15
15
 
16
- SignatureSyntaxErrorStatus = Struct.new(:timestamp, :errors, keyword_init: true)
17
- SignatureValidationErrorStatus = Struct.new(:timestamp, :errors, keyword_init: true)
18
- SignatureOtherErrorStatus = Struct.new(:timestamp, :error, keyword_init: true)
16
+ SignatureErrorStatus = Struct.new(:timestamp, :errors, keyword_init: true)
19
17
  TypeCheckStatus = Struct.new(:environment, :subtyping, :type_check_sources, :timestamp, keyword_init: true)
20
18
 
21
19
  def initialize(name:, options:, source_patterns:, ignore_patterns:, signature_patterns:)
@@ -137,39 +135,77 @@ module Steep
137
135
  @environment ||= RBS::Environment.from_loader(Target.construct_env_loader(options: options))
138
136
  end
139
137
 
140
- def load_signatures(validate:)
141
- timestamp = case status
142
- when TypeCheckStatus
143
- status.timestamp
144
- end
145
- now = Time.now
146
-
147
- updated_files = []
138
+ def parse_signatures(timestamp:)
139
+ updated_signature_files = []
148
140
 
149
141
  signature_files.each_value do |file|
150
142
  if !timestamp || file.content_updated_at >= timestamp
151
- updated_files << file
143
+ Steep.logger.debug { "Loading #{file.path}..."}
144
+ updated_signature_files << file
152
145
  file.load!()
153
146
  end
154
147
  end
155
148
 
156
- if signature_files.each_value.all? {|file| file.status.is_a?(SignatureFile::DeclarationsStatus) }
157
- if status.is_a?(TypeCheckStatus) && updated_files.empty?
158
- yield status.environment, status.subtyping, status.timestamp
159
- else
160
- begin
161
- env = environment.dup
162
-
163
- signature_files.each_value do |file|
164
- if file.status.is_a?(SignatureFile::DeclarationsStatus)
165
- file.status.declarations.each do |decl|
166
- env << decl
167
- end
168
- end
169
- end
149
+ error_sigs = signature_files.each_value.reject {|file| file.status.is_a?(SignatureFile::DeclarationsStatus) }
170
150
 
171
- env = env.resolve_type_names
151
+ if error_sigs.empty?
152
+ yield updated_signature_files
153
+ else
154
+ errors = error_sigs.map do |file|
155
+ case error = file.status.error
156
+ when RBS::Parser::SemanticsError
157
+ Diagnostic::Signature::SyntaxError.new(error, location: error.location)
158
+ when RBS::Parser::SyntaxError
159
+ Diagnostic::Signature::SyntaxError.new(error, location: error.error_value.location)
160
+ end
161
+ end
162
+
163
+ @status = SignatureErrorStatus.new(
164
+ errors: errors,
165
+ timestamp: Time.now
166
+ )
167
+ end
168
+ end
169
+
170
+ def load_decls(now:)
171
+ errors = []
172
+ env = environment.dup
173
+
174
+ signature_files.each_value do |file|
175
+ raise unless file.status.is_a?(SignatureFile::DeclarationsStatus)
176
+
177
+ file.status.declarations.each do |decl|
178
+ env << decl
179
+ rescue RBS::DuplicatedDeclarationError => exn
180
+ errors << Diagnostic::Signature::DuplicatedDeclaration.new(
181
+ type_name: exn.name,
182
+ location: exn.decls[0].location
183
+ )
184
+ end
185
+ end
172
186
 
187
+ if errors.empty?
188
+ yield env.resolve_type_names
189
+ else
190
+ @status = SignatureErrorStatus.new(
191
+ errors: errors,
192
+ timestamp: now
193
+ )
194
+ end
195
+ end
196
+
197
+ def load_signatures(validate:)
198
+ timestamp = case status
199
+ when TypeCheckStatus
200
+ status.timestamp
201
+ end
202
+ now = Time.now
203
+
204
+ parse_signatures(timestamp: timestamp) do |updated_signature_files|
205
+ if status.is_a?(TypeCheckStatus) && updated_signature_files.empty?
206
+ yield status.environment, status.subtyping, status.timestamp
207
+ else
208
+ load_decls(now: now) do |env|
173
209
  definition_builder = RBS::DefinitionBuilder.new(env: env)
174
210
  factory = AST::Types::Factory.new(builder: definition_builder)
175
211
  check = Subtyping::Check.new(factory: factory)
@@ -181,7 +217,7 @@ module Steep
181
217
  if validator.no_error?
182
218
  yield env, check, now
183
219
  else
184
- @status = SignatureValidationErrorStatus.new(
220
+ @status = SignatureErrorStatus.new(
185
221
  errors: validator.each_error.to_a,
186
222
  timestamp: now
187
223
  )
@@ -189,33 +225,8 @@ module Steep
189
225
  else
190
226
  yield env, check, Time.now
191
227
  end
192
- rescue RBS::DuplicatedDeclarationError => exn
193
- @status = SignatureValidationErrorStatus.new(
194
- errors: [
195
- Signature::Errors::DuplicatedDeclarationError.new(
196
- type_name: exn.name,
197
- location: exn.decls[0].location
198
- )
199
- ],
200
- timestamp: now
201
- )
202
- rescue => exn
203
- Steep.log_error exn
204
- @status = SignatureOtherErrorStatus.new(error: exn, timestamp: now)
205
- end
206
- end
207
-
208
- else
209
- errors = signature_files.each_value.with_object([]) do |file, errors|
210
- if file.status.is_a?(SignatureFile::ParseErrorStatus)
211
- errors << file.status.error
212
228
  end
213
229
  end
214
-
215
- @status = SignatureSyntaxErrorStatus.new(
216
- errors: errors,
217
- timestamp: Time.now
218
- )
219
230
  end
220
231
  end
221
232