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.
Files changed (177) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +315 -0
  3. data/README.md +199 -38
  4. data/config/default.yml +91 -12
  5. data/config/disabled.yml +45 -4
  6. data/config/enabled.yml +107 -9
  7. data/lib/rubocop/ast_node.rb +48 -0
  8. data/lib/rubocop/cli.rb +11 -1
  9. data/lib/rubocop/comment_config.rb +4 -1
  10. data/lib/rubocop/config.rb +26 -17
  11. data/lib/rubocop/config_loader.rb +61 -14
  12. data/lib/rubocop/cop/commissioner.rb +7 -12
  13. data/lib/rubocop/cop/cop.rb +43 -20
  14. data/lib/rubocop/cop/lint/block_alignment.rb +1 -1
  15. data/lib/rubocop/cop/lint/circular_argument_reference.rb +69 -0
  16. data/lib/rubocop/cop/lint/debugger.rb +9 -48
  17. data/lib/rubocop/cop/lint/def_end_alignment.rb +8 -4
  18. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +42 -23
  19. data/lib/rubocop/cop/lint/duplicate_methods.rb +2 -2
  20. data/lib/rubocop/cop/lint/duplicated_key.rb +37 -0
  21. data/lib/rubocop/cop/lint/end_alignment.rb +33 -13
  22. data/lib/rubocop/cop/lint/eval.rb +6 -2
  23. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +175 -0
  24. data/lib/rubocop/cop/lint/literal_in_condition.rb +0 -5
  25. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +10 -0
  26. data/lib/rubocop/cop/lint/nested_method_definition.rb +31 -0
  27. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +19 -1
  28. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +1 -1
  29. data/lib/rubocop/cop/lint/space_before_first_arg.rb +1 -1
  30. data/lib/rubocop/cop/lint/unneeded_disable.rb +72 -0
  31. data/lib/rubocop/cop/lint/unused_block_argument.rb +6 -0
  32. data/lib/rubocop/cop/lint/unused_method_argument.rb +8 -0
  33. data/lib/rubocop/cop/metrics/abc_size.rb +17 -6
  34. data/lib/rubocop/cop/metrics/class_length.rb +1 -1
  35. data/lib/rubocop/cop/metrics/method_length.rb +1 -3
  36. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  37. data/lib/rubocop/cop/metrics/parameter_lists.rb +1 -1
  38. data/lib/rubocop/cop/mixin/access_modifier_node.rb +1 -1
  39. data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -2
  40. data/lib/rubocop/cop/mixin/autocorrect_alignment.rb +28 -4
  41. data/lib/rubocop/cop/mixin/autocorrect_unless_changing_ast.rb +26 -3
  42. data/lib/rubocop/cop/mixin/check_assignment.rb +2 -3
  43. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +59 -12
  44. data/lib/rubocop/cop/mixin/configurable_max.rb +1 -1
  45. data/lib/rubocop/cop/mixin/configurable_naming.rb +14 -3
  46. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +1 -3
  47. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +10 -1
  48. data/lib/rubocop/cop/mixin/first_element_line_break.rb +41 -0
  49. data/lib/rubocop/cop/mixin/if_node.rb +10 -0
  50. data/lib/rubocop/cop/mixin/method_preference.rb +28 -0
  51. data/lib/rubocop/cop/mixin/negative_conditional.rb +1 -1
  52. data/lib/rubocop/cop/mixin/on_method_def.rb +4 -5
  53. data/lib/rubocop/cop/mixin/safe_assignment.rb +3 -14
  54. data/lib/rubocop/cop/mixin/space_after_punctuation.rb +8 -1
  55. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +8 -1
  56. data/lib/rubocop/cop/mixin/statement_modifier.rb +4 -7
  57. data/lib/rubocop/cop/mixin/string_help.rb +1 -1
  58. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -1
  59. data/lib/rubocop/cop/mixin/surrounding_space.rb +5 -4
  60. data/lib/rubocop/cop/offense.rb +16 -3
  61. data/lib/rubocop/cop/performance/case_when_splat.rb +160 -0
  62. data/lib/rubocop/cop/performance/count.rb +35 -30
  63. data/lib/rubocop/cop/performance/detect.rb +16 -3
  64. data/lib/rubocop/cop/performance/fixed_size.rb +50 -0
  65. data/lib/rubocop/cop/performance/flat_map.rb +3 -3
  66. data/lib/rubocop/cop/performance/sample.rb +103 -59
  67. data/lib/rubocop/cop/performance/size.rb +2 -1
  68. data/lib/rubocop/cop/performance/string_replacement.rb +187 -0
  69. data/lib/rubocop/cop/rails/action_filter.rb +31 -5
  70. data/lib/rubocop/cop/rails/date.rb +15 -14
  71. data/lib/rubocop/cop/rails/pluralization_grammar.rb +97 -0
  72. data/lib/rubocop/cop/rails/read_write_attribute.rb +1 -1
  73. data/lib/rubocop/cop/rails/time_zone.rb +46 -18
  74. data/lib/rubocop/cop/style/alias.rb +1 -0
  75. data/lib/rubocop/cop/style/align_hash.rb +8 -15
  76. data/lib/rubocop/cop/style/align_parameters.rb +19 -7
  77. data/lib/rubocop/cop/style/and_or.rb +42 -13
  78. data/lib/rubocop/cop/style/auto_resource_cleanup.rb +2 -1
  79. data/lib/rubocop/cop/style/block_comments.rb +4 -2
  80. data/lib/rubocop/cop/style/block_delimiters.rb +69 -24
  81. data/lib/rubocop/cop/style/braces_around_hash_parameters.rb +40 -12
  82. data/lib/rubocop/cop/style/case_indentation.rb +18 -4
  83. data/lib/rubocop/cop/style/collection_methods.rb +2 -20
  84. data/lib/rubocop/cop/style/command_literal.rb +2 -10
  85. data/lib/rubocop/cop/style/comment_annotation.rb +29 -8
  86. data/lib/rubocop/cop/style/copyright.rb +5 -3
  87. data/lib/rubocop/cop/style/documentation.rb +21 -12
  88. data/lib/rubocop/cop/style/dot_position.rb +6 -0
  89. data/lib/rubocop/cop/style/double_negation.rb +4 -15
  90. data/lib/rubocop/cop/style/each_with_object.rb +17 -4
  91. data/lib/rubocop/cop/style/else_alignment.rb +2 -1
  92. data/lib/rubocop/cop/style/empty_else.rb +25 -0
  93. data/lib/rubocop/cop/style/empty_line_between_defs.rb +39 -14
  94. data/lib/rubocop/cop/style/encoding.rb +10 -4
  95. data/lib/rubocop/cop/style/extra_spacing.rb +126 -5
  96. data/lib/rubocop/cop/style/first_array_element_line_break.rb +41 -0
  97. data/lib/rubocop/cop/style/first_hash_element_line_break.rb +35 -0
  98. data/lib/rubocop/cop/style/first_method_argument_line_break.rb +37 -0
  99. data/lib/rubocop/cop/style/first_method_parameter_line_break.rb +42 -0
  100. data/lib/rubocop/cop/style/first_parameter_indentation.rb +5 -3
  101. data/lib/rubocop/cop/style/for.rb +2 -1
  102. data/lib/rubocop/cop/style/hash_syntax.rb +5 -0
  103. data/lib/rubocop/cop/style/if_unless_modifier.rb +32 -5
  104. data/lib/rubocop/cop/style/indent_hash.rb +67 -37
  105. data/lib/rubocop/cop/style/indentation_width.rb +36 -10
  106. data/lib/rubocop/cop/style/initial_indentation.rb +37 -0
  107. data/lib/rubocop/cop/style/leading_comment_space.rb +3 -2
  108. data/lib/rubocop/cop/style/method_call_parentheses.rb +28 -1
  109. data/lib/rubocop/cop/style/method_def_parentheses.rb +10 -7
  110. data/lib/rubocop/cop/style/multiline_operation_indentation.rb +21 -24
  111. data/lib/rubocop/cop/style/mutable_constant.rb +35 -0
  112. data/lib/rubocop/cop/style/nested_modifier.rb +97 -0
  113. data/lib/rubocop/cop/style/next.rb +50 -15
  114. data/lib/rubocop/cop/style/non_nil_check.rb +12 -8
  115. data/lib/rubocop/cop/style/one_line_conditional.rb +8 -4
  116. data/lib/rubocop/cop/style/option_hash.rb +64 -0
  117. data/lib/rubocop/cop/style/optional_arguments.rb +49 -0
  118. data/lib/rubocop/cop/style/parallel_assignment.rb +218 -0
  119. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +3 -66
  120. data/lib/rubocop/cop/style/predicate_name.rb +7 -2
  121. data/lib/rubocop/cop/style/redundant_begin.rb +2 -13
  122. data/lib/rubocop/cop/style/redundant_freeze.rb +37 -0
  123. data/lib/rubocop/cop/style/redundant_return.rb +32 -3
  124. data/lib/rubocop/cop/style/regexp_literal.rb +2 -10
  125. data/lib/rubocop/cop/style/rescue_ensure_alignment.rb +81 -0
  126. data/lib/rubocop/cop/style/rescue_modifier.rb +30 -22
  127. data/lib/rubocop/cop/style/send.rb +18 -0
  128. data/lib/rubocop/cop/style/signal_exception.rb +24 -11
  129. data/lib/rubocop/cop/style/single_line_methods.rb +8 -9
  130. data/lib/rubocop/cop/style/single_space_before_first_arg.rb +1 -1
  131. data/lib/rubocop/cop/style/space_around_operators.rb +2 -0
  132. data/lib/rubocop/cop/style/space_inside_string_interpolation.rb +61 -0
  133. data/lib/rubocop/cop/style/special_global_vars.rb +4 -2
  134. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +108 -0
  135. data/lib/rubocop/cop/style/string_methods.rb +32 -0
  136. data/lib/rubocop/cop/style/struct_inheritance.rb +11 -10
  137. data/lib/rubocop/cop/style/symbol_literal.rb +1 -1
  138. data/lib/rubocop/cop/style/symbol_proc.rb +62 -13
  139. data/lib/rubocop/cop/style/trailing_blank_lines.rb +9 -1
  140. data/lib/rubocop/cop/style/trailing_comma.rb +17 -7
  141. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -2
  142. data/lib/rubocop/cop/style/trivial_accessors.rb +10 -1
  143. data/lib/rubocop/cop/style/unneeded_percent_q.rb +31 -20
  144. data/lib/rubocop/cop/style/variable_name.rb +5 -0
  145. data/lib/rubocop/cop/style/while_until_do.rb +1 -1
  146. data/lib/rubocop/cop/style/word_array.rb +15 -2
  147. data/lib/rubocop/cop/team.rb +25 -5
  148. data/lib/rubocop/cop/util.rb +7 -2
  149. data/lib/rubocop/cop/variable_force/locatable.rb +6 -6
  150. data/lib/rubocop/cop/variable_force.rb +10 -10
  151. data/lib/rubocop/formatter/base_formatter.rb +1 -1
  152. data/lib/rubocop/formatter/disabled_config_formatter.rb +70 -8
  153. data/lib/rubocop/formatter/formatter_set.rb +27 -1
  154. data/lib/rubocop/formatter/progress_formatter.rb +10 -2
  155. data/lib/rubocop/formatter/simple_text_formatter.rb +1 -1
  156. data/lib/rubocop/node_pattern.rb +390 -0
  157. data/lib/rubocop/options.rb +148 -81
  158. data/lib/rubocop/processed_source.rb +7 -2
  159. data/lib/rubocop/rake_task.rb +1 -1
  160. data/lib/rubocop/remote_config.rb +60 -0
  161. data/lib/rubocop/result_cache.rb +123 -0
  162. data/lib/rubocop/runner.rb +85 -22
  163. data/lib/rubocop/target_finder.rb +4 -4
  164. data/lib/rubocop/token.rb +2 -1
  165. data/lib/rubocop/version.rb +1 -1
  166. data/lib/rubocop/warning.rb +11 -0
  167. data/lib/rubocop.rb +32 -3
  168. data/relnotes/v0.32.0.md +139 -0
  169. data/relnotes/v0.32.1.md +122 -0
  170. data/relnotes/v0.33.0.md +157 -0
  171. data/relnotes/v0.34.0.md +182 -0
  172. data/relnotes/v0.34.1.md +129 -0
  173. data/relnotes/v0.34.2.md +139 -0
  174. data/relnotes/v0.35.0.md +210 -0
  175. data/rubocop.gemspec +4 -4
  176. metadata +50 -12
  177. 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
- expression, first_method, second_method, third_method = parse(node)
31
-
32
- return unless COUNTERS.include?(third_method)
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
- expression, first_method, second_method, = parse(node)
47
+ selector, selector_loc = parse(node)
54
48
 
55
- return if first_method == :reject || second_method == :reject
49
+ return if selector == :reject
56
50
 
57
- selector = if SELECTORS.include?(first_method)
58
- expression.loc.selector
59
- else
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(selector, 'count')
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, third_method = *node
76
- expression, second_method = *left
77
- _enumerable, first_method = *expression
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
- [expression, first_method, second_method, third_method]
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
- _, first_method, args = *receiver
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
- first_range = node.loc.dot.join(node.loc.selector)
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 = *node
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 = *node
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: utf-8
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` and
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[0]
14
- # [1, 2, 3].shuffle[0, 3]
15
- # [1, 2, 3].shuffle(random: Random.new(1))
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(random: Random.new(1))
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 `sample` instead of `shuffle%s`.'
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
- _receiver, first_method, params, = *node
29
- return unless first_method == :shuffle
30
- _receiver, second_method, params, = *node.parent if params.nil?
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
- _receiver, _method, params, selector = *node
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
- private
59
-
60
- def message(node, params)
61
- if params && params.lvar_type?
62
- format(MSG, shuffle_params(node))
63
- elsif node.parent
64
- _params, selector = *node.parent
65
- if selector == :[]
66
- format(MSG, node.parent.loc.selector.source)
67
- else
68
- format(MSG, ".#{node.parent.loc.selector.source}")
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
- def range_of_shuffle(node)
76
- Parser::Source::Range.new(node.loc.expression.source_buffer,
77
- node.loc.selector.begin_pos,
78
- node.loc.selector.end_pos)
79
- end
82
+ def method
83
+ method_node.to_a[1]
84
+ end
80
85
 
81
- def shuffle_params(node)
82
- params = Parser::Source::Range.new(node.loc.expression.source_buffer,
83
- node.loc.selector.end_pos,
84
- node.loc.expression.end_pos)
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
- params.source
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 = [:before_filter, :skip_before_filter,
16
- :after_filter, :around_filter]
17
-
18
- ACTION_METHODS = [:before_action, :skip_before_action,
19
- :after_action, :around_action]
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