rubocop-rspec 2.13.2 → 2.14.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 +4 -4
- data/CHANGELOG.md +402 -383
- data/CODE_OF_CONDUCT.md +4 -4
- data/MIT-LICENSE.md +1 -2
- data/config/default.yml +65 -4
- data/lib/rubocop/cop/rspec/capybara/negation_matcher.rb +106 -0
- data/lib/rubocop/cop/rspec/capybara/specific_actions.rb +85 -0
- data/lib/rubocop/cop/rspec/capybara/specific_matcher.rb +5 -82
- data/lib/rubocop/cop/rspec/change_by_zero.rb +1 -1
- data/lib/rubocop/cop/rspec/context_wording.rb +4 -2
- data/lib/rubocop/cop/rspec/example_wording.rb +32 -0
- data/lib/rubocop/cop/rspec/factory_bot/consistent_parentheses_style.rb +99 -0
- data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +2 -2
- data/lib/rubocop/cop/rspec/factory_bot/syntax_methods.rb +1 -19
- data/lib/rubocop/cop/rspec/implicit_subject.rb +86 -19
- data/lib/rubocop/cop/rspec/let_before_examples.rb +15 -1
- data/lib/rubocop/cop/rspec/mixin/capybara_help.rb +80 -0
- data/lib/rubocop/cop/rspec/mixin/css_selector.rb +48 -1
- data/lib/rubocop/cop/rspec/mixin/skip_or_pending.rb +23 -0
- data/lib/rubocop/cop/rspec/no_expectation_example.rb +42 -9
- data/lib/rubocop/cop/rspec/pending.rb +2 -11
- data/lib/rubocop/cop/rspec/rails/inferred_spec_type.rb +135 -0
- data/lib/rubocop/cop/rspec/repeated_include_example.rb +1 -1
- data/lib/rubocop/cop/rspec/sort_metadata.rb +102 -0
- data/lib/rubocop/cop/rspec/subject_declaration.rb +1 -1
- data/lib/rubocop/cop/rspec_cops.rb +5 -0
- data/lib/rubocop/rspec/factory_bot/language.rb +20 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- data/lib/rubocop-rspec.rb +2 -0
- metadata +9 -2
data/CODE_OF_CONDUCT.md
CHANGED
@@ -9,9 +9,9 @@ to the RuboCop community. It applies to all "collaborative spaces", which are
|
|
9
9
|
defined as community communications channels (such as mailing lists, submitted
|
10
10
|
patches, commit comments, etc.).
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
- Participants will be tolerant of opposing views.
|
13
|
+
- Participants must ensure that their language and actions are free of personal
|
14
14
|
attacks and disparaging personal remarks.
|
15
|
-
|
15
|
+
- When interpreting the words and actions of others, participants should always
|
16
16
|
assume good intentions.
|
17
|
-
|
17
|
+
- Behaviour which can be reasonably considered harassment will not be tolerated.
|
data/MIT-LICENSE.md
CHANGED
data/config/default.yml
CHANGED
@@ -194,7 +194,7 @@ RSpec/ChangeByZero:
|
|
194
194
|
Description: Prefer negated matchers over `to change.by(0)`.
|
195
195
|
Enabled: pending
|
196
196
|
VersionAdded: '2.11'
|
197
|
-
VersionChanged: '2.
|
197
|
+
VersionChanged: '2.14'
|
198
198
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ChangeByZero
|
199
199
|
NegatedMatcher: ~
|
200
200
|
|
@@ -383,8 +383,10 @@ RSpec/ExampleWording:
|
|
383
383
|
have: has
|
384
384
|
HAVE: HAS
|
385
385
|
IgnoredWords: []
|
386
|
+
DisallowedExamples:
|
387
|
+
- works
|
386
388
|
VersionAdded: '1.0'
|
387
|
-
VersionChanged: '
|
389
|
+
VersionChanged: '2.13'
|
388
390
|
StyleGuide: https://rspec.rubystyle.guide/#should-in-example-docstrings
|
389
391
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ExampleWording
|
390
392
|
|
@@ -492,8 +494,9 @@ RSpec/ImplicitSubject:
|
|
492
494
|
- single_line_only
|
493
495
|
- single_statement_only
|
494
496
|
- disallow
|
497
|
+
- require_implicit
|
495
498
|
VersionAdded: '1.29'
|
496
|
-
VersionChanged: '
|
499
|
+
VersionChanged: '2.13'
|
497
500
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitSubject
|
498
501
|
|
499
502
|
RSpec/InstanceSpy:
|
@@ -640,7 +643,11 @@ RSpec/NoExpectationExample:
|
|
640
643
|
Enabled: pending
|
641
644
|
Safe: false
|
642
645
|
VersionAdded: '2.13'
|
646
|
+
VersionChanged: '2.14'
|
643
647
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NoExpectationExample
|
648
|
+
AllowedPatterns:
|
649
|
+
- "^expect_"
|
650
|
+
- "^assert_"
|
644
651
|
|
645
652
|
RSpec/NotToNot:
|
646
653
|
Description: Checks for consistent method usage for negating expectations.
|
@@ -763,6 +770,12 @@ RSpec/SingleArgumentMessageChain:
|
|
763
770
|
VersionChanged: '1.10'
|
764
771
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SingleArgumentMessageChain
|
765
772
|
|
773
|
+
RSpec/SortMetadata:
|
774
|
+
Description: Sort RSpec metadata alphabetically.
|
775
|
+
Enabled: pending
|
776
|
+
VersionAdded: '2.14'
|
777
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SortMetadata
|
778
|
+
|
766
779
|
RSpec/StubbedMock:
|
767
780
|
Description: Checks that message expectations do not have a configured response.
|
768
781
|
Enabled: true
|
@@ -807,7 +820,6 @@ RSpec/VariableName:
|
|
807
820
|
- snake_case
|
808
821
|
- camelCase
|
809
822
|
AllowedPatterns: []
|
810
|
-
IgnoredPatterns: []
|
811
823
|
VersionAdded: '1.40'
|
812
824
|
VersionChanged: '2.13'
|
813
825
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VariableName
|
@@ -866,6 +878,22 @@ RSpec/Capybara/FeatureMethods:
|
|
866
878
|
VersionChanged: '2.0'
|
867
879
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Capybara/FeatureMethods
|
868
880
|
|
881
|
+
RSpec/Capybara/NegationMatcher:
|
882
|
+
Description: Enforces use of `have_no_*` or `not_to` for negated expectations.
|
883
|
+
Enabled: pending
|
884
|
+
VersionAdded: '2.14'
|
885
|
+
EnforcedStyle: not_to
|
886
|
+
SupportedStyles:
|
887
|
+
- have_no
|
888
|
+
- not_to
|
889
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Capybara/NegationMatcher
|
890
|
+
|
891
|
+
RSpec/Capybara/SpecificActions:
|
892
|
+
Description: Checks for there is a more specific actions offered by Capybara.
|
893
|
+
Enabled: pending
|
894
|
+
VersionAdded: '2.14'
|
895
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Capybara/SpecificActions
|
896
|
+
|
869
897
|
RSpec/Capybara/SpecificFinders:
|
870
898
|
Description: Checks if there is a more specific finder offered by Capybara.
|
871
899
|
Enabled: pending
|
@@ -901,6 +929,16 @@ RSpec/FactoryBot/AttributeDefinedStatically:
|
|
901
929
|
VersionChanged: '2.0'
|
902
930
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/AttributeDefinedStatically
|
903
931
|
|
932
|
+
RSpec/FactoryBot/ConsistentParenthesesStyle:
|
933
|
+
Description: Use a consistent style for parentheses in factory bot calls.
|
934
|
+
Enabled: pending
|
935
|
+
EnforcedStyle: require_parentheses
|
936
|
+
SupportedStyles:
|
937
|
+
- require_parentheses
|
938
|
+
- omit_parentheses
|
939
|
+
VersionAdded: '2.14'
|
940
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/ConsistentParenthesesStyle
|
941
|
+
|
904
942
|
RSpec/FactoryBot/CreateList:
|
905
943
|
Description: Checks for create_list usage.
|
906
944
|
Enabled: true
|
@@ -954,6 +992,29 @@ RSpec/Rails/HaveHttpStatus:
|
|
954
992
|
VersionAdded: '2.12'
|
955
993
|
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/HaveHttpStatus
|
956
994
|
|
995
|
+
RSpec/Rails/InferredSpecType:
|
996
|
+
Description: Identifies redundant spec type.
|
997
|
+
Enabled: pending
|
998
|
+
Safe: false
|
999
|
+
VersionAdded: '2.14'
|
1000
|
+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/InferredSpecType
|
1001
|
+
Inferences:
|
1002
|
+
channels: channel
|
1003
|
+
controllers: controller
|
1004
|
+
features: feature
|
1005
|
+
generator: generator
|
1006
|
+
helpers: helper
|
1007
|
+
jobs: job
|
1008
|
+
mailboxes: mailbox
|
1009
|
+
mailers: mailer
|
1010
|
+
models: model
|
1011
|
+
requests: request
|
1012
|
+
integration: request
|
1013
|
+
api: request
|
1014
|
+
routing: routing
|
1015
|
+
system: system
|
1016
|
+
views: view
|
1017
|
+
|
957
1018
|
RSpec/Rails/HttpStatus:
|
958
1019
|
Description: Enforces use of symbolic or numeric value to describe HTTP status.
|
959
1020
|
Enabled: true
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
module Capybara
|
7
|
+
# Enforces use of `have_no_*` or `not_to` for negated expectations.
|
8
|
+
#
|
9
|
+
# @example EnforcedStyle: not_to (default)
|
10
|
+
# # bad
|
11
|
+
# expect(page).to have_no_selector
|
12
|
+
# expect(page).to have_no_css('a')
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# expect(page).not_to have_selector
|
16
|
+
# expect(page).not_to have_css('a')
|
17
|
+
#
|
18
|
+
# @example EnforcedStyle: have_no
|
19
|
+
# # bad
|
20
|
+
# expect(page).not_to have_selector
|
21
|
+
# expect(page).not_to have_css('a')
|
22
|
+
#
|
23
|
+
# # good
|
24
|
+
# expect(page).to have_no_selector
|
25
|
+
# expect(page).to have_no_css('a')
|
26
|
+
#
|
27
|
+
class NegationMatcher < Base
|
28
|
+
extend AutoCorrector
|
29
|
+
include ConfigurableEnforcedStyle
|
30
|
+
|
31
|
+
MSG = 'Use `expect(...).%<runner>s %<matcher>s`.'
|
32
|
+
CAPYBARA_MATCHERS = %w[
|
33
|
+
selector css xpath text title current_path link button
|
34
|
+
field checked_field unchecked_field select table
|
35
|
+
sibling ancestor
|
36
|
+
].freeze
|
37
|
+
POSITIVE_MATCHERS =
|
38
|
+
Set.new(CAPYBARA_MATCHERS) { |element| :"have_#{element}" }.freeze
|
39
|
+
NEGATIVE_MATCHERS =
|
40
|
+
Set.new(CAPYBARA_MATCHERS) { |element| :"have_no_#{element}" }
|
41
|
+
.freeze
|
42
|
+
RESTRICT_ON_SEND = (POSITIVE_MATCHERS + NEGATIVE_MATCHERS).freeze
|
43
|
+
|
44
|
+
# @!method not_to?(node)
|
45
|
+
def_node_matcher :not_to?, <<~PATTERN
|
46
|
+
(send ... :not_to
|
47
|
+
(send nil? %POSITIVE_MATCHERS ...))
|
48
|
+
PATTERN
|
49
|
+
|
50
|
+
# @!method have_no?(node)
|
51
|
+
def_node_matcher :have_no?, <<~PATTERN
|
52
|
+
(send ... :to
|
53
|
+
(send nil? %NEGATIVE_MATCHERS ...))
|
54
|
+
PATTERN
|
55
|
+
|
56
|
+
def on_send(node)
|
57
|
+
return unless offense?(node.parent)
|
58
|
+
|
59
|
+
matcher = node.method_name.to_s
|
60
|
+
add_offense(offense_range(node),
|
61
|
+
message: message(matcher)) do |corrector|
|
62
|
+
corrector.replace(node.parent.loc.selector, replaced_runner)
|
63
|
+
corrector.replace(node.loc.selector,
|
64
|
+
replaced_matcher(matcher))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def offense?(node)
|
71
|
+
(style == :have_no && not_to?(node)) ||
|
72
|
+
(style == :not_to && have_no?(node))
|
73
|
+
end
|
74
|
+
|
75
|
+
def offense_range(node)
|
76
|
+
node.parent.loc.selector.with(end_pos: node.loc.selector.end_pos)
|
77
|
+
end
|
78
|
+
|
79
|
+
def message(matcher)
|
80
|
+
format(MSG,
|
81
|
+
runner: replaced_runner,
|
82
|
+
matcher: replaced_matcher(matcher))
|
83
|
+
end
|
84
|
+
|
85
|
+
def replaced_runner
|
86
|
+
case style
|
87
|
+
when :have_no
|
88
|
+
'to'
|
89
|
+
when :not_to
|
90
|
+
'not_to'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def replaced_matcher(matcher)
|
95
|
+
case style
|
96
|
+
when :have_no
|
97
|
+
matcher.sub('have_', 'have_no_')
|
98
|
+
when :not_to
|
99
|
+
matcher.sub('have_no_', 'have_')
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
module Capybara
|
7
|
+
# Checks for there is a more specific actions offered by Capybara.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# # bad
|
12
|
+
# find('a').click
|
13
|
+
# find('button.cls').click
|
14
|
+
# find('a', exact_text: 'foo').click
|
15
|
+
# find('div button').click
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# click_link
|
19
|
+
# click_button(class: 'cls')
|
20
|
+
# click_link(exact_text: 'foo')
|
21
|
+
# find('div').click_button
|
22
|
+
#
|
23
|
+
class SpecificActions < Base
|
24
|
+
MSG = "Prefer `%<good_action>s` over `find('%<selector>s').click`."
|
25
|
+
RESTRICT_ON_SEND = %i[click].freeze
|
26
|
+
SPECIFIC_ACTION = {
|
27
|
+
'button' => 'button',
|
28
|
+
'a' => 'link'
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
# @!method click_on_selector(node)
|
32
|
+
def_node_matcher :click_on_selector, <<-PATTERN
|
33
|
+
(send _ :find (str $_) ...)
|
34
|
+
PATTERN
|
35
|
+
|
36
|
+
def on_send(node)
|
37
|
+
click_on_selector(node.receiver) do |arg|
|
38
|
+
next unless supported_selector?(arg)
|
39
|
+
# Always check the last selector in the case of multiple selectors
|
40
|
+
# separated by whitespace.
|
41
|
+
# because the `.click` is executed on the element to
|
42
|
+
# which the last selector points.
|
43
|
+
next unless (selector = last_selector(arg))
|
44
|
+
next unless (action = specific_action(selector))
|
45
|
+
next unless CapybaraHelp.specific_option?(node.receiver, arg,
|
46
|
+
action)
|
47
|
+
next unless CapybaraHelp.specific_pseudo_classes?(arg)
|
48
|
+
|
49
|
+
range = offense_range(node, node.receiver)
|
50
|
+
add_offense(range, message: message(action, selector))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def specific_action(selector)
|
57
|
+
SPECIFIC_ACTION[last_selector(selector)]
|
58
|
+
end
|
59
|
+
|
60
|
+
def supported_selector?(selector)
|
61
|
+
!selector.match?(/[>,+~]/)
|
62
|
+
end
|
63
|
+
|
64
|
+
def last_selector(arg)
|
65
|
+
arg.split.last[/^\w+/, 0]
|
66
|
+
end
|
67
|
+
|
68
|
+
def offense_range(node, receiver)
|
69
|
+
receiver.loc.selector.with(end_pos: node.loc.expression.end_pos)
|
70
|
+
end
|
71
|
+
|
72
|
+
def message(action, selector)
|
73
|
+
format(MSG,
|
74
|
+
good_action: good_action(action),
|
75
|
+
selector: selector)
|
76
|
+
end
|
77
|
+
|
78
|
+
def good_action(action)
|
79
|
+
"click_#{action}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -26,7 +26,9 @@ module RuboCop
|
|
26
26
|
# expect(page).to have_select
|
27
27
|
# expect(page).to have_field('foo')
|
28
28
|
#
|
29
|
-
class SpecificMatcher < Base
|
29
|
+
class SpecificMatcher < Base
|
30
|
+
include CapybaraHelp
|
31
|
+
|
30
32
|
MSG = 'Prefer `%<good_matcher>s` over `%<bad_matcher>s`.'
|
31
33
|
RESTRICT_ON_SEND = %i[have_selector have_no_selector have_css
|
32
34
|
have_no_css].freeze
|
@@ -37,51 +39,18 @@ module RuboCop
|
|
37
39
|
'select' => 'select',
|
38
40
|
'input' => 'field'
|
39
41
|
}.freeze
|
40
|
-
SPECIFIC_MATCHER_OPTIONS = {
|
41
|
-
'button' => (
|
42
|
-
CssSelector::COMMON_OPTIONS + %w[disabled name value title type]
|
43
|
-
).freeze,
|
44
|
-
'link' => (
|
45
|
-
CssSelector::COMMON_OPTIONS + %w[href alt title download]
|
46
|
-
).freeze,
|
47
|
-
'table' => (
|
48
|
-
CssSelector::COMMON_OPTIONS + %w[
|
49
|
-
caption with_cols cols with_rows rows
|
50
|
-
]
|
51
|
-
).freeze,
|
52
|
-
'select' => (
|
53
|
-
CssSelector::COMMON_OPTIONS + %w[
|
54
|
-
disabled name placeholder options enabled_options
|
55
|
-
disabled_options selected with_selected multiple with_options
|
56
|
-
]
|
57
|
-
).freeze,
|
58
|
-
'field' => (
|
59
|
-
CssSelector::COMMON_OPTIONS + %w[
|
60
|
-
checked unchecked disabled valid name placeholder
|
61
|
-
validation_message readonly with type multiple
|
62
|
-
]
|
63
|
-
).freeze
|
64
|
-
}.freeze
|
65
|
-
SPECIFIC_MATCHER_PSEUDO_CLASSES = %w[
|
66
|
-
not() disabled enabled checked unchecked
|
67
|
-
].freeze
|
68
42
|
|
69
43
|
# @!method first_argument(node)
|
70
44
|
def_node_matcher :first_argument, <<-PATTERN
|
71
45
|
(send nil? _ (str $_) ... )
|
72
46
|
PATTERN
|
73
47
|
|
74
|
-
# @!method option?(node)
|
75
|
-
def_node_search :option?, <<-PATTERN
|
76
|
-
(pair (sym %) _)
|
77
|
-
PATTERN
|
78
|
-
|
79
48
|
def on_send(node)
|
80
49
|
first_argument(node) do |arg|
|
81
50
|
next unless (matcher = specific_matcher(arg))
|
82
51
|
next if CssSelector.multiple_selectors?(arg)
|
83
|
-
next unless
|
84
|
-
next unless
|
52
|
+
next unless specific_option?(node, arg, matcher)
|
53
|
+
next unless specific_pseudo_classes?(arg)
|
85
54
|
|
86
55
|
add_offense(node, message: message(node, matcher))
|
87
56
|
end
|
@@ -94,52 +63,6 @@ module RuboCop
|
|
94
63
|
SPECIFIC_MATCHER[splitted_arg]
|
95
64
|
end
|
96
65
|
|
97
|
-
def specific_matcher_option?(node, arg, matcher)
|
98
|
-
attrs = CssSelector.attributes(arg).keys
|
99
|
-
return true if attrs.empty?
|
100
|
-
return false unless replaceable_matcher?(node, matcher, attrs)
|
101
|
-
|
102
|
-
attrs.all? do |attr|
|
103
|
-
SPECIFIC_MATCHER_OPTIONS.fetch(matcher, []).include?(attr)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def specific_matcher_pseudo_classes?(arg)
|
108
|
-
CssSelector.pseudo_classes(arg).all? do |pseudo_class|
|
109
|
-
replaceable_pseudo_class?(pseudo_class, arg)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def replaceable_pseudo_class?(pseudo_class, arg)
|
114
|
-
unless SPECIFIC_MATCHER_PSEUDO_CLASSES.include?(pseudo_class)
|
115
|
-
return false
|
116
|
-
end
|
117
|
-
|
118
|
-
case pseudo_class
|
119
|
-
when 'not()' then replaceable_pseudo_class_not?(arg)
|
120
|
-
else true
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
def replaceable_pseudo_class_not?(arg)
|
125
|
-
arg.scan(/not\(.*?\)/).all? do |not_arg|
|
126
|
-
CssSelector.attributes(not_arg).values.all? do |v|
|
127
|
-
v.is_a?(TrueClass) || v.is_a?(FalseClass)
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
def replaceable_matcher?(node, matcher, attrs)
|
133
|
-
case matcher
|
134
|
-
when 'link' then replaceable_to_have_link?(node, attrs)
|
135
|
-
else true
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
def replaceable_to_have_link?(node, attrs)
|
140
|
-
option?(node, :href) || attrs.include?('href')
|
141
|
-
end
|
142
|
-
|
143
66
|
def message(node, matcher)
|
144
67
|
format(MSG,
|
145
68
|
good_matcher: good_matcher(node, matcher),
|
@@ -61,7 +61,7 @@ module RuboCop
|
|
61
61
|
extend AutoCorrector
|
62
62
|
MSG = 'Prefer `not_to change` over `to change.by(0)`.'
|
63
63
|
MSG_COMPOUND = 'Prefer %<preferred>s with compound expectations ' \
|
64
|
-
|
64
|
+
'over `change.by(0)`.'
|
65
65
|
RESTRICT_ON_SEND = %i[change].freeze
|
66
66
|
|
67
67
|
# @!method expect_change_with_arguments(node)
|
@@ -43,7 +43,7 @@ module RuboCop
|
|
43
43
|
# # .rubocop.yml
|
44
44
|
# # RSpec/ContextWording:
|
45
45
|
# # AllowedPatterns:
|
46
|
-
# # -
|
46
|
+
# # - とき$
|
47
47
|
#
|
48
48
|
# @example
|
49
49
|
# # bad
|
@@ -92,7 +92,9 @@ module RuboCop
|
|
92
92
|
end
|
93
93
|
|
94
94
|
def expect_patterns
|
95
|
-
inspected = allowed_patterns.map
|
95
|
+
inspected = allowed_patterns.map do |pattern|
|
96
|
+
pattern.inspect.gsub(/\A"|"\z/, '/')
|
97
|
+
end
|
96
98
|
return inspected.first if inspected.size == 1
|
97
99
|
|
98
100
|
inspected << "or #{inspected.pop}"
|
@@ -6,12 +6,17 @@ module RuboCop
|
|
6
6
|
# Checks for common mistakes in example descriptions.
|
7
7
|
#
|
8
8
|
# This cop will correct docstrings that begin with 'should' and 'it'.
|
9
|
+
# This cop will also look for insufficient examples and call them out.
|
9
10
|
#
|
10
11
|
# @see http://betterspecs.org/#should
|
11
12
|
#
|
12
13
|
# The autocorrect is experimental - use with care! It can be configured
|
13
14
|
# with CustomTransform (e.g. have => has) and IgnoredWords (e.g. only).
|
14
15
|
#
|
16
|
+
# Use the DisallowedExamples setting to prevent unclear or insufficient
|
17
|
+
# descriptions. Please note that this config will not be treated as
|
18
|
+
# case sensitive.
|
19
|
+
#
|
15
20
|
# @example
|
16
21
|
# # bad
|
17
22
|
# it 'should find nothing' do
|
@@ -30,11 +35,21 @@ module RuboCop
|
|
30
35
|
# it 'does things' do
|
31
36
|
# end
|
32
37
|
#
|
38
|
+
# @example `DisallowedExamples: ['works']` (default)
|
39
|
+
# # bad
|
40
|
+
# it 'works' do
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# # good
|
44
|
+
# it 'marks the task as done' do
|
45
|
+
# end
|
33
46
|
class ExampleWording < Base
|
34
47
|
extend AutoCorrector
|
35
48
|
|
36
49
|
MSG_SHOULD = 'Do not use should when describing your tests.'
|
37
50
|
MSG_IT = "Do not repeat 'it' when describing your tests."
|
51
|
+
MSG_INSUFFICIENT_DESCRIPTION = 'Your example description is ' \
|
52
|
+
'insufficient.'
|
38
53
|
|
39
54
|
SHOULD_PREFIX = /\Ashould(?:n't)?\b/i.freeze
|
40
55
|
IT_PREFIX = /\Ait /i.freeze
|
@@ -53,12 +68,20 @@ module RuboCop
|
|
53
68
|
add_wording_offense(description_node, MSG_SHOULD)
|
54
69
|
elsif message.match?(IT_PREFIX)
|
55
70
|
add_wording_offense(description_node, MSG_IT)
|
71
|
+
else
|
72
|
+
check_and_handle_insufficient_examples(description_node)
|
56
73
|
end
|
57
74
|
end
|
58
75
|
end
|
59
76
|
|
60
77
|
private
|
61
78
|
|
79
|
+
def check_and_handle_insufficient_examples(description)
|
80
|
+
if insufficient_examples.include?(preprocess(text(description)))
|
81
|
+
add_wording_offense(description, MSG_INSUFFICIENT_DESCRIPTION)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
62
85
|
def add_wording_offense(node, message)
|
63
86
|
docstring = docstring(node)
|
64
87
|
|
@@ -113,6 +136,15 @@ module RuboCop
|
|
113
136
|
def ignored_words
|
114
137
|
cop_config.fetch('IgnoredWords', [])
|
115
138
|
end
|
139
|
+
|
140
|
+
def insufficient_examples
|
141
|
+
examples = cop_config.fetch('DisallowedExamples', [])
|
142
|
+
examples.map! { |example| preprocess(example) }
|
143
|
+
end
|
144
|
+
|
145
|
+
def preprocess(message)
|
146
|
+
message.strip.squeeze(' ').downcase
|
147
|
+
end
|
116
148
|
end
|
117
149
|
end
|
118
150
|
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
module FactoryBot
|
7
|
+
# Use a consistent style for parentheses in factory bot calls.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# # bad
|
12
|
+
# create :user
|
13
|
+
# build(:user)
|
14
|
+
# create(:login)
|
15
|
+
# create :login
|
16
|
+
#
|
17
|
+
# @example `EnforcedStyle: require_parentheses` (default)
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# create(:user)
|
21
|
+
# create(:user)
|
22
|
+
# create(:login)
|
23
|
+
# build(:login)
|
24
|
+
#
|
25
|
+
# @example `EnforcedStyle: omit_parentheses`
|
26
|
+
#
|
27
|
+
# # good
|
28
|
+
# create :user
|
29
|
+
# build :user
|
30
|
+
# create :login
|
31
|
+
# create :login
|
32
|
+
#
|
33
|
+
class ConsistentParenthesesStyle < Base
|
34
|
+
extend AutoCorrector
|
35
|
+
include ConfigurableEnforcedStyle
|
36
|
+
include RuboCop::RSpec::FactoryBot::Language
|
37
|
+
include RuboCop::Cop::Util
|
38
|
+
|
39
|
+
def self.autocorrect_incompatible_with
|
40
|
+
[Style::MethodCallWithArgsParentheses]
|
41
|
+
end
|
42
|
+
|
43
|
+
MSG_REQUIRE_PARENS = 'Prefer method call with parentheses'
|
44
|
+
MSG_OMIT_PARENS = 'Prefer method call without parentheses'
|
45
|
+
|
46
|
+
FACTORY_CALLS = RuboCop::RSpec::FactoryBot::Language::METHODS
|
47
|
+
|
48
|
+
# @!method factory_call(node)
|
49
|
+
def_node_matcher :factory_call, <<-PATTERN
|
50
|
+
(send
|
51
|
+
${#factory_bot? nil?} %FACTORY_CALLS
|
52
|
+
$...)
|
53
|
+
PATTERN
|
54
|
+
|
55
|
+
def on_send(node)
|
56
|
+
return if nested_call?(node) # prevent from nested matching
|
57
|
+
|
58
|
+
factory_call(node) do
|
59
|
+
if node.parenthesized?
|
60
|
+
process_with_parentheses(node)
|
61
|
+
else
|
62
|
+
process_without_parentheses(node)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def process_with_parentheses(node)
|
68
|
+
return unless style == :omit_parentheses
|
69
|
+
|
70
|
+
add_offense(node.loc.selector,
|
71
|
+
message: MSG_OMIT_PARENS) do |corrector|
|
72
|
+
remove_parentheses(corrector, node)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def process_without_parentheses(node)
|
77
|
+
return unless style == :require_parentheses
|
78
|
+
|
79
|
+
add_offense(node.loc.selector,
|
80
|
+
message: MSG_REQUIRE_PARENS) do |corrector|
|
81
|
+
add_parentheses(node, corrector)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def nested_call?(node)
|
86
|
+
node.parent&.send_type?
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def remove_parentheses(corrector, node)
|
92
|
+
corrector.replace(node.location.begin, ' ')
|
93
|
+
corrector.remove(node.location.end)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -238,8 +238,8 @@ module RuboCop
|
|
238
238
|
indent = ' ' * node.body.loc.column
|
239
239
|
indent_end = ' ' * node.parent.loc.column
|
240
240
|
" do #{node.arguments.source}\n" \
|
241
|
-
|
242
|
-
|
241
|
+
"#{indent}#{node.body.source}\n" \
|
242
|
+
"#{indent_end}end"
|
243
243
|
end
|
244
244
|
|
245
245
|
def format_singleline_block(node)
|