statsd-instrument 2.4.0 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/benchmark.yml +32 -0
- data/.github/workflows/ci.yml +24 -8
- data/.rubocop.yml +24 -0
- data/CHANGELOG.md +116 -3
- data/CONTRIBUTING.md +8 -6
- data/Gemfile +3 -0
- data/Rakefile +1 -1
- data/benchmark/README.md +29 -0
- data/benchmark/send-metrics-to-dev-null-log +47 -0
- data/benchmark/send-metrics-to-local-udp-receiver +57 -0
- data/lib/statsd/instrument.rb +126 -94
- data/lib/statsd/instrument/assertions.rb +69 -37
- data/lib/statsd/instrument/backends/capture_backend.rb +2 -0
- data/lib/statsd/instrument/helpers.rb +12 -8
- data/lib/statsd/instrument/metric.rb +56 -42
- data/lib/statsd/instrument/rubocop/metaprogramming_positional_arguments.rb +46 -0
- data/lib/statsd/instrument/rubocop/metric_return_value.rb +31 -0
- data/lib/statsd/instrument/rubocop/metric_value_keyword_argument.rb +45 -0
- data/lib/statsd/instrument/rubocop/positional_arguments.rb +99 -0
- data/lib/statsd/instrument/rubocop/splat_arguments.rb +37 -0
- data/lib/statsd/instrument/strict.rb +145 -0
- data/lib/statsd/instrument/version.rb +1 -1
- data/test/assertions_test.rb +37 -0
- data/test/benchmark/clock_gettime.rb +27 -0
- data/test/benchmark/default_tags.rb +1 -1
- data/test/deprecations_test.rb +86 -0
- data/test/helpers/rubocop_helper.rb +47 -0
- data/test/integration_test.rb +6 -2
- data/test/matchers_test.rb +9 -9
- data/test/metric_test.rb +3 -18
- data/test/rubocop/metaprogramming_positional_arguments_test.rb +58 -0
- data/test/rubocop/metric_return_value_test.rb +78 -0
- data/test/rubocop/metric_value_keyword_argument_test.rb +39 -0
- data/test/rubocop/positional_arguments_test.rb +110 -0
- data/test/rubocop/splat_arguments_test.rb +27 -0
- data/test/statsd_instrumentation_test.rb +77 -86
- data/test/statsd_test.rb +32 -65
- data/test/test_helper.rb +12 -1
- data/test/udp_backend_test.rb +8 -0
- metadata +28 -2
@@ -9,6 +9,7 @@ module StatsD::Instrument::Backends
|
|
9
9
|
# @see StatsD::Instrument::Assertions
|
10
10
|
class CaptureBackend < StatsD::Instrument::Backend
|
11
11
|
attr_reader :collected_metrics
|
12
|
+
attr_accessor :parent
|
12
13
|
|
13
14
|
def initialize
|
14
15
|
reset
|
@@ -18,6 +19,7 @@ module StatsD::Instrument::Backends
|
|
18
19
|
# @param metric [StatsD::Instrument::Metric] The metric to collect.
|
19
20
|
# @return [void]
|
20
21
|
def collect_metric(metric)
|
22
|
+
parent&.collect_metric(metric)
|
21
23
|
@collected_metrics << metric
|
22
24
|
end
|
23
25
|
|
@@ -1,18 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module StatsD::Instrument::Helpers
|
4
|
-
def
|
5
|
-
|
4
|
+
def with_capture_backend(backend, &block)
|
5
|
+
if StatsD.backend.is_a?(StatsD::Instrument::Backends::CaptureBackend)
|
6
|
+
backend.parent = StatsD.backend
|
7
|
+
end
|
8
|
+
|
6
9
|
old_backend = StatsD.backend
|
7
|
-
StatsD.backend =
|
10
|
+
StatsD.backend = backend
|
8
11
|
|
9
12
|
block.call
|
10
|
-
mock_backend.collected_metrics
|
11
13
|
ensure
|
12
|
-
if old_backend.is_a?(StatsD::Instrument::Backends::CaptureBackend)
|
13
|
-
old_backend.collected_metrics.concat(mock_backend.collected_metrics)
|
14
|
-
end
|
15
|
-
|
16
14
|
StatsD.backend = old_backend
|
17
15
|
end
|
16
|
+
|
17
|
+
def capture_statsd_calls(&block)
|
18
|
+
capture_backend = StatsD::Instrument::Backends::CaptureBackend.new
|
19
|
+
with_capture_backend(capture_backend, &block)
|
20
|
+
capture_backend.collected_metrics
|
21
|
+
end
|
18
22
|
end
|
@@ -31,50 +31,24 @@
|
|
31
31
|
# @see StatsD::Instrument::Backend A StatsD::Instrument::Backend is used to collect metrics.
|
32
32
|
#
|
33
33
|
class StatsD::Instrument::Metric
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
# @option options [Symbol] :type The type of the metric.
|
41
|
-
# @option options [String] :name The name of the metric without prefix.
|
42
|
-
# @option options [String] :prefix Override the default StatsD prefix.
|
43
|
-
# @option options [Boolean] :no_prefix Set to <tt>true</tt> if you don't want to apply a prefix.
|
44
|
-
# @option options [Numeric, String, nil] :value The value to collect for the metric. If set to
|
45
|
-
# <tt>nil>/tt>, {#default_value} will be used.
|
46
|
-
# @option options [Numeric, nil] :sample_rate The sample rate to use. If not set, it will use
|
47
|
-
# {StatsD#default_sample_rate}.
|
48
|
-
# @option options [Array<String>, Hash<String, String>, nil] :tags The tags to apply to this metric.
|
49
|
-
# See {.normalize_tags} for more information.
|
50
|
-
def initialize(options = {})
|
51
|
-
if options[:type]
|
52
|
-
@type = options[:type]
|
53
|
-
else
|
54
|
-
raise ArgumentError, "Metric :type is required."
|
55
|
-
end
|
56
|
-
|
57
|
-
if options[:name]
|
58
|
-
@name = normalize_name(options[:name])
|
59
|
-
else
|
60
|
-
raise ArgumentError, "Metric :name is required."
|
61
|
-
end
|
62
|
-
|
63
|
-
unless options[:no_prefix]
|
64
|
-
@name = if options[:prefix]
|
65
|
-
"#{options[:prefix]}.#{@name}"
|
66
|
-
else
|
67
|
-
StatsD.prefix ? "#{StatsD.prefix}.#{@name}" : @name
|
34
|
+
unless Regexp.method_defined?(:match?) # for ruby 2.3
|
35
|
+
module RubyBackports
|
36
|
+
refine Regexp do
|
37
|
+
def match?(str)
|
38
|
+
(self =~ str) != nil
|
39
|
+
end
|
68
40
|
end
|
69
41
|
end
|
70
42
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
43
|
+
using RubyBackports
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.new(
|
47
|
+
type:, name:, value: default_value(type), sample_rate: StatsD.default_sample_rate, tags: nil, metadata: nil
|
48
|
+
)
|
49
|
+
# pass keyword arguments as positional arguments for performance reasons,
|
50
|
+
# since MRI's C implementation of new turns keyword arguments into a hash
|
51
|
+
super(type, name, value, sample_rate, tags, metadata)
|
78
52
|
end
|
79
53
|
|
80
54
|
# The default value for this metric, which will be used if it is not set.
|
@@ -82,15 +56,48 @@ class StatsD::Instrument::Metric
|
|
82
56
|
# A default value is only defined for counter metrics (<tt>1</tt>). For all other
|
83
57
|
# metric types, this emthod will raise an <tt>ArgumentError</tt>.
|
84
58
|
#
|
59
|
+
#
|
60
|
+
# A default value is only defined for counter metrics (<tt>1</tt>). For all other
|
61
|
+
# metric types, this emthod will raise an <tt>ArgumentError</tt>.
|
62
|
+
#
|
85
63
|
# @return [Numeric, String] The default value for this metric.
|
86
64
|
# @raise ArgumentError if the metric type doesn't have a default value
|
87
|
-
def default_value
|
65
|
+
def self.default_value(type)
|
88
66
|
case type
|
89
67
|
when :c then 1
|
90
68
|
else raise ArgumentError, "A value is required for metric type #{type.inspect}."
|
91
69
|
end
|
92
70
|
end
|
93
71
|
|
72
|
+
attr_accessor :type, :name, :value, :sample_rate, :tags, :metadata
|
73
|
+
|
74
|
+
# Initializes a new metric instance.
|
75
|
+
# Normally, you don't want to call this method directly, but use one of the metric collection
|
76
|
+
# methods on the {StatsD} module.
|
77
|
+
#
|
78
|
+
# @param type [Symbol] The type of the metric.
|
79
|
+
# @option name [String] :name The name of the metric without prefix.
|
80
|
+
# @option value [Numeric, String, nil] The value to collect for the metric.
|
81
|
+
# @option sample_rate [Numeric, nil] The sample rate to use. If not set, it will use
|
82
|
+
# {StatsD#default_sample_rate}.
|
83
|
+
# @option tags [Array<String>, Hash<String, String>, nil] :tags The tags to apply to this metric.
|
84
|
+
# See {.normalize_tags} for more information.
|
85
|
+
def initialize(type, name, value, sample_rate, tags, metadata) # rubocop:disable Metrics/ParameterLists
|
86
|
+
raise ArgumentError, "Metric :type is required." unless type
|
87
|
+
raise ArgumentError, "Metric :name is required." unless name
|
88
|
+
raise ArgumentError, "Metric :value is required." unless value
|
89
|
+
|
90
|
+
@type = type
|
91
|
+
@name = normalize_name(name)
|
92
|
+
@value = value
|
93
|
+
@sample_rate = sample_rate
|
94
|
+
@tags = StatsD::Instrument::Metric.normalize_tags(tags)
|
95
|
+
if StatsD.default_tags
|
96
|
+
@tags = Array(@tags) + StatsD.default_tags
|
97
|
+
end
|
98
|
+
@metadata = metadata
|
99
|
+
end
|
100
|
+
|
94
101
|
# @private
|
95
102
|
# @return [String]
|
96
103
|
def to_s
|
@@ -123,6 +130,9 @@ class StatsD::Instrument::Metric
|
|
123
130
|
# @param name [String]
|
124
131
|
# @return [String]
|
125
132
|
def normalize_name(name)
|
133
|
+
# fast path when no normalization is needed to avoid copying the string
|
134
|
+
return name unless /[:|@]/.match?(name)
|
135
|
+
|
126
136
|
name.tr(':|@', '_')
|
127
137
|
end
|
128
138
|
|
@@ -136,6 +146,10 @@ class StatsD::Instrument::Metric
|
|
136
146
|
def self.normalize_tags(tags)
|
137
147
|
return unless tags
|
138
148
|
tags = tags.map { |k, v| k.to_s + ":" + v.to_s } if tags.is_a?(Hash)
|
149
|
+
|
150
|
+
# fast path when no string replacement is needed
|
151
|
+
return tags unless tags.any? { |tag| /[|,]/.match?(tag) }
|
152
|
+
|
139
153
|
tags.map { |tag| tag.tr('|,', '') }
|
140
154
|
end
|
141
155
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module StatsD
|
6
|
+
# This Rubocop will check for using the metaprogramming macros for positional
|
7
|
+
# argument usage, which is deprecated. These macros include `statd_count_if`,
|
8
|
+
# `statsd_measure`, etc.
|
9
|
+
#
|
10
|
+
# Use the following Rubocop invocation to check your project's codebase:
|
11
|
+
#
|
12
|
+
# rubocop --only StatsD/MetaprogrammingPositionalArguments
|
13
|
+
# -r `bundle show statsd-instrument`/lib/statsd/instrument/rubocop/metaprogramming_positional_arguments.rb
|
14
|
+
#
|
15
|
+
#
|
16
|
+
# This cop will not autocorrect the offenses it finds, but generally the fixes are easy to fix
|
17
|
+
class MetaprogrammingPositionalArguments < Cop
|
18
|
+
MSG = 'Use keyword arguments for StatsD metaprogramming macros'
|
19
|
+
|
20
|
+
METAPROGRAMMING_METHODS = %i{
|
21
|
+
statsd_measure
|
22
|
+
statsd_distribution
|
23
|
+
statsd_count_success
|
24
|
+
statsd_count_if
|
25
|
+
statsd_count
|
26
|
+
}
|
27
|
+
|
28
|
+
def on_send(node)
|
29
|
+
if METAPROGRAMMING_METHODS.include?(node.method_name)
|
30
|
+
arguments = node.arguments.dup
|
31
|
+
arguments.shift # method
|
32
|
+
arguments.shift # metric
|
33
|
+
arguments.pop if arguments.last&.type == :block_pass
|
34
|
+
case arguments.length
|
35
|
+
when 0
|
36
|
+
when 1
|
37
|
+
add_offense(node) if arguments.first.type != :hash
|
38
|
+
else
|
39
|
+
add_offense(node)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module StatsD
|
6
|
+
# This Rubocop will check for using the return value of StatsD metric calls, which is deprecated.
|
7
|
+
# To check your codebase, use the following Rubocop invocation:
|
8
|
+
#
|
9
|
+
# rubocop --require `bundle show statsd-instrument`/lib/statsd/instrument/rubocop/metric_return_value.rb \
|
10
|
+
# --only StatsD/MetricReturnValue
|
11
|
+
#
|
12
|
+
# This cop cannot autocorrect offenses. In production code, StatsD should be used in a fire-and-forget
|
13
|
+
# fashion. This means that you shouldn't rely on the return value. If you really need to access the
|
14
|
+
# emitted metrics, you can look into `capture_statsd_calls`
|
15
|
+
class MetricReturnValue < Cop
|
16
|
+
MSG = 'Do not use the return value of StatsD metric methods'
|
17
|
+
|
18
|
+
STATSD_METRIC_METHODS = %i{increment gauge measure set histogram distribution key_value}
|
19
|
+
INVALID_PARENTS = %i{lvasgn array pair send return yield}
|
20
|
+
|
21
|
+
def on_send(node)
|
22
|
+
if node.receiver&.type == :const && node.receiver&.const_name == "StatsD"
|
23
|
+
if STATSD_METRIC_METHODS.include?(node.method_name) && node.arguments.last&.type != :block_pass
|
24
|
+
add_offense(node.parent) if INVALID_PARENTS.include?(node.parent&.type)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module StatsD
|
6
|
+
# This Rubocop will check for providing the value for a metric using a keyword argument, which is
|
7
|
+
# deprecated. Use the following Rubocop invocation to check your project's codebase:
|
8
|
+
#
|
9
|
+
# rubocop --require \
|
10
|
+
# `bundle show statsd-instrument`/lib/statsd/instrument/rubocop/metric_value_keyword_argument.rb \
|
11
|
+
# --only StatsD/MetricValueKeywordArgument
|
12
|
+
#
|
13
|
+
# This cop will not autocorrect offenses. Most of the time, these are easy to fix by providing the
|
14
|
+
# value as the second argument, rather than a keyword argument.
|
15
|
+
#
|
16
|
+
# `StatsD.increment('foo', value: 3)` => `StatsD.increment('foo', 3)`
|
17
|
+
class MetricValueKeywordArgument < Cop
|
18
|
+
MSG = 'Do not use the value keyword argument, but use a positional argument'
|
19
|
+
|
20
|
+
STATSD_METRIC_METHODS = %i{increment gauge measure set histogram distribution key_value}
|
21
|
+
|
22
|
+
def on_send(node)
|
23
|
+
if node.receiver&.type == :const && node.receiver&.const_name == "StatsD"
|
24
|
+
if STATSD_METRIC_METHODS.include?(node.method_name)
|
25
|
+
last_argument = if node.arguments.last&.type == :block_pass
|
26
|
+
node.arguments[node.arguments.length - 2]
|
27
|
+
else
|
28
|
+
node.arguments[node.arguments.length - 1]
|
29
|
+
end
|
30
|
+
|
31
|
+
check_keyword_arguments_for_value_entry(node, last_argument) if last_argument&.type == :hash
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def check_keyword_arguments_for_value_entry(node, keyword_arguments)
|
37
|
+
value_pair_found = keyword_arguments.child_nodes.any? do |pair|
|
38
|
+
pair.child_nodes[0].type == :sym && pair.child_nodes[0].value == :value
|
39
|
+
end
|
40
|
+
add_offense(node) if value_pair_found
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module StatsD
|
6
|
+
# This Rubocop will check for using the StatsD metric methods (e.g. `StatsD.instrument`)
|
7
|
+
# for positional argument usage, which is deprecated.
|
8
|
+
#
|
9
|
+
# Use the following Rubocop invocation to check your project's codebase:
|
10
|
+
#
|
11
|
+
# rubocop --require `bundle show statsd-instrument`/lib/statsd/instrument/rubocop/positional_arguments.rb \
|
12
|
+
# --only StatsD/PositionalArguments
|
13
|
+
#
|
14
|
+
# This cop can autocorrect some offenses it finds, but not all of them.
|
15
|
+
class PositionalArguments < Cop
|
16
|
+
MSG = 'Use keyword arguments for StatsD calls'
|
17
|
+
|
18
|
+
STATSD_SINGLETON_METHODS = %i{increment gauge measure set histogram distribution key_value}
|
19
|
+
|
20
|
+
POSITIONAL_ARGUMENT_TYPES = Set[:int, :float, :nil]
|
21
|
+
UNKNOWN_ARGUMENT_TYPES = Set[:send, :const, :lvar, :splat]
|
22
|
+
REFUSED_ARGUMENT_TYPES = POSITIONAL_ARGUMENT_TYPES | UNKNOWN_ARGUMENT_TYPES
|
23
|
+
|
24
|
+
KEYWORD_ARGUMENT_TYPES = Set[:hash]
|
25
|
+
BLOCK_ARGUMENT_TYPES = Set[:block_pass]
|
26
|
+
ACCEPTED_ARGUMENT_TYPES = KEYWORD_ARGUMENT_TYPES | BLOCK_ARGUMENT_TYPES
|
27
|
+
|
28
|
+
def on_send(node)
|
29
|
+
if node.receiver&.type == :const && node.receiver&.const_name == "StatsD"
|
30
|
+
if STATSD_SINGLETON_METHODS.include?(node.method_name) && node.arguments.length >= 3
|
31
|
+
case node.arguments[2].type
|
32
|
+
when *REFUSED_ARGUMENT_TYPES
|
33
|
+
add_offense(node)
|
34
|
+
when *ACCEPTED_ARGUMENT_TYPES
|
35
|
+
nil
|
36
|
+
else
|
37
|
+
$stderr.puts "[StatsD/PositionalArguments] Unhandled argument type: #{node.arguments[2].type.inspect}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def autocorrect(node)
|
44
|
+
-> (corrector) do
|
45
|
+
positial_arguments = if node.arguments.last.type == :block_pass
|
46
|
+
node.arguments[2...node.arguments.length - 1]
|
47
|
+
else
|
48
|
+
node.arguments[2...node.arguments.length]
|
49
|
+
end
|
50
|
+
|
51
|
+
case positial_arguments[0].type
|
52
|
+
when *UNKNOWN_ARGUMENT_TYPES
|
53
|
+
# We don't know whether the method returns a hash, in which case it would be interpreted
|
54
|
+
# as keyword arguments. In this case, the fix would be to add a keywordf splat:
|
55
|
+
#
|
56
|
+
# `StatsD.instrument('foo', 1, method_call)`
|
57
|
+
# => `StatsD.instrument('foo', 1, **method_call)`
|
58
|
+
#
|
59
|
+
# However, it's also possible this method returns a sample rate, in which case the fix
|
60
|
+
# above will not do the right thing.
|
61
|
+
#
|
62
|
+
# `StatsD.instrument('foo', 1, SAMPLE_RATE_CONSTANT)`
|
63
|
+
# => `StatsD.instrument('foo', 1, sample_rate: SAMPLE_RATE_CONSTANT)`
|
64
|
+
#
|
65
|
+
# Because of this, we will not auto-correct and let the user fix the issue manually.
|
66
|
+
return
|
67
|
+
|
68
|
+
when *POSITIONAL_ARGUMENT_TYPES
|
69
|
+
value_argument = node.arguments[1]
|
70
|
+
from = value_argument.source_range.end_pos
|
71
|
+
to = positial_arguments.last.source_range.end_pos
|
72
|
+
range = Parser::Source::Range.new(node.source_range.source_buffer, from, to)
|
73
|
+
corrector.remove(range)
|
74
|
+
|
75
|
+
keyword_arguments = []
|
76
|
+
sample_rate = positial_arguments[0]
|
77
|
+
if sample_rate && sample_rate.type != :nil
|
78
|
+
keyword_arguments << "sample_rate: #{sample_rate.source}"
|
79
|
+
end
|
80
|
+
|
81
|
+
tags = positial_arguments[1]
|
82
|
+
if tags && tags.type != :nil
|
83
|
+
keyword_arguments << if tags.type == :hash && tags.source[0] != '{'
|
84
|
+
"tags: { #{tags.source} }"
|
85
|
+
else
|
86
|
+
"tags: #{tags.source}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
unless keyword_arguments.empty?
|
91
|
+
corrector.insert_after(value_argument.source_range, ", #{keyword_arguments.join(', ')}")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module StatsD
|
6
|
+
# This Rubocop will check for using splat arguments (*args) in StatsD metric calls. To run
|
7
|
+
# this rule on your codebase, invoke Rubocop this way:
|
8
|
+
#
|
9
|
+
# rubocop --require \
|
10
|
+
# `bundle show statsd-instrument`/lib/statsd/instrument/rubocop/splat_arguments.rb \
|
11
|
+
# --only StatsD/SplatArguments
|
12
|
+
#
|
13
|
+
# This cop will not autocorrect offenses.
|
14
|
+
class SplatArguments < Cop
|
15
|
+
MSG = 'Do not use splat arguments in StatsD metric calls'
|
16
|
+
|
17
|
+
STATSD_METRIC_METHODS = %i{increment gauge measure set histogram distribution key_value}
|
18
|
+
|
19
|
+
def on_send(node)
|
20
|
+
if node.receiver&.type == :const && node.receiver&.const_name == "StatsD"
|
21
|
+
if STATSD_METRIC_METHODS.include?(node.method_name)
|
22
|
+
check_for_splat_arguments(node)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def check_for_splat_arguments(node)
|
30
|
+
if node.arguments.any? { |arg| arg.type == :splat }
|
31
|
+
add_offense(node)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|