Skip to content

Commit

Permalink
feat: Implement a rubocop for consistent usage of parens in factorybo…
Browse files Browse the repository at this point in the history
…t methods
  • Loading branch information
Liberatys authored and pirj committed Oct 19, 2022
1 parent f24f516 commit 993cecf
Show file tree
Hide file tree
Showing 9 changed files with 474 additions and 19 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* Add new `RSpec/Capybara/NegationMatcher` cop. ([@ydah][])
* Add `AllowedPatterns` configuration option to `RSpec/NoExpectationExample`. ([@ydah][])
* Improve `RSpec/NoExpectationExample` cop to ignore examples skipped or pending via metatada. ([@pirj][])
* Add `RSpec/FactoryBot/ConsistentParenthesesStyle` cop. ([@Liberatys][])

## 2.13.2 (2022-09-23)

Expand Down Expand Up @@ -704,6 +705,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
[@koic]: https://github.com/koic
[@lazycoder9]: https://github.com/lazycoder9
[@leoarnold]: https://github.com/leoarnold
[@Liberatys]: https://github.com/Liberatys
[@lokhi]: https://github.com/lokhi
[@luke-hill]: https://github.com/luke-hill
[@M-Yamashita01]: https://github.com/M-Yamashita01
Expand Down
10 changes: 10 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,16 @@ RSpec/FactoryBot/AttributeDefinedStatically:
VersionChanged: '2.0'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/AttributeDefinedStatically

RSpec/FactoryBot/ConsistentParenthesesStyle:
Description: Use a consistent style for parentheses in factory bot calls.
Enabled: pending
EnforcedStyle: require_parentheses
SupportedStyles:
- require_parentheses
- omit_parentheses
VersionAdded: "<<next>>"
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/ConsistentParenthesesStyle

RSpec/FactoryBot/CreateList:
Description: Checks for create_list usage.
Enabled: true
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/cops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
=== Department xref:cops_rspec_factorybot.adoc[RSpec/FactoryBot]

* xref:cops_rspec_factorybot.adoc#rspecfactorybot/attributedefinedstatically[RSpec/FactoryBot/AttributeDefinedStatically]
* xref:cops_rspec_factorybot.adoc#rspecfactorybot/consistentparenthesesstyle[RSpec/FactoryBot/ConsistentParenthesesStyle]
* xref:cops_rspec_factorybot.adoc#rspecfactorybot/createlist[RSpec/FactoryBot/CreateList]
* xref:cops_rspec_factorybot.adoc#rspecfactorybot/factoryclassname[RSpec/FactoryBot/FactoryClassName]
* xref:cops_rspec_factorybot.adoc#rspecfactorybot/syntaxmethods[RSpec/FactoryBot/SyntaxMethods]
Expand Down
61 changes: 61 additions & 0 deletions docs/modules/ROOT/pages/cops_rspec_factorybot.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,67 @@ count { 1 }

* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/AttributeDefinedStatically

== RSpec/FactoryBot/ConsistentParenthesesStyle

|===
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed

| Pending
| Yes
| Yes
| <<next>>
| -
|===

Use a consistent style for parentheses in factory bot calls.

=== Examples

[source,ruby]
----
# bad
create :user
build(:user)
create(:login)
create :login
----

==== `EnforcedStyle: require_parentheses` (default)

[source,ruby]
----
# good
create(:user)
create(:user)
create(:login)
build(:login)
----

==== `EnforcedStyle: omit_parentheses`

[source,ruby]
----
# good
create :user
build :user
create :login
create :login
----

=== Configurable attributes

|===
| Name | Default value | Configurable values

| EnforcedStyle
| `require_parentheses`
| `require_parentheses`, `omit_parentheses`
|===

=== References

* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/ConsistentParenthesesStyle

== RSpec/FactoryBot/CreateList

|===
Expand Down
99 changes: 99 additions & 0 deletions lib/rubocop/cop/rspec/factory_bot/consistent_parentheses_style.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# frozen_string_literal: true

module RuboCop
module Cop
module RSpec
module FactoryBot
# Use a consistent style for parentheses in factory bot calls.
#
# @example
#
# # bad
# create :user
# build(:user)
# create(:login)
# create :login
#
# @example `EnforcedStyle: require_parentheses` (default)
#
# # good
# create(:user)
# create(:user)
# create(:login)
# build(:login)
#
# @example `EnforcedStyle: omit_parentheses`
#
# # good
# create :user
# build :user
# create :login
# create :login
#
class ConsistentParenthesesStyle < Base
extend AutoCorrector
include ConfigurableEnforcedStyle
include RuboCop::RSpec::FactoryBot::Language
include RuboCop::Cop::Util

def self.autocorrect_incompatible_with
[Style::MethodCallWithArgsParentheses]
end

MSG_REQUIRE_PARENS = 'Prefer method call with parentheses'
MSG_OMIT_PARENS = 'Prefer method call without parentheses'

FACTORY_CALLS = RuboCop::RSpec::FactoryBot::Language::METHODS

# @!method factory_call(node)
def_node_matcher :factory_call, <<-PATTERN
(send
${#factory_bot? nil?} %FACTORY_CALLS
$...)
PATTERN

def on_send(node)
return if nested_call?(node) # prevent from nested matching

factory_call(node) do
if node.parenthesized?
process_with_parentheses(node)
else
process_without_parentheses(node)
end
end
end

def process_with_parentheses(node)
return unless style == :omit_parentheses

add_offense(node.loc.selector,
message: MSG_OMIT_PARENS) do |corrector|
remove_parentheses(corrector, node)
end
end

def process_without_parentheses(node)
return unless style == :require_parentheses

add_offense(node.loc.selector,
message: MSG_REQUIRE_PARENS) do |corrector|
add_parentheses(node, corrector)
end
end

def nested_call?(node)
node.parent&.send_type?
end

private

def remove_parentheses(corrector, node)
corrector.replace(node.location.begin, ' ')
corrector.remove(node.location.end)
end
end
end
end
end
end
20 changes: 1 addition & 19 deletions lib/rubocop/cop/rspec/factory_bot/syntax_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,25 +54,7 @@ class SyntaxMethods < Base

MSG = 'Use `%<method>s` from `FactoryBot::Syntax::Methods`.'

RESTRICT_ON_SEND = %i[
attributes_for
attributes_for_list
attributes_for_pair
build
build_list
build_pair
build_stubbed
build_stubbed_list
build_stubbed_pair
create
create_list
create_pair
generate
generate_list
null
null_list
null_pair
].to_set.freeze
RESTRICT_ON_SEND = RuboCop::RSpec::FactoryBot::Language::METHODS

def on_send(node)
return unless factory_bot?(node.receiver)
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/cop/rspec_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require_relative 'rspec/capybara/visibility_matcher'

require_relative 'rspec/factory_bot/attribute_defined_statically'
require_relative 'rspec/factory_bot/consistent_parentheses_style'
require_relative 'rspec/factory_bot/create_list'
require_relative 'rspec/factory_bot/factory_class_name'
require_relative 'rspec/factory_bot/syntax_methods'
Expand Down
20 changes: 20 additions & 0 deletions lib/rubocop/rspec/factory_bot/language.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,26 @@ module FactoryBot
module Language
extend RuboCop::NodePattern::Macros

METHODS = %i[
attributes_for
attributes_for_list
attributes_for_pair
build
build_list
build_pair
build_stubbed
build_stubbed_list
build_stubbed_pair
create
create_list
create_pair
generate
generate_list
null
null_list
null_pair
].to_set.freeze

# @!method factory_bot?(node)
def_node_matcher :factory_bot?, <<~PATTERN
(const {nil? cbase} {:FactoryGirl :FactoryBot})
Expand Down
Loading

0 comments on commit 993cecf

Please sign in to comment.