spoom 1.2.3 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e7ef8ebef70bc8827cdcb4b2c0e49ed6c53686e0db409908354f3762ad07c10
4
- data.tar.gz: a6adb405f0379cbe040e743f177e758e2bdaadcdea13ee9c7f75d84f764434b1
3
+ metadata.gz: 23df97e815cc01b35dbc960ec1921bbc1c65ae4c6f0588c690a0d9156877a013
4
+ data.tar.gz: d82fdb3fb66404b0c2d98ea9206be9d86ec2284bbae2ff24a5653a521ec9155e
5
5
  SHA512:
6
- metadata.gz: 86d74bc7d9554d67c1421908116bbab7615d77e9cb096d461f7885429d495f2b02f19d6024117f7083423cfe7c48165b18918c143f29574896bfbb76a4fb651f
7
- data.tar.gz: 754d41e8f26425a42288388879fe1658214adea03002421495297486343a827d6dc79a395e895553ed15f67cdc766a16f4a174aab2a3671bd2455e305a8c6f13
6
+ metadata.gz: 67b6209ba61fbc285fc89dcf64adcde27cfecf44bb37d644cfb8aef50b7fca6e53a15e8eec5bc45030e4c838a35d3777c50a2d75fb32461792a1bc5957d7b2e3
7
+ data.tar.gz: c3e06e5140aac671ff8489243dee9feba3912ec12681332b2abb37ddd5c367246dc180ea80f6207afe28f81f499626f4dcf4b9628baa263854600d19d593f2f5
data/README.md CHANGED
@@ -348,6 +348,16 @@ Find all the symbols for a file:
348
348
  puts client.document_symbols("file://path/to/my/file.rb")
349
349
  ```
350
350
 
351
+ ### Backtrace Filtering
352
+
353
+ Spoom provides a backtrace filter for Minitest to remove the Sorbet frames from test failures, giving a more readable output. To enable it:
354
+
355
+ ```ruby
356
+ # test/test_helper.rb
357
+ require "spoom/backtrace_filter/minitest"
358
+ Minitest.backtrace_filter = Spoom::BacktraceFilter::Minitest.new
359
+ ```
360
+
351
361
  ## Development
352
362
 
353
363
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. Don't forget to run `bin/sanity` before pushing your changes.
@@ -0,0 +1,21 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "minitest"
5
+
6
+ module Spoom
7
+ module BacktraceFilter
8
+ class Minitest < ::Minitest::BacktraceFilter
9
+ extend T::Sig
10
+
11
+ SORBET_PATHS = T.let(Gem.loaded_specs["sorbet-runtime"].full_require_paths.freeze, T::Array[String])
12
+
13
+ sig { override.params(bt: T.nilable(T::Array[String])).returns(T::Array[String]) }
14
+ def filter(bt)
15
+ super.select do |line|
16
+ SORBET_PATHS.none? { |path| line.include?(path) }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -44,7 +44,7 @@ module Spoom
44
44
 
45
45
  private
46
46
 
47
- sig { params(text: T.untyped).void }
47
+ sig { override.params(text: T.untyped).void }
48
48
  def add_text(text)
49
49
  return if text.empty?
50
50
 
@@ -62,7 +62,7 @@ module Spoom
62
62
 
63
63
  BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
64
64
 
65
- sig { params(indicator: T.untyped, code: T.untyped).void }
65
+ sig { override.params(indicator: T.untyped, code: T.untyped).void }
66
66
  def add_expression(indicator, code)
67
67
  flush_newline_if_pending(src)
68
68
 
@@ -79,13 +79,13 @@ module Spoom
79
79
  end
80
80
  end
81
81
 
82
- sig { params(code: T.untyped).void }
82
+ sig { override.params(code: T.untyped).void }
83
83
  def add_code(code)
84
84
  flush_newline_if_pending(src)
85
85
  super
86
86
  end
87
87
 
88
- sig { params(_: T.untyped).void }
88
+ sig { override.params(_: T.untyped).void }
89
89
  def add_postamble(_)
90
90
  flush_newline_if_pending(src)
91
91
  super
@@ -157,10 +157,10 @@ module Spoom
157
157
 
158
158
  sig { override.params(node: SyntaxTree::DefNode).void }
159
159
  def visit_def(node)
160
- super
161
-
162
160
  name = node_string(node.name)
163
161
  define_method(name, [*@names_nesting, name].join("::"), node)
162
+
163
+ super
164
164
  end
165
165
 
166
166
  sig { override.params(node: SyntaxTree::Field).void }
@@ -230,7 +230,7 @@ module Spoom
230
230
  end
231
231
  else
232
232
  @plugins.each do |plugin|
233
- plugin.on_send(self, send)
233
+ plugin.internal_on_send(self, send)
234
234
  end
235
235
 
236
236
  reference_method(send.name, send.node)
@@ -286,7 +286,7 @@ module Spoom
286
286
  location: node_location(node),
287
287
  )
288
288
  @index.define(definition)
289
- @plugins.each { |plugin| plugin.on_define_accessor(self, definition) }
289
+ @plugins.each { |plugin| plugin.internal_on_define_accessor(self, definition) }
290
290
  end
291
291
 
292
292
  sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
@@ -298,7 +298,7 @@ module Spoom
298
298
  location: node_location(node),
299
299
  )
300
300
  @index.define(definition)
301
- @plugins.each { |plugin| plugin.on_define_accessor(self, definition) }
301
+ @plugins.each { |plugin| plugin.internal_on_define_accessor(self, definition) }
302
302
  end
303
303
 
304
304
  sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
@@ -310,7 +310,7 @@ module Spoom
310
310
  location: node_location(node),
311
311
  )
312
312
  @index.define(definition)
313
- @plugins.each { |plugin| plugin.on_define_class(self, definition) }
313
+ @plugins.each { |plugin| plugin.internal_on_define_class(self, definition) }
314
314
  end
315
315
 
316
316
  sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
@@ -322,7 +322,7 @@ module Spoom
322
322
  location: node_location(node),
323
323
  )
324
324
  @index.define(definition)
325
- @plugins.each { |plugin| plugin.on_define_constant(self, definition) }
325
+ @plugins.each { |plugin| plugin.internal_on_define_constant(self, definition) }
326
326
  end
327
327
 
328
328
  sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
@@ -334,7 +334,7 @@ module Spoom
334
334
  location: node_location(node),
335
335
  )
336
336
  @index.define(definition)
337
- @plugins.each { |plugin| plugin.on_define_method(self, definition) }
337
+ @plugins.each { |plugin| plugin.internal_on_define_method(self, definition) }
338
338
  end
339
339
 
340
340
  sig { params(name: String, full_name: String, node: SyntaxTree::Node).void }
@@ -346,7 +346,7 @@ module Spoom
346
346
  location: node_location(node),
347
347
  )
348
348
  @index.define(definition)
349
- @plugins.each { |plugin| plugin.on_define_module(self, definition) }
349
+ @plugins.each { |plugin| plugin.internal_on_define_module(self, definition) }
350
350
  end
351
351
 
352
352
  # Reference indexing
@@ -361,6 +361,74 @@ module Spoom
361
361
  @index.reference(Reference.new(name: name, kind: Reference::Kind::Method, location: node_location(node)))
362
362
  end
363
363
 
364
+ # Context
365
+
366
+ sig { returns(SyntaxTree::Node) }
367
+ def current_node
368
+ T.must(@nodes_nesting.last)
369
+ end
370
+
371
+ sig { type_parameters(:N).params(type: T::Class[T.type_parameter(:N)]).returns(T.nilable(T.type_parameter(:N))) }
372
+ def nesting_node(type)
373
+ @nodes_nesting.reverse_each do |node|
374
+ return T.unsafe(node) if node.is_a?(type)
375
+ end
376
+
377
+ nil
378
+ end
379
+
380
+ sig { returns(T.nilable(SyntaxTree::ClassDeclaration)) }
381
+ def nesting_class
382
+ nesting_node(SyntaxTree::ClassDeclaration)
383
+ end
384
+
385
+ sig { returns(T.nilable(SyntaxTree::BlockNode)) }
386
+ def nesting_block
387
+ nesting_node(SyntaxTree::BlockNode)
388
+ end
389
+
390
+ sig { returns(T.nilable(SyntaxTree::MethodAddBlock)) }
391
+ def nesting_block_call
392
+ nesting_node(SyntaxTree::MethodAddBlock)
393
+ end
394
+
395
+ sig { returns(T.nilable(String)) }
396
+ def nesting_block_call_name
397
+ block = nesting_block_call
398
+ return unless block.is_a?(SyntaxTree::MethodAddBlock)
399
+
400
+ call = block.call
401
+ case call
402
+ when SyntaxTree::ARef
403
+ node_string(call.collection)
404
+ when SyntaxTree::CallNode, SyntaxTree::Command, SyntaxTree::CommandCall
405
+ node_string(call.message)
406
+ end
407
+ end
408
+
409
+ sig { returns(T.nilable(String)) }
410
+ def nesting_class_name
411
+ nesting_class = self.nesting_class
412
+ return unless nesting_class
413
+
414
+ node_string(nesting_class.constant)
415
+ end
416
+
417
+ sig { returns(T.nilable(String)) }
418
+ def nesting_class_superclass_name
419
+ nesting_class_superclass = nesting_class&.superclass
420
+ return unless nesting_class_superclass
421
+
422
+ node_string(nesting_class_superclass).delete_prefix("::")
423
+ end
424
+
425
+ sig { returns(T.nilable(String)) }
426
+ def last_sig
427
+ return unless @previous_node.is_a?(SyntaxTree::MethodAddBlock)
428
+
429
+ node_string(@previous_node)
430
+ end
431
+
364
432
  # Node utils
365
433
 
366
434
  sig { params(node: T.any(Symbol, SyntaxTree::Node)).returns(String) }
@@ -13,6 +13,23 @@ module Spoom
13
13
  class << self
14
14
  extend T::Sig
15
15
 
16
+ sig { params(location_string: String).returns(Location) }
17
+ def from_string(location_string)
18
+ file, rest = location_string.split(":", 2)
19
+ raise LocationError, "Invalid location string: #{location_string}" unless file && rest
20
+
21
+ start_line, rest = rest.split(":", 2)
22
+ raise LocationError, "Invalid location string: #{location_string}" unless start_line && rest
23
+
24
+ start_column, rest = rest.split("-", 2)
25
+ raise LocationError, "Invalid location string: #{location_string}" unless start_column && rest
26
+
27
+ end_line, end_column = rest.split(":", 2)
28
+ raise LocationError, "Invalid location string: #{location_string}" unless end_line && end_column
29
+
30
+ new(file, start_line.to_i, start_column.to_i, end_line.to_i, end_column.to_i)
31
+ end
32
+
16
33
  sig { params(file: String, location: SyntaxTree::Location).returns(Location) }
17
34
  def from_syntax_tree(file, location)
18
35
  new(file, location.start_line, location.start_column, location.end_line, location.end_column)
@@ -42,6 +59,17 @@ module Spoom
42
59
  @end_column = end_column
43
60
  end
44
61
 
62
+ sig { params(other: Location).returns(T::Boolean) }
63
+ def include?(other)
64
+ return false unless @file == other.file
65
+ return false if @start_line > other.start_line
66
+ return false if @start_line == other.start_line && @start_column > other.start_column
67
+ return false if @end_line < other.end_line
68
+ return false if @end_line == other.end_line && @end_column < other.end_column
69
+
70
+ true
71
+ end
72
+
45
73
  sig { override.params(other: BasicObject).returns(T.nilable(Integer)) }
46
74
  def <=>(other)
47
75
  return unless Location === other
@@ -0,0 +1,21 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class ActionMailer < Base
8
+ extend T::Sig
9
+
10
+ sig { override.params(indexer: Indexer, send: Send).void }
11
+ def on_send(indexer, send)
12
+ return unless send.recv.nil? && ActionPack::CALLBACKS.include?(send.name)
13
+
14
+ send.each_arg(SyntaxTree::SymbolLiteral) do |arg|
15
+ indexer.reference_method(indexer.node_string(arg.value), send.node)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,61 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class ActionPack < Base
8
+ extend T::Sig
9
+
10
+ CALLBACKS = T.let(
11
+ [
12
+ "after_action",
13
+ "append_after_action",
14
+ "append_around_action",
15
+ "append_before_action",
16
+ "around_action",
17
+ "before_action",
18
+ "prepend_after_action",
19
+ "prepend_around_action",
20
+ "prepend_before_action",
21
+ "skip_after_action",
22
+ "skip_around_action",
23
+ "skip_before_action",
24
+ ].freeze,
25
+ T::Array[String],
26
+ )
27
+
28
+ ignore_classes_named(/Controller$/)
29
+
30
+ sig { override.params(indexer: Indexer, definition: Definition).void }
31
+ def on_define_method(indexer, definition)
32
+ definition.ignored! if ignored_class_name?(indexer.nesting_class_name)
33
+ end
34
+
35
+ sig { override.params(indexer: Indexer, send: Send).void }
36
+ def on_send(indexer, send)
37
+ return unless send.recv.nil? && CALLBACKS.include?(send.name)
38
+
39
+ arg = send.args.first
40
+ case arg
41
+ when SyntaxTree::SymbolLiteral
42
+ indexer.reference_method(indexer.node_string(arg.value), send.node)
43
+ when SyntaxTree::VarRef
44
+ indexer.reference_constant(indexer.node_string(arg), send.node)
45
+ end
46
+
47
+ send.each_arg_assoc do |key, value|
48
+ key = indexer.node_string(key).delete_suffix(":")
49
+
50
+ case key
51
+ when "if", "unless"
52
+ indexer.reference_method(indexer.symbol_string(value), send.node) if value
53
+ else
54
+ indexer.reference_constant(camelize(key), send.node)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,13 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class ActiveJob < Base
8
+ ignore_classes_named("ApplicationJob")
9
+ ignore_methods_named("perform", "build_enumerator", "each_iteration")
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,46 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class ActiveModel < Base
8
+ extend T::Sig
9
+
10
+ ignore_classes_inheriting_from(/^(::)?ActiveModel::EachValidator$/)
11
+ ignore_methods_named("validate_each")
12
+
13
+ sig { override.params(indexer: Indexer, send: Send).void }
14
+ def on_send(indexer, send)
15
+ return if send.recv
16
+
17
+ case send.name
18
+ when "attribute", "attributes"
19
+ send.each_arg(SyntaxTree::SymbolLiteral) do |arg|
20
+ indexer.reference_method(indexer.node_string(arg.value), send.node)
21
+ end
22
+ when "validate", "validates", "validates!", "validates_each"
23
+ send.each_arg(SyntaxTree::SymbolLiteral) do |arg|
24
+ indexer.reference_method(indexer.node_string(arg.value), send.node)
25
+ end
26
+ send.each_arg_assoc do |key, value|
27
+ key = indexer.node_string(key).delete_suffix(":")
28
+
29
+ case key
30
+ when "if", "unless"
31
+ indexer.reference_method(indexer.symbol_string(value), send.node) if value
32
+ else
33
+ indexer.reference_constant(camelize(key), send.node)
34
+ end
35
+ end
36
+ when "validates_with"
37
+ arg = send.args.first
38
+ if arg.is_a?(SyntaxTree::SymbolLiteral)
39
+ indexer.reference_constant(indexer.node_string(arg.value), send.node)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,111 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class ActiveRecord < Base
8
+ extend T::Sig
9
+
10
+ ignore_classes_inheriting_from(/^(::)?ActiveRecord::Migration/)
11
+
12
+ ignore_methods_named(
13
+ "change",
14
+ "down",
15
+ "up",
16
+ "table_name_prefix",
17
+ "to_param",
18
+ )
19
+
20
+ CALLBACKS = T.let(
21
+ [
22
+ "after_commit",
23
+ "after_create_commit",
24
+ "after_create",
25
+ "after_destroy_commit",
26
+ "after_destroy",
27
+ "after_find",
28
+ "after_initialize",
29
+ "after_rollback",
30
+ "after_save_commit",
31
+ "after_save",
32
+ "after_touch",
33
+ "after_update_commit",
34
+ "after_update",
35
+ "after_validation",
36
+ "around_create",
37
+ "around_destroy",
38
+ "around_save",
39
+ "around_update",
40
+ "before_create",
41
+ "before_destroy",
42
+ "before_save",
43
+ "before_update",
44
+ "before_validation",
45
+ ].freeze,
46
+ T::Array[String],
47
+ )
48
+
49
+ CRUD_METHODS = T.let(
50
+ [
51
+ "assign_attributes",
52
+ "create",
53
+ "create!",
54
+ "insert",
55
+ "insert!",
56
+ "new",
57
+ "update",
58
+ "update!",
59
+ "upsert",
60
+ ].freeze,
61
+ T::Array[String],
62
+ )
63
+
64
+ ARRAY_METHODS = T.let(
65
+ [
66
+ "insert_all",
67
+ "insert_all!",
68
+ "upsert_all",
69
+ ].freeze,
70
+ T::Array[String],
71
+ )
72
+
73
+ sig { override.params(indexer: Indexer, send: Send).void }
74
+ def on_send(indexer, send)
75
+ if send.recv.nil? && CALLBACKS.include?(send.name)
76
+ send.each_arg(SyntaxTree::SymbolLiteral) do |arg|
77
+ indexer.reference_method(indexer.node_string(arg.value), send.node)
78
+ end
79
+ return
80
+ end
81
+
82
+ return unless send.recv
83
+
84
+ case send.name
85
+ when *CRUD_METHODS
86
+ send.each_arg_assoc do |key, _value|
87
+ key = indexer.symbol_string(key).delete_suffix(":")
88
+ indexer.reference_method("#{key}=", send.node)
89
+ end
90
+ when *ARRAY_METHODS
91
+ send.each_arg(SyntaxTree::ArrayLiteral) do |arg|
92
+ args = arg.contents
93
+ next unless args.is_a?(SyntaxTree::Args)
94
+
95
+ args.parts.each do |part|
96
+ next unless part.is_a?(SyntaxTree::HashLiteral)
97
+
98
+ part.assocs.each do |assoc|
99
+ next unless assoc.is_a?(SyntaxTree::Assoc)
100
+
101
+ key = indexer.symbol_string(assoc.key).delete_suffix(":")
102
+ indexer.reference_method("#{key}=", send.node)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,21 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Spoom
5
+ module Deadcode
6
+ module Plugins
7
+ class ActiveSupport < Base
8
+ ignore_classes_inheriting_from(/^(::)?ActiveSupport::TestCase$/)
9
+
10
+ ignore_methods_named(
11
+ "after_all",
12
+ "after_setup",
13
+ "after_teardown",
14
+ "before_all",
15
+ "before_setup",
16
+ "before_teardown",
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end