rubocop-rspec 1.37.1 → 1.41.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +53 -0
  3. data/CODE_OF_CONDUCT.md +17 -0
  4. data/README.md +2 -62
  5. data/config/default.yml +155 -15
  6. data/lib/rubocop-rspec.rb +3 -1
  7. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +69 -0
  8. data/lib/rubocop/cop/rspec/context_wording.rb +5 -1
  9. data/lib/rubocop/cop/rspec/cop.rb +9 -29
  10. data/lib/rubocop/cop/rspec/describe_class.rb +20 -5
  11. data/lib/rubocop/cop/rspec/describe_method.rb +0 -1
  12. data/lib/rubocop/cop/rspec/empty_hook.rb +50 -0
  13. data/lib/rubocop/cop/rspec/expect_actual.rb +27 -2
  14. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +5 -2
  15. data/lib/rubocop/cop/rspec/file_path.rb +32 -4
  16. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +3 -21
  17. data/lib/rubocop/cop/rspec/instance_variable.rb +16 -11
  18. data/lib/rubocop/cop/rspec/leading_subject.rb +3 -10
  19. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -4
  20. data/lib/rubocop/cop/rspec/let_before_examples.rb +3 -21
  21. data/lib/rubocop/cop/rspec/let_setup.rb +15 -3
  22. data/lib/rubocop/cop/rspec/named_subject.rb +6 -6
  23. data/lib/rubocop/cop/rspec/nested_groups.rb +9 -10
  24. data/lib/rubocop/cop/rspec/predicate_matcher.rb +2 -2
  25. data/lib/rubocop/cop/rspec/rails/http_status.rb +2 -0
  26. data/lib/rubocop/cop/rspec/repeated_description.rb +17 -2
  27. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +97 -0
  28. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +96 -0
  29. data/lib/rubocop/cop/rspec/return_from_stub.rb +3 -2
  30. data/lib/rubocop/cop/rspec/scattered_let.rb +13 -0
  31. data/lib/rubocop/cop/rspec/scattered_setup.rb +27 -9
  32. data/lib/rubocop/cop/rspec/shared_examples.rb +1 -0
  33. data/lib/rubocop/cop/rspec/subject_stub.rb +23 -51
  34. data/lib/rubocop/cop/rspec/variable_definition.rb +56 -0
  35. data/lib/rubocop/cop/rspec/variable_name.rb +47 -0
  36. data/lib/rubocop/cop/rspec_cops.rb +7 -1
  37. data/lib/rubocop/rspec/corrector/move_node.rb +52 -0
  38. data/lib/rubocop/rspec/description_extractor.rb +2 -6
  39. data/lib/rubocop/rspec/example.rb +1 -1
  40. data/lib/rubocop/rspec/example_group.rb +21 -49
  41. data/lib/rubocop/rspec/factory_bot.rb +7 -1
  42. data/lib/rubocop/rspec/hook.rb +44 -11
  43. data/lib/rubocop/rspec/language.rb +20 -0
  44. data/lib/rubocop/rspec/language/node_pattern.rb +5 -1
  45. data/lib/rubocop/rspec/top_level_group.rb +44 -0
  46. data/lib/rubocop/rspec/variable.rb +16 -0
  47. data/lib/rubocop/rspec/version.rb +1 -1
  48. metadata +20 -11
  49. data/lib/rubocop/rspec/util.rb +0 -19
@@ -11,18 +11,20 @@ require_relative 'rubocop/rspec/inject'
11
11
  require_relative 'rubocop/rspec/node'
12
12
  require_relative 'rubocop/rspec/top_level_describe'
13
13
  require_relative 'rubocop/rspec/wording'
14
- require_relative 'rubocop/rspec/util'
15
14
  require_relative 'rubocop/rspec/language'
16
15
  require_relative 'rubocop/rspec/language/node_pattern'
16
+ require_relative 'rubocop/rspec/top_level_group'
17
17
  require_relative 'rubocop/rspec/concept'
18
18
  require_relative 'rubocop/rspec/example_group'
19
19
  require_relative 'rubocop/rspec/example'
20
20
  require_relative 'rubocop/rspec/hook'
21
+ require_relative 'rubocop/rspec/variable'
21
22
  require_relative 'rubocop/cop/rspec/cop'
22
23
  require_relative 'rubocop/rspec/align_let_brace'
23
24
  require_relative 'rubocop/rspec/factory_bot'
24
25
  require_relative 'rubocop/rspec/final_end_location'
25
26
  require_relative 'rubocop/rspec/blank_line_separation'
27
+ require_relative 'rubocop/rspec/corrector/move_node'
26
28
 
27
29
  RuboCop::RSpec::Inject.defaults!
28
30
 
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ module Capybara
7
+ # Checks for boolean visibility in capybara finders.
8
+ #
9
+ # Capybara lets you find elements that match a certain visibility using
10
+ # the `:visible` option. `:visible` accepts both boolean and symbols as
11
+ # values, however using booleans can have unwanted effects. `visible:
12
+ # false` does not find just invisible elements, but both visible and
13
+ # invisible elements. For expressiveness and clarity, use one of the
14
+ # symbol values, `:all`, `:hidden` or `:visible`.
15
+ # (https://www.rubydoc.info/gems/capybara/Capybara%2FNode%2FFinders:all)
16
+ #
17
+ # @example
18
+ #
19
+ # # bad
20
+ # expect(page).to have_selector('.foo', visible: false)
21
+ # expect(page).to have_css('.foo', visible: true)
22
+ # expect(page).to have_link('my link', visible: false)
23
+ #
24
+ # # good
25
+ # expect(page).to have_selector('.foo', visible: :visible)
26
+ # expect(page).to have_css('.foo', visible: :all)
27
+ # expect(page).to have_link('my link', visible: :hidden)
28
+ #
29
+ class VisibilityMatcher < Cop
30
+ MSG_FALSE = 'Use `:all` or `:hidden` instead of `false`.'
31
+ MSG_TRUE = 'Use `:visible` instead of `true`.'
32
+ CAPYBARA_MATCHER_METHODS = %i[
33
+ have_selector
34
+ have_css
35
+ have_xpath
36
+ have_link
37
+ have_button
38
+ have_field
39
+ have_select
40
+ have_table
41
+ have_checked_field
42
+ have_unchecked_field
43
+ have_text
44
+ have_content
45
+ ].freeze
46
+
47
+ def_node_matcher :visible_true?, <<~PATTERN
48
+ (send nil? #capybara_matcher? ... (hash <$(pair (sym :visible) true) ...>))
49
+ PATTERN
50
+
51
+ def_node_matcher :visible_false?, <<~PATTERN
52
+ (send nil? #capybara_matcher? ... (hash <$(pair (sym :visible) false) ...>))
53
+ PATTERN
54
+
55
+ def on_send(node)
56
+ visible_false?(node) { |arg| add_offense(arg, message: MSG_FALSE) }
57
+ visible_true?(node) { |arg| add_offense(arg, message: MSG_TRUE) }
58
+ end
59
+
60
+ private
61
+
62
+ def capybara_matcher?(method_name)
63
+ CAPYBARA_MATCHER_METHODS.include? method_name
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -5,7 +5,11 @@ module RuboCop
5
5
  module RSpec
6
6
  # Checks that `context` docstring starts with an allowed prefix.
7
7
  #
8
- # @see https://github.com/reachlocal/rspec-style-guide#context-descriptions
8
+ # The default list of prefixes is minimal. Users are encouraged to tailor
9
+ # the configuration to meet project needs. Other acceptable prefixes may
10
+ # include `if`, `unless`, `for`, `before`, `after`, or `during`.
11
+ #
12
+ # @see https://rspec.rubystyle.guide/#context-descriptions
9
13
  # @see http://www.betterspecs.org/#contexts
10
14
  #
11
15
  # @example `Prefixes` configuration
@@ -1,29 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RuboCop
4
- module Cop # rubocop:disable Style/Documentation
5
- WorkaroundCop = Cop.dup
6
-
7
- # Clone of the the normal RuboCop::Cop::Cop class so we can rewrite
8
- # the inherited method without breaking functionality
9
- class WorkaroundCop
10
- # Remove the Cop.inherited method to be a noop. Our RSpec::Cop
11
- # class will invoke the inherited hook instead
12
- class << self
13
- undef inherited
14
- def inherited(*) end
15
- end
16
-
17
- # Special case `Module#<` so that the rspec support rubocop exports
18
- # is compatible with our subclass
19
- def self.<(other)
20
- other.equal?(RuboCop::Cop::Cop) || super
21
- end
22
- end
23
- private_constant(:WorkaroundCop)
24
-
4
+ module Cop
25
5
  module RSpec
26
- # @abstract parent class to rspec cops
6
+ # @abstract parent class to RSpec cops
27
7
  #
28
8
  # The criteria for whether rubocop-rspec analyzes a certain ruby file
29
9
  # is configured via `AllCops/RSpec`. For example, if you want to
@@ -31,13 +11,13 @@ module RuboCop
31
11
  # then you could add this to your configuration:
32
12
  #
33
13
  # @example configuring analyzed paths
34
- #
35
- # AllCops:
36
- # RSpec:
37
- # Patterns:
38
- # - '_test.rb$'
39
- # - '(?:^|/)test/'
40
- class Cop < WorkaroundCop
14
+ # # .rubocop.yml
15
+ # # AllCops:
16
+ # # RSpec:
17
+ # # Patterns:
18
+ # # - '_test.rb$'
19
+ # # - '(?:^|/)test/'
20
+ class Cop < ::RuboCop::Cop::Cop
41
21
  include RuboCop::RSpec::Language
42
22
  include RuboCop::RSpec::Language::NodePattern
43
23
 
@@ -12,6 +12,11 @@ module RuboCop
12
12
  #
13
13
  # # good
14
14
  # describe TestedClass do
15
+ # subject { described_class }
16
+ # end
17
+ #
18
+ # describe 'TestedClass::VERSION' do
19
+ # subject { Object.const_get(self.class.description) }
15
20
  # end
16
21
  #
17
22
  # describe "A feature example", type: :feature do
@@ -38,18 +43,28 @@ module RuboCop
38
43
  def_node_matcher :rails_metadata?, <<-PATTERN
39
44
  (pair
40
45
  (sym :type)
41
- (sym {:request :feature :system :routing :view})
46
+ (sym {
47
+ :channel :controller :helper :job :mailer :model :request
48
+ :routing :view :feature :system :mailbox
49
+ }
50
+ )
42
51
  )
43
52
  PATTERN
44
53
 
45
- def_node_matcher :shared_group?, SharedGroups::ALL.block_pattern
46
-
47
- def on_top_level_describe(node, args)
54
+ def on_top_level_describe(node, (described_value, _))
48
55
  return if shared_group?(root_node)
49
56
  return if valid_describe?(node)
50
57
  return if describe_with_rails_metadata?(node)
58
+ return if string_constant_describe?(described_value)
59
+
60
+ add_offense(described_value)
61
+ end
62
+
63
+ private
51
64
 
52
- add_offense(args.first)
65
+ def string_constant_describe?(described_value)
66
+ described_value.str_type? &&
67
+ described_value.value =~ /^((::)?[A-Z]\w*)+$/
53
68
  end
54
69
  end
55
70
  end
@@ -18,7 +18,6 @@ module RuboCop
18
18
  # end
19
19
  class DescribeMethod < Cop
20
20
  include RuboCop::RSpec::TopLevelDescribe
21
- include RuboCop::RSpec::Util
22
21
 
23
22
  MSG = 'The second argument to describe should be the method '\
24
23
  "being tested. '#instance' or '.class'."
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks for empty before and after hooks.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # before {}
11
+ # after do; end
12
+ # before(:all) do
13
+ # end
14
+ # after(:all) { }
15
+ #
16
+ # # good
17
+ # before { create_users }
18
+ # after do
19
+ # cleanup_users
20
+ # end
21
+ # before(:all) do
22
+ # create_feed
23
+ # end
24
+ # after(:all) { cleanup_feed }
25
+ class EmptyHook < Cop
26
+ include RuboCop::Cop::RangeHelp
27
+
28
+ MSG = 'Empty hook detected.'
29
+
30
+ def_node_matcher :empty_hook?, <<~PATTERN
31
+ (block $#{Hooks::ALL.send_pattern} _ nil?)
32
+ PATTERN
33
+
34
+ def on_block(node)
35
+ empty_hook?(node) do |hook|
36
+ add_offense(hook)
37
+ end
38
+ end
39
+
40
+ def autocorrect(node)
41
+ lambda do |corrector|
42
+ block = node.parent
43
+ range = range_with_surrounding_space(range: block.loc.expression)
44
+ corrector.remove(range)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -41,11 +41,31 @@ module RuboCop
41
41
  regexp
42
42
  ].freeze
43
43
 
44
- def_node_matcher :expect_literal, '(send _ :expect $#literal?)'
44
+ SUPPORTED_MATCHERS = %i[eq eql equal be].freeze
45
+
46
+ def_node_matcher :expect_literal, <<~PATTERN
47
+ (send
48
+ (send nil? :expect $#literal?)
49
+ #{Runners::ALL.node_pattern_union}
50
+ {
51
+ (send (send nil? $:be) :== $_)
52
+ (send nil? $_ $_ ...)
53
+ }
54
+ )
55
+ PATTERN
45
56
 
46
57
  def on_send(node)
47
58
  expect_literal(node) do |argument|
48
- add_offense(argument)
59
+ add_offense(node, location: argument.source_range)
60
+ end
61
+ end
62
+
63
+ def autocorrect(node)
64
+ actual, matcher, expected = expect_literal(node)
65
+ lambda do |corrector|
66
+ return unless SUPPORTED_MATCHERS.include?(matcher)
67
+
68
+ swap(corrector, actual, expected)
49
69
  end
50
70
  end
51
71
 
@@ -65,6 +85,11 @@ module RuboCop
65
85
  COMPLEX_LITERALS.include?(node.type) &&
66
86
  node.each_child_node.all?(&method(:literal?))
67
87
  end
88
+
89
+ def swap(corrector, actual, expected)
90
+ corrector.replace(actual.source_range, expected.source)
91
+ corrector.replace(expected.source_range, actual.source)
92
+ end
68
93
  end
69
94
  end
70
95
  end
@@ -31,12 +31,15 @@ module RuboCop
31
31
  (send _ !#reserved_method? $...)
32
32
  PATTERN
33
33
 
34
- def_node_search :factory_attributes, <<-PATTERN
34
+ def_node_matcher :factory_attributes, <<-PATTERN
35
35
  (block (send _ #attribute_defining_method? ...) _ { (begin $...) $(send ...) } )
36
36
  PATTERN
37
37
 
38
38
  def on_block(node)
39
- factory_attributes(node).to_a.flatten.each do |attribute|
39
+ attributes = factory_attributes(node) || []
40
+ attributes = [attributes] unless attributes.is_a?(Array)
41
+
42
+ attributes.each do |attribute|
40
43
  next unless offensive_receiver?(attribute.receiver, node)
41
44
  next if proc?(attribute) || association?(attribute.first_argument)
42
45
 
@@ -3,10 +3,11 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
- # Checks that spec file paths are consistent with the test subject.
6
+ # Checks that spec file paths are consistent and well-formed.
7
7
  #
8
- # Checks the path of the spec file and enforces that it reflects the
9
- # described class/module and its optionally called out method.
8
+ # By default, this checks that spec file paths are consistent with the
9
+ # test subject and and enforces that it reflects the described
10
+ # class/module and its optionally called out method.
10
11
  #
11
12
  # With the configuration option `IgnoreMethods` the called out method will
12
13
  # be ignored when determining the enforced path.
@@ -15,6 +16,10 @@ module RuboCop
15
16
  # be specified that should not as usual be transformed from CamelCase to
16
17
  # snake_case (e.g. 'RuboCop' => 'rubocop' ).
17
18
  #
19
+ # With the configuration option `SpecSuffixOnly` test files will only
20
+ # be checked to ensure they end in '_spec.rb'. This option disables
21
+ # checking for consistency in the test subject or test methods.
22
+ #
18
23
  # @example
19
24
  # # bad
20
25
  # whatever_spec.rb # describe MyClass
@@ -41,6 +46,16 @@ module RuboCop
41
46
  # # good
42
47
  # my_class_spec.rb # describe MyClass, '#method'
43
48
  #
49
+ # @example when configuration is `SpecSuffixOnly: true`
50
+ # # good
51
+ # whatever_spec.rb # describe MyClass
52
+ #
53
+ # # good
54
+ # my_class_spec.rb # describe MyClass
55
+ #
56
+ # # good
57
+ # my_class_spec.rb # describe MyClass, '#method'
58
+ #
44
59
  class FilePath < Cop
45
60
  include RuboCop::RSpec::TopLevelDescribe
46
61
 
@@ -70,9 +85,15 @@ module RuboCop
70
85
  end
71
86
 
72
87
  def glob_for((described_class, method_name))
88
+ return glob_for_spec_suffix_only? if spec_suffix_only?
89
+
73
90
  "#{expected_path(described_class)}#{name_glob(method_name)}*_spec.rb"
74
91
  end
75
92
 
93
+ def glob_for_spec_suffix_only?
94
+ '*_spec.rb'
95
+ end
96
+
76
97
  def name_glob(name)
77
98
  return unless name&.str_type?
78
99
 
@@ -103,12 +124,19 @@ module RuboCop
103
124
  end
104
125
 
105
126
  def filename_ends_with?(glob)
106
- File.fnmatch?("*#{glob}", processed_source.buffer.name)
127
+ filename =
128
+ RuboCop::PathUtil.relative_path(processed_source.buffer.name)
129
+ .gsub('../', '')
130
+ File.fnmatch?("*#{glob}", filename)
107
131
  end
108
132
 
109
133
  def relevant_rubocop_rspec_file?(_file)
110
134
  true
111
135
  end
136
+
137
+ def spec_suffix_only?
138
+ cop_config['SpecSuffixOnly']
139
+ end
112
140
  end
113
141
  end
114
142
  end
@@ -24,9 +24,6 @@ module RuboCop
24
24
  # end
25
25
  #
26
26
  class HooksBeforeExamples < Cop
27
- include RangeHelp
28
- include RuboCop::RSpec::FinalEndLocation
29
-
30
27
  MSG = 'Move `%<hook>s` above the examples in the group.'
31
28
 
32
29
  def_node_matcher :example_or_group?, <<-PATTERN
@@ -45,11 +42,9 @@ module RuboCop
45
42
  def autocorrect(node)
46
43
  lambda do |corrector|
47
44
  first_example = find_first_example(node.parent)
48
- first_example_pos = first_example.loc.expression
49
- indent = "\n" + ' ' * first_example.loc.column
50
-
51
- corrector.insert_before(first_example_pos, source(node) + indent)
52
- corrector.remove(node_range_with_surrounding_space(node))
45
+ RuboCop::RSpec::Corrector::MoveNode.new(
46
+ node, corrector, processed_source
47
+ ).move_before(first_example)
53
48
  end
54
49
  end
55
50
 
@@ -77,19 +72,6 @@ module RuboCop
77
72
  def find_first_example(node)
78
73
  node.children.find { |sibling| example_or_group?(sibling) }
79
74
  end
80
-
81
- def node_range_with_surrounding_space(node)
82
- range = node_range(node)
83
- range_by_whole_lines(range, include_final_newline: true)
84
- end
85
-
86
- def source(node)
87
- node_range(node).source
88
- end
89
-
90
- def node_range(node)
91
- node.loc.expression.with(end_pos: final_end_location(node).end_pos)
92
- end
93
75
  end
94
76
  end
95
77
  end