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

#bare_sig?, #on_block, #sig_with_runtime?, #sig_without_runtime?, #signature?

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)
  (call _ :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