From 7c794c14013b334656045b5c3804750c50088440 Mon Sep 17 00:00:00 2001 From: Dave Corson-Knowles Date: Wed, 18 Sep 2024 06:44:22 -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 | 3 ++ config/default.yml | 7 ++++ docs/modules/ROOT/pages/cops.adoc | 1 + docs/modules/ROOT/pages/cops_rspec.adoc | 29 ++++++++++++++ .../string_as_instance_double_constant.rb | 40 +++++++++++++++++++ lib/rubocop/cop/rspec_cops.rb | 1 + ...string_as_instance_double_constant_spec.rb | 33 +++++++++++++++ 8 files changed, 116 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 4795eec03..a2c675ec5 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 3fb31d825..601cbced3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Master (Unreleased) +- Add `RSpec/StringAsInstanceDoubleConstant` to check for and correct strings used as instance_doubles. ([@corsonknowles]) + ## 3.0.5 (2024-09-07) - Fix false-negative and error for `RSpec/MetadataStyle` when non-literal args are used in metadata in `EnforceStyle: hash`. ([@cbliard]) @@ -920,6 +922,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..c1b081dd5 100644 --- a/docs/modules/ROOT/pages/cops_rspec.adoc +++ b/docs/modules/ROOT/pages/cops_rspec.adoc @@ -5493,6 +5493,35 @@ 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. + +=== Examples + +[source,ruby] +---- +# bad +instance_double('Foo', failure_message, stubs) + +# good +instance_double(Foo, failure_message, stubs) +---- + +=== 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..2ed02f370 --- /dev/null +++ b/lib/rubocop/cop/rspec/string_as_instance_double_constant.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Do not use a string as `instance_double` constant. + # + # @example + # # bad + # instance_double('Foo', failure_message, stubs) + # + # # good + # instance_double(Foo, failure_message, stubs) + # + 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..1f4ba4ae1 --- /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 do + expect_no_offenses(<<~RUBY) + instance_double(Foo, failure_message, stubs) + RUBY + end + end + + context 'when using a symbol for instance_double' do + it do + expect_no_offenses(<<~RUBY) + instance_double(:Foo, failure_message, stubs) + 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('Foo', failure_message, stubs) + ^^^^^ Do not use a string as `instance_double` constant. + RUBY + + expect_correction <<~RUBY + instance_double(Foo, failure_message, stubs) + RUBY + end + end +end