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,339 @@
1
+ module Steep
2
+ module Drivers
3
+ class Check
4
+ LSP = LanguageServer::Protocol
5
+
6
+ attr_reader :stdout
7
+ attr_reader :stderr
8
+ attr_reader :command_line_patterns
9
+ attr_accessor :with_expectations_path
10
+ attr_accessor :save_expectations_path
11
+ attr_accessor :severity_level
12
+ attr_reader :jobs_option
13
+ attr_reader :targets
14
+ attr_reader :active_group_names
15
+ attr_accessor :type_check_code
16
+ attr_accessor :validate_group_signatures
17
+ attr_accessor :validate_project_signatures
18
+ attr_accessor :validate_library_signatures
19
+
20
+ include Utils::DriverHelper
21
+
22
+ def initialize(stdout:, stderr:)
23
+ @stdout = stdout
24
+ @stderr = stderr
25
+ @command_line_patterns = []
26
+ @severity_level = :warning
27
+ @jobs_option = Utils::JobsOption.new()
28
+ @active_group_names = []
29
+ @type_check_code = true
30
+ @validate_group_signatures = true
31
+ @validate_project_signatures = false
32
+ @validate_library_signatures = false
33
+ end
34
+
35
+ def active_group?(group)
36
+ return true if active_group_names.empty?
37
+
38
+ case group
39
+ when Project::Target
40
+ active_group_names.any? {|target_name, group_name|
41
+ target_name == group.name && (group_name == nil || group_name == true)
42
+ }
43
+ when Project::Group
44
+ active_group_names.any? {|target_name, group_name|
45
+ target_name == group.target.name &&
46
+ (group_name == group.name || group_name == true)
47
+ }
48
+ end
49
+ end
50
+
51
+ def run
52
+ project = load_config()
53
+
54
+ stdout.puts Rainbow("# Type checking files:").bold
55
+ stdout.puts
56
+
57
+ client_read, server_write = IO.pipe
58
+ server_read, client_write = IO.pipe
59
+
60
+ client_reader = LSP::Transport::Io::Reader.new(client_read)
61
+ client_writer = LSP::Transport::Io::Writer.new(client_write)
62
+
63
+ server_reader = LSP::Transport::Io::Reader.new(server_read)
64
+ server_writer = LSP::Transport::Io::Writer.new(server_write)
65
+
66
+ typecheck_workers = Server::WorkerProcess.start_typecheck_workers(
67
+ steepfile: project.steepfile_path,
68
+ args: command_line_patterns,
69
+ delay_shutdown: true,
70
+ steep_command: jobs_option.steep_command,
71
+ count: jobs_option.jobs_count_value
72
+ )
73
+
74
+ master = Server::Master.new(
75
+ project: project,
76
+ reader: server_reader,
77
+ writer: server_writer,
78
+ interaction_worker: nil,
79
+ typecheck_workers: typecheck_workers
80
+ )
81
+ master.typecheck_automatically = false
82
+ master.commandline_args.push(*command_line_patterns)
83
+
84
+ main_thread = Thread.start do
85
+ Thread.current.abort_on_exception = true
86
+ master.start()
87
+ end
88
+
89
+ Steep.logger.info { "Initializing server" }
90
+ initialize_id = request_id()
91
+ client_writer.write({ method: :initialize, id: initialize_id, params: DEFAULT_CLI_LSP_INITIALIZE_PARAMS })
92
+ wait_for_response_id(reader: client_reader, id: initialize_id)
93
+
94
+ params = { library_paths: [], signature_paths: [], code_paths: [] } #: Server::CustomMethods::TypeCheck::params
95
+
96
+ if command_line_patterns.empty?
97
+ files = Server::TargetGroupFiles.new(project)
98
+ loader = Services::FileLoader.new(base_dir: project.base_dir)
99
+
100
+ project.targets.each do |target|
101
+ target.new_env_loader.each_dir do |_, dir|
102
+ RBS::FileFinder.each_file(dir, skip_hidden: true) do |path|
103
+ files.add_library_path(target, path)
104
+ end
105
+ end
106
+
107
+ loader.each_path_in_target(target) do |path|
108
+ files.add_path(path)
109
+ end
110
+ end
111
+
112
+ project.targets.each do |target|
113
+ target.groups.each do |group|
114
+ if active_group?(group)
115
+ load_files(files, group.target, group, params: params)
116
+ end
117
+ end
118
+ if active_group?(target)
119
+ load_files(files, target, target, params: params)
120
+ end
121
+ end
122
+ else
123
+ command_line_patterns.each do |pattern|
124
+ path = Pathname(pattern)
125
+ path = project.absolute_path(path)
126
+ next unless path.file?
127
+ if target = project.target_for_source_path(path)
128
+ params[:code_paths] << [target.name.to_s, path.to_s]
129
+ end
130
+ if target = project.target_for_signature_path(path)
131
+ params[:signature_paths] << [target.name.to_s, path.to_s]
132
+ end
133
+ end
134
+ end
135
+
136
+ Steep.logger.info { "Starting type check with #{params[:code_paths].size} Ruby files and #{params[:signature_paths].size} RBS signatures..." }
137
+ Steep.logger.debug { params.inspect }
138
+
139
+ request_guid = SecureRandom.uuid
140
+ Steep.logger.info { "Starting type checking: #{request_guid}" }
141
+ client_writer.write(Server::CustomMethods::TypeCheck.request(request_guid, params))
142
+
143
+ diagnostic_notifications = [] #: Array[LanguageServer::Protocol::Interface::PublishDiagnosticsParams]
144
+ error_messages = [] #: Array[String]
145
+
146
+ response = wait_for_response_id(reader: client_reader, id: request_guid) do |message|
147
+ case
148
+ when message[:method] == "textDocument/publishDiagnostics"
149
+ ds = message[:params][:diagnostics]
150
+ ds.select! {|d| keep_diagnostic?(d, severity_level: severity_level) }
151
+ if ds.empty?
152
+ stdout.print "."
153
+ else
154
+ stdout.print "F"
155
+ end
156
+ diagnostic_notifications << message[:params]
157
+ stdout.flush
158
+ when message[:method] == "window/showMessage"
159
+ # Assuming ERROR message means unrecoverable error.
160
+ message = message[:params]
161
+ if message[:type] == LSP::Constant::MessageType::ERROR
162
+ error_messages << message[:message]
163
+ end
164
+ end
165
+ end
166
+
167
+ Steep.logger.info { "Finished type checking: #{response.inspect}" }
168
+
169
+ Steep.logger.info { "Shutting down..." }
170
+
171
+ shutdown_exit(reader: client_reader, writer: client_writer)
172
+ main_thread.join()
173
+
174
+ stdout.puts
175
+ stdout.puts
176
+
177
+ if error_messages.empty?
178
+ loader = Services::FileLoader.new(base_dir: project.base_dir)
179
+ all_files = project.targets.each.with_object(Set[]) do |target, set|
180
+ set.merge(loader.load_changes(target.source_pattern, command_line_patterns, changes: {}).each_key)
181
+ set.merge(loader.load_changes(target.signature_pattern, changes: {}).each_key)
182
+ end.to_a
183
+
184
+ case
185
+ when with_expectations_path
186
+ print_expectations(project: project,
187
+ all_files: all_files,
188
+ expectations_path: with_expectations_path,
189
+ notifications: diagnostic_notifications)
190
+ when save_expectations_path
191
+ save_expectations(project: project,
192
+ all_files: all_files,
193
+ expectations_path: save_expectations_path,
194
+ notifications: diagnostic_notifications)
195
+ else
196
+ print_result(project: project, notifications: diagnostic_notifications)
197
+ end
198
+ else
199
+ stdout.puts Rainbow("Unexpected error reported. 🚨").red.bold
200
+ 1
201
+ end
202
+ rescue Errno::EPIPE => error
203
+ stdout.puts Rainbow("Steep shutdown with an error: #{error.inspect}").red.bold
204
+ return 1
205
+ end
206
+
207
+ def load_files(files, target, group, params:)
208
+ if type_check_code
209
+ files.each_group_source_path(group, true) do |path|
210
+ params[:code_paths] << [target.name.to_s, target.project.absolute_path(path).to_s]
211
+ end
212
+ end
213
+ if validate_group_signatures
214
+ files.each_group_signature_path(group) do |path|
215
+ params[:signature_paths] << [target.name.to_s, target.project.absolute_path(path).to_s]
216
+ end
217
+ end
218
+ if validate_project_signatures
219
+ files.each_project_signature_path(target) do |path|
220
+ if path_target = files.signature_path_target(path)
221
+ params[:signature_paths] << [path_target.name.to_s, target.project.absolute_path(path).to_s]
222
+ end
223
+ end
224
+ if group.is_a?(Project::Group)
225
+ files.each_target_signature_path(target, group) do |path|
226
+ params[:signature_paths] << [target.name.to_s, target.project.absolute_path(path).to_s]
227
+ end
228
+ end
229
+ end
230
+ if validate_library_signatures
231
+ files.each_library_path(target) do |path|
232
+ params[:library_paths] << [target.name.to_s, path.to_s]
233
+ end
234
+ end
235
+ end
236
+
237
+ def print_expectations(project:, all_files:, expectations_path:, notifications:)
238
+ expectations = Expectations.load(path: expectations_path, content: expectations_path.read)
239
+
240
+ expected_count = 0
241
+ unexpected_count = 0
242
+ missing_count = 0
243
+
244
+ ns = notifications.each.with_object({}) do |notification, hash| #$ Hash[Pathname, Array[Expectations::Diagnostic]]
245
+ path = project.relative_path(Steep::PathHelper.to_pathname(notification[:uri]) || raise)
246
+ hash[path] = notification[:diagnostics].map do |diagnostic|
247
+ Expectations::Diagnostic.from_lsp(diagnostic)
248
+ end
249
+ end
250
+
251
+ all_files.sort.each do |path|
252
+ test = expectations.test(path: path, diagnostics: ns[path] || [])
253
+
254
+ buffer = RBS::Buffer.new(name: path, content: path.read)
255
+ printer = DiagnosticPrinter.new(buffer: buffer, stdout: stdout)
256
+
257
+ test.each_diagnostics.each do |type, diag|
258
+ case type
259
+ when :expected
260
+ expected_count += 1
261
+ when :unexpected
262
+ unexpected_count += 1
263
+ printer.print(diag.to_lsp, prefix: Rainbow("+ ").green)
264
+ when :missing
265
+ missing_count += 1
266
+ printer.print(diag.to_lsp, prefix: Rainbow("- ").red, source: false)
267
+ end
268
+ end
269
+ end
270
+
271
+ if unexpected_count > 0 || missing_count > 0
272
+ stdout.puts
273
+
274
+ stdout.puts Rainbow("Expectations unsatisfied:").bold.red
275
+ stdout.puts " #{expected_count} expected #{"diagnostic".pluralize(expected_count)}"
276
+ stdout.puts Rainbow(" + #{unexpected_count} unexpected #{"diagnostic".pluralize(unexpected_count)}").green
277
+ stdout.puts Rainbow(" - #{missing_count} missing #{"diagnostic".pluralize(missing_count)}").red
278
+ 1
279
+ else
280
+ stdout.puts Rainbow("Expectations satisfied:").bold.green
281
+ stdout.puts " #{expected_count} expected #{"diagnostic".pluralize(expected_count)}"
282
+ 0
283
+ end
284
+ end
285
+
286
+ def save_expectations(project:, all_files:, expectations_path:, notifications:)
287
+ expectations = if expectations_path.file?
288
+ Expectations.load(path: expectations_path, content: expectations_path.read)
289
+ else
290
+ Expectations.empty()
291
+ end
292
+
293
+ ns = notifications.each.with_object({}) do |notification, hash| #$ Hash[Pathname, Array[Expectations::Diagnostic]]
294
+ path = project.relative_path(Steep::PathHelper.to_pathname(notification[:uri]) || raise)
295
+ hash[path] = notification[:diagnostics].map {|diagnostic| Expectations::Diagnostic.from_lsp(diagnostic) }
296
+ end
297
+
298
+ all_files.sort.each do |path|
299
+ ds = ns[path] || []
300
+
301
+ if ds.empty?
302
+ expectations.diagnostics.delete(path)
303
+ else
304
+ expectations.diagnostics[path] = ds
305
+ end
306
+ end
307
+
308
+ expectations_path.write(expectations.to_yaml)
309
+ stdout.puts Rainbow("Saved expectations in #{expectations_path}...").bold
310
+ 0
311
+ end
312
+
313
+ def print_result(project:, notifications:)
314
+ if notifications.all? {|notification| notification[:diagnostics].empty? }
315
+ emoji = %w(🫖 🫖 🫖 🫖 🫖 🫖 🫖 🫖 🍵 🧋 🧉).sample
316
+ stdout.puts Rainbow("No type error detected. #{emoji}").green.bold
317
+ 0
318
+ else
319
+ errors = notifications.reject {|notification| notification[:diagnostics].empty? }
320
+ total = errors.sum {|notification| notification[:diagnostics].size }
321
+
322
+ errors.each do |notification|
323
+ path = Steep::PathHelper.to_pathname(notification[:uri]) or raise
324
+ buffer = RBS::Buffer.new(name: project.relative_path(path), content: path.read)
325
+ printer = DiagnosticPrinter.new(buffer: buffer, stdout: stdout)
326
+
327
+ notification[:diagnostics].each do |diag|
328
+ printer.print(diag)
329
+ stdout.puts
330
+ end
331
+ end
332
+
333
+ stdout.puts Rainbow("Detected #{total} #{"problem".pluralize(total)} from #{errors.size} #{"file".pluralize(errors.size)}").red.bold
334
+ 1
335
+ end
336
+ end
337
+ end
338
+ end
339
+ end
@@ -0,0 +1,210 @@
1
+ module Steep
2
+ module Drivers
3
+ class Checkfile
4
+ LSP = LanguageServer::Protocol
5
+
6
+ attr_reader :stdout
7
+ attr_reader :stderr
8
+ attr_reader :command_line_args
9
+ attr_accessor :all_ruby, :all_rbs
10
+ attr_reader :stdin_input
11
+ attr_reader :jobs_option
12
+
13
+ include Utils::DriverHelper
14
+
15
+ def initialize(stdout:, stderr:)
16
+ @stdout = stdout
17
+ @stderr = stderr
18
+ @command_line_args = []
19
+
20
+ @all_rbs = false
21
+ @all_ruby = false
22
+ @stdin_input = {}
23
+
24
+ @jobs_option = Utils::JobsOption.new()
25
+ end
26
+
27
+ def run
28
+ return 0 if command_line_args.empty? && !all_rbs && !all_ruby && stdin_input.empty?
29
+
30
+ project = load_config()
31
+
32
+ client_read, server_write = IO.pipe
33
+ server_read, client_write = IO.pipe
34
+
35
+ client_reader = LanguageServer::Protocol::Transport::Io::Reader.new(client_read)
36
+ client_writer = LanguageServer::Protocol::Transport::Io::Writer.new(client_write)
37
+
38
+ server_reader = LanguageServer::Protocol::Transport::Io::Reader.new(server_read)
39
+ server_writer = LanguageServer::Protocol::Transport::Io::Writer.new(server_write)
40
+
41
+ # @type var target_paths: Set[Pathname]
42
+ target_paths = Set[]
43
+ # @type var signature_paths: Set[Pathname]
44
+ signature_paths = Set[]
45
+
46
+ loader = Services::FileLoader.new(base_dir: project.base_dir)
47
+ project.targets.each do |target|
48
+ ruby_patterns =
49
+ case
50
+ when all_ruby
51
+ []
52
+ when command_line_args.empty?
53
+ nil
54
+ else
55
+ command_line_args
56
+ end #: Array[String]?
57
+
58
+ if ruby_patterns
59
+ loader.each_path_in_patterns(target.source_pattern, ruby_patterns) do |path|
60
+ target_paths << path
61
+ end
62
+ end
63
+
64
+ rbs_patterns =
65
+ case
66
+ when all_rbs
67
+ []
68
+ when command_line_args.empty?
69
+ nil
70
+ else
71
+ command_line_args
72
+ end #: Array[String]
73
+
74
+ if rbs_patterns
75
+ loader.each_path_in_patterns(target.signature_pattern, rbs_patterns) do |path|
76
+ signature_paths << path
77
+ end
78
+ end
79
+ end
80
+
81
+ stdin_input.each_key do |path|
82
+ case
83
+ when project.target_for_signature_path(path)
84
+ signature_paths << path
85
+ when project.target_for_source_path(path)
86
+ target_paths << path
87
+ end
88
+ end
89
+
90
+ files = target_paths + signature_paths
91
+
92
+ count =
93
+ if jobs_option.jobs_count
94
+ jobs_option.jobs_count
95
+ else
96
+ [
97
+ files.size + 2,
98
+ jobs_option.default_jobs_count
99
+ ].min || raise
100
+ end
101
+
102
+ Steep.logger.info { "Starting #{count} workers for #{files.size} files..." }
103
+
104
+ typecheck_workers = Server::WorkerProcess.start_typecheck_workers(
105
+ steepfile: project.steepfile_path,
106
+ args: [],
107
+ delay_shutdown: true,
108
+ steep_command: jobs_option.steep_command,
109
+ count: count
110
+ )
111
+
112
+ master = Server::Master.new(
113
+ project: project,
114
+ reader: server_reader,
115
+ writer: server_writer,
116
+ interaction_worker: nil,
117
+ typecheck_workers: typecheck_workers
118
+ )
119
+ master.typecheck_automatically = false
120
+
121
+ main_thread = Thread.start do
122
+ Thread.current.abort_on_exception = true
123
+ master.start()
124
+ end
125
+
126
+ Steep.logger.info { "Initializing server" }
127
+ initialize_id = request_id()
128
+ client_writer.write({ method: :initialize, id: initialize_id, params: DEFAULT_CLI_LSP_INITIALIZE_PARAMS })
129
+ wait_for_response_id(reader: client_reader, id: initialize_id)
130
+
131
+ stdin_input.each do |path, content|
132
+ uri = PathHelper.to_uri(project.absolute_path(path))
133
+
134
+ master.broadcast_notification(
135
+ {
136
+ method: "textDocument/didChange",
137
+ params: {
138
+ textDocument: { uri: uri, version: 0 },
139
+ contentChanges: [{ text: content }]
140
+ }
141
+ }
142
+ )
143
+ master.broadcast_notification(
144
+ {
145
+ method: "textDocument/didSave",
146
+ params: {
147
+ textDocument: { uri: uri }
148
+ }
149
+ }
150
+ )
151
+ end
152
+
153
+ ping_guid = master.fresh_request_id()
154
+ client_writer.write({ method: "$/ping", id: ping_guid, params: {}})
155
+ wait_for_response_id(reader: client_reader, id: ping_guid)
156
+
157
+ request_guid = master.fresh_request_id()
158
+ progress = master.work_done_progress(request_guid)
159
+ request = Server::TypeCheckController::Request.new(guid: request_guid, progress: progress)
160
+
161
+ target_paths.each do |path|
162
+ target = project.target_for_source_path(path) or next
163
+ request.code_paths << [target.name, project.absolute_path(path)]
164
+ end
165
+ signature_paths.each do |path|
166
+ target = project.target_for_signature_path(path) or next
167
+ request.signature_paths << [target.name, project.absolute_path(path)]
168
+ end
169
+
170
+ request.needs_response = true
171
+ master.start_type_check(request: request, last_request: nil, report_progress_threshold: 0)
172
+
173
+ Steep.logger.info { "Starting type checking: #{request_guid}" }
174
+
175
+ error_messages = [] #: Array[String]
176
+ client_reader.read do |response|
177
+ Steep.logger.info { response.inspect }
178
+ case
179
+ when response[:method] == "textDocument/publishDiagnostics"
180
+ params = response[:params]
181
+
182
+ if path = PathHelper.to_pathname(params[:uri])
183
+ stdout.puts(
184
+ {
185
+ path: project.relative_path(path).to_s,
186
+ diagnostics: params[:diagnostics]
187
+ }.to_json
188
+ )
189
+ end
190
+ when response[:method] == "window/showMessage"
191
+ # Assuming ERROR message means unrecoverable error.
192
+ message = response[:params]
193
+ if message[:type] == LSP::Constant::MessageType::ERROR
194
+ error_messages << message[:message]
195
+ end
196
+ when response[:id] == request_guid
197
+ break
198
+ end
199
+ end
200
+
201
+ Steep.logger.info { "Shutting down..." }
202
+
203
+ shutdown_exit(reader: client_reader, writer: client_writer)
204
+ main_thread.join()
205
+
206
+ 0
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,105 @@
1
+ module Steep
2
+ module Drivers
3
+ class DiagnosticPrinter
4
+ LSP = LanguageServer::Protocol
5
+
6
+ attr_reader :stdout
7
+ attr_reader :buffer
8
+
9
+ def initialize(stdout:, buffer:)
10
+ @stdout = stdout
11
+ @buffer = buffer
12
+ end
13
+
14
+ def path
15
+ Pathname(buffer.name)
16
+ end
17
+
18
+ def color_severity(string, severity:)
19
+ s = Rainbow(string)
20
+
21
+ case severity
22
+ when LSP::Constant::DiagnosticSeverity::ERROR
23
+ s.red
24
+ when LSP::Constant::DiagnosticSeverity::WARNING
25
+ s.yellow
26
+ when LSP::Constant::DiagnosticSeverity::INFORMATION
27
+ s.blue
28
+ else
29
+ s
30
+ end
31
+ end
32
+
33
+ def severity_message(severity)
34
+ string = case severity
35
+ when LSP::Constant::DiagnosticSeverity::ERROR
36
+ "error"
37
+ when LSP::Constant::DiagnosticSeverity::WARNING
38
+ "warning"
39
+ when LSP::Constant::DiagnosticSeverity::INFORMATION
40
+ "information"
41
+ when LSP::Constant::DiagnosticSeverity::HINT
42
+ "hint"
43
+ else
44
+ raise
45
+ end
46
+
47
+ color_severity(string, severity: severity)
48
+ end
49
+
50
+ def location(diagnostic)
51
+ start = diagnostic[:range][:start]
52
+ Rainbow("#{path}:#{start[:line]+1}:#{start[:character]}").magenta
53
+ end
54
+
55
+ def print(diagnostic, prefix: "", source: true)
56
+ header, *rest = diagnostic[:message].split(/\n/)
57
+
58
+ stdout.puts "#{prefix}#{location(diagnostic)}: [#{severity_message(diagnostic[:severity])}] #{Rainbow(header).underline}"
59
+
60
+ unless rest.empty?
61
+ rest.each do |message|
62
+ stdout.puts "#{prefix}│ #{message}"
63
+ end
64
+ end
65
+
66
+ if diagnostic[:code]
67
+ stdout.puts "#{prefix}│" unless rest.empty?
68
+ stdout.puts "#{prefix}│ Diagnostic ID: #{diagnostic[:code]}"
69
+ end
70
+
71
+ stdout.puts "#{prefix}│"
72
+
73
+ if source
74
+ print_source_line(diagnostic, prefix: prefix)
75
+ else
76
+ stdout.puts "#{prefix}└ (no source code available)"
77
+ stdout.puts "#{prefix}"
78
+ end
79
+ end
80
+
81
+ def print_source_line(diagnostic, prefix: "")
82
+ start_pos = diagnostic[:range][:start]
83
+ end_pos = diagnostic[:range][:end]
84
+
85
+ line = buffer.lines.fetch(start_pos[:line])
86
+
87
+ leading = line[0...start_pos[:character]] || ""
88
+ if start_pos[:line] == end_pos[:line]
89
+ subject = line[start_pos[:character]...end_pos[:character]] || ""
90
+ trailing = (line[end_pos[:character]...] || "").chomp
91
+ else
92
+ subject = (line[start_pos[:character]...] || "").chomp
93
+ trailing = ""
94
+ end
95
+
96
+ unless subject.valid_encoding?
97
+ subject.scrub!
98
+ end
99
+
100
+ stdout.puts "#{prefix}└ #{leading}#{color_severity(subject, severity: diagnostic[:severity])}#{trailing}"
101
+ stdout.puts "#{prefix} #{" " * leading.size}#{"~" * subject.size}"
102
+ end
103
+ end
104
+ end
105
+ end