class RuboCop::Cop::Style::MixinGrouping
end
extend Qox, Bar
class Foo
# good
end
extend Qox
extend Bar
class Foo
# bad
@example EnforcedStyle: grouped
end
include Bar
include Qox
class Foo
# good
end
include Bar, Qox
class Foo
# bad
@example EnforcedStyle: separated (default)
but it can be configured to enforce grouping them in one declaration.
By default it enforces mixins to be placed in separate declarations,
This cop checks for grouping of mixins in ‘class` and `module` bodies.
def check(send_node)
def check(send_node) if separated_style? check_separated_style(send_node) else check_grouped_style(send_node) end end
def check_grouped_style(send_node)
def check_grouped_style(send_node) return if sibling_mixins(send_node).size == 1 message = format(MSG, mixin: send_node.method_name, suffix: 'a single statement') add_offense(send_node, message: message) do |corrector| range = send_node.loc.expression mixins = sibling_mixins(send_node) if send_node == mixins.first correction = group_mixins(send_node, mixins) else range = range_to_remove_for_subsequent_mixin(mixins, send_node) correction = '' end corrector.replace(range, correction) end end
def check_separated_style(send_node)
def check_separated_style(send_node) return if send_node.arguments.one? message = format(MSG, mixin: send_node.method_name, suffix: 'separate statements') add_offense(send_node, message: message) do |corrector| range = send_node.loc.expression correction = separate_mixins(send_node) corrector.replace(range, correction) end end
def group_mixins(node, mixins)
def group_mixins(node, mixins) mixin_names = mixins.reverse.flat_map { |mixin| mixin.arguments.map(&:source) } "#{node.method_name} #{mixin_names.join(', ')}" end
def grouped_style?
def grouped_style? style == :grouped end
def on_class(node)
def on_class(node) begin_node = node.child_nodes.find(&:begin_type?) || node begin_node.each_child_node(:send).select(&:macro?).each do |macro| next unless MIXIN_METHODS.include?(macro.method_name) check(macro) end end
def range_to_remove_for_subsequent_mixin(mixins, node)
def range_to_remove_for_subsequent_mixin(mixins, node) range = node.loc.expression prev_mixin = mixins.each_cons(2) { |m, n| break m if n == node } between = prev_mixin.loc.expression.end.join(range.begin) # if separated from previous mixin with only whitespace? unless /\S/.match?(between.source) range = range.join(between) # then remove that too end range end
def separate_mixins(node)
def separate_mixins(node) arguments = node.arguments.reverse mixins = ["#{node.method_name} #{arguments.first.source}"] arguments[1..-1].inject(mixins) do |replacement, arg| replacement << "#{indent(node)}#{node.method_name} #{arg.source}" end.join("\n") end
def separated_style?
def separated_style? style == :separated end
def sibling_mixins(send_node)
def sibling_mixins(send_node) siblings = send_node.parent.each_child_node(:send).select(&:macro?) siblings.select { |sibling_node| sibling_node.method?(send_node.method_name) } end