diff --git a/lib/rubocop/cop/type_toolkit/dont_expect_unexpected_nil.rb b/lib/rubocop/cop/type_toolkit/dont_expect_unexpected_nil.rb index 23b57f3..079cbc3 100644 --- a/lib/rubocop/cop/type_toolkit/dont_expect_unexpected_nil.rb +++ b/lib/rubocop/cop/type_toolkit/dont_expect_unexpected_nil.rb @@ -1,6 +1,8 @@ # typed: true # frozen_string_literal: true +require "type_toolkit/ext/nil_assertions" + module RuboCop module Cop module TypeToolkit @@ -8,6 +10,11 @@ module TypeToolkit class DontExpectUnexpectedNil < Base RESTRICT_ON_SEND = [:assert_raises, :raise].freeze + UNEXPECTED_NIL_ERROR_NAME = ::TypeToolkit::UnexpectedNilError.name.not_nil! + .split("::").last.not_nil! + .to_sym #: Symbol + private_constant :UNEXPECTED_NIL_ERROR_NAME + #: (RuboCop::AST::SendNode) -> void def on_send(node) case node.method_name @@ -55,7 +62,12 @@ def on_investigation_end #: (RuboCop::AST::ConstNode) -> bool def unexpected_nil_error?(node) - node.short_name == :UnexpectedNilError && (node.namespace.nil? || node.namespace.cbase_type?) + return false unless node.short_name == UNEXPECTED_NIL_ERROR_NAME + + ns = node.namespace + return true if ns.nil? || ns.cbase_type? + + ns.const_type? && ns.short_name == :TypeToolkit && (ns.namespace.nil? || ns.namespace.cbase_type?) end # Check for `raise UnexpectedNilError` diff --git a/lib/type_toolkit/ext/nil_assertions.rb b/lib/type_toolkit/ext/nil_assertions.rb index 184ecf1..b36900d 100644 --- a/lib/type_toolkit/ext/nil_assertions.rb +++ b/lib/type_toolkit/ext/nil_assertions.rb @@ -18,17 +18,19 @@ class NilClass # @override #: -> bot def not_nil! - raise UnexpectedNilError + raise TypeToolkit::UnexpectedNilError end end -# An error raised when calling `#not_nil!` on a `nil` value. -# -# `UnexpectedNilError` should never occur in well-formed code, so it should never be rescued. -# This is why it inherits from `Exception` instead of `StandardError`, -# so that bare rescues clauses (like `rescue => e`) don't rescue it. -class UnexpectedNilError < Exception # rubocop:disable Lint/InheritException - def initialize(message = "Called `not_nil!` on nil.") - super(message) +module TypeToolkit + # An error raised when calling `#not_nil!` on a `nil` value. + # + # `UnexpectedNilError` should never occur in well-formed code, so it should never be rescued. + # This is why it inherits from `Exception` instead of `StandardError`, + # so that bare rescues clauses (like `rescue => e`) don't rescue it. + class UnexpectedNilError < Exception # rubocop:disable Lint/InheritException + def initialize(message = "Called `not_nil!` on nil.") + super + end end end diff --git a/sorbet/config b/sorbet/config index fc85648..718871c 100644 --- a/sorbet/config +++ b/sorbet/config @@ -4,3 +4,4 @@ --ignore=vendor/ --enable-experimental-rbs-comments --suppress-payload-superclass-redefinition-for=RDoc::Markup::Heading +--disable-watchman diff --git a/sorbet/rbi/shims/minitest.rbi b/sorbet/rbi/shims/minitest.rbi index 0ab274d..ccc6a13 100644 --- a/sorbet/rbi/shims/minitest.rbi +++ b/sorbet/rbi/shims/minitest.rbi @@ -2,5 +2,42 @@ # frozen_string_literal: true module Minitest - class Spec < Minitest::Test; end + class Spec < Minitest::Test + extend Minitest::Spec::DSL + + include RuboCop::Minitest::AssertOffense + + sig do + params( + desc: T.anything, + block: T.proc.bind(T.self_type).void, + ).void + end + def it(desc = T.unsafe(nil), &block); end + + module DSL + has_attached_class!(:out) + + sig do + params( + desc: T.anything, + block: T.proc.bind(T.attached_class).void, + ).void + end + def describe(desc, &block); end + + sig do + params( + desc: T.anything, + block: T.proc.bind(T.attached_class).void, + ).void + end + def it(desc = T.unsafe(nil), &block); end + + sig do + params(block: T.proc.bind(T.attached_class).void).void + end + def before(&block); end + end + end end diff --git a/spec/rubocop/cop/type_toolkit/dont_expect_unexpected_nil_spec.rb b/spec/rubocop/cop/type_toolkit/dont_expect_unexpected_nil_spec.rb index 4a2e8ff..1f4c12c 100644 --- a/spec/rubocop/cop/type_toolkit/dont_expect_unexpected_nil_spec.rb +++ b/spec/rubocop/cop/type_toolkit/dont_expect_unexpected_nil_spec.rb @@ -9,66 +9,53 @@ module RuboCop module Cop module TypeToolkit - describe DontExpectUnexpectedNil do + class DontExpectUnexpectedNilSpec < ::Minitest::Spec + CONSTANT_NAMES = [ + "UnexpectedNilError", + "::UnexpectedNilError", + "TypeToolkit::UnexpectedNilError", + "::TypeToolkit::UnexpectedNilError", + ].freeze + include RuboCop::Minitest::AssertOffense before do @cop = DontExpectUnexpectedNil.new end - describe "assert_raises with UnexpectedNilError" do - it "adds offense when assert_raises is used with UnexpectedNilError" do - assert_offense(<<~RUBY) - assert_raises(UnexpectedNilError) { foo } - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{assert_raises_message} - RUBY - end + describe "assert_raises" do + CONSTANT_NAMES.each do |constant_name| + arrows_______ = "^" * constant_name.size - it "adds offense when assert_raises is used with ::UnexpectedNilError" do - assert_offense(<<~RUBY) - assert_raises(::UnexpectedNilError) { foo } - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{assert_raises_message} - RUBY - end - - it "adds offense when assert_raises is used with UnexpectedNilError with a do ... end block" do - assert_offense(<<~RUBY) - assert_raises(UnexpectedNilError) do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{assert_raises_message} - foo + describe constant_name do + it "adds offense with a { } block" do + assert_offense(<<~RUBY) + assert_raises(#{constant_name}) { foo } + ^^^^^^^^^^^^^^#{arrows_______}^ #{assert_raises_message} + RUBY end - RUBY - end - it "adds offense when assert_raises is used with ::UnexpectedNilError with a do ... end block" do - assert_offense(<<~RUBY) - assert_raises(::UnexpectedNilError) do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{assert_raises_message} - foo + it "adds offense with a do ... end block" do + assert_offense(<<~RUBY) + assert_raises(#{constant_name}) do + ^^^^^^^^^^^^^^#{arrows_______}^ #{assert_raises_message} + foo + end + RUBY end - RUBY - end - - it "adds offense when assert_raises is passed UnexpectedNilError among other arguments" do - assert_offense(<<~RUBY) - assert_raises(ArgumentError, UnexpectedNilError) { foo } - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{assert_raises_message} - RUBY - - assert_offense(<<~RUBY) - assert_raises(UnexpectedNilError, ArgumentError) { foo } - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{assert_raises_message} - RUBY - assert_offense(<<~RUBY) - assert_raises(::UnexpectedNilError, ArgumentError) { foo } - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{assert_raises_message} - RUBY + it "adds offense when passed among other arguments" do + assert_offense(<<~RUBY) + assert_raises(ArgumentError, #{constant_name}) { foo } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#{arrows_______}^ #{assert_raises_message} + RUBY - assert_offense(<<~RUBY) - assert_raises(ArgumentError, ::UnexpectedNilError) { foo } - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{assert_raises_message} - RUBY + assert_offense(<<~RUBY) + assert_raises(#{constant_name}, ArgumentError) { foo } + ^^^^^^^^^^^^^^#{arrows_______}^^^^^^^^^^^^^^^^ #{assert_raises_message} + RUBY + end + end end it "does not add offense when assert_raises is used with a different error" do @@ -78,49 +65,33 @@ module TypeToolkit end end - describe "rescuing UnexpectedNilError" do - it "adds offense when rescuing UnexpectedNilError" do - assert_offense(<<~RUBY) - begin - foo - rescue UnexpectedNilError - ^^^^^^^^^^^^^^^^^^ #{rescue_message} - bar - end - RUBY - end - - it "adds offense when rescuing ::UnexpectedNilError" do - assert_offense(<<~RUBY) - begin - foo - rescue ::UnexpectedNilError - ^^^^^^^^^^^^^^^^^^^^ #{rescue_message} - bar - end - RUBY - end - - it "adds offense when rescuing UnexpectedNilError among other exceptions" do - assert_offense(<<~RUBY) - begin - foo - rescue UnexpectedNilError, ArgumentError - ^^^^^^^^^^^^^^^^^^ #{rescue_message} - bar + describe "rescue" do + CONSTANT_NAMES.each do |constant_name| + arrows_______ = "^" * constant_name.size + + describe constant_name do + it "adds offense" do + assert_offense(<<~RUBY) + begin + foo + rescue #{constant_name} + #{arrows_______} #{rescue_message} + bar + end + RUBY end - RUBY - end - it "adds offense when rescuing ::UnexpectedNilError among other exceptions" do - assert_offense(<<~RUBY) - begin - foo - rescue ::UnexpectedNilError, ArgumentError - ^^^^^^^^^^^^^^^^^^^^ #{rescue_message} - bar + it "adds offense when among other exceptions" do + assert_offense(<<~RUBY) + begin + foo + rescue #{constant_name}, ArgumentError + #{arrows_______} #{rescue_message} + bar + end + RUBY end - RUBY + end end it "does not add offense when rescuing other exceptions" do @@ -134,61 +105,39 @@ module TypeToolkit end end - describe "raising UnexpectedNilError" do - it "adds offense when raising UnexpectedNilError" do - assert_offense(<<~RUBY) - raise UnexpectedNilError - ^^^^^^^^^^^^^^^^^^^^^^^^ #{raise_message} - RUBY - end - - it "adds offense when raising UnexpectedNilError with a message" do - assert_offense(<<~RUBY) - raise UnexpectedNilError, "message" - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{raise_message} - RUBY - end - - it "adds offense when raising ::UnexpectedNilError" do - assert_offense(<<~RUBY) - raise ::UnexpectedNilError - ^^^^^^^^^^^^^^^^^^^^^^^^^^ #{raise_message} - RUBY - end - - it "adds offense when raising ::UnexpectedNilError with a message" do - assert_offense(<<~RUBY) - raise ::UnexpectedNilError, "message" - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{raise_message} - RUBY - end + describe "raise" do + CONSTANT_NAMES.each do |constant_name| + arrows_______ = "^" * constant_name.size - it "adds offense when raising UnexpectedNilError.new" do - assert_offense(<<~RUBY) - raise UnexpectedNilError.new - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{raise_message} - RUBY - end + describe constant_name do + it "adds offense" do + assert_offense(<<~RUBY) + raise #{constant_name} + ^^^^^^#{arrows_______} #{raise_message} + RUBY + end - it "adds offense when raising UnexpectedNilError.new with a message" do - assert_offense(<<~RUBY) - raise UnexpectedNilError.new, "message" - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{raise_message} - RUBY - end + it "adds offense with a message" do + assert_offense(<<~RUBY) + raise #{constant_name}, "message" + ^^^^^^#{arrows_______}^^^^^^^^^^^ #{raise_message} + RUBY + end - it "adds offense when raising ::UnexpectedNilError.new" do - assert_offense(<<~RUBY) - raise ::UnexpectedNilError.new - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{raise_message} - RUBY - end + it "adds offense with .new" do + assert_offense(<<~RUBY) + raise #{constant_name}.new + ^^^^^^#{arrows_______}^^^^ #{raise_message} + RUBY + end - it "adds offense when raising ::UnexpectedNilError.new with a message" do - assert_offense(<<~RUBY) - raise ::UnexpectedNilError.new, "message" - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ #{raise_message} - RUBY + it "adds offense with .new and a message" do + assert_offense(<<~RUBY) + raise #{constant_name}.new, "message" + ^^^^^^#{arrows_______}^^^^^^^^^^^^^^^ #{raise_message} + RUBY + end + end end it "does not add offense when raising other exceptions" do @@ -199,18 +148,15 @@ module TypeToolkit end describe "other usages of UnexpectedNilError" do - it "adds offense when using UnexpectedNilError" do - assert_offense(<<~RUBY) - x = UnexpectedNilError - ^^^^^^^^^^^^^^^^^^ #{general_usage_message} - RUBY - end - - it "adds offense when using ::UnexpectedNilError" do - assert_offense(<<~RUBY) - x = ::UnexpectedNilError - ^^^^^^^^^^^^^^^^^^^^ #{general_usage_message} - RUBY + CONSTANT_NAMES.each do |constant_name| + arrows_______ = "^" * constant_name.size + + it "adds offense when using #{constant_name}" do + assert_offense(<<~RUBY) + x = #{constant_name} + #{arrows_______} #{general_usage_message} + RUBY + end end it "does not add offense when using other constants" do