fasterer 0.1.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 +7 -0
- data/.fasterer.yml +17 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.rubocop.yml +8 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +93 -0
- data/Rakefile +7 -0
- data/bin/fasterer +5 -0
- data/fasterer.gemspec +28 -0
- data/lib/fasterer.rb +7 -0
- data/lib/fasterer/analyzer.rb +99 -0
- data/lib/fasterer/binary_call.rb +4 -0
- data/lib/fasterer/cli.rb +10 -0
- data/lib/fasterer/file_traverser.rb +81 -0
- data/lib/fasterer/method_call.rb +138 -0
- data/lib/fasterer/method_definition.rb +51 -0
- data/lib/fasterer/offense.rb +69 -0
- data/lib/fasterer/offense_collector.rb +17 -0
- data/lib/fasterer/parse_error.rb +10 -0
- data/lib/fasterer/parser.rb +11 -0
- data/lib/fasterer/rescue_call.rb +36 -0
- data/lib/fasterer/scanners/method_call_scanner.rb +134 -0
- data/lib/fasterer/scanners/method_definition_scanner.rb +52 -0
- data/lib/fasterer/scanners/offensive.rb +25 -0
- data/lib/fasterer/scanners/rescue_call_scanner.rb +28 -0
- data/lib/fasterer/token.rb +27 -0
- data/lib/fasterer/version.rb +3 -0
- data/spec/lib/fasterer/analyzer/01_parallel_assignment_spec.rb +12 -0
- data/spec/lib/fasterer/analyzer/02_rescue_vs_respond_to_spec.rb +12 -0
- data/spec/lib/fasterer/analyzer/03_module_eval_spec.rb +12 -0
- data/spec/lib/fasterer/analyzer/04_find_vs_bsearch_spec.rb +12 -0
- data/spec/lib/fasterer/analyzer/06_shuffle_first_vs_sample_spec.rb +12 -0
- data/spec/lib/fasterer/analyzer/08_for_loop_vs_each_spec.rb +12 -0
- data/spec/lib/fasterer/analyzer/09_each_with_index_vs_while_spec.rb +12 -0
- data/spec/lib/fasterer/analyzer/10_map_flatten_vs_flat_map_spec.rb +12 -0
- data/spec/lib/fasterer/analyzer/11_reverse_each_vs_reverse_each_spec.rb +12 -0
- data/spec/lib/fasterer/analyzer/12_select_first_vs_detect_spec.rb +12 -0
- data/spec/lib/fasterer/analyzer/13_sort_vs_sort_by_spec.rb +12 -0
- data/spec/lib/fasterer/analyzer/14_fetch_with_argument_vs_block_spec.rb +12 -0
- data/spec/lib/fasterer/analyzer/15_keys_each_vs_each_key_spec.rb +12 -0
- data/spec/lib/fasterer/analyzer/16_hash_merge_bang_vs_hash_brackets_spec.rb +12 -0
- data/spec/lib/fasterer/analyzer/18_block_vs_symbol_to_proc_spec.rb +12 -0
- data/spec/lib/fasterer/analyzer/19_proc_call_vs_yield_spec.rb +12 -0
- data/spec/lib/fasterer/analyzer/24_gsub_vs_tr_spec.rb +12 -0
- data/spec/lib/fasterer/analyzer/98_misc_spec.rb +11 -0
- data/spec/lib/fasterer/analyzer/99_exceptional_files_spec.rb +11 -0
- data/spec/lib/fasterer/method_call_spec.rb +483 -0
- data/spec/lib/fasterer/method_definition_spec.rb +93 -0
- data/spec/lib/fasterer/rescue_call_spec.rb +76 -0
- data/spec/spec_helper.rb +99 -0
- data/spec/support/analyzer/01_parallel_assignment.rb +10 -0
- data/spec/support/analyzer/02_rescue_vs_respond_to.rb +36 -0
- data/spec/support/analyzer/03_module_eval.rb +11 -0
- data/spec/support/analyzer/04_find_vs_bsearch.rb +5 -0
- data/spec/support/analyzer/06_shuffle_first_vs_sample.rb +5 -0
- data/spec/support/analyzer/08_for_loop_vs_each.rb +8 -0
- data/spec/support/analyzer/09_each_with_index_vs_while.rb +3 -0
- data/spec/support/analyzer/10_map_flatten_vs_flat_map.rb +15 -0
- data/spec/support/analyzer/11_reverse_each_vs_reverse_each.rb +9 -0
- data/spec/support/analyzer/12_select_first_vs_detect.rb +7 -0
- data/spec/support/analyzer/13_sort_vs_sort_by.rb +9 -0
- data/spec/support/analyzer/14_fetch_with_argument_vs_block.rb +11 -0
- data/spec/support/analyzer/15_keys_each_vs_each_key.rb +15 -0
- data/spec/support/analyzer/16_hash_merge_bang_vs_hash_brackets.rb +21 -0
- data/spec/support/analyzer/18_block_vs_symbol_to_proc.rb +30 -0
- data/spec/support/analyzer/19_proc_call_vs_yield.rb +24 -0
- data/spec/support/analyzer/24_gsub_vs_tr.rb +14 -0
- data/spec/support/analyzer/98_misc.rb +15 -0
- data/spec/support/analyzer/99_exceptional_files.rb +7 -0
- data/spec/support/binary_call/simple_comparison.rb +0 -0
- data/spec/support/method_call/method_call_on_constant.rb +1 -0
- data/spec/support/method_call/method_call_on_integer.rb +1 -0
- data/spec/support/method_call/method_call_on_method_call.rb +1 -0
- data/spec/support/method_call/method_call_on_string.rb +1 -0
- data/spec/support/method_call/method_call_on_variable.rb +2 -0
- data/spec/support/method_call/method_call_with_a_block.rb +5 -0
- data/spec/support/method_call/method_call_with_a_integer_argument.rb +1 -0
- data/spec/support/method_call/method_call_with_a_regex_argument.rb +1 -0
- data/spec/support/method_call/method_call_with_an_argument_and_a_block.rb +2 -0
- data/spec/support/method_call/method_call_with_an_implicit_receiver.rb +1 -0
- data/spec/support/method_call/method_call_with_an_implicit_receiver_and_no_brackets.rb +1 -0
- data/spec/support/method_call/method_call_with_an_implicit_receiver_and_no_brackets_and_do_end.rb +3 -0
- data/spec/support/method_call/method_call_with_equals.rb +1 -0
- data/spec/support/method_call/method_call_with_one_argument.rb +1 -0
- data/spec/support/method_call/method_call_with_two_arguments.rb +2 -0
- data/spec/support/method_call/method_call_without_brackets.rb +1 -0
- data/spec/support/method_definition/method_with_argument_and_block.rb +3 -0
- data/spec/support/method_definition/method_with_block.rb +3 -0
- data/spec/support/method_definition/method_with_default_argument.rb +3 -0
- data/spec/support/method_definition/method_with_splat_and_block.rb +3 -0
- data/spec/support/method_definition/simple_method.rb +2 -0
- data/spec/support/method_definition/simple_method_omitted_parenthesis.rb +2 -0
- data/spec/support/method_definition/simple_method_with_argument.rb +3 -0
- data/spec/support/rescue_call/plain_rescue.rb +7 -0
- data/spec/support/rescue_call/rescue_with_class.rb +5 -0
- data/spec/support/rescue_call/rescue_with_class_and_variable.rb +5 -0
- data/spec/support/rescue_call/rescue_with_multiple_classes.rb +4 -0
- data/spec/support/rescue_call/rescue_with_multiple_classes_and_variable.rb +5 -0
- data/spec/support/rescue_call/rescue_with_variable.rb +6 -0
- metadata +303 -0
@@ -0,0 +1,138 @@
|
|
1
|
+
module Fasterer
|
2
|
+
class MethodCall
|
3
|
+
attr_reader :element
|
4
|
+
attr_reader :receiver
|
5
|
+
attr_reader :method_name
|
6
|
+
attr_reader :arguments
|
7
|
+
attr_reader :block_body
|
8
|
+
attr_reader :block_argument_names
|
9
|
+
|
10
|
+
alias_method :name, :method_name
|
11
|
+
|
12
|
+
def initialize(element)
|
13
|
+
@element = element
|
14
|
+
set_call_element
|
15
|
+
set_receiver
|
16
|
+
set_method_name
|
17
|
+
set_arguments
|
18
|
+
set_block_presence
|
19
|
+
set_block_body
|
20
|
+
set_block_argument_names
|
21
|
+
end
|
22
|
+
|
23
|
+
def has_block?
|
24
|
+
@block_present || false
|
25
|
+
end
|
26
|
+
|
27
|
+
def receiver_element
|
28
|
+
call_element[1]
|
29
|
+
end
|
30
|
+
|
31
|
+
def arguments_element
|
32
|
+
call_element[3..-1] || []
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_reader :call_element
|
38
|
+
# TODO: explanation
|
39
|
+
def set_call_element
|
40
|
+
@call_element = case element.sexp_type
|
41
|
+
when :call
|
42
|
+
@element
|
43
|
+
when :iter
|
44
|
+
@element[1]
|
45
|
+
else
|
46
|
+
fail '!!!!!!!'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_receiver
|
51
|
+
@receiver = ReceiverFactory.new(receiver_element)
|
52
|
+
end
|
53
|
+
|
54
|
+
def set_method_name
|
55
|
+
@method_name = call_element[2]
|
56
|
+
end
|
57
|
+
|
58
|
+
def set_arguments
|
59
|
+
@arguments = arguments_element.map { |argument| Argument.new(argument) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_block_presence
|
63
|
+
if element.sexp_type == :iter
|
64
|
+
@block_present = true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def set_block_body
|
69
|
+
if has_block?
|
70
|
+
@block_body = element[3]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# TODO: write specs for lambdas and procs
|
75
|
+
def set_block_argument_names
|
76
|
+
@block_argument_names = if has_block? and element[2].is_a?(Sexp) # hack for lambdas
|
77
|
+
element[2].drop(1).map { |argument| argument }
|
78
|
+
end || []
|
79
|
+
end
|
80
|
+
|
81
|
+
def token
|
82
|
+
element[0]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# For now, used for determening if
|
87
|
+
# the receiver is a reference or not.
|
88
|
+
class ReceiverFactory
|
89
|
+
attr_reader :name
|
90
|
+
attr_reader :method
|
91
|
+
|
92
|
+
def self.new(receiver_info)
|
93
|
+
return if receiver_info.nil?
|
94
|
+
token = receiver_info.first
|
95
|
+
|
96
|
+
case token
|
97
|
+
when :lvar
|
98
|
+
return VariableReference.new(receiver_info)
|
99
|
+
when :method_add_arg, :method_add_block
|
100
|
+
case receiver_info[1][0]
|
101
|
+
when :call, :fcall
|
102
|
+
return MethodCall.new(receiver_info[1])
|
103
|
+
else
|
104
|
+
# binding.pry watch out for :method_add_arg
|
105
|
+
# raise 'nije ni metoda'
|
106
|
+
end
|
107
|
+
when :call, :iter
|
108
|
+
return MethodCall.new(receiver_info)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class VariableReference
|
114
|
+
attr_reader :name
|
115
|
+
|
116
|
+
def initialize(reference_info)
|
117
|
+
@reference_info = reference_info
|
118
|
+
@name = reference_info[1]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class Argument
|
123
|
+
|
124
|
+
attr_reader :element
|
125
|
+
|
126
|
+
def initialize(element)
|
127
|
+
@element = element
|
128
|
+
end
|
129
|
+
|
130
|
+
def type
|
131
|
+
@type ||= @element[0]
|
132
|
+
end
|
133
|
+
|
134
|
+
def value
|
135
|
+
@value ||= @element[1]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Fasterer
|
2
|
+
class MethodDefinition
|
3
|
+
attr_reader :element # for testing purposes
|
4
|
+
attr_reader :method_name
|
5
|
+
attr_reader :block_argument_name
|
6
|
+
attr_reader :body
|
7
|
+
|
8
|
+
def initialize(element)
|
9
|
+
@element = element # Ripper element
|
10
|
+
set_method_name
|
11
|
+
set_body
|
12
|
+
|
13
|
+
if arguments_element.any?
|
14
|
+
set_argument_names
|
15
|
+
set_block_argument_name
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def has_block?
|
20
|
+
!!@block_argument_name
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def arguments_element
|
26
|
+
element[2].drop(1)
|
27
|
+
end
|
28
|
+
|
29
|
+
def set_method_name
|
30
|
+
@method_name = @element[1]
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_argument_names
|
34
|
+
# TODO
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_body
|
38
|
+
@body = @element[3..-1]
|
39
|
+
end
|
40
|
+
|
41
|
+
def set_block_argument_name
|
42
|
+
if last_argument.to_s.start_with?('&')
|
43
|
+
@block_argument_name = last_argument.to_s[1..-1].to_sym
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def last_argument
|
48
|
+
arguments_element.last
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Fasterer
|
2
|
+
class Offense
|
3
|
+
attr_reader :offense_name, :line_number
|
4
|
+
|
5
|
+
alias_method :name, :offense_name
|
6
|
+
alias_method :line, :line_number
|
7
|
+
|
8
|
+
def initialize(offense_name, line_number)
|
9
|
+
@offense_name = offense_name
|
10
|
+
@line_number = line_number
|
11
|
+
explanation # Set explanation right away.
|
12
|
+
end
|
13
|
+
|
14
|
+
def explanation
|
15
|
+
@explanation ||= EXPLANATIONS.fetch(offense_name)
|
16
|
+
end
|
17
|
+
|
18
|
+
EXPLANATIONS = {
|
19
|
+
parallel_assignment:
|
20
|
+
'Parallel assignment is slower than sequential assignment',
|
21
|
+
|
22
|
+
rescue_vs_respond_to:
|
23
|
+
'Don\'t rescue NoMethodError, rather check with respond_to?',
|
24
|
+
|
25
|
+
module_eval:
|
26
|
+
'Using module_eval is slower than define_method',
|
27
|
+
|
28
|
+
shuffle_first_vs_sample:
|
29
|
+
'Array#shuffle.first is slower than Array#sample',
|
30
|
+
|
31
|
+
for_loop_vs_each:
|
32
|
+
'For loop is slower than using each',
|
33
|
+
|
34
|
+
each_with_index_vs_while:
|
35
|
+
'Using each_with_index is slower than while loop',
|
36
|
+
|
37
|
+
map_flatten_vs_flat_map:
|
38
|
+
'Array#map.flatten(1) is slower than Array#flat_map',
|
39
|
+
|
40
|
+
reverse_each_vs_reverse_each:
|
41
|
+
'Array#reverese.each is slower than Array#reverse_each',
|
42
|
+
|
43
|
+
select_first_vs_detect:
|
44
|
+
'Array#select.first is slower than Array#detect',
|
45
|
+
|
46
|
+
sort_vs_sort_by:
|
47
|
+
'Enumerable#sort is slower than Enumerable#sort_by',
|
48
|
+
|
49
|
+
fetch_with_argument_vs_block:
|
50
|
+
'Hash#fetch with second argument is slower than Hash#fetch with block',
|
51
|
+
|
52
|
+
keys_each_vs_each_key:
|
53
|
+
'Hash#keys.each is slower than Hash#each_key',
|
54
|
+
|
55
|
+
hash_merge_bang_vs_hash_brackets:
|
56
|
+
'Hash#merge! with one argument is slower than Hash#[]',
|
57
|
+
|
58
|
+
block_vs_symbol_to_proc:
|
59
|
+
'Calling argumentless methods within blocks is slower than using symbol to proc',
|
60
|
+
|
61
|
+
proc_call_vs_yield:
|
62
|
+
'Calling blocks with call is slower than yielding',
|
63
|
+
|
64
|
+
gsub_vs_tr:
|
65
|
+
'Use tr instead of gsub when grepping plain strings'
|
66
|
+
}
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Fasterer
|
4
|
+
class OffenseCollector
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@offenses = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](offense_name)
|
12
|
+
@offenses.select { |offense| offense.name == offense_name }
|
13
|
+
end
|
14
|
+
|
15
|
+
def_delegators :@offenses, :push, :any?, :each, :group_by
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Fasterer
|
2
|
+
class RescueCall
|
3
|
+
attr_reader :element
|
4
|
+
attr_reader :rescue_classes
|
5
|
+
|
6
|
+
def initialize(element)
|
7
|
+
@element = element
|
8
|
+
@rescue_classes = []
|
9
|
+
set_rescue_classes
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def set_rescue_classes
|
15
|
+
return if element[1].sexp_type != :array
|
16
|
+
|
17
|
+
@rescue_classes = element[1].drop(1).map do |rescue_reference|
|
18
|
+
if rescue_reference.sexp_type == :const
|
19
|
+
rescue_reference[1]
|
20
|
+
end
|
21
|
+
end.compact
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_multiple_rescue_classes
|
25
|
+
@rescue_classes = element[1].drop(1).map do |rescue_reference|
|
26
|
+
rescue_reference.flatten[2]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def set_single_rescue_class
|
31
|
+
if element[1][0][0] == :var_ref
|
32
|
+
@rescue_classes = Array(element[1][0][1][1])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'fasterer/method_call'
|
2
|
+
require 'fasterer/offense'
|
3
|
+
require 'fasterer/scanners/offensive'
|
4
|
+
|
5
|
+
module Fasterer
|
6
|
+
class MethodCallScanner
|
7
|
+
include Fasterer::Offensive
|
8
|
+
|
9
|
+
attr_reader :element
|
10
|
+
|
11
|
+
def initialize(element)
|
12
|
+
@element = element
|
13
|
+
check_offense
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_call
|
17
|
+
@method_call ||= MethodCall.new(element)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def check_offense
|
23
|
+
case method_call.method_name
|
24
|
+
when :module_eval
|
25
|
+
check_module_eval_offense
|
26
|
+
when :gsub
|
27
|
+
check_gsub_offense
|
28
|
+
when :sort
|
29
|
+
check_sort_offense
|
30
|
+
when :each_with_index
|
31
|
+
check_each_with_index_offense
|
32
|
+
when :first
|
33
|
+
check_first_offense
|
34
|
+
when :each
|
35
|
+
check_each_offense
|
36
|
+
when :flatten
|
37
|
+
check_flatten_offense
|
38
|
+
when :fetch
|
39
|
+
check_fetch_offense
|
40
|
+
when :merge!
|
41
|
+
check_merge_bang_offense
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def check_module_eval_offense
|
46
|
+
add_offense(:module_eval)
|
47
|
+
end
|
48
|
+
|
49
|
+
def check_gsub_offense
|
50
|
+
unless method_call.arguments.first.value.is_a? Regexp
|
51
|
+
add_offense(:gsub_vs_tr)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def check_sort_offense
|
56
|
+
if method_call.arguments.count > 0 || method_call.has_block?
|
57
|
+
add_offense(:sort_vs_sort_by)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def check_each_with_index_offense
|
62
|
+
add_offense(:each_with_index_vs_while)
|
63
|
+
end
|
64
|
+
|
65
|
+
def check_first_offense
|
66
|
+
return method_call unless method_call.receiver.is_a?(MethodCall)
|
67
|
+
|
68
|
+
case method_call.receiver.name
|
69
|
+
when :shuffle
|
70
|
+
add_offense(:shuffle_first_vs_sample)
|
71
|
+
when :select
|
72
|
+
add_offense(:select_first_vs_detect)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def check_each_offense
|
77
|
+
return method_call unless method_call.receiver.is_a?(MethodCall)
|
78
|
+
|
79
|
+
case method_call.receiver.name
|
80
|
+
when :reverse
|
81
|
+
add_offense(:reverse_each_vs_reverse_each)
|
82
|
+
when :keys
|
83
|
+
add_offense(:keys_each_vs_each_key)
|
84
|
+
end
|
85
|
+
|
86
|
+
check_symbol_to_proc
|
87
|
+
end
|
88
|
+
|
89
|
+
def check_flatten_offense
|
90
|
+
return method_call unless method_call.receiver.is_a?(MethodCall)
|
91
|
+
|
92
|
+
if method_call.receiver.name == :map &&
|
93
|
+
method_call.arguments.count == 1 &&
|
94
|
+
method_call.arguments.first.value == 1
|
95
|
+
|
96
|
+
add_offense(:map_flatten_vs_flat_map)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def check_fetch_offense
|
101
|
+
if method_call.arguments.count == 2 && !method_call.has_block?
|
102
|
+
add_offense(:fetch_with_argument_vs_block)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Need to refactor, fukken complicated conditions.
|
107
|
+
def check_symbol_to_proc
|
108
|
+
return unless method_call.block_argument_names.count == 1
|
109
|
+
return if method_call.block_body.nil?
|
110
|
+
return unless method_call.block_body.sexp_type == :call
|
111
|
+
return if method_call.arguments.count > 0
|
112
|
+
|
113
|
+
body_method_call = MethodCall.new(method_call.block_body)
|
114
|
+
|
115
|
+
return unless body_method_call.arguments.count.zero?
|
116
|
+
return if body_method_call.has_block?
|
117
|
+
return unless body_method_call.receiver.name == method_call.block_argument_names.first
|
118
|
+
|
119
|
+
add_offense(:block_vs_symbol_to_proc)
|
120
|
+
end
|
121
|
+
|
122
|
+
def check_merge_bang_offense
|
123
|
+
return unless method_call.arguments.count == 1
|
124
|
+
|
125
|
+
first_argument = method_call.arguments.first
|
126
|
+
return unless first_argument.type == :hash
|
127
|
+
|
128
|
+
if first_argument.element.drop(1).count == 2 # each key and value is an item by itself.
|
129
|
+
add_offense(:hash_merge_bang_vs_hash_brackets)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|