steep-activesupport-4 1.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) 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/Steepfile +68 -0
  9. data/bin/console +14 -0
  10. data/bin/generate-diagnostics-docs.rb +112 -0
  11. data/bin/mem_graph.rb +67 -0
  12. data/bin/mem_prof.rb +102 -0
  13. data/bin/output_rebaseline.rb +34 -0
  14. data/bin/output_test.rb +60 -0
  15. data/bin/rbs +20 -0
  16. data/bin/rbs-inline +19 -0
  17. data/bin/setup +9 -0
  18. data/bin/stackprof_test.rb +19 -0
  19. data/bin/steep +19 -0
  20. data/bin/steep-check.rb +251 -0
  21. data/bin/steep-prof +16 -0
  22. data/doc/narrowing.md +195 -0
  23. data/doc/shape.md +194 -0
  24. data/exe/steep +18 -0
  25. data/guides/README.md +5 -0
  26. data/guides/src/gem-rbs-collection/gem-rbs-collection.md +126 -0
  27. data/guides/src/getting-started/getting-started.md +163 -0
  28. data/guides/src/nil-optional/nil-optional.md +195 -0
  29. data/lib/steep/annotation_parser.rb +199 -0
  30. data/lib/steep/ast/annotation/collection.rb +172 -0
  31. data/lib/steep/ast/annotation.rb +137 -0
  32. data/lib/steep/ast/builtin.rb +104 -0
  33. data/lib/steep/ast/ignore.rb +148 -0
  34. data/lib/steep/ast/node/type_application.rb +88 -0
  35. data/lib/steep/ast/node/type_assertion.rb +81 -0
  36. data/lib/steep/ast/types/any.rb +35 -0
  37. data/lib/steep/ast/types/boolean.rb +45 -0
  38. data/lib/steep/ast/types/bot.rb +35 -0
  39. data/lib/steep/ast/types/class.rb +43 -0
  40. data/lib/steep/ast/types/factory.rb +557 -0
  41. data/lib/steep/ast/types/helper.rb +40 -0
  42. data/lib/steep/ast/types/instance.rb +42 -0
  43. data/lib/steep/ast/types/intersection.rb +93 -0
  44. data/lib/steep/ast/types/literal.rb +59 -0
  45. data/lib/steep/ast/types/logic.rb +84 -0
  46. data/lib/steep/ast/types/name.rb +128 -0
  47. data/lib/steep/ast/types/nil.rb +41 -0
  48. data/lib/steep/ast/types/proc.rb +117 -0
  49. data/lib/steep/ast/types/record.rb +79 -0
  50. data/lib/steep/ast/types/self.rb +43 -0
  51. data/lib/steep/ast/types/shared_instance.rb +11 -0
  52. data/lib/steep/ast/types/top.rb +35 -0
  53. data/lib/steep/ast/types/tuple.rb +60 -0
  54. data/lib/steep/ast/types/union.rb +97 -0
  55. data/lib/steep/ast/types/var.rb +65 -0
  56. data/lib/steep/ast/types/void.rb +35 -0
  57. data/lib/steep/cli.rb +401 -0
  58. data/lib/steep/diagnostic/deprecated/else_on_exhaustive_case.rb +20 -0
  59. data/lib/steep/diagnostic/deprecated/unknown_constant_assigned.rb +28 -0
  60. data/lib/steep/diagnostic/helper.rb +18 -0
  61. data/lib/steep/diagnostic/lsp_formatter.rb +78 -0
  62. data/lib/steep/diagnostic/result_printer2.rb +48 -0
  63. data/lib/steep/diagnostic/ruby.rb +1221 -0
  64. data/lib/steep/diagnostic/signature.rb +570 -0
  65. data/lib/steep/drivers/annotations.rb +52 -0
  66. data/lib/steep/drivers/check.rb +339 -0
  67. data/lib/steep/drivers/checkfile.rb +210 -0
  68. data/lib/steep/drivers/diagnostic_printer.rb +105 -0
  69. data/lib/steep/drivers/init.rb +66 -0
  70. data/lib/steep/drivers/langserver.rb +56 -0
  71. data/lib/steep/drivers/print_project.rb +113 -0
  72. data/lib/steep/drivers/stats.rb +203 -0
  73. data/lib/steep/drivers/utils/driver_helper.rb +143 -0
  74. data/lib/steep/drivers/utils/jobs_option.rb +26 -0
  75. data/lib/steep/drivers/vendor.rb +27 -0
  76. data/lib/steep/drivers/watch.rb +194 -0
  77. data/lib/steep/drivers/worker.rb +58 -0
  78. data/lib/steep/equatable.rb +23 -0
  79. data/lib/steep/expectations.rb +228 -0
  80. data/lib/steep/index/rbs_index.rb +350 -0
  81. data/lib/steep/index/signature_symbol_provider.rb +185 -0
  82. data/lib/steep/index/source_index.rb +167 -0
  83. data/lib/steep/interface/block.rb +103 -0
  84. data/lib/steep/interface/builder.rb +843 -0
  85. data/lib/steep/interface/function.rb +1090 -0
  86. data/lib/steep/interface/method_type.rb +330 -0
  87. data/lib/steep/interface/shape.rb +239 -0
  88. data/lib/steep/interface/substitution.rb +159 -0
  89. data/lib/steep/interface/type_param.rb +115 -0
  90. data/lib/steep/located_value.rb +20 -0
  91. data/lib/steep/method_name.rb +42 -0
  92. data/lib/steep/module_helper.rb +24 -0
  93. data/lib/steep/node_helper.rb +273 -0
  94. data/lib/steep/path_helper.rb +30 -0
  95. data/lib/steep/project/dsl.rb +268 -0
  96. data/lib/steep/project/group.rb +31 -0
  97. data/lib/steep/project/options.rb +63 -0
  98. data/lib/steep/project/pattern.rb +59 -0
  99. data/lib/steep/project/target.rb +92 -0
  100. data/lib/steep/project.rb +78 -0
  101. data/lib/steep/rake_task.rb +132 -0
  102. data/lib/steep/range_extension.rb +29 -0
  103. data/lib/steep/server/base_worker.rb +97 -0
  104. data/lib/steep/server/change_buffer.rb +73 -0
  105. data/lib/steep/server/custom_methods.rb +77 -0
  106. data/lib/steep/server/delay_queue.rb +45 -0
  107. data/lib/steep/server/interaction_worker.rb +492 -0
  108. data/lib/steep/server/lsp_formatter.rb +455 -0
  109. data/lib/steep/server/master.rb +912 -0
  110. data/lib/steep/server/target_group_files.rb +205 -0
  111. data/lib/steep/server/type_check_controller.rb +366 -0
  112. data/lib/steep/server/type_check_worker.rb +303 -0
  113. data/lib/steep/server/work_done_progress.rb +64 -0
  114. data/lib/steep/server/worker_process.rb +176 -0
  115. data/lib/steep/services/completion_provider.rb +802 -0
  116. data/lib/steep/services/content_change.rb +61 -0
  117. data/lib/steep/services/file_loader.rb +74 -0
  118. data/lib/steep/services/goto_service.rb +441 -0
  119. data/lib/steep/services/hover_provider/rbs.rb +88 -0
  120. data/lib/steep/services/hover_provider/ruby.rb +221 -0
  121. data/lib/steep/services/hover_provider/singleton_methods.rb +20 -0
  122. data/lib/steep/services/path_assignment.rb +46 -0
  123. data/lib/steep/services/signature_help_provider.rb +202 -0
  124. data/lib/steep/services/signature_service.rb +428 -0
  125. data/lib/steep/services/stats_calculator.rb +68 -0
  126. data/lib/steep/services/type_check_service.rb +394 -0
  127. data/lib/steep/services/type_name_completion.rb +236 -0
  128. data/lib/steep/signature/validator.rb +651 -0
  129. data/lib/steep/source/ignore_ranges.rb +69 -0
  130. data/lib/steep/source.rb +691 -0
  131. data/lib/steep/subtyping/cache.rb +30 -0
  132. data/lib/steep/subtyping/check.rb +1113 -0
  133. data/lib/steep/subtyping/constraints.rb +341 -0
  134. data/lib/steep/subtyping/relation.rb +101 -0
  135. data/lib/steep/subtyping/result.rb +324 -0
  136. data/lib/steep/subtyping/variable_variance.rb +89 -0
  137. data/lib/steep/test.rb +9 -0
  138. data/lib/steep/thread_waiter.rb +43 -0
  139. data/lib/steep/type_construction.rb +5183 -0
  140. data/lib/steep/type_inference/block_params.rb +416 -0
  141. data/lib/steep/type_inference/case_when.rb +303 -0
  142. data/lib/steep/type_inference/constant_env.rb +56 -0
  143. data/lib/steep/type_inference/context.rb +195 -0
  144. data/lib/steep/type_inference/logic_type_interpreter.rb +613 -0
  145. data/lib/steep/type_inference/method_call.rb +193 -0
  146. data/lib/steep/type_inference/method_params.rb +531 -0
  147. data/lib/steep/type_inference/multiple_assignment.rb +194 -0
  148. data/lib/steep/type_inference/send_args.rb +712 -0
  149. data/lib/steep/type_inference/type_env.rb +341 -0
  150. data/lib/steep/type_inference/type_env_builder.rb +138 -0
  151. data/lib/steep/typing.rb +321 -0
  152. data/lib/steep/version.rb +3 -0
  153. data/lib/steep.rb +369 -0
  154. data/manual/annotations.md +181 -0
  155. data/manual/ignore.md +20 -0
  156. data/manual/ruby-diagnostics.md +1879 -0
  157. data/sample/Steepfile +22 -0
  158. data/sample/lib/conference.rb +49 -0
  159. data/sample/lib/length.rb +35 -0
  160. data/sample/sig/conference.rbs +42 -0
  161. data/sample/sig/generics.rbs +15 -0
  162. data/sample/sig/length.rbs +34 -0
  163. data/steep-activesupport-4.gemspec +55 -0
  164. metadata +437 -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