rubocop 0.31.0 → 0.35.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 +315 -0
- data/README.md +199 -38
- data/config/default.yml +91 -12
- data/config/disabled.yml +45 -4
- data/config/enabled.yml +107 -9
- data/lib/rubocop/ast_node.rb +48 -0
- data/lib/rubocop/cli.rb +11 -1
- data/lib/rubocop/comment_config.rb +4 -1
- data/lib/rubocop/config.rb +26 -17
- data/lib/rubocop/config_loader.rb +61 -14
- data/lib/rubocop/cop/commissioner.rb +7 -12
- data/lib/rubocop/cop/cop.rb +43 -20
- data/lib/rubocop/cop/lint/block_alignment.rb +1 -1
- data/lib/rubocop/cop/lint/circular_argument_reference.rb +69 -0
- data/lib/rubocop/cop/lint/debugger.rb +9 -48
- data/lib/rubocop/cop/lint/def_end_alignment.rb +8 -4
- data/lib/rubocop/cop/lint/deprecated_class_methods.rb +42 -23
- data/lib/rubocop/cop/lint/duplicate_methods.rb +2 -2
- data/lib/rubocop/cop/lint/duplicated_key.rb +37 -0
- data/lib/rubocop/cop/lint/end_alignment.rb +33 -13
- data/lib/rubocop/cop/lint/eval.rb +6 -2
- data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +175 -0
- data/lib/rubocop/cop/lint/literal_in_condition.rb +0 -5
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +10 -0
- data/lib/rubocop/cop/lint/nested_method_definition.rb +31 -0
- data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +19 -1
- data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +1 -1
- data/lib/rubocop/cop/lint/space_before_first_arg.rb +1 -1
- data/lib/rubocop/cop/lint/unneeded_disable.rb +72 -0
- data/lib/rubocop/cop/lint/unused_block_argument.rb +6 -0
- data/lib/rubocop/cop/lint/unused_method_argument.rb +8 -0
- data/lib/rubocop/cop/metrics/abc_size.rb +17 -6
- data/lib/rubocop/cop/metrics/class_length.rb +1 -1
- data/lib/rubocop/cop/metrics/method_length.rb +1 -3
- data/lib/rubocop/cop/metrics/module_length.rb +1 -1
- data/lib/rubocop/cop/metrics/parameter_lists.rb +1 -1
- data/lib/rubocop/cop/mixin/access_modifier_node.rb +1 -1
- data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -2
- data/lib/rubocop/cop/mixin/autocorrect_alignment.rb +28 -4
- data/lib/rubocop/cop/mixin/autocorrect_unless_changing_ast.rb +26 -3
- data/lib/rubocop/cop/mixin/check_assignment.rb +2 -3
- data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +59 -12
- data/lib/rubocop/cop/mixin/configurable_max.rb +1 -1
- data/lib/rubocop/cop/mixin/configurable_naming.rb +14 -3
- data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +1 -3
- data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +10 -1
- data/lib/rubocop/cop/mixin/first_element_line_break.rb +41 -0
- data/lib/rubocop/cop/mixin/if_node.rb +10 -0
- data/lib/rubocop/cop/mixin/method_preference.rb +28 -0
- data/lib/rubocop/cop/mixin/negative_conditional.rb +1 -1
- data/lib/rubocop/cop/mixin/on_method_def.rb +4 -5
- data/lib/rubocop/cop/mixin/safe_assignment.rb +3 -14
- data/lib/rubocop/cop/mixin/space_after_punctuation.rb +8 -1
- data/lib/rubocop/cop/mixin/space_before_punctuation.rb +8 -1
- data/lib/rubocop/cop/mixin/statement_modifier.rb +4 -7
- data/lib/rubocop/cop/mixin/string_help.rb +1 -1
- data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -1
- data/lib/rubocop/cop/mixin/surrounding_space.rb +5 -4
- data/lib/rubocop/cop/offense.rb +16 -3
- data/lib/rubocop/cop/performance/case_when_splat.rb +160 -0
- data/lib/rubocop/cop/performance/count.rb +35 -30
- data/lib/rubocop/cop/performance/detect.rb +16 -3
- data/lib/rubocop/cop/performance/fixed_size.rb +50 -0
- data/lib/rubocop/cop/performance/flat_map.rb +3 -3
- data/lib/rubocop/cop/performance/sample.rb +103 -59
- data/lib/rubocop/cop/performance/size.rb +2 -1
- data/lib/rubocop/cop/performance/string_replacement.rb +187 -0
- data/lib/rubocop/cop/rails/action_filter.rb +31 -5
- data/lib/rubocop/cop/rails/date.rb +15 -14
- data/lib/rubocop/cop/rails/pluralization_grammar.rb +97 -0
- data/lib/rubocop/cop/rails/read_write_attribute.rb +1 -1
- data/lib/rubocop/cop/rails/time_zone.rb +46 -18
- data/lib/rubocop/cop/style/alias.rb +1 -0
- data/lib/rubocop/cop/style/align_hash.rb +8 -15
- data/lib/rubocop/cop/style/align_parameters.rb +19 -7
- data/lib/rubocop/cop/style/and_or.rb +42 -13
- data/lib/rubocop/cop/style/auto_resource_cleanup.rb +2 -1
- data/lib/rubocop/cop/style/block_comments.rb +4 -2
- data/lib/rubocop/cop/style/block_delimiters.rb +69 -24
- data/lib/rubocop/cop/style/braces_around_hash_parameters.rb +40 -12
- data/lib/rubocop/cop/style/case_indentation.rb +18 -4
- data/lib/rubocop/cop/style/collection_methods.rb +2 -20
- data/lib/rubocop/cop/style/command_literal.rb +2 -10
- data/lib/rubocop/cop/style/comment_annotation.rb +29 -8
- data/lib/rubocop/cop/style/copyright.rb +5 -3
- data/lib/rubocop/cop/style/documentation.rb +21 -12
- data/lib/rubocop/cop/style/dot_position.rb +6 -0
- data/lib/rubocop/cop/style/double_negation.rb +4 -15
- data/lib/rubocop/cop/style/each_with_object.rb +17 -4
- data/lib/rubocop/cop/style/else_alignment.rb +2 -1
- data/lib/rubocop/cop/style/empty_else.rb +25 -0
- data/lib/rubocop/cop/style/empty_line_between_defs.rb +39 -14
- data/lib/rubocop/cop/style/encoding.rb +10 -4
- data/lib/rubocop/cop/style/extra_spacing.rb +126 -5
- data/lib/rubocop/cop/style/first_array_element_line_break.rb +41 -0
- data/lib/rubocop/cop/style/first_hash_element_line_break.rb +35 -0
- data/lib/rubocop/cop/style/first_method_argument_line_break.rb +37 -0
- data/lib/rubocop/cop/style/first_method_parameter_line_break.rb +42 -0
- data/lib/rubocop/cop/style/first_parameter_indentation.rb +5 -3
- data/lib/rubocop/cop/style/for.rb +2 -1
- data/lib/rubocop/cop/style/hash_syntax.rb +5 -0
- data/lib/rubocop/cop/style/if_unless_modifier.rb +32 -5
- data/lib/rubocop/cop/style/indent_hash.rb +67 -37
- data/lib/rubocop/cop/style/indentation_width.rb +36 -10
- data/lib/rubocop/cop/style/initial_indentation.rb +37 -0
- data/lib/rubocop/cop/style/leading_comment_space.rb +3 -2
- data/lib/rubocop/cop/style/method_call_parentheses.rb +28 -1
- data/lib/rubocop/cop/style/method_def_parentheses.rb +10 -7
- data/lib/rubocop/cop/style/multiline_operation_indentation.rb +21 -24
- data/lib/rubocop/cop/style/mutable_constant.rb +35 -0
- data/lib/rubocop/cop/style/nested_modifier.rb +97 -0
- data/lib/rubocop/cop/style/next.rb +50 -15
- data/lib/rubocop/cop/style/non_nil_check.rb +12 -8
- data/lib/rubocop/cop/style/one_line_conditional.rb +8 -4
- data/lib/rubocop/cop/style/option_hash.rb +64 -0
- data/lib/rubocop/cop/style/optional_arguments.rb +49 -0
- data/lib/rubocop/cop/style/parallel_assignment.rb +218 -0
- data/lib/rubocop/cop/style/percent_literal_delimiters.rb +3 -66
- data/lib/rubocop/cop/style/predicate_name.rb +7 -2
- data/lib/rubocop/cop/style/redundant_begin.rb +2 -13
- data/lib/rubocop/cop/style/redundant_freeze.rb +37 -0
- data/lib/rubocop/cop/style/redundant_return.rb +32 -3
- data/lib/rubocop/cop/style/regexp_literal.rb +2 -10
- data/lib/rubocop/cop/style/rescue_ensure_alignment.rb +81 -0
- data/lib/rubocop/cop/style/rescue_modifier.rb +30 -22
- data/lib/rubocop/cop/style/send.rb +18 -0
- data/lib/rubocop/cop/style/signal_exception.rb +24 -11
- data/lib/rubocop/cop/style/single_line_methods.rb +8 -9
- data/lib/rubocop/cop/style/single_space_before_first_arg.rb +1 -1
- data/lib/rubocop/cop/style/space_around_operators.rb +2 -0
- data/lib/rubocop/cop/style/space_inside_string_interpolation.rb +61 -0
- data/lib/rubocop/cop/style/special_global_vars.rb +4 -2
- data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +108 -0
- data/lib/rubocop/cop/style/string_methods.rb +32 -0
- data/lib/rubocop/cop/style/struct_inheritance.rb +11 -10
- data/lib/rubocop/cop/style/symbol_literal.rb +1 -1
- data/lib/rubocop/cop/style/symbol_proc.rb +62 -13
- data/lib/rubocop/cop/style/trailing_blank_lines.rb +9 -1
- data/lib/rubocop/cop/style/trailing_comma.rb +17 -7
- data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -2
- data/lib/rubocop/cop/style/trivial_accessors.rb +10 -1
- data/lib/rubocop/cop/style/unneeded_percent_q.rb +31 -20
- data/lib/rubocop/cop/style/variable_name.rb +5 -0
- data/lib/rubocop/cop/style/while_until_do.rb +1 -1
- data/lib/rubocop/cop/style/word_array.rb +15 -2
- data/lib/rubocop/cop/team.rb +25 -5
- data/lib/rubocop/cop/util.rb +7 -2
- data/lib/rubocop/cop/variable_force/locatable.rb +6 -6
- data/lib/rubocop/cop/variable_force.rb +10 -10
- data/lib/rubocop/formatter/base_formatter.rb +1 -1
- data/lib/rubocop/formatter/disabled_config_formatter.rb +70 -8
- data/lib/rubocop/formatter/formatter_set.rb +27 -1
- data/lib/rubocop/formatter/progress_formatter.rb +10 -2
- data/lib/rubocop/formatter/simple_text_formatter.rb +1 -1
- data/lib/rubocop/node_pattern.rb +390 -0
- data/lib/rubocop/options.rb +148 -81
- data/lib/rubocop/processed_source.rb +7 -2
- data/lib/rubocop/rake_task.rb +1 -1
- data/lib/rubocop/remote_config.rb +60 -0
- data/lib/rubocop/result_cache.rb +123 -0
- data/lib/rubocop/runner.rb +85 -22
- data/lib/rubocop/target_finder.rb +4 -4
- data/lib/rubocop/token.rb +2 -1
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop/warning.rb +11 -0
- data/lib/rubocop.rb +32 -3
- data/relnotes/v0.32.0.md +139 -0
- data/relnotes/v0.32.1.md +122 -0
- data/relnotes/v0.33.0.md +157 -0
- data/relnotes/v0.34.0.md +182 -0
- data/relnotes/v0.34.1.md +129 -0
- data/relnotes/v0.34.2.md +139 -0
- data/relnotes/v0.35.0.md +210 -0
- data/rubocop.gemspec +4 -4
- metadata +50 -12
- data/lib/rubocop/cop/performance/parallel_assignment.rb +0 -79
|
@@ -14,12 +14,15 @@ module RuboCop
|
|
|
14
14
|
# [1, 2, 3].reject { |e| e > 2 }.length
|
|
15
15
|
# [1, 2, 3].select { |e| e > 2 }.count { |e| e.odd? }
|
|
16
16
|
# [1, 2, 3].reject { |e| e > 2 }.count { |e| e.even? }
|
|
17
|
+
# array.select(&:value).count
|
|
17
18
|
#
|
|
18
19
|
# # good
|
|
19
20
|
# [1, 2, 3].count { |e| e > 2 }
|
|
20
21
|
# [1, 2, 3].count { |e| e < 2 }
|
|
21
22
|
# [1, 2, 3].count { |e| e > 2 && e.odd? }
|
|
22
23
|
# [1, 2, 3].count { |e| e < 2 && e.even? }
|
|
24
|
+
# Model.select('field AS field_one').count
|
|
25
|
+
# Model.select(:value).count
|
|
23
26
|
class Count < Cop
|
|
24
27
|
MSG = 'Use `count` instead of `%s...%s`.'
|
|
25
28
|
|
|
@@ -27,56 +30,58 @@ module RuboCop
|
|
|
27
30
|
COUNTERS = [:count, :length, :size]
|
|
28
31
|
|
|
29
32
|
def on_send(node)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return unless
|
|
33
|
-
|
|
34
|
-
begin_pos = if SELECTORS.include?(first_method)
|
|
35
|
-
return if second_method.is_a?(Symbol)
|
|
36
|
-
expression.loc.selector.begin_pos
|
|
37
|
-
else
|
|
38
|
-
return unless SELECTORS.include?(second_method)
|
|
39
|
-
expression.parent.loc.selector.begin_pos
|
|
40
|
-
end
|
|
41
|
-
|
|
33
|
+
selector, selector_loc, params, counter = parse(node)
|
|
34
|
+
return unless COUNTERS.include?(counter)
|
|
35
|
+
return unless SELECTORS.include?(selector)
|
|
36
|
+
return if params && !params.block_pass_type?
|
|
42
37
|
return if node.parent && node.parent.block_type?
|
|
43
38
|
|
|
44
39
|
range = Parser::Source::Range.new(node.loc.expression.source_buffer,
|
|
45
|
-
begin_pos,
|
|
40
|
+
selector_loc.begin_pos,
|
|
46
41
|
node.loc.expression.end_pos)
|
|
47
42
|
|
|
48
|
-
add_offense(node, range,
|
|
49
|
-
format(MSG, first_method || second_method, third_method))
|
|
43
|
+
add_offense(node, range, format(MSG, selector, counter))
|
|
50
44
|
end
|
|
51
45
|
|
|
52
46
|
def autocorrect(node)
|
|
53
|
-
|
|
47
|
+
selector, selector_loc = parse(node)
|
|
54
48
|
|
|
55
|
-
return if
|
|
49
|
+
return if selector == :reject
|
|
56
50
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
expression.parent.loc.selector
|
|
61
|
-
end
|
|
51
|
+
range = Parser::Source::Range.new(node.loc.expression.source_buffer,
|
|
52
|
+
node.loc.dot.begin_pos,
|
|
53
|
+
node.loc.expression.end_pos)
|
|
62
54
|
|
|
63
55
|
lambda do |corrector|
|
|
64
|
-
range = Parser::Source::Range.new(node.loc.expression.source_buffer,
|
|
65
|
-
node.loc.dot.begin_pos,
|
|
66
|
-
node.loc.expression.end_pos)
|
|
67
56
|
corrector.remove(range)
|
|
68
|
-
corrector.replace(
|
|
57
|
+
corrector.replace(selector_loc, 'count')
|
|
69
58
|
end
|
|
70
59
|
end
|
|
71
60
|
|
|
72
61
|
private
|
|
73
62
|
|
|
74
63
|
def parse(node)
|
|
75
|
-
left,
|
|
76
|
-
expression,
|
|
77
|
-
|
|
64
|
+
left, counter = *node
|
|
65
|
+
expression, selector, params = *left
|
|
66
|
+
|
|
67
|
+
selector_loc =
|
|
68
|
+
if selector.is_a?(Symbol)
|
|
69
|
+
if expression && expression.parent.loc.respond_to?(:selector)
|
|
70
|
+
expression.parent.loc.selector
|
|
71
|
+
else
|
|
72
|
+
left.loc.selector if left.loc.respond_to?(:selector)
|
|
73
|
+
end
|
|
74
|
+
else
|
|
75
|
+
_enumerable, selector, params = *expression
|
|
76
|
+
|
|
77
|
+
expression.loc.selector if contains_selector?(expression)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
[selector, selector_loc, params, counter]
|
|
81
|
+
end
|
|
78
82
|
|
|
79
|
-
|
|
83
|
+
def contains_selector?(node)
|
|
84
|
+
node.respond_to?(:loc) && node.loc.respond_to?(:selector)
|
|
80
85
|
end
|
|
81
86
|
end
|
|
82
87
|
end
|
|
@@ -22,21 +22,24 @@ module RuboCop
|
|
|
22
22
|
REVERSE_MSG = 'Use `reverse.%s` instead of `%s.%s`.'
|
|
23
23
|
|
|
24
24
|
SELECT_METHODS = [:select, :find_all]
|
|
25
|
+
DANGEROUS_METHODS = [:first, :last]
|
|
25
26
|
|
|
26
27
|
def on_send(node)
|
|
27
28
|
receiver, second_method = *node
|
|
28
|
-
return unless second_method == :first || second_method == :last
|
|
29
29
|
return if receiver.nil?
|
|
30
|
+
return unless DANGEROUS_METHODS.include?(second_method)
|
|
30
31
|
|
|
31
32
|
receiver, _args, body = *receiver if receiver.block_type?
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
caller, first_method, args = *receiver
|
|
34
35
|
|
|
35
36
|
# check that we have usual block or block pass
|
|
36
37
|
return if body.nil? && (args.nil? || !args.block_pass_type?)
|
|
37
38
|
|
|
38
39
|
return unless SELECT_METHODS.include?(first_method)
|
|
39
40
|
|
|
41
|
+
return if lazy?(caller)
|
|
42
|
+
|
|
40
43
|
range = receiver.loc.selector.join(node.loc.selector)
|
|
41
44
|
|
|
42
45
|
message = second_method == :last ? REVERSE_MSG : MSG
|
|
@@ -54,7 +57,11 @@ module RuboCop
|
|
|
54
57
|
else
|
|
55
58
|
preferred_method
|
|
56
59
|
end
|
|
57
|
-
|
|
60
|
+
|
|
61
|
+
first_range = Parser::Source::Range.new(
|
|
62
|
+
receiver.loc.expression.source,
|
|
63
|
+
receiver.loc.end.end_pos,
|
|
64
|
+
receiver.loc.end.end_pos).join(node.loc.selector)
|
|
58
65
|
|
|
59
66
|
receiver, _args, _body = *receiver if receiver.block_type?
|
|
60
67
|
|
|
@@ -70,6 +77,12 @@ module RuboCop
|
|
|
70
77
|
config.for_cop('Style/CollectionMethods') \
|
|
71
78
|
['PreferredMethods']['detect'] || 'detect'
|
|
72
79
|
end
|
|
80
|
+
|
|
81
|
+
def lazy?(node)
|
|
82
|
+
return false if node.nil?
|
|
83
|
+
receiver, method, _args = *node
|
|
84
|
+
method == :lazy && !receiver.nil?
|
|
85
|
+
end
|
|
73
86
|
end
|
|
74
87
|
end
|
|
75
88
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Performance
|
|
6
|
+
# Do not compute the size of statically sized objects.
|
|
7
|
+
class FixedSize < Cop
|
|
8
|
+
MSG = 'Do not compute the size of statically sized objects.'.freeze
|
|
9
|
+
COUNTERS = [:count, :length, :size].freeze
|
|
10
|
+
STATIC_SIZED_TYPES = [:array, :hash, :str, :sym].freeze
|
|
11
|
+
|
|
12
|
+
def on_send(node)
|
|
13
|
+
variable, method, arg = *node
|
|
14
|
+
return unless variable
|
|
15
|
+
return unless COUNTERS.include?(method)
|
|
16
|
+
return unless STATIC_SIZED_TYPES.include?(variable.type)
|
|
17
|
+
return if contains_splat?(variable)
|
|
18
|
+
return if contains_double_splat?(variable)
|
|
19
|
+
return if string_argument?(arg)
|
|
20
|
+
if node.parent
|
|
21
|
+
return if node.parent.casgn_type? || node.parent.block_type?
|
|
22
|
+
end
|
|
23
|
+
add_offense(node, :expression)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def contains_splat?(node)
|
|
29
|
+
return unless node.array_type?
|
|
30
|
+
|
|
31
|
+
node.children.any? do |child|
|
|
32
|
+
child.respond_to?(:splat_type?) && child.splat_type?
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def contains_double_splat?(node)
|
|
37
|
+
return unless node.hash_type?
|
|
38
|
+
|
|
39
|
+
node.children.any? do |child|
|
|
40
|
+
child.respond_to?(:kwsplat_type?) && child.kwsplat_type?
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def string_argument?(node)
|
|
45
|
+
node && !node.str_type?
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -18,11 +18,11 @@ module RuboCop
|
|
|
18
18
|
MSG = 'Use `flat_map` instead of `%s...%s`.'
|
|
19
19
|
FLATTEN_MULTIPLE_LEVELS = ' Beware, `flat_map` only flattens 1 level ' \
|
|
20
20
|
'and `flatten` can be used to flatten ' \
|
|
21
|
-
'multiple levels'
|
|
21
|
+
'multiple levels.'
|
|
22
22
|
FLATTEN = [:flatten, :flatten!]
|
|
23
23
|
|
|
24
24
|
def on_send(node)
|
|
25
|
-
left, second_method, flatten_param
|
|
25
|
+
left, second_method, flatten_param = *node
|
|
26
26
|
return unless FLATTEN.include?(second_method)
|
|
27
27
|
flatten_level, = *flatten_param
|
|
28
28
|
expression, = *left
|
|
@@ -44,7 +44,7 @@ module RuboCop
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def autocorrect(node)
|
|
47
|
-
receiver, _flatten, flatten_param
|
|
47
|
+
receiver, _flatten, flatten_param = *node
|
|
48
48
|
flatten_level, = *flatten_param
|
|
49
49
|
return if flatten_level.nil?
|
|
50
50
|
|
|
@@ -1,89 +1,133 @@
|
|
|
1
|
-
# encoding:
|
|
1
|
+
# encoding: UTF-8
|
|
2
2
|
|
|
3
3
|
module RuboCop
|
|
4
4
|
module Cop
|
|
5
5
|
module Performance
|
|
6
|
-
# This cop is used to identify usages of `shuffle.first`
|
|
7
|
-
# change them to use `sample` instead.
|
|
6
|
+
# This cop is used to identify usages of `shuffle.first`, `shuffle.last`
|
|
7
|
+
# and `shuffle[]` and change them to use `sample` instead.
|
|
8
8
|
#
|
|
9
9
|
# @example
|
|
10
10
|
# # bad
|
|
11
11
|
# [1, 2, 3].shuffle.first
|
|
12
|
+
# [1, 2, 3].shuffle.first(2)
|
|
12
13
|
# [1, 2, 3].shuffle.last
|
|
13
|
-
# [1, 2, 3].shuffle[
|
|
14
|
-
# [1, 2, 3].shuffle[0,
|
|
15
|
-
# [1, 2, 3].shuffle
|
|
14
|
+
# [1, 2, 3].shuffle[2]
|
|
15
|
+
# [1, 2, 3].shuffle[0, 2] # sample(2) will do the same
|
|
16
|
+
# [1, 2, 3].shuffle[0..2] # sample(3) will do the same
|
|
17
|
+
# [1, 2, 3].shuffle(random: Random.new).first
|
|
16
18
|
#
|
|
17
19
|
# # good
|
|
18
20
|
# [1, 2, 3].shuffle
|
|
19
21
|
# [1, 2, 3].sample
|
|
20
22
|
# [1, 2, 3].sample(3)
|
|
21
|
-
# [1, 2, 3].sample(
|
|
23
|
+
# [1, 2, 3].shuffle[1, 3] # sample(3) might return a longer Array
|
|
24
|
+
# [1, 2, 3].shuffle[1..3] # sample(3) might return a longer Array
|
|
25
|
+
# [1, 2, 3].shuffle[foo, bar]
|
|
26
|
+
# [1, 2, 3].shuffle(random: Random.new)
|
|
22
27
|
class Sample < Cop
|
|
23
|
-
MSG = 'Use `
|
|
24
|
-
RANGE_TYPES = [:irange, :erange]
|
|
25
|
-
VALID_ARRAY_SELECTORS = [:first, :last, :[], nil]
|
|
28
|
+
MSG = 'Use `%<correct>s` instead of `%<incorrect>s`.'
|
|
26
29
|
|
|
27
30
|
def on_send(node)
|
|
28
|
-
|
|
29
|
-
return unless
|
|
30
|
-
|
|
31
|
-
return unless VALID_ARRAY_SELECTORS.include?(second_method)
|
|
32
|
-
return if second_method.nil? && params.nil?
|
|
33
|
-
|
|
34
|
-
add_offense(node, range_of_shuffle(node), message(node, params))
|
|
31
|
+
analyzer = ShuffleAnalyzer.new(node)
|
|
32
|
+
return unless analyzer.offensive?
|
|
33
|
+
add_offense(node, analyzer.source_range, analyzer.message)
|
|
35
34
|
end
|
|
36
35
|
|
|
37
36
|
def autocorrect(node)
|
|
38
|
-
|
|
39
|
-
_receiver, _method, params, selector = *node.parent if params.nil?
|
|
40
|
-
|
|
41
|
-
return if params && RANGE_TYPES.include?(params.type)
|
|
42
|
-
|
|
43
|
-
range = if params && (params.hash_type? || params.lvar_type?)
|
|
44
|
-
range_of_shuffle(node)
|
|
45
|
-
else
|
|
46
|
-
Parser::Source::Range.new(node.loc.expression.source_buffer,
|
|
47
|
-
node.loc.selector.begin_pos,
|
|
48
|
-
node.parent.loc.selector.end_pos)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
lambda do |corrector|
|
|
52
|
-
corrector.replace(range, 'sample')
|
|
53
|
-
return if selector.nil?
|
|
54
|
-
corrector.insert_after(range, "(#{selector.loc.expression.source})")
|
|
55
|
-
end
|
|
37
|
+
ShuffleAnalyzer.new(node).autocorrect
|
|
56
38
|
end
|
|
57
39
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
40
|
+
# An internal class for representing a shuffle + method node analyzer.
|
|
41
|
+
class ShuffleAnalyzer
|
|
42
|
+
def initialize(shuffle_node)
|
|
43
|
+
@shuffle_node = shuffle_node
|
|
44
|
+
@method_node = shuffle_node.parent
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def autocorrect
|
|
48
|
+
->(corrector) { corrector.replace(source_range, correct) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def message
|
|
52
|
+
format(MSG, correct: correct, incorrect: source_range.source)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def offensive?
|
|
56
|
+
shuffle_node.to_a[1] == :shuffle && corrigible?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def source_range
|
|
60
|
+
Parser::Source::Range.new(shuffle_node.loc.expression.source_buffer,
|
|
61
|
+
shuffle_node.loc.selector.begin_pos,
|
|
62
|
+
method_node.loc.expression.end_pos)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
attr_reader :method_node, :shuffle_node
|
|
68
|
+
|
|
69
|
+
def correct
|
|
70
|
+
args = [sample_arg, shuffle_arg].compact.join(', ')
|
|
71
|
+
args.empty? ? 'sample' : "sample(#{args})"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def corrigible?
|
|
75
|
+
case method
|
|
76
|
+
when :first, :last then true
|
|
77
|
+
when :[] then sample_size != :unknown
|
|
78
|
+
else false
|
|
69
79
|
end
|
|
70
|
-
else
|
|
71
|
-
format(MSG, shuffle_params(node))
|
|
72
80
|
end
|
|
73
|
-
end
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
node.loc.selector.end_pos)
|
|
79
|
-
end
|
|
82
|
+
def method
|
|
83
|
+
method_node.to_a[1]
|
|
84
|
+
end
|
|
80
85
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
def method_arg
|
|
87
|
+
_, _, arg = *method_node
|
|
88
|
+
arg.loc.expression.source if arg
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# FIXME: use Range#size once Ruby 1.9 support is dropped
|
|
92
|
+
def range_size(range_node)
|
|
93
|
+
vals = *range_node
|
|
94
|
+
return :unknown unless vals.all?(&:int_type?)
|
|
95
|
+
low, high = *vals.map(&:to_a).map(&:first)
|
|
96
|
+
return :unknown unless low.zero? && high >= 0
|
|
97
|
+
case range_node.type
|
|
98
|
+
when :erange then high - low
|
|
99
|
+
when :irange then high - low + 1
|
|
100
|
+
end
|
|
101
|
+
end
|
|
85
102
|
|
|
86
|
-
|
|
103
|
+
def sample_arg
|
|
104
|
+
case method
|
|
105
|
+
when :first, :last then method_arg
|
|
106
|
+
when :[] then sample_size
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def sample_size
|
|
111
|
+
_, _, *args = *method_node
|
|
112
|
+
case args.size
|
|
113
|
+
when 1
|
|
114
|
+
arg = args.first
|
|
115
|
+
case arg.type
|
|
116
|
+
when :erange, :irange then range_size(arg)
|
|
117
|
+
when :int then arg.to_a.first.zero? ? nil : :unknown
|
|
118
|
+
else :unknown
|
|
119
|
+
end
|
|
120
|
+
when 2
|
|
121
|
+
first, second = *args
|
|
122
|
+
return :unknown unless first.int_type? && first.to_a.first.zero?
|
|
123
|
+
second.int_type? ? second.to_a.first : :unknown
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def shuffle_arg
|
|
128
|
+
_, _, arg = *shuffle_node
|
|
129
|
+
arg.loc.expression.source if arg
|
|
130
|
+
end
|
|
87
131
|
end
|
|
88
132
|
end
|
|
89
133
|
end
|
|
@@ -27,12 +27,13 @@ module RuboCop
|
|
|
27
27
|
MSG = 'Use `size` instead of `count`.'
|
|
28
28
|
|
|
29
29
|
def on_send(node)
|
|
30
|
-
receiver, method = *node
|
|
30
|
+
receiver, method, args = *node
|
|
31
31
|
|
|
32
32
|
return if receiver.nil?
|
|
33
33
|
return unless method == :count
|
|
34
34
|
return unless array?(receiver) || hash?(receiver)
|
|
35
35
|
return if node.parent && node.parent.block_type?
|
|
36
|
+
return if args
|
|
36
37
|
|
|
37
38
|
add_offense(node, node.loc.selector)
|
|
38
39
|
end
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Performance
|
|
6
|
+
# This cop identifies places where `gsub` can be replaced by
|
|
7
|
+
# `tr` or `delete`.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# @bad
|
|
11
|
+
# 'abc'.gsub('b', 'd')
|
|
12
|
+
# 'abc'.gsub('a', '')
|
|
13
|
+
# 'abc'.gsub(/a/, 'd')
|
|
14
|
+
# 'abc'.gsub!('a', 'd')
|
|
15
|
+
#
|
|
16
|
+
# @good
|
|
17
|
+
# 'abc'.gsub(/.*/, 'a')
|
|
18
|
+
# 'abc'.gsub(/a+/, 'd')
|
|
19
|
+
# 'abc'.tr('b', 'd')
|
|
20
|
+
# 'a b c'.delete(' ')
|
|
21
|
+
class StringReplacement < Cop
|
|
22
|
+
MSG = 'Use `%s` instead of `%s`.'
|
|
23
|
+
DETERMINISTIC_REGEX = /^[\w\s\-,"']+$/.freeze
|
|
24
|
+
REGEXP_CONSTRUCTOR_METHODS = [:new, :compile].freeze
|
|
25
|
+
GSUB_METHODS = [:gsub, :gsub!].freeze
|
|
26
|
+
DETERMINISTIC_TYPES = [:regexp, :str, :send].freeze
|
|
27
|
+
DELETE = 'delete'.freeze
|
|
28
|
+
TR = 'tr'.freeze
|
|
29
|
+
BANG = '!'.freeze
|
|
30
|
+
SINGLE_QUOTE = "'".freeze
|
|
31
|
+
|
|
32
|
+
def on_send(node)
|
|
33
|
+
_string, method, first_param, second_param = *node
|
|
34
|
+
return unless GSUB_METHODS.include?(method)
|
|
35
|
+
return unless string?(second_param)
|
|
36
|
+
return unless DETERMINISTIC_TYPES.include?(first_param.type)
|
|
37
|
+
|
|
38
|
+
first_source, options = first_source(first_param)
|
|
39
|
+
second_source, = *second_param
|
|
40
|
+
|
|
41
|
+
return if first_source.nil?
|
|
42
|
+
|
|
43
|
+
if regex?(first_param)
|
|
44
|
+
return unless first_source =~ DETERMINISTIC_REGEX
|
|
45
|
+
return if options
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
return if first_source.length != 1
|
|
49
|
+
return unless second_source.length <= 1
|
|
50
|
+
|
|
51
|
+
message = message(method, first_source, second_source)
|
|
52
|
+
add_offense(node, range(node), message)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def autocorrect(node)
|
|
56
|
+
_string, method, first_param, second_param = *node
|
|
57
|
+
first_source, = first_source(first_param)
|
|
58
|
+
second_source, = *second_param
|
|
59
|
+
replacement_method = replacement_method(method,
|
|
60
|
+
first_source,
|
|
61
|
+
second_source)
|
|
62
|
+
|
|
63
|
+
lambda do |corrector|
|
|
64
|
+
corrector.replace(node.loc.selector, replacement_method)
|
|
65
|
+
unless first_param.str_type?
|
|
66
|
+
corrector.replace(first_param.loc.expression,
|
|
67
|
+
escape(first_source))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
if second_source.empty? && first_source.length == 1
|
|
71
|
+
remove_second_param(corrector, node, first_param)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def string?(node)
|
|
79
|
+
node && node.str_type?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def first_source(first_param)
|
|
83
|
+
case first_param.type
|
|
84
|
+
when :regexp, :send
|
|
85
|
+
return nil unless regex?(first_param)
|
|
86
|
+
|
|
87
|
+
source, options = extract_source(first_param)
|
|
88
|
+
when :str
|
|
89
|
+
source, = *first_param
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
[source, options]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def extract_source(node)
|
|
96
|
+
case node.type
|
|
97
|
+
when :regexp
|
|
98
|
+
source_from_regex_literal(node)
|
|
99
|
+
when :send
|
|
100
|
+
source_from_regex_constructor(node)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def source_from_regex_literal(node)
|
|
105
|
+
regex, options = *node
|
|
106
|
+
source, = *regex
|
|
107
|
+
options, = *options
|
|
108
|
+
[source, options]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def source_from_regex_constructor(node)
|
|
112
|
+
_const, _init, regex = *node
|
|
113
|
+
case regex.type
|
|
114
|
+
when :regexp
|
|
115
|
+
source_from_regex_literal(regex)
|
|
116
|
+
when :str
|
|
117
|
+
source, = *regex
|
|
118
|
+
source
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def regex?(node)
|
|
123
|
+
return true if node.regexp_type?
|
|
124
|
+
|
|
125
|
+
const, init, = *node
|
|
126
|
+
_, klass = *const
|
|
127
|
+
|
|
128
|
+
klass == :Regexp && REGEXP_CONSTRUCTOR_METHODS.include?(init)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def range(node)
|
|
132
|
+
Parser::Source::Range.new(node.loc.expression.source_buffer,
|
|
133
|
+
node.loc.selector.begin_pos,
|
|
134
|
+
node.loc.expression.end_pos)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def replacement_method(method, first_source, second_source)
|
|
138
|
+
replacement = if second_source.empty? && first_source.length == 1
|
|
139
|
+
DELETE
|
|
140
|
+
else
|
|
141
|
+
TR
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
"#{replacement}#{BANG if bang_method?(method)}"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def message(method, first_source, second_source)
|
|
148
|
+
replacement_method = replacement_method(method,
|
|
149
|
+
first_source,
|
|
150
|
+
second_source)
|
|
151
|
+
|
|
152
|
+
format(MSG, replacement_method, method)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def bang_method?(method)
|
|
156
|
+
method.to_s.end_with?(BANG)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def escape(string)
|
|
160
|
+
if require_double_quotes?(string)
|
|
161
|
+
string.inspect
|
|
162
|
+
else
|
|
163
|
+
"'#{string}'"
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def require_double_quotes?(string)
|
|
168
|
+
string.inspect.include?(SINGLE_QUOTE) ||
|
|
169
|
+
StringHelp::ESCAPED_CHAR_REGEXP =~ string
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def method_suffix(node)
|
|
173
|
+
node.loc.end ? node.loc.end.source : ''
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def remove_second_param(corrector, node, first_param)
|
|
177
|
+
end_range =
|
|
178
|
+
Parser::Source::Range.new(node.loc.expression.source_buffer,
|
|
179
|
+
first_param.loc.expression.end_pos,
|
|
180
|
+
node.loc.expression.end_pos)
|
|
181
|
+
|
|
182
|
+
corrector.replace(end_range, method_suffix(node))
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
@@ -12,11 +12,37 @@ module RuboCop
|
|
|
12
12
|
|
|
13
13
|
MSG = 'Prefer `%s` over `%s`.'
|
|
14
14
|
|
|
15
|
-
FILTER_METHODS = [
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
FILTER_METHODS = [
|
|
16
|
+
:after_filter,
|
|
17
|
+
:append_after_filter,
|
|
18
|
+
:append_around_filter,
|
|
19
|
+
:append_before_filter,
|
|
20
|
+
:around_filter,
|
|
21
|
+
:before_filter,
|
|
22
|
+
:prepend_after_filter,
|
|
23
|
+
:prepend_around_filter,
|
|
24
|
+
:prepend_before_filter,
|
|
25
|
+
:skip_after_filter,
|
|
26
|
+
:skip_around_filter,
|
|
27
|
+
:skip_before_filter,
|
|
28
|
+
:skip_filter
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
ACTION_METHODS = [
|
|
32
|
+
:after_action,
|
|
33
|
+
:append_after_action,
|
|
34
|
+
:append_around_action,
|
|
35
|
+
:append_before_action,
|
|
36
|
+
:around_action,
|
|
37
|
+
:before_action,
|
|
38
|
+
:prepend_after_action,
|
|
39
|
+
:prepend_around_action,
|
|
40
|
+
:prepend_before_action,
|
|
41
|
+
:skip_after_action,
|
|
42
|
+
:skip_around_action,
|
|
43
|
+
:skip_before_action,
|
|
44
|
+
:skip_action_callback
|
|
45
|
+
]
|
|
20
46
|
|
|
21
47
|
def on_block(node)
|
|
22
48
|
method, _args, _body = *node
|