rubocop-rspec 1.37.1 → 1.41.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 +53 -0
- data/CODE_OF_CONDUCT.md +17 -0
- data/README.md +2 -62
- data/config/default.yml +155 -15
- data/lib/rubocop-rspec.rb +3 -1
- data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +69 -0
- data/lib/rubocop/cop/rspec/context_wording.rb +5 -1
- data/lib/rubocop/cop/rspec/cop.rb +9 -29
- data/lib/rubocop/cop/rspec/describe_class.rb +20 -5
- data/lib/rubocop/cop/rspec/describe_method.rb +0 -1
- data/lib/rubocop/cop/rspec/empty_hook.rb +50 -0
- data/lib/rubocop/cop/rspec/expect_actual.rb +27 -2
- data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +5 -2
- data/lib/rubocop/cop/rspec/file_path.rb +32 -4
- data/lib/rubocop/cop/rspec/hooks_before_examples.rb +3 -21
- data/lib/rubocop/cop/rspec/instance_variable.rb +16 -11
- data/lib/rubocop/cop/rspec/leading_subject.rb +3 -10
- data/lib/rubocop/cop/rspec/leaky_constant_declaration.rb +1 -4
- data/lib/rubocop/cop/rspec/let_before_examples.rb +3 -21
- data/lib/rubocop/cop/rspec/let_setup.rb +15 -3
- data/lib/rubocop/cop/rspec/named_subject.rb +6 -6
- data/lib/rubocop/cop/rspec/nested_groups.rb +9 -10
- data/lib/rubocop/cop/rspec/predicate_matcher.rb +2 -2
- data/lib/rubocop/cop/rspec/rails/http_status.rb +2 -0
- data/lib/rubocop/cop/rspec/repeated_description.rb +17 -2
- data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +97 -0
- data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +96 -0
- data/lib/rubocop/cop/rspec/return_from_stub.rb +3 -2
- data/lib/rubocop/cop/rspec/scattered_let.rb +13 -0
- data/lib/rubocop/cop/rspec/scattered_setup.rb +27 -9
- data/lib/rubocop/cop/rspec/shared_examples.rb +1 -0
- data/lib/rubocop/cop/rspec/subject_stub.rb +23 -51
- data/lib/rubocop/cop/rspec/variable_definition.rb +56 -0
- data/lib/rubocop/cop/rspec/variable_name.rb +47 -0
- data/lib/rubocop/cop/rspec_cops.rb +7 -1
- data/lib/rubocop/rspec/corrector/move_node.rb +52 -0
- data/lib/rubocop/rspec/description_extractor.rb +2 -6
- data/lib/rubocop/rspec/example.rb +1 -1
- data/lib/rubocop/rspec/example_group.rb +21 -49
- data/lib/rubocop/rspec/factory_bot.rb +7 -1
- data/lib/rubocop/rspec/hook.rb +44 -11
- data/lib/rubocop/rspec/language.rb +20 -0
- data/lib/rubocop/rspec/language/node_pattern.rb +5 -1
- data/lib/rubocop/rspec/top_level_group.rb +44 -0
- data/lib/rubocop/rspec/variable.rb +16 -0
- data/lib/rubocop/rspec/version.rb +1 -1
- metadata +20 -11
- data/lib/rubocop/rspec/util.rb +0 -19
@@ -47,27 +47,30 @@ module RuboCop
|
|
47
47
|
# end
|
48
48
|
#
|
49
49
|
class InstanceVariable < Cop
|
50
|
+
include RuboCop::RSpec::TopLevelGroup
|
51
|
+
|
50
52
|
MSG = 'Avoid instance variables – use let, ' \
|
51
53
|
'a method call, or a local variable (if possible).'
|
52
54
|
|
53
|
-
EXAMPLE_GROUP_METHODS = ExampleGroups::ALL + SharedGroups::ALL
|
54
|
-
|
55
|
-
def_node_matcher :spec_group?, EXAMPLE_GROUP_METHODS.block_pattern
|
56
|
-
|
57
55
|
def_node_matcher :dynamic_class?, <<-PATTERN
|
58
56
|
(block (send (const nil? :Class) :new ...) ...)
|
59
57
|
PATTERN
|
60
58
|
|
59
|
+
def_node_matcher :custom_matcher?, <<-PATTERN
|
60
|
+
(block {
|
61
|
+
(send nil? :matcher sym)
|
62
|
+
(send (const (const nil? :RSpec) :Matchers) :define sym)
|
63
|
+
} ...)
|
64
|
+
PATTERN
|
65
|
+
|
61
66
|
def_node_search :ivar_usage, '$(ivar $_)'
|
62
67
|
|
63
68
|
def_node_search :ivar_assigned?, '(ivasgn % ...)'
|
64
69
|
|
65
|
-
def
|
66
|
-
return unless spec_group?(node)
|
67
|
-
|
70
|
+
def on_top_level_group(node)
|
68
71
|
ivar_usage(node) do |ivar, name|
|
69
|
-
|
70
|
-
|
72
|
+
next if valid_usage?(ivar)
|
73
|
+
next if assignment_only? && !ivar_assigned?(node, name)
|
71
74
|
|
72
75
|
add_offense(ivar)
|
73
76
|
end
|
@@ -75,8 +78,10 @@ module RuboCop
|
|
75
78
|
|
76
79
|
private
|
77
80
|
|
78
|
-
def
|
79
|
-
node.each_ancestor(:block).any?
|
81
|
+
def valid_usage?(node)
|
82
|
+
node.each_ancestor(:block).any? do |block|
|
83
|
+
dynamic_class?(block) || custom_matcher?(block)
|
84
|
+
end
|
80
85
|
end
|
81
86
|
|
82
87
|
def assignment_only?
|
@@ -32,8 +32,6 @@ module RuboCop
|
|
32
32
|
# it { expect_something_else }
|
33
33
|
#
|
34
34
|
class LeadingSubject < Cop
|
35
|
-
include RangeHelp
|
36
|
-
|
37
35
|
MSG = 'Declare `subject` above any other `%<offending>s` declarations.'
|
38
36
|
|
39
37
|
def on_block(node)
|
@@ -58,10 +56,9 @@ module RuboCop
|
|
58
56
|
def autocorrect(node)
|
59
57
|
lambda do |corrector|
|
60
58
|
first_node = find_first_offending_node(node)
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
corrector.remove(node_range(node))
|
59
|
+
RuboCop::RSpec::Corrector::MoveNode.new(
|
60
|
+
node, corrector, processed_source
|
61
|
+
).move_before(first_node)
|
65
62
|
end
|
66
63
|
end
|
67
64
|
|
@@ -75,10 +72,6 @@ module RuboCop
|
|
75
72
|
node.parent.children.find { |sibling| offending?(sibling) }
|
76
73
|
end
|
77
74
|
|
78
|
-
def node_range(node)
|
79
|
-
range_by_whole_lines(node.source_range, include_final_newline: true)
|
80
|
-
end
|
81
|
-
|
82
75
|
def in_spec_block?(node)
|
83
76
|
node.each_ancestor(:block).any? do |ancestor|
|
84
77
|
example?(ancestor)
|
@@ -119,11 +119,8 @@ module RuboCop
|
|
119
119
|
private
|
120
120
|
|
121
121
|
def inside_describe_block?(node)
|
122
|
-
node.each_ancestor(:block).any?(&method(:
|
122
|
+
node.each_ancestor(:block).any?(&method(:spec_group?))
|
123
123
|
end
|
124
|
-
|
125
|
-
def_node_matcher :in_example_or_shared_group?,
|
126
|
-
(ExampleGroups::ALL + SharedGroups::ALL).block_pattern
|
127
124
|
end
|
128
125
|
end
|
129
126
|
end
|
@@ -31,9 +31,6 @@ module RuboCop
|
|
31
31
|
# expect(some).to be
|
32
32
|
# end
|
33
33
|
class LetBeforeExamples < Cop
|
34
|
-
include RangeHelp
|
35
|
-
include RuboCop::RSpec::FinalEndLocation
|
36
|
-
|
37
34
|
MSG = 'Move `let` before the examples in the group.'
|
38
35
|
|
39
36
|
def_node_matcher :example_or_group?, <<-PATTERN
|
@@ -52,11 +49,9 @@ module RuboCop
|
|
52
49
|
def autocorrect(node)
|
53
50
|
lambda do |corrector|
|
54
51
|
first_example = find_first_example(node.parent)
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
corrector.insert_before(first_example_pos, source(node) + indent)
|
59
|
-
corrector.remove(node_range_with_surrounding_space(node))
|
52
|
+
RuboCop::RSpec::Corrector::MoveNode.new(
|
53
|
+
node, corrector, processed_source
|
54
|
+
).move_before(first_example)
|
60
55
|
end
|
61
56
|
end
|
62
57
|
|
@@ -80,19 +75,6 @@ module RuboCop
|
|
80
75
|
def find_first_example(node)
|
81
76
|
node.children.find { |sibling| example_or_group?(sibling) }
|
82
77
|
end
|
83
|
-
|
84
|
-
def node_range_with_surrounding_space(node)
|
85
|
-
range = node_range(node)
|
86
|
-
range_by_whole_lines(range, include_final_newline: true)
|
87
|
-
end
|
88
|
-
|
89
|
-
def source(node)
|
90
|
-
node_range(node).source
|
91
|
-
end
|
92
|
-
|
93
|
-
def node_range(node)
|
94
|
-
node.loc.expression.with(end_pos: final_end_location(node).end_pos)
|
95
|
-
end
|
96
78
|
end
|
97
79
|
end
|
98
80
|
end
|
@@ -28,14 +28,20 @@ module RuboCop
|
|
28
28
|
class LetSetup < Cop
|
29
29
|
MSG = 'Do not use `let!` to setup objects not referenced in tests.'
|
30
30
|
|
31
|
-
|
31
|
+
def_node_matcher :example_or_shared_group_or_including?,
|
32
|
+
(
|
33
|
+
ExampleGroups::ALL + SharedGroups::ALL +
|
34
|
+
Includes::ALL
|
35
|
+
).block_pattern
|
36
|
+
|
37
|
+
def_node_matcher :let_bang, <<-PATTERN
|
32
38
|
(block $(send nil? :let! (sym $_)) args ...)
|
33
39
|
PATTERN
|
34
40
|
|
35
41
|
def_node_search :method_called?, '(send nil? %)'
|
36
42
|
|
37
43
|
def on_block(node)
|
38
|
-
return unless
|
44
|
+
return unless example_or_shared_group_or_including?(node)
|
39
45
|
|
40
46
|
unused_let_bang(node) do |let|
|
41
47
|
add_offense(let)
|
@@ -45,10 +51,16 @@ module RuboCop
|
|
45
51
|
private
|
46
52
|
|
47
53
|
def unused_let_bang(node)
|
48
|
-
|
54
|
+
child_let_bang(node) do |method_send, method_name|
|
49
55
|
yield(method_send) unless method_called?(node, method_name)
|
50
56
|
end
|
51
57
|
end
|
58
|
+
|
59
|
+
def child_let_bang(node, &block)
|
60
|
+
RuboCop::RSpec::ExampleGroup.new(node).lets.each do |let|
|
61
|
+
let_bang(let, &block)
|
62
|
+
end
|
63
|
+
end
|
52
64
|
end
|
53
65
|
end
|
54
66
|
end
|
@@ -6,11 +6,11 @@ module RuboCop
|
|
6
6
|
# Checks for explicitly referenced test subjects.
|
7
7
|
#
|
8
8
|
# RSpec lets you declare an "implicit subject" using `subject { ... }`
|
9
|
-
# which allows for tests like `it {
|
10
|
-
# reference your test subject you should explicitly
|
11
|
-
# `subject(:your_subject_name) { ... }`. Your test subjects
|
12
|
-
# the most important object in your tests so they deserve
|
13
|
-
# name.
|
9
|
+
# which allows for tests like `it { is_expected.to be_valid }`.
|
10
|
+
# If you need to reference your test subject you should explicitly
|
11
|
+
# name it using `subject(:your_subject_name) { ... }`. Your test subjects
|
12
|
+
# should be the most important object in your tests so they deserve
|
13
|
+
# a descriptive name.
|
14
14
|
#
|
15
15
|
# This cop can be configured in your configuration using the
|
16
16
|
# `IgnoreSharedExamples` which will not report offenses for implicit
|
@@ -39,7 +39,7 @@ module RuboCop
|
|
39
39
|
# RSpec.describe Foo do
|
40
40
|
# subject(:user) { described_class.new }
|
41
41
|
#
|
42
|
-
# it {
|
42
|
+
# it { is_expected.to be_valid }
|
43
43
|
# end
|
44
44
|
class NamedSubject < Cop
|
45
45
|
MSG = 'Name your test subject if you need '\
|
@@ -97,13 +97,11 @@ module RuboCop
|
|
97
97
|
"Configuration key `#{DEPRECATED_MAX_KEY}` for #{cop_name} is " \
|
98
98
|
'deprecated in favor of `Max`. Please use that instead.'
|
99
99
|
|
100
|
-
def_node_search :find_contexts, ExampleGroups::ALL.block_pattern
|
101
|
-
|
102
100
|
def on_top_level_describe(node, _args)
|
103
|
-
|
101
|
+
find_nested_example_groups(node.parent) do |example_group, nesting|
|
104
102
|
self.max = nesting
|
105
103
|
add_offense(
|
106
|
-
|
104
|
+
example_group.send_node,
|
107
105
|
message: message(nesting)
|
108
106
|
)
|
109
107
|
end
|
@@ -111,13 +109,14 @@ module RuboCop
|
|
111
109
|
|
112
110
|
private
|
113
111
|
|
114
|
-
def
|
115
|
-
|
116
|
-
|
112
|
+
def find_nested_example_groups(node, nesting: 1, &block)
|
113
|
+
example_group = example_group?(node)
|
114
|
+
yield node, nesting if example_group && nesting > max_nesting
|
115
|
+
|
116
|
+
next_nesting = example_group ? nesting + 1 : nesting
|
117
117
|
|
118
|
-
|
119
|
-
|
120
|
-
end
|
118
|
+
node.each_child_node(:block, :begin) do |child|
|
119
|
+
find_nested_example_groups(child, nesting: next_nesting, &block)
|
121
120
|
end
|
122
121
|
end
|
123
122
|
|
@@ -277,12 +277,12 @@ module RuboCop
|
|
277
277
|
# expect(foo).to be_something
|
278
278
|
#
|
279
279
|
# # also good - It checks "true" strictly.
|
280
|
-
# expect(foo).to be(true)
|
280
|
+
# expect(foo.something?).to be(true)
|
281
281
|
#
|
282
282
|
# @example Strict: false, EnforcedStyle: inflected
|
283
283
|
# # bad
|
284
284
|
# expect(foo.something?).to be_truthy
|
285
|
-
# expect(foo).to be(true)
|
285
|
+
# expect(foo.something?).to be(true)
|
286
286
|
#
|
287
287
|
# # good
|
288
288
|
# expect(foo).to be_something
|
@@ -70,6 +70,7 @@ module RuboCop
|
|
70
70
|
'to describe HTTP status code.'
|
71
71
|
|
72
72
|
attr_reader :node
|
73
|
+
|
73
74
|
def initialize(node)
|
74
75
|
@node = node
|
75
76
|
end
|
@@ -110,6 +111,7 @@ module RuboCop
|
|
110
111
|
ALLOWED_STATUSES = %i[error success missing redirect].freeze
|
111
112
|
|
112
113
|
attr_reader :node
|
114
|
+
|
113
115
|
def initialize(node)
|
114
116
|
@node = node
|
115
117
|
end
|
@@ -29,6 +29,17 @@ module RuboCop
|
|
29
29
|
# end
|
30
30
|
# end
|
31
31
|
#
|
32
|
+
# # good
|
33
|
+
# RSpec.describe User do
|
34
|
+
# it 'is valid' do
|
35
|
+
# # ...
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# it 'is valid', :flag do
|
39
|
+
# # ...
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
#
|
32
43
|
class RepeatedDescription < Cop
|
33
44
|
MSG = "Don't repeat descriptions within an example group."
|
34
45
|
|
@@ -47,14 +58,18 @@ module RuboCop
|
|
47
58
|
grouped_examples =
|
48
59
|
RuboCop::RSpec::ExampleGroup.new(node)
|
49
60
|
.examples
|
50
|
-
.group_by(
|
61
|
+
.group_by { |example| example_signature(example) }
|
51
62
|
|
52
63
|
grouped_examples
|
53
|
-
.select { |
|
64
|
+
.select { |signatures, group| signatures.any? && group.size > 1 }
|
54
65
|
.values
|
55
66
|
.flatten
|
56
67
|
.map(&:definition)
|
57
68
|
end
|
69
|
+
|
70
|
+
def example_signature(example)
|
71
|
+
[example.metadata, example.doc_string]
|
72
|
+
end
|
58
73
|
end
|
59
74
|
end
|
60
75
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Check for repeated describe and context block body.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# # bad
|
11
|
+
# describe 'cool feature x' do
|
12
|
+
# it { cool_predicate }
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# describe 'cool feature y' do
|
16
|
+
# it { cool_predicate }
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# describe 'cool feature' do
|
21
|
+
# it { cool_predicate }
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# describe 'another cool feature' do
|
25
|
+
# it { another_predicate }
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# # good
|
29
|
+
# context 'when case x', :tag do
|
30
|
+
# it { cool_predicate }
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# context 'when case y' do
|
34
|
+
# it { cool_predicate }
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# # good
|
38
|
+
# context Array do
|
39
|
+
# it { is_expected.to respond_to :each }
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# context Hash do
|
43
|
+
# it { is_expected.to respond_to :each }
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
class RepeatedExampleGroupBody < Cop
|
47
|
+
MSG = 'Repeated %<group>s block body on line(s) %<loc>s'
|
48
|
+
|
49
|
+
def_node_matcher :several_example_groups?, <<-PATTERN
|
50
|
+
(begin <#example_group_with_body? #example_group_with_body? ...>)
|
51
|
+
PATTERN
|
52
|
+
|
53
|
+
def_node_matcher :metadata, '(block (send _ _ _ $...) ...)'
|
54
|
+
def_node_matcher :body, '(block _ args $...)'
|
55
|
+
def_node_matcher :const_arg, '(block (send _ _ $const ...) ...)'
|
56
|
+
|
57
|
+
def_node_matcher :skip_or_pending?, <<-PATTERN
|
58
|
+
(block <(send nil? {:skip :pending}) ...>)
|
59
|
+
PATTERN
|
60
|
+
|
61
|
+
def on_begin(node)
|
62
|
+
return unless several_example_groups?(node)
|
63
|
+
|
64
|
+
repeated_group_bodies(node).each do |group, repeats|
|
65
|
+
add_offense(group, message: message(group, repeats))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def repeated_group_bodies(node)
|
72
|
+
node
|
73
|
+
.children
|
74
|
+
.select { |child| example_group_with_body?(child) }
|
75
|
+
.reject { |child| skip_or_pending?(child) }
|
76
|
+
.group_by { |group| signature_keys(group) }
|
77
|
+
.values
|
78
|
+
.reject(&:one?)
|
79
|
+
.flat_map { |groups| add_repeated_lines(groups) }
|
80
|
+
end
|
81
|
+
|
82
|
+
def add_repeated_lines(groups)
|
83
|
+
repeated_lines = groups.map(&:first_line)
|
84
|
+
groups.map { |group| [group, repeated_lines - [group.first_line]] }
|
85
|
+
end
|
86
|
+
|
87
|
+
def signature_keys(group)
|
88
|
+
[metadata(group), body(group), const_arg(group)]
|
89
|
+
end
|
90
|
+
|
91
|
+
def message(group, repeats)
|
92
|
+
format(MSG, group: group.method_name, loc: repeats)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RSpec
|
6
|
+
# Check for repeated example group descriptions.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# # bad
|
11
|
+
# describe 'cool feature' do
|
12
|
+
# # example group
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# describe 'cool feature' do
|
16
|
+
# # example group
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # bad
|
20
|
+
# context 'when case x' do
|
21
|
+
# # example group
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# describe 'when case x' do
|
25
|
+
# # example group
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# # good
|
29
|
+
# describe 'cool feature' do
|
30
|
+
# # example group
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# describe 'another cool feature' do
|
34
|
+
# # example group
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# # good
|
38
|
+
# context 'when case x' do
|
39
|
+
# # example group
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# context 'when another case' do
|
43
|
+
# # example group
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
class RepeatedExampleGroupDescription < Cop
|
47
|
+
MSG = 'Repeated %<group>s block description on line(s) %<loc>s'
|
48
|
+
|
49
|
+
def_node_matcher :several_example_groups?, <<-PATTERN
|
50
|
+
(begin <#example_group? #example_group? ...>)
|
51
|
+
PATTERN
|
52
|
+
|
53
|
+
def_node_matcher :doc_string_and_metadata, <<-PATTERN
|
54
|
+
(block (send _ _ $_ $...) ...)
|
55
|
+
PATTERN
|
56
|
+
|
57
|
+
def_node_matcher :skip_or_pending?, <<-PATTERN
|
58
|
+
(block <(send nil? {:skip :pending}) ...>)
|
59
|
+
PATTERN
|
60
|
+
|
61
|
+
def_node_matcher :empty_description?, '(block (send _ _) ...)'
|
62
|
+
|
63
|
+
def on_begin(node)
|
64
|
+
return unless several_example_groups?(node)
|
65
|
+
|
66
|
+
repeated_group_descriptions(node).each do |group, repeats|
|
67
|
+
add_offense(group, message: message(group, repeats))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def repeated_group_descriptions(node)
|
74
|
+
node
|
75
|
+
.children
|
76
|
+
.select { |child| example_group?(child) }
|
77
|
+
.reject { |child| skip_or_pending?(child) }
|
78
|
+
.reject { |child| empty_description?(child) }
|
79
|
+
.group_by { |group| doc_string_and_metadata(group) }
|
80
|
+
.values
|
81
|
+
.reject(&:one?)
|
82
|
+
.flat_map { |groups| add_repeated_lines(groups) }
|
83
|
+
end
|
84
|
+
|
85
|
+
def add_repeated_lines(groups)
|
86
|
+
repeated_lines = groups.map(&:first_line)
|
87
|
+
groups.map { |group| [group, repeated_lines - [group.first_line]] }
|
88
|
+
end
|
89
|
+
|
90
|
+
def message(group, repeats)
|
91
|
+
format(MSG, group: group.method_name, loc: repeats)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|