rubocop 1.70.0 → 1.71.2

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 (195) 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/else_alignment.rb +1 -1
  25. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +7 -11
  26. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +1 -1
  27. data/lib/rubocop/cop/layout/end_alignment.rb +1 -1
  28. data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -1
  29. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +1 -1
  30. data/lib/rubocop/cop/layout/first_hash_element_line_break.rb +1 -1
  31. data/lib/rubocop/cop/layout/first_parameter_indentation.rb +2 -2
  32. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +1 -1
  33. data/lib/rubocop/cop/layout/multiline_hash_key_line_breaks.rb +1 -1
  34. data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +1 -0
  35. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -0
  36. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +2 -2
  37. data/lib/rubocop/cop/layout/redundant_line_break.rb +6 -5
  38. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -1
  39. data/lib/rubocop/cop/layout/single_line_block_chain.rb +1 -1
  40. data/lib/rubocop/cop/layout/space_after_colon.rb +2 -2
  41. data/lib/rubocop/cop/layout/space_after_comma.rb +1 -1
  42. data/lib/rubocop/cop/layout/space_after_semicolon.rb +1 -1
  43. data/lib/rubocop/cop/layout/space_around_keyword.rb +1 -0
  44. data/lib/rubocop/cop/layout/space_before_comma.rb +1 -1
  45. data/lib/rubocop/cop/layout/space_before_semicolon.rb +1 -1
  46. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +1 -1
  47. data/lib/rubocop/cop/lint/array_literal_in_regexp.rb +119 -0
  48. data/lib/rubocop/cop/lint/assignment_in_condition.rb +1 -3
  49. data/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb +1 -1
  50. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +3 -3
  51. data/lib/rubocop/cop/lint/constant_reassignment.rb +2 -6
  52. data/lib/rubocop/cop/lint/debugger.rb +1 -1
  53. data/lib/rubocop/cop/lint/duplicate_match_pattern.rb +1 -1
  54. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +1 -1
  55. data/lib/rubocop/cop/lint/duplicate_set_element.rb +1 -1
  56. data/lib/rubocop/cop/lint/empty_expression.rb +0 -2
  57. data/lib/rubocop/cop/lint/float_comparison.rb +5 -2
  58. data/lib/rubocop/cop/lint/float_out_of_range.rb +1 -1
  59. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +1 -1
  60. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +1 -1
  61. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +13 -3
  62. data/lib/rubocop/cop/lint/missing_super.rb +2 -2
  63. data/lib/rubocop/cop/lint/mixed_case_range.rb +1 -1
  64. data/lib/rubocop/cop/lint/mixed_regexp_capture_types.rb +1 -1
  65. data/lib/rubocop/cop/lint/nested_method_definition.rb +3 -3
  66. data/lib/rubocop/cop/lint/next_without_accumulator.rb +1 -1
  67. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +1 -1
  68. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +13 -18
  69. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +2 -1
  70. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +1 -5
  71. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +1 -1
  72. data/lib/rubocop/cop/lint/redundant_string_coercion.rb +2 -2
  73. data/lib/rubocop/cop/lint/rescue_exception.rb +1 -1
  74. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +8 -1
  75. data/lib/rubocop/cop/lint/shared_mutable_default.rb +3 -3
  76. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  77. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
  78. data/lib/rubocop/cop/lint/unexpected_block_arity.rb +1 -1
  79. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -1
  80. data/lib/rubocop/cop/lint/unreachable_code.rb +1 -1
  81. data/lib/rubocop/cop/lint/unreachable_loop.rb +1 -1
  82. data/lib/rubocop/cop/lint/useless_access_modifier.rb +4 -4
  83. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -1
  84. data/lib/rubocop/cop/lint/useless_method_definition.rb +1 -1
  85. data/lib/rubocop/cop/lint/useless_numeric_operation.rb +2 -1
  86. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +2 -2
  87. data/lib/rubocop/cop/lint/void.rb +2 -7
  88. data/lib/rubocop/cop/metrics/block_nesting.rb +1 -1
  89. data/lib/rubocop/cop/metrics/collection_literal_length.rb +7 -0
  90. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +1 -1
  91. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  92. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -1
  93. data/lib/rubocop/cop/mixin/check_line_breakable.rb +5 -5
  94. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  95. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
  96. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +4 -4
  97. data/lib/rubocop/cop/mixin/hash_subset.rb +188 -0
  98. data/lib/rubocop/cop/mixin/method_complexity.rb +1 -1
  99. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +22 -8
  100. data/lib/rubocop/cop/mixin/statement_modifier.rb +7 -2
  101. data/lib/rubocop/cop/mixin/string_help.rb +1 -1
  102. data/lib/rubocop/cop/mixin/trailing_comma.rb +3 -3
  103. data/lib/rubocop/cop/naming/block_forwarding.rb +18 -14
  104. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +3 -3
  105. data/lib/rubocop/cop/security/compound_hash.rb +1 -0
  106. data/lib/rubocop/cop/style/access_modifier_declarations.rb +2 -4
  107. data/lib/rubocop/cop/style/arguments_forwarding.rb +38 -19
  108. data/lib/rubocop/cop/style/array_first_last.rb +18 -2
  109. data/lib/rubocop/cop/style/block_delimiters.rb +7 -20
  110. data/lib/rubocop/cop/style/class_and_module_children.rb +1 -1
  111. data/lib/rubocop/cop/style/collection_methods.rb +1 -1
  112. data/lib/rubocop/cop/style/combinable_defined.rb +1 -1
  113. data/lib/rubocop/cop/style/combinable_loops.rb +2 -2
  114. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -1
  115. data/lib/rubocop/cop/style/conditional_assignment.rb +6 -4
  116. data/lib/rubocop/cop/style/documentation.rb +1 -1
  117. data/lib/rubocop/cop/style/double_negation.rb +3 -3
  118. data/lib/rubocop/cop/style/each_for_simple_loop.rb +1 -1
  119. data/lib/rubocop/cop/style/each_with_object.rb +2 -3
  120. data/lib/rubocop/cop/style/eval_with_location.rb +1 -1
  121. data/lib/rubocop/cop/style/exact_regexp_match.rb +1 -1
  122. data/lib/rubocop/cop/style/explicit_block_argument.rb +15 -2
  123. data/lib/rubocop/cop/style/fetch_env_var.rb +1 -1
  124. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +1 -1
  125. data/lib/rubocop/cop/style/hash_each_methods.rb +3 -6
  126. data/lib/rubocop/cop/style/hash_except.rb +20 -131
  127. data/lib/rubocop/cop/style/hash_slice.rb +80 -0
  128. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  129. data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
  130. data/lib/rubocop/cop/style/if_unless_modifier.rb +3 -3
  131. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +1 -1
  132. data/lib/rubocop/cop/style/if_with_semicolon.rb +2 -2
  133. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  134. data/lib/rubocop/cop/style/inverse_methods.rb +6 -6
  135. data/lib/rubocop/cop/style/it_assignment.rb +1 -1
  136. data/lib/rubocop/cop/style/keyword_parameters_order.rb +1 -1
  137. data/lib/rubocop/cop/style/map_into_array.rb +1 -1
  138. data/lib/rubocop/cop/style/map_to_hash.rb +1 -1
  139. data/lib/rubocop/cop/style/map_to_set.rb +3 -2
  140. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +10 -13
  141. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +2 -1
  142. data/lib/rubocop/cop/style/method_called_on_do_end_block.rb +2 -4
  143. data/lib/rubocop/cop/style/method_def_parentheses.rb +1 -1
  144. data/lib/rubocop/cop/style/multiline_block_chain.rb +1 -1
  145. data/lib/rubocop/cop/style/mutable_constant.rb +2 -2
  146. data/lib/rubocop/cop/style/negated_if_else_condition.rb +1 -1
  147. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +1 -1
  148. data/lib/rubocop/cop/style/open_struct_use.rb +5 -5
  149. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -5
  150. data/lib/rubocop/cop/style/parentheses_around_condition.rb +2 -2
  151. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +1 -1
  152. data/lib/rubocop/cop/style/proc.rb +1 -2
  153. data/lib/rubocop/cop/style/raise_args.rb +1 -1
  154. data/lib/rubocop/cop/style/redundant_begin.rb +1 -1
  155. data/lib/rubocop/cop/style/redundant_condition.rb +2 -2
  156. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -10
  157. data/lib/rubocop/cop/style/redundant_each.rb +1 -1
  158. data/lib/rubocop/cop/style/redundant_exception.rb +2 -2
  159. data/lib/rubocop/cop/style/redundant_freeze.rb +2 -2
  160. data/lib/rubocop/cop/style/redundant_line_continuation.rb +27 -10
  161. data/lib/rubocop/cop/style/redundant_parentheses.rb +9 -6
  162. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +1 -1
  163. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
  164. data/lib/rubocop/cop/style/redundant_self_assignment.rb +12 -27
  165. data/lib/rubocop/cop/style/redundant_sort.rb +2 -2
  166. data/lib/rubocop/cop/style/redundant_string_escape.rb +2 -2
  167. data/lib/rubocop/cop/style/return_nil.rb +1 -1
  168. data/lib/rubocop/cop/style/safe_navigation.rb +1 -1
  169. data/lib/rubocop/cop/style/semicolon.rb +1 -1
  170. data/lib/rubocop/cop/style/single_line_block_params.rb +1 -1
  171. data/lib/rubocop/cop/style/single_line_methods.rb +1 -1
  172. data/lib/rubocop/cop/style/sole_nested_conditional.rb +2 -2
  173. data/lib/rubocop/cop/style/string_concatenation.rb +1 -1
  174. data/lib/rubocop/cop/style/string_literals.rb +1 -1
  175. data/lib/rubocop/cop/style/string_methods.rb +1 -1
  176. data/lib/rubocop/cop/style/super_arguments.rb +4 -4
  177. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  178. data/lib/rubocop/cop/style/top_level_method_definition.rb +1 -1
  179. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +4 -1
  180. data/lib/rubocop/cop/style/while_until_modifier.rb +0 -1
  181. data/lib/rubocop/cop/style/yoda_expression.rb +1 -1
  182. data/lib/rubocop/cop/util.rb +2 -2
  183. data/lib/rubocop/cop/variable_force/variable.rb +14 -2
  184. data/lib/rubocop/cop/variable_force/variable_table.rb +3 -3
  185. data/lib/rubocop/cops_documentation_generator.rb +13 -13
  186. data/lib/rubocop/directive_comment.rb +9 -8
  187. data/lib/rubocop/options.rb +2 -1
  188. data/lib/rubocop/result_cache.rb +13 -13
  189. data/lib/rubocop/rspec/expect_offense.rb +6 -2
  190. data/lib/rubocop/rspec/support.rb +1 -2
  191. data/lib/rubocop/target_finder.rb +1 -0
  192. data/lib/rubocop/version.rb +1 -1
  193. data/lib/rubocop.rb +3 -0
  194. metadata +15 -11
  195. 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: 8d7c6408a777042eced58bec2bc889312b24d8701f5db99da3b471088f589d6f
4
+ data.tar.gz: 27423e682d55f04840a3bf3d5e0301556e4a1c419857e3422ced38ec423c0003
5
5
  SHA512:
6
- metadata.gz: 6cff98184e513e6a57e5a82234f57a8130ef2e242eb3df675b677cfe5be8920d3cb732ce1f615385641731c328253fb65829922d371621d5e5148601f309ee51
7
- data.tar.gz: 3c3d8e5fc22db6f9b1a1ca6f9e31a8a29467c4df358e12da099fcc528884832d00bc18842d846ee76300791c1cb76122a22b6eb079b5a713c6f7b827fbea71dc
6
+ metadata.gz: 8729a8ea3adae11fcb2501bfea338821a8f9894209ea6fb482b62ebfae87019e07ded7bf02d3371c18726b28c2491c79952e7b86c193388a045f886a833b427c
7
+ data.tar.gz: 44251afbbbc2aa7c121033ea05f25e1b3447afba7487d04a81f9463347ba28bcea05bdc2e434248f15cd235d9e452cb3f9f378f94adb5507eeb6d5ce58ba4119
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