steep 1.10.0 → 2.0.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +84 -1
- data/CLAUDE.md +114 -0
- data/README.md +1 -1
- data/Rakefile +15 -3
- data/Steepfile +13 -13
- data/lib/steep/annotation_parser.rb +5 -1
- data/lib/steep/annotations_helper.rb +12 -2
- data/lib/steep/ast/node/type_application.rb +22 -16
- data/lib/steep/ast/node/type_assertion.rb +7 -4
- data/lib/steep/ast/types/factory.rb +3 -2
- data/lib/steep/cli.rb +246 -2
- data/lib/steep/daemon/configuration.rb +19 -0
- data/lib/steep/daemon/server.rb +476 -0
- data/lib/steep/daemon.rb +201 -0
- data/lib/steep/diagnostic/ruby.rb +50 -8
- data/lib/steep/diagnostic/signature.rb +31 -8
- data/lib/steep/drivers/check.rb +301 -140
- data/lib/steep/drivers/print_project.rb +9 -10
- data/lib/steep/drivers/query.rb +102 -0
- data/lib/steep/drivers/start_server.rb +19 -0
- data/lib/steep/drivers/stop_server.rb +20 -0
- data/lib/steep/drivers/watch.rb +2 -2
- data/lib/steep/index/rbs_index.rb +38 -13
- data/lib/steep/index/signature_symbol_provider.rb +24 -3
- data/lib/steep/interface/builder.rb +48 -15
- data/lib/steep/interface/shape.rb +13 -5
- data/lib/steep/locator.rb +377 -0
- data/lib/steep/project/dsl.rb +26 -5
- data/lib/steep/project/group.rb +8 -2
- data/lib/steep/project/target.rb +16 -2
- data/lib/steep/project.rb +21 -2
- data/lib/steep/server/base_worker.rb +2 -2
- data/lib/steep/server/change_buffer.rb +2 -1
- data/lib/steep/server/custom_methods.rb +12 -0
- data/lib/steep/server/inline_source_change_detector.rb +94 -0
- data/lib/steep/server/interaction_worker.rb +51 -74
- data/lib/steep/server/lsp_formatter.rb +48 -12
- data/lib/steep/server/master.rb +100 -18
- data/lib/steep/server/target_group_files.rb +124 -151
- data/lib/steep/server/type_check_controller.rb +276 -123
- data/lib/steep/server/type_check_worker.rb +104 -3
- data/lib/steep/services/completion_provider/rbs.rb +74 -0
- data/lib/steep/services/completion_provider/ruby.rb +652 -0
- data/lib/steep/services/completion_provider/type_name.rb +243 -0
- data/lib/steep/services/completion_provider.rb +39 -662
- data/lib/steep/services/content_change.rb +14 -1
- data/lib/steep/services/file_loader.rb +4 -2
- data/lib/steep/services/goto_service.rb +271 -68
- data/lib/steep/services/hover_provider/content.rb +67 -0
- data/lib/steep/services/hover_provider/rbs.rb +8 -9
- data/lib/steep/services/hover_provider/ruby.rb +123 -64
- data/lib/steep/services/hover_provider/singleton_methods.rb +4 -0
- data/lib/steep/services/signature_service.rb +129 -54
- data/lib/steep/services/type_check_service.rb +72 -27
- data/lib/steep/signature/validator.rb +30 -18
- data/lib/steep/source/ignore_ranges.rb +14 -4
- data/lib/steep/source.rb +16 -2
- data/lib/steep/tagged_logging.rb +39 -0
- data/lib/steep/type_construction.rb +94 -21
- data/lib/steep/type_inference/block_params.rb +7 -7
- data/lib/steep/type_inference/context.rb +4 -2
- data/lib/steep/type_inference/logic_type_interpreter.rb +21 -3
- data/lib/steep/type_inference/method_call.rb +4 -0
- data/lib/steep/type_inference/type_env.rb +1 -1
- data/lib/steep/typing.rb +0 -2
- data/lib/steep/version.rb +1 -1
- data/lib/steep.rb +42 -32
- data/manual/ruby-diagnostics.md +67 -0
- data/sample/Steepfile +1 -0
- data/sample/lib/conference.rb +1 -0
- data/sample/lib/deprecated.rb +6 -0
- data/sample/lib/inline.rb +43 -0
- data/sample/sig/generics.rbs +3 -0
- data/steep.gemspec +4 -5
- metadata +26 -26
- data/lib/steep/services/type_name_completion.rb +0 -236
data/lib/steep/drivers/check.rb
CHANGED
|
@@ -17,9 +17,18 @@ module Steep
|
|
|
17
17
|
attr_accessor :validate_project_signatures
|
|
18
18
|
attr_accessor :validate_library_signatures
|
|
19
19
|
attr_accessor :formatter
|
|
20
|
+
attr_accessor :use_daemon
|
|
21
|
+
attr_reader :expressions
|
|
20
22
|
|
|
21
23
|
include Utils::DriverHelper
|
|
22
24
|
|
|
25
|
+
PLURALIZE = {
|
|
26
|
+
"diagnostic" => "diagnostics",
|
|
27
|
+
"expression" => "expressions",
|
|
28
|
+
"file" => "files",
|
|
29
|
+
"problem" => "problems",
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
23
32
|
def initialize(stdout:, stderr:)
|
|
24
33
|
@stdout = stdout
|
|
25
34
|
@stderr = stderr
|
|
@@ -32,6 +41,8 @@ module Steep
|
|
|
32
41
|
@validate_project_signatures = false
|
|
33
42
|
@validate_library_signatures = false
|
|
34
43
|
@formatter = 'code'
|
|
44
|
+
@use_daemon = true
|
|
45
|
+
@expressions = []
|
|
35
46
|
end
|
|
36
47
|
|
|
37
48
|
def active_group?(group)
|
|
@@ -53,178 +64,121 @@ module Steep
|
|
|
53
64
|
def run
|
|
54
65
|
project = load_config()
|
|
55
66
|
|
|
56
|
-
|
|
57
|
-
|
|
67
|
+
unless expressions.empty?
|
|
68
|
+
return run_expressions(project)
|
|
69
|
+
end
|
|
58
70
|
|
|
59
|
-
|
|
60
|
-
server_read, client_write = IO.pipe
|
|
71
|
+
params = build_typecheck_params(project)
|
|
61
72
|
|
|
62
|
-
|
|
63
|
-
|
|
73
|
+
diagnostic_notifications = [] #: Array[LanguageServer::Protocol::Interface::PublishDiagnosticsParams]
|
|
74
|
+
error_messages = [] #: Array[String]
|
|
64
75
|
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
setup_connection(project) do |reader, writer|
|
|
77
|
+
request_guid = SecureRandom.uuid
|
|
78
|
+
writer.write(Server::CustomMethods::TypeCheck.request(request_guid, params))
|
|
79
|
+
|
|
80
|
+
wait_for_response_id(reader: reader, id: request_guid) do |message|
|
|
81
|
+
case message[:method]
|
|
82
|
+
when "textDocument/publishDiagnostics"
|
|
83
|
+
ds = message[:params][:diagnostics]
|
|
84
|
+
ds.select! { |d| keep_diagnostic?(d, severity_level: severity_level) }
|
|
85
|
+
stdout.print(ds.empty? ? "." : "F")
|
|
86
|
+
diagnostic_notifications << message[:params]
|
|
87
|
+
stdout.flush
|
|
88
|
+
when "window/showMessage"
|
|
89
|
+
if message[:params][:type] == LSP::Constant::MessageType::ERROR
|
|
90
|
+
error_messages << message[:params][:message]
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
67
95
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
args: command_line_patterns,
|
|
71
|
-
delay_shutdown: true,
|
|
72
|
-
steep_command: jobs_option.steep_command,
|
|
73
|
-
count: jobs_option.jobs_count_value
|
|
74
|
-
)
|
|
96
|
+
stdout.puts
|
|
97
|
+
stdout.puts
|
|
75
98
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
typecheck_workers: typecheck_workers
|
|
82
|
-
)
|
|
83
|
-
master.typecheck_automatically = false
|
|
84
|
-
master.commandline_args.push(*command_line_patterns)
|
|
99
|
+
print_typecheck_result(project: project, diagnostic_notifications: diagnostic_notifications, error_messages: error_messages)
|
|
100
|
+
rescue Errno::EPIPE => error
|
|
101
|
+
stdout.puts Rainbow("Steep shutdown with an error: #{error.inspect}").red.bold
|
|
102
|
+
1
|
|
103
|
+
end
|
|
85
104
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
master.start()
|
|
89
|
-
end
|
|
105
|
+
def run_expressions(project)
|
|
106
|
+
target = project.targets.first or raise "No targets configured"
|
|
90
107
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
client_writer.write({ method: :initialize, id: initialize_id, params: DEFAULT_CLI_LSP_INITIALIZE_PARAMS })
|
|
94
|
-
wait_for_response_id(reader: client_reader, id: initialize_id)
|
|
108
|
+
stdout.puts Rainbow("# Type checking expression:").bold
|
|
109
|
+
stdout.puts
|
|
95
110
|
|
|
96
|
-
|
|
111
|
+
loader = Project::Target.construct_env_loader(options: target.options, project: project)
|
|
112
|
+
signature_service = Services::SignatureService.load_from(loader, implicitly_returns_nil: target.implicitly_returns_nil)
|
|
113
|
+
subtyping = signature_service.current_subtyping or raise "Failed to build subtyping"
|
|
97
114
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
loader = Services::FileLoader.new(base_dir: project.base_dir)
|
|
115
|
+
lsp_formatter = Diagnostic::LSPFormatter.new(target.code_diagnostics_config)
|
|
116
|
+
total = 0
|
|
101
117
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
RBS::FileFinder.each_file(dir, skip_hidden: true) do |path|
|
|
105
|
-
files.add_library_path(target, path)
|
|
106
|
-
end
|
|
107
|
-
end
|
|
118
|
+
expressions.each_with_index do |expr, index|
|
|
119
|
+
expr_path = Pathname(expressions.size > 1 ? "(expression:#{index})" : "(expression)")
|
|
108
120
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
121
|
+
begin
|
|
122
|
+
source = Source.parse(expr, path: expr_path, factory: subtyping.factory)
|
|
123
|
+
rescue ::Parser::SyntaxError => exn
|
|
124
|
+
stdout.puts Rainbow("Syntax error in #{expr_path}: #{exn.message}").red.bold
|
|
125
|
+
total += 1
|
|
126
|
+
next
|
|
112
127
|
end
|
|
113
128
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if active_group?(target)
|
|
121
|
-
load_files(files, target, target, params: params)
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
else
|
|
125
|
-
command_line_patterns.each do |pattern|
|
|
126
|
-
path = Pathname(pattern)
|
|
127
|
-
path = project.absolute_path(path)
|
|
128
|
-
next unless path.file?
|
|
129
|
-
if target = project.target_for_source_path(path)
|
|
130
|
-
params[:code_paths] << [target.name.to_s, path.to_s]
|
|
131
|
-
end
|
|
132
|
-
if target = project.target_for_signature_path(path)
|
|
133
|
-
params[:signature_paths] << [target.name.to_s, path.to_s]
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
end
|
|
129
|
+
typing = Services::TypeCheckService.type_check(
|
|
130
|
+
source: source,
|
|
131
|
+
subtyping: subtyping,
|
|
132
|
+
constant_resolver: signature_service.latest_constant_resolver,
|
|
133
|
+
cursor: nil
|
|
134
|
+
)
|
|
137
135
|
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
diagnostics = typing.errors.filter_map { |error| lsp_formatter.format(error) }
|
|
137
|
+
diagnostics.select! { |d| keep_diagnostic?(d, severity_level: severity_level) }
|
|
140
138
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
unless diagnostics.empty?
|
|
140
|
+
buffer = RBS::Buffer.new(name: expr_path, content: expr)
|
|
141
|
+
printer = DiagnosticPrinter.new(buffer: buffer, stdout: stdout, formatter: self.formatter)
|
|
144
142
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
response = wait_for_response_id(reader: client_reader, id: request_guid) do |message|
|
|
149
|
-
case
|
|
150
|
-
when message[:method] == "textDocument/publishDiagnostics"
|
|
151
|
-
ds = message[:params][:diagnostics]
|
|
152
|
-
ds.select! {|d| keep_diagnostic?(d, severity_level: severity_level) }
|
|
153
|
-
if ds.empty?
|
|
154
|
-
stdout.print "."
|
|
155
|
-
else
|
|
156
|
-
stdout.print "F"
|
|
157
|
-
end
|
|
158
|
-
diagnostic_notifications << message[:params]
|
|
159
|
-
stdout.flush
|
|
160
|
-
when message[:method] == "window/showMessage"
|
|
161
|
-
# Assuming ERROR message means unrecoverable error.
|
|
162
|
-
message = message[:params]
|
|
163
|
-
if message[:type] == LSP::Constant::MessageType::ERROR
|
|
164
|
-
error_messages << message[:message]
|
|
143
|
+
diagnostics.each do |diag|
|
|
144
|
+
printer.print(diag)
|
|
145
|
+
stdout.puts
|
|
165
146
|
end
|
|
147
|
+
|
|
148
|
+
total += diagnostics.size
|
|
166
149
|
end
|
|
167
150
|
end
|
|
168
151
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
shutdown_exit(reader: client_reader, writer: client_writer)
|
|
174
|
-
main_thread.join()
|
|
175
|
-
|
|
176
|
-
stdout.puts
|
|
177
|
-
stdout.puts
|
|
178
|
-
|
|
179
|
-
if error_messages.empty?
|
|
180
|
-
loader = Services::FileLoader.new(base_dir: project.base_dir)
|
|
181
|
-
all_files = project.targets.each.with_object(Set[]) do |target, set|
|
|
182
|
-
set.merge(loader.load_changes(target.source_pattern, command_line_patterns, changes: {}).each_key)
|
|
183
|
-
set.merge(loader.load_changes(target.signature_pattern, changes: {}).each_key)
|
|
184
|
-
end.to_a
|
|
185
|
-
|
|
186
|
-
case
|
|
187
|
-
when with_expectations_path
|
|
188
|
-
print_expectations(project: project,
|
|
189
|
-
all_files: all_files,
|
|
190
|
-
expectations_path: with_expectations_path,
|
|
191
|
-
notifications: diagnostic_notifications)
|
|
192
|
-
when save_expectations_path
|
|
193
|
-
save_expectations(project: project,
|
|
194
|
-
all_files: all_files,
|
|
195
|
-
expectations_path: save_expectations_path,
|
|
196
|
-
notifications: diagnostic_notifications)
|
|
197
|
-
else
|
|
198
|
-
print_result(project: project, notifications: diagnostic_notifications)
|
|
199
|
-
end
|
|
152
|
+
if total == 0
|
|
153
|
+
emoji = %w(🫖 🫖 🫖 🫖 🫖 🫖 🫖 🫖 🍵 🧋 🧉).sample
|
|
154
|
+
stdout.puts Rainbow("No type error detected. #{emoji}").green.bold
|
|
155
|
+
0
|
|
200
156
|
else
|
|
201
|
-
stdout.puts Rainbow("
|
|
157
|
+
stdout.puts Rainbow("Detected #{total} #{pluralize("problem", total)} from #{expressions.size} #{pluralize("expression", expressions.size)}").red.bold
|
|
202
158
|
1
|
|
203
159
|
end
|
|
204
|
-
rescue Errno::EPIPE => error
|
|
205
|
-
stdout.puts Rainbow("Steep shutdown with an error: #{error.inspect}").red.bold
|
|
206
|
-
return 1
|
|
207
160
|
end
|
|
208
161
|
|
|
209
162
|
def load_files(files, target, group, params:)
|
|
210
163
|
if type_check_code
|
|
211
|
-
files.
|
|
164
|
+
files.source_paths.each_group_path(group) do |path,|
|
|
212
165
|
params[:code_paths] << [target.name.to_s, target.project.absolute_path(path).to_s]
|
|
213
166
|
end
|
|
167
|
+
files.inline_paths.each_group_path(group) do |path,|
|
|
168
|
+
params[:inline_paths] << [target.name.to_s, target.project.absolute_path(path).to_s]
|
|
169
|
+
end
|
|
214
170
|
end
|
|
215
171
|
if validate_group_signatures
|
|
216
|
-
files.
|
|
172
|
+
files.signature_paths.each_group_path(group) do |path,|
|
|
217
173
|
params[:signature_paths] << [target.name.to_s, target.project.absolute_path(path).to_s]
|
|
218
174
|
end
|
|
219
175
|
end
|
|
220
176
|
if validate_project_signatures
|
|
221
|
-
files.
|
|
222
|
-
|
|
223
|
-
params[:signature_paths] << [path_target.name.to_s, target.project.absolute_path(path).to_s]
|
|
224
|
-
end
|
|
177
|
+
files.signature_paths.each_project_path(except: target) do |path, path_target|
|
|
178
|
+
params[:signature_paths] << [path_target.name.to_s, target.project.absolute_path(path).to_s]
|
|
225
179
|
end
|
|
226
180
|
if group.is_a?(Project::Group)
|
|
227
|
-
files.
|
|
181
|
+
files.signature_paths.each_target_path(target, except: group) do |path,|
|
|
228
182
|
params[:signature_paths] << [target.name.to_s, target.project.absolute_path(path).to_s]
|
|
229
183
|
end
|
|
230
184
|
end
|
|
@@ -274,13 +228,13 @@ module Steep
|
|
|
274
228
|
stdout.puts
|
|
275
229
|
|
|
276
230
|
stdout.puts Rainbow("Expectations unsatisfied:").bold.red
|
|
277
|
-
stdout.puts " #{expected_count} expected #{"diagnostic"
|
|
278
|
-
stdout.puts Rainbow(" + #{unexpected_count} unexpected #{"diagnostic"
|
|
279
|
-
stdout.puts Rainbow(" - #{missing_count} missing #{"diagnostic"
|
|
231
|
+
stdout.puts " #{expected_count} expected #{pluralize("diagnostic", expected_count)}"
|
|
232
|
+
stdout.puts Rainbow(" + #{unexpected_count} unexpected #{pluralize("diagnostic", unexpected_count)}").green
|
|
233
|
+
stdout.puts Rainbow(" - #{missing_count} missing #{pluralize("diagnostic", missing_count)}").red
|
|
280
234
|
1
|
|
281
235
|
else
|
|
282
236
|
stdout.puts Rainbow("Expectations satisfied:").bold.green
|
|
283
|
-
stdout.puts " #{expected_count} expected #{"diagnostic"
|
|
237
|
+
stdout.puts " #{expected_count} expected #{pluralize("diagnostic", expected_count)}"
|
|
284
238
|
0
|
|
285
239
|
end
|
|
286
240
|
end
|
|
@@ -332,10 +286,217 @@ module Steep
|
|
|
332
286
|
end
|
|
333
287
|
end
|
|
334
288
|
|
|
335
|
-
stdout.puts Rainbow("Detected #{total} #{"problem"
|
|
289
|
+
stdout.puts Rainbow("Detected #{total} #{pluralize("problem", total)} from #{errors.size} #{pluralize("file", errors.size)}").red.bold
|
|
336
290
|
1
|
|
337
291
|
end
|
|
338
292
|
end
|
|
293
|
+
|
|
294
|
+
def pluralize(string, count)
|
|
295
|
+
if count == 1
|
|
296
|
+
string
|
|
297
|
+
else
|
|
298
|
+
PLURALIZE.fetch(string)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
private
|
|
303
|
+
|
|
304
|
+
def setup_connection(project, &block)
|
|
305
|
+
if use_daemon && daemon_available?
|
|
306
|
+
begin
|
|
307
|
+
stdout.puts Rainbow("# Type checking files (server mode):").bold
|
|
308
|
+
stdout.puts
|
|
309
|
+
return with_daemon_connection(&block)
|
|
310
|
+
rescue Errno::ECONNREFUSED, Errno::ENOENT => e
|
|
311
|
+
stderr.puts "Steep server connection failed (#{e.message}), falling back to standard mode"
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
stdout.puts Rainbow("# Type checking files:").bold
|
|
316
|
+
stdout.puts
|
|
317
|
+
with_local_server(project, &block)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def daemon_available?
|
|
321
|
+
if Daemon.running?
|
|
322
|
+
Steep.logger.info { "Daemon detected, using server mode" }
|
|
323
|
+
return true
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
if Daemon.starting?
|
|
327
|
+
Steep.logger.info { "Daemon is starting, waiting for it to be ready" }
|
|
328
|
+
if wait_for_daemon
|
|
329
|
+
return true
|
|
330
|
+
else
|
|
331
|
+
stderr.puts Rainbow("Daemon failed to start, falling back to standard mode").yellow
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
false
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def with_daemon_connection
|
|
339
|
+
socket = UNIXSocket.new(Daemon.socket_path)
|
|
340
|
+
reader = LSP::Transport::Io::Reader.new(socket)
|
|
341
|
+
writer = LSP::Transport::Io::Writer.new(socket)
|
|
342
|
+
yield reader, writer
|
|
343
|
+
ensure
|
|
344
|
+
socket&.close
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def with_local_server(project)
|
|
348
|
+
client_read, server_write = IO.pipe
|
|
349
|
+
server_read, client_write = IO.pipe
|
|
350
|
+
|
|
351
|
+
client_reader = LSP::Transport::Io::Reader.new(client_read)
|
|
352
|
+
client_writer = LSP::Transport::Io::Writer.new(client_write)
|
|
353
|
+
|
|
354
|
+
server_reader = LSP::Transport::Io::Reader.new(server_read)
|
|
355
|
+
server_writer = LSP::Transport::Io::Writer.new(server_write)
|
|
356
|
+
|
|
357
|
+
typecheck_workers = Server::WorkerProcess.start_typecheck_workers(
|
|
358
|
+
steepfile: project.steepfile_path,
|
|
359
|
+
args: command_line_patterns,
|
|
360
|
+
delay_shutdown: true,
|
|
361
|
+
steep_command: jobs_option.steep_command,
|
|
362
|
+
count: jobs_option.jobs_count_value
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
master = Server::Master.new(
|
|
366
|
+
project: project,
|
|
367
|
+
reader: server_reader,
|
|
368
|
+
writer: server_writer,
|
|
369
|
+
interaction_worker: nil,
|
|
370
|
+
typecheck_workers: typecheck_workers
|
|
371
|
+
)
|
|
372
|
+
master.typecheck_automatically = false
|
|
373
|
+
master.commandline_args.push(*command_line_patterns)
|
|
374
|
+
|
|
375
|
+
main_thread = Thread.start do
|
|
376
|
+
Thread.current.abort_on_exception = true
|
|
377
|
+
master.start()
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
Steep.logger.info { "Initializing server" }
|
|
381
|
+
initialize_id = request_id()
|
|
382
|
+
client_writer.write({ method: :initialize, id: initialize_id, params: DEFAULT_CLI_LSP_INITIALIZE_PARAMS })
|
|
383
|
+
wait_for_response_id(reader: client_reader, id: initialize_id)
|
|
384
|
+
|
|
385
|
+
yield client_reader, client_writer
|
|
386
|
+
ensure
|
|
387
|
+
if client_reader && client_writer
|
|
388
|
+
shutdown_exit(reader: client_reader, writer: client_writer)
|
|
389
|
+
end
|
|
390
|
+
main_thread&.join()
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def print_typecheck_result(project:, diagnostic_notifications:, error_messages:)
|
|
394
|
+
if error_messages.empty?
|
|
395
|
+
loader = Services::FileLoader.new(base_dir: project.base_dir)
|
|
396
|
+
all_files = project.targets.each.with_object(Set[]) do |target, set|
|
|
397
|
+
set.merge(loader.load_changes(target.source_pattern, command_line_patterns, changes: {}).each_key)
|
|
398
|
+
set.merge(loader.load_changes(target.signature_pattern, changes: {}).each_key)
|
|
399
|
+
end.to_a
|
|
400
|
+
|
|
401
|
+
case
|
|
402
|
+
when with_expectations_path
|
|
403
|
+
print_expectations(project: project,
|
|
404
|
+
all_files: all_files,
|
|
405
|
+
expectations_path: with_expectations_path,
|
|
406
|
+
notifications: diagnostic_notifications)
|
|
407
|
+
when save_expectations_path
|
|
408
|
+
save_expectations(project: project,
|
|
409
|
+
all_files: all_files,
|
|
410
|
+
expectations_path: save_expectations_path,
|
|
411
|
+
notifications: diagnostic_notifications)
|
|
412
|
+
else
|
|
413
|
+
print_result(project: project, notifications: diagnostic_notifications)
|
|
414
|
+
end
|
|
415
|
+
else
|
|
416
|
+
stdout.puts Rainbow("Unexpected error reported. 🚨").red.bold
|
|
417
|
+
1
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def build_typecheck_params(project)
|
|
422
|
+
params = { library_paths: [], inline_paths: [], signature_paths: [], code_paths: [] } #: Server::CustomMethods::TypeCheck::params
|
|
423
|
+
|
|
424
|
+
if command_line_patterns.empty?
|
|
425
|
+
files = Server::TargetGroupFiles.new(project)
|
|
426
|
+
loader = Services::FileLoader.new(base_dir: project.base_dir)
|
|
427
|
+
|
|
428
|
+
project.targets.each do |target|
|
|
429
|
+
target.new_env_loader.each_dir do |_, dir|
|
|
430
|
+
RBS::FileFinder.each_file(dir, skip_hidden: true) do |path|
|
|
431
|
+
files.add_library_path(target, path)
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
loader.each_path_in_target(target) do |path|
|
|
436
|
+
files.add_path(path)
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
project.targets.each do |target|
|
|
441
|
+
target.groups.each do |group|
|
|
442
|
+
if active_group?(group)
|
|
443
|
+
load_files(files, target, group, params: params)
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
if active_group?(target)
|
|
447
|
+
load_files(files, target, target, params: params)
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
else
|
|
451
|
+
command_line_patterns.each do |pattern|
|
|
452
|
+
path = Pathname(pattern)
|
|
453
|
+
path = project.absolute_path(path)
|
|
454
|
+
next unless path.file?
|
|
455
|
+
if target = project.target_for_source_path(path)
|
|
456
|
+
params[:code_paths] << [target.name.to_s, path.to_s]
|
|
457
|
+
end
|
|
458
|
+
if target = project.target_for_signature_path(path)
|
|
459
|
+
params[:signature_paths] << [target.name.to_s, path.to_s]
|
|
460
|
+
end
|
|
461
|
+
if target = project.target_for_inline_source_path(path)
|
|
462
|
+
params[:inline_paths] << [target.name.to_s, path.to_s]
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
params
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def wait_for_daemon(timeout: 300)
|
|
471
|
+
stdout.puts "Daemon is warming up, waiting for it to be ready..."
|
|
472
|
+
start_time = Time.now
|
|
473
|
+
dots_printed = 0
|
|
474
|
+
|
|
475
|
+
loop do
|
|
476
|
+
if Daemon.running?
|
|
477
|
+
stdout.puts unless dots_printed == 0
|
|
478
|
+
return true
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
elapsed = Time.now - start_time
|
|
482
|
+
if elapsed > timeout
|
|
483
|
+
stdout.puts unless dots_printed == 0
|
|
484
|
+
Steep.logger.warn { "Daemon warm-up timed out after #{timeout}s" }
|
|
485
|
+
return false
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
unless Daemon.starting?
|
|
489
|
+
stdout.puts unless dots_printed == 0
|
|
490
|
+
Steep.logger.warn { "Daemon process died during warm-up" }
|
|
491
|
+
return false
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
sleep 1
|
|
495
|
+
stdout.print "."
|
|
496
|
+
stdout.flush
|
|
497
|
+
dots_printed += 1
|
|
498
|
+
end
|
|
499
|
+
end
|
|
339
500
|
end
|
|
340
501
|
end
|
|
341
502
|
end
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
require "active_support/core_ext/hash/keys"
|
|
2
|
-
|
|
3
1
|
module Steep
|
|
4
2
|
module Drivers
|
|
5
3
|
class PrintProject
|
|
@@ -19,17 +17,18 @@ module Steep
|
|
|
19
17
|
|
|
20
18
|
def as_json(project)
|
|
21
19
|
{
|
|
22
|
-
steepfile
|
|
23
|
-
targets
|
|
20
|
+
"steepfile" => project.steepfile_path.to_s,
|
|
21
|
+
"targets" => project.targets.map do |target|
|
|
24
22
|
target_as_json(target)
|
|
25
23
|
end
|
|
26
|
-
}
|
|
24
|
+
}
|
|
27
25
|
end
|
|
28
26
|
|
|
29
27
|
def target_as_json(target)
|
|
30
28
|
json = {
|
|
31
29
|
"name" => target.name.to_s,
|
|
32
30
|
"source_pattern" => pattern_as_json(target.source_pattern),
|
|
31
|
+
"inline_source_pattern" => pattern_as_json(target.inline_source_pattern),
|
|
33
32
|
"signature_pattern" => pattern_as_json(target.signature_pattern),
|
|
34
33
|
"groups" => target.groups.map do |group|
|
|
35
34
|
group_as_json(group)
|
|
@@ -52,10 +51,10 @@ module Steep
|
|
|
52
51
|
} #: target_json
|
|
53
52
|
|
|
54
53
|
if files
|
|
55
|
-
files.
|
|
54
|
+
files.signature_paths.each_group_path(target) do |path,|
|
|
56
55
|
(json["signature_paths"] ||= []) << path.to_s
|
|
57
56
|
end
|
|
58
|
-
files.
|
|
57
|
+
files.source_paths.each_group_path(target) do |path,|
|
|
59
58
|
(json["source_paths"] ||= []) << path.to_s
|
|
60
59
|
end
|
|
61
60
|
end
|
|
@@ -71,11 +70,11 @@ module Steep
|
|
|
71
70
|
} #: group_json
|
|
72
71
|
|
|
73
72
|
if files
|
|
74
|
-
files.
|
|
73
|
+
files.signature_paths.each_group_path(group) do |path,|
|
|
75
74
|
(json["signature_paths"] ||= []) << path.to_s
|
|
76
|
-
|
|
77
75
|
end
|
|
78
|
-
|
|
76
|
+
|
|
77
|
+
files.source_paths.each_group_path(group) do |path,|
|
|
79
78
|
(json["source_paths"] ||= []) << path.to_s
|
|
80
79
|
end
|
|
81
80
|
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
module Steep
|
|
2
|
+
module Drivers
|
|
3
|
+
class Query
|
|
4
|
+
LSP = LanguageServer::Protocol
|
|
5
|
+
|
|
6
|
+
attr_reader :stdout
|
|
7
|
+
attr_reader :stderr
|
|
8
|
+
|
|
9
|
+
def initialize(stdout:, stderr:)
|
|
10
|
+
@stdout = stdout
|
|
11
|
+
@stderr = stderr
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @rbs locations: Array[[String, Integer, Integer]] -- array of [path, line, column]
|
|
15
|
+
# @rbs return: Integer
|
|
16
|
+
def run_hover(locations:)
|
|
17
|
+
unless Daemon.running?
|
|
18
|
+
stderr.puts "Error: Steep daemon is not running. Start it with `steep server start`."
|
|
19
|
+
return 1
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
locations.each do |path, line, column|
|
|
23
|
+
absolute_path = to_absolute_path(path)
|
|
24
|
+
unless absolute_path
|
|
25
|
+
stdout.puts JSON.generate({ file: path, line: line, column: column, error: "File not found: #{path}" })
|
|
26
|
+
next
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
uri = PathHelper.to_uri(absolute_path).to_s
|
|
30
|
+
|
|
31
|
+
request = {
|
|
32
|
+
id: SecureRandom.uuid,
|
|
33
|
+
method: "textDocument/hover",
|
|
34
|
+
params: {
|
|
35
|
+
textDocument: { uri: uri },
|
|
36
|
+
position: { line: line - 1, character: column - 1 }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
result = send_request(request)
|
|
41
|
+
stdout.puts JSON.generate({ file: path, line: line, column: column, result: result })
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
0
|
|
45
|
+
rescue Errno::ECONNREFUSED, Errno::ENOENT => e
|
|
46
|
+
stderr.puts "Error: Failed to connect to Steep daemon: #{e.message}"
|
|
47
|
+
1
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @rbs names: Array[String]
|
|
51
|
+
# @rbs return: Integer
|
|
52
|
+
def run_definition(names:)
|
|
53
|
+
unless Daemon.running?
|
|
54
|
+
stderr.puts "Error: Steep daemon is not running. Start it with `steep server start`."
|
|
55
|
+
return 1
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
names.each do |name|
|
|
59
|
+
request = {
|
|
60
|
+
id: SecureRandom.uuid,
|
|
61
|
+
method: Server::CustomMethods::Query__Definition::METHOD,
|
|
62
|
+
params: { name: name }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
result = send_request(request)
|
|
66
|
+
stdout.puts JSON.generate({ name: name, result: result })
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
0
|
|
70
|
+
rescue Errno::ECONNREFUSED, Errno::ENOENT => e
|
|
71
|
+
stderr.puts "Error: Failed to connect to Steep daemon: #{e.message}"
|
|
72
|
+
1
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def to_absolute_path(path)
|
|
78
|
+
pathname = Pathname(path)
|
|
79
|
+
pathname = Pathname.pwd + pathname unless pathname.absolute?
|
|
80
|
+
pathname.file? ? pathname : nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def send_request(request)
|
|
84
|
+
socket = UNIXSocket.new(Daemon.socket_path)
|
|
85
|
+
reader = LSP::Transport::Io::Reader.new(socket)
|
|
86
|
+
writer = LSP::Transport::Io::Writer.new(socket)
|
|
87
|
+
|
|
88
|
+
writer.write(request)
|
|
89
|
+
|
|
90
|
+
result = nil #: untyped
|
|
91
|
+
reader.read do |message|
|
|
92
|
+
result = message[:result]
|
|
93
|
+
break
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
result
|
|
97
|
+
ensure
|
|
98
|
+
socket&.close
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|