rubocop 0.67.2 → 0.68.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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +86 -233
  4. data/exe/rubocop +0 -12
  5. data/lib/rubocop.rb +13 -30
  6. data/lib/rubocop/ast/builder.rb +4 -0
  7. data/lib/rubocop/ast/node/alias_node.rb +24 -0
  8. data/lib/rubocop/ast/node/class_node.rb +31 -0
  9. data/lib/rubocop/ast/node/module_node.rb +24 -0
  10. data/lib/rubocop/ast/node/range_node.rb +7 -0
  11. data/lib/rubocop/ast/node/resbody_node.rb +12 -0
  12. data/lib/rubocop/ast/node/self_class_node.rb +24 -0
  13. data/lib/rubocop/cli.rb +40 -4
  14. data/lib/rubocop/config.rb +9 -7
  15. data/lib/rubocop/config_loader.rb +48 -7
  16. data/lib/rubocop/config_loader_resolver.rb +5 -4
  17. data/lib/rubocop/cop/commissioner.rb +24 -0
  18. data/lib/rubocop/cop/correctors/unused_arg_corrector.rb +18 -6
  19. data/lib/rubocop/cop/internal_affairs/node_destructuring.rb +12 -14
  20. data/lib/rubocop/cop/layout/access_modifier_indentation.rb +9 -20
  21. data/lib/rubocop/cop/layout/align_arguments.rb +93 -0
  22. data/lib/rubocop/cop/layout/align_parameters.rb +57 -33
  23. data/lib/rubocop/cop/layout/class_structure.rb +5 -5
  24. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +6 -8
  25. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +3 -6
  26. data/lib/rubocop/cop/layout/empty_lines_around_module_body.rb +1 -2
  27. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +1 -0
  28. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +292 -0
  29. data/lib/rubocop/cop/layout/{first_parameter_indentation.rb → indent_first_argument.rb} +11 -12
  30. data/lib/rubocop/cop/layout/{indent_array.rb → indent_first_array_element.rb} +2 -2
  31. data/lib/rubocop/cop/layout/{indent_hash.rb → indent_first_hash_element.rb} +2 -2
  32. data/lib/rubocop/cop/layout/indent_first_parameter.rb +96 -0
  33. data/lib/rubocop/cop/layout/indentation_width.rb +4 -16
  34. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +2 -4
  35. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +1 -16
  36. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +1 -2
  37. data/lib/rubocop/cop/lint/duplicate_methods.rb +6 -8
  38. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +4 -8
  39. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +157 -0
  40. data/lib/rubocop/cop/lint/inherit_exception.rb +3 -4
  41. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +18 -1
  42. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +3 -5
  43. data/lib/rubocop/cop/lint/underscore_prefixed_variable_name.rb +25 -5
  44. data/lib/rubocop/cop/lint/useless_assignment.rb +2 -6
  45. data/lib/rubocop/cop/lint/useless_setter_call.rb +1 -2
  46. data/lib/rubocop/cop/message_annotator.rb +1 -0
  47. data/lib/rubocop/cop/metrics/line_length.rb +139 -28
  48. data/lib/rubocop/cop/metrics/perceived_complexity.rb +3 -4
  49. data/lib/rubocop/cop/mixin/check_line_breakable.rb +190 -0
  50. data/lib/rubocop/cop/mixin/{array_hash_indentation.rb → multiline_element_indentation.rb} +3 -2
  51. data/lib/rubocop/cop/mixin/too_many_lines.rb +3 -7
  52. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +33 -4
  53. data/lib/rubocop/cop/rails/active_record_override.rb +23 -8
  54. data/lib/rubocop/cop/rails/delegate.rb +5 -8
  55. data/lib/rubocop/cop/rails/environment_comparison.rb +5 -3
  56. data/lib/rubocop/cop/rails/find_each.rb +1 -1
  57. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +3 -3
  58. data/lib/rubocop/cop/rails/reflection_class_name.rb +1 -1
  59. data/lib/rubocop/cop/rails/skips_model_validations.rb +6 -7
  60. data/lib/rubocop/cop/rails/time_zone.rb +3 -10
  61. data/lib/rubocop/cop/rails/validation.rb +3 -0
  62. data/lib/rubocop/cop/registry.rb +3 -3
  63. data/lib/rubocop/cop/style/alias.rb +13 -7
  64. data/lib/rubocop/cop/style/block_delimiters.rb +20 -0
  65. data/lib/rubocop/cop/style/class_and_module_children.rb +19 -21
  66. data/lib/rubocop/cop/style/class_methods.rb +16 -24
  67. data/lib/rubocop/cop/style/conditional_assignment.rb +20 -49
  68. data/lib/rubocop/cop/style/documentation.rb +3 -7
  69. data/lib/rubocop/cop/style/format_string.rb +18 -21
  70. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  71. data/lib/rubocop/cop/style/inverse_methods.rb +4 -0
  72. data/lib/rubocop/cop/style/lambda.rb +12 -8
  73. data/lib/rubocop/cop/style/mixin_grouping.rb +8 -10
  74. data/lib/rubocop/cop/style/module_function.rb +2 -3
  75. data/lib/rubocop/cop/style/next.rb +10 -14
  76. data/lib/rubocop/cop/style/one_line_conditional.rb +5 -3
  77. data/lib/rubocop/cop/style/optional_arguments.rb +1 -4
  78. data/lib/rubocop/cop/style/random_with_offset.rb +44 -47
  79. data/lib/rubocop/cop/style/redundant_return.rb +6 -14
  80. data/lib/rubocop/cop/style/redundant_sort_by.rb +1 -1
  81. data/lib/rubocop/cop/style/safe_navigation.rb +3 -0
  82. data/lib/rubocop/cop/style/struct_inheritance.rb +2 -3
  83. data/lib/rubocop/cop/style/symbol_proc.rb +20 -40
  84. data/lib/rubocop/cop/style/unless_else.rb +1 -2
  85. data/lib/rubocop/cop/style/yoda_condition.rb +8 -7
  86. data/lib/rubocop/cop/util.rb +2 -4
  87. data/lib/rubocop/file_finder.rb +5 -10
  88. data/lib/rubocop/formatter/disabled_config_formatter.rb +5 -0
  89. data/lib/rubocop/node_pattern.rb +304 -170
  90. data/lib/rubocop/options.rb +4 -1
  91. data/lib/rubocop/rspec/shared_contexts.rb +3 -0
  92. data/lib/rubocop/version.rb +1 -1
  93. data/lib/rubocop/yaml_duplication_checker.rb +1 -1
  94. metadata +26 -50
  95. data/lib/rubocop/cop/performance/caller.rb +0 -69
  96. data/lib/rubocop/cop/performance/case_when_splat.rb +0 -177
  97. data/lib/rubocop/cop/performance/casecmp.rb +0 -108
  98. data/lib/rubocop/cop/performance/chain_array_allocation.rb +0 -78
  99. data/lib/rubocop/cop/performance/compare_with_block.rb +0 -122
  100. data/lib/rubocop/cop/performance/count.rb +0 -102
  101. data/lib/rubocop/cop/performance/detect.rb +0 -110
  102. data/lib/rubocop/cop/performance/double_start_end_with.rb +0 -94
  103. data/lib/rubocop/cop/performance/end_with.rb +0 -56
  104. data/lib/rubocop/cop/performance/fixed_size.rb +0 -97
  105. data/lib/rubocop/cop/performance/flat_map.rb +0 -78
  106. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +0 -99
  107. data/lib/rubocop/cop/performance/open_struct.rb +0 -46
  108. data/lib/rubocop/cop/performance/range_include.rb +0 -50
  109. data/lib/rubocop/cop/performance/redundant_block_call.rb +0 -93
  110. data/lib/rubocop/cop/performance/redundant_match.rb +0 -56
  111. data/lib/rubocop/cop/performance/redundant_merge.rb +0 -183
  112. data/lib/rubocop/cop/performance/regexp_match.rb +0 -265
  113. data/lib/rubocop/cop/performance/reverse_each.rb +0 -42
  114. data/lib/rubocop/cop/performance/size.rb +0 -77
  115. data/lib/rubocop/cop/performance/start_with.rb +0 -59
  116. data/lib/rubocop/cop/performance/string_replacement.rb +0 -173
  117. data/lib/rubocop/cop/performance/times_map.rb +0 -71
  118. data/lib/rubocop/cop/performance/unfreeze_string.rb +0 -50
  119. data/lib/rubocop/cop/performance/uri_default_parser.rb +0 -47
@@ -90,6 +90,8 @@ module RuboCop
90
90
 
91
91
  # rubocop:disable Metrics/CyclomaticComplexity
92
92
  def check_branch(node)
93
+ return unless node
94
+
93
95
  case node.type
94
96
  when :return then check_return_node(node)
95
97
  when :case then check_case_node(node)
@@ -111,25 +113,15 @@ module RuboCop
111
113
  end
112
114
 
113
115
  def check_case_node(node)
114
- _cond, *when_nodes, else_node = *node
115
- when_nodes.each { |when_node| check_when_node(when_node) }
116
- check_branch(else_node) if else_node
117
- end
118
-
119
- def check_when_node(node)
120
- return unless node
121
-
122
- _cond, body = *node
123
- check_branch(body) if body
116
+ node.when_branches.each { |when_node| check_branch(when_node.body) }
117
+ check_branch(node.else_branch)
124
118
  end
125
119
 
126
120
  def check_if_node(node)
127
121
  return if node.modifier_form? || node.ternary?
128
122
 
129
- _cond, if_node, else_node = *node.node_parts
130
-
131
- check_branch(if_node) if if_node
132
- check_branch(else_node) if else_node
123
+ check_branch(node.if_branch)
124
+ check_branch(node.else_branch)
133
125
  end
134
126
 
135
127
  def check_rescue_node(node)
@@ -35,7 +35,7 @@ module RuboCop
35
35
  end
36
36
 
37
37
  def autocorrect(node)
38
- send, = *node
38
+ send = node.send_node
39
39
  ->(corrector) { corrector.replace(sort_by_range(send, node), 'sort') }
40
40
  end
41
41
 
@@ -65,6 +65,8 @@ module RuboCop
65
65
 
66
66
  MSG = 'Use safe navigation (`&.`) instead of checking if an object ' \
67
67
  'exists before calling the method.'.freeze
68
+ LOGIC_JUMP_KEYWORDS = %i[break fail next raise
69
+ return throw yield].freeze
68
70
 
69
71
  minimum_target_ruby_version 2.3
70
72
 
@@ -149,6 +151,7 @@ module RuboCop
149
151
 
150
152
  checked_variable, matching_receiver, method =
151
153
  extract_common_parts(receiver, variable)
154
+ matching_receiver = nil if LOGIC_JUMP_KEYWORDS.include?(receiver.type)
152
155
  [checked_variable, matching_receiver, receiver, method]
153
156
  end
154
157
 
@@ -24,10 +24,9 @@ module RuboCop
24
24
  'Use a block to customize the struct.'.freeze
25
25
 
26
26
  def on_class(node)
27
- _name, superclass, _body = *node
28
- return unless struct_constructor?(superclass)
27
+ return unless struct_constructor?(node.parent_class)
29
28
 
30
- add_offense(node, location: superclass.source_range)
29
+ add_offense(node, location: node.parent_class.source_range)
31
30
  end
32
31
 
33
32
  def_node_matcher :struct_constructor?, <<-PATTERN
@@ -32,46 +32,38 @@ module RuboCop
32
32
  end
33
33
 
34
34
  def on_block(node)
35
- symbol_proc?(node) do |send_or_super, block_args, method|
36
- block_method_name = resolve_block_method_name(send_or_super)
37
-
35
+ symbol_proc?(node) do |dispatch_node, arguments_node, method_name|
38
36
  # TODO: Rails-specific handling that we should probably make
39
37
  # configurable - https://github.com/rubocop-hq/rubocop/issues/1485
40
38
  # we should ignore lambdas & procs
41
- return if proc_node?(send_or_super)
42
- return if %i[lambda proc].include?(block_method_name)
43
- return if ignored_method?(block_method_name)
44
- return if block_args.children.size == 1 &&
45
- block_args.source.include?(',')
39
+ return if proc_node?(dispatch_node)
40
+ return if %i[lambda proc].include?(dispatch_node.method_name)
41
+ return if ignored_method?(dispatch_node.method_name)
42
+ return if destructuring_block_argument?(arguments_node)
46
43
 
47
- offense(node, method, block_method_name)
44
+ register_offense(node, method_name, dispatch_node.method_name)
48
45
  end
49
46
  end
50
47
 
48
+ def destructuring_block_argument?(argument_node)
49
+ argument_node.one? && argument_node.source.include?(',')
50
+ end
51
+
51
52
  def autocorrect(node)
52
53
  lambda do |corrector|
53
- block_send_or_super, _block_args, block_body = *node
54
- _receiver, method_name, _args = *block_body
55
-
56
- if super?(block_send_or_super)
57
- args = *block_send_or_super
54
+ if node.send_node.arguments?
55
+ autocorrect_with_args(corrector, node,
56
+ node.send_node.arguments,
57
+ node.body.method_name)
58
58
  else
59
- _breceiver, _bmethod_name, *args = *block_send_or_super
59
+ autocorrect_without_args(corrector, node)
60
60
  end
61
- autocorrect_method(corrector, node, args, method_name)
62
61
  end
63
62
  end
64
63
 
65
64
  private
66
65
 
67
- def resolve_block_method_name(block_send_or_super)
68
- return :super if super?(block_send_or_super)
69
-
70
- _receiver, method_name, _args = *block_send_or_super
71
- method_name
72
- end
73
-
74
- def offense(node, method_name, block_method_name)
66
+ def register_offense(node, method_name, block_method_name)
75
67
  block_start = node.loc.begin.begin_pos
76
68
  block_end = node.loc.end.end_pos
77
69
  range = range_between(block_start, block_end)
@@ -83,16 +75,9 @@ module RuboCop
83
75
  block_method: block_method_name))
84
76
  end
85
77
 
86
- def autocorrect_method(corrector, node, args, method_name)
87
- if args.empty?
88
- autocorrect_no_args(corrector, node, method_name)
89
- else
90
- autocorrect_with_args(corrector, node, args, method_name)
91
- end
92
- end
93
-
94
- def autocorrect_no_args(corrector, node, method_name)
95
- corrector.replace(block_range_with_space(node), "(&:#{method_name})")
78
+ def autocorrect_without_args(corrector, node)
79
+ corrector.replace(block_range_with_space(node),
80
+ "(&:#{node.body.method_name})")
96
81
  end
97
82
 
98
83
  def autocorrect_with_args(corrector, node, args, method_name)
@@ -111,8 +96,7 @@ module RuboCop
111
96
  end
112
97
 
113
98
  def begin_pos_for_replacement(node)
114
- block_send_or_super, _block_args, _block_body = *node
115
- expr = block_send_or_super.source_range
99
+ expr = node.send_node.source_range
116
100
 
117
101
  if (paren_pos = (expr.source =~ /\(\s*\)$/))
118
102
  expr.begin_pos + paren_pos
@@ -120,10 +104,6 @@ module RuboCop
120
104
  node.loc.begin.begin_pos
121
105
  end
122
106
  end
123
-
124
- def super?(node)
125
- SUPER_TYPES.include?(node.type)
126
- end
127
107
  end
128
108
  end
129
109
  end
@@ -32,8 +32,7 @@ module RuboCop
32
32
  end
33
33
 
34
34
  def autocorrect(node)
35
- condition, = *node
36
- body_range = range_between_condition_and_else(node, condition)
35
+ body_range = range_between_condition_and_else(node, node.condition)
37
36
  else_range = range_between_else_and_end(node)
38
37
 
39
38
  lambda do |corrector|
@@ -98,7 +98,8 @@ module RuboCop
98
98
  end
99
99
 
100
100
  def valid_yoda?(node)
101
- lhs, _operator, rhs = *node
101
+ lhs = node.receiver
102
+ rhs = node.first_argument
102
103
 
103
104
  return true if lhs.literal? && rhs.literal? ||
104
105
  !lhs.literal? && !rhs.literal?
@@ -111,8 +112,10 @@ module RuboCop
111
112
  end
112
113
 
113
114
  def corrected_code(node)
114
- lhs, operator, rhs = *node
115
- "#{rhs.source} #{reverse_comparison(operator)} #{lhs.source}"
115
+ lhs = node.receiver
116
+ rhs = node.first_argument
117
+
118
+ "#{rhs.source} #{reverse_comparison(node.method_name)} #{lhs.source}"
116
119
  end
117
120
 
118
121
  def actual_code_range(node)
@@ -126,13 +129,11 @@ module RuboCop
126
129
  end
127
130
 
128
131
  def non_equality_operator?(node)
129
- _, operator, = *node
130
- !EQUALITY_OPERATORS.include?(operator)
132
+ !EQUALITY_OPERATORS.include?(node.method_name)
131
133
  end
132
134
 
133
135
  def noncommutative_operator?(node)
134
- _, operator, = *node
135
- NONCOMMUTATIVE_OPERATORS.include?(operator)
136
+ NONCOMMUTATIVE_OPERATORS.include?(node.method_name)
136
137
  end
137
138
  end
138
139
  end
@@ -45,11 +45,9 @@ module RuboCop
45
45
  while node
46
46
  case node.type
47
47
  when :send
48
- receiver, _method_name, _args = *node
49
- node = receiver
48
+ node = node.receiver
50
49
  when :block
51
- method, _args, _body = *node
52
- node = method
50
+ node = node.send_node
53
51
  else
54
52
  break
55
53
  end
@@ -13,16 +13,16 @@ module RuboCop
13
13
  @root_level == path.to_s
14
14
  end
15
15
 
16
- def find_file_upwards(filename, start_dir, use_home: false)
17
- traverse_files_upwards(filename, start_dir, use_home) do |file|
16
+ def find_file_upwards(filename, start_dir)
17
+ traverse_files_upwards(filename, start_dir) do |file|
18
18
  # minimize iteration for performance
19
19
  return file if file
20
20
  end
21
21
  end
22
22
 
23
- def find_files_upwards(filename, start_dir, use_home: false)
23
+ def find_files_upwards(filename, start_dir)
24
24
  files = []
25
- traverse_files_upwards(filename, start_dir, use_home) do |file|
25
+ traverse_files_upwards(filename, start_dir) do |file|
26
26
  files << file
27
27
  end
28
28
  files
@@ -30,18 +30,13 @@ module RuboCop
30
30
 
31
31
  private
32
32
 
33
- def traverse_files_upwards(filename, start_dir, use_home)
33
+ def traverse_files_upwards(filename, start_dir)
34
34
  Pathname.new(start_dir).expand_path.ascend do |dir|
35
35
  break if FileFinder.root_level?(dir)
36
36
 
37
37
  file = dir + filename
38
38
  yield(file.to_s) if file.exist?
39
39
  end
40
-
41
- return unless use_home && ENV.key?('HOME')
42
-
43
- file = File.join(Dir.home, filename)
44
- yield(file) if File.exist?(file)
45
40
  end
46
41
  end
47
42
  end
@@ -58,6 +58,11 @@ module RuboCop
58
58
 
59
59
  def command
60
60
  command = 'rubocop --auto-gen-config'
61
+
62
+ if @options[:auto_gen_only_exclude]
63
+ command += ' --auto-gen-only-exclude'
64
+ end
65
+
61
66
  if @exclude_limit_option
62
67
  command +=
63
68
  format(' --exclude-limit %<limit>d',
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'delegate'
4
+ require 'erb'
5
+
3
6
  # rubocop:disable Metrics/ClassLength, Metrics/CyclomaticComplexity
4
7
  module RuboCop
5
8
  # This class performs a pattern-matching operation on an AST node.
@@ -38,6 +41,12 @@ module RuboCop
38
41
  # '$(send const ...)' # arbitrary matching can be performed on a capture
39
42
  # '(send _recv _msg)' # wildcards can be named (for readability)
40
43
  # '(send ... :new)' # you can match against the last children
44
+ # '(array <str sym>)' # you can match children in any order. This
45
+ # # would match `['x', :y]` as well as `[:y, 'x']
46
+ # '(_ <str sym ...>)' # will match if arguments have at least a `str` and
47
+ # # a `sym` node, but can have more.
48
+ # '(array <$str $_>)' # captures are in the order of the pattern,
49
+ # # irrespective of the actual order of the children
41
50
  # '(send $...)' # capture all the children as an array
42
51
  # '(send $... int)' # capture all children but the last as an array
43
52
  # '(send _x :+ _x)' # unification is performed on named wildcards
@@ -97,7 +106,7 @@ module RuboCop
97
106
  class Compiler
98
107
  SYMBOL = %r{:(?:[\w+@*/?!<>=~|%^-]+|\[\]=?)}.freeze
99
108
  IDENTIFIER = /[a-zA-Z_][a-zA-Z0-9_-]*/.freeze
100
- META = /\(|\)|\{|\}|\[|\]|\$\.\.\.|\$|!|\^|\.\.\./.freeze
109
+ META = Regexp.union(%w"( ) { } [ ] $< < > $... $ ! ^ ...").freeze
101
110
  NUMBER = /-?\d+(?:\.\d+)?/.freeze
102
111
  STRING = /".+?"/.freeze
103
112
  METHOD_NAME = /\#?#{IDENTIFIER}[\!\?]?\(?/.freeze
@@ -121,10 +130,36 @@ module RuboCop
121
130
  REST = '...'.freeze
122
131
  CAPTURED_REST = '$...'.freeze
123
132
 
124
- attr_reader :match_code
133
+ attr_reader :match_code, :tokens, :captures
125
134
 
126
135
  SEQ_HEAD_INDEX = -1
127
136
 
137
+ # Placeholders while compiling, see with_..._context methods
138
+ CUR_PLACEHOLDER = '@@@cur'.freeze
139
+ CUR_NODE = "#{CUR_PLACEHOLDER} node@@@".freeze
140
+ CUR_ELEMENT = "#{CUR_PLACEHOLDER} element@@@".freeze
141
+ SEQ_HEAD_GUARD = '@@@seq guard head@@@'.freeze
142
+
143
+ line = __LINE__
144
+ ANY_ORDER_TEMPLATE = ERB.new <<-RUBY.strip_indent.gsub("-%>\n", '%>')
145
+ <% if capture_rest %>(<%= capture_rest %> = []) && <% end -%>
146
+ <% if capture_all %>(<%= capture_all %> = <% end -%>
147
+ <%= CUR_NODE %>.children[<%= range %>]<% if capture_all %>)<% end -%>
148
+ .each_with_object({}) { |<%= child %>, <%= matched %>|
149
+ case
150
+ <% patterns.each_with_index do |pattern, i| -%>
151
+ when !<%= matched %>[<%= i %>] && <%=
152
+ with_context(pattern, child, use_temp_node: false)
153
+ %> then <%= matched %>[<%= i %>] = true
154
+ <% end -%>
155
+ <% if !rest %> else break({})
156
+ <% elsif capture_rest %> else <%= capture_rest %> << <%= child %>
157
+ <% end -%>
158
+ end
159
+ }.size == <%= patterns.size -%>
160
+ RUBY
161
+ ANY_ORDER_TEMPLATE.location = [__FILE__, line + 1]
162
+
128
163
  def initialize(str, node_var = 'node0')
129
164
  @string = str
130
165
  @root = node_var
@@ -133,40 +168,45 @@ module RuboCop
133
168
  @captures = 0 # number of captures seen
134
169
  @unify = {} # named wildcard -> temp variable number
135
170
  @params = 0 # highest % (param) number seen
136
-
137
171
  run(node_var)
138
172
  end
139
173
 
140
174
  def run(node_var)
141
- tokens = Compiler.tokens(@string)
175
+ @tokens = Compiler.tokens(@string)
142
176
 
143
- @match_code = compile_expr(tokens, node_var, false)
177
+ @match_code = with_context(compile_expr, node_var, use_temp_node: false)
178
+ @match_code.prepend("(captures = Array.new(#{@captures})) && ") \
179
+ if @captures > 0
144
180
 
145
181
  fail_due_to('unbalanced pattern') unless tokens.empty?
146
182
  end
147
183
 
148
184
  # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
149
- def compile_expr(tokens, cur_node, seq_head)
185
+ def compile_expr(token = tokens.shift)
150
186
  # read a single pattern-matching expression from the token stream,
151
187
  # return Ruby code which performs the corresponding matching operation
152
- # on 'cur_node' (which is Ruby code which evaluates to an AST node)
153
188
  #
154
189
  # the 'pattern-matching' expression may be a composite which
155
- # contains an arbitrary number of sub-expressions
156
- token = tokens.shift
190
+ # contains an arbitrary number of sub-expressions, but that composite
191
+ # must all have precedence higher or equal to that of `&&`
192
+ #
193
+ # Expressions may use placeholders like:
194
+ # CUR_NODE: Ruby code that evaluates to an AST node
195
+ # CUR_ELEMENT: Either the node or the type if in first element of
196
+ # a sequence (aka seq_head, e.g. "(seq_head first_node_arg ...")
157
197
  case token
158
- when '(' then compile_seq(tokens, cur_node, seq_head)
159
- when '{' then compile_union(tokens, cur_node, seq_head)
160
- when '[' then compile_intersect(tokens, cur_node, seq_head)
161
- when '!' then compile_negation(tokens, cur_node, seq_head)
162
- when '$' then compile_capture(tokens, cur_node, seq_head)
163
- when '^' then compile_ascend(tokens, cur_node, seq_head)
164
- when WILDCARD then compile_wildcard(cur_node, token[1..-1], seq_head)
165
- when FUNCALL then compile_funcall(tokens, cur_node, token, seq_head)
166
- when LITERAL then compile_literal(cur_node, token, seq_head)
167
- when PREDICATE then compile_predicate(tokens, cur_node, token, seq_head)
168
- when NODE then compile_nodetype(cur_node, token)
169
- when PARAM then compile_param(cur_node, token[1..-1], seq_head)
198
+ when '(' then compile_seq
199
+ when '{' then compile_union
200
+ when '[' then compile_intersect
201
+ when '!' then compile_negation
202
+ when '$' then compile_capture
203
+ when '^' then compile_ascend
204
+ when WILDCARD then compile_wildcard(token[1..-1])
205
+ when FUNCALL then compile_funcall(token)
206
+ when LITERAL then compile_literal(token)
207
+ when PREDICATE then compile_predicate(token)
208
+ when NODE then compile_nodetype(token)
209
+ when PARAM then compile_param(token[1..-1])
170
210
  when CLOSING then fail_due_to("#{token} in invalid position")
171
211
  when nil then fail_due_to('pattern ended prematurely')
172
212
  else fail_due_to("invalid token #{token.inspect}")
@@ -174,211 +214,260 @@ module RuboCop
174
214
  end
175
215
  # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
176
216
 
177
- def compile_seq(tokens, cur_node, seq_head)
178
- fail_due_to('empty parentheses') if tokens.first == ')'
179
- fail_due_to('parentheses at sequence head') if seq_head
217
+ def tokens_until(stop, what)
218
+ return to_enum __method__, stop, what unless block_given?
180
219
 
181
- # 'cur_node' is a Ruby expression which evaluates to an AST node,
182
- # but we don't know how expensive it is
183
- # to be safe, cache the node in a temp variable and then use the
184
- # temp variable as 'cur_node'
185
- with_temp_node(cur_node) do |init, temp_node|
186
- terms = compile_seq_terms(tokens, temp_node)
187
- terms.unshift(compile_guard_clause(temp_node))
220
+ fail_due_to("empty #{what}") if tokens.first == stop && what
221
+ yield until tokens.first == stop
222
+ tokens.shift
223
+ end
188
224
 
189
- join_terms(init, terms, " &&\n")
190
- end
225
+ def compile_seq
226
+ terms = tokens_until(')', 'sequence').map { variadic_seq_term }
227
+ Sequence.new(self, *terms).compile
191
228
  end
192
229
 
193
- def compile_guard_clause(cur_node)
194
- "#{cur_node}.is_a?(RuboCop::AST::Node)"
230
+ def compile_guard_clause
231
+ "#{CUR_NODE}.is_a?(RuboCop::AST::Node)"
195
232
  end
196
233
 
197
- def compile_seq_terms(tokens, cur_node)
198
- ret =
199
- compile_seq_terms_with_size(tokens, cur_node) do |token, terms, index|
200
- capture = next_capture if token == CAPTURED_REST
201
- if capture || token == REST
202
- index = 0 if index == SEQ_HEAD_INDEX # Consider ($...) as (_ $...)
203
- return compile_ellipsis(tokens, cur_node, terms, index, capture)
204
- end
205
- end
206
- ret << "(#{cur_node}.children.size == #{ret.size - 1})"
234
+ def variadic_seq_term
235
+ token = tokens.shift
236
+ case token
237
+ when CAPTURED_REST then compile_captured_ellipsis
238
+ when REST then compile_ellipsis
239
+ when '$<' then compile_any_order(next_capture)
240
+ when '<' then compile_any_order
241
+ else [1, compile_expr(token)]
242
+ end
207
243
  end
208
244
 
209
- def compile_seq_terms_with_size(tokens, cur_node)
210
- index = SEQ_HEAD_INDEX
211
- terms = []
212
- until tokens.first == ')'
213
- yield tokens.first, terms, index
214
- term = compile_expr_with_index(tokens, cur_node, index)
215
- index += 1
216
- terms << term
245
+ # @private
246
+ # Builds Ruby code for a sequence
247
+ # (head *first_terms variadic_term *last_terms)
248
+ class Sequence < SimpleDelegator
249
+ def initialize(compiler, *arity_term_list)
250
+ @arities, @terms = arity_term_list.transpose
251
+
252
+ super(compiler)
253
+ @variadic_index = @arities.find_index { |a| a.is_a?(Range) }
254
+ fail_due_to 'multiple variable patterns in same sequence' \
255
+ if @variadic_index && !@arities.one? { |a| a.is_a?(Range) }
217
256
  end
218
257
 
219
- tokens.shift # drop concluding )
220
- terms
221
- end
258
+ def compile
259
+ [
260
+ compile_guard_clause,
261
+ compile_child_nb_guard,
262
+ compile_seq_head,
263
+ *compile_first_terms,
264
+ compile_variadic_term,
265
+ *compile_last_terms
266
+ ].compact.join(" &&\n") << SEQ_HEAD_GUARD
267
+ end
222
268
 
223
- def compile_expr_with_index(tokens, cur_node, index)
224
- if index == SEQ_HEAD_INDEX
225
- # in 'sequence head' position; some expressions are compiled
226
- # differently at 'sequence head' (notably 'node type' expressions)
227
- # grep for seq_head to see where it makes a difference
228
- compile_expr(tokens, cur_node, true)
229
- else
230
- child_node = "#{cur_node}.children[#{index}]"
231
- compile_expr(tokens, child_node, false)
269
+ private
270
+
271
+ def first_terms_arity
272
+ first_terms_range { |r| @arities[r].inject(0, :+) } || 0
232
273
  end
233
- end
234
274
 
235
- def compile_ellipsis(tokens, cur_node, terms, index, capture = nil)
236
- tokens.shift # drop ellipsis
237
- tail = compile_seq_tail(tokens, cur_node)
238
- terms << "(#{cur_node}.children.size >= #{index + tail.size})"
239
- terms.concat tail
240
- if capture
241
- range = index..-tail.size - 1
242
- terms << "(#{capture} = #{cur_node}.children[#{range}])"
275
+ def last_terms_arity
276
+ last_terms_range { |r| @arities[r].inject(0, :+) } || 0
243
277
  end
244
- terms
245
- end
246
278
 
247
- def compile_seq_tail(tokens, cur_node)
248
- child_node = "#{cur_node}.children[%<revindex>i]"
249
- terms = []
250
- until tokens.first == ')'
251
- terms << compile_expr(tokens, child_node, false)
279
+ def first_terms_range
280
+ yield 1..(@variadic_index || @terms.size) - 1 if seq_head?
252
281
  end
253
- tokens.shift # drop ')'
254
- # E.g. for terms.size == 3, we want to replace the three [%<revindex>i]
255
- # with [-3], [-2] and [-1]
256
- terms.map.with_index { |term, i| format term, revindex: i - terms.size }
257
- end
258
282
 
259
- def compile_union(tokens, cur_node, seq_head)
260
- fail_due_to('empty union') if tokens.first == '}'
283
+ def last_terms_range
284
+ yield @variadic_index + 1...@terms.size if @variadic_index
285
+ end
261
286
 
262
- with_temp_node(cur_node) do |init, temp_node|
263
- terms = union_terms(tokens, temp_node, seq_head)
264
- join_terms(init, terms, ' || ')
287
+ def seq_head?
288
+ @variadic_index != 0
265
289
  end
266
- end
267
290
 
268
- def union_terms(tokens, temp_node, seq_head)
269
- # we need to ensure that each branch of the {} contains the same
270
- # number of captures (since only one branch of the {} can actually
271
- # match, the same variables are used to hold the captures for each
272
- # branch)
273
- compile_expr_with_captures(tokens,
274
- temp_node, seq_head) do |term, before, after|
275
- terms = [term]
276
- until tokens.first == '}'
277
- terms << compile_expr_with_capture_check(tokens, temp_node,
278
- seq_head, before, after)
291
+ def compile_child_nb_guard
292
+ min = first_terms_arity + last_terms_arity
293
+ "#{CUR_NODE}.children.size #{@variadic_index ? '>' : '='}= #{min}"
294
+ end
295
+
296
+ def term(index, range)
297
+ t = @terms[index]
298
+ if t.respond_to? :call
299
+ t.call(range)
300
+ else
301
+ with_child_context(t, range.begin)
279
302
  end
280
- tokens.shift
303
+ end
304
+
305
+ def compile_seq_head
306
+ return unless seq_head?
307
+
308
+ fail_due_to 'sequences can not start with <' \
309
+ if @terms[0].respond_to? :call
281
310
 
282
- terms
311
+ with_seq_head_context(@terms[0])
283
312
  end
284
- end
285
313
 
286
- def compile_expr_with_captures(tokens, temp_node, seq_head)
287
- captures_before = @captures
288
- expr = compile_expr(tokens, temp_node, seq_head)
314
+ def compile_first_terms
315
+ first_terms_range { |range| compile_terms(range, 0) }
316
+ end
289
317
 
290
- yield expr, captures_before, @captures
291
- end
318
+ def compile_last_terms
319
+ last_terms_range { |r| compile_terms(r, -last_terms_arity) }
320
+ end
292
321
 
293
- def compile_expr_with_capture_check(tokens, temp_node, seq_head, before,
294
- after)
295
- @captures = before
296
- expr = compile_expr(tokens, temp_node, seq_head)
297
- if @captures != after
298
- fail_due_to('each branch of {} must have same # of captures')
322
+ def compile_terms(index_range, start)
323
+ index_range.map do |i|
324
+ current = start
325
+ start += @arities.fetch(i)
326
+ term(i, current..start - 1)
327
+ end
299
328
  end
300
329
 
301
- expr
302
- end
330
+ def compile_variadic_term
331
+ variadic_arity { |arity| term(@variadic_index, arity) }
332
+ end
303
333
 
304
- def compile_intersect(tokens, cur_node, seq_head)
305
- fail_due_to('empty intersection') if tokens.first == ']'
334
+ def variadic_arity
335
+ return unless @variadic_index
306
336
 
307
- with_temp_node(cur_node) do |init, temp_node|
308
- terms = []
309
- until tokens.first == ']'
310
- terms << compile_expr(tokens, temp_node, seq_head)
337
+ first = @variadic_index > 0 ? first_terms_arity : SEQ_HEAD_INDEX
338
+ yield first..-last_terms_arity - 1
339
+ end
340
+ end
341
+ private_constant :Sequence
342
+
343
+ def compile_captured_ellipsis
344
+ capture = next_capture
345
+ block = lambda { |range|
346
+ # Consider ($...) like (_ $...):
347
+ range = 0..range.end if range.begin == SEQ_HEAD_INDEX
348
+ "(#{capture} = #{CUR_NODE}.children[#{range}])"
349
+ }
350
+ [0..Float::INFINITY, block]
351
+ end
352
+
353
+ def compile_ellipsis
354
+ [0..Float::INFINITY, 'true']
355
+ end
356
+
357
+ # rubocop:disable Metrics/MethodLength
358
+ def compile_any_order(capture_all = nil)
359
+ rest = capture_rest = nil
360
+ patterns = []
361
+ with_temp_variables do |child, matched|
362
+ tokens_until('>', 'any child').each do
363
+ fail_due_to 'ellipsis must be at the end of <>' if rest
364
+ token = tokens.shift
365
+ case token
366
+ when CAPTURED_REST then rest = capture_rest = next_capture
367
+ when REST then rest = true
368
+ else patterns << compile_expr(token)
369
+ end
311
370
  end
312
- tokens.shift
371
+ [rest ? patterns.size..Float::INFINITY : patterns.size,
372
+ ->(range) { ANY_ORDER_TEMPLATE.result(binding) }]
373
+ end
374
+ end
375
+ # rubocop:enable Metrics/MethodLength
376
+
377
+ def insure_same_captures(enum, what)
378
+ return to_enum __method__, enum, what unless block_given?
313
379
 
314
- join_terms(init, terms, ' && ')
380
+ captures_before = captures_after = nil
381
+ enum.each do
382
+ captures_before ||= @captures
383
+ @captures = captures_before
384
+ yield
385
+ captures_after ||= @captures
386
+ if captures_after != @captures
387
+ fail_due_to("each #{what} must have same # of captures")
388
+ end
315
389
  end
316
390
  end
317
391
 
318
- def compile_capture(tokens, cur_node, seq_head)
319
- "(#{next_capture} = #{cur_node}#{'.type' if seq_head}; " \
320
- "#{compile_expr(tokens, cur_node, seq_head)})"
392
+ def compile_union
393
+ # we need to ensure that each branch of the {} contains the same
394
+ # number of captures (since only one branch of the {} can actually
395
+ # match, the same variables are used to hold the captures for each
396
+ # branch)
397
+ enum = tokens_until('}', 'union')
398
+ terms = insure_same_captures(enum, 'branch of {}')
399
+ .map { compile_expr }
400
+
401
+ "(#{terms.join(' || ')})"
402
+ end
403
+
404
+ def compile_intersect
405
+ tokens_until(']', 'intersection')
406
+ .map { compile_expr }
407
+ .join(' && ')
321
408
  end
322
409
 
323
- def compile_negation(tokens, cur_node, seq_head)
324
- "(!#{compile_expr(tokens, cur_node, seq_head)})"
410
+ def compile_capture
411
+ "(#{next_capture} = #{CUR_ELEMENT}; #{compile_expr})"
325
412
  end
326
413
 
327
- def compile_ascend(tokens, cur_node, seq_head)
328
- "(#{cur_node}.parent && " \
329
- "#{compile_expr(tokens, "#{cur_node}.parent", seq_head)})"
414
+ def compile_negation
415
+ "!(#{compile_expr})"
330
416
  end
331
417
 
332
- def compile_wildcard(cur_node, name, seq_head)
418
+ def compile_ascend
419
+ with_context("#{CUR_NODE} && #{compile_expr}", "#{CUR_NODE}.parent")
420
+ end
421
+
422
+ def compile_wildcard(name)
333
423
  if name.empty?
334
424
  'true'
335
425
  elsif @unify.key?(name)
336
426
  # we have already seen a wildcard with this name before
337
427
  # so the value it matched the first time will already be stored
338
428
  # in a temp. check if this value matches the one stored in the temp
339
- "(#{cur_node}#{'.type' if seq_head} == temp#{@unify[name]})"
429
+ "#{CUR_ELEMENT} == temp#{@unify[name]}"
340
430
  else
341
431
  n = @unify[name] = next_temp_value
342
432
  # double assign to temp#{n} to avoid "assigned but unused variable"
343
- "(temp#{n} = #{cur_node}#{'.type' if seq_head}; " \
433
+ "(temp#{n} = #{CUR_ELEMENT}; " \
344
434
  "temp#{n} = temp#{n}; true)"
345
435
  end
346
436
  end
347
437
 
348
- def compile_literal(cur_node, literal, seq_head)
349
- "(#{cur_node}#{'.type' if seq_head} == #{literal})"
438
+ def compile_literal(literal)
439
+ "#{CUR_ELEMENT} == #{literal}"
350
440
  end
351
441
 
352
- def compile_predicate(tokens, cur_node, predicate, seq_head)
442
+ def compile_predicate(predicate)
353
443
  if predicate.end_with?('(') # is there an arglist?
354
444
  args = compile_args(tokens)
355
445
  predicate = predicate[0..-2] # drop the trailing (
356
- "(#{cur_node}#{'.type' if seq_head}.#{predicate}(#{args.join(',')}))"
446
+ "#{CUR_ELEMENT}.#{predicate}(#{args.join(',')})"
357
447
  else
358
- "(#{cur_node}#{'.type' if seq_head}.#{predicate})"
448
+ "#{CUR_ELEMENT}.#{predicate}"
359
449
  end
360
450
  end
361
451
 
362
- def compile_funcall(tokens, cur_node, method, seq_head)
452
+ def compile_funcall(method)
363
453
  # call a method in the context which this pattern-matching
364
454
  # code is used in. pass target value as an argument
365
455
  method = method[1..-1] # drop the leading #
366
456
  if method.end_with?('(') # is there an arglist?
367
457
  args = compile_args(tokens)
368
458
  method = method[0..-2] # drop the trailing (
369
- "(#{method}(#{cur_node}#{'.type' if seq_head},#{args.join(',')}))"
459
+ "#{method}(#{CUR_ELEMENT},#{args.join(',')})"
370
460
  else
371
- "(#{method}(#{cur_node}#{'.type' if seq_head}))"
461
+ "#{method}(#{CUR_ELEMENT})"
372
462
  end
373
463
  end
374
464
 
375
- def compile_nodetype(cur_node, type)
376
- "(#{cur_node}.is_a?(RuboCop::AST::Node) && " \
377
- "#{cur_node}.#{type.tr('-', '_')}_type?)"
465
+ def compile_nodetype(type)
466
+ "#{compile_guard_clause} && #{CUR_NODE}.#{type.tr('-', '_')}_type?"
378
467
  end
379
468
 
380
- def compile_param(cur_node, number, seq_head)
381
- "(#{cur_node}#{'.type' if seq_head} == #{get_param(number)})"
469
+ def compile_param(number)
470
+ "#{CUR_ELEMENT} == #{get_param(number)}"
382
471
  end
383
472
 
384
473
  def compile_args(tokens)
@@ -406,7 +495,9 @@ module RuboCop
406
495
  end
407
496
 
408
497
  def next_capture
409
- "capture#{@captures += 1}"
498
+ index = @captures
499
+ @captures += 1
500
+ "captures[#{index}]"
410
501
  end
411
502
 
412
503
  def get_param(number)
@@ -415,21 +506,24 @@ module RuboCop
415
506
  number.zero? ? @root : "param#{number}"
416
507
  end
417
508
 
418
- def join_terms(init, terms, operator)
419
- "(#{init};#{terms.join(operator)})"
420
- end
421
-
422
- def emit_capture_list
423
- (1..@captures).map { |n| "capture#{n}" }.join(',')
509
+ def emit_yield_capture(when_no_capture = '')
510
+ yield_val = if @captures.zero?
511
+ when_no_capture
512
+ elsif @captures == 1
513
+ 'captures[0]' # Circumvent https://github.com/jruby/jruby/issues/5710
514
+ else
515
+ '*captures'
516
+ end
517
+ "yield(#{yield_val})"
424
518
  end
425
519
 
426
520
  def emit_retval
427
521
  if @captures.zero?
428
522
  'true'
429
523
  elsif @captures == 1
430
- 'capture1'
524
+ 'captures[0]'
431
525
  else
432
- "[#{emit_capture_list}]"
526
+ 'captures'
433
527
  end
434
528
  end
435
529
 
@@ -445,7 +539,7 @@ module RuboCop
445
539
  def emit_method_code
446
540
  <<-RUBY
447
541
  return unless #{@match_code}
448
- block_given? ? yield(#{emit_capture_list}) : (return #{emit_retval})
542
+ block_given? ? #{emit_yield_capture} : (return #{emit_retval})
449
543
  RUBY
450
544
  end
451
545
 
@@ -454,20 +548,62 @@ module RuboCop
454
548
  end
455
549
 
456
550
  def with_temp_node(cur_node)
457
- with_temp_variable do |temp_var|
458
- # double assign to temp#{n} to avoid "assigned but unused variable"
459
- yield "#{temp_var} = #{cur_node}; #{temp_var} = #{temp_var}", temp_var
551
+ with_temp_variables do |node|
552
+ yield "(#{node} = #{cur_node})", node
460
553
  end
554
+ .gsub("\n", "\n ") # Nicer indent for debugging
461
555
  end
462
556
 
463
- def with_temp_variable
464
- yield "temp#{next_temp_value}"
557
+ def with_temp_variables(&block)
558
+ names = block.parameters.map { |_, name| "#{name}#{next_temp_value}" }
559
+ yield(*names)
465
560
  end
466
561
 
467
562
  def next_temp_value
468
563
  @temps += 1
469
564
  end
470
565
 
566
+ def auto_use_temp_node?(code)
567
+ code.scan(CUR_PLACEHOLDER).count > 1
568
+ end
569
+
570
+ # with_<...>_context methods are used whenever the context,
571
+ # i.e the current node or the current element can be determined.
572
+
573
+ def with_child_context(code, child_index)
574
+ with_context(code, "#{CUR_NODE}.children[#{child_index}]")
575
+ end
576
+
577
+ def with_context(code, cur_node,
578
+ use_temp_node: auto_use_temp_node?(code))
579
+ if use_temp_node
580
+ with_temp_node(cur_node) do |init, temp_var|
581
+ substitute_cur_node(code, temp_var, first_cur_node: init)
582
+ end
583
+ else
584
+ substitute_cur_node(code, cur_node)
585
+ end
586
+ end
587
+
588
+ def with_seq_head_context(code)
589
+ if code.include?(SEQ_HEAD_GUARD)
590
+ fail_due_to('parentheses at sequence head')
591
+ end
592
+
593
+ code.gsub CUR_ELEMENT, "#{CUR_NODE}.type"
594
+ end
595
+
596
+ def substitute_cur_node(code, cur_node, first_cur_node: cur_node)
597
+ iter = 0
598
+ code
599
+ .gsub(CUR_ELEMENT, CUR_NODE)
600
+ .gsub(CUR_NODE) do
601
+ iter += 1
602
+ iter == 1 ? first_cur_node : cur_node
603
+ end
604
+ .gsub(SEQ_HEAD_GUARD, '')
605
+ end
606
+
471
607
  def self.tokens(pattern)
472
608
  pattern.scan(TOKEN).reject { |token| token =~ /\A#{SEPARATORS}\Z/ }
473
609
  end
@@ -515,13 +651,11 @@ module RuboCop
515
651
  end
516
652
 
517
653
  def node_search_all(method_name, compiler, called_from)
518
- yieldval = compiler.emit_capture_list
519
- yieldval = 'node' if yieldval.empty?
654
+ yield_code = compiler.emit_yield_capture('node')
520
655
  prelude = "return enum_for(:#{method_name}, node0" \
521
656
  "#{compiler.emit_trailing_params}) unless block_given?"
522
657
 
523
- node_search(method_name, compiler, "yield(#{yieldval})", prelude,
524
- called_from)
658
+ node_search(method_name, compiler, yield_code, prelude, called_from)
525
659
  end
526
660
 
527
661
  def node_search(method_name, compiler, on_match, prelude, called_from)