rubocop 0.52.0 → 0.52.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (166) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -5
  3. data/config/default.yml +1 -11
  4. data/config/disabled.yml +5 -0
  5. data/config/enabled.yml +6 -8
  6. data/lib/rubocop.rb +13 -2
  7. data/lib/rubocop/ast/node.rb +23 -15
  8. data/lib/rubocop/cli.rb +25 -2
  9. data/lib/rubocop/config.rb +23 -8
  10. data/lib/rubocop/cop/bundler/duplicated_gem.rb +2 -2
  11. data/lib/rubocop/cop/bundler/ordered_gems.rb +9 -0
  12. data/lib/rubocop/cop/commissioner.rb +1 -1
  13. data/lib/rubocop/cop/correctors/alignment_corrector.rb +121 -0
  14. data/lib/rubocop/cop/correctors/condition_corrector.rb +28 -0
  15. data/lib/rubocop/cop/correctors/empty_line_corrector.rb +26 -0
  16. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +62 -0
  17. data/lib/rubocop/cop/correctors/ordered_gem_corrector.rb +44 -0
  18. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +31 -0
  19. data/lib/rubocop/cop/correctors/punctuation_corrector.rb +29 -0
  20. data/lib/rubocop/cop/correctors/space_corrector.rb +34 -0
  21. data/lib/rubocop/cop/correctors/string_literal_corrector.rb +25 -0
  22. data/lib/rubocop/cop/correctors/unused_arg_corrector.rb +31 -0
  23. data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +2 -2
  24. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +9 -0
  25. data/lib/rubocop/cop/generator.rb +18 -87
  26. data/lib/rubocop/cop/generator/require_file_injector.rb +78 -0
  27. data/lib/rubocop/cop/layout/access_modifier_indentation.rb +5 -1
  28. data/lib/rubocop/cop/layout/align_array.rb +5 -1
  29. data/lib/rubocop/cop/layout/align_hash.rb +1 -1
  30. data/lib/rubocop/cop/layout/align_parameters.rb +5 -1
  31. data/lib/rubocop/cop/layout/case_indentation.rb +1 -1
  32. data/lib/rubocop/cop/layout/class_structure.rb +2 -2
  33. data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +5 -1
  34. data/lib/rubocop/cop/layout/comment_indentation.rb +5 -1
  35. data/lib/rubocop/cop/layout/else_alignment.rb +5 -1
  36. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +3 -3
  37. data/lib/rubocop/cop/layout/empty_lines_around_arguments.rb +17 -19
  38. data/lib/rubocop/cop/layout/empty_lines_around_begin_body.rb +4 -0
  39. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +4 -0
  40. data/lib/rubocop/cop/layout/empty_lines_around_class_body.rb +4 -0
  41. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +4 -0
  42. data/lib/rubocop/cop/layout/empty_lines_around_method_body.rb +4 -0
  43. data/lib/rubocop/cop/layout/empty_lines_around_module_body.rb +4 -0
  44. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +4 -0
  45. data/lib/rubocop/cop/layout/first_hash_element_line_break.rb +4 -0
  46. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +4 -0
  47. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +4 -0
  48. data/lib/rubocop/cop/layout/first_parameter_indentation.rb +6 -2
  49. data/lib/rubocop/cop/layout/indent_array.rb +6 -2
  50. data/lib/rubocop/cop/layout/indent_assignment.rb +6 -2
  51. data/lib/rubocop/cop/layout/indent_hash.rb +5 -1
  52. data/lib/rubocop/cop/layout/indentation_consistency.rb +5 -1
  53. data/lib/rubocop/cop/layout/indentation_width.rb +5 -1
  54. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +4 -0
  55. data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +3 -3
  56. data/lib/rubocop/cop/layout/multiline_block_layout.rb +2 -2
  57. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +4 -0
  58. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +4 -0
  59. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +6 -2
  60. data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +4 -0
  61. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +6 -2
  62. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +3 -1
  63. data/lib/rubocop/cop/layout/space_after_comma.rb +4 -0
  64. data/lib/rubocop/cop/layout/space_after_semicolon.rb +4 -0
  65. data/lib/rubocop/cop/layout/space_before_comma.rb +4 -0
  66. data/lib/rubocop/cop/layout/space_before_semicolon.rb +4 -0
  67. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +3 -2
  68. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +16 -7
  69. data/lib/rubocop/cop/layout/tab.rb +1 -1
  70. data/lib/rubocop/cop/lint/block_alignment.rb +1 -1
  71. data/lib/rubocop/cop/lint/def_end_alignment.rb +2 -2
  72. data/lib/rubocop/cop/lint/end_alignment.rb +3 -1
  73. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +1 -1
  74. data/lib/rubocop/cop/lint/unused_block_argument.rb +4 -0
  75. data/lib/rubocop/cop/lint/unused_method_argument.rb +6 -0
  76. data/lib/rubocop/cop/lint/uri_escape_unescape.rb +2 -1
  77. data/lib/rubocop/cop/mixin/alignment.rb +70 -0
  78. data/lib/rubocop/cop/mixin/array_hash_indentation.rb +2 -0
  79. data/lib/rubocop/cop/mixin/array_syntax.rb +2 -0
  80. data/lib/rubocop/cop/mixin/code_length.rb +2 -0
  81. data/lib/rubocop/cop/mixin/configurable_max.rb +2 -0
  82. data/lib/rubocop/cop/mixin/def_node.rb +3 -1
  83. data/lib/rubocop/cop/mixin/documentation_comment.rb +2 -2
  84. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +3 -15
  85. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -25
  86. data/lib/rubocop/cop/mixin/enforce_superclass.rb +0 -6
  87. data/lib/rubocop/cop/mixin/first_element_line_break.rb +5 -9
  88. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +2 -2
  89. data/lib/rubocop/cop/mixin/ignored_pattern.rb +2 -0
  90. data/lib/rubocop/cop/mixin/integer_node.rb +2 -0
  91. data/lib/rubocop/cop/mixin/match_range.rb +2 -0
  92. data/lib/rubocop/cop/mixin/min_body_length.rb +2 -0
  93. data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +2 -0
  94. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +9 -48
  95. data/lib/rubocop/cop/mixin/negative_conditional.rb +2 -16
  96. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +12 -31
  97. data/lib/rubocop/cop/mixin/parentheses.rb +2 -19
  98. data/lib/rubocop/cop/mixin/percent_literal.rb +3 -3
  99. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +2 -0
  100. data/lib/rubocop/cop/mixin/rescue_node.rb +2 -0
  101. data/lib/rubocop/cop/mixin/safe_assignment.rb +2 -0
  102. data/lib/rubocop/cop/mixin/space_after_punctuation.rb +1 -3
  103. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +1 -3
  104. data/lib/rubocop/cop/mixin/statement_modifier.rb +4 -2
  105. data/lib/rubocop/cop/mixin/string_help.rb +2 -0
  106. data/lib/rubocop/cop/mixin/string_literals_help.rb +2 -13
  107. data/lib/rubocop/cop/mixin/surrounding_space.rb +4 -21
  108. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -10
  109. data/lib/rubocop/cop/mixin/unused_argument.rb +2 -15
  110. data/lib/rubocop/cop/performance/case_when_splat.rb +1 -1
  111. data/lib/rubocop/cop/rails/action_filter.rb +3 -2
  112. data/lib/rubocop/cop/rails/active_support_aliases.rb +3 -2
  113. data/lib/rubocop/cop/rails/application_job.rb +6 -0
  114. data/lib/rubocop/cop/rails/application_record.rb +6 -0
  115. data/lib/rubocop/cop/rails/blank.rb +10 -9
  116. data/lib/rubocop/cop/rails/date.rb +22 -14
  117. data/lib/rubocop/cop/rails/delegate.rb +1 -1
  118. data/lib/rubocop/cop/rails/dynamic_find_by.rb +3 -2
  119. data/lib/rubocop/cop/rails/enum_uniqueness.rb +4 -2
  120. data/lib/rubocop/cop/rails/environment_comparison.rb +2 -2
  121. data/lib/rubocop/cop/rails/file_path.rb +1 -1
  122. data/lib/rubocop/cop/rails/find_by.rb +2 -2
  123. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +15 -7
  124. data/lib/rubocop/cop/rails/http_positional_arguments.rb +2 -2
  125. data/lib/rubocop/cop/rails/inverse_of.rb +130 -8
  126. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +3 -3
  127. data/lib/rubocop/cop/rails/pluralization_grammar.rb +3 -2
  128. data/lib/rubocop/cop/rails/presence.rb +31 -18
  129. data/lib/rubocop/cop/rails/present.rb +11 -8
  130. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +52 -10
  131. data/lib/rubocop/cop/rails/request_referer.rb +2 -3
  132. data/lib/rubocop/cop/style/auto_resource_cleanup.rb +9 -2
  133. data/lib/rubocop/cop/style/braces_around_hash_parameters.rb +38 -10
  134. data/lib/rubocop/cop/style/class_and_module_children.rb +76 -0
  135. data/lib/rubocop/cop/style/commented_keyword.rb +1 -1
  136. data/lib/rubocop/cop/style/eval_with_location.rb +1 -1
  137. data/lib/rubocop/cop/style/format_string_token.rb +24 -4
  138. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +46 -0
  139. data/lib/rubocop/cop/style/hash_syntax.rb +4 -3
  140. data/lib/rubocop/cop/style/if_unless_modifier.rb +14 -0
  141. data/lib/rubocop/cop/style/method_def_parentheses.rb +79 -0
  142. data/lib/rubocop/cop/style/mixin_usage.rb +13 -2
  143. data/lib/rubocop/cop/style/multiline_if_modifier.rb +1 -1
  144. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +19 -0
  145. data/lib/rubocop/cop/style/negated_if.rb +1 -1
  146. data/lib/rubocop/cop/style/negated_while.rb +6 -4
  147. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -1
  148. data/lib/rubocop/cop/style/parentheses_around_condition.rb +4 -0
  149. data/lib/rubocop/cop/style/redundant_conditional.rb +1 -1
  150. data/lib/rubocop/cop/style/redundant_parentheses.rb +4 -0
  151. data/lib/rubocop/cop/style/rescue_modifier.rb +1 -1
  152. data/lib/rubocop/cop/style/single_line_methods.rb +1 -1
  153. data/lib/rubocop/cop/style/string_literals.rb +4 -0
  154. data/lib/rubocop/cop/style/string_literals_in_interpolation.rb +4 -0
  155. data/lib/rubocop/cop/style/trailing_body_on_method_definition.rb +1 -1
  156. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +4 -0
  157. data/lib/rubocop/cop/style/trailing_comma_in_literal.rb +4 -0
  158. data/lib/rubocop/cop/style/trailing_method_end_statement.rb +1 -1
  159. data/lib/rubocop/formatter/disabled_config_formatter.rb +33 -24
  160. data/lib/rubocop/options.rb +33 -10
  161. data/lib/rubocop/path_util.rb +7 -0
  162. data/lib/rubocop/token.rb +4 -0
  163. data/lib/rubocop/version.rb +1 -1
  164. metadata +14 -4
  165. data/lib/rubocop/cop/mixin/autocorrect_alignment.rb +0 -149
  166. data/lib/rubocop/cop/style/extend_self.rb +0 -92
@@ -38,9 +38,10 @@ module RuboCop
38
38
  # something
39
39
  # end
40
40
  class Blank < Cop
41
- MSG_NIL_OR_EMPTY = 'Use `%s` instead of `%s`.'.freeze
42
- MSG_NOT_PRESENT = 'Use `%s` instead of `%s`.'.freeze
43
- MSG_UNLESS_PRESENT = 'Use `if %s` instead of `%s`.'.freeze
41
+ MSG_NIL_OR_EMPTY = 'Use `%<prefer>s` instead of `%<current>s`.'.freeze
42
+ MSG_NOT_PRESENT = 'Use `%<prefer>s` instead of `%<current>s`.'.freeze
43
+ MSG_UNLESS_PRESENT = 'Use `if %<prefer>s` instead of ' \
44
+ '`%<current>s`.'.freeze
44
45
 
45
46
  # `(send nil $_)` is not actually a valid match for an offense. Nodes
46
47
  # that have a single method call on the left hand side
@@ -73,8 +74,8 @@ module RuboCop
73
74
  not_present?(node) do |receiver|
74
75
  add_offense(node,
75
76
  message: format(MSG_NOT_PRESENT,
76
- replacement(receiver),
77
- node.source))
77
+ prefer: replacement(receiver),
78
+ current: node.source))
78
79
  end
79
80
  end
80
81
 
@@ -86,8 +87,8 @@ module RuboCop
86
87
 
87
88
  add_offense(node,
88
89
  message: format(MSG_NIL_OR_EMPTY,
89
- replacement(variable1),
90
- node.source))
90
+ prefer: replacement(variable1),
91
+ current: node.source))
91
92
  end
92
93
  end
93
94
 
@@ -101,8 +102,8 @@ module RuboCop
101
102
  add_offense(node,
102
103
  location: range,
103
104
  message: format(MSG_UNLESS_PRESENT,
104
- replacement(receiver),
105
- range.source))
105
+ prefer: replacement(receiver),
106
+ current: range.source))
106
107
  end
107
108
  end
108
109
 
@@ -20,27 +20,37 @@ module RuboCop
20
20
  # When EnforcedStyle is 'flexible' then only 'Date.today' is prohibited
21
21
  # and only 'to_time' is reported as warning.
22
22
  #
23
- # @example
24
- # # no offense
25
- # Time.zone.today
26
- # Time.zone.today - 1.day
27
- #
28
- # # flexible
23
+ # @example EnforcedStyle: strict
24
+ # # bad
29
25
  # Date.current
30
26
  # Date.yesterday
27
+ # Date.today
28
+ # date.to_time
29
+ # date.to_time_in_current_zone
30
+ #
31
+ # # good
32
+ # Time.zone.today
33
+ # Time.zone.today - 1.day
31
34
  #
32
- # # always reports offense
35
+ # @example EnforcedStyle: flexible (default)
36
+ # # bad
33
37
  # Date.today
34
38
  # date.to_time
35
39
  #
36
- # # reports offense only when style is 'strict'
40
+ # # good
41
+ # Time.zone.today
42
+ # Time.zone.today - 1.day
43
+ # Date.current
44
+ # Date.yesterday
37
45
  # date.to_time_in_current_zone
46
+ #
38
47
  class Date < Cop
39
48
  include ConfigurableEnforcedStyle
40
49
 
41
- MSG = 'Do not use `%s` without zone. Use `%s` instead.'.freeze
50
+ MSG = 'Do not use `Date.%<day>s` without zone. Use ' \
51
+ '`Time.zone.%<day>s` instead.'.freeze
42
52
 
43
- MSG_SEND = 'Do not use `%s` on Date objects, because they ' \
53
+ MSG_SEND = 'Do not use `%<method>s` on Date objects, because they ' \
44
54
  'know nothing about the time zone in use.'.freeze
45
55
 
46
56
  BAD_DAYS = %i[today current yesterday tomorrow].freeze
@@ -59,7 +69,7 @@ module RuboCop
59
69
  return if safe_chain?(node) || safe_to_time?(node)
60
70
 
61
71
  add_offense(node, location: :selector,
62
- message: format(MSG_SEND, node.method_name))
72
+ message: format(MSG_SEND, method: node.method_name))
63
73
  end
64
74
 
65
75
  private
@@ -72,9 +82,7 @@ module RuboCop
72
82
  method_name = (chain & bad_days).join('.')
73
83
 
74
84
  add_offense(node, location: :selector,
75
- message: format(MSG,
76
- "Date.#{method_name}",
77
- "Time.zone.#{method_name}"))
85
+ message: format(MSG, day: method_name.to_s))
78
86
  end
79
87
 
80
88
  def extract_method_chain(node)
@@ -112,7 +112,7 @@ module RuboCop
112
112
  end
113
113
 
114
114
  def private_or_protected_delegation(node)
115
- line = node.loc.line
115
+ line = node.first_line
116
116
  private_or_protected_before(line) ||
117
117
  private_or_protected_inline(line)
118
118
  end
@@ -26,7 +26,7 @@ module RuboCop
26
26
  # # good
27
27
  # User.find_by!(email: email)
28
28
  class DynamicFindBy < Cop
29
- MSG = 'Use `%s` instead of dynamic `%s`.'.freeze
29
+ MSG = 'Use `%<static_name>s` instead of dynamic `%<method>s`.'.freeze
30
30
  METHOD_PATTERN = /^find_by_(.+?)(!)?$/
31
31
 
32
32
  def on_send(node)
@@ -39,7 +39,8 @@ module RuboCop
39
39
  return unless static_name
40
40
 
41
41
  add_offense(node,
42
- message: format(MSG, static_name, node.method_name))
42
+ message: format(MSG, static_name: static_name,
43
+ method: node.method_name))
43
44
  end
44
45
 
45
46
  def autocorrect(node)
@@ -20,7 +20,8 @@ module RuboCop
20
20
  class EnumUniqueness < Cop
21
21
  include Duplication
22
22
 
23
- MSG = 'Duplicate value `%s` found in `%s` enum declaration.'.freeze
23
+ MSG = 'Duplicate value `%<value>s` found in `%<enum>s` ' \
24
+ 'enum declaration.'.freeze
24
25
 
25
26
  def_node_matcher :enum_declaration, <<-PATTERN
26
27
  (send nil? :enum (hash (pair (_ $_) ${array hash})))
@@ -33,7 +34,8 @@ module RuboCop
33
34
  return unless duplicates?(items)
34
35
 
35
36
  consecutive_duplicates(items).each do |item|
36
- add_offense(item, message: format(MSG, item.source, name))
37
+ add_offense(item, message: format(MSG, value: item.source,
38
+ enum: name))
37
39
  end
38
40
  end
39
41
  end
@@ -16,7 +16,7 @@ module RuboCop
16
16
  # # good
17
17
  # Rails.env.production?
18
18
  class EnvironmentComparison < Cop
19
- MSG = 'Favor `Rails.env.%s?` over `Rails.env == %s`.'.freeze
19
+ MSG = "Favor `Rails.env.%<env>s?` over `Rails.env == '%<env>s'`.".freeze
20
20
 
21
21
  SYM_MSG = 'Do not compare `Rails.env` with a symbol, it will always ' \
22
22
  'evaluate to `false`.'.freeze
@@ -40,7 +40,7 @@ module RuboCop
40
40
  def on_send(node)
41
41
  environment_str_comparison?(node) do |env_node|
42
42
  env, = *env_node
43
- add_offense(node, message: format(MSG, env, env_node.source))
43
+ add_offense(node, message: format(MSG, env: env))
44
44
  end
45
45
  environment_sym_comparison?(node) do |_|
46
46
  add_offense(node, message: SYM_MSG)
@@ -62,7 +62,7 @@ module RuboCop
62
62
 
63
63
  def register_offense(node)
64
64
  line_range = node.loc.column...node.loc.last_column
65
- source_range = source_range(processed_source.buffer, node.loc.line,
65
+ source_range = source_range(processed_source.buffer, node.first_line,
66
66
  line_range)
67
67
  add_offense(node, location: source_range)
68
68
  end
@@ -14,7 +14,7 @@ module RuboCop
14
14
  # # good
15
15
  # User.find_by(name: 'Bruce')
16
16
  class FindBy < Cop
17
- MSG = 'Use `find_by` instead of `where.%s`.'.freeze
17
+ MSG = 'Use `find_by` instead of `where.%<method>s`.'.freeze
18
18
  TARGET_SELECTORS = %i[first take].freeze
19
19
 
20
20
  def_node_matcher :where_first?, <<-PATTERN
@@ -28,7 +28,7 @@ module RuboCop
28
28
  node.loc.selector.end_pos)
29
29
 
30
30
  add_offense(node, location: range,
31
- message: format(MSG, node.method_name))
31
+ message: format(MSG, method: node.method_name))
32
32
  end
33
33
 
34
34
  def autocorrect(node)
@@ -47,21 +47,29 @@ module RuboCop
47
47
  PATTERN
48
48
 
49
49
  def on_send(node)
50
- if !association_without_options?(node)
50
+ unless association_without_options?(node)
51
51
  return if valid_options?(association_with_options?(node))
52
- else
53
- n = node.parent.begin_type? ? node.parent.parent : node.parent
54
-
55
- if with_options_block(n)
56
- return if valid_options?(with_options_block(n))
57
- end
58
52
  end
59
53
 
54
+ return if valid_options_in_with_options_block?(node)
55
+
60
56
  add_offense(node, location: :selector)
61
57
  end
62
58
 
63
59
  private
64
60
 
61
+ def valid_options_in_with_options_block?(node)
62
+ return true unless node.parent
63
+
64
+ n = node.parent.begin_type? ? node.parent.parent : node.parent
65
+
66
+ if with_options_block(n)
67
+ return true if valid_options?(with_options_block(n))
68
+ end
69
+
70
+ false
71
+ end
72
+
65
73
  def valid_options?(options)
66
74
  return true unless options
67
75
  return true if options.any? do |o|
@@ -20,7 +20,7 @@ module RuboCop
20
20
  extend TargetRailsVersion
21
21
 
22
22
  MSG = 'Use keyword arguments instead of ' \
23
- 'positional arguments for http call: `%s`.'.freeze
23
+ 'positional arguments for http call: `%<verb>s`.'.freeze
24
24
  KEYWORD_ARGS = %i[
25
25
  headers env params body flash as xhr session method
26
26
  ].freeze
@@ -37,7 +37,7 @@ module RuboCop
37
37
  return unless needs_conversion?(data)
38
38
 
39
39
  add_offense(node, location: :selector,
40
- message: format(MSG, node.method_name))
40
+ message: format(MSG, verb: node.method_name))
41
41
  end
42
42
  end
43
43
 
@@ -10,19 +10,109 @@ module RuboCop
10
10
  # for associations to work in both ways, or set to `false` to opt-out.
11
11
  #
12
12
  # @example
13
+ # # good
14
+ # class Blog < ApplicationRecord
15
+ # has_many :posts
16
+ # end
17
+ #
18
+ # class Post < ApplicationRecord
19
+ # belongs_to :blog
20
+ # end
21
+ #
22
+ # @example
13
23
  # # bad
14
24
  # class Blog < ApplicationRecord
15
- # has_many :recent_posts, -> { order(published_at: :desc) }
25
+ # has_many :posts, -> { order(published_at: :desc) }
26
+ # end
27
+ #
28
+ # class Post < ApplicationRecord
29
+ # belongs_to :blog
16
30
  # end
17
31
  #
18
32
  # # good
19
33
  # class Blog < ApplicationRecord
20
- # has_many(:recent_posts,
34
+ # has_many(:posts,
21
35
  # -> { order(published_at: :desc) },
22
36
  # inverse_of: :blog
23
37
  # )
24
38
  # end
25
39
  #
40
+ # class Post < ApplicationRecord
41
+ # belongs_to :blog
42
+ # end
43
+ #
44
+ # # good
45
+ # class Blog < ApplicationRecord
46
+ # with_options inverse_of: :blog do
47
+ # has_many :posts, -> { order(published_at: :desc) }
48
+ # end
49
+ # end
50
+ #
51
+ # class Post < ApplicationRecord
52
+ # belongs_to :blog
53
+ # end
54
+ #
55
+ # @example
56
+ # # bad
57
+ # class Picture < ApplicationRecord
58
+ # belongs_to :imageable, polymorphic: true
59
+ # end
60
+ #
61
+ # class Employee < ApplicationRecord
62
+ # has_many :pictures, as: :imageable
63
+ # end
64
+ #
65
+ # class Product < ApplicationRecord
66
+ # has_many :pictures, as: :imageable
67
+ # end
68
+ #
69
+ # # good
70
+ # class Picture < ApplicationRecord
71
+ # belongs_to :imageable, polymorphic: true
72
+ # end
73
+ #
74
+ # class Employee < ApplicationRecord
75
+ # has_many :pictures, as: :imageable, inverse_of: :imageable
76
+ # end
77
+ #
78
+ # class Product < ApplicationRecord
79
+ # has_many :pictures, as: :imageable, inverse_of: :imageable
80
+ # end
81
+ #
82
+ # @example
83
+ # # bad
84
+ # # However, RuboCop can not detect this pattern...
85
+ # class Physician < ApplicationRecord
86
+ # has_many :appointments
87
+ # has_many :patients, through: :appointments
88
+ # end
89
+ #
90
+ # class Appointment < ApplicationRecord
91
+ # belongs_to :physician
92
+ # belongs_to :patient
93
+ # end
94
+ #
95
+ # class Patient < ApplicationRecord
96
+ # has_many :appointments
97
+ # has_many :physicians, through: :appointments
98
+ # end
99
+ #
100
+ # # good
101
+ # class Physician < ApplicationRecord
102
+ # has_many :appointments
103
+ # has_many :patients, through: :appointments
104
+ # end
105
+ #
106
+ # class Appointment < ApplicationRecord
107
+ # belongs_to :physician, inverse_of: :appointments
108
+ # belongs_to :patient, inverse_of: :appointments
109
+ # end
110
+ #
111
+ # class Patient < ApplicationRecord
112
+ # has_many :appointments
113
+ # has_many :physicians, through: :appointments
114
+ # end
115
+ #
26
116
  # @see http://guides.rubyonrails.org/association_basics.html#bi-directional-associations
27
117
  # @see http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Setting+Inverses
28
118
  class InverseOf < Cop
@@ -32,8 +122,8 @@ module RuboCop
32
122
 
33
123
  MSG = 'Specify an `:inverse_of` option.'.freeze
34
124
 
35
- def_node_matcher :association_arguments, <<-PATTERN
36
- (send nil? {:has_many :has_one :belongs_to} _ $...)
125
+ def_node_matcher :association_recv_arguments, <<-PATTERN
126
+ (send $_ {:has_many :has_one :belongs_to} _ $...)
37
127
  PATTERN
38
128
 
39
129
  def_node_matcher :options_from_argument, <<-PATTERN
@@ -52,6 +142,14 @@ module RuboCop
52
142
  (pair (sym :polymorphic) !nil)
53
143
  PATTERN
54
144
 
145
+ def_node_matcher :as_option?, <<-PATTERN
146
+ (pair (sym :as) !nil)
147
+ PATTERN
148
+
149
+ def_node_matcher :class_name_option?, <<-PATTERN
150
+ (pair (sym :class_name) !nil)
151
+ PATTERN
152
+
55
153
  def_node_matcher :foreign_key_option?, <<-PATTERN
56
154
  (pair (sym :foreign_key) !nil)
57
155
  PATTERN
@@ -61,10 +159,15 @@ module RuboCop
61
159
  PATTERN
62
160
 
63
161
  def on_send(node)
64
- arguments = association_arguments(node)
162
+ recv, arguments = association_recv_arguments(node)
65
163
  return unless arguments
164
+ with_options = with_options_arguments(recv, node)
165
+
166
+ options = arguments.concat(with_options).flat_map do |arg|
167
+ options_from_argument(arg)
168
+ end
169
+ return if options_ignoring_inverse_of?(options)
66
170
 
67
- options = arguments.flat_map { |arg| options_from_argument(arg) }
68
171
  return unless scope?(arguments) ||
69
172
  options_requiring_inverse_of?(options)
70
173
 
@@ -79,17 +182,36 @@ module RuboCop
79
182
  def options_requiring_inverse_of?(options)
80
183
  required = options.any? do |opt|
81
184
  conditions_option?(opt) ||
82
- through_option?(opt) ||
185
+ class_name_option?(opt) ||
83
186
  foreign_key_option?(opt)
84
187
  end
85
188
 
86
189
  return required if target_rails_version >= 5.2
87
- required || options.any? { |opt| polymorphic_option?(opt) }
190
+ required || options.any? { |opt| as_option?(opt) }
191
+ end
192
+
193
+ def options_ignoring_inverse_of?(options)
194
+ options.any? do |opt|
195
+ through_option?(opt) || polymorphic_option?(opt)
196
+ end
88
197
  end
89
198
 
90
199
  def options_contain_inverse_of?(options)
91
200
  options.any? { |opt| inverse_of_option?(opt) }
92
201
  end
202
+
203
+ def with_options_arguments(recv, node)
204
+ blocks = node.each_ancestor(:block).select do |block|
205
+ block.send_node.command?(:with_options) &&
206
+ same_context_in_with_options?(block.arguments.first, recv)
207
+ end
208
+ blocks.flat_map { |n| n.send_node.arguments }
209
+ end
210
+
211
+ def same_context_in_with_options?(arg, recv)
212
+ return true if arg.nil? && recv.nil?
213
+ arg && recv && arg.children[0] == recv.children[0]
214
+ end
93
215
  end
94
216
  end
95
217
  end