From 9f1a2488d3853bb72ba7e4f08eb892cbf8c35769 Mon Sep 17 00:00:00 2001 From: Dave Corson-Knowles Date: Tue, 17 Sep 2024 16:28:08 -0700 Subject: [PATCH] Add RSpec/NoStringifiedNoStringifiedInstanceDoubleConstant Addresses #1136 --- .rubocop.yml | 24 +++++++---- CHANGELOG.md | 5 +++ Rakefile | 2 +- config/default.yml | 7 ++++ docs/modules/ROOT/pages/cops.adoc | 1 + docs/modules/ROOT/pages/cops_rspec.adoc | 29 +++++++++++++ lefthook.yml | 35 ++++++++++++++++ ...no_stringified_instance_double_constant.rb | 42 +++++++++++++++++++ lib/rubocop/cop/rspec_cops.rb | 1 + ...ringified_instance_double_constant_spec.rb | 37 ++++++++++++++++ 10 files changed, 173 insertions(+), 10 deletions(-) create mode 100644 lefthook.yml create mode 100644 lib/rubocop/cop/rspec/no_stringified_instance_double_constant.rb create mode 100644 spec/rubocop/cop/rspec/no_stringified_instance_double_constant_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index 4795eec03..78847b5c4 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -101,34 +101,40 @@ RSpec: - expect_no_offenses - expect_offense +RSpec/DescribeClass: + Exclude: + - spec/project/**/*.rb + RSpec/ExampleLength: CountAsOne: - heredoc Max: 11 -RSpec/DescribeClass: - Exclude: - - spec/project/**/*.rb +RSpec/ExpectInLet: + Enabled: true RSpec/MultipleExpectations: Max: 2 -# `expect_offense` does not use Kernel#format or String#% -Style/FormatStringToken: - Exclude: - - spec/rubocop/**/*.rb - -Style/RequireOrder: +RSpec/NoStringifiedInstanceDoubleConstant: Enabled: true RSpec/SpecFilePathFormat: Exclude: - spec/rubocop/cop/rspec/mixin/**/*.rb +# `expect_offense` does not use Kernel#format or String#% +Style/FormatStringToken: + Exclude: + - spec/rubocop/**/*.rb + Style/NumberedParameters: Enabled: true EnforcedStyle: disallow +Style/RequireOrder: + Enabled: true + # Enable RuboCop's pending cops up to v1.63 Gemspec/DeprecatedAttributeAssignment: {Enabled: true} diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fb31d825..914180c55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Master (Unreleased) +## 3.0.6 (2024-09-17) + +- Add `RSpec/NoStringifiedInstanceDoubleConstant` 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 +924,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/Rakefile b/Rakefile index a91382b68..05a24fc0d 100644 --- a/Rakefile +++ b/Rakefile @@ -76,7 +76,7 @@ task confirm_documentation: :generate_cops_documentation do Open3.popen3('git diff --exit-code docs/') unless process.value.success? - raise 'Please run `rake generate_cops_documentation` ' \ + raise 'Please run `bundle exec rake generate_cops_documentation` ' \ 'and add docs/ to the commit.' end end diff --git a/config/default.yml b/config/default.yml index 62135b81d..74fd9568a 100644 --- a/config/default.yml +++ b/config/default.yml @@ -726,6 +726,13 @@ RSpec/NoExpectationExample: - "^expect_" - "^assert_" +RSpec/NoStringifiedInstanceDoubleConstant: + Description: Do not use a string as `instance_double` constant. + Enabled: pending + Safe: true + VersionAdded: "<>" + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NoStringifiedInstanceDoubleConstant + RSpec/NotToNot: Description: Checks for consistent method usage for negating expectations. Enabled: true diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index 16ddf0fd6..5ea5e8dfb 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -74,6 +74,7 @@ * xref:cops_rspec.adoc#rspecnamedsubject[RSpec/NamedSubject] * xref:cops_rspec.adoc#rspecnestedgroups[RSpec/NestedGroups] * xref:cops_rspec.adoc#rspecnoexpectationexample[RSpec/NoExpectationExample] +* xref:cops_rspec.adoc#rspecnostringifiedinstancedoubleconstant[RSpec/NoStringifiedInstanceDoubleConstant] * xref:cops_rspec.adoc#rspecnottonot[RSpec/NotToNot] * xref:cops_rspec.adoc#rspecoverwritingsetup[RSpec/OverwritingSetup] * xref:cops_rspec.adoc#rspecpending[RSpec/Pending] diff --git a/docs/modules/ROOT/pages/cops_rspec.adoc b/docs/modules/ROOT/pages/cops_rspec.adoc index 143791aa5..78604e103 100644 --- a/docs/modules/ROOT/pages/cops_rspec.adoc +++ b/docs/modules/ROOT/pages/cops_rspec.adoc @@ -4117,6 +4117,35 @@ end * https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NoExpectationExample +== RSpec/NoStringifiedInstanceDoubleConstant + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| Always +| <> +| - +|=== + +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/NoStringifiedInstanceDoubleConstant + == RSpec/NotToNot |=== diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 000000000..f6e5dfe59 --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,35 @@ +# EXAMPLE USAGE: +# +# Refer for explanation to following link: +# https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md +# +# pre-push: +# commands: +# packages-audit: +# tags: frontend security +# run: yarn audit +# gems-audit: +# tags: backend security +# run: bundle audit +# +# pre-commit: +# parallel: true +# commands: +# eslint: +# glob: "*.{js,ts,jsx,tsx}" +# run: yarn eslint {staged_files} +# rubocop: +# tags: backend style +# glob: "*.rb" +# exclude: '(^|/)(application|routes)\.rb$' +# run: bundle exec rubocop --force-exclusion {all_files} +# govet: +# tags: backend style +# files: git ls-files -m +# glob: "*.go" +# run: go vet {files} +# scripts: +# "hello.js": +# runner: node +# "any.go": +# runner: go run diff --git a/lib/rubocop/cop/rspec/no_stringified_instance_double_constant.rb b/lib/rubocop/cop/rspec/no_stringified_instance_double_constant.rb new file mode 100644 index 000000000..345b32615 --- /dev/null +++ b/lib/rubocop/cop/rspec/no_stringified_instance_double_constant.rb @@ -0,0 +1,42 @@ +# 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 NoStringifiedInstanceDoubleConstant < 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 $...) + PATTERN + + def on_send(node) + stringified_instance_double_const?(node) do |args_node| + if args_node.first.str_type? + add_offense(args_node.first) do |corrector| + autocorrect(corrector, args_node.first) + end + 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..0ddc55a3c 100644 --- a/lib/rubocop/cop/rspec_cops.rb +++ b/lib/rubocop/cop/rspec_cops.rb @@ -72,6 +72,7 @@ require_relative 'rspec/named_subject' require_relative 'rspec/nested_groups' require_relative 'rspec/no_expectation_example' +require_relative 'rspec/no_stringified_instance_double_constant' require_relative 'rspec/not_to_not' require_relative 'rspec/overwriting_setup' require_relative 'rspec/pending' diff --git a/spec/rubocop/cop/rspec/no_stringified_instance_double_constant_spec.rb b/spec/rubocop/cop/rspec/no_stringified_instance_double_constant_spec.rb new file mode 100644 index 000000000..107e5515d --- /dev/null +++ b/spec/rubocop/cop/rspec/no_stringified_instance_double_constant_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::RSpec::NoStringifiedInstanceDoubleConstant, + :config do + context 'when using a class for instance_double' do + let(:source) do + <<~RUBY + instance_double(Foo, failure_message, stubs) + RUBY + end + + it { expect_no_offenses source } + end + + context 'when using a symbol for instance_double' do + let(:source) do + <<~RUBY + instance_double(:Foo, failure_message, stubs) + RUBY + end + + it { expect_no_offenses source } + 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