steep-relaxed 1.9.3.3

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 (165) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.gitmodules +0 -0
  4. data/CHANGELOG.md +1032 -0
  5. data/LICENSE +21 -0
  6. data/README.md +260 -0
  7. data/Rakefile +227 -0
  8. data/STDGEM_DEPENDENCIES.txt +59 -0
  9. data/Steepfile +68 -0
  10. data/bin/console +14 -0
  11. data/bin/generate-diagnostics-docs.rb +112 -0
  12. data/bin/mem_graph.rb +67 -0
  13. data/bin/mem_prof.rb +102 -0
  14. data/bin/output_rebaseline.rb +34 -0
  15. data/bin/output_test.rb +60 -0
  16. data/bin/rbs +20 -0
  17. data/bin/rbs-inline +19 -0
  18. data/bin/setup +9 -0
  19. data/bin/stackprof_test.rb +19 -0
  20. data/bin/steep +19 -0
  21. data/bin/steep-check.rb +251 -0
  22. data/bin/steep-prof +16 -0
  23. data/doc/narrowing.md +195 -0
  24. data/doc/shape.md +194 -0
  25. data/exe/steep +18 -0
  26. data/guides/README.md +5 -0
  27. data/guides/src/gem-rbs-collection/gem-rbs-collection.md +126 -0
  28. data/guides/src/getting-started/getting-started.md +163 -0
  29. data/guides/src/nil-optional/nil-optional.md +195 -0
  30. data/lib/steep/annotation_parser.rb +199 -0
  31. data/lib/steep/ast/annotation/collection.rb +172 -0
  32. data/lib/steep/ast/annotation.rb +137 -0
  33. data/lib/steep/ast/builtin.rb +104 -0
  34. data/lib/steep/ast/ignore.rb +148 -0
  35. data/lib/steep/ast/node/type_application.rb +88 -0
  36. data/lib/steep/ast/node/type_assertion.rb +81 -0
  37. data/lib/steep/ast/types/any.rb +35 -0
  38. data/lib/steep/ast/types/boolean.rb +45 -0
  39. data/lib/steep/ast/types/bot.rb +35 -0
  40. data/lib/steep/ast/types/class.rb +43 -0
  41. data/lib/steep/ast/types/factory.rb +557 -0
  42. data/lib/steep/ast/types/helper.rb +40 -0
  43. data/lib/steep/ast/types/instance.rb +42 -0
  44. data/lib/steep/ast/types/intersection.rb +93 -0
  45. data/lib/steep/ast/types/literal.rb +59 -0
  46. data/lib/steep/ast/types/logic.rb +84 -0
  47. data/lib/steep/ast/types/name.rb +128 -0
  48. data/lib/steep/ast/types/nil.rb +41 -0
  49. data/lib/steep/ast/types/proc.rb +117 -0
  50. data/lib/steep/ast/types/record.rb +79 -0
  51. data/lib/steep/ast/types/self.rb +43 -0
  52. data/lib/steep/ast/types/shared_instance.rb +11 -0
  53. data/lib/steep/ast/types/top.rb +35 -0
  54. data/lib/steep/ast/types/tuple.rb +60 -0
  55. data/lib/steep/ast/types/union.rb +97 -0
  56. data/lib/steep/ast/types/var.rb +65 -0
  57. data/lib/steep/ast/types/void.rb +35 -0
  58. data/lib/steep/cli.rb +401 -0
  59. data/lib/steep/diagnostic/deprecated/else_on_exhaustive_case.rb +20 -0
  60. data/lib/steep/diagnostic/deprecated/unknown_constant_assigned.rb +28 -0
  61. data/lib/steep/diagnostic/helper.rb +18 -0
  62. data/lib/steep/diagnostic/lsp_formatter.rb +78 -0
  63. data/lib/steep/diagnostic/result_printer2.rb +48 -0
  64. data/lib/steep/diagnostic/ruby.rb +1221 -0
  65. data/lib/steep/diagnostic/signature.rb +570 -0
  66. data/lib/steep/drivers/annotations.rb +52 -0
  67. data/lib/steep/drivers/check.rb +339 -0
  68. data/lib/steep/drivers/checkfile.rb +210 -0
  69. data/lib/steep/drivers/diagnostic_printer.rb +105 -0
  70. data/lib/steep/drivers/init.rb +66 -0
  71. data/lib/steep/drivers/langserver.rb +56 -0
  72. data/lib/steep/drivers/print_project.rb +113 -0
  73. data/lib/steep/drivers/stats.rb +203 -0
  74. data/lib/steep/drivers/utils/driver_helper.rb +143 -0
  75. data/lib/steep/drivers/utils/jobs_option.rb +26 -0
  76. data/lib/steep/drivers/vendor.rb +27 -0
  77. data/lib/steep/drivers/watch.rb +194 -0
  78. data/lib/steep/drivers/worker.rb +58 -0
  79. data/lib/steep/equatable.rb +23 -0
  80. data/lib/steep/expectations.rb +228 -0
  81. data/lib/steep/index/rbs_index.rb +350 -0
  82. data/lib/steep/index/signature_symbol_provider.rb +185 -0
  83. data/lib/steep/index/source_index.rb +167 -0
  84. data/lib/steep/interface/block.rb +103 -0
  85. data/lib/steep/interface/builder.rb +843 -0
  86. data/lib/steep/interface/function.rb +1090 -0
  87. data/lib/steep/interface/method_type.rb +330 -0
  88. data/lib/steep/interface/shape.rb +239 -0
  89. data/lib/steep/interface/substitution.rb +159 -0
  90. data/lib/steep/interface/type_param.rb +115 -0
  91. data/lib/steep/located_value.rb +20 -0
  92. data/lib/steep/method_name.rb +42 -0
  93. data/lib/steep/module_helper.rb +24 -0
  94. data/lib/steep/node_helper.rb +273 -0
  95. data/lib/steep/path_helper.rb +30 -0
  96. data/lib/steep/project/dsl.rb +268 -0
  97. data/lib/steep/project/group.rb +31 -0
  98. data/lib/steep/project/options.rb +63 -0
  99. data/lib/steep/project/pattern.rb +59 -0
  100. data/lib/steep/project/target.rb +92 -0
  101. data/lib/steep/project.rb +78 -0
  102. data/lib/steep/rake_task.rb +132 -0
  103. data/lib/steep/range_extension.rb +29 -0
  104. data/lib/steep/server/base_worker.rb +97 -0
  105. data/lib/steep/server/change_buffer.rb +73 -0
  106. data/lib/steep/server/custom_methods.rb +77 -0
  107. data/lib/steep/server/delay_queue.rb +45 -0
  108. data/lib/steep/server/interaction_worker.rb +492 -0
  109. data/lib/steep/server/lsp_formatter.rb +455 -0
  110. data/lib/steep/server/master.rb +922 -0
  111. data/lib/steep/server/target_group_files.rb +205 -0
  112. data/lib/steep/server/type_check_controller.rb +366 -0
  113. data/lib/steep/server/type_check_worker.rb +303 -0
  114. data/lib/steep/server/work_done_progress.rb +64 -0
  115. data/lib/steep/server/worker_process.rb +176 -0
  116. data/lib/steep/services/completion_provider.rb +802 -0
  117. data/lib/steep/services/content_change.rb +61 -0
  118. data/lib/steep/services/file_loader.rb +74 -0
  119. data/lib/steep/services/goto_service.rb +441 -0
  120. data/lib/steep/services/hover_provider/rbs.rb +88 -0
  121. data/lib/steep/services/hover_provider/ruby.rb +221 -0
  122. data/lib/steep/services/hover_provider/singleton_methods.rb +20 -0
  123. data/lib/steep/services/path_assignment.rb +46 -0
  124. data/lib/steep/services/signature_help_provider.rb +202 -0
  125. data/lib/steep/services/signature_service.rb +428 -0
  126. data/lib/steep/services/stats_calculator.rb +68 -0
  127. data/lib/steep/services/type_check_service.rb +394 -0
  128. data/lib/steep/services/type_name_completion.rb +236 -0
  129. data/lib/steep/signature/validator.rb +651 -0
  130. data/lib/steep/source/ignore_ranges.rb +69 -0
  131. data/lib/steep/source.rb +691 -0
  132. data/lib/steep/subtyping/cache.rb +30 -0
  133. data/lib/steep/subtyping/check.rb +1113 -0
  134. data/lib/steep/subtyping/constraints.rb +341 -0
  135. data/lib/steep/subtyping/relation.rb +101 -0
  136. data/lib/steep/subtyping/result.rb +324 -0
  137. data/lib/steep/subtyping/variable_variance.rb +89 -0
  138. data/lib/steep/test.rb +9 -0
  139. data/lib/steep/thread_waiter.rb +43 -0
  140. data/lib/steep/type_construction.rb +5183 -0
  141. data/lib/steep/type_inference/block_params.rb +416 -0
  142. data/lib/steep/type_inference/case_when.rb +303 -0
  143. data/lib/steep/type_inference/constant_env.rb +56 -0
  144. data/lib/steep/type_inference/context.rb +195 -0
  145. data/lib/steep/type_inference/logic_type_interpreter.rb +613 -0
  146. data/lib/steep/type_inference/method_call.rb +193 -0
  147. data/lib/steep/type_inference/method_params.rb +531 -0
  148. data/lib/steep/type_inference/multiple_assignment.rb +194 -0
  149. data/lib/steep/type_inference/send_args.rb +712 -0
  150. data/lib/steep/type_inference/type_env.rb +341 -0
  151. data/lib/steep/type_inference/type_env_builder.rb +138 -0
  152. data/lib/steep/typing.rb +321 -0
  153. data/lib/steep/version.rb +3 -0
  154. data/lib/steep.rb +369 -0
  155. data/manual/annotations.md +181 -0
  156. data/manual/ignore.md +20 -0
  157. data/manual/ruby-diagnostics.md +1879 -0
  158. data/sample/Steepfile +22 -0
  159. data/sample/lib/conference.rb +49 -0
  160. data/sample/lib/length.rb +35 -0
  161. data/sample/sig/conference.rbs +42 -0
  162. data/sample/sig/generics.rbs +15 -0
  163. data/sample/sig/length.rbs +34 -0
  164. data/steep-relaxed.gemspec +56 -0
  165. metadata +340 -0
@@ -0,0 +1,194 @@
1
+ module Steep
2
+ module Drivers
3
+ class Watch
4
+ attr_reader :dirs
5
+ attr_reader :stdout
6
+ attr_reader :stderr
7
+ attr_reader :queue
8
+ attr_accessor :severity_level
9
+ attr_reader :jobs_option
10
+
11
+ include Utils::DriverHelper
12
+
13
+ LSP = LanguageServer::Protocol
14
+
15
+ def initialize(stdout:, stderr:)
16
+ @dirs = []
17
+ @stdout = stdout
18
+ @stderr = stderr
19
+ @queue = Thread::Queue.new
20
+ @severity_level = :warning
21
+ @jobs_option = Utils::JobsOption.new()
22
+ end
23
+
24
+ def watching?(changed_path, files:, dirs:)
25
+ files.empty? || files.include?(changed_path) || dirs.intersect?(changed_path.ascend.to_set)
26
+ end
27
+
28
+ def run()
29
+ if dirs.empty?
30
+ stdout.puts "Specify directories to watch"
31
+ return 1
32
+ end
33
+
34
+ project = load_config()
35
+
36
+ client_read, server_write = IO.pipe
37
+ server_read, client_write = IO.pipe
38
+
39
+ client_reader = LanguageServer::Protocol::Transport::Io::Reader.new(client_read)
40
+ client_writer = LanguageServer::Protocol::Transport::Io::Writer.new(client_write)
41
+
42
+ server_reader = LanguageServer::Protocol::Transport::Io::Reader.new(server_read)
43
+ server_writer = LanguageServer::Protocol::Transport::Io::Writer.new(server_write)
44
+
45
+ typecheck_workers = Server::WorkerProcess.start_typecheck_workers(steepfile: project.steepfile_path, args: dirs.map(&:to_s), steep_command: jobs_option.steep_command, count: jobs_option.jobs_count_value)
46
+
47
+ master = Server::Master.new(
48
+ project: project,
49
+ reader: server_reader,
50
+ writer: server_writer,
51
+ interaction_worker: nil,
52
+ typecheck_workers: typecheck_workers
53
+ )
54
+ master.typecheck_automatically = false
55
+ master.commandline_args.push(*dirs.map(&:to_s))
56
+
57
+ main_thread = Thread.start do
58
+ master.start()
59
+ end
60
+
61
+ initialize_id = request_id()
62
+ client_writer.write(method: "initialize", id: initialize_id, params: DEFAULT_CLI_LSP_INITIALIZE_PARAMS)
63
+ wait_for_response_id(reader: client_reader, id: initialize_id)
64
+
65
+ Steep.logger.info "Watching #{dirs.join(", ")}..."
66
+
67
+ watch_paths = dirs.map do |dir|
68
+ case
69
+ when dir.directory?
70
+ dir.realpath
71
+ when dir.file?
72
+ dir.parent.realpath
73
+ else
74
+ dir
75
+ end
76
+ end
77
+
78
+ dir_paths = Set.new(dirs.select(&:directory?).map(&:realpath))
79
+ file_paths = Set.new(dirs.select(&:file?).map(&:realpath))
80
+
81
+ listener = Listen.to(*watch_paths.map(&:to_s)) do |modified, added, removed|
82
+ stdout.print Rainbow("🔬 Type checking updated files...").bold
83
+
84
+ version = Time.now.to_i
85
+ Steep.logger.tagged "watch" do
86
+ Steep.logger.info "Received file system updates: modified=[#{modified.join(",")}], added=[#{added.join(",")}], removed=[#{removed.join(",")}]"
87
+
88
+ (modified + added).each do |path|
89
+ p = Pathname(path)
90
+ if watching?(p, files: file_paths, dirs: dir_paths)
91
+ client_writer.write(
92
+ method: "textDocument/didChange",
93
+ params: {
94
+ textDocument: { uri: "file://#{path}", version: version },
95
+ contentChanges: [{ text: p.read }]
96
+ }
97
+ )
98
+ end
99
+ end
100
+
101
+ removed.each do |path|
102
+ p = Pathname(path)
103
+ if watching?(p, files: file_paths, dirs: dir_paths)
104
+ client_writer.write(
105
+ method: "textDocument/didChange",
106
+ params: {
107
+ textDocument: { uri: "file://#{path}", version: version },
108
+ contentChanges: [{ text: "" }]
109
+ }
110
+ )
111
+ end
112
+ end
113
+ end
114
+
115
+ params = { library_paths: [], signature_paths: [], code_paths: [] } #: Server::CustomMethods::TypeCheck::params
116
+
117
+ (modified + added).each do |path|
118
+ path = Pathname(path)
119
+ if target = project.target_for_source_path(path)
120
+ params[:code_paths] << [target.name.to_s, path.to_s]
121
+ end
122
+ if target = project.target_for_signature_path(path)
123
+ params[:signature_paths] << [target.name.to_s, path.to_s]
124
+ end
125
+ end
126
+
127
+ client_writer.write(Server::CustomMethods::TypeCheck.request(SecureRandom.uuid, params))
128
+
129
+ stdout.puts Rainbow("done!").bold
130
+ end.tap(&:start)
131
+
132
+ begin
133
+ stdout.puts Rainbow("👀 Watching directories, Ctrl-C to stop.").bold
134
+
135
+ params = { library_paths: [], signature_paths: [], code_paths: [] } #: Server::CustomMethods::TypeCheck::params
136
+ file_loader = Services::FileLoader.new(base_dir: project.base_dir)
137
+ project.targets.each do |target|
138
+ file_loader.each_path_in_patterns(target.source_pattern, dirs.map(&:to_s)) do |path|
139
+ params[:code_paths] << [target.name.to_s, project.absolute_path(path).to_s]
140
+ end
141
+ file_loader.each_path_in_patterns(target.signature_pattern, dirs.map(&:to_s)) do |path|
142
+ params[:signature_paths] << [target.name.to_s, project.absolute_path(path).to_s]
143
+ end
144
+ end
145
+ client_writer.write(Server::CustomMethods::TypeCheck.request(SecureRandom.uuid, params))
146
+
147
+ client_reader.read do |response|
148
+ case response[:method]
149
+ when "textDocument/publishDiagnostics"
150
+ path = PathHelper.to_pathname(response[:params][:uri]) or break
151
+ buffer = RBS::Buffer.new(content: path.read, name: path)
152
+ printer = DiagnosticPrinter.new(stdout: stdout, buffer: buffer)
153
+
154
+ diagnostics = response[:params][:diagnostics]
155
+ diagnostics.filter! {|d| keep_diagnostic?(d, severity_level: severity_level) }
156
+
157
+ unless diagnostics.empty?
158
+ diagnostics.each do |diagnostic|
159
+ printer.print(diagnostic)
160
+ stdout.flush
161
+ end
162
+ end
163
+ when "window/showMessage"
164
+ # Assuming ERROR message means unrecoverable error.
165
+ message = response[:params]
166
+ if message[:type] == LSP::Constant::MessageType::ERROR
167
+ stdout.puts "Unexpected error reported... 🚨"
168
+ end
169
+ end
170
+ end
171
+ rescue Interrupt
172
+ stdout.puts "Shutting down workers..."
173
+ shutdown_exit(reader: client_reader, writer: client_writer)
174
+ end
175
+
176
+ listener.stop
177
+ begin
178
+ main_thread.join
179
+ rescue Interrupt
180
+ master.kill
181
+ begin
182
+ main_thread.join
183
+ rescue
184
+ master.each_worker do |worker|
185
+ worker.kill(force: true)
186
+ end
187
+ end
188
+ end
189
+
190
+ 0
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,58 @@
1
+ module Steep
2
+ module Drivers
3
+ class Worker
4
+ attr_reader :stdout, :stderr, :stdin
5
+
6
+ attr_accessor :worker_type
7
+ attr_accessor :worker_name
8
+ attr_accessor :delay_shutdown
9
+ attr_accessor :max_index
10
+ attr_accessor :index
11
+ attr_accessor :commandline_args
12
+
13
+ include Utils::DriverHelper
14
+
15
+ def initialize(stdout:, stderr:, stdin:)
16
+ @stdout = stdout
17
+ @stderr = stderr
18
+ @stdin = stdin
19
+ @commandline_args = []
20
+ end
21
+
22
+ def run()
23
+ Steep.logger.tagged("#{worker_type}:#{worker_name}") do
24
+ project = load_config()
25
+
26
+ reader = LanguageServer::Protocol::Transport::Io::Reader.new(stdin)
27
+ writer = LanguageServer::Protocol::Transport::Io::Writer.new(stdout)
28
+
29
+ worker = case worker_type
30
+ when :typecheck
31
+ assignment = Services::PathAssignment.new(max_index: max_index, index: index)
32
+ Server::TypeCheckWorker.new(project: project,
33
+ reader: reader,
34
+ writer: writer,
35
+ assignment: assignment,
36
+ commandline_args: commandline_args)
37
+ when :interaction
38
+ Server::InteractionWorker.new(project: project, reader: reader, writer: writer)
39
+ else
40
+ raise "Unknown worker type: #{worker_type}"
41
+ end
42
+
43
+ unless delay_shutdown
44
+ worker.skip_jobs_after_shutdown!
45
+ end
46
+
47
+ Steep.logger.info "Starting #{worker_type} worker..."
48
+
49
+ worker.run()
50
+ rescue Interrupt
51
+ Steep.logger.info "Shutting down by interrupt..."
52
+ end
53
+
54
+ 0
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,23 @@
1
+ module Steep
2
+ module Equatable
3
+ def ==(other)
4
+ if other.class == self.class
5
+ instance_variables.all? do |name|
6
+ other.instance_variable_get(name) == instance_variable_get(name)
7
+ end
8
+ else
9
+ false
10
+ end
11
+ end
12
+
13
+ def eql?(other)
14
+ self == other
15
+ end
16
+
17
+ def hash
18
+ instance_variables.inject(self.class.hash) do |hash, name|
19
+ hash ^ instance_variable_get(name).hash
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,228 @@
1
+ module Steep
2
+ class Expectations
3
+ class Diagnostic < Struct.new(:start_position, :end_position, :severity, :message, :code, keyword_init: true)
4
+ DiagnosticSeverity = LanguageServer::Protocol::Constant::DiagnosticSeverity
5
+
6
+ def self.from_hash(hash)
7
+ start_position = {
8
+ line: hash.dig("range", "start", "line") - 1,
9
+ character: hash.dig("range", "start", "character")
10
+ } #: position
11
+ end_position = {
12
+ line: hash.dig("range", "end", "line") - 1,
13
+ character: hash.dig("range", "end", "character")
14
+ } #: position
15
+
16
+ severity =
17
+ case hash["severity"] || "ERROR"
18
+ when "ERROR"
19
+ :error
20
+ when "WARNING"
21
+ :warning
22
+ when "INFORMATION"
23
+ :information
24
+ when "HINT"
25
+ :hint
26
+ end #: Steep::Diagnostic::LSPFormatter::severity
27
+
28
+ Diagnostic.new(
29
+ start_position: start_position,
30
+ end_position: end_position,
31
+ severity: severity,
32
+ message: hash["message"],
33
+ code: hash["code"]
34
+ )
35
+ end
36
+
37
+ def self.from_lsp(diagnostic)
38
+ start_position = {
39
+ line: diagnostic.dig(:range, :start, :line),
40
+ character: diagnostic.dig(:range, :start, :character)
41
+ } #: position
42
+ end_position = {
43
+ line: diagnostic.dig(:range, :end, :line),
44
+ character: diagnostic.dig(:range, :end, :character)
45
+ } #: position
46
+
47
+ severity =
48
+ case diagnostic[:severity]
49
+ when DiagnosticSeverity::ERROR
50
+ :error
51
+ when DiagnosticSeverity::WARNING
52
+ :warning
53
+ when DiagnosticSeverity::INFORMATION
54
+ :information
55
+ when DiagnosticSeverity::HINT
56
+ :hint
57
+ else
58
+ :error
59
+ end #: Steep::Diagnostic::LSPFormatter::severity
60
+
61
+ Diagnostic.new(
62
+ start_position: start_position,
63
+ end_position: end_position,
64
+ severity: severity,
65
+ message: diagnostic[:message],
66
+ code: diagnostic[:code]
67
+ )
68
+ end
69
+
70
+ def to_hash
71
+ {
72
+ "range" => {
73
+ "start" => {
74
+ "line" => start_position[:line] + 1,
75
+ "character" => start_position[:character]
76
+ },
77
+ "end" => {
78
+ "line" => end_position[:line] + 1,
79
+ "character" => end_position[:character]
80
+ }
81
+ },
82
+ "severity" => severity.to_s.upcase,
83
+ "message" => message,
84
+ "code" => code
85
+ }
86
+ end
87
+
88
+ def lsp_severity
89
+ case severity
90
+ when :error
91
+ DiagnosticSeverity::ERROR
92
+ when :warning
93
+ DiagnosticSeverity::WARNING
94
+ when :information
95
+ DiagnosticSeverity::INFORMATION
96
+ when :hint
97
+ DiagnosticSeverity::HINT
98
+ else
99
+ raise
100
+ end
101
+ end
102
+
103
+ def to_lsp
104
+ {
105
+ range: {
106
+ start: {
107
+ line: start_position[:line],
108
+ character: start_position[:character]
109
+ },
110
+ end: {
111
+ line: end_position[:line],
112
+ character: end_position[:character]
113
+ }
114
+ },
115
+ severity: lsp_severity,
116
+ message: message,
117
+ code: code
118
+ }
119
+ end
120
+
121
+ def sort_key
122
+ [
123
+ start_position[:line],
124
+ start_position[:character],
125
+ end_position[:line],
126
+ end_position[:character],
127
+ code,
128
+ severity,
129
+ message
130
+ ]
131
+ end
132
+ end
133
+
134
+ class TestResult
135
+ attr_reader :path
136
+ attr_reader :expectation
137
+ attr_reader :actual
138
+
139
+ def initialize(path:, expectation:, actual:)
140
+ @path = path
141
+ @expectation = expectation
142
+ @actual = actual
143
+ end
144
+
145
+ def empty?
146
+ actual.empty?
147
+ end
148
+
149
+ def satisfied?
150
+ unexpected_diagnostics.empty? && missing_diagnostics.empty?
151
+ end
152
+
153
+ def each_diagnostics
154
+ if block_given?
155
+ expected_set = Set.new(expectation) #: Set[Diagnostic]
156
+ actual_set = Set.new(actual) #: Set[Diagnostic]
157
+
158
+ (expected_set + actual_set).sort_by(&:sort_key).each do |diagnostic|
159
+ case
160
+ when expected_set.include?(diagnostic) && actual_set.include?(diagnostic)
161
+ yield [:expected, diagnostic]
162
+ when expected_set.include?(diagnostic)
163
+ yield [:missing, diagnostic]
164
+ when actual_set.include?(diagnostic)
165
+ yield [:unexpected, diagnostic]
166
+ end
167
+ end
168
+ else
169
+ enum_for :each_diagnostics
170
+ end
171
+ end
172
+
173
+ def expected_diagnostics
174
+ each_diagnostics.select {|type, _| type == :expected }.map {|_, diag| diag }
175
+ end
176
+
177
+ def unexpected_diagnostics
178
+ each_diagnostics.select {|type, _| type == :unexpected }.map {|_, diag| diag }
179
+ end
180
+
181
+ def missing_diagnostics
182
+ each_diagnostics.select {|type, _| type == :missing }.map {|_, diag| diag }
183
+ end
184
+ end
185
+
186
+ LSP = LanguageServer::Protocol
187
+
188
+ attr_reader :diagnostics
189
+
190
+ def initialize()
191
+ @diagnostics = {}
192
+ end
193
+
194
+ def test(path:, diagnostics:)
195
+ TestResult.new(path: path, expectation: self.diagnostics[path] || [], actual: diagnostics)
196
+ end
197
+
198
+ def self.empty
199
+ new()
200
+ end
201
+
202
+ def to_yaml
203
+ array = [] #: Array[{ "file" => String, "diagnostics" => Array[untyped] }]
204
+
205
+ diagnostics.each_key.sort.each do |key|
206
+ ds = diagnostics.fetch(key)
207
+ array << {
208
+ "file" => key.to_s,
209
+ 'diagnostics' => ds.sort_by(&:sort_key).map(&:to_hash)
210
+ }
211
+ end
212
+
213
+ YAML.dump(array)
214
+ end
215
+
216
+ def self.load(path:, content:)
217
+ expectations = new()
218
+
219
+ YAML.load(content, filename: path.to_s).each do |entry|
220
+ file = Pathname(entry["file"])
221
+ expectations.diagnostics[file] =
222
+ entry["diagnostics"].map {|hash| Diagnostic.from_hash(hash) }.sort_by!(&:sort_key)
223
+ end
224
+
225
+ expectations
226
+ end
227
+ end
228
+ end