steep 2.0.0 → 2.1.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.
@@ -186,6 +186,12 @@ module Steep
186
186
  attr_accessor :typecheck_automatically
187
187
  attr_reader :start_type_checking_queue
188
188
 
189
+ # Type check requests waiting for the current type check to finish
190
+ attr_reader :pending_typecheck_requests
191
+
192
+ # Callbacks to be called when no type check is running anymore
193
+ attr_reader :typecheck_quiescent_callbacks
194
+
189
195
  def initialize(project:, reader:, writer:, interaction_worker:, typecheck_workers:, queue: Queue.new, refork: false)
190
196
  @project = project
191
197
  @reader = reader
@@ -199,6 +205,11 @@ module Steep
199
205
  @write_queue = SizedQueue.new(100)
200
206
  @refork_mutex = Mutex.new
201
207
  @need_to_refork = refork
208
+ @typecheck_quiescent_callbacks = []
209
+ @pending_typecheck_requests = []
210
+ @project_file_mtimes = nil
211
+ @command_socket_requests = {}
212
+ @command_socket_mutex = Mutex.new
202
213
 
203
214
  @controller = TypeCheckController.new(project: project)
204
215
  @result_controller = ResultController.new()
@@ -245,7 +256,7 @@ module Steep
245
256
  case job.dest
246
257
  when :client
247
258
  Steep.logger.info { "Processing SendMessageJob: dest=client, method=#{job.message[:method] || "-"}, id=#{job.message[:id] || "-"}" }
248
- writer.write job.message
259
+ write_message_to_client(job.message)
249
260
  when WorkerProcess
250
261
  refork_mutex.synchronize do
251
262
  Steep.logger.info { "Processing SendMessageJob: dest=#{job.dest.name}, method=#{job.message[:method] || "-"}, id=#{job.message[:id] || "-"}" }
@@ -420,6 +431,8 @@ module Steep
420
431
  progress.end()
421
432
  end
422
433
 
434
+ reset_project_file_mtimes()
435
+
423
436
  if file_system_watcher_supported?
424
437
  setup_file_system_watcher()
425
438
  end
@@ -445,6 +458,7 @@ module Steep
445
458
 
446
459
  unless controller.open_paths.include?(path)
447
460
  updated_watched_files << path
461
+ record_project_file_mtime(path)
448
462
 
449
463
  case type
450
464
  when LSP::Constant::FileChangeType::CREATED, LSP::Constant::FileChangeType::CHANGED
@@ -684,7 +698,7 @@ module Steep
684
698
  request.inline_paths << [target_name.to_sym, Pathname(path)]
685
699
  end
686
700
 
687
- start_type_check(request: request, last_request: nil)
701
+ start_type_check(request: request, last_request: current_type_check_request)
688
702
 
689
703
  when CustomMethods::TypeCheckGroups::METHOD
690
704
  params = message[:params] #: CustomMethods::TypeCheckGroups::params
@@ -703,6 +717,15 @@ module Steep
703
717
  request.needs_response = false
704
718
  start_type_check(request: request, last_request: current_type_check_request, report_progress_threshold: 0)
705
719
 
720
+ when CustomMethods::Query__Diagnostics::METHOD
721
+ id = message[:id]
722
+ params = message[:params] #: CustomMethods::Query__Diagnostics::params
723
+ paths = params[:paths]
724
+
725
+ run_when_typecheck_quiescent do
726
+ collect_query_diagnostics(id, paths)
727
+ end
728
+
706
729
  when "$/ping"
707
730
  enqueue_write_job SendMessageJob.to_client(
708
731
  message: {
@@ -805,6 +828,17 @@ module Steep
805
828
 
806
829
  def start_type_check(request: nil, last_request:, progress: nil, include_unchanged: false, report_progress_threshold: 10, needs_response: nil)
807
830
  Steep.logger.tagged "#start_type_check(#{progress&.guid || request&.guid}, #{last_request&.guid}" do
831
+ if (current = current_type_check_request) && typecheck_request_from_command_socket?(current)
832
+ # Never supersede a type check that a command socket client is waiting for
833
+ if request
834
+ Steep.logger.info { "Queueing type check request #{request.guid} until the command socket request finishes" }
835
+ pending_typecheck_requests << request
836
+ else
837
+ Steep.logger.info { "Deferring automatic type checking until the command socket request finishes" }
838
+ end
839
+ return
840
+ end
841
+
808
842
  if last_request
809
843
  finish_type_check(last_request)
810
844
  end
@@ -884,6 +918,7 @@ module Steep
884
918
  finish_type_check(current)
885
919
  @current_type_check_request = nil
886
920
  refork_workers
921
+ start_pending_typecheck()
887
922
  end
888
923
  end
889
924
  end
@@ -955,6 +990,17 @@ module Steep
955
990
  refork_finished.pop
956
991
  end
957
992
  end
993
+
994
+ # The reforked workers started from a copy of the primary's state, which has the
995
+ # results of the primary's assigned paths only, and the results the replaced workers
996
+ # had computed are gone with them. Type check everything again so that queries
997
+ # reading the stored results, like `$/steep/query/diagnostics`, see all files.
998
+ job_queue << -> do
999
+ guid = SecureRandom.uuid
1000
+ request = controller.make_all_request(guid: guid, progress: work_done_progress(guid))
1001
+ request.needs_response = false
1002
+ start_type_check(request: request, last_request: current_type_check_request, report_progress_threshold: 0)
1003
+ end
958
1004
  end
959
1005
  end
960
1006
 
@@ -1021,6 +1067,341 @@ module Steep
1021
1067
  write_queue.push(job)
1022
1068
  end
1023
1069
 
1070
+ # Calls the block once no type check is running
1071
+ #
1072
+ # Starts a type check for dirty paths first, so that the block observes up-to-date results.
1073
+ #
1074
+ def run_when_typecheck_quiescent(&block)
1075
+ unless current_type_check_request
1076
+ guid = SecureRandom.uuid
1077
+ if request = controller.make_request(guid: guid, progress: work_done_progress(guid))
1078
+ request.needs_response = false
1079
+ start_type_check(request: request, last_request: nil)
1080
+ end
1081
+ end
1082
+
1083
+ if current_type_check_request || !pending_typecheck_requests.empty?
1084
+ typecheck_quiescent_callbacks << block
1085
+ else
1086
+ yield
1087
+ end
1088
+ end
1089
+
1090
+ # Starts the next queued type check request if any, or the automatic type check for dirty paths
1091
+ #
1092
+ # Calls the quiescent callbacks when nothing is left to type check.
1093
+ #
1094
+ def start_pending_typecheck
1095
+ while request = pending_typecheck_requests.shift
1096
+ start_type_check(request: request, last_request: nil)
1097
+ return if current_type_check_request
1098
+ end
1099
+
1100
+ if typecheck_automatically && initialize_params
1101
+ guid = SecureRandom.uuid
1102
+ if request = controller.make_request(guid: guid, progress: work_done_progress(guid))
1103
+ request.needs_response = false
1104
+ start_type_check(request: request, last_request: nil)
1105
+ return if current_type_check_request
1106
+ end
1107
+ end
1108
+
1109
+ flush_typecheck_quiescent_callbacks()
1110
+ end
1111
+
1112
+ # Calls the waiting quiescent callbacks, once a type check for the paths that went dirty
1113
+ # in the meantime finishes
1114
+ #
1115
+ def flush_typecheck_quiescent_callbacks
1116
+ return if typecheck_quiescent_callbacks.empty?
1117
+ return if current_type_check_request
1118
+
1119
+ guid = SecureRandom.uuid
1120
+ if request = controller.make_request(guid: guid, progress: work_done_progress(guid))
1121
+ request.needs_response = false
1122
+ start_type_check(request: request, last_request: nil)
1123
+ return if current_type_check_request
1124
+ end
1125
+
1126
+ callbacks = typecheck_quiescent_callbacks.dup
1127
+ typecheck_quiescent_callbacks.clear
1128
+ callbacks.each(&:call)
1129
+ end
1130
+
1131
+ # Collects the stored diagnostics from all typecheck workers and sends the response
1132
+ #
1133
+ # `paths` is an array of absolute path strings to filter the result, or `nil` to return everything.
1134
+ #
1135
+ def collect_query_diagnostics(id, paths)
1136
+ uris = paths&.map {|path| PathHelper.to_uri(Pathname(path)).to_s }
1137
+
1138
+ result_controller << group_request do |group|
1139
+ typecheck_workers.each do |worker|
1140
+ group << send_request(method: CustomMethods::Query__Diagnostics::METHOD, params: nil, worker: worker)
1141
+ end
1142
+
1143
+ group.on_completion do |handlers|
1144
+ diagnostics = {} #: Hash[String, Array[untyped]]
1145
+
1146
+ handlers.each do |handler|
1147
+ result = handler.result or next
1148
+ result.each do |entry|
1149
+ array = diagnostics[entry[:uri]] ||= []
1150
+ array.concat(entry[:diagnostics] || [])
1151
+ array.uniq!
1152
+ end
1153
+ end
1154
+
1155
+ # @type var result: CustomMethods::Query__Diagnostics::result
1156
+ result =
1157
+ if uris
1158
+ # Files the server has not type checked yet are reported with `diagnostics: nil`
1159
+ uris.sort.map do |uri|
1160
+ { uri: uri, diagnostics: diagnostics[uri] }
1161
+ end
1162
+ else
1163
+ diagnostics.keys.sort.map do |uri|
1164
+ { uri: uri, diagnostics: diagnostics.fetch(uri) }
1165
+ end
1166
+ end
1167
+
1168
+ enqueue_write_job SendMessageJob.to_client(
1169
+ message: CustomMethods::Query__Diagnostics.response(id, result)
1170
+ )
1171
+ end
1172
+ end
1173
+ end
1174
+
1175
+ # Methods that command socket clients cannot send because they control the server lifecycle
1176
+ # The only methods a command socket client may send: what `steep check` and
1177
+ # `steep query` use. Everything else is rejected -- lifecycle methods because the
1178
+ # server belongs to whoever started it, and document synchronization because the
1179
+ # LSP client owns the content of the files it opens.
1180
+ COMMAND_SOCKET_ALLOWED_METHODS = [
1181
+ CustomMethods::TypeCheck::METHOD,
1182
+ CustomMethods::Query__Diagnostics::METHOD,
1183
+ CustomMethods::Query__Definition::METHOD,
1184
+ "textDocument/hover",
1185
+ "$/ping"
1186
+ ].freeze
1187
+
1188
+ # Notifications that are copied to command socket sessions with a pending `$/steep/typecheck` request
1189
+ COMMAND_SOCKET_FORWARDED_NOTIFICATIONS = ["textDocument/publishDiagnostics", "window/showMessage"].freeze
1190
+
1191
+ # Processes a message received from a command socket client
1192
+ #
1193
+ # Requests are assigned a fresh id and enqueued as if they came from the LSP client,
1194
+ # and the responses are routed back to the session through `#write_message_to_client`.
1195
+ #
1196
+ # May be called from any thread.
1197
+ #
1198
+ def process_command_socket_message(message, session)
1199
+ method = message[:method]
1200
+ id = message[:id]
1201
+
1202
+ if method && !COMMAND_SOCKET_ALLOWED_METHODS.include?(method.to_s)
1203
+ Steep.logger.warn { "Command socket: rejected `#{method}`" }
1204
+ if id
1205
+ session.write({
1206
+ id: id,
1207
+ error: {
1208
+ code: LSP::Constant::ErrorCodes::INVALID_REQUEST,
1209
+ message: "`#{method}` is not allowed through the command socket"
1210
+ }
1211
+ })
1212
+ end
1213
+ return
1214
+ end
1215
+
1216
+ unless initialize_params
1217
+ if id
1218
+ session.write({
1219
+ id: id,
1220
+ error: {
1221
+ code: LSP::Constant::ErrorCodes::SERVER_NOT_INITIALIZED,
1222
+ message: "The language server is not initialized yet"
1223
+ }
1224
+ })
1225
+ end
1226
+ return
1227
+ end
1228
+
1229
+ case
1230
+ when method && id
1231
+ guid = SecureRandom.uuid
1232
+ @command_socket_mutex.synchronize do
1233
+ @command_socket_requests[guid] = [session, id, method.to_s == CustomMethods::TypeCheck::METHOD]
1234
+ end
1235
+ message = message.merge({ id: guid })
1236
+ when id
1237
+ # A response from the client, but nothing is waiting for it
1238
+ return
1239
+ end
1240
+
1241
+ # Reload files changed on disk before processing, because command socket clients
1242
+ # modify files without sending `didChange` notifications
1243
+ job_queue << -> do
1244
+ sync_project_files_from_disk()
1245
+ process_message_from_client(message)
1246
+ end
1247
+ rescue ClosedQueueError
1248
+ Steep.logger.warn { "Command socket: server is shutting down" }
1249
+ end
1250
+
1251
+ # Deregisters the pending requests of a disconnected command socket session
1252
+ def finish_command_socket_session(session)
1253
+ @command_socket_mutex.synchronize do
1254
+ @command_socket_requests.delete_if {|_, entry| entry[0] == session }
1255
+ end
1256
+ end
1257
+
1258
+ def write_message_to_client(message)
1259
+ unless message.key?(:method)
1260
+ entry = @command_socket_mutex.synchronize { @command_socket_requests.delete(message[:id]) }
1261
+ if entry
1262
+ session, original_id, _ = entry
1263
+ session.write(message.merge({ id: original_id }))
1264
+ return
1265
+ end
1266
+ end
1267
+
1268
+ writer.write(message)
1269
+
1270
+ if message.key?(:method) && !message.key?(:id)
1271
+ forward_notification_to_command_sessions(message)
1272
+ end
1273
+ end
1274
+
1275
+ def forward_notification_to_command_sessions(message)
1276
+ return unless COMMAND_SOCKET_FORWARDED_NOTIFICATIONS.include?(message[:method].to_s)
1277
+
1278
+ sessions = @command_socket_mutex.synchronize do
1279
+ @command_socket_requests.each_value.with_object([]) do |entry, array|
1280
+ session, _, typecheck = entry
1281
+ array << session if typecheck
1282
+ end
1283
+ end
1284
+
1285
+ sessions.uniq!
1286
+ sessions.each {|session| session.write(message) }
1287
+ end
1288
+
1289
+ def typecheck_request_from_command_socket?(request)
1290
+ @command_socket_mutex.synchronize do
1291
+ @command_socket_requests.key?(request.guid)
1292
+ end
1293
+ end
1294
+
1295
+ # Records the mtimes of all project files, as the baseline of `#sync_project_files_from_disk`
1296
+ def reset_project_file_mtimes
1297
+ mtimes = {} #: Hash[Pathname, Time?]
1298
+ each_project_file_path do |path|
1299
+ mtimes[path] = file_mtime(path)
1300
+ end
1301
+ @project_file_mtimes = mtimes
1302
+ end
1303
+
1304
+ def record_project_file_mtime(path)
1305
+ if mtimes = @project_file_mtimes
1306
+ mtimes[path] = file_mtime(path)
1307
+ end
1308
+ end
1309
+
1310
+ def file_mtime(path)
1311
+ File.mtime(path)
1312
+ rescue SystemCallError
1313
+ nil
1314
+ end
1315
+
1316
+ def each_project_file_path(&block)
1317
+ loader = Services::FileLoader.new(base_dir: project.base_dir)
1318
+ project.targets.each do |target|
1319
+ loader.each_path_in_target(target) do |relative_path|
1320
+ yield project.absolute_path(relative_path)
1321
+ end
1322
+ end
1323
+ end
1324
+
1325
+ # Detects files changed on disk since the last load/sync, and reloads them
1326
+ #
1327
+ # Skips files open in the editor because the client owns their contents.
1328
+ # Does nothing until the initial project load finishes.
1329
+ #
1330
+ def sync_project_files_from_disk
1331
+ mtimes = @project_file_mtimes or return
1332
+
1333
+ changes = {} #: Hash[Pathname, Symbol]
1334
+ seen = Set[] #: Set[Pathname]
1335
+
1336
+ each_project_file_path do |path|
1337
+ next if seen.include?(path)
1338
+ seen << path
1339
+
1340
+ mtime = file_mtime(path)
1341
+ recorded = mtimes[path]
1342
+
1343
+ case
1344
+ when recorded.nil? && mtime
1345
+ changes[path] = mtimes.key?(path) ? :changed : :created
1346
+ when recorded && mtime.nil?
1347
+ changes[path] = :deleted
1348
+ when recorded != mtime
1349
+ changes[path] = :changed
1350
+ end
1351
+
1352
+ mtimes[path] = mtime
1353
+ end
1354
+
1355
+ deleted_paths = mtimes.keys.select {|path| !seen.include?(path) && mtimes[path] && file_mtime(path).nil? }
1356
+ deleted_paths.each do |path|
1357
+ changes[path] = :deleted
1358
+ mtimes[path] = nil
1359
+ end
1360
+
1361
+ changes.delete_if {|path, _| controller.open_paths.include?(path) }
1362
+ return if changes.empty?
1363
+
1364
+ Steep.logger.info { "Command socket: reloading #{changes.size} file(s) changed on disk" }
1365
+
1366
+ changes.each do |path, type|
1367
+ content =
1368
+ if type == :deleted
1369
+ ""
1370
+ else
1371
+ begin
1372
+ path.read
1373
+ rescue SystemCallError
1374
+ next
1375
+ end
1376
+ end
1377
+
1378
+ case
1379
+ when controller.code_path?(path)
1380
+ controller.add_dirty_code_path(path)
1381
+ when controller.signature_path?(path)
1382
+ controller.add_dirty_signature_path(path)
1383
+ when controller.inline_path?(path)
1384
+ controller.add_dirty_inline_path(path, content)
1385
+ end
1386
+
1387
+ broadcast_notification(CustomMethods::FileReset.notification({ uri: PathHelper.to_uri(path).to_s, content: content }))
1388
+ end
1389
+
1390
+ if typecheck_automatically
1391
+ start_type_checking_queue.execute do
1392
+ job_queue.push(
1393
+ -> do
1394
+ start_type_check(
1395
+ last_request: current_type_check_request,
1396
+ progress: work_done_progress(SecureRandom.uuid),
1397
+ needs_response: false
1398
+ )
1399
+ end
1400
+ )
1401
+ end
1402
+ end
1403
+ end
1404
+
1024
1405
  def work_done_progress(guid)
1025
1406
  if work_done_progress_supported?
1026
1407
  WorkDoneProgress.new(guid) do |message|
@@ -1,13 +1,14 @@
1
1
  module Steep
2
2
  module Server
3
3
  class TypeCheckWorker < BaseWorker
4
- attr_reader :project, :assignment, :service
4
+ attr_reader :project, :assignment
5
5
  attr_reader :commandline_args
6
6
  attr_reader :current_type_check_guid
7
7
 
8
8
  WorkspaceSymbolJob = _ = Struct.new(:query, :id, keyword_init: true)
9
9
  StatsJob = _ = Struct.new(:id, keyword_init: true)
10
10
  QueryDefinitionJob = _ = Struct.new(:id, :name, keyword_init: true)
11
+ QueryDiagnosticsJob = _ = Struct.new(:id, keyword_init: true)
11
12
  StartTypeCheckJob = _ = Struct.new(:guid, :changes, keyword_init: true)
12
13
  TypeCheckCodeJob = _ = Struct.new(:guid, :path, :target, keyword_init: true)
13
14
  ValidateAppSignatureJob = _ = Struct.new(:guid, :path, :target, keyword_init: true)
@@ -68,7 +69,7 @@ module Steep
68
69
  @io_socket = io_socket
69
70
  @service = service if service
70
71
  @child_pids = []
71
- @need_to_warmup = defined?(Process.warmup)
72
+ @need_to_warmup = true
72
73
 
73
74
  if io_socket
74
75
  Signal.trap "SIGCHLD" do
@@ -112,6 +113,8 @@ module Steep
112
113
  when CustomMethods::Query__Definition::METHOD
113
114
  params = request[:params] #: CustomMethods::Query__Definition::params
114
115
  queue << QueryDefinitionJob.new(id: request[:id], name: params[:name])
116
+ when CustomMethods::Query__Diagnostics::METHOD
117
+ queue << QueryDiagnosticsJob.new(id: request[:id])
115
118
  when "textDocument/definition"
116
119
  queue << GotoJob.definition(id: request[:id], params: request[:params])
117
120
  when "textDocument/implementation"
@@ -307,6 +310,10 @@ module Steep
307
310
  writer.write(
308
311
  CustomMethods::Query__Definition.response(job.id, query_definition_result(job.name))
309
312
  )
313
+ when QueryDiagnosticsJob
314
+ writer.write(
315
+ CustomMethods::Query__Diagnostics.response(job.id, query_diagnostics_result())
316
+ )
310
317
  end
311
318
  end
312
319
 
@@ -358,6 +365,45 @@ module Steep
358
365
  end
359
366
  end
360
367
 
368
+ # Returns the diagnostics of the files this worker has type checked so far
369
+ #
370
+ # An array of `Query__Diagnostics::entry`, with the LSP-formatted diagnostics per file URI.
371
+ # Files that are loaded but not type checked yet are not included.
372
+ #
373
+ def query_diagnostics_result
374
+ result = {} #: Hash[String, Array[untyped]]
375
+
376
+ service.source_files.each_value do |file|
377
+ next if file.typing.nil? && file.errors.nil?
378
+
379
+ absolute_path = project.absolute_path(file.path)
380
+
381
+ group_target =
382
+ project.group_for_source_path(absolute_path) ||
383
+ project.group_for_inline_source_path(absolute_path) ||
384
+ project.target_for_source_path(absolute_path) ||
385
+ project.target_for_inline_source_path(absolute_path)
386
+ next unless group_target
387
+
388
+ formatter = Diagnostic::LSPFormatter.new(group_target.code_diagnostics_config)
389
+ uri = PathHelper.to_uri(absolute_path).to_s
390
+ array = result[uri] ||= []
391
+ array.concat(file.diagnostics.filter_map { formatter.format(_1) })
392
+ end
393
+
394
+ formatter = Diagnostic::LSPFormatter.new({}, **{})
395
+ service.signature_validation_diagnostics.each_value do |path_diagnostics|
396
+ path_diagnostics.each do |path, diagnostics|
397
+ absolute_path = path.absolute? ? path : project.absolute_path(path)
398
+ uri = PathHelper.to_uri(absolute_path).to_s
399
+ array = result[uri] ||= []
400
+ array.concat(diagnostics.filter_map { formatter.format(_1) })
401
+ end
402
+ end
403
+
404
+ result.map { { uri: _1, diagnostics: _2 } }
405
+ end
406
+
361
407
  def query_definition_result(name_string)
362
408
  name = Services::GotoService.parse_name(name_string)
363
409
 
@@ -422,7 +422,7 @@ module Steep
422
422
  when :send
423
423
  location = (_ = node.location) #: Parser::AST::_SelectorLocation
424
424
  if test_ast_location(location.selector, line: line, column: column)
425
- if (parent = parents[0]) && parent.type == :block && parent.children[0] === node
425
+ if (parent = parents[0]) && (parent.type == :block || parent.type == :numblock || parent.type == :itblock) && parent.children[0] === node
426
426
  node = parents[0]
427
427
  end
428
428
 
@@ -524,7 +524,9 @@ module Steep
524
524
  def constant_definition_in_ruby(name, locations:)
525
525
  type_check.source_files.each do |path, source|
526
526
  if typing = source.typing
527
- target = project.target_for_source_path(path) or raise
527
+ # Inline sources are type checked too, but they don't have a Ruby definition to go to.
528
+ # Their declarations are found through the RBS lookup instead.
529
+ target = project.target_for_source_path(path) or next
528
530
  entry = typing.source_index.entry(constant: name)
529
531
  entry.definitions.each do |node|
530
532
  case node.type
@@ -551,7 +553,9 @@ module Steep
551
553
  if in_ruby
552
554
  type_check.source_files.each do |path, source|
553
555
  if typing = source.typing
554
- target = project.target_for_source_path(path) or raise
556
+ # Inline sources are type checked too, but they don't have a Ruby definition to go to.
557
+ # Their declarations are found through the RBS lookup instead.
558
+ target = project.target_for_source_path(path) or next
555
559
  entry = typing.source_index.entry(method: name)
556
560
 
557
561
  if entry.definitions.empty?
@@ -56,7 +56,7 @@ module Steep
56
56
 
57
57
  def class_or_module?
58
58
  (class_decl || class_alias) ? true : false
59
- end
59
+ end
60
60
  end
61
61
 
62
62
  TypeAliasContent = _ = Struct.new(:location, :decl, keyword_init: true)
@@ -103,7 +103,7 @@ module Steep
103
103
  when :send, :csend
104
104
  result_node =
105
105
  case parents[0]&.type
106
- when :block, :numblock
106
+ when :block, :numblock, :itblock
107
107
  if node == parents.fetch(0).children[0]
108
108
  parents.fetch(0)
109
109
  else
@@ -59,7 +59,7 @@ module Steep
59
59
  if begin_loc.end_pos <= pos && pos <= end_loc.begin_pos
60
60
  # Given position is between open/close parens of args of send node
61
61
 
62
- if parent && (parent.type == :block || parent.type == :numblock) && node.equal?(parent.children[0])
62
+ if parent && (parent.type == :block || parent.type == :numblock || parent.type == :itblock) && node.equal?(parent.children[0])
63
63
  send_node = parent
64
64
  else
65
65
  send_node = node
data/lib/steep/source.rb CHANGED
@@ -31,7 +31,7 @@ module Steep
31
31
  end
32
32
 
33
33
  def self.new_parser
34
- Prism::Translation::Parser33.new(Builder.new).tap do |parser|
34
+ Prism::Translation::Parser34.new(Builder.new).tap do |parser|
35
35
  parser.diagnostics.all_errors_are_fatal = true
36
36
  parser.diagnostics.ignore_warnings = true
37
37
  end
@@ -231,7 +231,7 @@ module Steep
231
231
  end
232
232
 
233
233
  when :rescue
234
- body, resbodies, else_node, loc = deconstruct_rescue_node!(node)
234
+ body, _, else_node, loc = deconstruct_rescue_node!(node)
235
235
 
236
236
  if else_node
237
237
  loc.else or raise
@@ -257,7 +257,7 @@ module Steep
257
257
  annot.line or next
258
258
 
259
259
  case node.type
260
- when :def, :module, :class, :block, :numblock, :ensure, :defs, :resbody
260
+ when :def, :module, :class, :block, :numblock, :itblock, :ensure, :defs, :resbody
261
261
  location = node.loc
262
262
  location.line <= annot.line && annot.line < location.last_line
263
263
  else
@@ -356,7 +356,6 @@ module Steep
356
356
 
357
357
  def find_heredoc_nodes(line, column, position)
358
358
  each_heredoc_node() do |nodes, location|
359
- node = nodes[0]
360
359
  loc = location.heredoc_body #: Parser::Source::Range
361
360
 
362
361
  if range = loc.to_range
@@ -470,7 +469,7 @@ module Steep
470
469
  return false unless send_node
471
470
 
472
471
  if send_node.type == :send
473
- receiver, method, args = deconstruct_send_node!(send_node)
472
+ receiver, method, _ = deconstruct_send_node!(send_node)
474
473
 
475
474
  return false unless receiver
476
475
 
@@ -565,13 +564,13 @@ module Steep
565
564
  end
566
565
  ]
567
566
  )
568
- when :numblock
569
- send, size, body = node.children
567
+ when :numblock, :itblock
568
+ send, arg, body = node.children
570
569
  node = node.updated(
571
570
  nil,
572
571
  [
573
572
  map_child_node(send) {|child| insert_type_node(child, child_assertions) },
574
- size,
573
+ arg,
575
574
  insert_type_node(body, child_assertions)
576
575
  ]
577
576
  )
@@ -648,7 +647,7 @@ module Steep
648
647
  case node.type
649
648
  when :send, :csend
650
649
  node
651
- when :block, :numblock
650
+ when :block, :numblock, :itblock
652
651
  send = node.children[0]
653
652
  case send.type
654
653
  when :send, :csend
@@ -657,7 +656,7 @@ module Steep
657
656
  end
658
657
 
659
658
  if send_node
660
- receiver_node, name, _, location = deconstruct_send_node!(send_node)
659
+ receiver_node, _, _, location = deconstruct_send_node!(send_node)
661
660
 
662
661
  if receiver_node
663
662
  if location.dot && location.selector