rubocop 0.46.0 → 0.47.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rubocop might be problematic. Click here for more details.

Files changed (214) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +77 -2
  4. data/config/default.yml +151 -74
  5. data/config/disabled.yml +9 -0
  6. data/config/enabled.yml +49 -9
  7. data/lib/rubocop.rb +36 -8
  8. data/lib/rubocop/ast/builder.rb +59 -0
  9. data/lib/rubocop/ast/node.rb +607 -0
  10. data/lib/rubocop/ast/node/array_node.rb +45 -0
  11. data/lib/rubocop/ast/node/case_node.rb +63 -0
  12. data/lib/rubocop/ast/node/for_node.rb +53 -0
  13. data/lib/rubocop/ast/node/hash_node.rb +102 -0
  14. data/lib/rubocop/ast/node/if_node.rb +136 -0
  15. data/lib/rubocop/ast/node/keyword_splat_node.rb +45 -0
  16. data/lib/rubocop/ast/node/mixin/conditional_node.rb +45 -0
  17. data/lib/rubocop/ast/node/mixin/hash_element_node.rb +125 -0
  18. data/lib/rubocop/ast/node/mixin/modifier_node.rb +17 -0
  19. data/lib/rubocop/ast/node/pair_node.rb +64 -0
  20. data/lib/rubocop/ast/node/until_node.rb +43 -0
  21. data/lib/rubocop/ast/node/when_node.rb +61 -0
  22. data/lib/rubocop/ast/node/while_node.rb +43 -0
  23. data/lib/rubocop/ast/sexp.rb +16 -0
  24. data/lib/rubocop/{ast_node → ast}/traversal.rb +1 -1
  25. data/lib/rubocop/cli.rb +18 -14
  26. data/lib/rubocop/comment_config.rb +1 -3
  27. data/lib/rubocop/config.rb +93 -35
  28. data/lib/rubocop/config_loader.rb +1 -1
  29. data/lib/rubocop/cop/badge.rb +73 -0
  30. data/lib/rubocop/cop/bundler/duplicated_gem.rb +2 -2
  31. data/lib/rubocop/cop/bundler/ordered_gems.rb +43 -3
  32. data/lib/rubocop/cop/commissioner.rb +17 -6
  33. data/lib/rubocop/cop/cop.rb +25 -112
  34. data/lib/rubocop/cop/lint/ambiguous_operator.rb +9 -4
  35. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +7 -0
  36. data/lib/rubocop/cop/lint/assignment_in_condition.rb +18 -4
  37. data/lib/rubocop/cop/lint/block_alignment.rb +40 -9
  38. data/lib/rubocop/cop/lint/circular_argument_reference.rb +14 -0
  39. data/lib/rubocop/cop/lint/condition_position.rb +14 -16
  40. data/lib/rubocop/cop/lint/debugger.rb +28 -0
  41. data/lib/rubocop/cop/lint/def_end_alignment.rb +21 -1
  42. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +13 -1
  43. data/lib/rubocop/cop/lint/duplicate_case_condition.rb +26 -22
  44. data/lib/rubocop/cop/lint/duplicate_methods.rb +15 -1
  45. data/lib/rubocop/cop/lint/duplicated_key.rb +16 -8
  46. data/lib/rubocop/cop/lint/each_with_object_argument.rb +9 -0
  47. data/lib/rubocop/cop/lint/else_layout.rb +26 -29
  48. data/lib/rubocop/cop/lint/empty_ensure.rb +38 -0
  49. data/lib/rubocop/cop/lint/empty_expression.rb +11 -1
  50. data/lib/rubocop/cop/lint/empty_interpolation.rb +8 -0
  51. data/lib/rubocop/cop/lint/empty_when.rb +14 -16
  52. data/lib/rubocop/cop/lint/end_alignment.rb +48 -28
  53. data/lib/rubocop/cop/lint/end_in_method.rb +23 -0
  54. data/lib/rubocop/cop/lint/ensure_return.rb +21 -0
  55. data/lib/rubocop/cop/lint/float_out_of_range.rb +5 -0
  56. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +29 -4
  57. data/lib/rubocop/cop/lint/handle_exceptions.rb +40 -0
  58. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +7 -2
  59. data/lib/rubocop/cop/lint/ineffective_access_modifier.rb +11 -2
  60. data/lib/rubocop/cop/lint/invalid_character_literal.rb +3 -0
  61. data/lib/rubocop/cop/lint/literal_in_condition.rb +34 -36
  62. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +8 -0
  63. data/lib/rubocop/cop/lint/loop.rb +36 -0
  64. data/lib/rubocop/cop/lint/multiple_compare.rb +46 -0
  65. data/lib/rubocop/cop/lint/nested_method_definition.rb +22 -0
  66. data/lib/rubocop/cop/lint/next_without_accumulator.rb +5 -0
  67. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +8 -0
  68. data/lib/rubocop/cop/lint/percent_string_array.rb +27 -13
  69. data/lib/rubocop/cop/lint/percent_symbol_array.rb +14 -4
  70. data/lib/rubocop/cop/lint/rand_one.rb +7 -3
  71. data/lib/rubocop/cop/lint/require_parentheses.rb +20 -19
  72. data/lib/rubocop/cop/lint/rescue_exception.rb +20 -0
  73. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +66 -0
  74. data/lib/rubocop/cop/lint/shadowed_exception.rb +6 -1
  75. data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +24 -0
  76. data/lib/rubocop/cop/lint/string_conversion_in_interpolation.rb +8 -0
  77. data/lib/rubocop/cop/lint/underscore_prefixed_variable_name.rb +24 -0
  78. data/lib/rubocop/cop/lint/unified_integer.rb +5 -0
  79. data/lib/rubocop/cop/lint/unneeded_disable.rb +2 -2
  80. data/lib/rubocop/cop/lint/unneeded_splat_expansion.rb +5 -0
  81. data/lib/rubocop/cop/lint/unreachable_code.rb +17 -0
  82. data/lib/rubocop/cop/lint/unused_block_argument.rb +2 -0
  83. data/lib/rubocop/cop/lint/unused_method_argument.rb +10 -0
  84. data/lib/rubocop/cop/lint/useless_access_modifier.rb +28 -1
  85. data/lib/rubocop/cop/lint/useless_assignment.rb +18 -0
  86. data/lib/rubocop/cop/lint/useless_comparison.rb +3 -1
  87. data/lib/rubocop/cop/lint/useless_else_without_rescue.rb +16 -1
  88. data/lib/rubocop/cop/lint/useless_setter_call.rb +16 -4
  89. data/lib/rubocop/cop/lint/void.rb +52 -0
  90. data/lib/rubocop/cop/message_annotator.rb +102 -0
  91. data/lib/rubocop/cop/metrics/block_length.rb +6 -0
  92. data/lib/rubocop/cop/metrics/block_nesting.rb +17 -5
  93. data/lib/rubocop/cop/metrics/line_length.rb +11 -4
  94. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -2
  95. data/lib/rubocop/cop/mixin/array_syntax.rb +2 -11
  96. data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +12 -5
  97. data/lib/rubocop/cop/mixin/configurable_formatting.rb +48 -0
  98. data/lib/rubocop/cop/mixin/configurable_max.rb +3 -3
  99. data/lib/rubocop/cop/mixin/configurable_naming.rb +5 -33
  100. data/lib/rubocop/cop/mixin/configurable_numbering.rb +6 -47
  101. data/lib/rubocop/cop/mixin/documentation_comment.rb +7 -1
  102. data/lib/rubocop/cop/mixin/duplication.rb +46 -0
  103. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +2 -2
  104. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +14 -11
  105. data/lib/rubocop/cop/mixin/hash_alignment.rb +114 -0
  106. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +3 -3
  107. data/lib/rubocop/cop/mixin/negative_conditional.rb +21 -7
  108. data/lib/rubocop/cop/mixin/on_method_def.rb +14 -0
  109. data/lib/rubocop/cop/mixin/on_normal_if_unless.rb +1 -24
  110. data/lib/rubocop/cop/mixin/statement_modifier.rb +8 -13
  111. data/lib/rubocop/cop/mixin/target_ruby_version.rb +16 -0
  112. data/lib/rubocop/cop/mixin/trailing_comma.rb +2 -3
  113. data/lib/rubocop/cop/offense.rb +1 -1
  114. data/lib/rubocop/cop/performance/case_when_splat.rb +56 -59
  115. data/lib/rubocop/cop/performance/detect.rb +2 -2
  116. data/lib/rubocop/cop/performance/flat_map.rb +3 -3
  117. data/lib/rubocop/cop/performance/redundant_merge.rb +3 -6
  118. data/lib/rubocop/cop/performance/regexp_match.rb +201 -0
  119. data/lib/rubocop/cop/rails/delegate.rb +2 -2
  120. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +10 -19
  121. data/lib/rubocop/cop/rails/enum_uniqueness.rb +12 -40
  122. data/lib/rubocop/cop/rails/file_path.rb +80 -0
  123. data/lib/rubocop/cop/rails/find_each.rb +5 -14
  124. data/lib/rubocop/cop/rails/http_positional_arguments.rb +30 -24
  125. data/lib/rubocop/cop/rails/not_null_column.rb +23 -0
  126. data/lib/rubocop/cop/rails/reversible_migration.rb +217 -0
  127. data/lib/rubocop/cop/rails/safe_navigation.rb +4 -2
  128. data/lib/rubocop/cop/rails/skips_model_validations.rb +46 -0
  129. data/lib/rubocop/cop/rails/time_zone.rb +1 -1
  130. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +7 -5
  131. data/lib/rubocop/cop/registry.rb +170 -0
  132. data/lib/rubocop/cop/{lint → security}/eval.rb +7 -1
  133. data/lib/rubocop/cop/security/marshal_load.rb +33 -0
  134. data/lib/rubocop/cop/security/yaml_load.rb +37 -0
  135. data/lib/rubocop/cop/style/align_hash.rb +138 -169
  136. data/lib/rubocop/cop/style/and_or.rb +1 -1
  137. data/lib/rubocop/cop/style/braces_around_hash_parameters.rb +10 -15
  138. data/lib/rubocop/cop/style/case_indentation.rb +36 -27
  139. data/lib/rubocop/cop/style/conditional_assignment.rb +64 -47
  140. data/lib/rubocop/cop/style/each_with_object.rb +4 -1
  141. data/lib/rubocop/cop/style/else_alignment.rb +14 -20
  142. data/lib/rubocop/cop/style/empty_case_condition.rb +16 -25
  143. data/lib/rubocop/cop/style/empty_else.rb +20 -22
  144. data/lib/rubocop/cop/style/empty_literal.rb +4 -4
  145. data/lib/rubocop/cop/style/empty_method.rb +12 -6
  146. data/lib/rubocop/cop/style/encoding.rb +1 -1
  147. data/lib/rubocop/cop/style/file_name.rb +24 -4
  148. data/lib/rubocop/cop/style/first_method_argument_line_break.rb +1 -1
  149. data/lib/rubocop/cop/style/format_string.rb +17 -48
  150. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +40 -11
  151. data/lib/rubocop/cop/style/guard_clause.rb +11 -17
  152. data/lib/rubocop/cop/style/hash_syntax.rb +24 -42
  153. data/lib/rubocop/cop/style/identical_conditional_branches.rb +40 -28
  154. data/lib/rubocop/cop/style/if_inside_else.rb +6 -9
  155. data/lib/rubocop/cop/style/if_unless_modifier.rb +16 -25
  156. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +3 -9
  157. data/lib/rubocop/cop/style/indent_array.rb +1 -1
  158. data/lib/rubocop/cop/style/indentation_width.rb +29 -60
  159. data/lib/rubocop/cop/style/infinite_loop.rb +21 -22
  160. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +86 -0
  161. data/lib/rubocop/cop/style/{method_call_parentheses.rb → method_call_without_args_parentheses.rb} +8 -1
  162. data/lib/rubocop/cop/style/missing_else.rb +40 -14
  163. data/lib/rubocop/cop/style/multiline_if_modifier.rb +5 -15
  164. data/lib/rubocop/cop/style/multiline_if_then.rb +14 -8
  165. data/lib/rubocop/cop/style/multiline_method_call_indentation.rb +3 -3
  166. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +1 -5
  167. data/lib/rubocop/cop/style/mutable_constant.rb +3 -2
  168. data/lib/rubocop/cop/style/negated_if.rb +3 -19
  169. data/lib/rubocop/cop/style/negated_while.rb +2 -17
  170. data/lib/rubocop/cop/style/nested_modifier.rb +16 -43
  171. data/lib/rubocop/cop/style/nested_ternary_operator.rb +3 -5
  172. data/lib/rubocop/cop/style/next.rb +23 -21
  173. data/lib/rubocop/cop/style/non_nil_check.rb +2 -3
  174. data/lib/rubocop/cop/style/not.rb +1 -3
  175. data/lib/rubocop/cop/style/numeric_literals.rb +2 -2
  176. data/lib/rubocop/cop/style/one_line_conditional.rb +12 -22
  177. data/lib/rubocop/cop/style/option_hash.rb +4 -15
  178. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -3
  179. data/lib/rubocop/cop/style/parentheses_around_condition.rb +8 -12
  180. data/lib/rubocop/cop/style/percent_q_literals.rb +15 -12
  181. data/lib/rubocop/cop/style/redundant_freeze.rb +3 -2
  182. data/lib/rubocop/cop/style/redundant_parentheses.rb +27 -4
  183. data/lib/rubocop/cop/style/redundant_return.rb +4 -8
  184. data/lib/rubocop/cop/style/safe_navigation.rb +13 -6
  185. data/lib/rubocop/cop/style/space_after_colon.rb +2 -4
  186. data/lib/rubocop/cop/style/space_around_block_parameters.rb +1 -1
  187. data/lib/rubocop/cop/style/space_around_operators.rb +15 -13
  188. data/lib/rubocop/cop/style/string_methods.rb +1 -3
  189. data/lib/rubocop/cop/style/symbol_array.rb +1 -5
  190. data/lib/rubocop/cop/style/ternary_parentheses.rb +5 -6
  191. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +2 -5
  192. data/lib/rubocop/cop/style/trailing_comma_in_literal.rb +1 -1
  193. data/lib/rubocop/cop/style/unless_else.rb +1 -5
  194. data/lib/rubocop/cop/style/when_then.rb +4 -2
  195. data/lib/rubocop/cop/style/while_until_do.rb +9 -13
  196. data/lib/rubocop/cop/style/while_until_modifier.rb +12 -11
  197. data/lib/rubocop/cop/style/word_array.rb +5 -9
  198. data/lib/rubocop/cop/team.rb +16 -15
  199. data/lib/rubocop/cop/util.rb +13 -3
  200. data/lib/rubocop/formatter/clang_style_formatter.rb +2 -2
  201. data/lib/rubocop/formatter/disabled_config_formatter.rb +2 -1
  202. data/lib/rubocop/magic_comment.rb +196 -0
  203. data/lib/rubocop/options.rb +5 -4
  204. data/lib/rubocop/processed_source.rb +1 -1
  205. data/lib/rubocop/rspec/cop_helper.rb +9 -0
  206. data/lib/rubocop/rspec/shared_examples.rb +1 -1
  207. data/lib/rubocop/runner.rb +7 -2
  208. data/lib/rubocop/version.rb +1 -1
  209. metadata +41 -14
  210. data/lib/rubocop/ast_node.rb +0 -624
  211. data/lib/rubocop/ast_node/builder.rb +0 -30
  212. data/lib/rubocop/ast_node/sexp.rb +0 -13
  213. data/lib/rubocop/cop/mixin/hash_node.rb +0 -14
  214. data/lib/rubocop/cop/mixin/if_node.rb +0 -42
@@ -61,8 +61,8 @@ module RuboCop
61
61
 
62
62
  def delegate?(body)
63
63
  receiver, = *body
64
- return false unless receiver.is_a?(Node) && receiver.send_type?
65
- receiver.child_nodes.empty?
64
+ receiver.respond_to?(:type) && receiver.send_type? &&
65
+ receiver.child_nodes.empty?
66
66
  end
67
67
 
68
68
  def arguments_match?(args, body)
@@ -17,44 +17,35 @@ module RuboCop
17
17
  MSG = '`allow_blank` is not a valid option, use `allow_nil`.'.freeze
18
18
 
19
19
  def_node_matcher :delegate, <<-PATTERN
20
- (send _ :delegate _ $hash)
20
+ (send nil :delegate _ $hash)
21
21
  PATTERN
22
22
 
23
- def_node_matcher :delegate_options, <<-PATTERN
24
- (hash $...)
25
- PATTERN
26
-
27
- def_node_matcher :allow_blank?, <<-PATTERN
28
- (pair $(sym :allow_blank) true)
23
+ def_node_matcher :allow_blank_option?, <<-PATTERN
24
+ (pair (sym :allow_blank) true)
29
25
  PATTERN
30
26
 
31
27
  def on_send(node)
32
28
  offending_node = allow_blank_option(node)
29
+
33
30
  return unless offending_node
34
31
 
35
- allow_blank = offending_node.children.first
36
- add_offense(node, allow_blank.source_range, MSG)
32
+ add_offense(offending_node, :expression, MSG)
37
33
  end
38
34
 
39
- def autocorrect(node)
40
- offending_node = allow_blank_option(node)
41
- return unless offending_node
35
+ private
42
36
 
43
- allow_blank = offending_node.children.first
37
+ def autocorrect(pair_node)
44
38
  lambda do |corrector|
45
- corrector.replace(allow_blank.source_range, 'allow_nil')
39
+ corrector.replace(pair_node.key.source_range, 'allow_nil')
46
40
  end
47
41
  end
48
42
 
49
- private
50
-
51
43
  def allow_blank_option(node)
52
44
  options_hash = delegate(node)
45
+
53
46
  return unless options_hash
54
- options = delegate_options(options_hash)
55
- return unless options
56
47
 
57
- options.detect { |opt| allow_blank?(opt) }
48
+ options_hash.pairs.find { |opt| allow_blank_option?(opt) }
58
49
  end
59
50
  end
60
51
  end
@@ -18,53 +18,25 @@ module RuboCop
18
18
  # # good
19
19
  # enum status: [:active, :archived]
20
20
  class EnumUniqueness < Cop
21
- MSG = 'Duplicate value `%s` found in `%s` enum declaration.'.freeze
22
-
23
- def on_send(node)
24
- _receiver, method_name, *args = *node
25
-
26
- return unless method_name == :enum
27
-
28
- enum_name, enum_args = parse_args(args)
21
+ include Duplication
29
22
 
30
- dupes = arr_dupes(enum_values(enum_args))
31
- return if dupes.empty?
32
-
33
- add_offense(node, :selector, format(MSG, dupes.join(','), enum_name))
34
- end
23
+ MSG = 'Duplicate value `%s` found in `%s` enum declaration.'.freeze
35
24
 
36
- private
25
+ def_node_matcher :enum_declaration, <<-END
26
+ (send nil :enum (hash (pair (_ $_) $_)))
27
+ END
37
28
 
38
- def enum_values(enum_args)
39
- if enum_args.type == :array
40
- enum_array_keys(enum_args)
41
- else
42
- enum_hash_values(enum_args)
43
- end
44
- end
29
+ def on_send(node)
30
+ enum_declaration(node) do |name, args|
31
+ items = args.values
45
32
 
46
- def enum_array_keys(array_node)
47
- array_node.each_child_node.map(&:source)
48
- end
33
+ return unless duplicates?(items)
49
34
 
50
- def enum_hash_values(hash_node)
51
- hash_node.each_child_node.map do |child_node|
52
- child_node.child_nodes.last.source
35
+ consecutive_duplicates(items).each do |item|
36
+ add_offense(item, :expression, format(MSG, item.source, name))
37
+ end
53
38
  end
54
39
  end
55
-
56
- def arr_dupes(array)
57
- array.select { |element| array.count(element) > 1 }.uniq
58
- end
59
-
60
- def parse_args(args)
61
- enum_config = args.first.each_child_node.first.child_nodes
62
-
63
- enum_name = enum_config.first.source
64
- enum_opts = enum_config.last
65
-
66
- [enum_name, enum_opts]
67
- end
68
40
  end
69
41
  end
70
42
  end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop is used to identify usages of file path joining process
7
+ # to use `Rails.root.join` clause.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # Rails.root.join('app/models/goober')
12
+ # File.join(Rails.root, 'app/models/goober')
13
+ # "#{Rails.root}/app/models/goober"
14
+ #
15
+ # # good
16
+ # Rails.root.join('app', 'models', 'goober')
17
+ class FilePath < Cop
18
+ MSG = 'Please use `Rails.root.join(\'path\', \'to\')` instead.'.freeze
19
+
20
+ def_node_search :file_join_nodes, <<-PATTERN
21
+ (send (const nil :File) :join ...)
22
+ PATTERN
23
+
24
+ def_node_search :file_join_nodes?, <<-PATTERN
25
+ (send (const nil :File) :join ...)
26
+ PATTERN
27
+
28
+ def_node_search :rails_root_nodes?, <<-PATTERN
29
+ (send (const nil :Rails) :root)
30
+ PATTERN
31
+
32
+ def_node_search :rails_root_join_nodes, <<-PATTERN
33
+ (send (send (const nil :Rails) :root) :join ...)
34
+ PATTERN
35
+
36
+ def on_dstr(node)
37
+ return unless rails_root_nodes?(node)
38
+ register_offense(node)
39
+ end
40
+
41
+ def on_send(node)
42
+ check_for_file_join_with_rails_root(node)
43
+ check_for_rails_root_join_with_slash_separated_path(node)
44
+ end
45
+
46
+ private
47
+
48
+ def check_for_file_join_with_rails_root(node)
49
+ return unless file_join_nodes?(node)
50
+ return unless file_join_nodes(node).map(&:method_args)
51
+ .flatten
52
+ .any? { |e| rails_root_nodes?(e) }
53
+
54
+ register_offense(node)
55
+ end
56
+
57
+ def check_for_rails_root_join_with_slash_separated_path(node)
58
+ return unless rails_root_nodes?(node)
59
+ return unless rails_root_join_nodes(node).map(&:method_args)
60
+ .flatten
61
+ .any? do |arg|
62
+ arg.source =~ %r{/}
63
+ end
64
+
65
+ register_offense(node)
66
+ end
67
+
68
+ def register_offense(node)
69
+ line_range = node.loc.column...node.loc.last_column
70
+
71
+ add_offense(
72
+ node,
73
+ source_range(processed_source.buffer, node.loc.line, line_range),
74
+ MSG
75
+ )
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -18,12 +18,11 @@ module RuboCop
18
18
  SCOPE_METHODS = [:all, :where, :not].freeze
19
19
 
20
20
  def on_send(node)
21
- receiver, second_method, _selector = *node
22
- return unless second_method == :each
23
- return if receiver.nil?
21
+ receiver, method, _selector = *node
22
+ return unless receiver && method == :each
24
23
 
25
- _model, first_method = *receiver
26
- return unless SCOPE_METHODS.include?(first_method)
24
+ _model, preceding_method = *receiver
25
+ return unless SCOPE_METHODS.include?(preceding_method)
27
26
  return if method_chain(node).any? { |m| ignored_by_find_each?(m) }
28
27
 
29
28
  add_offense(node, node.loc.selector, MSG)
@@ -38,15 +37,7 @@ module RuboCop
38
37
  private
39
38
 
40
39
  def method_chain(node)
41
- if (node.send_type? || node.block_type?) && !node.receiver.nil?
42
- if node.parent
43
- method_chain(node.parent) << node.method_name
44
- else
45
- [node.method_name]
46
- end
47
- else
48
- []
49
- end
40
+ [*node.ancestors, node].map(&:method_name)
50
41
  end
51
42
 
52
43
  def ignored_by_find_each?(relation_method)
@@ -3,9 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- # This cop is used to identify usages of http methods
7
- # like `get`, `post`, `put`, `path` without the usage of keyword arguments
8
- # in your tests and change them to use keyword arguments.
6
+ # This cop is used to identify usages of http methods like `get`, `post`,
7
+ # `put`, `patch` without the usage of keyword arguments in your tests and
8
+ # change them to use keyword arguments.
9
9
  #
10
10
  # @example
11
11
  # # bad
@@ -17,45 +17,50 @@ module RuboCop
17
17
  MSG = 'Use keyword arguments instead of ' \
18
18
  'positional arguments for http call: `%s`.'.freeze
19
19
  KEYWORD_ARGS = [
20
- :headers, :env, :params, :body, :flash, :as,
21
- :xhr, :session, :method, :format
20
+ :headers, :env, :params, :body, :flash, :as, :xhr, :session, :method
22
21
  ].freeze
23
22
  HTTP_METHODS = [:get, :post, :put, :patch, :delete, :head].freeze
24
23
 
24
+ def_node_matcher :http_request?, <<-END
25
+ (send nil {#{HTTP_METHODS.map(&:inspect).join(' ')}} !nil $_data ...)
26
+ END
27
+
25
28
  def on_send(node)
26
- receiver, http_method, http_path, data = *node
27
- # if the first word on the line is not an http method, then skip
28
- return unless HTTP_METHODS.include?(http_method)
29
+ data = http_request?(node)
29
30
  # if the data is nil then we don't need to add keyword arguments
30
31
  # because there is no data to put in params or headers, so skip
31
32
  return if data.nil?
32
33
  return unless needs_conversion?(data)
33
- # ensures this is the first method on the line
34
- # there is an edge case here where sometimes the http method is
35
- # wrapped into another method, but its just safer to skip those
36
- # cases and process manually
37
- return unless receiver.nil?
38
- # a http_method without a path?, must be something else
39
- return if http_path.nil?
34
+
40
35
  add_offense(node, node.loc.selector, format(MSG, node.method_name))
41
36
  end
42
37
 
43
38
  # @return [Boolean] true if the line needs to be converted
44
39
  def needs_conversion?(data)
45
- # if the line has already been converted to use keyword args
46
- # then skip
47
- # ie. get :new, params: { user_id: 1 } (looking for keyword arg)
48
- value = data.descendants.find do |d|
49
- KEYWORD_ARGS.include?(d.children.first) if d.type == :sym
40
+ return true unless data.hash_type?
41
+ children = data.child_nodes
42
+
43
+ value = children.find do |d|
44
+ special_keyword_arg?(d.children.first) ||
45
+ (format_arg?(d.children.first) && children.size == 1)
50
46
  end
47
+
51
48
  value.nil?
52
49
  end
53
50
 
51
+ def special_keyword_arg?(node)
52
+ KEYWORD_ARGS.include?(node.children.first) if node.type == :sym
53
+ end
54
+
55
+ def format_arg?(node)
56
+ node.children.first == :format if node.type == :sym
57
+ end
58
+
54
59
  def convert_hash_data(data, type)
55
60
  # empty hash or no hash return empty string
56
- return '' if data.nil? || data.children.count < 1
61
+ return '' if data.nil? || data.children.empty?
57
62
  hash_data = if data.hash_type?
58
- format('{ %s }', data.children.map(&:source).join(', '))
63
+ format('{ %s }', data.pairs.map(&:source).join(', '))
59
64
  else
60
65
  # user supplies an object,
61
66
  # no need to surround with braces
@@ -78,11 +83,12 @@ module RuboCop
78
83
  _receiver, http_method, http_path, *data = *node
79
84
  controller_action = http_path.source
80
85
  params = convert_hash_data(data.first, 'params')
81
- headers = convert_hash_data(data.last, 'headers') if data.count > 1
86
+ headers = convert_hash_data(data.last, 'headers') if data.size > 1
82
87
  # the range of the text to replace, which is the whole line
83
88
  code_to_replace = node.loc.expression
84
89
  # what to replace with
85
- new_code = format('%s %s%s%s', http_method, controller_action,
90
+ format = parentheses?(node) ? '%s(%s%s%s)' : '%s %s%s%s'
91
+ new_code = format(format, http_method, controller_action,
86
92
  params, headers)
87
93
  ->(corrector) { corrector.replace(code_to_replace, new_code) }
88
94
  end
@@ -9,10 +9,13 @@ module RuboCop
9
9
  # @example
10
10
  # # bad
11
11
  # add_column :users, :name, :string, null: false
12
+ # add_reference :products, :category, null: false
12
13
  #
13
14
  # # good
14
15
  # add_column :users, :name, :string, null: true
15
16
  # add_column :users, :name, :string, null: false, default: ''
17
+ # add_reference :products, :category
18
+ # add_reference :products, :category, null: false, default: 1
16
19
  class NotNullColumn < Cop
17
20
  MSG = 'Do not add a NOT NULL column without a default value.'.freeze
18
21
 
@@ -20,6 +23,10 @@ module RuboCop
20
23
  (send nil :add_column _ _ _ (hash $...))
21
24
  PATTERN
22
25
 
26
+ def_node_matcher :add_not_null_reference?, <<-PATTERN
27
+ (send nil :add_reference _ _ (hash $...))
28
+ PATTERN
29
+
23
30
  def_node_matcher :null_false?, <<-PATTERN
24
31
  (pair (sym :null) (false))
25
32
  PATTERN
@@ -29,7 +36,23 @@ module RuboCop
29
36
  PATTERN
30
37
 
31
38
  def on_send(node)
39
+ check_add_column(node)
40
+ check_add_reference(node)
41
+ end
42
+
43
+ private
44
+
45
+ def check_add_column(node)
32
46
  pairs = add_not_null_column?(node)
47
+ check_pairs(pairs)
48
+ end
49
+
50
+ def check_add_reference(node)
51
+ pairs = add_not_null_reference?(node)
52
+ check_pairs(pairs)
53
+ end
54
+
55
+ def check_pairs(pairs)
33
56
  return unless pairs
34
57
  return if pairs.any? { |pair| has_default?(pair) }
35
58
 
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks whether the change method of the migration file is
7
+ # reversible.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # def change
12
+ # change_table :users do |t|
13
+ # t.column :name, :string
14
+ # end
15
+ # end
16
+ #
17
+ # # good
18
+ # def change
19
+ # create_table :users do |t|
20
+ # t.string :name
21
+ # end
22
+ # end
23
+ #
24
+ # # good
25
+ # def change
26
+ # reversible do |dir|
27
+ # change_table :users do |t|
28
+ # dir.up do
29
+ # t.column :name, :string
30
+ # end
31
+ #
32
+ # dir.down do
33
+ # t.remove :name
34
+ # end
35
+ # end
36
+ # end
37
+ # end
38
+ #
39
+ # @example
40
+ # # drop_table
41
+ #
42
+ # # bad
43
+ # def change
44
+ # drop_table :users
45
+ # end
46
+ #
47
+ # # good
48
+ # def change
49
+ # drop_table :users do |t|
50
+ # t.string :name
51
+ # end
52
+ # end
53
+ #
54
+ # @example
55
+ # # change_column_default
56
+ #
57
+ # # bad
58
+ # def change
59
+ # change_column_default(:suppliers, :qualification, 'new')
60
+ # end
61
+ #
62
+ # # good
63
+ # def change
64
+ # change_column_default(:posts, :state, from: nil, to: "draft")
65
+ # end
66
+ #
67
+ # @example
68
+ # # remove_column
69
+ #
70
+ # # bad
71
+ # def change
72
+ # remove_column(:suppliers, :qualification)
73
+ # end
74
+ #
75
+ # # good
76
+ # def change
77
+ # remove_column(:suppliers, :qualification, :string)
78
+ # end
79
+ #
80
+ # @example
81
+ # # remove_foreign_key
82
+ #
83
+ # # bad
84
+ # def change
85
+ # remove_foreign_key :accounts, column: :owner_id
86
+ # end
87
+ #
88
+ # # good
89
+ # def change
90
+ # remove_foreign_key :accounts, :branches
91
+ # end
92
+ #
93
+ # @see http://api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html
94
+ class ReversibleMigration < Cop
95
+ MSG = '%s is not reversible.'.freeze
96
+
97
+ def_node_matcher :irreversible_schema_statement_call, <<-END
98
+ (send nil ${:change_table :change_table_comment :execute :remove_belongs_to} ...)
99
+ END
100
+
101
+ def_node_matcher :drop_table_call, <<-END
102
+ (send nil :drop_table ...)
103
+ END
104
+
105
+ def_node_matcher :change_column_default_call, <<-END
106
+ (send nil :change_column_default _ _ $...)
107
+ END
108
+
109
+ def_node_matcher :remove_column_call, <<-END
110
+ (send nil :remove_column $...)
111
+ END
112
+
113
+ def_node_matcher :remove_foreign_key_call, <<-END
114
+ (send nil :remove_foreign_key _ $_)
115
+ END
116
+
117
+ def on_send(node)
118
+ return unless within_change_method?(node)
119
+ return if within_reversible_block?(node)
120
+
121
+ check_irreversible_schema_statement_node(node)
122
+ check_drop_table_node(node)
123
+ check_change_column_default_node(node)
124
+ check_remove_column_node(node)
125
+ check_remove_foreign_key_node(node)
126
+ end
127
+
128
+ private
129
+
130
+ def check_irreversible_schema_statement_node(node)
131
+ irreversible_schema_statement_call(node) do |method_name|
132
+ add_offense(node, :expression, format(MSG, method_name))
133
+ end
134
+ end
135
+
136
+ def check_drop_table_node(node)
137
+ drop_table_call(node) do
138
+ unless node.parent.block_type?
139
+ add_offense(
140
+ node, :expression,
141
+ format(MSG, 'drop_table(without block)')
142
+ )
143
+ end
144
+ end
145
+ end
146
+
147
+ def check_change_column_default_node(node)
148
+ change_column_default_call(node) do |args|
149
+ unless all_hash_key?(args.first, :from, :to)
150
+ add_offense(
151
+ node, :expression,
152
+ format(MSG, 'change_column_default(without :from and :to)')
153
+ )
154
+ end
155
+ end
156
+ end
157
+
158
+ def check_remove_column_node(node)
159
+ remove_column_call(node) do |args|
160
+ if args.to_a.size < 3
161
+ add_offense(
162
+ node, :expression,
163
+ format(MSG, 'remove_column(without type)')
164
+ )
165
+ end
166
+ end
167
+ end
168
+
169
+ def check_remove_foreign_key_node(node)
170
+ remove_foreign_key_call(node) do |arg|
171
+ if arg.hash_type?
172
+ add_offense(
173
+ node, :expression,
174
+ format(MSG, 'remove_foreign_key(without table)')
175
+ )
176
+ end
177
+ end
178
+ end
179
+
180
+ def within_change_method?(node)
181
+ parent = node.parent
182
+ while parent
183
+ if parent.def_type?
184
+ method_name, = *parent
185
+ return true if method_name == :change
186
+ end
187
+ parent = parent.parent
188
+ end
189
+ false
190
+ end
191
+
192
+ def within_reversible_block?(node)
193
+ parent = node.parent
194
+ while parent
195
+ if parent.block_type?
196
+ _, block_name = *parent.to_a.first
197
+ return true if block_name == :reversible
198
+ end
199
+ parent = parent.parent
200
+ end
201
+ false
202
+ end
203
+
204
+ def all_hash_key?(args, *keys)
205
+ return false unless args
206
+ return false unless args.hash_type?
207
+
208
+ hash_keys = args.to_a.map do |arg|
209
+ arg.to_a.first.children.first.to_sym
210
+ end
211
+
212
+ hash_keys & keys == keys
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end