# frozen_string_literal: true module RuboCop module Cop module Lint # This cop checks for ambiguous operators in the first argument of a # method invocation without parentheses. # # @example # # # bad # # # The `*` is interpreted as a splat operator but it could possibly be # # a `*` method invocation (i.e. `do_something.*(some_array)`). # do_something *some_array # # @example # # # good # # # With parentheses, there's no ambiguity. # do_something(*some_array) class AmbiguousOperator < Cop include ParserDiagnostic AMBIGUITIES = { '+' => { actual: 'positive number', possible: 'addition' }, '-' => { actual: 'negative number', possible: 'subtraction' }, '*' => { actual: 'splat', possible: 'multiplication' }, '&' => { actual: 'block', possible: 'binary AND' }, '**' => { actual: 'keyword splat', possible: 'exponent' } }.each do |key, hash| hash[:operator] = key end MSG_FORMAT = 'Ambiguous %s operator. Parenthesize the method ' \ "arguments if it's surely a %s operator, or add " \ 'a whitespace to the right of the `%s` if it ' \ 'should be a %s.' def autocorrect(node) lambda do |corrector| add_parentheses(node, corrector) end end private def relevant_diagnostic?(diagnostic) diagnostic.reason == :ambiguous_prefix end def find_offense_node_by(diagnostic) ast = processed_source.ast ast.each_node(:splat, :block_pass, :kwsplat) do |node| next unless offense_position?(node, diagnostic) offense_node = offense_node(node) return offense_node if offense_node end ast.each_node(:send).find do |send_node| first_argument = send_node.first_argument first_argument && offense_position?(first_argument, diagnostic) && unary_operator?(first_argument, diagnostic) end end def alternative_message(diagnostic) operator = diagnostic.location.source hash = AMBIGUITIES[operator] format(MSG_FORMAT, hash) end def offense_position?(node, diagnostic) node.source_range.begin_pos == diagnostic.location.begin_pos end def offense_node(node) case node.type when :splat, :block_pass node.parent when :kwsplat node.parent.parent end end def unary_operator?(node, diagnostic) node.source.start_with?(diagnostic.arguments[:prefix]) end end end end end