lib/rubocop/cop/rails/duplicate_association.rb in rubocop-rails-2.21.2 vs lib/rubocop/cop/rails/duplicate_association.rb in rubocop-rails-2.22.0

- old
+ new

@@ -18,41 +18,94 @@ # # # good # belongs_to :bar # has_one :foo # + # # bad + # belongs_to :foo, class_name: 'Foo' + # belongs_to :bar, class_name: 'Foo' + # has_one :baz + # + # # good + # belongs_to :bar, class_name: 'Foo' + # has_one :foo + # class DuplicateAssociation < Base include RangeHelp extend AutoCorrector include ClassSendNodeHelper include ActiveRecordHelper MSG = "Association `%<name>s` is defined multiple times. Don't repeat associations." + MSG_CLASS_NAME = "Association `class_name: %<name>s` is defined multiple times. Don't repeat associations." def_node_matcher :association, <<~PATTERN - (send nil? {:belongs_to :has_one :has_many :has_and_belongs_to_many} ({sym str} $_) ...) + (send nil? {:belongs_to :has_one :has_many :has_and_belongs_to_many} ({sym str} $_) $...) PATTERN + def_node_matcher :class_name, <<~PATTERN + (hash (pair (sym :class_name) $_)) + PATTERN + def on_class(class_node) return unless active_record?(class_node.parent_class) - offenses(class_node).each do |name, nodes| - nodes.each do |node| - add_offense(node, message: format(MSG, name: name)) do |corrector| - next if same_line?(nodes.last, node) + association_nodes = association_nodes(class_node) - corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true)) - end - end + duplicated_association_name_nodes(association_nodes).each do |name, nodes| + register_offense(name, nodes, MSG) end + + duplicated_class_name_nodes(association_nodes).each do |class_name, nodes| + register_offense(class_name, nodes, MSG_CLASS_NAME) + end end private - def offenses(class_node) - class_send_nodes(class_node).select { |node| association(node) } - .group_by { |node| association(node).to_sym } - .select { |_, nodes| nodes.length > 1 } + def register_offense(name, nodes, message_template) + nodes.each do |node| + add_offense(node, message: format(message_template, name: name)) do |corrector| + next if same_line?(nodes.last, node) + + corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true)) + end + end + end + + def association_nodes(class_node) + class_send_nodes(class_node).select do |node| + association(node)&.first + end + end + + def duplicated_association_name_nodes(association_nodes) + grouped_associations = association_nodes.group_by do |node| + association(node).first.to_sym + end + + leave_duplicated_association(grouped_associations) + end + + def duplicated_class_name_nodes(association_nodes) + grouped_associations = association_nodes.group_by do |node| + arguments = association(node).last + next unless arguments.count == 1 + + if (class_name = class_name(arguments.first)) + class_name.source + end + end + + grouped_associations.delete(nil) + + leave_duplicated_association(grouped_associations) + end + + def leave_duplicated_association(grouped_associations) + grouped_associations.select do |_, nodes| + nodes.length > 1 + end end end end end end