steep 0.37.0 → 0.42.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (221) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +1 -1
  3. data/CHANGELOG.md +34 -0
  4. data/Rakefile +5 -2
  5. data/bin/output_rebaseline.rb +34 -0
  6. data/bin/output_test.rb +53 -0
  7. data/lib/steep.rb +95 -14
  8. data/lib/steep/ast/types/bot.rb +1 -1
  9. data/lib/steep/ast/types/class.rb +4 -0
  10. data/lib/steep/ast/types/factory.rb +10 -0
  11. data/lib/steep/ast/types/logic.rb +16 -3
  12. data/lib/steep/ast/types/top.rb +1 -1
  13. data/lib/steep/cli.rb +31 -7
  14. data/lib/steep/diagnostic/helper.rb +17 -0
  15. data/lib/steep/diagnostic/lsp_formatter.rb +16 -0
  16. data/lib/steep/diagnostic/ruby.rb +619 -0
  17. data/lib/steep/diagnostic/signature.rb +357 -0
  18. data/lib/steep/drivers/annotations.rb +19 -28
  19. data/lib/steep/drivers/check.rb +182 -60
  20. data/lib/steep/drivers/diagnostic_printer.rb +99 -0
  21. data/lib/steep/drivers/langserver.rb +3 -8
  22. data/lib/steep/drivers/print_project.rb +10 -9
  23. data/lib/steep/drivers/stats.rb +124 -32
  24. data/lib/steep/drivers/trace_printer.rb +5 -1
  25. data/lib/steep/drivers/utils/jobs_count.rb +9 -0
  26. data/lib/steep/drivers/validate.rb +31 -13
  27. data/lib/steep/drivers/watch.rb +69 -48
  28. data/lib/steep/drivers/worker.rb +16 -8
  29. data/lib/steep/expectations.rb +159 -0
  30. data/lib/steep/index/rbs_index.rb +334 -0
  31. data/lib/steep/index/signature_symbol_provider.rb +162 -0
  32. data/lib/steep/index/source_index.rb +100 -0
  33. data/lib/steep/project.rb +0 -30
  34. data/lib/steep/project/dsl.rb +5 -3
  35. data/lib/steep/project/options.rb +4 -4
  36. data/lib/steep/project/pattern.rb +56 -0
  37. data/lib/steep/project/target.rb +9 -214
  38. data/lib/steep/range_extension.rb +29 -0
  39. data/lib/steep/server/base_worker.rb +43 -7
  40. data/lib/steep/server/change_buffer.rb +63 -0
  41. data/lib/steep/server/interaction_worker.rb +73 -56
  42. data/lib/steep/server/master.rb +245 -109
  43. data/lib/steep/server/type_check_worker.rb +122 -0
  44. data/lib/steep/server/worker_process.rb +17 -15
  45. data/lib/steep/{project → services}/completion_provider.rb +3 -3
  46. data/lib/steep/services/content_change.rb +61 -0
  47. data/lib/steep/services/file_loader.rb +48 -0
  48. data/lib/steep/{project → services}/hover_content.rb +14 -16
  49. data/lib/steep/services/path_assignment.rb +29 -0
  50. data/lib/steep/services/signature_service.rb +369 -0
  51. data/lib/steep/services/stats_calculator.rb +69 -0
  52. data/lib/steep/services/type_check_service.rb +342 -0
  53. data/lib/steep/signature/validator.rb +174 -32
  54. data/lib/steep/subtyping/check.rb +248 -47
  55. data/lib/steep/subtyping/constraints.rb +2 -2
  56. data/lib/steep/type_construction.rb +565 -295
  57. data/lib/steep/type_inference/constant_env.rb +5 -1
  58. data/lib/steep/type_inference/local_variable_type_env.rb +26 -12
  59. data/lib/steep/type_inference/logic_type_interpreter.rb +99 -26
  60. data/lib/steep/type_inference/type_env.rb +43 -17
  61. data/lib/steep/typing.rb +8 -2
  62. data/lib/steep/version.rb +1 -1
  63. data/smoke/alias/a.rb +0 -3
  64. data/smoke/alias/b.rb +0 -1
  65. data/smoke/alias/c.rb +0 -2
  66. data/smoke/alias/test_expectations.yml +96 -0
  67. data/smoke/and/a.rb +0 -3
  68. data/smoke/and/test_expectations.yml +31 -0
  69. data/smoke/array/a.rb +0 -3
  70. data/smoke/array/b.rb +0 -2
  71. data/smoke/array/c.rb +0 -1
  72. data/smoke/array/test_expectations.yml +103 -0
  73. data/smoke/block/a.rb +0 -2
  74. data/smoke/block/b.rb +0 -2
  75. data/smoke/block/d.rb +0 -4
  76. data/smoke/block/test_expectations.yml +125 -0
  77. data/smoke/case/a.rb +0 -3
  78. data/smoke/case/test_expectations.yml +47 -0
  79. data/smoke/class/a.rb +0 -3
  80. data/smoke/class/c.rb +0 -1
  81. data/smoke/class/f.rb +0 -1
  82. data/smoke/class/g.rb +0 -2
  83. data/smoke/class/i.rb +0 -2
  84. data/smoke/class/test_expectations.yml +120 -0
  85. data/smoke/const/a.rb +0 -3
  86. data/smoke/const/b.rb +7 -0
  87. data/smoke/const/b.rbs +5 -0
  88. data/smoke/const/test_expectations.yml +139 -0
  89. data/smoke/diagnostics-rbs-duplicated/Steepfile +5 -0
  90. data/smoke/diagnostics-rbs-duplicated/a.rbs +5 -0
  91. data/smoke/diagnostics-rbs-duplicated/test_expectations.yml +13 -0
  92. data/smoke/diagnostics-rbs/Steepfile +8 -0
  93. data/smoke/diagnostics-rbs/duplicated-method-definition.rbs +20 -0
  94. data/smoke/diagnostics-rbs/generic-parameter-mismatch.rbs +7 -0
  95. data/smoke/diagnostics-rbs/invalid-method-overload.rbs +3 -0
  96. data/smoke/diagnostics-rbs/invalid-type-application.rbs +7 -0
  97. data/smoke/diagnostics-rbs/invalid_variance_annotation.rbs +3 -0
  98. data/smoke/diagnostics-rbs/recursive-alias.rbs +5 -0
  99. data/smoke/diagnostics-rbs/recursive-class.rbs +8 -0
  100. data/smoke/diagnostics-rbs/superclass-mismatch.rbs +7 -0
  101. data/smoke/diagnostics-rbs/test_expectations.yml +231 -0
  102. data/smoke/diagnostics-rbs/unknown-method-alias.rbs +3 -0
  103. data/smoke/diagnostics-rbs/unknown-type-name-2.rbs +5 -0
  104. data/smoke/diagnostics-rbs/unknown-type-name.rbs +13 -0
  105. data/smoke/diagnostics/Steepfile +5 -0
  106. data/smoke/diagnostics/a.rbs +26 -0
  107. data/smoke/diagnostics/argument_type_mismatch.rb +1 -0
  108. data/smoke/diagnostics/block_body_type_mismatch.rb +1 -0
  109. data/smoke/diagnostics/block_type_mismatch.rb +3 -0
  110. data/smoke/diagnostics/break_type_mismatch.rb +1 -0
  111. data/smoke/diagnostics/else_on_exhaustive_case.rb +12 -0
  112. data/smoke/diagnostics/incompatible_annotation.rb +6 -0
  113. data/smoke/diagnostics/incompatible_argument.rb +1 -0
  114. data/smoke/diagnostics/incompatible_assignment.rb +8 -0
  115. data/smoke/diagnostics/method_arity_mismatch.rb +11 -0
  116. data/smoke/diagnostics/method_body_type_mismatch.rb +6 -0
  117. data/smoke/diagnostics/method_definition_missing.rb +2 -0
  118. data/smoke/diagnostics/method_return_type_annotation_mismatch.rb +7 -0
  119. data/smoke/diagnostics/missing_keyword.rb +1 -0
  120. data/smoke/diagnostics/no_method.rb +1 -0
  121. data/smoke/diagnostics/required_block_missing.rb +1 -0
  122. data/smoke/diagnostics/return_type_mismatch.rb +6 -0
  123. data/smoke/diagnostics/test_expectations.yml +477 -0
  124. data/smoke/diagnostics/unexpected_block_given.rb +1 -0
  125. data/smoke/diagnostics/unexpected_dynamic_method.rb +3 -0
  126. data/smoke/diagnostics/unexpected_jump.rb +4 -0
  127. data/smoke/diagnostics/unexpected_jump_value.rb +3 -0
  128. data/smoke/diagnostics/unexpected_keyword.rb +1 -0
  129. data/smoke/diagnostics/unexpected_splat.rb +1 -0
  130. data/smoke/diagnostics/unexpected_yield.rb +6 -0
  131. data/smoke/diagnostics/unknown_constant_assigned.rb +7 -0
  132. data/smoke/diagnostics/unresolved_overloading.rb +1 -0
  133. data/smoke/diagnostics/unsatisfiable_constraint.rb +7 -0
  134. data/smoke/diagnostics/unsupported_syntax.rb +2 -0
  135. data/smoke/dstr/a.rb +0 -1
  136. data/smoke/dstr/test_expectations.yml +13 -0
  137. data/smoke/ensure/a.rb +0 -4
  138. data/smoke/ensure/test_expectations.yml +62 -0
  139. data/smoke/enumerator/a.rb +0 -6
  140. data/smoke/enumerator/b.rb +0 -3
  141. data/smoke/enumerator/test_expectations.yml +135 -0
  142. data/smoke/extension/a.rb +0 -1
  143. data/smoke/extension/b.rb +0 -2
  144. data/smoke/extension/c.rb +0 -1
  145. data/smoke/extension/f.rb +2 -0
  146. data/smoke/extension/f.rbs +3 -0
  147. data/smoke/extension/test_expectations.yml +73 -0
  148. data/smoke/hash/b.rb +0 -1
  149. data/smoke/hash/c.rb +0 -3
  150. data/smoke/hash/d.rb +0 -1
  151. data/smoke/hash/e.rb +0 -1
  152. data/smoke/hash/test_expectations.yml +81 -0
  153. data/smoke/hello/hello.rb +0 -2
  154. data/smoke/hello/test_expectations.yml +25 -0
  155. data/smoke/if/a.rb +0 -2
  156. data/smoke/if/test_expectations.yml +34 -0
  157. data/smoke/implements/a.rb +0 -2
  158. data/smoke/implements/test_expectations.yml +23 -0
  159. data/smoke/initialize/test_expectations.yml +1 -0
  160. data/smoke/integer/a.rb +0 -7
  161. data/smoke/integer/test_expectations.yml +101 -0
  162. data/smoke/interface/a.rb +0 -2
  163. data/smoke/interface/test_expectations.yml +23 -0
  164. data/smoke/kwbegin/a.rb +0 -1
  165. data/smoke/kwbegin/test_expectations.yml +17 -0
  166. data/smoke/lambda/a.rb +1 -4
  167. data/smoke/lambda/test_expectations.yml +39 -0
  168. data/smoke/literal/a.rb +0 -5
  169. data/smoke/literal/b.rb +0 -2
  170. data/smoke/literal/test_expectations.yml +106 -0
  171. data/smoke/map/test_expectations.yml +1 -0
  172. data/smoke/method/a.rb +0 -5
  173. data/smoke/method/b.rb +0 -1
  174. data/smoke/method/test_expectations.yml +90 -0
  175. data/smoke/module/a.rb +0 -2
  176. data/smoke/module/b.rb +0 -2
  177. data/smoke/module/c.rb +0 -1
  178. data/smoke/module/d.rb +0 -1
  179. data/smoke/module/f.rb +0 -2
  180. data/smoke/module/test_expectations.yml +75 -0
  181. data/smoke/regexp/a.rb +0 -38
  182. data/smoke/regexp/b.rb +0 -26
  183. data/smoke/regexp/test_expectations.yml +615 -0
  184. data/smoke/regression/set_divide.rb +0 -4
  185. data/smoke/regression/test_expectations.yml +43 -0
  186. data/smoke/rescue/a.rb +0 -5
  187. data/smoke/rescue/test_expectations.yml +79 -0
  188. data/smoke/self/a.rb +0 -2
  189. data/smoke/self/test_expectations.yml +23 -0
  190. data/smoke/skip/skip.rb +0 -2
  191. data/smoke/skip/test_expectations.yml +23 -0
  192. data/smoke/stdout/test_expectations.yml +1 -0
  193. data/smoke/super/a.rb +0 -4
  194. data/smoke/super/test_expectations.yml +79 -0
  195. data/smoke/toplevel/a.rb +0 -1
  196. data/smoke/toplevel/test_expectations.yml +15 -0
  197. data/smoke/tsort/Steepfile +2 -0
  198. data/smoke/tsort/a.rb +0 -3
  199. data/smoke/tsort/test_expectations.yml +63 -0
  200. data/smoke/type_case/a.rb +0 -4
  201. data/smoke/type_case/test_expectations.yml +48 -0
  202. data/smoke/unexpected/Steepfile +5 -0
  203. data/smoke/unexpected/test_expectations.yml +25 -0
  204. data/smoke/unexpected/unexpected.rb +1 -0
  205. data/smoke/unexpected/unexpected.rbs +3 -0
  206. data/smoke/yield/a.rb +0 -3
  207. data/smoke/yield/b.rb +6 -0
  208. data/smoke/yield/test_expectations.yml +68 -0
  209. data/steep.gemspec +4 -3
  210. metadata +144 -29
  211. data/bin/smoke_runner.rb +0 -139
  212. data/lib/steep/drivers/signature_error_printer.rb +0 -25
  213. data/lib/steep/errors.rb +0 -565
  214. data/lib/steep/project/file_loader.rb +0 -68
  215. data/lib/steep/project/signature_file.rb +0 -33
  216. data/lib/steep/project/source_file.rb +0 -129
  217. data/lib/steep/server/code_worker.rb +0 -137
  218. data/lib/steep/server/signature_worker.rb +0 -152
  219. data/lib/steep/server/utils.rb +0 -69
  220. data/lib/steep/signature/errors.rb +0 -82
  221. data/lib/steep/type_assignability.rb +0 -367
@@ -7,6 +7,9 @@ module Steep
7
7
  attr_reader :queue
8
8
 
9
9
  include Utils::DriverHelper
10
+ include Utils::JobsCount
11
+
12
+ LSP = LanguageServer::Protocol
10
13
 
11
14
  def initialize(stdout:, stderr:)
12
15
  @dirs = []
@@ -15,6 +18,10 @@ module Steep
15
18
  @queue = Thread::Queue.new
16
19
  end
17
20
 
21
+ def watching?(changed_path, files:, dirs:)
22
+ files.empty? || files.include?(changed_path) || dirs.intersect?(changed_path.ascend.to_set)
23
+ end
24
+
18
25
  def run()
19
26
  if dirs.empty?
20
27
  stdout.puts "Specify directories to watch"
@@ -23,10 +30,6 @@ module Steep
23
30
 
24
31
  project = load_config()
25
32
 
26
- loader = Project::FileLoader.new(project: project)
27
- loader.load_sources(dirs)
28
- loader.load_signatures()
29
-
30
33
  client_read, server_write = IO.pipe
31
34
  server_read, client_write = IO.pipe
32
35
 
@@ -36,17 +39,14 @@ module Steep
36
39
  server_reader = LanguageServer::Protocol::Transport::Io::Reader.new(server_read)
37
40
  server_writer = LanguageServer::Protocol::Transport::Io::Writer.new(server_write)
38
41
 
39
- interaction_worker = Server::WorkerProcess.spawn_worker(:interaction, name: "interaction", steepfile: project.steepfile_path)
40
- signature_worker = Server::WorkerProcess.spawn_worker(:signature, name: "signature", steepfile: project.steepfile_path)
41
- code_workers = Server::WorkerProcess.spawn_code_workers(steepfile: project.steepfile_path)
42
+ typecheck_workers = Server::WorkerProcess.spawn_typecheck_workers(steepfile: project.steepfile_path, args: dirs.map(&:to_s), count: jobs_count)
42
43
 
43
44
  master = Server::Master.new(
44
45
  project: project,
45
46
  reader: server_reader,
46
47
  writer: server_writer,
47
- interaction_worker: interaction_worker,
48
- signature_worker: signature_worker,
49
- code_workers: code_workers
48
+ interaction_worker: nil,
49
+ typecheck_workers: typecheck_workers
50
50
  )
51
51
 
52
52
  main_thread = Thread.start do
@@ -57,79 +57,100 @@ module Steep
57
57
  client_writer.write(method: "initialize", id: 0)
58
58
 
59
59
  Steep.logger.info "Watching #{dirs.join(", ")}..."
60
- listener = Listen.to(*dirs.map(&:to_s)) do |modified, added, removed|
61
- stdout.puts "🔬 Type checking updated files..."
60
+
61
+ watch_paths = dirs.map do |dir|
62
+ case
63
+ when dir.directory?
64
+ dir.realpath
65
+ when dir.file?
66
+ dir.parent.realpath
67
+ else
68
+ dir
69
+ end
70
+ end
71
+
72
+ dir_paths = Set.new(dirs.select(&:directory?).map(&:realpath))
73
+ file_paths = Set.new(dirs.select(&:file?).map(&:realpath))
74
+
75
+ listener = Listen.to(*watch_paths.map(&:to_s)) do |modified, added, removed|
76
+ stdout.puts Rainbow("🔬 Type checking updated files...").bold
62
77
 
63
78
  version = Time.now.to_i
64
79
  Steep.logger.tagged "watch" do
65
80
  Steep.logger.info "Received file system updates: modified=[#{modified.join(",")}], added=[#{added.join(",")}], removed=[#{removed.join(",")}]"
66
81
 
67
82
  (modified + added).each do |path|
68
- client_writer.write(
69
- method: "textDocument/didChange",
70
- params: {
71
- textDocument: {
72
- uri: "file://#{path}",
73
- version: version
74
- },
75
- contentChanges: [
76
- {
77
- text: Pathname(path).read
78
- }
79
- ]
80
- }
81
- )
83
+ p = Pathname(path)
84
+ if watching?(p, files: file_paths, dirs: dir_paths)
85
+ client_writer.write(
86
+ method: "textDocument/didChange",
87
+ params: {
88
+ textDocument: { uri: "file://#{path}", version: version },
89
+ contentChanges: [{ text: p.read }]
90
+ }
91
+ )
92
+ end
82
93
  end
83
94
 
84
95
  removed.each do |path|
85
- client_writer.write(
86
- method: "textDocument/didChange",
87
- params: {
88
- textDocument: {
89
- uri: "file://#{path}",
90
- version: version
91
- },
92
- contentChanges: [
93
- {
94
- text: ""
95
- }
96
- ]
97
- }
98
- )
96
+ if watching?(p, files: file_paths, dirs: dir_paths)
97
+ client_writer.write(
98
+ method: "textDocument/didChange",
99
+ params: {
100
+ textDocument: { uri: "file://#{path}", version: version },
101
+ contentChanges: [{ text: "" }]
102
+ }
103
+ )
104
+ end
99
105
  end
100
106
  end
101
107
  end.tap(&:start)
102
108
 
103
109
  begin
104
- stdout.puts "👀 Watching directories, Ctrl-C to stop."
110
+ stdout.puts Rainbow("👀 Watching directories, Ctrl-C to stop.").bold
105
111
  client_reader.read do |response|
106
112
  case response[:method]
107
113
  when "textDocument/publishDiagnostics"
108
114
  uri = URI.parse(response[:params][:uri])
109
115
  path = project.relative_path(Pathname(uri.path))
116
+ buffer = RBS::Buffer.new(content: path.read, name: path)
117
+ printer = DiagnosticPrinter.new(stdout: stdout, buffer: buffer)
110
118
 
111
119
  diagnostics = response[:params][:diagnostics]
112
120
 
113
121
  unless diagnostics.empty?
114
122
  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}"
123
+ printer.print(diagnostic)
120
124
  end
121
125
  end
126
+ when "window/showMessage"
127
+ # Assuming ERROR message means unrecoverable error.
128
+ message = response[:params]
129
+ if message[:type] == LSP::Constant::MessageType::ERROR
130
+ stdout.puts "Unexpected error reported... 🚨"
131
+ end
122
132
  end
123
133
  end
124
134
  rescue Interrupt
135
+ shutdown_id = -1
125
136
  stdout.puts "Shutting down workers..."
126
- client_writer.write({ method: :shutdown, id: 10000 })
137
+ client_writer.write({ method: :shutdown, id: shutdown_id })
138
+ client_reader.read do |response|
139
+ if response[:id] == shutdown_id
140
+ break
141
+ end
142
+ end
127
143
  client_writer.write({ method: :exit })
128
144
  client_writer.io.close()
129
145
  end
130
146
 
131
147
  listener.stop
132
- main_thread.join
148
+ begin
149
+ main_thread.join
150
+ rescue Interrupt
151
+ master.kill
152
+ main_thread.join
153
+ end
133
154
 
134
155
  0
135
156
  end
@@ -6,6 +6,10 @@ 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
10
+ attr_accessor :max_index
11
+ attr_accessor :index
12
+ attr_accessor :commandline_args
9
13
 
10
14
  include Utils::DriverHelper
11
15
 
@@ -13,30 +17,34 @@ module Steep
13
17
  @stdout = stdout
14
18
  @stderr = stderr
15
19
  @stdin = stdin
20
+ @commandline_args = []
16
21
  end
17
22
 
18
23
  def run()
19
24
  Steep.logger.tagged("#{worker_type}:#{worker_name}") do
20
25
  project = load_config()
21
26
 
22
- loader = Project::FileLoader.new(project: project)
23
- loader.load_sources([])
24
- loader.load_signatures()
25
-
26
27
  reader = LanguageServer::Protocol::Transport::Io::Reader.new(stdin)
27
28
  writer = LanguageServer::Protocol::Transport::Io::Writer.new(stdout)
28
29
 
29
30
  worker = case worker_type
30
- when :code
31
- Server::CodeWorker.new(project: project, reader: reader, writer: writer)
32
- when :signature
33
- Server::SignatureWorker.new(project: project, reader: reader, writer: writer)
31
+ when :typecheck
32
+ assignment = Services::PathAssignment.new(max_index: max_index, index: index)
33
+ Server::TypeCheckWorker.new(project: project,
34
+ reader: reader,
35
+ writer: writer,
36
+ assignment: assignment,
37
+ commandline_args: commandline_args)
34
38
  when :interaction
35
39
  Server::InteractionWorker.new(project: project, reader: reader, writer: writer)
36
40
  else
37
41
  raise "Unknown worker type: #{worker_type}"
38
42
  end
39
43
 
44
+ unless delay_shutdown
45
+ worker.skip_jobs_after_shutdown!
46
+ end
47
+
40
48
  Steep.logger.info "Starting #{worker_type} worker..."
41
49
 
42
50
  worker.run()
@@ -0,0 +1,159 @@
1
+ module Steep
2
+ class Expectations
3
+ class TestResult
4
+ attr_reader :path
5
+ attr_reader :expectation
6
+ attr_reader :actual
7
+
8
+ def initialize(path:, expectation:, actual:)
9
+ @path = path
10
+ @expectation = expectation
11
+ @actual = actual
12
+ end
13
+
14
+ def empty?
15
+ actual.empty?
16
+ end
17
+
18
+ def satisfied?
19
+ unexpected_diagnostics.empty? && missing_diagnostics.empty?
20
+ end
21
+
22
+ def each_diagnostics
23
+ if block_given?
24
+ expected_set = Set.new(expectation)
25
+ actual_set = Set.new(actual)
26
+
27
+ (expected_set + actual_set).sort_by {|a| Expectations.sort_key(a) }.each do |lsp|
28
+ case
29
+ when expected_set.include?(lsp) && actual_set.include?(lsp)
30
+ yield :expected, lsp
31
+ when expected_set.include?(lsp)
32
+ yield :missing, lsp
33
+ when actual_set.include?(lsp)
34
+ yield :unexpected, lsp
35
+ end
36
+ end
37
+ else
38
+ enum_for :each_diagnostics
39
+ end
40
+ end
41
+
42
+ def expected_diagnostics
43
+ each_diagnostics.select {|type, _| type == :expected }.map {|_, diag| diag }
44
+ end
45
+
46
+ def unexpected_diagnostics
47
+ each_diagnostics.select {|type, _| type == :unexpected }.map {|_, diag| diag }
48
+ end
49
+
50
+ def missing_diagnostics
51
+ each_diagnostics.select {|type, _| type == :missing }.map {|_, diag| diag }
52
+ end
53
+ end
54
+
55
+ LSP = LanguageServer::Protocol
56
+
57
+ attr_reader :diagnostics
58
+
59
+ def self.sort_key(hash)
60
+ [
61
+ hash.dig(:range, :start, :line),
62
+ hash.dig(:range, :start, :character),
63
+ hash.dig(:range, :end, :line),
64
+ hash.dig(:range, :end, :character),
65
+ hash[:code],
66
+ hash[:severity],
67
+ hash[:message]
68
+ ]
69
+ end
70
+
71
+ def initialize()
72
+ @diagnostics = {}
73
+ end
74
+
75
+ def test(path:, diagnostics:)
76
+ TestResult.new(path: path, expectation: self.diagnostics[path] || [], actual: diagnostics)
77
+ end
78
+
79
+ def self.empty
80
+ new()
81
+ end
82
+
83
+ def to_yaml
84
+ array = []
85
+
86
+ diagnostics.each_key.sort.each do |key|
87
+ ds = diagnostics[key]
88
+ array << {
89
+ "file" => key.to_s,
90
+ 'diagnostics' => ds.sort_by {|hash| Expectations.sort_key(hash) }
91
+ .map { |d| Expectations.lsp_to_hash(d) }
92
+ }
93
+ end
94
+
95
+ YAML.dump(array)
96
+ end
97
+
98
+ def self.load(path:, content:)
99
+ expectations = new()
100
+
101
+ YAML.load(content, filename: path.to_s).each do |entry|
102
+ file = Pathname(entry["file"])
103
+ expectations.diagnostics[file] = entry["diagnostics"]
104
+ .map {|hash| hash_to_lsp(hash) }
105
+ .sort_by! {|h| sort_key(h) }
106
+ end
107
+
108
+ expectations
109
+ end
110
+
111
+ # Translate hash to LSP Diagnostic message
112
+ def self.hash_to_lsp(hash)
113
+ {
114
+ range: {
115
+ start: {
116
+ line: hash.dig("range", "start", "line") - 1,
117
+ character: hash.dig("range", "start", "character")
118
+ },
119
+ end: {
120
+ line: hash.dig("range", "end", "line") - 1,
121
+ character: hash.dig("range", "end", "character")
122
+ }
123
+ },
124
+ severity: {
125
+ "ERROR" => LSP::Constant::DiagnosticSeverity::ERROR,
126
+ "WARNING" => LSP::Constant::DiagnosticSeverity::WARNING,
127
+ "INFORMATION" => LSP::Constant::DiagnosticSeverity::INFORMATION,
128
+ "HINT" => LSP::Constant::DiagnosticSeverity::HINT
129
+ }[hash["severity"] || "ERROR"],
130
+ message: hash["message"],
131
+ code: hash["code"]
132
+ }
133
+ end
134
+
135
+ # Translate LSP diagnostic message to hash
136
+ def self.lsp_to_hash(lsp)
137
+ {
138
+ "range" => {
139
+ "start" => {
140
+ "line" => lsp.dig(:range, :start, :line) + 1,
141
+ "character" => lsp.dig(:range, :start, :character)
142
+ },
143
+ "end" => {
144
+ "line" => lsp.dig(:range, :end, :line) + 1,
145
+ "character" => lsp.dig(:range, :end, :character)
146
+ }
147
+ },
148
+ "severity" => {
149
+ LSP::Constant::DiagnosticSeverity::ERROR => "ERROR",
150
+ LSP::Constant::DiagnosticSeverity::WARNING => "WARNING",
151
+ LSP::Constant::DiagnosticSeverity::INFORMATION => "INFORMATION",
152
+ LSP::Constant::DiagnosticSeverity::HINT => "HINT"
153
+ }[lsp[:severity] || LSP::Constant::DiagnosticSeverity::ERROR],
154
+ "message" => lsp[:message],
155
+ "code" => lsp[:code]
156
+ }
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,334 @@
1
+ module Steep
2
+ module Index
3
+ class RBSIndex
4
+ class TypeEntry
5
+ attr_reader :type_name
6
+ attr_reader :declarations
7
+ attr_reader :references
8
+
9
+ def initialize(type_name:)
10
+ @type_name = type_name
11
+ @declarations = Set[]
12
+ @references = Set[]
13
+ end
14
+
15
+ def add_declaration(decl)
16
+ case decl
17
+ when RBS::AST::Declarations::Class, RBS::AST::Declarations::Module
18
+ declarations << decl
19
+ when RBS::AST::Declarations::Interface
20
+ declarations << decl
21
+ when RBS::AST::Declarations::Alias
22
+ declarations << decl
23
+ else
24
+ raise "Unexpected type declaration: #{decl}"
25
+ end
26
+
27
+ self
28
+ end
29
+
30
+ def add_reference(ref)
31
+ case ref
32
+ when RBS::AST::Members::MethodDefinition
33
+ references << ref
34
+ when RBS::AST::Members::AttrAccessor, RBS::AST::Members::AttrReader, RBS::AST::Members::AttrWriter
35
+ references << ref
36
+ when RBS::AST::Members::InstanceVariable, RBS::AST::Members::ClassInstanceVariable, RBS::AST::Members::ClassVariable
37
+ references << ref
38
+ when RBS::AST::Members::Include, RBS::AST::Members::Extend
39
+ references << ref
40
+ when RBS::AST::Declarations::Module, RBS::AST::Declarations::Class
41
+ references << ref
42
+ when RBS::AST::Declarations::Constant, RBS::AST::Declarations::Global
43
+ references << ref
44
+ when RBS::AST::Declarations::Alias
45
+ references << ref
46
+ else
47
+ raise "Unexpected type reference: #{ref}"
48
+ end
49
+
50
+ self
51
+ end
52
+ end
53
+
54
+ class MethodEntry
55
+ attr_reader :method_name
56
+ attr_reader :declarations
57
+ attr_reader :references
58
+
59
+ def initialize(method_name:)
60
+ @method_name = method_name
61
+ @declarations = Set[]
62
+ @references = Set[]
63
+ end
64
+
65
+ def add_declaration(decl)
66
+ case decl
67
+ when RBS::AST::Members::MethodDefinition,
68
+ RBS::AST::Members::Alias,
69
+ RBS::AST::Members::AttrWriter,
70
+ RBS::AST::Members::AttrReader,
71
+ RBS::AST::Members::AttrAccessor
72
+ declarations << decl
73
+ else
74
+ raise "Unexpected method declaration: #{decl}"
75
+ end
76
+
77
+ self
78
+ end
79
+ end
80
+
81
+ class ConstantEntry
82
+ attr_reader :const_name
83
+ attr_reader :declarations
84
+
85
+ def initialize(const_name:)
86
+ @const_name = const_name
87
+ @declarations = Set[]
88
+ end
89
+
90
+ def add_declaration(decl)
91
+ case decl
92
+ when RBS::AST::Declarations::Constant
93
+ declarations << decl
94
+ else
95
+ raise
96
+ end
97
+
98
+ self
99
+ end
100
+ end
101
+
102
+ class GlobalEntry
103
+ attr_reader :global_name
104
+ attr_reader :declarations
105
+
106
+ def initialize(global_name:)
107
+ @global_name = global_name
108
+ @declarations = Set[]
109
+ end
110
+
111
+ def add_declaration(decl)
112
+ case decl
113
+ when RBS::AST::Declarations::Global
114
+ declarations << decl
115
+ else
116
+ raise
117
+ end
118
+
119
+ self
120
+ end
121
+ end
122
+
123
+ attr_reader :type_index
124
+ attr_reader :method_index
125
+ attr_reader :const_index
126
+ attr_reader :global_index
127
+
128
+ def initialize()
129
+ @type_index = {}
130
+ @method_index = {}
131
+ @const_index = {}
132
+ @global_index = {}
133
+ end
134
+
135
+ def entry(type_name: nil, method_name: nil, const_name: nil, global_name: nil)
136
+ case
137
+ when type_name
138
+ type_index[type_name] ||= TypeEntry.new(type_name: type_name)
139
+ when method_name
140
+ method_index[method_name] ||= MethodEntry.new(method_name: method_name)
141
+ when const_name
142
+ const_index[const_name] ||= ConstantEntry.new(const_name: const_name)
143
+ when global_name
144
+ global_index[global_name] ||= GlobalEntry.new(global_name: global_name)
145
+ else
146
+ raise
147
+ end
148
+ end
149
+
150
+ def each_entry(&block)
151
+ if block_given?
152
+ type_index.each_value(&block)
153
+ method_index.each_value(&block)
154
+ const_index.each_value(&block)
155
+ global_index.each_value(&block)
156
+ else
157
+ enum_for(:each_entry)
158
+ end
159
+ end
160
+
161
+ def add_type_declaration(type_name, declaration)
162
+ entry(type_name: type_name).add_declaration(declaration)
163
+ end
164
+
165
+ def add_method_declaration(method_name, member)
166
+ entry(method_name: method_name).add_declaration(member)
167
+ end
168
+
169
+ def add_constant_declaration(const_name, decl)
170
+ entry(const_name: const_name).add_declaration(decl)
171
+ end
172
+
173
+ def add_global_declaration(global_name, decl)
174
+ entry(global_name: global_name).add_declaration(decl)
175
+ end
176
+
177
+ def each_declaration(type_name: nil, method_name: nil, const_name: nil, global_name: nil, &block)
178
+ if block
179
+ entry = entry(type_name: type_name, method_name: method_name, const_name: const_name, global_name: global_name)
180
+ entry.declarations.each(&block)
181
+ else
182
+ enum_for(:each_declaration, type_name: type_name, method_name: method_name, const_name: const_name, global_name: global_name)
183
+ end
184
+ end
185
+
186
+ def add_type_reference(type_name, ref)
187
+ entry(type_name: type_name).add_reference(ref)
188
+ end
189
+
190
+ def each_reference(type_name: nil, &block)
191
+ if block
192
+ entry(type_name: type_name).references.each(&block)
193
+ else
194
+ enum_for(:each_reference, type_name: type_name)
195
+ end
196
+ end
197
+
198
+ class Builder
199
+ attr_reader :index
200
+
201
+ def initialize(index:)
202
+ @index = index
203
+ end
204
+
205
+ def member(type_name, member)
206
+ case member
207
+ when RBS::AST::Members::MethodDefinition
208
+ member.types.each do |method_type|
209
+ method_type.each_type do |type|
210
+ type_reference type, from: member
211
+ end
212
+ end
213
+
214
+ if member.instance?
215
+ method_name = InstanceMethodName.new(type_name: type_name, method_name: member.name)
216
+ index.add_method_declaration(method_name, member)
217
+ end
218
+
219
+ if member.singleton?
220
+ method_name = SingletonMethodName.new(type_name: type_name, method_name: member.name)
221
+ index.add_method_declaration(method_name, member)
222
+ end
223
+
224
+ when RBS::AST::Members::AttrAccessor, RBS::AST::Members::AttrReader, RBS::AST::Members::AttrWriter
225
+ type_reference member.type, from: member
226
+
227
+ if member.is_a?(RBS::AST::Members::AttrReader) || member.is_a?(RBS::AST::Members::AttrAccessor)
228
+ method_name = case member.kind
229
+ when :instance
230
+ InstanceMethodName.new(type_name: type_name, method_name: member.name)
231
+ when :singleton
232
+ SingletonMethodName.new(type_name: type_name, method_name: member.name)
233
+ end
234
+ index.add_method_declaration(method_name, member)
235
+ end
236
+
237
+ if member.is_a?(RBS::AST::Members::AttrWriter) || member.is_a?(RBS::AST::Members::AttrAccessor)
238
+ method_name = case member.kind
239
+ when :instance
240
+ InstanceMethodName.new(type_name: type_name, method_name: "#{member.name}=".to_sym)
241
+ when :singleton
242
+ SingletonMethodName.new(type_name: type_name, method_name: "#{member.name}=".to_sym)
243
+ end
244
+ index.add_method_declaration(method_name, member)
245
+ end
246
+
247
+ when RBS::AST::Members::InstanceVariable, RBS::AST::Members::ClassVariable, RBS::AST::Members::ClassInstanceVariable
248
+ type_reference member.type, from: member
249
+
250
+ when RBS::AST::Members::Include, RBS::AST::Members::Extend
251
+ index.add_type_reference member.name, member
252
+ member.args.each do |type|
253
+ type_reference type, from: member
254
+ end
255
+
256
+ when RBS::AST::Members::Alias
257
+ if member.instance?
258
+ new_name = InstanceMethodName.new(type_name: type_name, method_name: member.new_name)
259
+ index.add_method_declaration(new_name, member)
260
+ end
261
+
262
+ if member.singleton?
263
+ new_name = SingletonMethodName.new(type_name: type_name, method_name: member.new_name)
264
+ index.add_method_declaration(new_name, member)
265
+ end
266
+ end
267
+ end
268
+
269
+ def type_reference(type, from:)
270
+ case type
271
+ when RBS::Types::ClassInstance, RBS::Types::ClassSingleton, RBS::Types::Alias, RBS::Types::Interface
272
+ index.add_type_reference(type.name, from)
273
+ end
274
+
275
+ type.each_type do |ty|
276
+ type_reference ty, from: from
277
+ end
278
+ end
279
+
280
+ def env(env)
281
+ env.class_decls.each do |name, decl|
282
+ decl.decls.each do |d|
283
+ index.add_type_declaration(name, d.decl)
284
+
285
+ case d.decl
286
+ when RBS::AST::Declarations::Class
287
+ if super_class = d.decl.super_class
288
+ index.add_type_reference(super_class.name, d.decl)
289
+ super_class.args.each do |type|
290
+ type_reference(type, from: d.decl)
291
+ end
292
+ end
293
+ when RBS::AST::Declarations::Module
294
+ d.decl.self_types.each do |self_type|
295
+ index.add_type_reference(self_type.name, d.decl)
296
+ self_type.args.each do |type|
297
+ type_reference(type, from: d.decl)
298
+ end
299
+ end
300
+ end
301
+
302
+ d.decl.members.each do |member|
303
+ member(name, member)
304
+ end
305
+ end
306
+ end
307
+
308
+ env.interface_decls.each do |name, decl|
309
+ index.add_type_declaration(name, decl.decl)
310
+
311
+ decl.decl.members.each do |member|
312
+ member(name, member)
313
+ end
314
+ end
315
+
316
+ env.alias_decls.each do |name, decl|
317
+ index.add_type_declaration(name, decl.decl)
318
+ type_reference decl.decl.type, from: decl.decl
319
+ end
320
+
321
+ env.constant_decls.each do |name, decl|
322
+ index.add_constant_declaration(name, decl.decl)
323
+ type_reference decl.decl.type, from: decl.decl
324
+ end
325
+
326
+ env.global_decls.each do |name, decl|
327
+ index.add_global_declaration(name, decl.decl)
328
+ type_reference decl.decl.type, from: decl.decl
329
+ end
330
+ end
331
+ end
332
+ end
333
+ end
334
+ end