class RuboCop::Cop::Style::RaiseArgs

fail ‘message’
raise MyCustomError.new(arg1, arg2, arg3)
raise MyCustomError
raise StandardError.new(‘message’)
# good
raise RuntimeError, arg1, arg2, arg3
raise StandardError, ‘message’
# bad
@example EnforcedStyle: compact
raise MyWrappedError.new(obj), ‘message’
raise MyWrappedError.new(obj)
# With ‘AllowedCompactTypes` set to [’MyWrappedError’]
raise MyKwArgError.new(key1: val1, key2: val2)
raise MyCustomError.new(arg1, arg2, arg3)
raise MyCustomError
fail ‘message’
raise StandardError, ‘message’
# good
raise StandardError.new(‘message’)
# bad
@example EnforcedStyle: exploded (default)
option that takes an Array of exception name Strings.
The exploded style has an ‘AllowedCompactTypes` configuration
passed multiple arguments.
will also suggest constructing error objects when the exception is
The exploded style works identically, but with the addition that it
with more than one argument.
still allow passing just a message, or the construction of an error
to `raise`, rather than construct an instance of the error. It will
style (default), it recommends passing the exception class and message
This cop checks the args passed to `fail` and `raise`. For exploded

def acceptable_exploded_args?(args)

def acceptable_exploded_args?(args)
  # Allow code like `raise Ex.new(arg1, arg2)`.
  return true if args.size > 1
  # Disallow zero arguments.
  return false if args.empty?
  arg = args.first
  # Allow code like `raise Ex.new(kw: arg)`.
  # Allow code like `raise Ex.new(*args)`.
  arg.hash_type? || arg.splat_type?
end

def allowed_non_exploded_type?(arg)

def allowed_non_exploded_type?(arg)
  type = arg.receiver.const_name
  Array(cop_config['AllowedCompactTypes']).include?(type)
end

def check_compact(node)

def check_compact(node)
  if node.arguments.size > 1
    add_offense(node, message: format(COMPACT_MSG, method: node.method_name)) do |corrector|
      replacement = correction_exploded_to_compact(node)
      corrector.replace(node, replacement)
      opposite_style_detected
    end
  else
    correct_style_detected
  end
end

def check_exploded(node)

def check_exploded(node)
  return correct_style_detected unless node.arguments.one?
  first_arg = node.first_argument
  return unless first_arg.send_type? && first_arg.method?(:new)
  return if acceptable_exploded_args?(first_arg.arguments)
  return if allowed_non_exploded_type?(first_arg)
  add_offense(node, message: format(EXPLODED_MSG, method: node.method_name)) do |corrector|
    replacement = correction_compact_to_exploded(node)
    corrector.replace(node, replacement)
    opposite_style_detected
  end
end

def correction_compact_to_exploded(node)

def correction_compact_to_exploded(node)
  exception_node, _new, message_node = *node.first_argument
  arguments = [exception_node, message_node].compact.map(&:source).join(', ')
  if node.parent && requires_parens?(node.parent)
    "#{node.method_name}(#{arguments})"
  else
    "#{node.method_name} #{arguments}"
  end
end

def correction_exploded_to_compact(node)

def correction_exploded_to_compact(node)
  exception_node, *message_nodes = *node.arguments
  return node.source if message_nodes.size > 1
  argument = message_nodes.first.source
  exception_class = exception_node.const_name || exception_node.receiver.source
  if node.parent && requires_parens?(node.parent)
    "#{node.method_name}(#{exception_class}.new(#{argument}))"
  else
    "#{node.method_name} #{exception_class}.new(#{argument})"
  end
end

def on_send(node)

def on_send(node)
  return unless node.command?(:raise) || node.command?(:fail)
  case style
  when :compact
    check_compact(node)
  when :exploded
    check_exploded(node)
  end
end

def requires_parens?(parent)

def requires_parens?(parent)
  parent.and_type? || parent.or_type? || (parent.if_type? && parent.ternary?)
end