statsd-instrument 2.4.0 → 2.5.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/.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
|