rubocop 0.70.0 → 0.72.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -10
  3. data/config/default.yml +50 -491
  4. data/lib/rubocop.rb +5 -53
  5. data/lib/rubocop/ast/builder.rb +2 -0
  6. data/lib/rubocop/ast/node.rb +1 -1
  7. data/lib/rubocop/ast/node/float_node.rb +12 -0
  8. data/lib/rubocop/ast/node/int_node.rb +12 -0
  9. data/lib/rubocop/ast/node/mixin/numeric_node.rb +21 -0
  10. data/lib/rubocop/ast/node/resbody_node.rb +1 -6
  11. data/lib/rubocop/cached_data.rb +1 -1
  12. data/lib/rubocop/config.rb +35 -6
  13. data/lib/rubocop/config_loader.rb +2 -2
  14. data/lib/rubocop/config_loader_resolver.rb +0 -6
  15. data/lib/rubocop/cop/cop.rb +0 -4
  16. data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +55 -0
  17. data/lib/rubocop/cop/layout/class_structure.rb +1 -1
  18. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +3 -1
  19. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +4 -0
  20. data/lib/rubocop/cop/layout/indent_first_argument.rb +6 -2
  21. data/lib/rubocop/cop/layout/indent_first_parameter.rb +7 -3
  22. data/lib/rubocop/cop/layout/indent_heredoc.rb +0 -1
  23. data/lib/rubocop/cop/layout/indentation_consistency.rb +13 -12
  24. data/lib/rubocop/cop/layout/indentation_width.rb +8 -4
  25. data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +2 -0
  26. data/lib/rubocop/cop/lint/number_conversion.rb +1 -1
  27. data/lib/rubocop/cop/mixin/hash_alignment.rb +4 -0
  28. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +20 -22
  29. data/lib/rubocop/cop/style/commented_keyword.rb +1 -1
  30. data/lib/rubocop/cop/style/conditional_assignment.rb +2 -1
  31. data/lib/rubocop/cop/style/float_division.rb +94 -0
  32. data/lib/rubocop/cop/style/format_string.rb +7 -3
  33. data/lib/rubocop/cop/style/if_inside_else.rb +42 -0
  34. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +7 -1
  35. data/lib/rubocop/cop/style/safe_navigation.rb +1 -1
  36. data/lib/rubocop/cop/style/ternary_parentheses.rb +12 -2
  37. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +4 -0
  38. data/lib/rubocop/cop/style/word_array.rb +2 -2
  39. data/lib/rubocop/cop/style/zero_length_predicate.rb +1 -1
  40. data/lib/rubocop/node_pattern.rb +84 -5
  41. data/lib/rubocop/options.rb +0 -2
  42. data/lib/rubocop/processed_source.rb +5 -1
  43. data/lib/rubocop/rspec/cop_helper.rb +0 -1
  44. data/lib/rubocop/rspec/shared_contexts.rb +0 -17
  45. data/lib/rubocop/rspec/support.rb +0 -1
  46. data/lib/rubocop/runner.rb +6 -7
  47. data/lib/rubocop/version.rb +1 -1
  48. data/lib/rubocop/yaml_duplication_checker.rb +8 -2
  49. metadata +7 -69
  50. data/lib/rubocop/cop/mixin/target_rails_version.rb +0 -16
  51. data/lib/rubocop/cop/rails/action_filter.rb +0 -117
  52. data/lib/rubocop/cop/rails/active_record_aliases.rb +0 -48
  53. data/lib/rubocop/cop/rails/active_record_override.rb +0 -82
  54. data/lib/rubocop/cop/rails/active_support_aliases.rb +0 -69
  55. data/lib/rubocop/cop/rails/application_job.rb +0 -40
  56. data/lib/rubocop/cop/rails/application_record.rb +0 -40
  57. data/lib/rubocop/cop/rails/assert_not.rb +0 -44
  58. data/lib/rubocop/cop/rails/belongs_to.rb +0 -102
  59. data/lib/rubocop/cop/rails/blank.rb +0 -164
  60. data/lib/rubocop/cop/rails/bulk_change_table.rb +0 -289
  61. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +0 -91
  62. data/lib/rubocop/cop/rails/date.rb +0 -161
  63. data/lib/rubocop/cop/rails/delegate.rb +0 -132
  64. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +0 -37
  65. data/lib/rubocop/cop/rails/dynamic_find_by.rb +0 -91
  66. data/lib/rubocop/cop/rails/enum_uniqueness.rb +0 -45
  67. data/lib/rubocop/cop/rails/environment_comparison.rb +0 -68
  68. data/lib/rubocop/cop/rails/exit.rb +0 -67
  69. data/lib/rubocop/cop/rails/file_path.rb +0 -108
  70. data/lib/rubocop/cop/rails/find_by.rb +0 -55
  71. data/lib/rubocop/cop/rails/find_each.rb +0 -51
  72. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +0 -25
  73. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +0 -106
  74. data/lib/rubocop/cop/rails/http_positional_arguments.rb +0 -117
  75. data/lib/rubocop/cop/rails/http_status.rb +0 -179
  76. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +0 -94
  77. data/lib/rubocop/cop/rails/inverse_of.rb +0 -246
  78. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +0 -175
  79. data/lib/rubocop/cop/rails/link_to_blank.rb +0 -98
  80. data/lib/rubocop/cop/rails/not_null_column.rb +0 -67
  81. data/lib/rubocop/cop/rails/output.rb +0 -49
  82. data/lib/rubocop/cop/rails/output_safety.rb +0 -99
  83. data/lib/rubocop/cop/rails/pluralization_grammar.rb +0 -107
  84. data/lib/rubocop/cop/rails/presence.rb +0 -124
  85. data/lib/rubocop/cop/rails/present.rb +0 -153
  86. data/lib/rubocop/cop/rails/read_write_attribute.rb +0 -74
  87. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +0 -111
  88. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +0 -136
  89. data/lib/rubocop/cop/rails/reflection_class_name.rb +0 -37
  90. data/lib/rubocop/cop/rails/refute_methods.rb +0 -76
  91. data/lib/rubocop/cop/rails/relative_date_constant.rb +0 -93
  92. data/lib/rubocop/cop/rails/request_referer.rb +0 -56
  93. data/lib/rubocop/cop/rails/reversible_migration.rb +0 -286
  94. data/lib/rubocop/cop/rails/safe_navigation.rb +0 -87
  95. data/lib/rubocop/cop/rails/save_bang.rb +0 -316
  96. data/lib/rubocop/cop/rails/scope_args.rb +0 -29
  97. data/lib/rubocop/cop/rails/skips_model_validations.rb +0 -87
  98. data/lib/rubocop/cop/rails/time_zone.rb +0 -238
  99. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +0 -105
  100. data/lib/rubocop/cop/rails/unknown_env.rb +0 -63
  101. data/lib/rubocop/cop/rails/validation.rb +0 -109
  102. data/lib/rubocop/rspec/shared_examples.rb +0 -59
@@ -83,7 +83,7 @@ module RuboCop
83
83
 
84
84
  check_indentation(end_loc, node.body)
85
85
 
86
- return unless indentation_consistency_style == 'rails'
86
+ return unless indented_internal_methods_style?
87
87
 
88
88
  check_members(end_loc, [node.body])
89
89
  end
@@ -155,8 +155,8 @@ module RuboCop
155
155
 
156
156
  return unless members.any? && members.first.begin_type?
157
157
 
158
- if indentation_consistency_style == 'rails'
159
- check_members_for_rails_style(members)
158
+ if indentation_consistency_style == 'indented_internal_methods'
159
+ check_members_for_indented_internal_methods_style(members)
160
160
  else
161
161
  members.first.children.each do |member|
162
162
  next if member.send_type? && member.access_modifier?
@@ -176,7 +176,7 @@ module RuboCop
176
176
  end
177
177
  end
178
178
 
179
- def check_members_for_rails_style(members)
179
+ def check_members_for_indented_internal_methods_style(members)
180
180
  each_member(members) do |member, previous_modifier|
181
181
  check_indentation(previous_modifier, member,
182
182
  indentation_consistency_style)
@@ -195,6 +195,10 @@ module RuboCop
195
195
  end
196
196
  end
197
197
 
198
+ def indented_internal_methods_style?
199
+ indentation_consistency_style == 'indented_internal_methods'
200
+ end
201
+
198
202
  def indentation_consistency_style
199
203
  config.for_cop('Layout/IndentationConsistency')['EnforcedStyle']
200
204
  end
@@ -26,6 +26,8 @@ module RuboCop
26
26
  'on a separate line.'
27
27
 
28
28
  def on_send(node)
29
+ return if node.method_name == :[]=
30
+
29
31
  args = node.arguments
30
32
 
31
33
  # If there is a trailing hash arg without explicit braces, like this:
@@ -64,7 +64,7 @@ module RuboCop
64
64
 
65
65
  def date_time_object?(node)
66
66
  child = node
67
- while child.send_type?
67
+ while child&.send_type?
68
68
  return true if datetime? child
69
69
 
70
70
  child = child.children[0]
@@ -83,6 +83,10 @@ module RuboCop
83
83
  class TableAlignment
84
84
  include ValueAlignment
85
85
 
86
+ def initialize
87
+ self.max_key_width = 0
88
+ end
89
+
86
90
  def deltas_for_first_pair(first_pair, node)
87
91
  self.max_key_width = node.keys.map { |key| key.source.length }.max
88
92
 
@@ -57,24 +57,20 @@ module RuboCop
57
57
  MSG = 'Use `%<preferred>s` instead of `%<bad>s`.'
58
58
 
59
59
  def on_resbody(node)
60
- exception_type, @exception_name = *node
61
- return unless exception_type || @exception_name
62
-
63
- @exception_name ||= exception_type.children.first
64
- return if @exception_name.const_type? ||
65
- variable_name == preferred_name
60
+ name = variable_name(node)
61
+ return unless name
62
+ return if preferred_name(name).to_sym == name
66
63
 
67
64
  add_offense(node, location: offense_range(node))
68
65
  end
69
66
 
70
67
  def autocorrect(node)
71
68
  lambda do |corrector|
72
- offending_name = node.exception_variable.children.first
69
+ offending_name = variable_name(node)
70
+ preferred_name = preferred_name(offending_name)
73
71
  corrector.replace(offense_range(node), preferred_name)
74
72
 
75
- return unless node.body
76
-
77
- node.body.each_descendant(:lvar) do |var|
73
+ node.body&.each_descendant(:lvar) do |var|
78
74
  next unless var.children.first == offending_name
79
75
 
80
76
  corrector.replace(var.loc.expression, preferred_name)
@@ -89,24 +85,26 @@ module RuboCop
89
85
  variable.loc.expression
90
86
  end
91
87
 
92
- def preferred_name
93
- @preferred_name ||= begin
94
- name = cop_config.fetch('PreferredName', 'e')
95
- name = "_#{name}" if variable_name.to_s.start_with?('_')
96
- name
88
+ def preferred_name(variable_name)
89
+ preferred_name = cop_config.fetch('PreferredName', 'e')
90
+ if variable_name.to_s.start_with?('_')
91
+ "_#{preferred_name}"
92
+ else
93
+ preferred_name
97
94
  end
98
95
  end
99
96
 
100
- def variable_name
101
- location.source
102
- end
97
+ def variable_name(node)
98
+ asgn_node = node.exception_variable
99
+ return unless asgn_node
103
100
 
104
- def location
105
- @exception_name.loc.expression
101
+ asgn_node.children.last
106
102
  end
107
103
 
108
- def message(_node = nil)
109
- format(MSG, preferred: preferred_name, bad: variable_name)
104
+ def message(node)
105
+ offending_name = variable_name(node)
106
+ preferred_name = preferred_name(offending_name)
107
+ format(MSG, preferred: preferred_name, bad: offending_name)
110
108
  end
111
109
  end
112
110
  end
@@ -6,7 +6,7 @@ module RuboCop
6
6
  # This cop checks for comments put on the same line as some keywords.
7
7
  # These keywords are: `begin`, `class`, `def`, `end`, `module`.
8
8
  #
9
- # Note that some comments (`:nodoc:`, `:yields:, and `rubocop:disable`)
9
+ # Note that some comments (`:nodoc:`, `:yields:`, and `rubocop:disable`)
10
10
  # are allowed.
11
11
  #
12
12
  # @example
@@ -596,7 +596,8 @@ module RuboCop
596
596
 
597
597
  remove_whitespace_in_branches(corrector, branch, condition, column)
598
598
 
599
- branch_else = branch.parent.loc.else
599
+ return unless (branch_else = branch.parent.loc.else)
600
+
600
601
  corrector.remove_preceding(branch_else, branch_else.column - column)
601
602
  end
602
603
  end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # This cop checks for division with integers coerced to floats.
7
+ # It is recommended to either always use `fdiv` or coerce one side only.
8
+ # This cop also provides other options for code consistency.
9
+ #
10
+ # @example EnforcedStyle: single_coerce (default)
11
+ # # bad
12
+ # a.to_f / b.to_f
13
+ #
14
+ # # good
15
+ # a.to_f / b
16
+ # a / b.to_f
17
+ #
18
+ # @example EnforcedStyle: left_coerce
19
+ # # bad
20
+ # a / b.to_f
21
+ # a.to_f / b.to_f
22
+ #
23
+ # # good
24
+ # a.to_f / b
25
+ #
26
+ # @example EnforcedStyle: right_coerce
27
+ # # bad
28
+ # a.to_f / b
29
+ # a.to_f / b.to_f
30
+ #
31
+ # # good
32
+ # a / b.to_f
33
+ #
34
+ # @example EnforcedStyle: fdiv
35
+ # # bad
36
+ # a / b.to_f
37
+ # a.to_f / b
38
+ # a.to_f / b.to_f
39
+ #
40
+ # # good
41
+ # a.fdiv(b)
42
+ class FloatDivision < Cop
43
+ include ConfigurableEnforcedStyle
44
+
45
+ def_node_matcher :right_coerce?, <<-PATTERN
46
+ (send _ :/ (send _ :to_f))
47
+ PATTERN
48
+ def_node_matcher :left_coerce?, <<-PATTERN
49
+ (send (send _ :to_f) :/ _)
50
+ PATTERN
51
+ def_node_matcher :both_coerce?, <<-PATTERN
52
+ (send (send _ :to_f) :/ (send _ :to_f))
53
+ PATTERN
54
+ def_node_matcher :any_coerce?, <<-PATTERN
55
+ {(send _ :/ (send _ :to_f)) (send (send _ :to_f) :/ _)}
56
+ PATTERN
57
+
58
+ def on_send(node)
59
+ add_offense(node) if offense_condition?(node)
60
+ end
61
+
62
+ private
63
+
64
+ def offense_condition?(node)
65
+ case style
66
+ when :left_coerce
67
+ right_coerce?(node)
68
+ when :right_coerce
69
+ left_coerce?(node)
70
+ when :single_coerce
71
+ both_coerce?(node)
72
+ when :fdiv
73
+ any_coerce?(node)
74
+ else
75
+ false
76
+ end
77
+ end
78
+
79
+ def message(_node)
80
+ case style
81
+ when :left_coerce
82
+ 'Prefer using `.to_f` on the left side.'
83
+ when :right_coerce
84
+ 'Prefer using `.to_f` on the right side.'
85
+ when :single_coerce
86
+ 'Prefer using `.to_f` on one side only.'
87
+ when :fdiv
88
+ 'Prefer using `fdiv` for float divisions.'
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -48,6 +48,10 @@ module RuboCop
48
48
  }
49
49
  PATTERN
50
50
 
51
+ def_node_matcher :variable_argument?, <<-PATTERN
52
+ (send {str dstr} :% {send_type? lvar_type?})
53
+ PATTERN
54
+
51
55
  def on_send(node)
52
56
  formatter(node) do |selector|
53
57
  detected_style = selector == :% ? :percent : selector
@@ -70,10 +74,10 @@ module RuboCop
70
74
  end
71
75
 
72
76
  def autocorrect(node)
73
- lambda do |corrector|
74
- detected_method = node.method_name
77
+ return if variable_argument?(node)
75
78
 
76
- case detected_method
79
+ lambda do |corrector|
80
+ case node.method_name
77
81
  when :%
78
82
  autocorrect_from_percent(corrector, node)
79
83
  when :format, :sprintf
@@ -27,6 +27,37 @@ module RuboCop
27
27
  # else
28
28
  # action_c
29
29
  # end
30
+ #
31
+ # @example AllowIfModifier: false (default)
32
+ # # bad
33
+ # if condition_a
34
+ # action_a
35
+ # else
36
+ # action_b if condition_b
37
+ # end
38
+ #
39
+ # # good
40
+ # if condition_a
41
+ # action_a
42
+ # elsif condition_b
43
+ # action_b
44
+ # end
45
+ #
46
+ # @example AllowIfModifier: true
47
+ # # good
48
+ # if condition_a
49
+ # action_a
50
+ # else
51
+ # action_b if condition_b
52
+ # end
53
+ #
54
+ # # good
55
+ # if condition_a
56
+ # action_a
57
+ # elsif condition_b
58
+ # action_b
59
+ # end
60
+ #
30
61
  class IfInsideElse < Cop
31
62
  MSG = 'Convert `if` nested inside `else` to `elsif`.'
32
63
 
@@ -36,9 +67,20 @@ module RuboCop
36
67
  else_branch = node.else_branch
37
68
 
38
69
  return unless else_branch&.if_type? && else_branch&.if?
70
+ return if allow_if_modifier_in_else_branch?(else_branch)
39
71
 
40
72
  add_offense(else_branch, location: :keyword)
41
73
  end
74
+
75
+ private
76
+
77
+ def allow_if_modifier_in_else_branch?(else_branch)
78
+ allow_if_modifier? && else_branch&.modifier_form?
79
+ end
80
+
81
+ def allow_if_modifier?
82
+ cop_config['AllowIfModifier']
83
+ end
42
84
  end
43
85
  end
44
86
  end
@@ -348,7 +348,8 @@ module RuboCop
348
348
  end
349
349
 
350
350
  def ambigious_literal?(node)
351
- splat?(node) || ternary_if?(node) || regexp_slash_literal?(node)
351
+ splat?(node) || ternary_if?(node) || regexp_slash_literal?(node) ||
352
+ unary_literal?(node)
352
353
  end
353
354
 
354
355
  def splat?(node)
@@ -371,6 +372,11 @@ module RuboCop
371
372
  node.regexp_type? && node.loc.begin.source == '/'
372
373
  end
373
374
 
375
+ def unary_literal?(node)
376
+ node.numeric_type? && node.sign? ||
377
+ node.parent&.send_type? && node.parent&.unary_operation?
378
+ end
379
+
374
380
  def assigned_before?(node, target)
375
381
  node.assignment? &&
376
382
  node.loc.operator.begin < target.loc.begin
@@ -224,7 +224,7 @@ module RuboCop
224
224
  end
225
225
 
226
226
  def method_called?(send_node)
227
- send_node.parent&.send_type?
227
+ send_node&.parent&.send_type?
228
228
  end
229
229
 
230
230
  def begin_range(node, method_call)
@@ -108,7 +108,7 @@ module RuboCop
108
108
  end
109
109
 
110
110
  def non_complex_send?(node)
111
- return false unless node.send_type?
111
+ return false unless node.call_type?
112
112
 
113
113
  !node.operator_method? || node.method?(:[])
114
114
  end
@@ -149,7 +149,8 @@ module RuboCop
149
149
 
150
150
  def unsafe_autocorrect?(condition)
151
151
  condition.children.any? do |child|
152
- unparenthesized_method_call?(child)
152
+ unparenthesized_method_call?(child) ||
153
+ below_ternary_precedence?(child)
153
154
  end
154
155
  end
155
156
 
@@ -157,6 +158,15 @@ module RuboCop
157
158
  method_name(child) =~ /^[a-z]/i && !child.parenthesized?
158
159
  end
159
160
 
161
+ def below_ternary_precedence?(child)
162
+ # Handle English "or", e.g. 'foo or bar ? a : b'
163
+ (child.or_type? && child.semantic_operator?) ||
164
+ # Handle English "and", e.g. 'foo and bar ? a : b'
165
+ (child.and_type? && child.semantic_operator?) ||
166
+ # Handle English "not", e.g. 'not foo ? a : b'
167
+ (child.send_type? && child.prefix_not?)
168
+ end
169
+
160
170
  def_node_matcher :method_name, <<-PATTERN
161
171
  {($:defined? (send nil? _) ...)
162
172
  (send {_ nil?} $_ _ ...)}
@@ -65,6 +65,10 @@ module RuboCop
65
65
  PunctuationCorrector.swap_comma(range)
66
66
  end
67
67
 
68
+ def self.autocorrect_incompatible_with
69
+ [Layout::HeredocArgumentClosingParenthesis]
70
+ end
71
+
68
72
  private
69
73
 
70
74
  def avoid_autocorrect?(args)
@@ -71,8 +71,8 @@ module RuboCop
71
71
 
72
72
  def complex_content?(strings)
73
73
  strings.any? do |s|
74
- string = s.str_content
75
- !string.dup.force_encoding(::Encoding::UTF_8).valid_encoding? ||
74
+ string = s.str_content.dup.force_encoding(::Encoding::UTF_8)
75
+ !string.valid_encoding? ||
76
76
  string !~ word_regex || string =~ / /
77
77
  end
78
78
  end
@@ -7,7 +7,7 @@ module RuboCop
7
7
  # by a predicate method, such as receiver.length == 0,
8
8
  # receiver.length > 0, receiver.length != 0,
9
9
  # receiver.length < 1 and receiver.size == 0 that can be
10
- # replaced by receiver.empty? and !receiver.empty.
10
+ # replaced by receiver.empty? and !receiver.empty?.
11
11
  #
12
12
  # @example
13
13
  # # bad
@@ -47,6 +47,11 @@ module RuboCop
47
47
  # # a `sym` node, but can have more.
48
48
  # '(array <$str $_>)' # captures are in the order of the pattern,
49
49
  # # irrespective of the actual order of the children
50
+ # '(array int*)' # will match an array of 0 or more integers
51
+ # '(array int ?)' # will match 0 or 1 integer.
52
+ # # Note: Space needed to distinguish from int?
53
+ # '(array int+)' # will match an array of 1 or more integers
54
+ # '(array (int $_)+)' # as above and will capture the numbers in an array
50
55
  # '(send $...)' # capture all the children as an array
51
56
  # '(send $... int)' # capture all children but the last as an array
52
57
  # '(send _x :+ _x)' # unification is performed on named wildcards
@@ -106,7 +111,9 @@ module RuboCop
106
111
  class Compiler
107
112
  SYMBOL = %r{:(?:[\w+@*/?!<>=~|%^-]+|\[\]=?)}.freeze
108
113
  IDENTIFIER = /[a-zA-Z_][a-zA-Z0-9_-]*/.freeze
109
- META = Regexp.union(%w"( ) { } [ ] $< < > $... $ ! ^ ...").freeze
114
+ META = Regexp.union(
115
+ %w"( ) { } [ ] $< < > $... $ ! ^ ... + * ?"
116
+ ).freeze
110
117
  NUMBER = /-?\d+(?:\.\d+)?/.freeze
111
118
  STRING = /".+?"/.freeze
112
119
  METHOD_NAME = /\#?#{IDENTIFIER}[\!\?]?\(?/.freeze
@@ -160,6 +167,21 @@ module RuboCop
160
167
  RUBY
161
168
  ANY_ORDER_TEMPLATE.location = [__FILE__, line + 1]
162
169
 
170
+ line = __LINE__
171
+ REPEATED_TEMPLATE = ERB.new <<~RUBY.gsub("-%>\n", '%>')
172
+ <% if captured %>(<%= accumulate %> = Array.new) && <% end %>
173
+ <%= CUR_NODE %>.children[<%= range %>].all? do |<%= child %>|
174
+ <%= with_context(expr, child, use_temp_node: false) %><% if captured %>&&
175
+ <%= accumulate %>.push(<%= captured %>)<% end %>
176
+ end <% if captured %>&&
177
+ (<%= captured %> = if <%= accumulate %>.empty?
178
+ <%= captured %>.map{[]} # Transpose hack won't work for empty case
179
+ else
180
+ <%= accumulate %>.transpose
181
+ end) <% end -%>
182
+ RUBY
183
+ REPEATED_TEMPLATE.location = [__FILE__, line + 1]
184
+
163
185
  def initialize(str, node_var = 'node0')
164
186
  @string = str
165
187
  @root = node_var
@@ -238,10 +260,48 @@ module RuboCop
238
260
  when REST then compile_ellipsis
239
261
  when '$<' then compile_any_order(next_capture)
240
262
  when '<' then compile_any_order
241
- else [1, compile_expr(token)]
263
+ else compile_repeated_expr(token)
264
+ end
265
+ end
266
+
267
+ def compile_repeated_expr(token)
268
+ before = @captures
269
+ expr = compile_expr(token)
270
+ min, max = parse_repetition_token
271
+ return [1, expr] if min.nil?
272
+
273
+ if @captures != before
274
+ captured = "captures[#{before}...#{@captures}]"
275
+ accumulate = next_temp_variable(:accumulate)
276
+ end
277
+ arity = min..max || Float::INFINITY
278
+
279
+ [arity, repeated_generator(expr, captured, accumulate)]
280
+ end
281
+
282
+ def repeated_generator(expr, captured, accumulate)
283
+ with_temp_variables do |child|
284
+ lambda do |range|
285
+ if range.begin == SEQ_HEAD_INDEX
286
+ fail_due_to 'repeated pattern at beginning of sequence'
287
+ end
288
+ REPEATED_TEMPLATE.result(binding)
289
+ end
242
290
  end
243
291
  end
244
292
 
293
+ def parse_repetition_token
294
+ case tokens.first
295
+ when '*' then min = 0
296
+ when '+' then min = 1
297
+ when '?' then min = 0
298
+ max = 1
299
+ else return
300
+ end
301
+ tokens.shift
302
+ [min, max]
303
+ end
304
+
245
305
  # @private
246
306
  # Builds Ruby code for a sequence
247
307
  # (head *first_terms variadic_term *last_terms)
@@ -276,6 +336,10 @@ module RuboCop
276
336
  last_terms_range { |r| @arities[r].inject(0, :+) } || 0
277
337
  end
278
338
 
339
+ def variadic_term_min_arity
340
+ @variadic_index ? @arities[@variadic_index].begin : 0
341
+ end
342
+
279
343
  def first_terms_range
280
344
  yield 1..(@variadic_index || @terms.size) - 1 if seq_head?
281
345
  end
@@ -289,8 +353,19 @@ module RuboCop
289
353
  end
290
354
 
291
355
  def compile_child_nb_guard
292
- min = first_terms_arity + last_terms_arity
293
- "#{CUR_NODE}.children.size #{@variadic_index ? '>' : '='}= #{min}"
356
+ fixed = first_terms_arity + last_terms_arity
357
+ min = fixed + variadic_term_min_arity
358
+ op = if @variadic_index
359
+ max_variadic = @arities[@variadic_index].end
360
+ if max_variadic != Float::INFINITY
361
+ range = min..fixed + max_variadic
362
+ return "(#{range}).cover?(#{CUR_NODE}.children.size)"
363
+ end
364
+ '>='
365
+ else
366
+ '=='
367
+ end
368
+ "#{CUR_NODE}.children.size #{op} #{min}"
294
369
  end
295
370
 
296
371
  def term(index, range)
@@ -555,10 +630,14 @@ module RuboCop
555
630
  end
556
631
 
557
632
  def with_temp_variables(&block)
558
- names = block.parameters.map { |_, name| "#{name}#{next_temp_value}" }
633
+ names = block.parameters.map { |_, name| next_temp_variable(name) }
559
634
  yield(*names)
560
635
  end
561
636
 
637
+ def next_temp_variable(name)
638
+ "#{name}#{next_temp_value}"
639
+ end
640
+
562
641
  def next_temp_value
563
642
  @temps += 1
564
643
  end