spoom 1.2.3 → 1.2.4

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