steep 0.39.0 → 0.40.0

Sign up to get free protection for your applications and to get access to all the features.
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