rubocop 1.70.0 → 1.71.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (190) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +17 -0
  4. data/lib/rubocop/cli/command/show_cops.rb +24 -2
  5. data/lib/rubocop/comment_config.rb +1 -1
  6. data/lib/rubocop/cop/autocorrect_logic.rb +1 -1
  7. data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -1
  8. data/lib/rubocop/cop/internal_affairs/location_expression.rb +2 -1
  9. data/lib/rubocop/cop/internal_affairs/node_first_or_last_argument.rb +3 -2
  10. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -1
  11. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_processor.rb +63 -0
  12. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_walker.rb +131 -0
  13. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +229 -0
  14. data/lib/rubocop/cop/internal_affairs/node_type_multiple_predicates.rb +126 -0
  15. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +90 -0
  16. data/lib/rubocop/cop/internal_affairs/redundant_source_range.rb +3 -1
  17. data/lib/rubocop/cop/internal_affairs/single_line_comparison.rb +5 -4
  18. data/lib/rubocop/cop/internal_affairs.rb +3 -0
  19. data/lib/rubocop/cop/layout/access_modifier_indentation.rb +1 -1
  20. data/lib/rubocop/cop/layout/argument_alignment.rb +1 -1
  21. data/lib/rubocop/cop/layout/block_alignment.rb +1 -1
  22. data/lib/rubocop/cop/layout/class_structure.rb +9 -9
  23. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  24. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +7 -5
  25. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +1 -1
  26. data/lib/rubocop/cop/layout/end_alignment.rb +1 -1
  27. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -1
  28. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +1 -1
  29. data/lib/rubocop/cop/layout/first_hash_element_line_break.rb +1 -1
  30. data/lib/rubocop/cop/layout/first_parameter_indentation.rb +2 -2
  31. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +1 -1
  32. data/lib/rubocop/cop/layout/multiline_hash_key_line_breaks.rb +1 -1
  33. data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +1 -0
  34. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -0
  35. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +2 -2
  36. data/lib/rubocop/cop/layout/redundant_line_break.rb +6 -5
  37. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -1
  38. data/lib/rubocop/cop/layout/single_line_block_chain.rb +1 -1
  39. data/lib/rubocop/cop/layout/space_after_colon.rb +2 -2
  40. data/lib/rubocop/cop/layout/space_after_comma.rb +1 -1
  41. data/lib/rubocop/cop/layout/space_after_semicolon.rb +1 -1
  42. data/lib/rubocop/cop/layout/space_around_keyword.rb +1 -0
  43. data/lib/rubocop/cop/layout/space_before_comma.rb +1 -1
  44. data/lib/rubocop/cop/layout/space_before_semicolon.rb +1 -1
  45. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +1 -1
  46. data/lib/rubocop/cop/lint/array_literal_in_regexp.rb +119 -0
  47. data/lib/rubocop/cop/lint/assignment_in_condition.rb +1 -3
  48. data/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb +1 -1
  49. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +3 -3
  50. data/lib/rubocop/cop/lint/constant_reassignment.rb +2 -6
  51. data/lib/rubocop/cop/lint/debugger.rb +1 -1
  52. data/lib/rubocop/cop/lint/duplicate_match_pattern.rb +1 -1
  53. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +1 -1
  54. data/lib/rubocop/cop/lint/duplicate_set_element.rb +1 -1
  55. data/lib/rubocop/cop/lint/float_comparison.rb +5 -2
  56. data/lib/rubocop/cop/lint/float_out_of_range.rb +1 -1
  57. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +1 -1
  58. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +1 -1
  59. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +13 -3
  60. data/lib/rubocop/cop/lint/missing_super.rb +2 -2
  61. data/lib/rubocop/cop/lint/mixed_case_range.rb +1 -1
  62. data/lib/rubocop/cop/lint/mixed_regexp_capture_types.rb +1 -1
  63. data/lib/rubocop/cop/lint/nested_method_definition.rb +3 -3
  64. data/lib/rubocop/cop/lint/next_without_accumulator.rb +1 -1
  65. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +1 -1
  66. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +13 -18
  67. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +2 -1
  68. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +1 -5
  69. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +1 -1
  70. data/lib/rubocop/cop/lint/redundant_string_coercion.rb +2 -2
  71. data/lib/rubocop/cop/lint/rescue_exception.rb +1 -1
  72. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +8 -1
  73. data/lib/rubocop/cop/lint/shared_mutable_default.rb +3 -3
  74. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  75. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
  76. data/lib/rubocop/cop/lint/unexpected_block_arity.rb +1 -1
  77. data/lib/rubocop/cop/lint/unreachable_code.rb +1 -1
  78. data/lib/rubocop/cop/lint/unreachable_loop.rb +1 -1
  79. data/lib/rubocop/cop/lint/useless_access_modifier.rb +4 -4
  80. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -1
  81. data/lib/rubocop/cop/lint/useless_method_definition.rb +1 -1
  82. data/lib/rubocop/cop/lint/useless_numeric_operation.rb +2 -1
  83. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +2 -2
  84. data/lib/rubocop/cop/lint/void.rb +1 -1
  85. data/lib/rubocop/cop/metrics/block_nesting.rb +1 -1
  86. data/lib/rubocop/cop/metrics/collection_literal_length.rb +7 -0
  87. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +1 -1
  88. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  89. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -1
  90. data/lib/rubocop/cop/mixin/check_line_breakable.rb +5 -5
  91. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  92. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
  93. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +4 -4
  94. data/lib/rubocop/cop/mixin/hash_subset.rb +188 -0
  95. data/lib/rubocop/cop/mixin/method_complexity.rb +1 -1
  96. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +22 -8
  97. data/lib/rubocop/cop/mixin/statement_modifier.rb +7 -2
  98. data/lib/rubocop/cop/mixin/string_help.rb +1 -1
  99. data/lib/rubocop/cop/mixin/trailing_comma.rb +3 -3
  100. data/lib/rubocop/cop/naming/block_forwarding.rb +18 -14
  101. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +3 -3
  102. data/lib/rubocop/cop/security/compound_hash.rb +1 -0
  103. data/lib/rubocop/cop/style/access_modifier_declarations.rb +2 -4
  104. data/lib/rubocop/cop/style/arguments_forwarding.rb +38 -19
  105. data/lib/rubocop/cop/style/array_first_last.rb +18 -2
  106. data/lib/rubocop/cop/style/block_delimiters.rb +7 -20
  107. data/lib/rubocop/cop/style/class_and_module_children.rb +1 -1
  108. data/lib/rubocop/cop/style/collection_methods.rb +1 -1
  109. data/lib/rubocop/cop/style/combinable_defined.rb +1 -1
  110. data/lib/rubocop/cop/style/combinable_loops.rb +2 -2
  111. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -1
  112. data/lib/rubocop/cop/style/conditional_assignment.rb +6 -4
  113. data/lib/rubocop/cop/style/documentation.rb +1 -1
  114. data/lib/rubocop/cop/style/double_negation.rb +3 -3
  115. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  116. data/lib/rubocop/cop/style/eval_with_location.rb +1 -1
  117. data/lib/rubocop/cop/style/exact_regexp_match.rb +1 -1
  118. data/lib/rubocop/cop/style/explicit_block_argument.rb +1 -1
  119. data/lib/rubocop/cop/style/fetch_env_var.rb +1 -1
  120. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +1 -1
  121. data/lib/rubocop/cop/style/hash_each_methods.rb +3 -6
  122. data/lib/rubocop/cop/style/hash_except.rb +20 -131
  123. data/lib/rubocop/cop/style/hash_slice.rb +80 -0
  124. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  125. data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
  126. data/lib/rubocop/cop/style/if_unless_modifier.rb +3 -3
  127. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +1 -1
  128. data/lib/rubocop/cop/style/if_with_semicolon.rb +2 -2
  129. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  130. data/lib/rubocop/cop/style/inverse_methods.rb +6 -6
  131. data/lib/rubocop/cop/style/it_assignment.rb +1 -1
  132. data/lib/rubocop/cop/style/keyword_parameters_order.rb +1 -1
  133. data/lib/rubocop/cop/style/map_into_array.rb +1 -1
  134. data/lib/rubocop/cop/style/map_to_hash.rb +1 -1
  135. data/lib/rubocop/cop/style/map_to_set.rb +3 -2
  136. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +10 -13
  137. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +2 -1
  138. data/lib/rubocop/cop/style/method_called_on_do_end_block.rb +2 -4
  139. data/lib/rubocop/cop/style/method_def_parentheses.rb +1 -1
  140. data/lib/rubocop/cop/style/multiline_block_chain.rb +1 -1
  141. data/lib/rubocop/cop/style/mutable_constant.rb +2 -2
  142. data/lib/rubocop/cop/style/negated_if_else_condition.rb +1 -1
  143. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +1 -1
  144. data/lib/rubocop/cop/style/open_struct_use.rb +5 -5
  145. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -5
  146. data/lib/rubocop/cop/style/parentheses_around_condition.rb +2 -2
  147. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +1 -1
  148. data/lib/rubocop/cop/style/proc.rb +1 -2
  149. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  150. data/lib/rubocop/cop/style/redundant_begin.rb +1 -1
  151. data/lib/rubocop/cop/style/redundant_condition.rb +2 -2
  152. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -10
  153. data/lib/rubocop/cop/style/redundant_each.rb +1 -1
  154. data/lib/rubocop/cop/style/redundant_exception.rb +2 -2
  155. data/lib/rubocop/cop/style/redundant_freeze.rb +2 -2
  156. data/lib/rubocop/cop/style/redundant_line_continuation.rb +27 -10
  157. data/lib/rubocop/cop/style/redundant_parentheses.rb +9 -6
  158. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +1 -1
  159. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
  160. data/lib/rubocop/cop/style/redundant_self_assignment.rb +12 -27
  161. data/lib/rubocop/cop/style/redundant_sort.rb +2 -2
  162. data/lib/rubocop/cop/style/redundant_string_escape.rb +2 -2
  163. data/lib/rubocop/cop/style/return_nil.rb +1 -1
  164. data/lib/rubocop/cop/style/safe_navigation.rb +1 -1
  165. data/lib/rubocop/cop/style/semicolon.rb +1 -1
  166. data/lib/rubocop/cop/style/single_line_block_params.rb +1 -1
  167. data/lib/rubocop/cop/style/single_line_methods.rb +1 -1
  168. data/lib/rubocop/cop/style/sole_nested_conditional.rb +2 -2
  169. data/lib/rubocop/cop/style/string_concatenation.rb +1 -1
  170. data/lib/rubocop/cop/style/string_literals.rb +1 -1
  171. data/lib/rubocop/cop/style/string_methods.rb +1 -1
  172. data/lib/rubocop/cop/style/super_arguments.rb +4 -4
  173. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  174. data/lib/rubocop/cop/style/top_level_method_definition.rb +1 -1
  175. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +4 -1
  176. data/lib/rubocop/cop/style/yoda_expression.rb +1 -1
  177. data/lib/rubocop/cop/util.rb +2 -2
  178. data/lib/rubocop/cop/variable_force/variable.rb +1 -1
  179. data/lib/rubocop/cop/variable_force/variable_table.rb +3 -3
  180. data/lib/rubocop/cops_documentation_generator.rb +13 -13
  181. data/lib/rubocop/directive_comment.rb +9 -8
  182. data/lib/rubocop/options.rb +2 -1
  183. data/lib/rubocop/result_cache.rb +13 -13
  184. data/lib/rubocop/rspec/expect_offense.rb +6 -2
  185. data/lib/rubocop/rspec/support.rb +1 -2
  186. data/lib/rubocop/target_finder.rb +1 -0
  187. data/lib/rubocop/version.rb +1 -1
  188. data/lib/rubocop.rb +3 -0
  189. metadata +15 -11
  190. data/lib/rubocop/rspec/host_environment_simulation_helper.rb +0 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55382302106a1f2c44166213aaa0c23bfccc3c0ebac5b1af95fa9073ddf59aa0
4
- data.tar.gz: bb1a6e491c7750b3f4aff90fbe2590f289d5f85ca9bbc2d71e01bd35d420b38c
3
+ metadata.gz: a6e4d1daf086c733bdbc26101d597970c6db01884a84b1688fcdcb1d35790a93
4
+ data.tar.gz: d21d0251334f815c178e4f3654e0884857a4bfb9f13395c96c27dc26d44b735a
5
5
  SHA512:
6
- metadata.gz: 6cff98184e513e6a57e5a82234f57a8130ef2e242eb3df675b677cfe5be8920d3cb732ce1f615385641731c328253fb65829922d371621d5e5148601f309ee51
7
- data.tar.gz: 3c3d8e5fc22db6f9b1a1ca6f9e31a8a29467c4df358e12da099fcc528884832d00bc18842d846ee76300791c1cb76122a22b6eb079b5a713c6f7b827fbea71dc
6
+ metadata.gz: befa7c474d65523d27c5a309f66d5c9b90d8656c2aff30f153ef764b6ce489ece67f6851e9be3484111cf4da59cea023eedfa502ccd9407814bf0ea6e21fa146
7
+ data.tar.gz: '088876817707413c841fb293784228563caa287ac7785a53d7b60322762c2bf971f36c1d8d5ede445d2fbaaaf48360eef9a2196be154696572af820a07576a2d'
data/README.md CHANGED
@@ -52,7 +52,7 @@ To prevent an unwanted RuboCop update you might want to use a conservative versi
52
52
  in your `Gemfile`:
53
53
 
54
54
  ```rb
55
- gem 'rubocop', '~> 1.70', require: false
55
+ gem 'rubocop', '~> 1.71', require: false
56
56
  ```
57
57
 
58
58
  See [our versioning policy](https://docs.rubocop.org/rubocop/versioning.html) for further details.
data/config/default.yml CHANGED
@@ -1616,6 +1616,12 @@ Lint/AmbiguousRegexpLiteral:
1616
1616
  VersionAdded: '0.17'
1617
1617
  VersionChanged: '0.83'
1618
1618
 
1619
+ Lint/ArrayLiteralInRegexp:
1620
+ Description: 'Checks for an array literal interpolated inside a regexp.'
1621
+ Enabled: pending
1622
+ VersionAdded: '1.71'
1623
+ SafeAutoCorrect: false
1624
+
1619
1625
  Lint/AssignmentInCondition:
1620
1626
  Description: "Don't use assignment in conditions."
1621
1627
  StyleGuide: '#safe-assignment-in-condition'
@@ -4063,6 +4069,9 @@ Style/FrozenStringLiteralComment:
4063
4069
  # exist in a file.
4064
4070
  - never
4065
4071
  SafeAutoCorrect: false
4072
+ Exclude:
4073
+ # Prevent the Ruby warning: `'frozen_string_literal' is ignored after any tokens` when using Active Admin.
4074
+ - '**/*.arb'
4066
4075
 
4067
4076
  Style/GlobalStdStream:
4068
4077
  Description: 'Enforces the use of `$stdout/$stderr/$stdin` instead of `STDOUT/STDERR/STDIN`.'
@@ -4141,6 +4150,14 @@ Style/HashLikeCase:
4141
4150
  # to trigger this cop
4142
4151
  MinBranchesCount: 3
4143
4152
 
4153
+ Style/HashSlice:
4154
+ Description: >-
4155
+ Checks for usages of `Hash#reject`, `Hash#select`, and `Hash#filter` methods
4156
+ that can be replaced with `Hash#slice` method.
4157
+ Enabled: pending
4158
+ Safe: false
4159
+ VersionAdded: '1.71'
4160
+
4144
4161
  Style/HashSyntax:
4145
4162
  Description: >-
4146
4163
  Prefer Ruby 1.9 hash syntax { a: 1, b: 2 } over 1.8 syntax
@@ -9,11 +9,31 @@ module RuboCop
9
9
  class ShowCops < Base
10
10
  self.command_name = :show_cops
11
11
 
12
+ ExactMatcher = Struct.new(:pattern) do
13
+ def match?(name)
14
+ name == pattern
15
+ end
16
+ end
17
+
18
+ WildcardMatcher = Struct.new(:pattern) do
19
+ def match?(name)
20
+ File.fnmatch(pattern, name, File::FNM_PATHNAME)
21
+ end
22
+ end
23
+
12
24
  def initialize(env)
13
25
  super
14
26
 
15
27
  # Load the configs so the require()s are done for custom cops
16
28
  @config = @config_store.for(Dir.pwd)
29
+
30
+ @cop_matchers = @options[:show_cops].map do |pattern|
31
+ if pattern.include?('*')
32
+ WildcardMatcher.new(pattern)
33
+ else
34
+ ExactMatcher.new(pattern)
35
+ end
36
+ end
17
37
  end
18
38
 
19
39
  def run
@@ -24,7 +44,7 @@ module RuboCop
24
44
 
25
45
  def print_available_cops
26
46
  registry = Cop::Registry.global
27
- show_all = @options[:show_cops].empty?
47
+ show_all = @cop_matchers.empty?
28
48
 
29
49
  puts "# Available cops (#{registry.length}) + config for #{Dir.pwd}: " if show_all
30
50
 
@@ -56,7 +76,9 @@ module RuboCop
56
76
 
57
77
  def selected_cops_of_department(cops, department)
58
78
  cops_of_department(cops, department).select do |cop|
59
- @options[:show_cops].include?(cop.cop_name)
79
+ @cop_matchers.any? do |matcher|
80
+ matcher.match?(cop.cop_name)
81
+ end
60
82
  end
61
83
  end
62
84
 
@@ -87,7 +87,7 @@ module RuboCop
87
87
  next unless directive.enabled?
88
88
  next if directive.all_cops?
89
89
 
90
- cops.merge(directive.cop_names)
90
+ cops.merge(directive.raw_cop_names)
91
91
  end
92
92
  cops
93
93
  end
@@ -102,7 +102,7 @@ module RuboCop
102
102
  end
103
103
 
104
104
  def string_continuation?(node)
105
- (node.str_type? || node.dstr_type? || node.xstr_type?) && node.source.match?(/\\\s*$/)
105
+ node.type?(:str, :dstr, :xstr) && node.source.match?(/\\\s*$/)
106
106
  end
107
107
 
108
108
  def multiline_string?(node)
@@ -66,7 +66,7 @@ module RuboCop
66
66
 
67
67
  def conditional_declaration?(nodes)
68
68
  parent = nodes[0].each_ancestor.find { |ancestor| !ancestor.begin_type? }
69
- return false unless parent&.if_type? || parent&.when_type?
69
+ return false unless parent&.type?(:if, :when)
70
70
 
71
71
  root_conditional_node = parent.if_type? ? parent : parent.parent
72
72
  nodes.all? { |node| within_conditional?(node, root_conditional_node) }
@@ -22,7 +22,7 @@ module RuboCop
22
22
 
23
23
  def on_send(node)
24
24
  return unless (parent = node.parent)
25
- return unless parent.send_type? && parent.method?(:expression)
25
+ return unless parent.call_type? && parent.method?(:expression)
26
26
  return unless parent.receiver.receiver
27
27
 
28
28
  offense = node.loc.selector.join(parent.source_range.end)
@@ -31,6 +31,7 @@ module RuboCop
31
31
  corrector.replace(offense, 'source_range')
32
32
  end
33
33
  end
34
+ alias on_csend on_send
34
35
  end
35
36
  end
36
37
  end
@@ -27,8 +27,8 @@ module RuboCop
27
27
  # @!method arguments_first_or_last?(node)
28
28
  def_node_matcher :arguments_first_or_last?, <<~PATTERN
29
29
  {
30
- (send (send !nil? :arguments) ${:first :last})
31
- (send (send !nil? :arguments) :[] (int ${0 -1}))
30
+ (call (call !nil? :arguments) ${:first :last})
31
+ (call (call !nil? :arguments) :[] (int ${0 -1}))
32
32
  }
33
33
  PATTERN
34
34
 
@@ -47,6 +47,7 @@ module RuboCop
47
47
  end
48
48
  end
49
49
  end
50
+ alias on_csend on_send
50
51
  end
51
52
  end
52
53
  end
@@ -62,7 +62,7 @@ module RuboCop
62
62
  private
63
63
 
64
64
  def valid_method_name?(node)
65
- node.first_argument.str_type? || node.first_argument.sym_type?
65
+ node.first_argument.type?(:str, :sym)
66
66
  end
67
67
 
68
68
  def method_directives(node)
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ class NodePatternGroups
7
+ # AST Processor for NodePattern ASTs, for use with `InternalAffairs/NodePatternGroups`.
8
+ #
9
+ # Looks for sequences and subsequences where the first item is a `node_type` node,
10
+ # and converts them to `node_sequence` nodes (not a true `Rubocop::AST::NodePattern`
11
+ # node type).
12
+ #
13
+ # The resulting AST will be walked by `InternalAffairs::NodePatternGroups::ASTWalker`
14
+ # in order to find node types in a `union` node that can be rewritten as a node group.
15
+ #
16
+ # NOTE: The `on_*` methods in this class relate not to the normal node types but
17
+ # rather to the Node Pattern node types. Not every node type is handled.
18
+ #
19
+ class ASTProcessor
20
+ include ::AST::Processor::Mixin
21
+
22
+ def handler_missing(node)
23
+ node.updated(nil, process_children(node))
24
+ end
25
+
26
+ # Look for `sequence` and `subsequence` nodes that contain a `node_type` node as
27
+ # their first child. These are rewritten as `node_sequence` nodes so that it is
28
+ # possible to compare nodes while looking for replacement candidates for node groups.
29
+ # This is necessary so that extended patterns can be matched and replaced.
30
+ # ie. `{(send _ :foo ...) (csend _ :foo ...)}` can become `(call _ :foo ...)`
31
+ def on_sequence(node)
32
+ first_child = node.child
33
+
34
+ if first_child.type == :node_type
35
+ children = [first_child.child, *process_children(node, 1..)]
36
+
37
+ # The `node_sequence` node contains the `node_type` symbol as its first child,
38
+ # followed by all the other nodes contained in the `sequence` node.
39
+ # The location is copied from the sequence, so that the entire sequence can
40
+ # eventually be corrected in the cop.
41
+ n(:node_sequence, children, location: node.location)
42
+ else
43
+ node.updated(nil, process_children(node))
44
+ end
45
+ end
46
+ alias on_subsequence on_sequence
47
+
48
+ private
49
+
50
+ def n(type, children = [], properties = {})
51
+ NodePattern::Node.new(type, children, properties)
52
+ end
53
+
54
+ def process_children(node, range = 0..-1)
55
+ node.children[range].map do |child|
56
+ child.is_a?(::AST::Node) ? process(child) : child
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ # rubocop:disable InternalAffairs/RedundantSourceRange - node here is a `NodePattern::Node`
7
+ class NodePatternGroups
8
+ # Walks an AST that has been processed by `InternalAffairs::NodePatternGroups::Processor`
9
+ # in order to find `node_type` and `node_sequence` nodes that can be replaced with a node
10
+ # group in `InternalAffairs/NodePatternGroups`.
11
+ #
12
+ # Calling `ASTWalker#walk` sets `node_groups` with an array of `NodeGroup` structs
13
+ # that contain metadata about nodes that can be replaced, including location data. That
14
+ # metadata is used by the cop to register offenses and perform corrections.
15
+ class ASTWalker
16
+ # Struct to contain data about parts of a node pattern that can be replaced
17
+ NodeGroup = Struct.new(
18
+ :name, # The name of the node group that will be inserted
19
+ :union, # The entire `union` node
20
+ :node_types, # An array of `node_type` nodes that will be removed
21
+ :sequence?, # The pattern matches a node type with given attributes
22
+ :start_index, # The index in the union of the first node type to remove
23
+ :offense_range, # The range to mark an offense on
24
+ :ranges, # Range of each element to remove, since they may not be adjacent
25
+ :pipe, # Is the union delimited by pipes?
26
+ :other_elements?, # Does the union have other elements other than those to remove?
27
+ keyword_init: true
28
+ )
29
+
30
+ def initialize
31
+ reset!
32
+ end
33
+
34
+ def reset!
35
+ @node_groups = []
36
+ end
37
+
38
+ attr_reader :node_groups
39
+
40
+ # Recursively walk the AST in a depth-first manner.
41
+ # Only `union` nodes are handled further.
42
+ def walk(node)
43
+ return if node.nil?
44
+
45
+ on_union(node) if node.type == :union
46
+
47
+ node.child_nodes.each do |child|
48
+ walk(child)
49
+ end
50
+ end
51
+
52
+ # Search `union` nodes for `node_type` and `node_sequence` nodes that can be
53
+ # collapsed into a node group.
54
+ # * `node_type` nodes are nodes with no further configuration (ie. `send`)
55
+ # * `node_sequence` nodes are nodes with further configuration (ie. `(send ...)`)
56
+ #
57
+ # Each group of types that can be collapsed will have a `NodeGroup` record added
58
+ # to `node_groups`, which is then used by the cop.
59
+ def on_union(node)
60
+ all_node_types = each_child_node(node, :node_type, :node_sequence).to_a
61
+
62
+ each_node_group(all_node_types) do |group_name, node_types|
63
+ next unless sequences_match?(node_types)
64
+
65
+ node_groups << node_group_data(
66
+ group_name, node, node_types,
67
+ all_node_types.index(node_types.first),
68
+ (node.children - node_types).any?
69
+ )
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def each_child_node(node, *types)
76
+ return to_enum(__method__, node, *types) unless block_given?
77
+
78
+ node.children.each do |child|
79
+ yield child if types.empty? || types.include?(child.type)
80
+ end
81
+
82
+ self
83
+ end
84
+
85
+ def each_node_group(types_to_check)
86
+ # Find all node groups where all of the members are present in the union
87
+ type_names = types_to_check.map(&:child)
88
+
89
+ NODE_GROUPS.select { |_, group| group & type_names == group }.each_key do |name|
90
+ nodes = get_relevant_nodes(types_to_check, name)
91
+
92
+ yield name, nodes
93
+ end
94
+ end
95
+
96
+ def get_relevant_nodes(node_types, group_name)
97
+ node_types.each_with_object([]) do |node_type, arr|
98
+ next unless NODE_GROUPS[group_name].include?(node_type.child)
99
+
100
+ arr << node_type
101
+ end
102
+ end
103
+
104
+ def node_group_data(name, union, node_types, start_index, other)
105
+ NodeGroup.new(
106
+ name: name,
107
+ union: union,
108
+ node_types: node_types,
109
+ sequence?: node_types.first.type == :node_sequence,
110
+ start_index: start_index,
111
+ pipe: union.source_range.source['|'],
112
+ other_elements?: other
113
+ )
114
+ end
115
+
116
+ def sequences_match?(types)
117
+ # Ensure all given types have the same type and the same sequence
118
+ # ie. `(send ...)` and `(csend ...) is a match
119
+ # `(send)` and `(csend ...)` is not a match
120
+ # `send` and `(csend ...)` is not a match
121
+
122
+ types.each_cons(2).all? do |left, right|
123
+ left.type == right.type && left.children[1..] == right.children[1..]
124
+ end
125
+ end
126
+ end
127
+ end
128
+ # rubocop:enable InternalAffairs/RedundantSourceRange
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,229 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ # Use node groups (`any_block`, `argument`, `boolean`, `call`, `numeric`, `range`)
7
+ # in node patterns instead of a union (`{ ... }`) of the member types of the group.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # def_node_matcher :my_matcher, <<~PATTERN
12
+ # {send csend}
13
+ # PATTERN
14
+ #
15
+ # # good
16
+ # def_node_matcher :my_matcher, <<~PATTERN
17
+ # call
18
+ # PATTERN
19
+ #
20
+ class NodePatternGroups < Base
21
+ require_relative 'node_pattern_groups/ast_processor'
22
+ require_relative 'node_pattern_groups/ast_walker'
23
+
24
+ include RangeHelp
25
+ extend AutoCorrector
26
+
27
+ MSG = 'Replace `%<names>s` in node pattern union with `%<replacement>s`.'
28
+ RESTRICT_ON_SEND = %i[def_node_matcher def_node_search].freeze
29
+ NODE_GROUPS = {
30
+ any_block: %i[block numblock],
31
+ argument: %i[arg optarg restarg kwarg kwoptarg kwrestarg blockarg forward_arg shadowarg],
32
+ boolean: %i[true false],
33
+ call: %i[send csend],
34
+ numeric: %i[int float rational complex],
35
+ range: %i[irange erange]
36
+ }.freeze
37
+
38
+ def on_new_investigation
39
+ @walker = ASTWalker.new
40
+ end
41
+
42
+ # When a Node Pattern matcher is defined, investigate the pattern string to search
43
+ # for node types that can be replaced with a node group (ie. `{send csend}` can be
44
+ # replaced with `call`).
45
+ #
46
+ # In order to deal with node patterns in an efficient and non-brittle way, we will
47
+ # parse the Node Pattern string given to this `send` node using
48
+ # `RuboCop::AST::NodePattern::Parser::WithMeta`. `WithMeta` is important! We need
49
+ # location information so that we can calculate the exact locations within the
50
+ # pattern to report and correct.
51
+ #
52
+ # The resulting AST is processed by `NodePatternGroups::ASTProccessor` which rewrites
53
+ # the AST slightly to handle node sequences (ie. `(send _ :foo ...)`). See the
54
+ # documentation of that class for more details.
55
+ #
56
+ # Then the processed AST is walked, and metadata is collected for node types that
57
+ # can be replaced with a node group.
58
+ #
59
+ # Finally, the metadata is used to register offenses and make corrections, using
60
+ # the location data captured earlier. The ranges captured while parsing the Node
61
+ # Pattern are offset using the string argument to this `send` node to ensure
62
+ # that offenses are registered at the correct location.
63
+ #
64
+ def on_send(node)
65
+ pattern_node = node.arguments[1]
66
+ return unless acceptable_heredoc?(pattern_node) || pattern_node.str_type?
67
+
68
+ process_pattern(pattern_node)
69
+ return if node_groups.nil?
70
+
71
+ apply_range_offsets(pattern_node)
72
+
73
+ node_groups.each_with_index do |group, index|
74
+ register_offense(group, index)
75
+ end
76
+ end
77
+
78
+ def after_send(_)
79
+ @walker.reset!
80
+ end
81
+
82
+ private
83
+
84
+ def node_groups
85
+ @walker.node_groups
86
+ end
87
+
88
+ # rubocop:disable InternalAffairs/RedundantSourceRange -- `node` here is a NodePatternNode
89
+ def register_offense(group, index)
90
+ replacement = replacement(group)
91
+ message = format(
92
+ MSG,
93
+ names: group.node_types.map { |node| node.source_range.source }.join('`, `'),
94
+ replacement: replacement
95
+ )
96
+
97
+ add_offense(group.offense_range, message: message) do |corrector|
98
+ # Only correct one group at a time to avoid clobbering.
99
+ # Other offenses will be corrected in the subsequent iterations of the
100
+ # correction loop.
101
+ next if index.positive?
102
+
103
+ if group.other_elements?
104
+ replace_types_with_node_group(corrector, group, replacement)
105
+ else
106
+ replace_union(corrector, group, replacement)
107
+ end
108
+ end
109
+ end
110
+
111
+ def replacement(group)
112
+ if group.sequence?
113
+ # If the original nodes were in a sequence (ie. wrapped in parentheses),
114
+ # use it to generate the resulting NodePattern syntax.
115
+ first_node_type = group.node_types.first
116
+ template = first_node_type.source_range.source
117
+ template.sub(first_node_type.child.to_s, group.name.to_s)
118
+ else
119
+ group.name
120
+ end
121
+ end
122
+ # rubocop:enable InternalAffairs/RedundantSourceRange
123
+
124
+ # When there are other elements in the union, remove the node types that can be replaced.
125
+ def replace_types_with_node_group(corrector, group, replacement)
126
+ ranges = group.ranges.map.with_index do |range, index|
127
+ # Collect whitespace and pipes preceding each element
128
+ range_for_full_union_element(range, index, group.pipe)
129
+ end
130
+
131
+ ranges.each { |range| corrector.remove(range) }
132
+
133
+ corrector.insert_before(ranges.first, replacement)
134
+ end
135
+
136
+ # If the union contains pipes, remove the pipe character as well.
137
+ # Unfortunately we don't get the location of the pipe in `loc` object, so we have
138
+ # to find it.
139
+ def range_for_full_union_element(range, index, pipe)
140
+ if index.positive?
141
+ range = if pipe
142
+ range_with_preceding_pipe(range)
143
+ else
144
+ range_with_surrounding_space(range: range, side: :left, newlines: true)
145
+ end
146
+ end
147
+
148
+ range
149
+ end
150
+
151
+ # Collect a preceding pipe and any whitespace left of the pipe
152
+ def range_with_preceding_pipe(range)
153
+ pos = range.begin_pos - 1
154
+
155
+ while pos
156
+ unless processed_source.buffer.source[pos].match?(/[\s|]/)
157
+ return range.with(begin_pos: pos + 1)
158
+ end
159
+
160
+ pos -= 1
161
+ end
162
+
163
+ range
164
+ end
165
+
166
+ # When there are no other elements, the entire union can be replaced
167
+ def replace_union(corrector, group, replacement)
168
+ corrector.replace(group.ranges.first, replacement)
169
+ end
170
+
171
+ # rubocop:disable Metrics/AbcSize
172
+ # Calculate the ranges for each node within the pattern string that will
173
+ # be replaced or removed. Takes the offset of the string node into account.
174
+ def apply_range_offsets(pattern_node)
175
+ range, offset = range_with_offset(pattern_node)
176
+
177
+ node_groups.each do |node_group|
178
+ node_group.ranges ||= []
179
+ node_group.offense_range = pattern_range(range, node_group.union, offset)
180
+
181
+ if node_group.other_elements?
182
+ node_group.node_types.each do |node_type|
183
+ node_group.ranges << pattern_range(range, node_type, offset)
184
+ end
185
+ else
186
+ node_group.ranges << node_group.offense_range
187
+ end
188
+ end
189
+ end
190
+ # rubocop:enable Metrics/AbcSize
191
+
192
+ def pattern_range(range, node, offset)
193
+ begin_pos = node.source_range.begin_pos
194
+ end_pos = node.source_range.end_pos
195
+ size = end_pos - begin_pos
196
+
197
+ range.adjust(begin_pos: begin_pos + offset).resize(size)
198
+ end
199
+
200
+ def range_with_offset(pattern_node)
201
+ if pattern_node.heredoc?
202
+ [pattern_node.loc.heredoc_body, 0]
203
+ else
204
+ [pattern_node.source_range, pattern_node.loc.begin.size]
205
+ end
206
+ end
207
+
208
+ # A heredoc can be a `dstr` without interpolation, but if there is interpolation
209
+ # there'll be a `begin` node, in which case, we cannot evaluate the pattern.
210
+ def acceptable_heredoc?(node)
211
+ node.type?(:str, :dstr) && node.heredoc? && node.each_child_node(:begin).none?
212
+ end
213
+
214
+ def process_pattern(pattern_node)
215
+ parser = RuboCop::AST::NodePattern::Parser::WithMeta.new
216
+ ast = parser.parse(pattern_value(pattern_node))
217
+ ast = ASTProcessor.new.process(ast)
218
+ @walker.walk(ast)
219
+ rescue RuboCop::AST::NodePattern::Invalid
220
+ # if the pattern is invalid, no offenses will be registered
221
+ end
222
+
223
+ def pattern_value(pattern_node)
224
+ pattern_node.heredoc? ? pattern_node.loc.heredoc_body.source : pattern_node.value
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end