steep 0.44.0 → 0.47.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.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +8 -0
  3. data/.github/workflows/ruby.yml +3 -2
  4. data/.gitignore +0 -1
  5. data/CHANGELOG.md +42 -0
  6. data/Gemfile +0 -3
  7. data/Gemfile.lock +75 -0
  8. data/README.md +2 -1
  9. data/lib/steep/annotation_parser.rb +1 -1
  10. data/lib/steep/ast/builtin.rb +7 -1
  11. data/lib/steep/ast/types/factory.rb +19 -25
  12. data/lib/steep/cli.rb +7 -1
  13. data/lib/steep/diagnostic/lsp_formatter.rb +59 -6
  14. data/lib/steep/diagnostic/ruby.rb +188 -60
  15. data/lib/steep/diagnostic/signature.rb +38 -15
  16. data/lib/steep/drivers/check.rb +3 -0
  17. data/lib/steep/drivers/init.rb +10 -3
  18. data/lib/steep/drivers/utils/driver_helper.rb +15 -0
  19. data/lib/steep/drivers/validate.rb +1 -1
  20. data/lib/steep/drivers/watch.rb +3 -0
  21. data/lib/steep/equatable.rb +21 -0
  22. data/lib/steep/interface/function.rb +798 -579
  23. data/lib/steep/project/dsl.rb +135 -36
  24. data/lib/steep/project/options.rb +13 -53
  25. data/lib/steep/project/target.rb +22 -8
  26. data/lib/steep/server/interaction_worker.rb +245 -26
  27. data/lib/steep/server/master.rb +2 -2
  28. data/lib/steep/server/type_check_worker.rb +6 -9
  29. data/lib/steep/services/file_loader.rb +26 -19
  30. data/lib/steep/services/goto_service.rb +1 -0
  31. data/lib/steep/services/hover_content.rb +135 -80
  32. data/lib/steep/source.rb +12 -11
  33. data/lib/steep/type_construction.rb +435 -502
  34. data/lib/steep/type_inference/block_params.rb +3 -6
  35. data/lib/steep/type_inference/method_params.rb +483 -0
  36. data/lib/steep/type_inference/send_args.rb +599 -128
  37. data/lib/steep/typing.rb +46 -21
  38. data/lib/steep/version.rb +1 -1
  39. data/lib/steep.rb +4 -2
  40. data/sample/Steepfile +10 -3
  41. data/smoke/alias/Steepfile +2 -1
  42. data/smoke/and/Steepfile +2 -1
  43. data/smoke/array/Steepfile +2 -1
  44. data/smoke/array/test_expectations.yml +3 -3
  45. data/smoke/block/Steepfile +2 -2
  46. data/smoke/block/c.rb +0 -1
  47. data/smoke/case/Steepfile +2 -1
  48. data/smoke/class/Steepfile +2 -1
  49. data/smoke/class/test_expectations.yml +12 -15
  50. data/smoke/const/Steepfile +2 -1
  51. data/smoke/diagnostics/Steepfile +2 -1
  52. data/smoke/diagnostics/different_method_parameter_kind.rb +9 -0
  53. data/smoke/diagnostics/method_arity_mismatch.rb +2 -2
  54. data/smoke/diagnostics/method_parameter_mismatch.rb +10 -0
  55. data/smoke/diagnostics/test_expectations.yml +108 -31
  56. data/smoke/diagnostics-rbs/Steepfile +1 -1
  57. data/smoke/diagnostics-rbs/mixin-class-error.rbs +6 -0
  58. data/smoke/diagnostics-rbs/test_expectations.yml +12 -0
  59. data/smoke/diagnostics-rbs-duplicated/Steepfile +2 -1
  60. data/smoke/diagnostics-ruby-unsat/Steepfile +2 -1
  61. data/smoke/dstr/Steepfile +2 -1
  62. data/smoke/ensure/Steepfile +2 -1
  63. data/smoke/ensure/test_expectations.yml +3 -3
  64. data/smoke/enumerator/Steepfile +2 -1
  65. data/smoke/enumerator/test_expectations.yml +1 -1
  66. data/smoke/extension/Steepfile +2 -1
  67. data/smoke/extension/e.rbs +1 -1
  68. data/smoke/hash/Steepfile +2 -1
  69. data/smoke/hello/Steepfile +2 -1
  70. data/smoke/if/Steepfile +2 -1
  71. data/smoke/implements/Steepfile +2 -1
  72. data/smoke/initialize/Steepfile +2 -1
  73. data/smoke/integer/Steepfile +2 -1
  74. data/smoke/interface/Steepfile +2 -1
  75. data/smoke/kwbegin/Steepfile +2 -1
  76. data/smoke/lambda/Steepfile +2 -1
  77. data/smoke/literal/Steepfile +2 -1
  78. data/smoke/literal/test_expectations.yml +2 -2
  79. data/smoke/map/Steepfile +2 -1
  80. data/smoke/method/Steepfile +2 -1
  81. data/smoke/method/test_expectations.yml +11 -10
  82. data/smoke/module/Steepfile +2 -1
  83. data/smoke/regexp/Steepfile +2 -1
  84. data/smoke/regression/Steepfile +2 -1
  85. data/smoke/rescue/Steepfile +2 -1
  86. data/smoke/rescue/test_expectations.yml +3 -3
  87. data/smoke/self/Steepfile +2 -1
  88. data/smoke/skip/Steepfile +2 -1
  89. data/smoke/stdout/Steepfile +2 -1
  90. data/smoke/super/Steepfile +2 -1
  91. data/smoke/toplevel/Steepfile +2 -1
  92. data/smoke/toplevel/test_expectations.yml +3 -3
  93. data/smoke/tsort/Steepfile +4 -5
  94. data/smoke/tsort/test_expectations.yml +2 -2
  95. data/smoke/type_case/Steepfile +2 -1
  96. data/smoke/unexpected/Steepfile +2 -1
  97. data/smoke/yield/Steepfile +2 -1
  98. data/steep.gemspec +2 -2
  99. metadata +16 -10
  100. data/sig/project.rbi +0 -109
@@ -7,6 +7,8 @@ module Steep
7
7
  HoverJob = Struct.new(:id, :path, :line, :column, keyword_init: true)
8
8
  CompletionJob = Struct.new(:id, :path, :line, :column, :trigger, keyword_init: true)
9
9
 
10
+ LSP = LanguageServer::Protocol
11
+
10
12
  attr_reader :service
11
13
 
12
14
  def initialize(project:, reader:, writer:, queue: Queue.new)
@@ -77,11 +79,12 @@ module Steep
77
79
  Steep.logger.info { "path=#{job.path}, line=#{job.line}, column=#{job.column}" }
78
80
 
79
81
  hover = Services::HoverContent.new(service: service)
80
- content = hover.content_for(path: job.path, line: job.line, column: job.column+1)
82
+ content = hover.content_for(path: job.path, line: job.line, column: job.column)
81
83
  if content
82
84
  range = content.location.yield_self do |location|
83
- start_position = { line: location.line - 1, character: location.column }
84
- end_position = { line: location.last_line - 1, character: location.last_column }
85
+ lsp_range = location.as_lsp_range
86
+ start_position = { line: lsp_range[:start][:line], character: lsp_range[:start][:character] }
87
+ end_position = { line: lsp_range[:end][:line], character: lsp_range[:end][:character] }
85
88
  { start: start_position, end: end_position }
86
89
  end
87
90
 
@@ -99,6 +102,36 @@ module Steep
99
102
 
100
103
  def format_hover(content)
101
104
  case content
105
+ when Services::HoverContent::TypeAliasContent
106
+ comment = content.decl.comment&.string || ''
107
+
108
+ <<-MD
109
+ #{comment}
110
+
111
+ ```rbs
112
+ #{retrieve_decl_information(content.decl)}
113
+ ```
114
+ MD
115
+ when Services::HoverContent::InterfaceContent
116
+ comment = content.decl.comment&.string || ''
117
+
118
+ <<-MD
119
+ #{comment}
120
+
121
+ ```rbs
122
+ #{retrieve_decl_information(content.decl)}
123
+ ```
124
+ MD
125
+ when Services::HoverContent::ClassContent
126
+ comment = content.decl.comment&.string || ''
127
+
128
+ <<-MD
129
+ #{comment}
130
+
131
+ ```rbs
132
+ #{retrieve_decl_information(content.decl)}
133
+ ```
134
+ MD
102
135
  when Services::HoverContent::VariableContent
103
136
  "`#{content.name}`: `#{content.type.to_s}`"
104
137
  when Services::HoverContent::MethodCallContent
@@ -151,32 +184,207 @@ HOVER
151
184
  Steep.logger.tagged("#response_to_completion") do
152
185
  Steep.measure "Generating response" do
153
186
  Steep.logger.info "path: #{job.path}, line: #{job.line}, column: #{job.column}, trigger: #{job.trigger}"
187
+ case
188
+ when target = project.target_for_source_path(job.path)
189
+ file = service.source_files[job.path] or return
190
+ subtyping = service.signature_services[target.name].current_subtyping or return
191
+
192
+ provider = Services::CompletionProvider.new(source_text: file.content, path: job.path, subtyping: subtyping)
193
+ items = begin
194
+ provider.run(line: job.line, column: job.column)
195
+ rescue Parser::SyntaxError
196
+ []
197
+ end
198
+
199
+ completion_items = items.map do |item|
200
+ format_completion_item(item)
201
+ end
202
+
203
+ Steep.logger.debug "items = #{completion_items.inspect}"
204
+
205
+ LSP::Interface::CompletionList.new(
206
+ is_incomplete: false,
207
+ items: completion_items
208
+ )
209
+ when (_, targets = project.targets_for_path(job.path))
210
+ target = targets[0] or return
211
+ sig_service = service.signature_services[target.name]
212
+ relative_path = job.path
213
+ buffer = RBS::Buffer.new(name: relative_path, content: sig_service.files[relative_path].content)
214
+ pos = buffer.loc_to_pos([job.line, job.column])
215
+ prefix = buffer.content[0...pos].reverse[/\A[\w\d]*/].reverse
216
+
217
+ case sig_service.status
218
+ when Steep::Services::SignatureService::SyntaxErrorStatus, Steep::Services::SignatureService::AncestorErrorStatus
219
+ return
220
+ end
221
+
222
+ decls = sig_service.files[relative_path].decls
223
+ locator = RBS::Locator.new(decls: decls)
224
+
225
+ hd, tail = locator.find2(line: job.line, column: job.column)
226
+
227
+ namespace = []
228
+ tail.each do |t|
229
+ case t
230
+ when RBS::AST::Declarations::Module, RBS::AST::Declarations::Class
231
+ namespace << t.name.to_namespace
232
+ end
233
+ end
234
+ context = []
235
+
236
+ namespace.each do |ns|
237
+ context.map! { |n| ns + n }
238
+ context << ns
239
+ end
240
+
241
+ context.map!(&:absolute!)
242
+
243
+ class_items = sig_service.latest_env.class_decls.keys.map { |type_name|
244
+ format_completion_item_for_rbs(sig_service, type_name, context, job, prefix)
245
+ }.compact
154
246
 
155
- target = project.target_for_source_path(job.path) or return
156
- file = service.source_files[job.path] or return
157
- subtyping = service.signature_services[target.name].current_subtyping or return
247
+ alias_items = sig_service.latest_env.alias_decls.keys.map { |type_name|
248
+ format_completion_item_for_rbs(sig_service, type_name, context, job, prefix)
249
+ }.compact
158
250
 
159
- provider = Services::CompletionProvider.new(source_text: file.content, path: job.path, subtyping: subtyping)
160
- items = begin
161
- provider.run(line: job.line, column: job.column)
162
- rescue Parser::SyntaxError
163
- []
164
- end
251
+ interface_items = sig_service.latest_env.interface_decls.keys.map {|type_name|
252
+ format_completion_item_for_rbs(sig_service, type_name, context, job, prefix)
253
+ }.compact
165
254
 
166
- completion_items = items.map do |item|
167
- format_completion_item(item)
255
+ completion_items = class_items + alias_items + interface_items
256
+
257
+ LSP::Interface::CompletionList.new(
258
+ is_incomplete: false,
259
+ items: completion_items
260
+ )
168
261
  end
262
+ end
263
+ end
264
+ end
169
265
 
170
- Steep.logger.debug "items = #{completion_items.inspect}"
266
+ def format_completion_item_for_rbs(sig_service, type_name, context, job, prefix)
267
+ range = LanguageServer::Protocol::Interface::Range.new(
268
+ start: LanguageServer::Protocol::Interface::Position.new(
269
+ line: job.line - 1,
270
+ character: job.column - prefix.size
271
+ ),
272
+ end: LanguageServer::Protocol::Interface::Position.new(
273
+ line: job.line - 1,
274
+ character: job.column - prefix.size
275
+ )
276
+ )
171
277
 
172
- LSP::Interface::CompletionList.new(
173
- is_incomplete: false,
174
- items: completion_items
175
- )
278
+ name = relative_name_in_context(type_name, context).to_s
279
+
280
+ return unless name.start_with?(prefix)
281
+
282
+ case type_name.kind
283
+ when :class
284
+ class_decl = sig_service.latest_env.class_decls[type_name]&.decls[0]&.decl or raise
285
+
286
+ LanguageServer::Protocol::Interface::CompletionItem.new(
287
+ label: "#{name}",
288
+ documentation: format_comment(class_decl.comment),
289
+ text_edit: LanguageServer::Protocol::Interface::TextEdit.new(
290
+ range: range,
291
+ new_text: name
292
+ ),
293
+ kind: LSP::Constant::CompletionItemKind::CLASS,
294
+ insert_text_format: LSP::Constant::InsertTextFormat::SNIPPET
295
+
296
+ )
297
+ when :alias
298
+ alias_decl = sig_service.latest_env.alias_decls[type_name]&.decl or raise
299
+ LanguageServer::Protocol::Interface::CompletionItem.new(
300
+ label: "#{name}",
301
+ text_edit: LanguageServer::Protocol::Interface::TextEdit.new(
302
+ range: range,
303
+ new_text: name
304
+ ),
305
+ documentation: format_comment(alias_decl.comment),
306
+ # https://github.com/microsoft/vscode-languageserver-node/blob/6d78fc4d25719b231aba64a721a606f58b9e0a5f/client/src/common/client.ts#L624-L650
307
+ kind: LSP::Constant::CompletionItemKind::FIELD,
308
+ insert_text_format: LSP::Constant::InsertTextFormat::SNIPPET
309
+ )
310
+ when :interface
311
+ interface_decl = sig_service.latest_env.interface_decls[type_name]&.decl or raise
312
+
313
+ LanguageServer::Protocol::Interface::CompletionItem.new(
314
+ label: "#{name}",
315
+ text_edit: LanguageServer::Protocol::Interface::TextEdit.new(
316
+ range: range,
317
+ new_text: name
318
+ ),
319
+ documentation: format_comment(interface_decl.comment),
320
+ kind: LanguageServer::Protocol::Constant::CompletionItemKind::INTERFACE,
321
+ insert_text_format: LanguageServer::Protocol::Constant::InsertTextFormat::SNIPPET
322
+ )
323
+ end
324
+ end
325
+
326
+ def format_comment(comment)
327
+ if comment
328
+ LSP::Interface::MarkupContent.new(
329
+ kind: LSP::Constant::MarkupKind::MARKDOWN,
330
+ value: comment.string
331
+ )
332
+ end
333
+ end
334
+
335
+ def name_and_params(name, params)
336
+ if params.empty?
337
+ "#{name}"
338
+ else
339
+ ps = params.each.map do |param|
340
+ s = ""
341
+ if param.skip_validation
342
+ s << "unchecked "
343
+ end
344
+ case param.variance
345
+ when :invariant
346
+ # nop
347
+ when :covariant
348
+ s << "out "
349
+ when :contravariant
350
+ s << "in "
351
+ end
352
+ s + param.name.to_s
353
+ end
354
+
355
+ "#{name}[#{ps.join(", ")}]"
356
+ end
357
+ end
358
+
359
+ def name_and_args(name, args)
360
+ if name && args
361
+ if args.empty?
362
+ "#{name}"
363
+ else
364
+ "#{name}[#{args.join(", ")}]"
176
365
  end
177
366
  end
178
367
  end
179
368
 
369
+ def retrieve_decl_information(decl)
370
+ case decl
371
+ when RBS::AST::Declarations::Class
372
+ super_class = if super_class = decl.super_class
373
+ " < #{name_and_args(super_class.name, super_class.args)}"
374
+ end
375
+ "class #{name_and_params(decl.name, decl.type_params)}#{super_class}"
376
+ when RBS::AST::Declarations::Module
377
+ self_type = unless decl.self_types.empty?
378
+ " : #{decl.self_types.join(", ")}"
379
+ end
380
+ "module #{name_and_params(decl.name, decl.type_params)}#{self_type}"
381
+ when RBS::AST::Declarations::Alias
382
+ "type #{decl.name} = #{decl.type}"
383
+ when RBS::AST::Declarations::Interface
384
+ "interface #{name_and_params(decl.name, decl.type_params)}"
385
+ end
386
+ end
387
+
180
388
  def format_completion_item(item)
181
389
  range = LanguageServer::Protocol::Interface::Range.new(
182
390
  start: LanguageServer::Protocol::Interface::Position.new(
@@ -194,18 +402,18 @@ HOVER
194
402
  LanguageServer::Protocol::Interface::CompletionItem.new(
195
403
  label: item.identifier,
196
404
  kind: LanguageServer::Protocol::Constant::CompletionItemKind::VARIABLE,
197
- detail: "#{item.identifier}: #{item.type}",
405
+ detail: item.type.to_s,
198
406
  text_edit: LanguageServer::Protocol::Interface::TextEdit.new(
199
407
  range: range,
200
- new_text: "#{item.identifier}"
408
+ new_text: item.identifier
201
409
  )
202
410
  )
203
411
  when Services::CompletionProvider::MethodNameItem
204
- label = "def #{item.identifier}: #{item.method_type}"
205
412
  method_type_snippet = method_type_to_snippet(item.method_type)
206
413
  LanguageServer::Protocol::Interface::CompletionItem.new(
207
- label: label,
414
+ label: item.identifier,
208
415
  kind: LanguageServer::Protocol::Constant::CompletionItemKind::METHOD,
416
+ detail: item.method_type.to_s,
209
417
  text_edit: LanguageServer::Protocol::Interface::TextEdit.new(
210
418
  new_text: "#{item.identifier}#{method_type_snippet}",
211
419
  range: range
@@ -215,14 +423,14 @@ HOVER
215
423
  sort_text: item.inherited? ? 'z' : 'a' # Ensure language server puts non-inherited methods before inherited methods
216
424
  )
217
425
  when Services::CompletionProvider::InstanceVariableItem
218
- label = "#{item.identifier}: #{item.type}"
219
426
  LanguageServer::Protocol::Interface::CompletionItem.new(
220
- label: label,
427
+ label: item.identifier,
221
428
  kind: LanguageServer::Protocol::Constant::CompletionItemKind::FIELD,
429
+ detail: item.type.to_s,
222
430
  text_edit: LanguageServer::Protocol::Interface::TextEdit.new(
223
431
  range: range,
224
432
  new_text: item.identifier,
225
- ),
433
+ ),
226
434
  insert_text_format: LanguageServer::Protocol::Constant::InsertTextFormat::SNIPPET
227
435
  )
228
436
  end
@@ -297,6 +505,17 @@ HOVER
297
505
 
298
506
  params.join(", ")
299
507
  end
508
+
509
+ def relative_name_in_context(type_name, context)
510
+ context.each do |namespace|
511
+ if (type_name.to_s == namespace.to_type_name.to_s || type_name.namespace.to_s == "::")
512
+ return RBS::TypeName.new(namespace: RBS::Namespace.empty, name: type_name.name)
513
+ elsif type_name.to_s.start_with?(namespace.to_s)
514
+ return TypeName(type_name.to_s.sub(namespace.to_type_name.to_s, '')).relative!
515
+ end
516
+ end
517
+ type_name
518
+ end
300
519
  end
301
520
  end
302
521
  end
@@ -501,9 +501,9 @@ module Steep
501
501
  ),
502
502
  workspace_symbol_provider: true,
503
503
  definition_provider: true,
504
- declaration_provider: true,
504
+ declaration_provider: false,
505
505
  implementation_provider: true,
506
- type_definition_provider: true
506
+ type_definition_provider: false
507
507
  )
508
508
  )
509
509
  }
@@ -144,7 +144,7 @@ module Steep
144
144
  if job.guid == current_type_check_guid
145
145
  Steep.logger.info { "Processing ValidateAppSignature for guid=#{job.guid}, path=#{job.path}" }
146
146
  service.validate_signature(path: project.relative_path(job.path)) do |path, diagnostics|
147
- formatter = Diagnostic::LSPFormatter.new()
147
+ formatter = Diagnostic::LSPFormatter.new({})
148
148
 
149
149
  writer.write(
150
150
  method: :"textDocument/publishDiagnostics",
@@ -162,13 +162,13 @@ module Steep
162
162
  if job.guid == current_type_check_guid
163
163
  Steep.logger.info { "Processing ValidateLibrarySignature for guid=#{job.guid}, path=#{job.path}" }
164
164
  service.validate_signature(path: job.path) do |path, diagnostics|
165
- formatter = Diagnostic::LSPFormatter.new()
165
+ formatter = Diagnostic::LSPFormatter.new({})
166
166
 
167
167
  writer.write(
168
168
  method: :"textDocument/publishDiagnostics",
169
169
  params: LSP::Interface::PublishDiagnosticsParams.new(
170
170
  uri: URI.parse(job.path.to_s).tap {|uri| uri.scheme = "file"},
171
- diagnostics: diagnostics.map {|diagnostic| formatter.format(diagnostic) }.uniq
171
+ diagnostics: diagnostics.map {|diagnostic| formatter.format(diagnostic) }.uniq.compact
172
172
  )
173
173
  )
174
174
  end
@@ -180,17 +180,14 @@ module Steep
180
180
  if job.guid == current_type_check_guid
181
181
  Steep.logger.info { "Processing TypeCheckCodeJob for guid=#{job.guid}, path=#{job.path}" }
182
182
  service.typecheck_source(path: project.relative_path(job.path)) do |path, diagnostics|
183
- if target = project.target_for_source_path(path)
184
- diagnostics = diagnostics.select {|diagnostic| target.options.error_to_report?(diagnostic) }
185
- end
186
-
187
- formatter = Diagnostic::LSPFormatter.new()
183
+ target = project.target_for_source_path(path)
184
+ formatter = Diagnostic::LSPFormatter.new(target&.code_diagnostics_config || {})
188
185
 
189
186
  writer.write(
190
187
  method: :"textDocument/publishDiagnostics",
191
188
  params: LSP::Interface::PublishDiagnosticsParams.new(
192
189
  uri: URI.parse(job.path.to_s).tap {|uri| uri.scheme = "file"},
193
- diagnostics: diagnostics.map {|diagnostic| formatter.format(diagnostic) }.uniq
190
+ diagnostics: diagnostics.map {|diagnostic| formatter.format(diagnostic) }.uniq.compact
194
191
  )
195
192
  )
196
193
  end
@@ -8,29 +8,36 @@ module Steep
8
8
  end
9
9
 
10
10
  def each_path_in_patterns(pattern, commandline_patterns = [])
11
- pats = commandline_patterns.empty? ? pattern.patterns : commandline_patterns
12
-
13
- pats.each do |path|
14
- absolute_path = base_dir + path
15
-
16
- if absolute_path.file?
17
- yield absolute_path.relative_path_from(base_dir)
18
- else
19
- files = if absolute_path.directory?
20
- Pathname.glob("#{absolute_path}/**/*#{pattern.ext}")
21
- else
22
- Pathname.glob(absolute_path)
23
- end
24
-
25
- files.sort.each do |source_path|
26
- if source_path.file?
27
- relative_path = source_path.relative_path_from(base_dir)
28
- unless pattern.ignore?(relative_path)
29
- yield relative_path
11
+ if block_given?
12
+ pats = commandline_patterns.empty? ? pattern.patterns : commandline_patterns
13
+
14
+ pats.each do |path|
15
+ absolute_path = base_dir + path
16
+
17
+ if absolute_path.file?
18
+ if pattern =~ path
19
+ yield absolute_path.relative_path_from(base_dir)
20
+ end
21
+ else
22
+ files = if absolute_path.directory?
23
+ Pathname.glob("#{absolute_path}/**/*#{pattern.ext}")
24
+ else
25
+ Pathname.glob(absolute_path)
26
+ end
27
+
28
+ files.sort.each do |source_path|
29
+ if source_path.file?
30
+ relative_path = source_path.relative_path_from(base_dir)
31
+ unless pattern.ignore?(relative_path)
32
+ yield relative_path
33
+ end
30
34
  end
31
35
  end
32
36
  end
37
+
33
38
  end
39
+ else
40
+ enum_for :each_path_in_patterns, pattern, commandline_patterns
34
41
  end
35
42
  end
36
43
 
@@ -125,6 +125,7 @@ module Steep
125
125
  end
126
126
  when :send
127
127
  if test_ast_location(node.location.selector, line: line, column: column)
128
+ node = parents[0] if parents[0]&.type == :block
128
129
  case call = typing.call_of(node: node)
129
130
  when TypeInference::MethodCall::Typed, TypeInference::MethodCall::Error
130
131
  call.method_decls.each do |decl|