rubocop 0.58.2 → 0.59.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 (211) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +22 -7
  4. data/config/disabled.yml +33 -4
  5. data/config/enabled.yml +4 -11
  6. data/lib/rubocop.rb +5 -0
  7. data/lib/rubocop/ast/builder.rb +1 -0
  8. data/lib/rubocop/ast/node.rb +11 -33
  9. data/lib/rubocop/ast/node/block_node.rb +8 -1
  10. data/lib/rubocop/ast/node/defined_node.rb +13 -0
  11. data/lib/rubocop/ast/node/mixin/method_dispatch_node.rb +16 -5
  12. data/lib/rubocop/ast/node/mixin/method_identifier_predicates.rb +21 -0
  13. data/lib/rubocop/ast/node/send_node.rb +3 -12
  14. data/lib/rubocop/ast/traversal.rb +10 -0
  15. data/lib/rubocop/cli.rb +4 -1
  16. data/lib/rubocop/config.rb +21 -5
  17. data/lib/rubocop/config_loader.rb +2 -0
  18. data/lib/rubocop/config_loader_resolver.rb +3 -1
  19. data/lib/rubocop/cop/autocorrect_logic.rb +1 -0
  20. data/lib/rubocop/cop/bundler/gem_comment.rb +64 -0
  21. data/lib/rubocop/cop/bundler/ordered_gems.rb +2 -0
  22. data/lib/rubocop/cop/commissioner.rb +2 -0
  23. data/lib/rubocop/cop/cop.rb +3 -0
  24. data/lib/rubocop/cop/corrector.rb +2 -0
  25. data/lib/rubocop/cop/correctors/alignment_corrector.rb +1 -0
  26. data/lib/rubocop/cop/correctors/line_break_corrector.rb +2 -0
  27. data/lib/rubocop/cop/correctors/space_corrector.rb +2 -0
  28. data/lib/rubocop/cop/force.rb +1 -0
  29. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +1 -0
  30. data/lib/rubocop/cop/generator.rb +1 -0
  31. data/lib/rubocop/cop/layout/access_modifier_indentation.rb +1 -0
  32. data/lib/rubocop/cop/layout/class_structure.rb +4 -0
  33. data/lib/rubocop/cop/layout/closing_heredoc_indentation.rb +5 -4
  34. data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +35 -0
  35. data/lib/rubocop/cop/layout/else_alignment.rb +1 -0
  36. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +2 -0
  37. data/lib/rubocop/cop/layout/empty_lines_around_arguments.rb +1 -0
  38. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +5 -2
  39. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +1 -0
  40. data/lib/rubocop/cop/layout/end_of_line.rb +1 -0
  41. data/lib/rubocop/cop/layout/extra_spacing.rb +1 -0
  42. data/lib/rubocop/cop/layout/indent_array.rb +1 -0
  43. data/lib/rubocop/cop/layout/indent_heredoc.rb +3 -0
  44. data/lib/rubocop/cop/layout/indentation_width.rb +2 -0
  45. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +1 -0
  46. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +2 -1
  47. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +34 -11
  48. data/lib/rubocop/cop/layout/space_after_method_name.rb +1 -0
  49. data/lib/rubocop/cop/layout/space_after_not.rb +1 -1
  50. data/lib/rubocop/cop/layout/space_around_keyword.rb +3 -1
  51. data/lib/rubocop/cop/layout/space_around_operators.rb +1 -0
  52. data/lib/rubocop/cop/layout/space_before_comment.rb +1 -0
  53. data/lib/rubocop/cop/layout/space_in_lambda_literal.rb +16 -8
  54. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +2 -0
  55. data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +1 -1
  56. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +2 -0
  57. data/lib/rubocop/cop/layout/tab.rb +1 -0
  58. data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -0
  59. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +1 -0
  60. data/lib/rubocop/cop/lint/duplicate_methods.rb +9 -1
  61. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +1 -0
  62. data/lib/rubocop/cop/lint/ineffective_access_modifier.rb +1 -0
  63. data/lib/rubocop/cop/lint/interpolation_check.rb +2 -0
  64. data/lib/rubocop/cop/lint/literal_as_condition.rb +3 -6
  65. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +1 -0
  66. data/lib/rubocop/cop/lint/nested_method_definition.rb +1 -0
  67. data/lib/rubocop/cop/lint/rescue_exception.rb +1 -0
  68. data/lib/rubocop/cop/lint/rescue_type.rb +1 -0
  69. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +2 -2
  70. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +2 -0
  71. data/lib/rubocop/cop/lint/script_permission.rb +1 -0
  72. data/lib/rubocop/cop/lint/shadowed_argument.rb +3 -0
  73. data/lib/rubocop/cop/lint/shadowed_exception.rb +2 -0
  74. data/lib/rubocop/cop/lint/unneeded_cop_disable_directive.rb +1 -0
  75. data/lib/rubocop/cop/lint/unneeded_cop_enable_directive.rb +1 -0
  76. data/lib/rubocop/cop/lint/unneeded_require_statement.rb +1 -0
  77. data/lib/rubocop/cop/lint/unneeded_splat_expansion.rb +1 -1
  78. data/lib/rubocop/cop/lint/unreachable_code.rb +2 -0
  79. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -0
  80. data/lib/rubocop/cop/lint/useless_setter_call.rb +3 -0
  81. data/lib/rubocop/cop/lint/void.rb +1 -0
  82. data/lib/rubocop/cop/message_annotator.rb +1 -0
  83. data/lib/rubocop/cop/metrics/block_length.rb +1 -0
  84. data/lib/rubocop/cop/metrics/block_nesting.rb +1 -0
  85. data/lib/rubocop/cop/metrics/line_length.rb +6 -1
  86. data/lib/rubocop/cop/metrics/method_length.rb +1 -0
  87. data/lib/rubocop/cop/mixin/annotation_comment.rb +1 -0
  88. data/lib/rubocop/cop/mixin/classish_length.rb +1 -0
  89. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +1 -0
  90. data/lib/rubocop/cop/mixin/configurable_formatting.rb +1 -0
  91. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -6
  92. data/lib/rubocop/cop/mixin/empty_parameter.rb +1 -0
  93. data/lib/rubocop/cop/mixin/ignored_methods.rb +19 -0
  94. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +25 -1
  95. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +5 -3
  96. data/lib/rubocop/cop/mixin/percent_literal.rb +2 -0
  97. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +2 -0
  98. data/lib/rubocop/cop/mixin/safe_assignment.rb +2 -1
  99. data/lib/rubocop/cop/mixin/statement_modifier.rb +6 -1
  100. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -0
  101. data/lib/rubocop/cop/mixin/surrounding_space.rb +4 -0
  102. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -0
  103. data/lib/rubocop/cop/mixin/uncommunicative_name.rb +2 -0
  104. data/lib/rubocop/cop/naming/ascii_identifiers.rb +1 -0
  105. data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +1 -0
  106. data/lib/rubocop/cop/naming/file_name.rb +4 -1
  107. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +1 -0
  108. data/lib/rubocop/cop/naming/predicate_name.rb +1 -0
  109. data/lib/rubocop/cop/naming/uncommunicative_block_param_name.rb +1 -0
  110. data/lib/rubocop/cop/naming/uncommunicative_method_param_name.rb +1 -0
  111. data/lib/rubocop/cop/naming/variable_name.rb +1 -0
  112. data/lib/rubocop/cop/performance/case_when_splat.rb +11 -7
  113. data/lib/rubocop/cop/performance/casecmp.rb +33 -42
  114. data/lib/rubocop/cop/performance/chain_array_allocation.rb +77 -0
  115. data/lib/rubocop/cop/performance/compare_with_block.rb +3 -0
  116. data/lib/rubocop/cop/performance/regexp_match.rb +1 -0
  117. data/lib/rubocop/cop/performance/sample.rb +2 -0
  118. data/lib/rubocop/cop/performance/size.rb +8 -2
  119. data/lib/rubocop/cop/performance/string_replacement.rb +1 -0
  120. data/lib/rubocop/cop/rails/active_support_aliases.rb +1 -0
  121. data/lib/rubocop/cop/rails/bulk_change_table.rb +9 -2
  122. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +1 -0
  123. data/lib/rubocop/cop/rails/delegate.rb +7 -2
  124. data/lib/rubocop/cop/rails/dynamic_find_by.rb +1 -0
  125. data/lib/rubocop/cop/rails/find_each.rb +7 -2
  126. data/lib/rubocop/cop/rails/http_positional_arguments.rb +1 -1
  127. data/lib/rubocop/cop/rails/http_status.rb +2 -0
  128. data/lib/rubocop/cop/rails/inverse_of.rb +4 -0
  129. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +1 -0
  130. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +1 -0
  131. data/lib/rubocop/cop/rails/reversible_migration.rb +1 -0
  132. data/lib/rubocop/cop/rails/save_bang.rb +189 -38
  133. data/lib/rubocop/cop/rails/time_zone.rb +1 -0
  134. data/lib/rubocop/cop/security/eval.rb +1 -0
  135. data/lib/rubocop/cop/security/json_load.rb +2 -2
  136. data/lib/rubocop/cop/security/open.rb +6 -3
  137. data/lib/rubocop/cop/severity.rb +1 -0
  138. data/lib/rubocop/cop/style/and_or.rb +3 -3
  139. data/lib/rubocop/cop/style/ascii_comments.rb +1 -0
  140. data/lib/rubocop/cop/style/block_delimiters.rb +2 -4
  141. data/lib/rubocop/cop/style/braces_around_hash_parameters.rb +2 -3
  142. data/lib/rubocop/cop/style/class_and_module_children.rb +3 -0
  143. data/lib/rubocop/cop/style/class_vars.rb +1 -1
  144. data/lib/rubocop/cop/style/colon_method_definition.rb +1 -0
  145. data/lib/rubocop/cop/style/commented_keyword.rb +2 -0
  146. data/lib/rubocop/cop/style/conditional_assignment.rb +2 -0
  147. data/lib/rubocop/cop/style/copyright.rb +7 -2
  148. data/lib/rubocop/cop/style/date_time.rb +40 -7
  149. data/lib/rubocop/cop/style/double_negation.rb +1 -1
  150. data/lib/rubocop/cop/style/empty_case_condition.rb +8 -0
  151. data/lib/rubocop/cop/style/empty_else.rb +2 -0
  152. data/lib/rubocop/cop/style/empty_lambda_parameter.rb +1 -0
  153. data/lib/rubocop/cop/style/eval_with_location.rb +2 -0
  154. data/lib/rubocop/cop/style/for.rb +56 -10
  155. data/lib/rubocop/cop/style/format_string_token.rb +1 -1
  156. data/lib/rubocop/cop/style/if_with_semicolon.rb +1 -0
  157. data/lib/rubocop/cop/style/inverse_methods.rb +1 -0
  158. data/lib/rubocop/cop/style/lambda.rb +1 -0
  159. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +3 -5
  160. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +3 -5
  161. data/lib/rubocop/cop/style/method_def_parentheses.rb +2 -2
  162. data/lib/rubocop/cop/style/missing_else.rb +1 -0
  163. data/lib/rubocop/cop/style/multiline_memoization.rb +1 -0
  164. data/lib/rubocop/cop/style/multiline_method_signature.rb +65 -0
  165. data/lib/rubocop/cop/style/multiple_comparison.rb +1 -0
  166. data/lib/rubocop/cop/style/nil_comparison.rb +45 -5
  167. data/lib/rubocop/cop/style/not.rb +1 -1
  168. data/lib/rubocop/cop/style/numeric_predicate.rb +5 -0
  169. data/lib/rubocop/cop/style/one_line_conditional.rb +1 -1
  170. data/lib/rubocop/cop/style/or_assignment.rb +2 -0
  171. data/lib/rubocop/cop/style/percent_q_literals.rb +1 -1
  172. data/lib/rubocop/cop/style/random_with_offset.rb +1 -0
  173. data/lib/rubocop/cop/style/redundant_begin.rb +13 -0
  174. data/lib/rubocop/cop/style/redundant_conditional.rb +1 -0
  175. data/lib/rubocop/cop/style/redundant_parentheses.rb +6 -1
  176. data/lib/rubocop/cop/style/redundant_return.rb +1 -0
  177. data/lib/rubocop/cop/style/rescue_modifier.rb +1 -0
  178. data/lib/rubocop/cop/style/rescue_standard_error.rb +1 -0
  179. data/lib/rubocop/cop/style/safe_navigation.rb +4 -0
  180. data/lib/rubocop/cop/style/semicolon.rb +4 -0
  181. data/lib/rubocop/cop/style/signal_exception.rb +1 -0
  182. data/lib/rubocop/cop/style/string_hash_keys.rb +1 -0
  183. data/lib/rubocop/cop/style/symbol_proc.rb +1 -8
  184. data/lib/rubocop/cop/style/trailing_comma_in_array_literal.rb +1 -0
  185. data/lib/rubocop/cop/style/trailing_method_end_statement.rb +1 -0
  186. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +1 -0
  187. data/lib/rubocop/cop/style/unneeded_condition.rb +13 -2
  188. data/lib/rubocop/cop/style/unneeded_percent_q.rb +2 -0
  189. data/lib/rubocop/cop/style/word_array.rb +13 -1
  190. data/lib/rubocop/cop/team.rb +1 -0
  191. data/lib/rubocop/cop/variable_force.rb +5 -0
  192. data/lib/rubocop/cop/variable_force/assignment.rb +4 -0
  193. data/lib/rubocop/cop/variable_force/branch.rb +4 -0
  194. data/lib/rubocop/cop/variable_force/branchable.rb +2 -0
  195. data/lib/rubocop/cop/variable_force/scope.rb +6 -0
  196. data/lib/rubocop/cop/variable_force/variable_table.rb +1 -0
  197. data/lib/rubocop/file_finder.rb +2 -0
  198. data/lib/rubocop/formatter/disabled_config_formatter.rb +4 -4
  199. data/lib/rubocop/formatter/file_list_formatter.rb +1 -0
  200. data/lib/rubocop/formatter/simple_text_formatter.rb +1 -0
  201. data/lib/rubocop/options.rb +16 -0
  202. data/lib/rubocop/path_util.rb +16 -1
  203. data/lib/rubocop/processed_source.rb +4 -0
  204. data/lib/rubocop/remote_config.rb +6 -1
  205. data/lib/rubocop/result_cache.rb +1 -0
  206. data/lib/rubocop/rspec/cop_helper.rb +3 -5
  207. data/lib/rubocop/rspec/shared_examples.rb +1 -9
  208. data/lib/rubocop/runner.rb +4 -0
  209. data/lib/rubocop/target_finder.rb +2 -0
  210. data/lib/rubocop/version.rb +1 -1
  211. metadata +7 -2
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop is used to identify usages of
7
+ # @example
8
+ # # bad
9
+ # array = ["a", "b", "c"]
10
+ # array.compact.flatten.map { |x| x.downcase }
11
+ #
12
+ # Each of these methods (`compact`, `flatten`, `map`) will generate a
13
+ # new intermediate array that is promptly thrown away. Instead it is
14
+ # faster to mutate when we know it's safe.
15
+ #
16
+ # @example
17
+ # # good.
18
+ # array = ["a", "b", "c"]
19
+ # array.compact!
20
+ # array.flatten!
21
+ # array.map! { |x| x.downcase }
22
+ # array
23
+ class ChainArrayAllocation < Cop
24
+ include RangeHelp
25
+
26
+ # These methods return a new array but only sometimes. They must be
27
+ # called with an argument. For example:
28
+ #
29
+ # [1,2].first # => 1
30
+ # [1,2].first(1) # => [1]
31
+ #
32
+ RETURN_NEW_ARRAY_WHEN_ARGS = ':first :last :pop :sample :shift '.freeze
33
+
34
+ # These methods return a new array only when called without a block.
35
+ RETURNS_NEW_ARRAY_WHEN_NO_BLOCK = ':zip :product '.freeze
36
+
37
+ # These methods ALWAYS return a new array
38
+ # after they're called it's safe to mutate the the resulting array
39
+ ALWAYS_RETURNS_NEW_ARRAY = ':* :+ :- :collect :compact :drop '\
40
+ ':drop_while :flatten :map :reject ' \
41
+ ':reverse :rotate :select :shuffle :sort ' \
42
+ ':take :take_while :transpose :uniq ' \
43
+ ':values_at :| '.freeze
44
+
45
+ # These methods have a mutation alternative. For example :collect
46
+ # can be called as :collect!
47
+ HAS_MUTATION_ALTERNATIVE = ':collect :compact :flatten :map :reject '\
48
+ ':reverse :rotate :select :shuffle :sort '\
49
+ ':uniq '.freeze
50
+ MSG = 'Use `%<method>s...%<second_method>s!` instead of `%<method>s' \
51
+ '...%<second_method>s`.'.freeze
52
+
53
+ def_node_matcher :flat_map_candidate?, <<-PATTERN
54
+ {
55
+ (send (send _ ${#{RETURN_NEW_ARRAY_WHEN_ARGS}} {int lvar ivar cvar gvar}) ${#{HAS_MUTATION_ALTERNATIVE}} $...)
56
+ (send (block (send _ ${#{ALWAYS_RETURNS_NEW_ARRAY} }) ...) ${#{HAS_MUTATION_ALTERNATIVE}} $...)
57
+ (send (send _ ${#{ALWAYS_RETURNS_NEW_ARRAY + RETURNS_NEW_ARRAY_WHEN_NO_BLOCK}} ...) ${#{HAS_MUTATION_ALTERNATIVE}} $...)
58
+ }
59
+ PATTERN
60
+
61
+ def on_send(node)
62
+ flat_map_candidate?(node) do |fm, sm, _|
63
+ range = range_between(
64
+ node.loc.dot.begin_pos,
65
+ node.source_range.end_pos
66
+ )
67
+ add_offense(
68
+ node,
69
+ location: range,
70
+ message: format(MSG, method: fm, second_method: sm)
71
+ )
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -48,6 +48,7 @@ module RuboCop
48
48
  compare?(node) do |send, var_a, var_b, body|
49
49
  replaceable_body?(body, var_a, var_b) do |method, args_a, args_b|
50
50
  return unless slow_compare?(method, args_a, args_b)
51
+
51
52
  range = compare_range(send, node)
52
53
 
53
54
  add_offense(
@@ -78,8 +79,10 @@ module RuboCop
78
79
 
79
80
  def slow_compare?(method, args_a, args_b)
80
81
  return false unless args_a == args_b
82
+
81
83
  if method == :[]
82
84
  return false unless args_a.size == 1
85
+
83
86
  key = args_a.first
84
87
  return false unless %i[sym str int].include?(key.type)
85
88
  else
@@ -101,6 +101,7 @@ module RuboCop
101
101
 
102
102
  def match_with_lvasgn?(node)
103
103
  return false unless node.match_with_lvasgn_type?
104
+
104
105
  regexp, _rhs = *node
105
106
  regexp.to_regexp.named_captures.empty?
106
107
  end
@@ -88,12 +88,14 @@ module RuboCop
88
88
 
89
89
  def sample_size_for_two_args(first, second)
90
90
  return :unknown unless first.int_type? && first.to_a.first.zero?
91
+
91
92
  second.int_type? ? second.to_a.first : :unknown
92
93
  end
93
94
 
94
95
  def range_size(range_node)
95
96
  vals = range_node.to_a
96
97
  return :unknown unless vals.all?(&:int_type?)
98
+
97
99
  low, high = vals.map { |val| val.children[0] }
98
100
  return :unknown unless low.zero? && high >= 0
99
101
 
@@ -55,15 +55,21 @@ module RuboCop
55
55
  end
56
56
 
57
57
  def array?(node)
58
+ return true if node.array_type?
59
+ return false unless node.send_type?
60
+
58
61
  _, constant = *node.receiver
59
62
 
60
- node.array_type? || constant == :Array || node.method_name == :to_a
63
+ constant == :Array || node.method_name == :to_a
61
64
  end
62
65
 
63
66
  def hash?(node)
67
+ return true if node.hash_type?
68
+ return false unless node.send_type?
69
+
64
70
  _, constant = *node.receiver
65
71
 
66
- node.hash_type? || constant == :Hash || node.method_name == :to_h
72
+ constant == :Hash || node.method_name == :to_h
67
73
  end
68
74
  end
69
75
  end
@@ -87,6 +87,7 @@ module RuboCop
87
87
  unless first_param.str_type?
88
88
  return true if options
89
89
  return true unless first_source =~ DETERMINISTIC_REGEX
90
+
90
91
  # This must be done after checking DETERMINISTIC_REGEX
91
92
  # Otherwise things like \s will trip us up
92
93
  first_source = interpret_string_escapes(first_source)
@@ -46,6 +46,7 @@ module RuboCop
46
46
 
47
47
  def autocorrect(node)
48
48
  return false if append(node)
49
+
49
50
  lambda do |corrector|
50
51
  method_name = node.loc.selector.source
51
52
  replacement = ALIASES[method_name.to_sym][:original]
@@ -6,8 +6,8 @@ module RuboCop
6
6
  # This Cop checks whether alter queries are combinable.
7
7
  # If combinable queries are detected, it suggests to you
8
8
  # to use `change_table` with `bulk: true` instead.
9
- # When use this method, make combinable alter queries
10
- # a bulk alter query.
9
+ # This option causes the migration to generate a single
10
+ # ALTER TABLE statement combining multiple column alterations.
11
11
  #
12
12
  # The `bulk` option is only supported on the MySQL and
13
13
  # the PostgreSQL (5.2 later) adapter; thus it will
@@ -155,6 +155,7 @@ module RuboCop
155
155
  return unless node.command?(:change_table)
156
156
  return if include_bulk_options?(node)
157
157
  return unless node.block_node
158
+
158
159
  send_nodes = node.block_node.body.each_child_node(:send).to_a
159
160
 
160
161
  transformations = send_nodes.select do |send_node|
@@ -171,6 +172,7 @@ module RuboCop
171
172
  # arguments: [{(sym :table)(str "table")} (hash (pair (sym :bulk) _))]
172
173
  options = node.arguments[1]
173
174
  return false unless options
175
+
174
176
  options.hash_type? &&
175
177
  options.keys.any? { |key| key.sym_type? && key.value == :bulk }
176
178
  end
@@ -181,6 +183,7 @@ module RuboCop
181
183
 
182
184
  def database_from_yaml
183
185
  return nil unless database_yaml
186
+
184
187
  case database_yaml['adapter']
185
188
  when 'mysql2'
186
189
  MYSQL
@@ -191,10 +194,13 @@ module RuboCop
191
194
 
192
195
  def database_yaml
193
196
  return nil unless File.exist?('config/database.yml')
197
+
194
198
  yaml = YAML.load_file('config/database.yml')
195
199
  return nil unless yaml.is_a? Hash
200
+
196
201
  config = yaml['development']
197
202
  return nil unless config.is_a?(Hash)
203
+
198
204
  config
199
205
  rescue Psych::SyntaxError
200
206
  nil
@@ -236,6 +242,7 @@ module RuboCop
236
242
  # arguments: [{(sym :table)(str "table")} ...]
237
243
  table_node = node.arguments[0]
238
244
  return unless table_node.is_a? RuboCop::AST::BasicLiteralNode
245
+
239
246
  message = format(MSG_FOR_ALTER_METHODS, table: table_node.value)
240
247
  add_offense(node, message: message)
241
248
  end
@@ -66,6 +66,7 @@ module RuboCop
66
66
 
67
67
  def on_send(node)
68
68
  return unless node.command?(:create_table)
69
+
69
70
  parent = node.parent
70
71
 
71
72
  if create_table_with_block?(parent)
@@ -94,8 +94,13 @@ module RuboCop
94
94
  def arguments_match?(arg_array, body)
95
95
  argument_array = body.arguments
96
96
 
97
- arg_array == argument_array ||
98
- arg_array.map(&:children) == argument_array.map(&:children)
97
+ return false if arg_array.size != argument_array.size
98
+
99
+ arg_array.zip(argument_array).all? do |arg, argument|
100
+ arg.arg_type? &&
101
+ argument.lvar_type? &&
102
+ arg.children == argument.children
103
+ end
99
104
  end
100
105
 
101
106
  def method_name_matches?(method_name, body)
@@ -74,6 +74,7 @@ module RuboCop
74
74
  def static_method_name(method_name)
75
75
  match = METHOD_PATTERN.match(method_name)
76
76
  return nil unless match
77
+
77
78
  match[2] ? 'find_by!' : 'find_by'
78
79
  end
79
80
  end
@@ -15,11 +15,16 @@ module RuboCop
15
15
  class FindEach < Cop
16
16
  MSG = 'Use `find_each` instead of `each`.'.freeze
17
17
 
18
- SCOPE_METHODS = %i[all where not].freeze
18
+ SCOPE_METHODS = %i[
19
+ all eager_load includes joins left_joins left_outer_joins not preload
20
+ references unscoped where
21
+ ].freeze
19
22
  IGNORED_METHODS = %i[order limit select].freeze
20
23
 
21
24
  def on_send(node)
22
- return unless node.receiver && node.method?(:each)
25
+ return unless node.receiver &&
26
+ node.receiver.send_type? &&
27
+ node.method?(:each)
23
28
 
24
29
  return unless SCOPE_METHODS.include?(node.receiver.method_name)
25
30
  return if method_chain(node).any? { |m| ignored_by_find_each?(m) }
@@ -5,7 +5,7 @@ module RuboCop
5
5
  module Rails
6
6
  # This cop is used to identify usages of http methods like `get`, `post`,
7
7
  # `put`, `patch` without the usage of keyword arguments in your tests and
8
- # change them to use keyword args. This cop only applies to Rails >= 5 .
8
+ # change them to use keyword args. This cop only applies to Rails >= 5.
9
9
  # If you are running Rails < 5 you should disable the
10
10
  # Rails/HttpPositionalArguments cop or set your TargetRailsVersion in your
11
11
  # .rubocop.yml file to 4.0, etc.
@@ -56,8 +56,10 @@ module RuboCop
56
56
  http_status(node) do |hash_node|
57
57
  status = status_code(hash_node)
58
58
  return unless status
59
+
59
60
  checker = checker_class.new(status)
60
61
  return unless checker.offensive?
62
+
61
63
  add_offense(checker.node, message: checker.message)
62
64
  end
63
65
  end
@@ -178,6 +178,7 @@ module RuboCop
178
178
  def on_send(node)
179
179
  recv, arguments = association_recv_arguments(node)
180
180
  return unless arguments
181
+
181
182
  with_options = with_options_arguments(recv, node)
182
183
 
183
184
  options = arguments.concat(with_options).flat_map do |arg|
@@ -189,6 +190,7 @@ module RuboCop
189
190
  options_requiring_inverse_of?(options)
190
191
 
191
192
  return if options_contain_inverse_of?(options)
193
+
192
194
  add_offense(node, message: message(options), location: :selector)
193
195
  end
194
196
 
@@ -203,6 +205,7 @@ module RuboCop
203
205
  end
204
206
 
205
207
  return required if target_rails_version >= 5.2
208
+
206
209
  required || options.any? { |opt| as_option?(opt) }
207
210
  end
208
211
 
@@ -226,6 +229,7 @@ module RuboCop
226
229
 
227
230
  def same_context_in_with_options?(arg, recv)
228
231
  return true if arg.nil? && recv.nil?
232
+
229
233
  arg && recv && arg.children[0] == recv.children[0]
230
234
  end
231
235
 
@@ -92,6 +92,7 @@ module RuboCop
92
92
 
93
93
  parent = node.each_ancestor(:class, :module).first
94
94
  return unless parent
95
+
95
96
  block = parent.each_child_node(:begin).first
96
97
  return unless block
97
98
 
@@ -82,6 +82,7 @@ module RuboCop
82
82
  def on_block(node)
83
83
  with_options?(node) do |arg, body|
84
84
  return unless all_block_nodes_in(body).count.zero?
85
+
85
86
  send_nodes = all_send_nodes_in(body)
86
87
 
87
88
  if send_nodes.all? { |n| same_value?(arg, n.receiver) }
@@ -249,6 +249,7 @@ module RuboCop
249
249
  method_name = node.method_name
250
250
  return if receiver != node.receiver &&
251
251
  !IRREVERSIBLE_CHANGE_TABLE_CALLS.include?(method_name)
252
+
252
253
  add_offense(
253
254
  node,
254
255
  message: format(MSG, action: "change_table(with #{method_name})")
@@ -7,12 +7,21 @@ module RuboCop
7
7
  # should be used instead of save because the model might have failed to
8
8
  # save and an exception is better than unhandled failure.
9
9
  #
10
- # This will ignore calls that return a boolean for success if the result
11
- # is assigned to a variable or used as the condition in an if/unless
12
- # statement. It will also ignore calls that return a model assigned to a
13
- # variable that has a call to `persisted?`. Finally, it will ignore any
14
- # call with more than 2 arguments as that is likely not an Active Record
15
- # call or a Model.update(id, attributes) call.
10
+ # This will allow:
11
+ # - update or save calls, assigned to a variable,
12
+ # or used as a condition in an if/unless/case statement.
13
+ # - create calls, assigned to a variable that then has a
14
+ # call to `persisted?`.
15
+ # - calls if the result is explicitly returned from methods and blocks,
16
+ # or provided as arguments.
17
+ # - calls whose signature doesn't look like an ActiveRecord
18
+ # persistence method.
19
+ #
20
+ # By default it will also allow implicit returns from methods and blocks.
21
+ # that behavior can be turned off with `AllowImplicitReturn: false`.
22
+ #
23
+ # You can permit receivers that are giving false positives with
24
+ # `AllowedReceivers: []`
16
25
  #
17
26
  # @example
18
27
  #
@@ -35,6 +44,58 @@ module RuboCop
35
44
  # unless user.persisted?
36
45
  # # ...
37
46
  # end
47
+ #
48
+ # def save_user
49
+ # return user.save
50
+ # end
51
+ #
52
+ # @example AllowImplicitReturn: true (default)
53
+ #
54
+ # # good
55
+ # users.each { |u| u.save }
56
+ #
57
+ # def save_user
58
+ # user.save
59
+ # end
60
+ #
61
+ # @example AllowImplicitReturn: false
62
+ #
63
+ # # bad
64
+ # users.each { |u| u.save }
65
+ # def save_user
66
+ # user.save
67
+ # end
68
+ #
69
+ # # good
70
+ # users.each { |u| u.save! }
71
+ #
72
+ # def save_user
73
+ # user.save!
74
+ # end
75
+ #
76
+ # def save_user
77
+ # return user.save
78
+ # end
79
+ #
80
+ # @example AllowedReceivers: ['merchant.customers', 'Service::Mailer']
81
+ #
82
+ # # bad
83
+ # merchant.create
84
+ # customers.builder.save
85
+ # Mailer.create
86
+ #
87
+ # module Service::Mailer
88
+ # self.create
89
+ # end
90
+ #
91
+ # # good
92
+ # merchant.customers.create
93
+ # MerchantService.merchant.customers.destroy
94
+ # Service::Mailer.update(message: 'Message')
95
+ # ::Service::Mailer.update
96
+ # Services::Service::Mailer.update(message: 'Message')
97
+ # Service::Mailer::update
98
+ #
38
99
  class SaveBang < Cop
39
100
  include NegativeConditional
40
101
 
@@ -43,7 +104,7 @@ module RuboCop
43
104
  CREATE_MSG = (MSG +
44
105
  ' Or check `persisted?` on model returned from ' \
45
106
  '`%<current>s`.').freeze
46
- CREATE_CONDITIONAL_MSG = '`%<method>s` returns a model which is ' \
107
+ CREATE_CONDITIONAL_MSG = '`%<current>s` returns a model which is ' \
47
108
  'always truthy.'.freeze
48
109
 
49
110
  CREATE_PERSIST_METHODS = %i[create
@@ -67,28 +128,23 @@ module RuboCop
67
128
 
68
129
  def check_assignment(assignment)
69
130
  node = right_assignment_node(assignment)
70
- return unless node
71
- return unless CREATE_PERSIST_METHODS.include?(node.method_name)
72
- return unless expected_signature?(node)
131
+
132
+ return unless node && node.send_type?
133
+ return unless persist_method?(node, CREATE_PERSIST_METHODS)
73
134
  return if persisted_referenced?(assignment)
74
135
 
75
- add_offense(node, location: :selector,
76
- message: format(CREATE_MSG,
77
- prefer: "#{node.method_name}!",
78
- current: node.method_name.to_s))
136
+ add_offense_for_node(node, CREATE_MSG)
79
137
  end
80
138
 
81
- def on_send(node)
82
- return unless PERSIST_METHODS.include?(node.method_name)
83
- return unless expected_signature?(node)
139
+ def on_send(node) # rubocop:disable Metrics/CyclomaticComplexity
140
+ return unless persist_method?(node)
84
141
  return if return_value_assigned?(node)
85
142
  return if check_used_in_conditional(node)
86
- return if last_call_of_method?(node)
143
+ return if argument?(node)
144
+ return if implicit_return?(node)
145
+ return if explicit_return?(node)
87
146
 
88
- add_offense(node, location: :selector,
89
- message: format(MSG,
90
- prefer: "#{node.method_name}!",
91
- current: node.method_name.to_s))
147
+ add_offense_for_node(node)
92
148
  end
93
149
 
94
150
  def autocorrect(node)
@@ -100,10 +156,19 @@ module RuboCop
100
156
 
101
157
  private
102
158
 
159
+ def add_offense_for_node(node, msg = MSG)
160
+ name = node.method_name
161
+ full_message = format(msg, prefer: "#{name}!", current: name.to_s)
162
+
163
+ add_offense(node, location: :selector, message: full_message)
164
+ end
165
+
103
166
  def right_assignment_node(assignment)
104
167
  node = assignment.node.child_nodes.first
168
+
105
169
  return node unless node && node.block_type?
106
- node.child_nodes.first
170
+
171
+ node.send_node
107
172
  end
108
173
 
109
174
  def persisted_referenced?(assignment)
@@ -118,36 +183,122 @@ module RuboCop
118
183
  node.send_type? && node.method?(:persisted?)
119
184
  end
120
185
 
186
+ def assignable_node(node)
187
+ assignable = node.block_node || node
188
+ while node
189
+ node = hash_parent(node) || array_parent(node)
190
+ assignable = node if node
191
+ end
192
+ assignable
193
+ end
194
+
195
+ def hash_parent(node)
196
+ pair = node.parent
197
+ return unless pair && pair.pair_type?
198
+
199
+ hash = pair.parent
200
+ return unless hash && hash.hash_type?
201
+
202
+ hash
203
+ end
204
+
205
+ def array_parent(node)
206
+ array = node.parent
207
+ return unless array && array.array_type?
208
+
209
+ array
210
+ end
211
+
121
212
  def check_used_in_conditional(node)
122
213
  return false unless conditional?(node)
123
214
 
124
215
  unless MODIFY_PERSIST_METHODS.include?(node.method_name)
125
- add_offense(node, location: :selector,
126
- message: format(CREATE_CONDITIONAL_MSG,
127
- method: node.method_name.to_s))
216
+ add_offense_for_node(node, CREATE_CONDITIONAL_MSG)
128
217
  end
129
218
 
130
219
  true
131
220
  end
132
221
 
133
- def conditional?(node)
134
- node.parent && (
135
- node.parent.if_type? || node.parent.case_type? ||
136
- node.parent.or_type? || node.parent.and_type? ||
137
- single_negative?(node.parent)
222
+ def conditional?(node) # rubocop:disable Metrics/CyclomaticComplexity
223
+ node = node.block_node || node
224
+
225
+ condition = node.parent
226
+ return false unless condition
227
+
228
+ condition.if_type? || condition.case_type? ||
229
+ condition.or_type? || condition.and_type? ||
230
+ single_negative?(condition)
231
+ end
232
+
233
+ def allowed_receiver?(node)
234
+ return false unless node.receiver
235
+ return false unless cop_config['AllowedReceivers']
236
+
237
+ cop_config['AllowedReceivers'].any? do |allowed_receiver|
238
+ receiver_chain_matches?(node, allowed_receiver)
239
+ end
240
+ end
241
+
242
+ def receiver_chain_matches?(node, allowed_receiver)
243
+ allowed_receiver.split('.').reverse.all? do |receiver_part|
244
+ node = node.receiver
245
+ return false unless node
246
+
247
+ if node.variable?
248
+ node.node_parts.first == receiver_part.to_sym
249
+ elsif node.send_type?
250
+ node.method_name == receiver_part.to_sym
251
+ elsif node.const_type?
252
+ const_matches?(node.const_name, receiver_part)
253
+ end
254
+ end
255
+ end
256
+
257
+ # Const == Const
258
+ # ::Const == ::Const
259
+ # ::Const == Const
260
+ # Const == ::Const
261
+ # NameSpace::Const == Const
262
+ # NameSpace::Const == NameSpace::Const
263
+ # NameSpace::Const != ::Const
264
+ # Const != NameSpace::Const
265
+ def const_matches?(const, allowed_const)
266
+ parts = allowed_const.split('::').reverse.zip(
267
+ const.split('::').reverse
138
268
  )
269
+ parts.all? do |(allowed_part, const_part)|
270
+ allowed_part == const_part.to_s
271
+ end
139
272
  end
140
273
 
141
- def last_call_of_method?(node)
142
- node.parent && node.parent.children.size == node.sibling_index + 1
274
+ def implicit_return?(node)
275
+ return false unless cop_config['AllowImplicitReturn']
276
+
277
+ node = assignable_node(node)
278
+ method = node.parent
279
+ return unless method && (method.def_type? || method.block_type?)
280
+
281
+ method.children.size == node.sibling_index + 1
282
+ end
283
+
284
+ def argument?(node)
285
+ assignable_node(node).argument?
286
+ end
287
+
288
+ def explicit_return?(node)
289
+ ret = assignable_node(node).parent
290
+ ret && (ret.return_type? || ret.next_type?)
143
291
  end
144
292
 
145
- # Ignore simple assignment or if condition
146
293
  def return_value_assigned?(node)
147
- return false unless node.parent
148
- node.parent.lvasgn_type? ||
149
- (node.parent.block_type? && node.parent.parent &&
150
- node.parent.parent.lvasgn_type?)
294
+ assignment = assignable_node(node).parent
295
+ assignment && assignment.lvasgn_type?
296
+ end
297
+
298
+ def persist_method?(node, methods = PERSIST_METHODS)
299
+ methods.include?(node.method_name) &&
300
+ expected_signature?(node) &&
301
+ !allowed_receiver?(node)
151
302
  end
152
303
 
153
304
  # Check argument signature as no arguments or one hash