steep 0.49.1 → 0.52.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/Gemfile +4 -1
  4. data/Gemfile.lock +8 -5
  5. data/lib/steep/ast/annotation/collection.rb +10 -8
  6. data/lib/steep/ast/types/factory.rb +5 -5
  7. data/lib/steep/cli.rb +83 -1
  8. data/lib/steep/diagnostic/deprecated/unknown_constant_assigned.rb +28 -0
  9. data/lib/steep/diagnostic/ruby.rb +21 -15
  10. data/lib/steep/drivers/check.rb +1 -0
  11. data/lib/steep/drivers/langserver.rb +2 -2
  12. data/lib/steep/drivers/stats.rb +1 -0
  13. data/lib/steep/drivers/utils/jobs_count.rb +1 -1
  14. data/lib/steep/drivers/watch.rb +1 -1
  15. data/lib/steep/index/source_index.rb +18 -1
  16. data/lib/steep/interface/function.rb +5 -0
  17. data/lib/steep/module_helper.rb +4 -7
  18. data/lib/steep/project.rb +15 -1
  19. data/lib/steep/server/interaction_worker.rb +47 -4
  20. data/lib/steep/server/master.rb +10 -5
  21. data/lib/steep/server/type_check_worker.rb +19 -4
  22. data/lib/steep/server/worker_process.rb +15 -6
  23. data/lib/steep/services/completion_provider.rb +109 -1
  24. data/lib/steep/services/goto_service.rb +23 -10
  25. data/lib/steep/services/hover_content.rb +52 -4
  26. data/lib/steep/services/type_check_service.rb +6 -3
  27. data/lib/steep/source.rb +71 -18
  28. data/lib/steep/type_construction.rb +209 -162
  29. data/lib/steep/type_inference/constant_env.rb +33 -15
  30. data/lib/steep/type_inference/context.rb +6 -7
  31. data/lib/steep/type_inference/type_env.rb +16 -54
  32. data/lib/steep/version.rb +1 -1
  33. data/smoke/const/test_expectations.yml +24 -19
  34. data/smoke/diagnostics/test_expectations.yml +77 -17
  35. data/smoke/integer/test_expectations.yml +24 -4
  36. data/smoke/method/test_expectations.yml +30 -0
  37. data/smoke/regression/test_expectations.yml +24 -0
  38. data/smoke/yield/test_expectations.yml +20 -0
  39. data/steep.gemspec +1 -1
  40. metadata +5 -4
@@ -135,6 +135,21 @@ module Steep
135
135
  end
136
136
 
137
137
  def handle_job(job)
138
+ job_path = if job.respond_to?(:path)
139
+ if Gem.win_platform?
140
+ # FIXME: Sometimes drive letter is missing, using base_dir
141
+ if job.path.to_s.start_with?(%r{/[a-z](:|%3A)/}i)
142
+ job.path.to_s
143
+ else
144
+ "/#{project.base_dir.to_s.split("/").first}/#{job.path}"
145
+ end
146
+ else
147
+ job.path.to_s
148
+ end
149
+ else
150
+ nil
151
+ end
152
+
138
153
  case job
139
154
  when StartTypeCheckJob
140
155
  Steep.logger.info { "Processing StartTypeCheckJob for guid=#{job.guid}" }
@@ -149,7 +164,7 @@ module Steep
149
164
  writer.write(
150
165
  method: :"textDocument/publishDiagnostics",
151
166
  params: LSP::Interface::PublishDiagnosticsParams.new(
152
- uri: URI.parse(job.path.to_s).tap {|uri| uri.scheme = "file"},
167
+ uri: URI.parse(job_path).tap {|uri| uri.scheme = "file"},
153
168
  diagnostics: diagnostics.map {|diagnostic| formatter.format(diagnostic) }.uniq
154
169
  )
155
170
  )
@@ -167,7 +182,7 @@ module Steep
167
182
  writer.write(
168
183
  method: :"textDocument/publishDiagnostics",
169
184
  params: LSP::Interface::PublishDiagnosticsParams.new(
170
- uri: URI.parse(job.path.to_s).tap {|uri| uri.scheme = "file"},
185
+ uri: URI.parse(job_path).tap {|uri| uri.scheme = "file"},
171
186
  diagnostics: diagnostics.map {|diagnostic| formatter.format(diagnostic) }.uniq.compact
172
187
  )
173
188
  )
@@ -186,7 +201,7 @@ module Steep
186
201
  writer.write(
187
202
  method: :"textDocument/publishDiagnostics",
188
203
  params: LSP::Interface::PublishDiagnosticsParams.new(
189
- uri: URI.parse(job.path.to_s).tap {|uri| uri.scheme = "file"},
204
+ uri: URI.parse(job_path).tap {|uri| uri.scheme = "file"},
190
205
  diagnostics: diagnostics.map {|diagnostic| formatter.format(diagnostic) }.uniq.compact
191
206
  )
192
207
  )
@@ -268,7 +283,7 @@ module Steep
268
283
  line = job.params[:position][:line] + 1
269
284
  column = job.params[:position][:character]
270
285
 
271
- goto_service = Services::GotoService.new(type_check: service)
286
+ goto_service = Services::GotoService.new(type_check: service, assignment: assignment)
272
287
  locations =
273
288
  case
274
289
  when job.definition?
@@ -18,13 +18,17 @@ module Steep
18
18
  @index = index
19
19
  end
20
20
 
21
- def self.spawn_worker(type, name:, steepfile:, options: [], delay_shutdown: false, index: nil)
22
- log_level = %w(debug info warn error fatal unknown)[Steep.logger.level]
21
+ def self.spawn_worker(type, name:, steepfile:, steep_command: "steep", options: [], delay_shutdown: false, index: nil)
22
+ args = ["--name=#{name}", "--steepfile=#{steepfile}"]
23
+ args << (%w(debug info warn error fatal unknown)[Steep.logger.level].yield_self {|log_level| "--log-level=#{log_level}" })
24
+ if Steep.log_output.is_a?(String)
25
+ args << "--log-output=#{Steep.log_output}"
26
+ end
23
27
  command = case type
24
28
  when :interaction
25
- ["steep", "worker", "--interaction", "--name=#{name}", "--log-level=#{log_level}", "--steepfile=#{steepfile}", *options]
29
+ [steep_command, "worker", "--interaction", *args, *options]
26
30
  when :typecheck
27
- ["steep", "worker", "--typecheck", "--name=#{name}", "--log-level=#{log_level}", "--steepfile=#{steepfile}", *options]
31
+ [steep_command, "worker", "--typecheck", *args, *options]
28
32
  else
29
33
  raise "Unknown type: #{type}"
30
34
  end
@@ -33,7 +37,11 @@ module Steep
33
37
  command << "--delay-shutdown"
34
38
  end
35
39
 
36
- stdin, stdout, thread = Open3.popen2(*command, pgroup: true)
40
+ stdin, stdout, thread = if Gem.win_platform?
41
+ Open3.popen2(*command, new_pgroup: true)
42
+ else
43
+ Open3.popen2(*command, pgroup: true)
44
+ end
37
45
  stderr = nil
38
46
 
39
47
  writer = LanguageServer::Protocol::Transport::Io::Writer.new(stdin)
@@ -42,11 +50,12 @@ module Steep
42
50
  new(reader: reader, writer: writer, stderr: stderr, wait_thread: thread, name: name, index: index)
43
51
  end
44
52
 
45
- def self.spawn_typecheck_workers(steepfile:, args:, count: [Etc.nprocessors - 1, 1].max, delay_shutdown: false)
53
+ def self.spawn_typecheck_workers(steepfile:, args:, steep_command: "steep", count: [Etc.nprocessors - 1, 1].max, delay_shutdown: false)
46
54
  count.times.map do |i|
47
55
  spawn_worker(:typecheck,
48
56
  name: "typecheck@#{i}",
49
57
  steepfile: steepfile,
58
+ steep_command: steep_command,
50
59
  options: ["--max-index=#{count}", "--index=#{i}", *args],
51
60
  delay_shutdown: delay_shutdown,
52
61
  index: i)
@@ -10,6 +10,30 @@ module Steep
10
10
 
11
11
  InstanceVariableItem = Struct.new(:identifier, :range, :type, keyword_init: true)
12
12
  LocalVariableItem = Struct.new(:identifier, :range, :type, keyword_init: true)
13
+ ConstantItem = Struct.new(:env, :identifier, :range, :type, :full_name, keyword_init: true) do
14
+ def class?
15
+ if decl = env.class_decls[full_name]
16
+ decl.primary.decl.is_a?(RBS::AST::Declarations::Class)
17
+ end
18
+ end
19
+
20
+ def module?
21
+ if decl = env.class_decls[full_name]
22
+ decl.primary.decl.is_a?(RBS::AST::Declarations::Module)
23
+ end
24
+ end
25
+
26
+ def comments
27
+ case
28
+ when decl = env.class_decls[full_name]
29
+ decl.decls.filter_map {|d| d.decl.comment }
30
+ when comment = env.constant_decls[full_name]&.decl&.comment
31
+ [comment]
32
+ else
33
+ []
34
+ end
35
+ end
36
+ end
13
37
  MethodNameItem = Struct.new(:identifier, :range, :receiver_type, :method_type, :method_decls, keyword_init: true) do
14
38
  def comment
15
39
  case method_decls.size
@@ -61,6 +85,10 @@ module Steep
61
85
  end
62
86
  end
63
87
 
88
+ def env
89
+ subtyping.factory.env
90
+ end
91
+
64
92
  def run(line:, column:)
65
93
  source_text = self.source_text.dup
66
94
  index = index_for(source_text, line:line, column: column)
@@ -92,6 +120,15 @@ module Steep
92
120
  source_text[index-1] = " "
93
121
  type_check!(source_text, line: line, column: column)
94
122
  items_for_atmark(position: position)
123
+ when ":"
124
+ if source_text[index-2] == ":"
125
+ source_text[index-1] = " "
126
+ source_text[index-2] = " "
127
+ type_check!(source_text, line: line, column: column)
128
+ items_for_colon2(position: position)
129
+ else
130
+ []
131
+ end
95
132
  else
96
133
  []
97
134
  end
@@ -170,8 +207,25 @@ module Steep
170
207
  prefix: prefix,
171
208
  position: position,
172
209
  items: items)
210
+ constant_items_for_context(context, prefix: prefix, position: position, items: items)
173
211
 
174
- when node.type == :send && at_end?(position, of: node.loc.dot)
212
+ when node.type == :const && node.children[0] && at_end?(position, of: node.loc)
213
+ # Foo::Ba ← (const)
214
+ parent_node = node.children[0]
215
+ parent_type = typing.type_of(node: parent_node)
216
+
217
+ if parent_type
218
+ prefix = node.children[1].to_s
219
+
220
+ method_items_for_receiver_type(parent_type,
221
+ include_private: false,
222
+ prefix: prefix,
223
+ position: position,
224
+ items: items)
225
+ constant_items_for_context(context, parent: parent_node, prefix: prefix, position: position, items: items)
226
+ end
227
+
228
+ when node.type == :send && at_end?(position, of: node.loc.dot) && node.loc.dot.source == "."
175
229
  # foo.← ba
176
230
  receiver_type = case (type = typing.type_of(node: node.children[0]))
177
231
  when AST::Types::Self
@@ -186,6 +240,10 @@ module Steep
186
240
  position: position,
187
241
  items: items)
188
242
 
243
+ when node.type == :send && at_end?(position, of: node.loc.dot) && node.loc.dot.source == "::"
244
+ # foo::← ba
245
+ items.push(*items_for_colon2(position: position))
246
+
189
247
  when node.type == :ivar && at_end?(position, of: node.loc)
190
248
  # @fo ←
191
249
  instance_variable_items_for_context(context, position: position, prefix: node.children[0].to_s, items: items)
@@ -198,6 +256,7 @@ module Steep
198
256
  items: items)
199
257
  local_variable_items_for_context(context, position: position, prefix: "", items: items)
200
258
  instance_variable_items_for_context(context, position: position, prefix: "", items: items)
259
+ constant_items_for_context(context, position: position, prefix: "", items: items)
201
260
  end
202
261
 
203
262
  items
@@ -236,6 +295,31 @@ module Steep
236
295
  end
237
296
  end
238
297
 
298
+ def items_for_colon2(position:)
299
+ # :: ←
300
+ shift_pos = position-2
301
+ node, *_ = source.find_nodes(line: shift_pos.line, column: shift_pos.column)
302
+ node ||= source.node
303
+
304
+ items = []
305
+ case node&.type
306
+ when :const
307
+ # Constant:: ←
308
+ context = typing.context_at(line: position.line, column: position.column)
309
+ constant_items_for_context(context, parent: node, position: position, items: items, prefix: "")
310
+ when nil
311
+ # :: ←
312
+ context = typing.context_at(line: position.line, column: position.column)
313
+ constant_items_for_context(context, parent: nil, position: position, items: items, prefix: "")
314
+ end
315
+
316
+ if node
317
+ items.push(*items_for_dot(position: position - 1))
318
+ end
319
+
320
+ items
321
+ end
322
+
239
323
  def items_for_atmark(position:)
240
324
  # @ ←
241
325
  shift_pos = position-1
@@ -290,6 +374,30 @@ module Steep
290
374
  end
291
375
  end
292
376
 
377
+ def constant_items_for_context(context, parent: nil, position:, prefix:, items:)
378
+ range = range_for(position, prefix: prefix)
379
+
380
+ if parent
381
+ case parent.type
382
+ when :const
383
+ const_name = typing.source_index.reference(constant_node: parent)
384
+ consts = context.module_context.const_env.children(const_name)
385
+ end
386
+ else
387
+ consts = context.module_context.const_env.constants
388
+ end
389
+
390
+ if consts
391
+ consts.each do |name, tuple|
392
+ type, full_name, _ = tuple
393
+
394
+ if name.to_s.start_with?(prefix)
395
+ items << ConstantItem.new(env: env, identifier: name, range: range, type: type, full_name: full_name)
396
+ end
397
+ end
398
+ end
399
+ end
400
+
293
401
  def instance_variable_items_for_context(context, position:, prefix:, items:)
294
402
  range = range_for(position, prefix: prefix)
295
403
  context.type_env.ivar_types.map do |name, type|
@@ -21,10 +21,11 @@ module Steep
21
21
  end
22
22
  TypeNameQuery = Struct.new(:name, keyword_init: true)
23
23
 
24
- attr_reader :type_check
24
+ attr_reader :type_check, :assignment
25
25
 
26
- def initialize(type_check:)
26
+ def initialize(type_check:, assignment:)
27
27
  @type_check = type_check
28
+ @assignment = assignment
28
29
  end
29
30
 
30
31
  def project
@@ -78,7 +79,17 @@ module Steep
78
79
  end
79
80
  end
80
81
 
81
- locations.uniq
82
+ # Drop un-assigned paths here.
83
+ # The path assignment makes sense only for `.rbs` files, because un-assigned `.rb` files are already skipped since they are not type checked.
84
+ #
85
+ locations.uniq.select do |loc|
86
+ case loc
87
+ when RBS::Location
88
+ assignment =~ loc.name
89
+ else
90
+ true
91
+ end
92
+ end
82
93
  end
83
94
 
84
95
  def test_ast_location(loc, line:, column:)
@@ -100,14 +111,13 @@ module Steep
100
111
  typing, signature = type_check_path(target: target, path: relative_path, content: source.content, line: line, column: column)
101
112
  if typing
102
113
  node, *parents = typing.source.find_nodes(line: line, column: column)
114
+
103
115
  if node
104
116
  case node.type
105
117
  when :const, :casgn
106
118
  if test_ast_location(node.location.name, line: line, column: column)
107
- if module_context = typing.context_at(line: line, column: column).module_context
108
- const_env = module_context.const_env
109
- const = const_env.lookup_constant(module_name_from_node(node))
110
- queries << ConstantQuery.new(name: const.name, from: :ruby)
119
+ if name = typing.source_index.reference(constant_node: node)
120
+ queries << ConstantQuery.new(name: name, from: :ruby)
111
121
  end
112
122
  end
113
123
  when :def, :defs
@@ -125,7 +135,10 @@ module Steep
125
135
  end
126
136
  when :send
127
137
  if test_ast_location(node.location.selector, line: line, column: column)
128
- node = parents[0] if parents[0]&.type == :block
138
+ if (parent = parents[0]) && parent.type == :block && parent.children[0] == node
139
+ node = parents[0]
140
+ end
141
+
129
142
  case call = typing.call_of(node: node)
130
143
  when TypeInference::MethodCall::Typed, TypeInference::MethodCall::Error
131
144
  call.method_decls.each do |decl|
@@ -232,8 +245,8 @@ module Steep
232
245
  entry = typing.source_index.entry(constant: name)
233
246
  entry.definitions.each do |node|
234
247
  case node.type
235
- when :class, :module
236
- locations << node.children[0].location.expression
248
+ when :const
249
+ locations << node.location.expression
237
250
  when :casgn
238
251
  parent = node.children[0]
239
252
  location =
@@ -5,16 +5,42 @@ module Steep
5
5
  VariableContent = Struct.new(:node, :name, :type, :location, keyword_init: true)
6
6
  MethodCallContent = Struct.new(:node, :method_name, :type, :definition, :location, keyword_init: true)
7
7
  DefinitionContent = Struct.new(:node, :method_name, :method_type, :definition, :location, keyword_init: true) do
8
- TypeAliasContent = Struct.new(:location, :decl, keyword_init: true)
9
- ClassContent = Struct.new(:location, :decl, keyword_init: true)
10
- InterfaceContent = Struct.new(:location, :decl, keyword_init: true)
11
-
12
8
  def comment_string
13
9
  if comments = definition&.comments
14
10
  comments.map {|c| c.string.chomp }.uniq.join("\n----\n")
15
11
  end
16
12
  end
17
13
  end
14
+ ConstantContent = Struct.new(:location, :full_name, :type, :decl, keyword_init: true) do
15
+ def comment_string
16
+ if class_or_module?
17
+ comments = decl.decls.filter_map {|d| d.decl.comment&.string }
18
+ unless comments.empty?
19
+ return comments.join("\n----\n")
20
+ end
21
+ end
22
+
23
+ if constant?
24
+ if comment = decl.decl.comment
25
+ return comment.string
26
+ end
27
+ end
28
+
29
+ nil
30
+ end
31
+
32
+ def constant?
33
+ decl.is_a?(RBS::Environment::SingleEntry)
34
+ end
35
+
36
+ def class_or_module?
37
+ decl.is_a?(RBS::Environment::MultiEntry)
38
+ end
39
+ end
40
+
41
+ TypeAliasContent = Struct.new(:location, :decl, keyword_init: true)
42
+ ClassContent = Struct.new(:location, :decl, keyword_init: true)
43
+ InterfaceContent = Struct.new(:location, :decl, keyword_init: true)
18
44
 
19
45
  InstanceMethodName = Struct.new(:class_name, :method_name)
20
46
  SingletonMethodName = Struct.new(:class_name, :method_name)
@@ -190,6 +216,28 @@ module Steep
190
216
  location: node.loc.expression
191
217
  )
192
218
  end
219
+ when :const, :casgn
220
+ context = typing.context_at(line: line, column: column)
221
+
222
+ type = typing.type_of(node: node)
223
+ const_name = typing.source_index.reference(constant_node: node)
224
+
225
+ if const_name
226
+ decl = context.env.class_decls[const_name] || context.env.constant_decls[const_name]
227
+
228
+ ConstantContent.new(
229
+ location: node.location.name,
230
+ full_name: const_name,
231
+ type: type,
232
+ decl: decl
233
+ )
234
+ else
235
+ TypeContent.new(
236
+ node: node,
237
+ type: type,
238
+ location: node.location.expression
239
+ )
240
+ end
193
241
  else
194
242
  type = typing.type_of(node: node)
195
243
 
@@ -338,8 +338,12 @@ module Steep
338
338
  end
339
339
 
340
340
  def self.type_check(source:, subtyping:)
341
- annotations = source.annotations(block: source.node, factory: subtyping.factory, current_module: RBS::Namespace.root)
342
- const_env = TypeInference::ConstantEnv.new(factory: subtyping.factory, context: [RBS::Namespace.root])
341
+ annotations = source.annotations(block: source.node, factory: subtyping.factory, context: nil)
342
+ const_env = TypeInference::ConstantEnv.new(
343
+ factory: subtyping.factory,
344
+ context: nil,
345
+ resolver: RBS::Resolver::ConstantResolver.new(builder: subtyping.factory.definition_builder)
346
+ )
343
347
  type_env = TypeInference::TypeEnv.build(annotations: annotations,
344
348
  subtyping: subtyping,
345
349
  const_env: const_env,
@@ -357,7 +361,6 @@ module Steep
357
361
  instance_type: AST::Builtin::Object.instance_type,
358
362
  module_type: AST::Builtin::Object.module_type,
359
363
  implement_name: nil,
360
- current_namespace: RBS::Namespace.root,
361
364
  const_env: const_env,
362
365
  class_name: AST::Builtin::Object.module_name,
363
366
  instance_definition: subtyping.factory.definition_builder.build_instance(AST::Builtin::Object.module_name),
data/lib/steep/source.rb CHANGED
@@ -283,11 +283,11 @@ module Steep
283
283
  node.updated(nil, children)
284
284
  end
285
285
 
286
- def annotations(block:, factory:, current_module:)
286
+ def annotations(block:, factory:, context:)
287
287
  AST::Annotation::Collection.new(
288
288
  annotations: (mapping[block] || []).map(&:annotation),
289
289
  factory: factory,
290
- current_module: current_module
290
+ context: context
291
291
  )
292
292
  end
293
293
 
@@ -301,11 +301,42 @@ module Steep
301
301
  end
302
302
  end
303
303
 
304
- def find_nodes(line:, column:, node: self.node, position: nil, parents: [])
305
- position ||= (line-1).times.sum do |i|
306
- node.location.expression.source_buffer.source_line(i+1).size + 1
307
- end + column
304
+ def each_heredoc_node(node = self.node, parents = [], &block)
305
+ if block
306
+ case node.type
307
+ when :dstr, :str
308
+ if node.location.is_a?(Parser::Source::Map::Heredoc)
309
+ yield [node, *parents]
310
+ end
311
+ end
312
+
313
+ parents.unshift(node)
314
+ Source.each_child_node(node) do |child|
315
+ each_heredoc_node(child, parents, &block)
316
+ end
317
+ parents.shift()
318
+ else
319
+ enum_for :each_heredoc_node, node
320
+ end
321
+ end
308
322
 
323
+ def find_heredoc_nodes(line, column, position)
324
+ each_heredoc_node() do |nodes|
325
+ node = nodes[0]
326
+
327
+ range = node.location.heredoc_body&.yield_self do |r|
328
+ r.begin_pos..r.end_pos
329
+ end
330
+
331
+ if range && (range === position)
332
+ return nodes
333
+ end
334
+ end
335
+
336
+ nil
337
+ end
338
+
339
+ def find_nodes_loc(node, position, parents)
309
340
  range = node.location.expression&.yield_self do |r|
310
341
  r.begin_pos..r.end_pos
311
342
  end
@@ -315,7 +346,7 @@ module Steep
315
346
  parents.unshift node
316
347
 
317
348
  Source.each_child_node(node) do |child|
318
- ns = find_nodes(line: line, column: column, node: child, position: position, parents: parents) and return ns
349
+ ns = find_nodes_loc(child, position, parents) and return ns
319
350
  end
320
351
 
321
352
  parents
@@ -323,6 +354,24 @@ module Steep
323
354
  end
324
355
  end
325
356
 
357
+ def find_nodes(line:, column:)
358
+ return [] unless node
359
+
360
+ position = (line-1).times.sum do |i|
361
+ node.location.expression.source_buffer.source_line(i+1).size + 1
362
+ end + column
363
+
364
+ if nodes = find_heredoc_nodes(line, column, position)
365
+ Source.each_child_node(nodes[0]) do |child|
366
+ find_nodes_loc(child, position, nodes) and break
367
+ end
368
+
369
+ nodes
370
+ else
371
+ find_nodes_loc(node, position, [])
372
+ end
373
+ end
374
+
326
375
  def self.delete_defs(node, allow_list)
327
376
  case node.type
328
377
  when :def
@@ -345,22 +394,26 @@ module Steep
345
394
  end
346
395
 
347
396
  def without_unrelated_defs(line:, column:)
348
- nodes = find_nodes(line: line, column: column) || []
349
- defs = Set[].compare_by_identity.merge(nodes.select {|node| node.type == :def || node.type == :defs })
397
+ if node
398
+ nodes = find_nodes(line: line, column: column) || []
399
+ defs = Set[].compare_by_identity.merge(nodes.select {|node| node.type == :def || node.type == :defs })
350
400
 
351
- node_ = Source.delete_defs(node, defs)
401
+ node_ = Source.delete_defs(node, defs)
352
402
 
353
- mapping = {}.compare_by_identity
403
+ mapping = {}.compare_by_identity
354
404
 
355
- annotations = self.mapping.values.flatten
356
- Source.construct_mapping(node: node_, annotations: annotations, mapping: mapping)
405
+ annotations = self.mapping.values.flatten
406
+ Source.construct_mapping(node: node_, annotations: annotations, mapping: mapping)
357
407
 
358
- annotations.each do |annot|
359
- mapping[node] ||= []
360
- mapping[node] << annot
361
- end
408
+ annotations.each do |annot|
409
+ mapping[node] ||= []
410
+ mapping[node] << annot
411
+ end
362
412
 
363
- Source.new(path: path, node: node_, mapping: mapping)
413
+ Source.new(path: path, node: node_, mapping: mapping)
414
+ else
415
+ self
416
+ end
364
417
  end
365
418
 
366
419
  def compact_siblings(node)