rubocop-rspec 1.38.0 → 1.42.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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/CODE_OF_CONDUCT.md +17 -0
  4. data/README.md +1 -61
  5. data/config/default.yml +147 -17
  6. data/lib/rubocop-rspec.rb +3 -1
  7. data/lib/rubocop/cop/rspec/align_left_let_brace.rb +11 -18
  8. data/lib/rubocop/cop/rspec/align_right_let_brace.rb +11 -18
  9. data/lib/rubocop/cop/rspec/be.rb +1 -1
  10. data/lib/rubocop/cop/rspec/be_eql.rb +5 -5
  11. data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +18 -16
  12. data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +8 -9
  13. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +69 -0
  14. data/lib/rubocop/cop/rspec/context_method.rb +5 -7
  15. data/lib/rubocop/cop/rspec/cop.rb +9 -29
  16. data/lib/rubocop/cop/rspec/describe_class.rb +20 -5
  17. data/lib/rubocop/cop/rspec/describe_method.rb +0 -1
  18. data/lib/rubocop/cop/rspec/described_class.rb +10 -7
  19. data/lib/rubocop/cop/rspec/dialect.rb +4 -11
  20. data/lib/rubocop/cop/rspec/empty_hook.rb +46 -0
  21. data/lib/rubocop/cop/rspec/empty_line_after_example.rb +5 -3
  22. data/lib/rubocop/cop/rspec/empty_line_after_example_group.rb +5 -5
  23. data/lib/rubocop/cop/rspec/empty_line_after_final_let.rb +4 -1
  24. data/lib/rubocop/cop/rspec/empty_line_after_hook.rb +5 -5
  25. data/lib/rubocop/cop/rspec/empty_line_after_subject.rb +4 -1
  26. data/lib/rubocop/cop/rspec/example_wording.rb +6 -7
  27. data/lib/rubocop/cop/rspec/expect_actual.rb +7 -10
  28. data/lib/rubocop/cop/rspec/expect_change.rb +9 -34
  29. data/lib/rubocop/cop/rspec/expect_in_hook.rb +2 -2
  30. data/lib/rubocop/cop/rspec/expect_output.rb +1 -1
  31. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +23 -20
  32. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +10 -16
  33. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +6 -7
  34. data/lib/rubocop/cop/rspec/file_path.rb +32 -4
  35. data/lib/rubocop/cop/rspec/hook_argument.rb +11 -17
  36. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +9 -28
  37. data/lib/rubocop/cop/rspec/implicit_expect.rb +6 -14
  38. data/lib/rubocop/cop/rspec/implicit_subject.rb +8 -5
  39. data/lib/rubocop/cop/rspec/instance_spy.rb +17 -11
  40. data/lib/rubocop/cop/rspec/instance_variable.rb +3 -7
  41. data/lib/rubocop/cop/rspec/invalid_predicate_matcher.rb +2 -5
  42. data/lib/rubocop/cop/rspec/it_behaves_like.rb +4 -5
  43. data/lib/rubocop/cop/rspec/leading_subject.rb +12 -19
  44. data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -4
  45. data/lib/rubocop/cop/rspec/let_before_examples.rb +9 -25
  46. data/lib/rubocop/cop/rspec/let_setup.rb +15 -3
  47. data/lib/rubocop/cop/rspec/message_chain.rb +6 -5
  48. data/lib/rubocop/cop/rspec/message_expectation.rb +1 -1
  49. data/lib/rubocop/cop/rspec/message_spies.rb +1 -2
  50. data/lib/rubocop/cop/rspec/multiple_subjects.rb +17 -18
  51. data/lib/rubocop/cop/rspec/named_subject.rb +7 -7
  52. data/lib/rubocop/cop/rspec/nested_groups.rb +9 -10
  53. data/lib/rubocop/cop/rspec/not_to_not.rb +4 -5
  54. data/lib/rubocop/cop/rspec/predicate_matcher.rb +25 -55
  55. data/lib/rubocop/cop/rspec/rails/http_status.rb +6 -8
  56. data/lib/rubocop/cop/rspec/receive_counts.rb +14 -16
  57. data/lib/rubocop/cop/rspec/receive_never.rb +10 -10
  58. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +11 -1
  59. data/lib/rubocop/cop/rspec/return_from_stub.rb +11 -21
  60. data/lib/rubocop/cop/rspec/scattered_let.rb +11 -1
  61. data/lib/rubocop/cop/rspec/shared_context.rb +7 -20
  62. data/lib/rubocop/cop/rspec/shared_examples.rb +6 -8
  63. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +14 -17
  64. data/lib/rubocop/cop/rspec/subject_stub.rb +23 -51
  65. data/lib/rubocop/cop/rspec/variable_definition.rb +56 -0
  66. data/lib/rubocop/cop/rspec/variable_name.rb +47 -0
  67. data/lib/rubocop/cop/rspec/yield.rb +13 -10
  68. data/lib/rubocop/cop/rspec_cops.rb +5 -1
  69. data/lib/rubocop/rspec/blank_line_separation.rb +0 -8
  70. data/lib/rubocop/rspec/corrector/move_node.rb +52 -0
  71. data/lib/rubocop/rspec/description_extractor.rb +2 -6
  72. data/lib/rubocop/rspec/example.rb +1 -1
  73. data/lib/rubocop/rspec/example_group.rb +21 -49
  74. data/lib/rubocop/rspec/factory_bot.rb +7 -1
  75. data/lib/rubocop/rspec/language.rb +8 -0
  76. data/lib/rubocop/rspec/language/node_pattern.rb +5 -1
  77. data/lib/rubocop/rspec/top_level_group.rb +44 -0
  78. data/lib/rubocop/rspec/variable.rb +16 -0
  79. data/lib/rubocop/rspec/version.rb +1 -1
  80. metadata +17 -10
  81. data/lib/rubocop/rspec/util.rb +0 -19
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks that memoized helpers names are symbols or strings.
7
+ #
8
+ # @example EnforcedStyle: symbols (default)
9
+ # # bad
10
+ # let('user_name') { 'Adam' }
11
+ # subject('user') { create_user }
12
+ #
13
+ # # good
14
+ # let(:user_name) { 'Adam' }
15
+ # subject(:user) { create_user }
16
+ #
17
+ # @example EnforcedStyle: strings
18
+ # # bad
19
+ # let(:user_name) { 'Adam' }
20
+ # subject(:user) { create_user }
21
+ #
22
+ # # good
23
+ # let('user_name') { 'Adam' }
24
+ # subject('user') { create_user }
25
+ class VariableDefinition < Cop
26
+ include ConfigurableEnforcedStyle
27
+ include RuboCop::RSpec::Variable
28
+
29
+ MSG = 'Use %<style>s for variable names.'
30
+
31
+ def on_send(node)
32
+ variable_definition?(node) do |variable|
33
+ if style_violation?(variable)
34
+ add_offense(variable, message: format(MSG, style: style))
35
+ end
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def style_violation?(variable)
42
+ style == :symbols && string?(variable) ||
43
+ style == :strings && symbol?(variable)
44
+ end
45
+
46
+ def string?(node)
47
+ node.str_type? || node.dstr_type?
48
+ end
49
+
50
+ def symbol?(node)
51
+ node.sym_type? || node.dsym_type?
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Checks that memoized helper names use the configured style.
7
+ #
8
+ # @example EnforcedStyle: snake_case (default)
9
+ # # bad
10
+ # let(:userName) { 'Adam' }
11
+ # subject(:userName) { 'Adam' }
12
+ #
13
+ # # good
14
+ # let(:user_name) { 'Adam' }
15
+ # subject(:user_name) { 'Adam' }
16
+ #
17
+ # @example EnforcedStyle: camelCase
18
+ # # bad
19
+ # let(:user_name) { 'Adam' }
20
+ # subject(:user_name) { 'Adam' }
21
+ #
22
+ # # good
23
+ # let(:userName) { 'Adam' }
24
+ # subject(:userName) { 'Adam' }
25
+ class VariableName < Cop
26
+ include ConfigurableNaming
27
+ include RuboCop::RSpec::Variable
28
+
29
+ MSG = 'Use %<style>s for variable names.'
30
+
31
+ def on_send(node)
32
+ variable_definition?(node) do |variable|
33
+ return if variable.dstr_type? || variable.dsym_type?
34
+
35
+ check_name(node, variable.value, variable.loc.expression)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def message(style)
42
+ format(MSG, style: style)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -12,6 +12,7 @@ module RuboCop
12
12
  # # good
13
13
  # expect(foo).to be(:bar).and_yield(1)
14
14
  class Yield < Cop
15
+ extend AutoCorrector
15
16
  include RangeHelp
16
17
 
17
18
  MSG = 'Use `.and_yield`.'
@@ -27,22 +28,24 @@ module RuboCop
27
28
 
28
29
  block_arg(node.arguments) do |block|
29
30
  if calling_block?(node.body, block)
30
- add_offense(node, location: block_range(node))
31
- end
32
- end
33
- end
31
+ range = block_range(node)
34
32
 
35
- def autocorrect(node)
36
- lambda do |corrector|
37
- node_range = range_with_surrounding_space(
38
- range: block_range(node), side: :left
39
- )
40
- corrector.replace(node_range, generate_replacement(node.body))
33
+ add_offense(range) do |corrector|
34
+ autocorrect(corrector, node, range)
35
+ end
36
+ end
41
37
  end
42
38
  end
43
39
 
44
40
  private
45
41
 
42
+ def autocorrect(corrector, node, range)
43
+ corrector.replace(
44
+ range_with_surrounding_space(range: range, side: :left),
45
+ generate_replacement(node.body)
46
+ )
47
+ end
48
+
46
49
  def calling_block?(node, block)
47
50
  if node.begin_type?
48
51
  node.each_child_node.all? { |child| block_call?(child, block) }
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'rspec/capybara/current_path_expectation'
4
4
  require_relative 'rspec/capybara/feature_methods'
5
+ require_relative 'rspec/capybara/visibility_matcher'
5
6
 
6
7
  require_relative 'rspec/factory_bot/attribute_defined_statically'
7
8
  require_relative 'rspec/factory_bot/create_list'
@@ -9,7 +10,7 @@ require_relative 'rspec/factory_bot/factory_class_name'
9
10
 
10
11
  begin
11
12
  require_relative 'rspec/rails/http_status'
12
- rescue LoadError # rubocop:disable Lint/SuppressedException
13
+ rescue LoadError
13
14
  # Rails/HttpStatus cannot be loaded if rack/utils is unavailable.
14
15
  end
15
16
 
@@ -29,6 +30,7 @@ require_relative 'rspec/described_class'
29
30
  require_relative 'rspec/described_class_module_wrapping'
30
31
  require_relative 'rspec/dialect'
31
32
  require_relative 'rspec/empty_example_group'
33
+ require_relative 'rspec/empty_hook'
32
34
  require_relative 'rspec/empty_line_after_example'
33
35
  require_relative 'rspec/empty_line_after_example_group'
34
36
  require_relative 'rspec/empty_line_after_final_let'
@@ -84,6 +86,8 @@ require_relative 'rspec/shared_examples'
84
86
  require_relative 'rspec/single_argument_message_chain'
85
87
  require_relative 'rspec/subject_stub'
86
88
  require_relative 'rspec/unspecified_exception'
89
+ require_relative 'rspec/variable_definition'
90
+ require_relative 'rspec/variable_name'
87
91
  require_relative 'rspec/verified_doubles'
88
92
  require_relative 'rspec/void_expect'
89
93
  require_relative 'rspec/yield'
@@ -32,14 +32,6 @@ module RuboCop
32
32
 
33
33
  node.equal?(node.parent.children.last)
34
34
  end
35
-
36
- def autocorrect(node)
37
- lambda do |corrector|
38
- missing_separating_line(node) do |location|
39
- corrector.insert_after(location.end, "\n")
40
- end
41
- end
42
- end
43
35
  end
44
36
  end
45
37
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module RSpec
5
+ module Corrector
6
+ # Helper methods to move a node
7
+ class MoveNode
8
+ include RuboCop::Cop::RangeHelp
9
+ include RuboCop::RSpec::FinalEndLocation
10
+
11
+ attr_reader :original, :corrector, :processed_source
12
+
13
+ def initialize(node, corrector, processed_source)
14
+ @original = node
15
+ @corrector = corrector
16
+ @processed_source = processed_source # used by RangeHelp
17
+ end
18
+
19
+ def move_before(other) # rubocop:disable Metrics/AbcSize
20
+ position = other.loc.expression
21
+ indent = "\n" + ' ' * other.loc.column
22
+
23
+ corrector.insert_before(position, source(original) + indent)
24
+ corrector.remove(node_range_with_surrounding_space(original))
25
+ end
26
+
27
+ def move_after(other)
28
+ position = final_end_location(other)
29
+ indent = "\n" + ' ' * other.loc.column
30
+
31
+ corrector.insert_after(position, indent + source(original))
32
+ corrector.remove(node_range_with_surrounding_space(original))
33
+ end
34
+
35
+ private
36
+
37
+ def source(node)
38
+ node_range(node).source
39
+ end
40
+
41
+ def node_range(node)
42
+ node.loc.expression.with(end_pos: final_end_location(node).end_pos)
43
+ end
44
+
45
+ def node_range_with_surrounding_space(node)
46
+ range = node_range(node)
47
+ range_by_whole_lines(range, include_final_newline: true)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -21,7 +21,7 @@ module RuboCop
21
21
 
22
22
  # Decorator of a YARD code object for working with documented rspec cops
23
23
  class CodeObject
24
- COP_CLASS_NAMES = %w[RuboCop::Cop RuboCop::Cop::RSpec::Cop].freeze
24
+ COP_CLASS_NAME = 'RuboCop::Cop::RSpec::Cop'
25
25
  RSPEC_NAMESPACE = 'RuboCop::Cop::RSpec'
26
26
 
27
27
  def initialize(yardoc)
@@ -68,11 +68,7 @@ module RuboCop
68
68
  end
69
69
 
70
70
  def cop_subclass?
71
- # YARD superclass resolution is a bit flaky: All classes loaded before
72
- # RuboCop::Cop::WorkaroundCop are shown as having RuboCop::Cop as
73
- # superclass, while all the following classes are listed as having
74
- # RuboCop::Cop::RSpec::Cop as their superclass.
75
- COP_CLASS_NAMES.include?(yardoc.superclass.path)
71
+ yardoc.superclass.path == COP_CLASS_NAME
76
72
  end
77
73
 
78
74
  def abstract?
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module RSpec
5
5
  # Wrapper for RSpec examples
6
6
  class Example < Concept
7
- def_node_matcher :extract_doc_string, '(send _ _ $str ...)'
7
+ def_node_matcher :extract_doc_string, '(send _ _ $_ ...)'
8
8
  def_node_matcher :extract_metadata, '(send _ _ _ $...)'
9
9
  def_node_matcher :extract_implementation, '(block send args $_)'
10
10
 
@@ -14,72 +14,44 @@ module RuboCop
14
14
  ExampleGroups::ALL + SharedGroups::ALL + Includes::ALL
15
15
  ).block_pattern
16
16
 
17
+ def lets
18
+ find_all_in_scope(node, :let?)
19
+ end
20
+
17
21
  def subjects
18
- subjects_in_scope(node)
22
+ find_all_in_scope(node, :subject?)
19
23
  end
20
24
 
21
25
  def examples
22
- examples_in_scope(node).map(&Example.public_method(:new))
26
+ find_all_in_scope(node, :example?).map(&Example.public_method(:new))
23
27
  end
24
28
 
25
29
  def hooks
26
- hooks_in_scope(node).map(&Hook.public_method(:new))
30
+ find_all_in_scope(node, :hook?).map(&Hook.public_method(:new))
27
31
  end
28
32
 
29
33
  private
30
34
 
31
- def subjects_in_scope(node)
32
- node.each_child_node.flat_map do |child|
33
- find_subjects(child)
34
- end
35
- end
36
-
37
- def find_subjects(node)
38
- return [] if scope_change?(node)
39
-
40
- if subject?(node)
41
- [node]
42
- else
43
- subjects_in_scope(node)
44
- end
45
- end
46
-
47
- def hooks_in_scope(node)
48
- node.each_child_node.flat_map do |child|
49
- find_hooks(child)
50
- end
51
- end
52
-
53
- def find_hooks(node)
54
- return [] if scope_change?(node) || example?(node)
55
-
56
- if hook?(node)
57
- [node]
58
- else
59
- hooks_in_scope(node)
60
- end
61
- end
62
-
63
- def examples_in_scope(node, &blk)
64
- node.each_child_node.flat_map do |child|
65
- find_examples(child, &blk)
66
- end
67
- end
68
-
69
- # Recursively search for examples within the current scope
35
+ # Recursively search for predicate within the current scope
70
36
  #
71
- # Searches node for examples and halts when a scope change is detected
37
+ # Searches node and halts when a scope change is detected
72
38
  #
73
- # @param node [RuboCop::Node] node to recursively search for examples
39
+ # @param node [RuboCop::Node] node to recursively search
74
40
  #
75
- # @return [Array<RuboCop::Node>] discovered example nodes
76
- def find_examples(node)
77
- return [] if scope_change?(node)
41
+ # @return [Array<RuboCop::Node>] discovered nodes
42
+ def find_all_in_scope(node, predicate)
43
+ node.each_child_node.flat_map do |child|
44
+ find_all(child, predicate)
45
+ end
46
+ end
78
47
 
79
- if example?(node)
48
+ def find_all(node, predicate)
49
+ if public_send(predicate, node)
80
50
  [node]
51
+ elsif scope_change?(node) || example?(node)
52
+ []
81
53
  else
82
- examples_in_scope(node)
54
+ find_all_in_scope(node, predicate)
83
55
  end
84
56
  end
85
57
  end
@@ -4,7 +4,13 @@ module RuboCop
4
4
  module RSpec
5
5
  # RuboCop FactoryBot project namespace
6
6
  module FactoryBot
7
- ATTRIBUTE_DEFINING_METHODS = %i[factory trait transient ignore].freeze
7
+ ATTRIBUTE_DEFINING_METHODS = %i[
8
+ factory
9
+ ignore
10
+ trait
11
+ traits_for_enum
12
+ transient
13
+ ].freeze
8
14
 
9
15
  UNPROXIED_METHODS = %i[
10
16
  __send__
@@ -28,6 +28,14 @@ module RuboCop
28
28
  "(block #{send_pattern} ...)"
29
29
  end
30
30
 
31
+ def block_pass_pattern
32
+ "(send #{RSPEC} #{node_pattern_union} _ block_pass)"
33
+ end
34
+
35
+ def block_or_block_pass_pattern
36
+ "{#{block_pattern} #{block_pass_pattern}}"
37
+ end
38
+
31
39
  def send_pattern
32
40
  "(send #{RSPEC} #{node_pattern_union} ...)"
33
41
  end
@@ -8,6 +8,10 @@ module RuboCop
8
8
  extend RuboCop::NodePattern::Macros
9
9
 
10
10
  def_node_matcher :example_group?, ExampleGroups::ALL.block_pattern
11
+ def_node_matcher :shared_group?, SharedGroups::ALL.block_pattern
12
+
13
+ spec_groups = ExampleGroups::ALL + SharedGroups::ALL
14
+ def_node_matcher :spec_group?, spec_groups.block_pattern
11
15
 
12
16
  def_node_matcher :example_group_with_body?, <<-PATTERN
13
17
  (block #{ExampleGroups::ALL.send_pattern} args [!nil?])
@@ -17,7 +21,7 @@ module RuboCop
17
21
 
18
22
  def_node_matcher :hook?, Hooks::ALL.block_pattern
19
23
 
20
- def_node_matcher :let?, Helpers::ALL.block_pattern
24
+ def_node_matcher :let?, Helpers::ALL.block_or_block_pass_pattern
21
25
 
22
26
  def_node_matcher :subject?, Subject::ALL.block_pattern
23
27
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module RSpec
5
+ # Helper methods for top level example group cops
6
+ module TopLevelGroup
7
+ extend RuboCop::NodePattern::Macros
8
+ include RuboCop::RSpec::Language
9
+
10
+ def_node_matcher :example_or_shared_group?,
11
+ (ExampleGroups::ALL + SharedGroups::ALL).block_pattern
12
+
13
+ def on_block(node)
14
+ return unless respond_to?(:on_top_level_group)
15
+ return unless top_level_group?(node)
16
+
17
+ on_top_level_group(node)
18
+ end
19
+
20
+ private
21
+
22
+ def top_level_group?(node)
23
+ top_level_groups.include?(node)
24
+ end
25
+
26
+ def top_level_groups
27
+ @top_level_groups ||=
28
+ top_level_nodes.select { |n| example_or_shared_group?(n) }
29
+ end
30
+
31
+ def top_level_nodes
32
+ if root_node.begin_type?
33
+ root_node.children
34
+ else
35
+ [root_node]
36
+ end
37
+ end
38
+
39
+ def root_node
40
+ processed_source.ast
41
+ end
42
+ end
43
+ end
44
+ end