even_better_specs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1654cb014ec4bfbe5ac31e7f75259ff4f161041cd1fb15e2658a279b4ceb1f01
4
+ data.tar.gz: f5f45ee95234cf0631612cb9a4f48946fff2df2415fcd80aceb5b295b619dba3
5
+ SHA512:
6
+ metadata.gz: 672498e77b41332d9f1d8510505b5edde0bfb227cd5168fc858beaa21a0d506d2e6dd536d06fce90d24c2399e52fed993ac84e6a46250a7e5d7894878e9887c8
7
+ data.tar.gz: 6c38c8e78159a86f6358dae83faacd41fe246c70104ba0700b7d2a5b9ccfd3db36cbd90acf1c4ca636e5f86a43ec4f4ab447f1c69d0f23dace9873a938adc8b4
data/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5
+ and this project follows [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-10-30
10
+
11
+ ### Added
12
+
13
+ - `even_better_specs` CLI that runs with its own config, so it works in projects without rubocop in their Gemfile.
14
+ - Cops for the Even Better Specs guidelines: `DescribeMethod`, `FactoriesNotFixtures`, `UseSubject`,
15
+ `ShortDescription`, `GroupExpectations`, `AllPossibleCases` (info severity), `RequestSpecs`, `ExpectSyntax`,
16
+ `RedundantRequire` (autocorrectable), `NoLet`, `NoHooks`, `NoSharedExamples`, `StubEnv`, `CreateOnlyNeededData`.
17
+ - rubocop-rspec cops enabled: `DescribeClass`, `ContextMethod`, `ContextWording`, `DescribedClass`, `VerifiedDoubles`.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # Even Better Specs
2
+
3
+ RuboCop cops for the [Even Better Specs](https://evenbetterspecs.github.io) guidelines, with a CLI
4
+ that runs in any project, including Rails apps that do not have `rubocop` in their Gemfile.
5
+
6
+ ## Usage
7
+
8
+ Install the gem globally (do **not** add it to the Gemfile) and run it from the project root:
9
+
10
+ ```sh
11
+ gem install even_better_specs
12
+ even_better_specs # checks every *_spec.rb
13
+ even_better_specs spec/models
14
+ even_better_specs -a # autocorrect what can be autocorrected
15
+ ```
16
+
17
+ Do not use `bundle exec`, because the gem is not in the Gemfile. The CLI accepts all `rubocop` options.
18
+ It runs with its own config only, so the project's `.rubocop.yml` (if any) is ignored.
19
+
20
+ ## Rules
21
+
22
+ | Guideline | Cop |
23
+ |---|---|
24
+ | Describe what you are testing | `RSpec/DescribeClass`, `EvenBetterSpecs/DescribeMethod` |
25
+ | Use contexts | `RSpec/ContextMethod`, `RSpec/ContextWording` (`when`) |
26
+ | Factories, not fixtures | `EvenBetterSpecs/FactoriesNotFixtures` |
27
+ | Leverage described class | `RSpec/DescribedClass` |
28
+ | Use subject | `EvenBetterSpecs/UseSubject` |
29
+ | Short description | `EvenBetterSpecs/ShortDescription` (`Max: 100`) |
30
+ | Group expectations | `EvenBetterSpecs/GroupExpectations` |
31
+ | All possible cases | `EvenBetterSpecs/AllPossibleCases` (flags a `describe` with a single `context`; reported as `info`, so it does not fail the run) |
32
+ | Request vs controller specs | `EvenBetterSpecs/RequestSpecs` |
33
+ | Expect vs should syntax | `EvenBetterSpecs/ExpectSyntax` |
34
+ | Redundant require | `EvenBetterSpecs/RedundantRequire` (autocorrectable) |
35
+ | Instance double over double | `RSpec/VerifiedDoubles` |
36
+ | Let's not | `EvenBetterSpecs/NoLet` |
37
+ | Avoid hooks | `EvenBetterSpecs/NoHooks` |
38
+ | Don't use shared examples | `EvenBetterSpecs/NoSharedExamples` |
39
+ | Stub environment variables | `EvenBetterSpecs/StubEnv` (flags writes to `ENV`) |
40
+ | Create only the data you need | `EvenBetterSpecs/CreateOnlyNeededData` (`*_list` above `Max: 2`) |
41
+
42
+ "Mock external dependencies" and "Stub HTTP requests" cannot be detected statically. For HTTP, use
43
+ `WebMock.disable_net_connect!` in `spec_helper.rb` so unstubbed requests fail.
44
+
45
+ ## Development
46
+
47
+ ```sh
48
+ bundle install
49
+ bundle exec rspec
50
+ ```
@@ -0,0 +1,116 @@
1
+ # Even Better Specs: https://evenbetterspecs.github.io
2
+ plugins:
3
+ - rubocop-rspec
4
+
5
+ AllCops:
6
+ DisabledByDefault: true
7
+ NewCops: disable
8
+ SuggestExtensions: false
9
+ Include:
10
+ - "**/*_spec.rb"
11
+
12
+ # Describe what you are testing
13
+ RSpec/DescribeClass:
14
+ Enabled: true
15
+ # Unlike rubocop-rspec's default, request specs must describe the controller constant.
16
+ Exclude:
17
+ - "**/spec/features/**/*"
18
+ - "**/spec/routing/**/*"
19
+ - "**/spec/system/**/*"
20
+ - "**/spec/views/**/*"
21
+ IgnoredMetadata:
22
+ type:
23
+ - routing
24
+ - view
25
+ - feature
26
+ - system
27
+ - aruba
28
+ - task
29
+ EvenBetterSpecs/DescribeMethod:
30
+ Description: Nested `describe` blocks name a method using `.` or `#`.
31
+ Enabled: true
32
+
33
+ # Use contexts
34
+ RSpec/ContextMethod:
35
+ Enabled: true
36
+ RSpec/ContextWording:
37
+ Enabled: true
38
+ Prefixes:
39
+ - when
40
+
41
+ # Factories, not fixtures
42
+ EvenBetterSpecs/FactoriesNotFixtures:
43
+ Description: Use factories instead of fixtures.
44
+ Enabled: true
45
+
46
+ # Leverage described class
47
+ RSpec/DescribedClass:
48
+ Enabled: true
49
+
50
+ # Use subject
51
+ EvenBetterSpecs/UseSubject:
52
+ Description: Use `subject` instead of `described_class.new` without arguments.
53
+ Enabled: true
54
+
55
+ # Short description
56
+ EvenBetterSpecs/ShortDescription:
57
+ Description: Descriptions are short and on a single line.
58
+ Enabled: true
59
+ Max: 100
60
+
61
+ # Group expectations
62
+ EvenBetterSpecs/GroupExpectations:
63
+ Description: Sibling examples with the same setup are merged into one.
64
+ Enabled: true
65
+
66
+ # All possible cases
67
+ EvenBetterSpecs/AllPossibleCases:
68
+ Description: A `describe` has more than one `context`.
69
+ Enabled: true
70
+ # Heuristic: it reports but does not fail the run.
71
+ Severity: info
72
+
73
+ # Request vs controller specs
74
+ EvenBetterSpecs/RequestSpecs:
75
+ Description: Write request specs instead of controller specs.
76
+ Enabled: true
77
+
78
+ # Expect vs should syntax
79
+ EvenBetterSpecs/ExpectSyntax:
80
+ Description: Use `expect` instead of `should`.
81
+ Enabled: true
82
+
83
+ # Redundant require
84
+ EvenBetterSpecs/RedundantRequire:
85
+ Description: Require helpers from `.rspec`, not from each spec file.
86
+ Enabled: true
87
+
88
+ # Instance double over double
89
+ RSpec/VerifiedDoubles:
90
+ Enabled: true
91
+
92
+ # Let's not
93
+ EvenBetterSpecs/NoLet:
94
+ Description: Do not use `let` / `let!`.
95
+ Enabled: true
96
+
97
+ # Avoid hooks
98
+ EvenBetterSpecs/NoHooks:
99
+ Description: Avoid hooks.
100
+ Enabled: true
101
+
102
+ # Don't use shared examples
103
+ EvenBetterSpecs/NoSharedExamples:
104
+ Description: Do not use shared examples or shared contexts.
105
+ Enabled: true
106
+
107
+ # Stub environment variables
108
+ EvenBetterSpecs/StubEnv:
109
+ Description: Stub environment variables instead of changing `ENV`.
110
+ Enabled: true
111
+
112
+ # Create only the data you need
113
+ EvenBetterSpecs/CreateOnlyNeededData:
114
+ Description: Do not create more records than the example needs.
115
+ Enabled: true
116
+ Max: 3
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "even_better_specs"
5
+
6
+ # --config makes RuboCop ignore the project's own .rubocop.yml (if any).
7
+ exit RuboCop::CLI.new.run(["--config", EvenBetterSpecs::CONFIG, *ARGV])
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvenBetterSpecs
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop"
4
+ require "rubocop-rspec"
5
+ require_relative "even_better_specs/version"
6
+
7
+ module EvenBetterSpecs
8
+ CONFIG = File.expand_path("../config/default.yml", __dir__)
9
+ end
10
+
11
+ Dir[File.join(__dir__, "rubocop/cop/even_better_specs/*.rb")].each { |file| require file }
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module EvenBetterSpecs
6
+ # A `describe` with a single `context` most likely misses the edge and invalid cases.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # describe '#destroy' do
11
+ # context 'when the product exists' do
12
+ # end
13
+ # end
14
+ #
15
+ # # good
16
+ # describe '#destroy' do
17
+ # context 'when the product exists' do
18
+ # end
19
+ #
20
+ # context 'when the product does not exist' do
21
+ # end
22
+ # end
23
+ class AllPossibleCases < RuboCop::Cop::RSpec::Base
24
+ MSG = "Only one `context` here. Test valid, edge and invalid cases."
25
+
26
+ def_node_matcher :describe?, "(block (send #rspec? :describe ...) ...)"
27
+ def_node_matcher :context?, "(block (send nil? :context ...) ...)"
28
+
29
+ def on_block(node)
30
+ return unless describe?(node) && node.body && context?(node.body)
31
+
32
+ add_offense(node.send_node)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module EvenBetterSpecs
6
+ # Create only the data you need. Flags `*_list` factory calls above `Max` records.
7
+ #
8
+ # @example Max: 2 (default)
9
+ # # bad
10
+ # create_list(:product, 5)
11
+ #
12
+ # # good
13
+ # create(:product, featured: false)
14
+ class CreateOnlyNeededData < Base
15
+ MSG = "Create only the data you need (%<count>d records, max %<max>d)."
16
+ RESTRICT_ON_SEND = %i[create_list build_list build_stubbed_list attributes_for_list].freeze
17
+
18
+ def_node_matcher :list_count, "(send {nil? (const {nil? cbase} :FactoryBot)} _ _ (int $_) ...)"
19
+
20
+ def on_send(node)
21
+ count = list_count(node)
22
+ max = cop_config.fetch("Max", 3)
23
+ return unless count && count > max
24
+
25
+ add_offense(node, message: format(MSG, count: count, max: max))
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module EvenBetterSpecs
6
+ # Nested `describe` blocks name a method: `.class_method` or `#instance_method`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # describe 'the authenticate method for User' do
11
+ # describe 'GET #index' do
12
+ #
13
+ # # good
14
+ # describe '.authenticate' do
15
+ # describe '#index' do
16
+ class DescribeMethod < RuboCop::Cop::RSpec::Base
17
+ MSG = "Describe a method using `.class_method` or `#instance_method`, or use `context`."
18
+
19
+ def_node_matcher :describe_with_string, "(block (send #rspec? :describe $str ...) ...)"
20
+
21
+ def on_block(node)
22
+ description = describe_with_string(node)
23
+ return unless description
24
+ return if description.value.start_with?(".", "#")
25
+ return unless node.each_ancestor(:block).any? { |ancestor| example_group?(ancestor) }
26
+
27
+ add_offense(description)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module EvenBetterSpecs
6
+ # Always use `expect` instead of `should`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # response.code.should eq('200')
11
+ # it { should eq('200') }
12
+ #
13
+ # # good
14
+ # expect(response.code).to eq('200')
15
+ # it { is_expected.to eq('200') }
16
+ class ExpectSyntax < Base
17
+ MSG = "Use `expect` instead of `%<method>s`."
18
+ RESTRICT_ON_SEND = %i[should should_not].freeze
19
+
20
+ def on_send(node)
21
+ add_offense(node.loc.selector, message: format(MSG, method: node.method_name))
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module EvenBetterSpecs
6
+ # Use factories instead of fixtures.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # fixtures :users
11
+ #
12
+ # # good
13
+ # user = create(:user)
14
+ class FactoriesNotFixtures < Base
15
+ MSG = "Use factories instead of fixtures."
16
+ RESTRICT_ON_SEND = %i[fixtures].freeze
17
+
18
+ def on_send(node)
19
+ add_offense(node) if node.receiver.nil?
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module EvenBetterSpecs
6
+ # Sibling examples with the same arrange and act steps should be one example
7
+ # with several expectations.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # it 'returns 200' do
12
+ # get user_path(user)
13
+ # expect(response.code).to eq('200')
14
+ # end
15
+ #
16
+ # it 'returns JSON content type' do
17
+ # get user_path(user)
18
+ # expect(response.content_type).to eq('application/json')
19
+ # end
20
+ #
21
+ # # good
22
+ # it 'responds with 200 http status and a JSON content type' do
23
+ # get user_path(user)
24
+ # expect(response.code).to eq('200')
25
+ # expect(response.content_type).to eq('application/json')
26
+ # end
27
+ class GroupExpectations < RuboCop::Cop::RSpec::Base
28
+ MSG = "Same setup as a sibling example. Group the expectations in a single example."
29
+ EXPECTATIONS = %i[expect expect_any_instance_of is_expected are_expected should should_not].freeze
30
+
31
+ def on_block(node)
32
+ return unless example_group_with_body?(node)
33
+
34
+ seen = {}
35
+ node.body.each_child_node(:block).select { |child| example?(child) }.each do |example|
36
+ setup = setup_source(example)
37
+ next if setup.empty?
38
+
39
+ seen.key?(setup) ? add_offense(example.send_node) : seen[setup] = true
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def setup_source(example)
46
+ statements = example.body&.begin_type? ? example.body.children : [example.body].compact
47
+ statements.reject { |statement| expectation?(statement) }.map(&:source).join("\n")
48
+ end
49
+
50
+ def expectation?(statement)
51
+ statement.each_node(:send).any? { |send| EXPECTATIONS.include?(send.method_name) }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module EvenBetterSpecs
6
+ # Avoid hooks. Arrange everything inside each example.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # before do
11
+ # sign_in user
12
+ # end
13
+ #
14
+ # # good
15
+ # it 'returns 200' do
16
+ # sign_in user
17
+ # end
18
+ class NoHooks < RuboCop::Cop::RSpec::Base
19
+ MSG = "Avoid hooks. Arrange everything inside the example."
20
+
21
+ def on_block(node)
22
+ add_offense(node.send_node) if hook?(node)
23
+ end
24
+ alias on_numblock on_block
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module EvenBetterSpecs
6
+ # Do not use `let` / `let!`. Define the data inside each example.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # let(:user) { build(:user) }
11
+ #
12
+ # # good
13
+ # it 'returns the full name' do
14
+ # user = build(:user)
15
+ # end
16
+ class NoLet < Base
17
+ MSG = "Do not use `%<method>s`. Define the data inside the example."
18
+ RESTRICT_ON_SEND = %i[let let!].freeze
19
+
20
+ def on_send(node)
21
+ add_offense(node, message: format(MSG, method: node.method_name)) if node.receiver.nil?
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module EvenBetterSpecs
6
+ # Do not use shared examples or shared contexts.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # shared_examples 'a barking animal' do
11
+ # it_behaves_like 'a barking animal'
12
+ #
13
+ # # good
14
+ # it 'barks' do
15
+ # expect(subject.able_to_bark?).to eq(true)
16
+ # end
17
+ class NoSharedExamples < Base
18
+ MSG = "Do not use `%<method>s`. Write self-contained examples."
19
+ RESTRICT_ON_SEND = %i[
20
+ shared_examples shared_examples_for shared_context
21
+ it_behaves_like it_should_behave_like include_examples include_context
22
+ ].freeze
23
+
24
+ def_node_matcher :rspec_receiver?, "{nil? (const {nil? cbase} :RSpec)}"
25
+
26
+ def on_send(node)
27
+ return unless rspec_receiver?(node.receiver)
28
+
29
+ add_offense(node, message: format(MSG, method: node.method_name))
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module EvenBetterSpecs
6
+ # Remove `require 'rails_helper'` / `require 'spec_helper'` from spec files.
7
+ # Use `--require rails_helper` in `.rspec` instead.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # require 'rails_helper'
12
+ #
13
+ # describe User do
14
+ # end
15
+ #
16
+ # # good
17
+ # describe User do
18
+ # end
19
+ class RedundantRequire < Base
20
+ extend AutoCorrector
21
+ include RangeHelp
22
+
23
+ MSG = "Remove redundant `require`. Use `--require %<helper>s` in `.rspec` instead."
24
+ RESTRICT_ON_SEND = %i[require].freeze
25
+
26
+ def_node_matcher :helper_require, "(send nil? :require (str ${\"rails_helper\" \"spec_helper\"}))"
27
+
28
+ def on_send(node)
29
+ helper = helper_require(node)
30
+ return unless helper
31
+
32
+ add_offense(node, message: format(MSG, helper: helper)) do |corrector|
33
+ corrector.remove(range_with_surrounding_space(node.source_range, side: :right))
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module EvenBetterSpecs
6
+ # Write request specs instead of controller specs.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # describe UsersController, type: :controller do
11
+ #
12
+ # # good
13
+ # describe UsersController, type: :request do
14
+ class RequestSpecs < Base
15
+ MSG = "Write request specs instead of controller specs."
16
+
17
+ def_node_matcher :controller_type?, "(pair (sym :type) (sym :controller))"
18
+
19
+ def on_pair(node)
20
+ add_offense(node) if controller_type?(node)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module EvenBetterSpecs
6
+ # Descriptions must not be longer than `Max` characters nor span many lines.
7
+ # Split them using `context`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # it 'returns 422 when user first_name is missing from params and when user last_name is missing' do
12
+ #
13
+ # # good
14
+ # context 'when user first_name is missing from params' do
15
+ # it 'returns 422' do
16
+ class ShortDescription < RuboCop::Cop::RSpec::Base
17
+ MSG = "Keep descriptions under %<max>d characters and on a single line. Split them using `context`."
18
+
19
+ def_node_matcher :description, "(block (send #rspec? _ ${str dstr} ...) ...)"
20
+
21
+ def on_block(node)
22
+ return unless example?(node) || example_group?(node)
23
+
24
+ text = description(node)
25
+ return unless text
26
+
27
+ length = text.str_type? ? text.value.length : text.source.length
28
+ return unless text.multiline? || length > max
29
+
30
+ add_offense(text, message: format(MSG, max: max))
31
+ end
32
+
33
+ private
34
+
35
+ def max
36
+ cop_config.fetch("Max", 100)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module EvenBetterSpecs
6
+ # Stub environment variables instead of changing `ENV`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # ENV['SALES_TEAM_EMAIL'] = 'sales-group@company.com'
11
+ #
12
+ # # good
13
+ # stub_env('SALES_TEAM_EMAIL', 'sales-group@company.com')
14
+ class StubEnv < Base
15
+ MSG = "Stub environment variables (e.g. `stub_env`) instead of changing `ENV`."
16
+ RESTRICT_ON_SEND = %i[[]= store update merge! delete replace clear].freeze
17
+
18
+ def_node_matcher :env?, "(const {nil? cbase} :ENV)"
19
+
20
+ def on_send(node)
21
+ add_offense(node) if env?(node.receiver)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module EvenBetterSpecs
6
+ # Use `subject` instead of `described_class.new` when no argument is given.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # calculator = described_class.new
11
+ #
12
+ # # good
13
+ # subject.add(1, 2)
14
+ # subject = described_class.new(5)
15
+ class UseSubject < Base
16
+ MSG = "Use `subject` instead of `described_class.new` without arguments."
17
+ RESTRICT_ON_SEND = %i[new].freeze
18
+
19
+ def_node_matcher :described_class_new?, "(send (send nil? :described_class) :new)"
20
+
21
+ def on_send(node)
22
+ add_offense(node) if described_class_new?(node)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: even_better_specs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Glauco Custodio
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-10-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rubocop
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.72'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.72'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubocop-rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.5'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.5'
41
+ description: Enforces https://evenbetterspecs.github.io with RuboCop. Ships its own
42
+ CLI so it runs in projects that do not have rubocop in their Gemfile.
43
+ email:
44
+ - glauco.custodio@gmail.com
45
+ executables:
46
+ - even_better_specs
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - CHANGELOG.md
51
+ - README.md
52
+ - config/default.yml
53
+ - exe/even_better_specs
54
+ - lib/even_better_specs.rb
55
+ - lib/even_better_specs/version.rb
56
+ - lib/rubocop/cop/even_better_specs/all_possible_cases.rb
57
+ - lib/rubocop/cop/even_better_specs/create_only_needed_data.rb
58
+ - lib/rubocop/cop/even_better_specs/describe_method.rb
59
+ - lib/rubocop/cop/even_better_specs/expect_syntax.rb
60
+ - lib/rubocop/cop/even_better_specs/factories_not_fixtures.rb
61
+ - lib/rubocop/cop/even_better_specs/group_expectations.rb
62
+ - lib/rubocop/cop/even_better_specs/no_hooks.rb
63
+ - lib/rubocop/cop/even_better_specs/no_let.rb
64
+ - lib/rubocop/cop/even_better_specs/no_shared_examples.rb
65
+ - lib/rubocop/cop/even_better_specs/redundant_require.rb
66
+ - lib/rubocop/cop/even_better_specs/request_specs.rb
67
+ - lib/rubocop/cop/even_better_specs/short_description.rb
68
+ - lib/rubocop/cop/even_better_specs/stub_env.rb
69
+ - lib/rubocop/cop/even_better_specs/use_subject.rb
70
+ homepage: https://evenbetterspecs.github.io
71
+ licenses:
72
+ - MIT
73
+ metadata:
74
+ source_code_uri: https://github.com/evenbetterspecs/even_better_specs
75
+ changelog_uri: https://github.com/evenbetterspecs/even_better_specs/blob/main/CHANGELOG.md
76
+ bug_tracker_uri: https://github.com/evenbetterspecs/even_better_specs/issues
77
+ rubygems_mfa_required: 'true'
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '3.1'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubygems_version: 3.5.16
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: RuboCop cops for the Even Better Specs guidelines
97
+ test_files: []