rubocop 0.46.0 → 0.47.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rubocop might be problematic. Click here for more details.

Files changed (214) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +77 -2
  4. data/config/default.yml +151 -74
  5. data/config/disabled.yml +9 -0
  6. data/config/enabled.yml +49 -9
  7. data/lib/rubocop.rb +36 -8
  8. data/lib/rubocop/ast/builder.rb +59 -0
  9. data/lib/rubocop/ast/node.rb +607 -0
  10. data/lib/rubocop/ast/node/array_node.rb +45 -0
  11. data/lib/rubocop/ast/node/case_node.rb +63 -0
  12. data/lib/rubocop/ast/node/for_node.rb +53 -0
  13. data/lib/rubocop/ast/node/hash_node.rb +102 -0
  14. data/lib/rubocop/ast/node/if_node.rb +136 -0
  15. data/lib/rubocop/ast/node/keyword_splat_node.rb +45 -0
  16. data/lib/rubocop/ast/node/mixin/conditional_node.rb +45 -0
  17. data/lib/rubocop/ast/node/mixin/hash_element_node.rb +125 -0
  18. data/lib/rubocop/ast/node/mixin/modifier_node.rb +17 -0
  19. data/lib/rubocop/ast/node/pair_node.rb +64 -0
  20. data/lib/rubocop/ast/node/until_node.rb +43 -0
  21. data/lib/rubocop/ast/node/when_node.rb +61 -0
  22. data/lib/rubocop/ast/node/while_node.rb +43 -0
  23. data/lib/rubocop/ast/sexp.rb +16 -0
  24. data/lib/rubocop/{ast_node → ast}/traversal.rb +1 -1
  25. data/lib/rubocop/cli.rb +18 -14
  26. data/lib/rubocop/comment_config.rb +1 -3
  27. data/lib/rubocop/config.rb +93 -35
  28. data/lib/rubocop/config_loader.rb +1 -1
  29. data/lib/rubocop/cop/badge.rb +73 -0
  30. data/lib/rubocop/cop/bundler/duplicated_gem.rb +2 -2
  31. data/lib/rubocop/cop/bundler/ordered_gems.rb +43 -3
  32. data/lib/rubocop/cop/commissioner.rb +17 -6
  33. data/lib/rubocop/cop/cop.rb +25 -112
  34. data/lib/rubocop/cop/lint/ambiguous_operator.rb +9 -4
  35. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +7 -0
  36. data/lib/rubocop/cop/lint/assignment_in_condition.rb +18 -4
  37. data/lib/rubocop/cop/lint/block_alignment.rb +40 -9
  38. data/lib/rubocop/cop/lint/circular_argument_reference.rb +14 -0
  39. data/lib/rubocop/cop/lint/condition_position.rb +14 -16
  40. data/lib/rubocop/cop/lint/debugger.rb +28 -0
  41. data/lib/rubocop/cop/lint/def_end_alignment.rb +21 -1
  42. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +13 -1
  43. data/lib/rubocop/cop/lint/duplicate_case_condition.rb +26 -22
  44. data/lib/rubocop/cop/lint/duplicate_methods.rb +15 -1
  45. data/lib/rubocop/cop/lint/duplicated_key.rb +16 -8
  46. data/lib/rubocop/cop/lint/each_with_object_argument.rb +9 -0
  47. data/lib/rubocop/cop/lint/else_layout.rb +26 -29
  48. data/lib/rubocop/cop/lint/empty_ensure.rb +38 -0
  49. data/lib/rubocop/cop/lint/empty_expression.rb +11 -1
  50. data/lib/rubocop/cop/lint/empty_interpolation.rb +8 -0
  51. data/lib/rubocop/cop/lint/empty_when.rb +14 -16
  52. data/lib/rubocop/cop/lint/end_alignment.rb +48 -28
  53. data/lib/rubocop/cop/lint/end_in_method.rb +23 -0
  54. data/lib/rubocop/cop/lint/ensure_return.rb +21 -0
  55. data/lib/rubocop/cop/lint/float_out_of_range.rb +5 -0
  56. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +29 -4
  57. data/lib/rubocop/cop/lint/handle_exceptions.rb +40 -0
  58. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +7 -2
  59. data/lib/rubocop/cop/lint/ineffective_access_modifier.rb +11 -2
  60. data/lib/rubocop/cop/lint/invalid_character_literal.rb +3 -0
  61. data/lib/rubocop/cop/lint/literal_in_condition.rb +34 -36
  62. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +8 -0
  63. data/lib/rubocop/cop/lint/loop.rb +36 -0
  64. data/lib/rubocop/cop/lint/multiple_compare.rb +46 -0
  65. data/lib/rubocop/cop/lint/nested_method_definition.rb +22 -0
  66. data/lib/rubocop/cop/lint/next_without_accumulator.rb +5 -0
  67. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +8 -0
  68. data/lib/rubocop/cop/lint/percent_string_array.rb +27 -13
  69. data/lib/rubocop/cop/lint/percent_symbol_array.rb +14 -4
  70. data/lib/rubocop/cop/lint/rand_one.rb +7 -3
  71. data/lib/rubocop/cop/lint/require_parentheses.rb +20 -19
  72. data/lib/rubocop/cop/lint/rescue_exception.rb +20 -0
  73. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +66 -0
  74. data/lib/rubocop/cop/lint/shadowed_exception.rb +6 -1
  75. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +24 -0
  76. data/lib/rubocop/cop/lint/string_conversion_in_interpolation.rb +8 -0
  77. data/lib/rubocop/cop/lint/underscore_prefixed_variable_name.rb +24 -0
  78. data/lib/rubocop/cop/lint/unified_integer.rb +5 -0
  79. data/lib/rubocop/cop/lint/unneeded_disable.rb +2 -2
  80. data/lib/rubocop/cop/lint/unneeded_splat_expansion.rb +5 -0
  81. data/lib/rubocop/cop/lint/unreachable_code.rb +17 -0
  82. data/lib/rubocop/cop/lint/unused_block_argument.rb +2 -0
  83. data/lib/rubocop/cop/lint/unused_method_argument.rb +10 -0
  84. data/lib/rubocop/cop/lint/useless_access_modifier.rb +28 -1
  85. data/lib/rubocop/cop/lint/useless_assignment.rb +18 -0
  86. data/lib/rubocop/cop/lint/useless_comparison.rb +3 -1
  87. data/lib/rubocop/cop/lint/useless_else_without_rescue.rb +16 -1
  88. data/lib/rubocop/cop/lint/useless_setter_call.rb +16 -4
  89. data/lib/rubocop/cop/lint/void.rb +52 -0
  90. data/lib/rubocop/cop/message_annotator.rb +102 -0
  91. data/lib/rubocop/cop/metrics/block_length.rb +6 -0
  92. data/lib/rubocop/cop/metrics/block_nesting.rb +17 -5
  93. data/lib/rubocop/cop/metrics/line_length.rb +11 -4
  94. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -2
  95. data/lib/rubocop/cop/mixin/array_syntax.rb +2 -11
  96. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +12 -5
  97. data/lib/rubocop/cop/mixin/configurable_formatting.rb +48 -0
  98. data/lib/rubocop/cop/mixin/configurable_max.rb +3 -3
  99. data/lib/rubocop/cop/mixin/configurable_naming.rb +5 -33
  100. data/lib/rubocop/cop/mixin/configurable_numbering.rb +6 -47
  101. data/lib/rubocop/cop/mixin/documentation_comment.rb +7 -1
  102. data/lib/rubocop/cop/mixin/duplication.rb +46 -0
  103. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +2 -2
  104. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +14 -11
  105. data/lib/rubocop/cop/mixin/hash_alignment.rb +114 -0
  106. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +3 -3
  107. data/lib/rubocop/cop/mixin/negative_conditional.rb +21 -7
  108. data/lib/rubocop/cop/mixin/on_method_def.rb +14 -0
  109. data/lib/rubocop/cop/mixin/on_normal_if_unless.rb +1 -24
  110. data/lib/rubocop/cop/mixin/statement_modifier.rb +8 -13
  111. data/lib/rubocop/cop/mixin/target_ruby_version.rb +16 -0
  112. data/lib/rubocop/cop/mixin/trailing_comma.rb +2 -3
  113. data/lib/rubocop/cop/offense.rb +1 -1
  114. data/lib/rubocop/cop/performance/case_when_splat.rb +56 -59
  115. data/lib/rubocop/cop/performance/detect.rb +2 -2
  116. data/lib/rubocop/cop/performance/flat_map.rb +3 -3
  117. data/lib/rubocop/cop/performance/redundant_merge.rb +3 -6
  118. data/lib/rubocop/cop/performance/regexp_match.rb +201 -0
  119. data/lib/rubocop/cop/rails/delegate.rb +2 -2
  120. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +10 -19
  121. data/lib/rubocop/cop/rails/enum_uniqueness.rb +12 -40
  122. data/lib/rubocop/cop/rails/file_path.rb +80 -0
  123. data/lib/rubocop/cop/rails/find_each.rb +5 -14
  124. data/lib/rubocop/cop/rails/http_positional_arguments.rb +30 -24
  125. data/lib/rubocop/cop/rails/not_null_column.rb +23 -0
  126. data/lib/rubocop/cop/rails/reversible_migration.rb +217 -0
  127. data/lib/rubocop/cop/rails/safe_navigation.rb +4 -2
  128. data/lib/rubocop/cop/rails/skips_model_validations.rb +46 -0
  129. data/lib/rubocop/cop/rails/time_zone.rb +1 -1
  130. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +7 -5
  131. data/lib/rubocop/cop/registry.rb +170 -0
  132. data/lib/rubocop/cop/{lint → security}/eval.rb +7 -1
  133. data/lib/rubocop/cop/security/marshal_load.rb +33 -0
  134. data/lib/rubocop/cop/security/yaml_load.rb +37 -0
  135. data/lib/rubocop/cop/style/align_hash.rb +138 -169
  136. data/lib/rubocop/cop/style/and_or.rb +1 -1
  137. data/lib/rubocop/cop/style/braces_around_hash_parameters.rb +10 -15
  138. data/lib/rubocop/cop/style/case_indentation.rb +36 -27
  139. data/lib/rubocop/cop/style/conditional_assignment.rb +64 -47
  140. data/lib/rubocop/cop/style/each_with_object.rb +4 -1
  141. data/lib/rubocop/cop/style/else_alignment.rb +14 -20
  142. data/lib/rubocop/cop/style/empty_case_condition.rb +16 -25
  143. data/lib/rubocop/cop/style/empty_else.rb +20 -22
  144. data/lib/rubocop/cop/style/empty_literal.rb +4 -4
  145. data/lib/rubocop/cop/style/empty_method.rb +12 -6
  146. data/lib/rubocop/cop/style/encoding.rb +1 -1
  147. data/lib/rubocop/cop/style/file_name.rb +24 -4
  148. data/lib/rubocop/cop/style/first_method_argument_line_break.rb +1 -1
  149. data/lib/rubocop/cop/style/format_string.rb +17 -48
  150. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +40 -11
  151. data/lib/rubocop/cop/style/guard_clause.rb +11 -17
  152. data/lib/rubocop/cop/style/hash_syntax.rb +24 -42
  153. data/lib/rubocop/cop/style/identical_conditional_branches.rb +40 -28
  154. data/lib/rubocop/cop/style/if_inside_else.rb +6 -9
  155. data/lib/rubocop/cop/style/if_unless_modifier.rb +16 -25
  156. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +3 -9
  157. data/lib/rubocop/cop/style/indent_array.rb +1 -1
  158. data/lib/rubocop/cop/style/indentation_width.rb +29 -60
  159. data/lib/rubocop/cop/style/infinite_loop.rb +21 -22
  160. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +86 -0
  161. data/lib/rubocop/cop/style/{method_call_parentheses.rb → method_call_without_args_parentheses.rb} +8 -1
  162. data/lib/rubocop/cop/style/missing_else.rb +40 -14
  163. data/lib/rubocop/cop/style/multiline_if_modifier.rb +5 -15
  164. data/lib/rubocop/cop/style/multiline_if_then.rb +14 -8
  165. data/lib/rubocop/cop/style/multiline_method_call_indentation.rb +3 -3
  166. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +1 -5
  167. data/lib/rubocop/cop/style/mutable_constant.rb +3 -2
  168. data/lib/rubocop/cop/style/negated_if.rb +3 -19
  169. data/lib/rubocop/cop/style/negated_while.rb +2 -17
  170. data/lib/rubocop/cop/style/nested_modifier.rb +16 -43
  171. data/lib/rubocop/cop/style/nested_ternary_operator.rb +3 -5
  172. data/lib/rubocop/cop/style/next.rb +23 -21
  173. data/lib/rubocop/cop/style/non_nil_check.rb +2 -3
  174. data/lib/rubocop/cop/style/not.rb +1 -3
  175. data/lib/rubocop/cop/style/numeric_literals.rb +2 -2
  176. data/lib/rubocop/cop/style/one_line_conditional.rb +12 -22
  177. data/lib/rubocop/cop/style/option_hash.rb +4 -15
  178. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -3
  179. data/lib/rubocop/cop/style/parentheses_around_condition.rb +8 -12
  180. data/lib/rubocop/cop/style/percent_q_literals.rb +15 -12
  181. data/lib/rubocop/cop/style/redundant_freeze.rb +3 -2
  182. data/lib/rubocop/cop/style/redundant_parentheses.rb +27 -4
  183. data/lib/rubocop/cop/style/redundant_return.rb +4 -8
  184. data/lib/rubocop/cop/style/safe_navigation.rb +13 -6
  185. data/lib/rubocop/cop/style/space_after_colon.rb +2 -4
  186. data/lib/rubocop/cop/style/space_around_block_parameters.rb +1 -1
  187. data/lib/rubocop/cop/style/space_around_operators.rb +15 -13
  188. data/lib/rubocop/cop/style/string_methods.rb +1 -3
  189. data/lib/rubocop/cop/style/symbol_array.rb +1 -5
  190. data/lib/rubocop/cop/style/ternary_parentheses.rb +5 -6
  191. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +2 -5
  192. data/lib/rubocop/cop/style/trailing_comma_in_literal.rb +1 -1
  193. data/lib/rubocop/cop/style/unless_else.rb +1 -5
  194. data/lib/rubocop/cop/style/when_then.rb +4 -2
  195. data/lib/rubocop/cop/style/while_until_do.rb +9 -13
  196. data/lib/rubocop/cop/style/while_until_modifier.rb +12 -11
  197. data/lib/rubocop/cop/style/word_array.rb +5 -9
  198. data/lib/rubocop/cop/team.rb +16 -15
  199. data/lib/rubocop/cop/util.rb +13 -3
  200. data/lib/rubocop/formatter/clang_style_formatter.rb +2 -2
  201. data/lib/rubocop/formatter/disabled_config_formatter.rb +2 -1
  202. data/lib/rubocop/magic_comment.rb +196 -0
  203. data/lib/rubocop/options.rb +5 -4
  204. data/lib/rubocop/processed_source.rb +1 -1
  205. data/lib/rubocop/rspec/cop_helper.rb +9 -0
  206. data/lib/rubocop/rspec/shared_examples.rb +1 -1
  207. data/lib/rubocop/runner.rb +7 -2
  208. data/lib/rubocop/version.rb +1 -1
  209. metadata +41 -14
  210. data/lib/rubocop/ast_node.rb +0 -624
  211. data/lib/rubocop/ast_node/builder.rb +0 -30
  212. data/lib/rubocop/ast_node/sexp.rb +0 -13
  213. data/lib/rubocop/cop/mixin/hash_node.rb +0 -14
  214. data/lib/rubocop/cop/mixin/if_node.rb +0 -42
@@ -101,9 +101,9 @@ module RuboCop
101
101
  next unless a.loc.respond_to?(:keyword)
102
102
 
103
103
  case a.type
104
- when :if, :while, :until then expression, = *a
105
- when :for then _, expression, = *a
106
- when :return then expression, = *a
104
+ when :for then _, expression, = *a
105
+ when :return then expression, = *a
106
+ when *Util::MODIFIER_NODES then expression, = *a
107
107
  end
108
108
 
109
109
  within_node?(node, expression) if expression
@@ -2,28 +2,42 @@
2
2
 
3
3
  module RuboCop
4
4
  module Cop
5
- # Some common code shared between FavorUnlessOverNegatedIf and
6
- # FavorUntilOverNegatedWhile.
5
+ # Some common code shared between `NegatedIf` and
6
+ # `NegatedWhile` cops.
7
7
  module NegativeConditional
8
8
  extend NodePattern::Macros
9
- include IfNode
10
9
 
11
10
  def_node_matcher :single_negative?, '(send !(send _ :!) :!)'
12
11
  def_node_matcher :empty_condition?, '(begin)'
13
12
 
14
13
  def check_negative_conditional(node)
15
- condition, _body, _rest = *node
14
+ condition = node.condition
16
15
 
17
16
  return if empty_condition?(condition)
18
17
 
19
- # Look at last expression of contents if there are parentheses
20
- # around condition.
21
18
  condition = condition.children.last while condition.begin_type?
22
19
 
23
- return unless single_negative?(condition) && !if_else?(node)
20
+ return unless single_negative?(condition)
21
+ return if node.if_type? && node.else?
24
22
 
25
23
  add_offense(node, :expression)
26
24
  end
25
+
26
+ def negative_conditional_corrector(node)
27
+ condition = negated_condition(node)
28
+
29
+ lambda do |corrector|
30
+ corrector.replace(node.loc.keyword, node.inverse_keyword)
31
+ corrector.replace(condition.source_range,
32
+ condition.children.first.source)
33
+ end
34
+ end
35
+
36
+ def negated_condition(node)
37
+ condition = node.condition
38
+ condition = condition.children.first while condition.begin_type?
39
+ condition
40
+ end
27
41
  end
28
42
  end
29
43
  end
@@ -14,6 +14,20 @@ module RuboCop
14
14
  on_method_def(node, method_name, args, body)
15
15
  end
16
16
 
17
+ # This method provides scope agnostic method node destructuring by moving
18
+ # the scope to the end where it can easily be ignored.
19
+ def method_def_node_parts(node)
20
+ if node.def_type?
21
+ method_name, args, body = *node
22
+ elsif node.defs_type?
23
+ scope, method_name, args, body = *node
24
+ else
25
+ return []
26
+ end
27
+
28
+ [method_name, args, body, scope]
29
+ end
30
+
17
31
  private
18
32
 
19
33
  # Returns true for constructs such as
@@ -4,34 +4,11 @@ module RuboCop
4
4
  module Cop
5
5
  # Common functionality for cops checking if and unless expressions.
6
6
  module OnNormalIfUnless
7
- include IfNode
8
-
9
7
  def on_if(node)
10
- invoke_hook_for_normal_if_unless(node)
11
- end
8
+ return if node.modifier_form? || node.ternary?
12
9
 
13
- def invoke_hook_for_normal_if_unless(node)
14
- # We won't check modifier or ternary conditionals.
15
- return if modifier_if?(node) || ternary?(node)
16
10
  on_normal_if_unless(node)
17
11
  end
18
-
19
- def if_else_clause(node)
20
- return unless node.if_type?
21
-
22
- keyword = node.loc.keyword
23
- if keyword.is?('if')
24
- node.children.last
25
- elsif keyword.is?('elsif')
26
- node.children.last
27
- elsif keyword.is?('unless')
28
- node.children[1]
29
- end
30
- end
31
-
32
- def case_else_clause(node)
33
- node.children.last if node.case_type?
34
- end
35
12
  end
36
13
  end
37
14
  end
@@ -4,13 +4,10 @@ module RuboCop
4
4
  module Cop
5
5
  # Common functionality for modifier cops.
6
6
  module StatementModifier
7
- include IfNode
8
-
9
7
  def single_line_as_modifier?(node)
10
- cond, body, = if_node_parts(node)
11
-
12
- return false if non_eligible_node?(node) || non_eligible_body?(body) ||
13
- non_eligible_condition?(cond)
8
+ return false if non_eligible_node?(node) ||
9
+ non_eligible_body?(node.body) ||
10
+ non_eligible_condition?(node.condition)
14
11
 
15
12
  modifier_fits_on_single_line?(node)
16
13
  end
@@ -20,9 +17,7 @@ module RuboCop
20
17
  end
21
18
 
22
19
  def non_eligible_body?(body)
23
- return true unless body
24
-
25
- body.begin_type? || empty_body?(body) || commented?(body.source_range)
20
+ empty_body?(body) || body.begin_type? || commented?(body.source_range)
26
21
  end
27
22
 
28
23
  def non_eligible_condition?(condition)
@@ -30,10 +25,10 @@ module RuboCop
30
25
  end
31
26
 
32
27
  def modifier_fits_on_single_line?(node)
33
- cond, body, = if_node_parts(node)
34
- body_length = body_length(body)
28
+ modifier_length = length_in_modifier_form(node, node.condition,
29
+ body_length(node.body))
35
30
 
36
- length_in_modifier_form(node, cond, body_length) <= max_line_length
31
+ modifier_length <= max_line_length
37
32
  end
38
33
 
39
34
  def length_in_modifier_form(node, cond, body_length)
@@ -54,7 +49,7 @@ module RuboCop
54
49
  end
55
50
 
56
51
  def empty_body?(body)
57
- body_length(body).zero?
52
+ !body || body_length(body).zero?
58
53
  end
59
54
 
60
55
  def body_length(body)
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Common functionality for checking target ruby version.
6
+ module TargetRubyVersion
7
+ def minimum_target_ruby_version(version)
8
+ @minimum_target_ruby_version = version
9
+ end
10
+
11
+ def support_target_ruby_version?(version)
12
+ @minimum_target_ruby_version <= version
13
+ end
14
+ end
15
+ end
16
+ end
@@ -9,7 +9,7 @@ module RuboCop
9
9
 
10
10
  MSG = '%s comma after the last %s'.freeze
11
11
 
12
- def parameter_name
12
+ def style_parameter_name
13
13
  'EnforcedStyleForMultiline'
14
14
  end
15
15
 
@@ -95,8 +95,7 @@ module RuboCop
95
95
  # For each argument, if it is a multi-line hash without braces,
96
96
  # then promote the hash elements to method arguments
97
97
  # for the purpose of determining multi-line-ness.
98
- if a.hash_type? && a.loc.first_line != a.loc.last_line &&
99
- !brackets?(a)
98
+ if a.hash_type? && a.multiline? && !a.braces?
100
99
  a.children
101
100
  else
102
101
  a
@@ -44,7 +44,7 @@ module RuboCop
44
44
  # @!attribute [r] cop_name
45
45
  #
46
46
  # @return [String]
47
- # a cop class name without namespace.
47
+ # a cop class name without department.
48
48
  # i.e. type of the violation.
49
49
  #
50
50
  # @example
@@ -61,13 +61,8 @@ module RuboCop
61
61
  PERCENT_I = '%i'.freeze
62
62
  PERCENT_CAPITAL_I = '%I'.freeze
63
63
 
64
- def on_case(node)
65
- _case_branch, *when_branches, _else_branch = *node
66
- when_conditions =
67
- when_branches.each_with_object([]) do |branch, conditions|
68
- *condition, _ = *branch
69
- condition.each { |c| conditions << c }
70
- end
64
+ def on_case(case_node)
65
+ when_conditions = case_node.when_branches.flat_map(&:conditions)
71
66
 
72
67
  splat_offenses(when_conditions).reverse_each do |condition|
73
68
  range = condition.parent.loc.keyword.join(condition.source_range)
@@ -77,99 +72,101 @@ module RuboCop
77
72
  end
78
73
  end
79
74
 
80
- def autocorrect(node)
81
- *conditions, _body = *node
75
+ private
82
76
 
77
+ def autocorrect(when_node)
83
78
  lambda do |corrector|
84
- if needs_reorder?(node)
85
- reorder_condition(corrector, node, replacement(conditions))
79
+ if needs_reorder?(when_node)
80
+ reorder_condition(corrector, when_node)
86
81
  else
87
- inline_fix_branch(corrector, node, conditions,
88
- replacement(conditions))
82
+ inline_fix_branch(corrector, when_node)
89
83
  end
90
84
  end
91
85
  end
92
86
 
93
- private
94
-
95
87
  def replacement(conditions)
96
- ordered_conditions = conditions.partition { |cond| !cond.splat_type? }
97
- ordered_conditions.flatten!
98
- ordered_conditions.map!(&:source)
99
- ordered_conditions.join(', ')
88
+ reordered = conditions.partition(&:splat_type?).reverse
89
+ reordered.flatten.map(&:source).join(', ')
100
90
  end
101
91
 
102
- def inline_fix_branch(corrector, _node, conditions, new_condition)
92
+ def inline_fix_branch(corrector, when_node)
93
+ conditions = when_node.conditions
103
94
  range = range_between(conditions[0].loc.expression.begin_pos,
104
95
  conditions[-1].loc.expression.end_pos)
105
- corrector.replace(range, new_condition)
96
+
97
+ corrector.replace(range, replacement(conditions))
106
98
  end
107
99
 
108
- def reorder_condition(corrector, node, new_condition)
109
- *_conditions, body = *node
110
- _case_branch, *when_branches, _else_branch = *node.parent
111
- return if when_branches.size == 1 # Can't reorder one branch
100
+ def reorder_condition(corrector, when_node)
101
+ when_branches = when_node.parent.when_branches
102
+
103
+ return if when_branches.one?
112
104
 
113
- corrector.remove(when_branch_range(node, when_branches))
105
+ corrector.remove(when_branch_range(when_node))
106
+ corrector.insert_after(when_branches.last.source_range,
107
+ reordering_correction(when_node))
108
+ end
114
109
 
115
- correction = if same_line?(node, body)
116
- new_condition_with_then(node, new_condition)
117
- else
118
- new_branch_without_then(node, body, new_condition)
119
- end
110
+ def reordering_correction(when_node)
111
+ new_condition = replacement(when_node.conditions)
120
112
 
121
- corrector.insert_after(when_branches.last.source_range, correction)
113
+ if same_line?(when_node, when_node.body)
114
+ new_condition_with_then(when_node, new_condition)
115
+ else
116
+ new_branch_without_then(when_node, new_condition)
117
+ end
122
118
  end
123
119
 
124
- def when_branch_range(node, when_branches)
125
- current_index = when_branches.index { |branch| branch == node }
126
- next_branch = when_branches[current_index + 1]
120
+ def when_branch_range(when_node)
121
+ next_branch =
122
+ when_node.parent.when_branches[when_node.branch_index + 1]
127
123
 
128
- range_between(node.source_range.begin_pos,
124
+ range_between(when_node.source_range.begin_pos,
129
125
  next_branch.source_range.begin_pos)
130
126
  end
131
127
 
132
- def same_line?(node, other)
133
- node.loc.first_line == other.loc.first_line
128
+ def new_condition_with_then(node, new_condition)
129
+ "\n#{indent_for(node)}when " \
130
+ "#{new_condition} then #{node.body.source}"
134
131
  end
135
132
 
136
- def new_condition_with_then(node, new_condition)
137
- "\n#{' ' * node.loc.column}when " \
138
- "#{new_condition} then #{node.children.last.source}"
133
+ def new_branch_without_then(node, new_condition)
134
+ "\n#{indent_for(node)}when #{new_condition}" \
135
+ "\n#{indent_for(node.body)}#{node.body.source}"
139
136
  end
140
137
 
141
- def new_branch_without_then(node, body, new_condition)
142
- "\n#{' ' * node.loc.column}when #{new_condition}\n" \
143
- "#{' ' * body.loc.column}#{node.children.last.source}"
138
+ def indent_for(node)
139
+ ' ' * node.loc.column
144
140
  end
145
141
 
146
142
  def splat_offenses(when_conditions)
147
143
  found_non_splat = false
148
- when_conditions.reverse.each_with_object([]) do |condition, result|
149
- found_non_splat ||= error_condition?(condition)
150
144
 
151
- next unless condition.splat_type?
152
- variable, = *condition
153
- next if variable.array_type?
154
- result << condition if found_non_splat
145
+ offenses = when_conditions.reverse.map do |condition|
146
+ found_non_splat ||= non_splat?(condition)
147
+
148
+ next if non_splat?(condition)
149
+
150
+ condition if found_non_splat
155
151
  end
152
+
153
+ offenses.compact
156
154
  end
157
155
 
158
- def error_condition?(condition)
156
+ def non_splat?(condition)
159
157
  variable, = *condition
160
158
 
161
159
  (condition.splat_type? && variable.array_type?) ||
162
160
  !condition.splat_type?
163
161
  end
164
162
 
165
- def needs_reorder?(node)
166
- _case_condition, *when_branches, _else_branch = *node.parent
167
- current_index = when_branches.index { |branch| branch == node }
168
- when_branches[(current_index + 1)..-1].any? do |branch|
169
- *conditions, _ = *branch
170
- conditions.none? do |condition|
171
- variable, = *condition
172
- condition.splat_type? && !(variable && variable.array_type?)
163
+ def needs_reorder?(when_node)
164
+ following_branches =
165
+ when_node.parent.when_branches[(when_node.branch_index + 1)..-1]
166
+
167
+ following_branches.any? do |when_branch|
168
+ when_branch.conditions.any? do |condition|
169
+ non_splat?(condition)
173
170
  end
174
171
  end
175
172
  end
@@ -45,7 +45,7 @@ module RuboCop
45
45
  receiver, _args, body = *receiver if receiver.block_type?
46
46
  return if accept_first_call?(receiver, body)
47
47
 
48
- offense(node, receiver, second_method)
48
+ register_offense(node, receiver, second_method)
49
49
  end
50
50
  end
51
51
 
@@ -79,7 +79,7 @@ module RuboCop
79
79
  lazy?(caller)
80
80
  end
81
81
 
82
- def offense(node, receiver, second_method)
82
+ def register_offense(node, receiver, second_method)
83
83
  _caller, first_method, _args = *receiver
84
84
  range = receiver.loc.selector.join(node.loc.selector)
85
85
 
@@ -54,14 +54,14 @@ module RuboCop
54
54
 
55
55
  def offense_for_levels(node, map_node, first_method, flatten)
56
56
  message = MSG + FLATTEN_MULTIPLE_LEVELS
57
- offense(node, map_node, first_method, flatten, message)
57
+ register_offense(node, map_node, first_method, flatten, message)
58
58
  end
59
59
 
60
60
  def offense_for_method(node, map_node, first_method, flatten)
61
- offense(node, map_node, first_method, flatten, MSG)
61
+ register_offense(node, map_node, first_method, flatten, MSG)
62
62
  end
63
63
 
64
- def offense(node, map_node, first_method, flatten, message)
64
+ def register_offense(node, map_node, first_method, flatten, message)
65
65
  range = range_between(map_node.loc.selector.begin_pos,
66
66
  node.loc.expression.end_pos)
67
67
 
@@ -73,13 +73,10 @@ module RuboCop
73
73
  def to_assignments(receiver, pairs)
74
74
  pairs.map do |pair|
75
75
  key, value = *pair
76
- key_src = if key.sym_type? && !key.source.start_with?(':')
77
- ":#{key.source}"
78
- else
79
- key.source
80
- end
81
76
 
82
- format(AREF_ASGN, receiver.source, key_src, value.source)
77
+ key = key.sym_type? && pair.colon? ? ":#{key.source}" : key.source
78
+
79
+ format(AREF_ASGN, receiver.source, key, value.source)
83
80
  end
84
81
  end
85
82
 
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # In Ruby 2.4, `String#match?`, `Regexp#match?` and `Symbol#match?`
7
+ # have been added. The methods are faster than `match`.
8
+ # Because the methods avoid creating a `MatchData` object or saving
9
+ # backref.
10
+ # So, when `MatchData` is not used, use `match?` instead of `match`.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # def foo
15
+ # if x =~ /re/
16
+ # do_something
17
+ # end
18
+ # end
19
+ #
20
+ # # bad
21
+ # def foo
22
+ # if x.match(/re/)
23
+ # do_something
24
+ # end
25
+ # end
26
+ #
27
+ # # bad
28
+ # def foo
29
+ # if /re/ === x
30
+ # do_something
31
+ # end
32
+ # end
33
+ #
34
+ # # good
35
+ # def foo
36
+ # if x.match?(/re/)
37
+ # do_something
38
+ # end
39
+ # end
40
+ #
41
+ # # good
42
+ # def foo
43
+ # if x =~ /re/
44
+ # do_something(Regexp.last_match)
45
+ # end
46
+ # end
47
+ #
48
+ # # good
49
+ # def foo
50
+ # if x.match(/re/)
51
+ # do_something($~)
52
+ # end
53
+ # end
54
+ #
55
+ # # good
56
+ # def foo
57
+ # if /re/ === x
58
+ # do_something($~)
59
+ # end
60
+ # end
61
+ class RegexpMatch < Cop
62
+ extend TargetRubyVersion
63
+
64
+ minimum_target_ruby_version 2.4
65
+
66
+ MSG =
67
+ 'Use `match?` instead of `%s` when `MatchData` is not used.'.freeze
68
+
69
+ def_node_matcher :match_method?, <<-PATTERN
70
+ {
71
+ (send _recv :match _)
72
+ (send _recv :match _ (:int ...))
73
+ }
74
+ PATTERN
75
+
76
+ def_node_matcher :match_operator?, <<-PATTERN
77
+ (send !nil :=~ !nil)
78
+ PATTERN
79
+
80
+ def_node_matcher :match_threequals?, <<-PATTERN
81
+ (send (regexp (str _) {(regopt) (regopt _)}) :=== !nil)
82
+ PATTERN
83
+
84
+ def_node_matcher :match_with_lvasgn?, <<-PATTERN
85
+ (match_with_lvasgn !nil !nil)
86
+ PATTERN
87
+
88
+ MATCH_NODE_PATTERN = <<-PATTERN.freeze
89
+ {
90
+ #match_method?
91
+ #match_operator?
92
+ #match_threequals?
93
+ #match_with_lvasgn?
94
+ }
95
+ PATTERN
96
+
97
+ def_node_matcher :match_node?, MATCH_NODE_PATTERN
98
+ def_node_search :search_match_nodes, MATCH_NODE_PATTERN
99
+
100
+ def_node_search :last_matches, <<-PATTERN
101
+ {
102
+ (send (const nil :Regexp) :last_match)
103
+ (send (const nil :Regexp) :last_match _)
104
+ ({back_ref nth_ref} _)
105
+ (gvar #dollar_tilde)
106
+ }
107
+ PATTERN
108
+
109
+ def on_if(node)
110
+ check_condition(node.condition)
111
+ end
112
+
113
+ def on_case(node)
114
+ return if node.condition
115
+
116
+ node.each_when do |when_node|
117
+ when_node.each_condition do |condition|
118
+ check_condition(condition)
119
+ end
120
+ end
121
+ end
122
+
123
+ def autocorrect(node)
124
+ lambda do |corrector|
125
+ if match_method?(node)
126
+ corrector.replace(node.loc.selector, 'match?')
127
+ elsif match_operator?(node) || match_threequals?(node)
128
+ recv, _, arg = *node
129
+ correct_operator(corrector, recv, arg)
130
+ elsif match_with_lvasgn?(node)
131
+ recv, arg = *node
132
+ correct_operator(corrector, recv, arg)
133
+ end
134
+ end
135
+ end
136
+
137
+ private
138
+
139
+ def check_condition(cond)
140
+ match_node?(cond) do
141
+ return if last_match_used?(cond)
142
+ add_offense(cond, :expression,
143
+ format(MSG, cond.loc.selector.source))
144
+ end
145
+ end
146
+
147
+ def last_match_used?(match_node)
148
+ scope_root = scope_root(match_node)
149
+ body = scope_root ? scope_body(scope_root) : match_node.ancestors.last
150
+ match_node_pos = match_node.loc.expression.begin_pos
151
+
152
+ next_match_pos = next_match_pos(body, match_node_pos, scope_root)
153
+ range = match_node_pos..next_match_pos
154
+
155
+ find_last_match(body, range, scope_root)
156
+ end
157
+
158
+ def next_match_pos(body, match_node_pos, scope_root)
159
+ node = search_match_nodes(body).find do |match|
160
+ match.loc.expression.begin_pos > match_node_pos &&
161
+ scope_root(match) == scope_root
162
+ end
163
+ node ? node.loc.expression.begin_pos : Float::INFINITY
164
+ end
165
+
166
+ def find_last_match(body, range, scope_root)
167
+ last_matches(body).find do |ref|
168
+ ref_pos = ref.loc.expression.begin_pos
169
+ range.cover?(ref_pos) &&
170
+ scope_root(ref) == scope_root
171
+ end
172
+ end
173
+
174
+ def scope_body(node)
175
+ node.children[2]
176
+ end
177
+
178
+ def scope_root(node)
179
+ node.each_ancestor.find do |ancestor|
180
+ ancestor.def_type? ||
181
+ ancestor.class_type? ||
182
+ ancestor.module_type?
183
+ end
184
+ end
185
+
186
+ def dollar_tilde(sym)
187
+ sym == :$~
188
+ end
189
+
190
+ def correct_operator(corrector, recv, arg)
191
+ buffer = processed_source.buffer
192
+ op_begin_pos = recv.loc.expression.end_pos
193
+ op_end_pos = arg.loc.expression.begin_pos
194
+ op_range = Parser::Source::Range.new(buffer, op_begin_pos, op_end_pos)
195
+ corrector.replace(op_range, '.match?(')
196
+ corrector.insert_after(arg.loc.expression, ')')
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end