rubocop 0.52.0 → 0.52.1

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 (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