Class: RuboCop::Cop::Sorbet::VoidCheckedTests

Inherits:
Base
  • Object
show all
Extended by:
AutoCorrector
Includes:
RangeHelp, SignatureHelp
Defined in:
lib/rubocop/cop/sorbet/signatures/void_checked_tests.rb

Overview

Disallows the usage of ‘.void.checked(:tests)`.

Using ‘.void` changes the value returned from the method, but only if runtime type checking is enabled for the method. Methods marked `.void` will return different values in tests compared with non-test environments. This is particularly troublesome if branching on the result of a `.void` method, because the returned value in test code will be the truthy `VOID` value, while the non-test return value may be falsy depending on the method’s implementation.

  • Use ‘.returns(T.anything).checked(:tests)` to keep the runtime type checking for the rest of the parameters.

  • Use ‘.void.checked(:never)` if you are on an older version of Sorbet which does not have `T.anything` (meaning versions 0.5.10781 or earlier. Versions released after 2023-04-14 include `T.anything`.)

Examples:


# bad
sig { void.checked(:tests) }

# good
sig { void }
sig { returns(T.anything).checked(:tests) }
sig { void.checked(:never) }

Instance Method Summary collapse

Methods included from SignatureHelp

#on_block, #signature?, #with_runtime?, #without_runtime?

Instance Method Details

#checked_tests(node) ⇒ Object



37
38
39
# File 'lib/rubocop/cop/sorbet/signatures/void_checked_tests.rb', line 37

def_node_search(:checked_tests, <<~PATTERN)
  ({csend send} _ :checked (sym :tests))
PATTERN

#on_signature(node) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/rubocop/cop/sorbet/signatures/void_checked_tests.rb', line 58

def on_signature(node)
  checked_send = checked_tests(node).first
  return unless checked_send

  if (parent = node.parent) && (sibling_index = node.sibling_index)
    later_siblings = parent.children[(sibling_index + 1)..]
    if (def_node = later_siblings.find { |sibling| sibling.is_a?(RuboCop::AST::DefNode) })
      # Sorbet requires that `initialize` methods return `.void`
      # (A stylistic convention which happens to be enforced by Sorbet)
      return if def_node.method?(:initialize)
    end
  end

  void_send = top_level_void(node.body)

  return unless void_send

  add_offense(
    void_send.selector,
    message: MESSAGE,
  ) do |corrector|
    corrector.replace(void_send.selector, "returns(T.anything)")
  end
end