class RuboCop::Cop::Style::TernaryParentheses

Checks for the presence of parentheses around ternary conditions. It is configurable to enforce inclusion or omission of parentheses using ‘EnforcedStyle`. Omission is only enforced when removing the parentheses won’t cause a different behavior.

‘AllowSafeAssignment` option for safe assignment. By safe assignment we mean putting parentheses around an assignment to indicate “I know I’m using an assignment as a condition. It’s not a mistake.”

@example EnforcedStyle: require_no_parentheses (default)

# bad
foo = (bar?) ? a : b
foo = (bar.baz?) ? a : b
foo = (bar && baz) ? a : b

# good
foo = bar? ? a : b
foo = bar.baz? ? a : b
foo = bar && baz ? a : b

@example EnforcedStyle: require_parentheses

# bad
foo = bar? ? a : b
foo = bar.baz? ? a : b
foo = bar && baz ? a : b

# good
foo = (bar?) ? a : b
foo = (bar.baz?) ? a : b
foo = (bar && baz) ? a : b

@example EnforcedStyle: require_parentheses_when_complex

# bad
foo = (bar?) ? a : b
foo = (bar.baz?) ? a : b
foo = bar && baz ? a : b

# good
foo = bar? ? a : b
foo = bar.baz? ? a : b
foo = (bar && baz) ? a : b

@example AllowSafeAssignment: true (default)

# good
foo = (bar = baz) ? a : b

@example AllowSafeAssignment: false

# bad
foo = (bar = baz) ? a : b

Constants

MSG
MSG_COMPLEX
NON_COMPLEX_TYPES
VARIABLE_TYPES

Public Instance Methods

on_if(node) click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 69
def on_if(node)
  condition = node.condition

  return if only_closing_parenthesis_is_last_line?(condition)
  return if condition_as_parenthesized_one_line_pattern_matching?(condition)
  return unless node.ternary? && offense?(node)

  message = message(node)

  add_offense(node, message: message) do |corrector|
    autocorrect(corrector, node)
  end
end

Private Instance Methods

autocorrect(corrector, node) click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 100
def autocorrect(corrector, node)
  condition = node.condition

  return nil if parenthesized?(condition) &&
                (safe_assignment?(condition) || unsafe_autocorrect?(condition))

  if parenthesized?(condition)
    correct_parenthesized(corrector, condition)
  else
    correct_unparenthesized(corrector, condition)
  end
end
below_ternary_precedence?(child) click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 181
def below_ternary_precedence?(child)
  # Handle English "or", e.g. 'foo or bar ? a : b'
  (child.or_type? && child.semantic_operator?) ||
    # Handle English "and", e.g. 'foo and bar ? a : b'
    (child.and_type? && child.semantic_operator?) ||
    # Handle English "not", e.g. 'not foo ? a : b'
    (child.send_type? && child.prefix_not?)
end
complex_condition?(condition) click to toggle source

If the condition is parenthesized we recurse and check for any complex expressions within it.

# File lib/rubocop/cop/style/ternary_parentheses.rb, line 131
def complex_condition?(condition)
  if condition.begin_type?
    condition.to_a.any? { |x| complex_condition?(x) }
  else
    !non_complex_expression?(condition)
  end
end
condition_as_parenthesized_one_line_pattern_matching?(condition) click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 89
def condition_as_parenthesized_one_line_pattern_matching?(condition)
  return false unless condition.parenthesized_call?
  return false unless (first_child = condition.children.first)

  if target_ruby_version >= 3.0
    first_child.match_pattern_p_type?
  else
    first_child.match_pattern_type? # For Ruby 2.7's one line pattern matching AST.
  end
end
correct_parenthesized(corrector, condition) click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 196
def correct_parenthesized(corrector, condition)
  corrector.remove(condition.loc.begin)
  corrector.remove(condition.loc.end)

  # Ruby allows no space between the question mark and parentheses.
  # If we remove the parentheses, we need to add a space or we'll
  # generate invalid code.
  corrector.insert_after(condition.loc.end, ' ') unless whitespace_after?(condition)

  if (send_node = condition.child_nodes.last) && node_args_need_parens?(send_node)
    parenthesize_condition_arguments(corrector, send_node)
  end
end
correct_unparenthesized(corrector, condition) click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 210
def correct_unparenthesized(corrector, condition)
  corrector.wrap(condition, '(', ')')
end
message(node) click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 151
def message(node)
  if require_parentheses_when_complex?
    command = parenthesized?(node.condition) ? 'Only use' : 'Use'
    format(MSG_COMPLEX, command: command)
  else
    command = require_parentheses? ? 'Use' : 'Omit'
    format(MSG, command: command)
  end
end
node_args_need_parens?(send_node) click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 227
def node_args_need_parens?(send_node)
  return false unless node_with_args?(send_node)
  return false if send_node.arguments.none? || send_node.parenthesized?

  send_node.dot? || send_node.safe_navigation? || unparenthesized_method_call?(send_node)
end
node_with_args?(node) click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 234
def node_with_args?(node)
  node.call_type? || node.defined_type?
end
non_complex_expression?(condition) click to toggle source

Anything that is not a variable, constant, or method/.method call will be counted as a complex expression.

# File lib/rubocop/cop/style/ternary_parentheses.rb, line 141
def non_complex_expression?(condition)
  NON_COMPLEX_TYPES.include?(condition.type) || non_complex_send?(condition)
end
non_complex_send?(node) click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 145
def non_complex_send?(node)
  return false unless node.call_type?

  !node.operator_method? || node.method?(:[])
end
offense?(node) click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 113
def offense?(node)
  condition = node.condition

  if safe_assignment?(condition)
    !safe_assignment_allowed?
  else
    parens = parenthesized?(condition)
    case style
    when :require_parentheses_when_complex
      complex_condition?(condition) ? !parens : parens
    else
      require_parentheses? ? !parens : parens
    end
  end
end
only_closing_parenthesis_is_last_line?(condition) click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 85
def only_closing_parenthesis_is_last_line?(condition)
  condition.source.split("\n").last == ')'
end
parenthesize_condition_arguments(corrector, send_node) click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 214
def parenthesize_condition_arguments(corrector, send_node)
  range_start = send_node.defined_type? ? send_node.loc.keyword : send_node.loc.selector
  opening_range = range_start.end.join(send_node.first_argument.source_range.begin)

  corrector.replace(opening_range, '(')
  corrector.insert_after(send_node.last_argument, ')')
end
parenthesized?(node) click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 169
def parenthesized?(node)
  node.begin_type?
end
require_parentheses?() click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 161
def require_parentheses?
  style == :require_parentheses
end
require_parentheses_when_complex?() click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 165
def require_parentheses_when_complex?
  style == :require_parentheses_when_complex
end
unparenthesized_method_call?(child) click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 177
def unparenthesized_method_call?(child)
  /^[a-z]/i.match?(method_name(child)) && !child.parenthesized?
end
unsafe_autocorrect?(condition) click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 173
def unsafe_autocorrect?(condition)
  condition.children.any? { |child| below_ternary_precedence?(child) }
end
whitespace_after?(node) click to toggle source
# File lib/rubocop/cop/style/ternary_parentheses.rb, line 222
def whitespace_after?(node)
  last_token = processed_source.last_token_of(node)
  last_token.space_after?
end