steep 0.49.1 → 0.52.0

Sign up to get free protection for your applications and to get access to all the features.
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)