From 324552cc8ff780b43a5eab2e697e0f4541213086 Mon Sep 17 00:00:00 2001 From: Dave Corson-Knowles Date: Wed, 18 Sep 2024 06:49:10 -0700 Subject: [PATCH] Add RSpec/StringAsInstanceDoubleConstant Addresses #1136 Adds a cop which can autocorrect from String declarations for instance_double to Class declarations. Symbol declarations are not affected. --- .rubocop.yml | 3 +- CHANGELOG.md | 2 + config/default.yml | 7 +++ docs/modules/ROOT/pages/cops.adoc | 1 + docs/modules/ROOT/pages/cops_rspec.adoc | 35 +++++++++++++++ .../string_as_instance_double_constant.rb | 45 +++++++++++++++++++ lib/rubocop/cop/rspec_cops.rb | 1 + ...string_as_instance_double_constant_spec.rb | 33 ++++++++++++++ 8 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 lib/rubocop/cop/rspec/string_as_instance_double_constant.rb create mode 100644 spec/rubocop/cop/rspec/string_as_instance_double_constant_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index e5b68d25d..6fb427aef 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -253,4 +253,5 @@ Style/YAMLFileRead: {Enabled: true} # Enable our own pending cops. # -# No pending cops yet. +RSpec/StringAsInstanceDoubleConstant: + Enabled: true diff --git a/CHANGELOG.md b/CHANGELOG.md index d1027dc93..bf2338aee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Master (Unreleased) +- Add `RSpec/StringAsInstanceDoubleConstant` to check for and correct strings used as instance_doubles. ([@corsonknowles]) - Fix false-positive for `RSpec/UnspecifiedException` when a method is literally named `raise_exception`. ([@aarestad]) - Fix false-positive for `RSpec/UnspecifiedException` when `not_to raise_error` is used within a block. ([@aarestad], [@G-Rath]) @@ -924,6 +925,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features. [@cfabianski]: https://github.com/cfabianski [@clupprich]: https://github.com/clupprich [@composerinteralia]: https://github.com/composerinteralia +[@corsonknowles]: https://github.com/corsonknowles [@corydiamand]: https://github.com/corydiamand [@darhazer]: https://github.com/Darhazer [@daveworth]: https://github.com/daveworth diff --git a/config/default.yml b/config/default.yml index 62135b81d..7e6e742b4 100644 --- a/config/default.yml +++ b/config/default.yml @@ -929,6 +929,13 @@ RSpec/SpecFilePathSuffix: - "**/spec/**/*" Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SpecFilePathSuffix +RSpec/StringAsInstanceDoubleConstant: + Description: Do not use a string as `instance_double` constant. + Enabled: pending + Safe: false + VersionAdded: "<>" + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/StringAsInstanceDoubleConstant + RSpec/StubbedMock: Description: Checks that message expectations do not have a configured response. Enabled: true diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index 16ddf0fd6..116d2a714 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -101,6 +101,7 @@ * xref:cops_rspec.adoc#rspecsortmetadata[RSpec/SortMetadata] * xref:cops_rspec.adoc#rspecspecfilepathformat[RSpec/SpecFilePathFormat] * xref:cops_rspec.adoc#rspecspecfilepathsuffix[RSpec/SpecFilePathSuffix] +* xref:cops_rspec.adoc#rspecstringasinstancedoubleconstant[RSpec/StringAsInstanceDoubleConstant] * xref:cops_rspec.adoc#rspecstubbedmock[RSpec/StubbedMock] * xref:cops_rspec.adoc#rspecsubjectdeclaration[RSpec/SubjectDeclaration] * xref:cops_rspec.adoc#rspecsubjectstub[RSpec/SubjectStub] diff --git a/docs/modules/ROOT/pages/cops_rspec.adoc b/docs/modules/ROOT/pages/cops_rspec.adoc index 143791aa5..01240298c 100644 --- a/docs/modules/ROOT/pages/cops_rspec.adoc +++ b/docs/modules/ROOT/pages/cops_rspec.adoc @@ -5493,6 +5493,41 @@ spec/models/user.rb # shared_examples_for 'foo' * https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SpecFilePathSuffix +== RSpec/StringAsInstanceDoubleConstant + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| No +| Always (Unsafe) +| <> +| - +|=== + +Do not use a string as `instance_double` constant. + +=== Safety + +This cop is unsafe because the correction requires loading the class. +Loading before stubbing causes RSpec to only allow instance methods +to be stubbed. + +=== Examples + +[source,ruby] +---- +# bad +instance_double('User', name: 'John') + +# good +instance_double(User, name: 'John') +---- + +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/StringAsInstanceDoubleConstant + == RSpec/StubbedMock |=== diff --git a/lib/rubocop/cop/rspec/string_as_instance_double_constant.rb b/lib/rubocop/cop/rspec/string_as_instance_double_constant.rb new file mode 100644 index 000000000..c7a346821 --- /dev/null +++ b/lib/rubocop/cop/rspec/string_as_instance_double_constant.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Do not use a string as `instance_double` constant. + # + # @safety + # This cop is unsafe because the correction requires loading the class. + # Loading before stubbing causes RSpec to only allow instance methods + # to be stubbed. + # + # @example + # # bad + # instance_double('User', name: 'John') + # + # # good + # instance_double(User, name: 'John') + # + class StringAsInstanceDoubleConstant < Base + extend AutoCorrector + + MSG = 'Do not use a string as `instance_double` constant.' + RESTRICT_ON_SEND = %i[instance_double].freeze + + # @!method stringified_instance_double_const?(node) + def_node_matcher :stringified_instance_double_const?, <<~PATTERN + (send nil? :instance_double $str ...) + PATTERN + + def on_send(node) + stringified_instance_double_const?(node) do |args_node| + add_offense(args_node) do |corrector| + autocorrect(corrector, args_node) + end + end + end + + def autocorrect(corrector, node) + corrector.replace(node, node.value) + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec_cops.rb b/lib/rubocop/cop/rspec_cops.rb index 5eed7e860..85c177e8e 100644 --- a/lib/rubocop/cop/rspec_cops.rb +++ b/lib/rubocop/cop/rspec_cops.rb @@ -99,6 +99,7 @@ require_relative 'rspec/sort_metadata' require_relative 'rspec/spec_file_path_format' require_relative 'rspec/spec_file_path_suffix' +require_relative 'rspec/string_as_instance_double_constant' require_relative 'rspec/stubbed_mock' require_relative 'rspec/subject_declaration' require_relative 'rspec/subject_stub' diff --git a/spec/rubocop/cop/rspec/string_as_instance_double_constant_spec.rb b/spec/rubocop/cop/rspec/string_as_instance_double_constant_spec.rb new file mode 100644 index 000000000..8f19ada94 --- /dev/null +++ b/spec/rubocop/cop/rspec/string_as_instance_double_constant_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::RSpec::StringAsInstanceDoubleConstant, + :config do + context 'when using a class for instance_double' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + instance_double(Shape, area: 12) + RUBY + end + end + + context 'when passing a variable to initialize instance_double' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + instance_double(type_undetectable_in_static_analysis) + RUBY + end + end + + context 'when using a string for instance_double' do + it 'replaces the string with the class' do + expect_offense <<~RUBY + instance_double('Shape', area: 12) + ^^^^^^^ Do not use a string as `instance_double` constant. + RUBY + + expect_correction <<~RUBY + instance_double(Shape, area: 12) + RUBY + end + end +end