diff --git a/.rubocop.yml b/.rubocop.yml index a3d324c18..ba314d92a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -158,6 +158,8 @@ RSpec/ClassCheck: Enabled: true RSpec/ContainExactly: Enabled: true +RSpec/UndescriptiveLiteralsDescription: + Enabled: true RSpec/DuplicatedMetadata: Enabled: true RSpec/EmptyMetadata: diff --git a/CHANGELOG.md b/CHANGELOG.md index b1b5c669d..ef48eaa5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Master (Unreleased) - Fix an autocorrect error for `RSpec/ExpectActual`. ([@bquorning]) +- Add new `RSpec/UndescriptiveLiteralsDescription` cop. ([@ydah]) ## 2.28.0 (2024-03-30) diff --git a/config/default.yml b/config/default.yml index 7a0fc924e..fb96a34d0 100644 --- a/config/default.yml +++ b/config/default.yml @@ -932,6 +932,12 @@ RSpec/SubjectStub: StyleGuide: https://rspec.rubystyle.guide/#dont-stub-subject Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SubjectStub +RSpec/UndescriptiveLiteralsDescription: + Description: Description should be descriptive. + Enabled: pending + VersionAdded: "<>" + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/UndescriptiveLiteralsDescription + RSpec/UnspecifiedException: Description: Checks for a specified error in checking raised errors. Enabled: true diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index 4dfed17f0..e11215741 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -102,6 +102,7 @@ * xref:cops_rspec.adoc#rspecstubbedmock[RSpec/StubbedMock] * xref:cops_rspec.adoc#rspecsubjectdeclaration[RSpec/SubjectDeclaration] * xref:cops_rspec.adoc#rspecsubjectstub[RSpec/SubjectStub] +* xref:cops_rspec.adoc#rspecundescriptiveliteralsdescription[RSpec/UndescriptiveLiteralsDescription] * xref:cops_rspec.adoc#rspecunspecifiedexception[RSpec/UnspecifiedException] * xref:cops_rspec.adoc#rspecvariabledefinition[RSpec/VariableDefinition] * xref:cops_rspec.adoc#rspecvariablename[RSpec/VariableName] diff --git a/docs/modules/ROOT/pages/cops_rspec.adoc b/docs/modules/ROOT/pages/cops_rspec.adoc index a2d7e018f..3f424196d 100644 --- a/docs/modules/ROOT/pages/cops_rspec.adoc +++ b/docs/modules/ROOT/pages/cops_rspec.adoc @@ -5617,6 +5617,67 @@ end * https://robots.thoughtbot.com/don-t-stub-the-system-under-test * https://penelope.zone/2015/12/27/introducing-rspec-smells-and-where-to-find-them.html#smell-1-stubjec +== RSpec/UndescriptiveLiteralsDescription + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| No +| <> +| - +|=== + +Description should be descriptive. + +If example group or example contains only `execute string`, numbers +and regular expressions, the description is not clear. + +=== Examples + +[source,ruby] +---- +# bad +describe `time` do + # ... +end + +# bad +context /when foo/ do + # ... +end + +# bad +it 10000 do + # ... +end + +# good +describe Foo do + # ... +end + +# good +describe '#foo' do + # ... +end + +# good +context "when #{foo} is bar" do + # ... +end + +# good +it 'does something' do + # ... +end +---- + +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/UndescriptiveLiteralsDescription + == RSpec/UnspecifiedException |=== diff --git a/lib/rubocop/cop/rspec/undescriptive_literals_description.rb b/lib/rubocop/cop/rspec/undescriptive_literals_description.rb new file mode 100644 index 000000000..73cd90662 --- /dev/null +++ b/lib/rubocop/cop/rspec/undescriptive_literals_description.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + # Description should be descriptive. + # + # If example group or example contains only `execute string`, numbers + # and regular expressions, the description is not clear. + # + # @example + # # bad + # describe `time` do + # # ... + # end + # + # # bad + # context /when foo/ do + # # ... + # end + # + # # bad + # it 10000 do + # # ... + # end + # + # # good + # describe Foo do + # # ... + # end + # + # # good + # describe '#foo' do + # # ... + # end + # + # # good + # context "when #{foo} is bar" do + # # ... + # end + # + # # good + # it 'does something' do + # # ... + # end + # + class UndescriptiveLiteralsDescription < Base + MSG = 'Description should be descriptive.' + + # @!method example_groups_or_example?(node) + def_node_matcher :example_groups_or_example?, <<~PATTERN + (block (send #rspec? {#ExampleGroups.all #Examples.all} $_) ...) + PATTERN + + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler + example_groups_or_example?(node) do |arg| + add_offense(arg) if offense?(arg) + end + end + + private + + def offense?(node) + %i[xstr int regexp].include?(node.type) + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec_cops.rb b/lib/rubocop/cop/rspec_cops.rb index 7d74ef8a7..6c83b7dbe 100644 --- a/lib/rubocop/cop/rspec_cops.rb +++ b/lib/rubocop/cop/rspec_cops.rb @@ -124,6 +124,7 @@ require_relative 'rspec/stubbed_mock' require_relative 'rspec/subject_declaration' require_relative 'rspec/subject_stub' +require_relative 'rspec/undescriptive_literals_description' require_relative 'rspec/unspecified_exception' require_relative 'rspec/variable_definition' require_relative 'rspec/variable_name' diff --git a/spec/rubocop/cop/rspec/undescriptive_literals_description_spec.rb b/spec/rubocop/cop/rspec/undescriptive_literals_description_spec.rb new file mode 100644 index 000000000..2114be22f --- /dev/null +++ b/spec/rubocop/cop/rspec/undescriptive_literals_description_spec.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::RSpec::UndescriptiveLiteralsDescription, :config do + it 'registers an offense when using `describe` with only execute string' do + expect_offense(<<~RUBY) + describe `time` do + ^^^^^^ Description should be descriptive. + end + RUBY + end + + it 'registers an offense when using `context` with only execute string' do + expect_offense(<<~RUBY) + context `time` do + ^^^^^^ Description should be descriptive. + end + RUBY + end + + it 'registers an offense when using `it` with only execute string' do + expect_offense(<<~RUBY) + it `time` do + ^^^^^^ Description should be descriptive. + end + RUBY + end + + it 'registers an offense when using `describe` with only regex' do + expect_offense(<<~RUBY) + describe /time/ do + ^^^^^^ Description should be descriptive. + end + RUBY + end + + it 'registers an offense when using `describe` with only Integer' do + expect_offense(<<~RUBY) + describe 10000 do + ^^^^^ Description should be descriptive. + end + RUBY + end + + it 'does not register an offense when using `describe` with method call' do + expect_no_offenses(<<~RUBY) + describe foo.to_s do + end + RUBY + end + + it 'does not register an offense when using `describe` with local variable' do + expect_no_offenses(<<~RUBY) + types.each do |type| + describe type do + end + end + RUBY + end + + it 'does not register an offense when using `describe` with ' \ + 'instance variable' do + expect_no_offenses(<<~RUBY) + describe @foo do + end + RUBY + end + + it 'does not register an offense when using `describe` with ' \ + 'grobal variable' do + expect_no_offenses(<<~RUBY) + describe $foo do + end + RUBY + end + + it 'does not register an offense when using `describe` with a string' do + expect_no_offenses(<<~RUBY) + describe '#foo' do + end + RUBY + end + + it 'does not register an offense when using `describe` with a class' do + expect_no_offenses(<<~RUBY) + describe Foo do + end + RUBY + end + + it 'does not register an offense when using `context` with a string' do + expect_no_offenses(<<~RUBY) + context 'when foo is bar' do + end + RUBY + end + + it 'does not register an offense when using `it` with a string' do + expect_no_offenses(<<~RUBY) + it 'does something' do + end + RUBY + end + + it 'does not register an offense when using `describe` with an ' \ + 'interpolation string' do + expect_no_offenses(<<~RUBY) + describe "foo \#{bar}" do + end + RUBY + end + + it 'does not register an offense when using `describe` with a ' \ + 'heredoc string' do + expect_no_offenses(<<~RUBY) + describe <<~DESC do + foo + DESC + end + RUBY + end + + it 'does not register an offense when using `describe` with a ' \ + 'string concatenation' do + expect_no_offenses(<<~RUBY) + describe 'foo' + `time` do + context 'when ' + 1.to_s do + it 'returns ' + /something/.to_s do + end + end + end + RUBY + end +end