123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Lint::FormatParameterMismatch

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, ::RuboCop::Cop::Base, ::RuboCop::ExcludeLimit, NodePattern::Macros, RuboCop::AST::Sexp
Instance Chain:
Inherits: RuboCop::Cop::Base
Defined in: lib/rubocop/cop/lint/format_parameter_mismatch.rb

Overview

This lint sees if there is a mismatch between the number of expected fields for format/sprintf/#% and what is actually passed as arguments.

In addition it checks whether different formats are used in the same format string. Do not mix numbered, unnumbered, and named formats in the same format string.

Examples:

# bad

format('A value: %s and another: %i', a_value)
# good

format('A value: %s and another: %i', a_value, another)
# bad

format('Unnumbered format: %s and numbered: %2$s', a_value, another)
# good

format('Numbered format: %1$s and numbered %2$s', a_value, another)

Constant Summary

::RuboCop::Cop::Base - Inherited

EMPTY_OFFENSES, RESTRICT_ON_SEND

Class Attribute Summary

::RuboCop::Cop::Base - Inherited

.gem_requirements, .lint?,
.support_autocorrect?

Returns if class supports autocorrect.

.support_multiple_source?

Override if your cop should be called repeatedly for multiple investigations Between calls to on_new_investigation and on_investigation_end, the result of processed_source will remain constant.

Class Method Summary

::RuboCop::Cop::Base - Inherited

.autocorrect_incompatible_with

List of cops that should not try to autocorrect at the same time as this cop.

.badge

Naming.

.callbacks_needed, .cop_name, .department,
.documentation_url

Returns an url to view this cops documentation online.

.exclude_from_registry

Call for abstract Cop classes.

.inherited,
.joining_forces

Override and return the Force class(es) you need to join.

.match?

Returns true if the cop name or the cop namespace matches any of the given names.

.new,
.requires_gem

Register a version requirement for the given gem name.

.restrict_on_send

::RuboCop::ExcludeLimit - Extended

exclude_limit

Sets up a configuration option to have an exclude limit tracked.

transform

Instance Attribute Summary

Instance Method Summary

::RuboCop::Cop::Base - Inherited

#add_global_offense

Adds an offense that has no particular location.

#add_offense

Adds an offense on the specified range (or node with an expression) Unless that offense is disabled for this range, a corrector will be yielded to provide the cop the opportunity to autocorrect the offense.

#begin_investigation

Called before any investigation.

#callbacks_needed,
#cop_config

Configuration Helpers.

#cop_name, #excluded_file?,
#external_dependency_checksum

This method should be overridden when a cop’s behavior depends on state that lives outside of these locations:

#inspect,
#message

Gets called if no message is specified when calling add_offense or add_global_offense Cops are discouraged to override this; instead pass your message directly.

#name

Alias for Base#cop_name.

#offenses,
#on_investigation_end

Called after all on_…​

#on_new_investigation

Called before all on_…​

#on_other_file

Called instead of all on_…​

#parse

There should be very limited reasons for a Cop to do it’s own parsing.

#parser_engine,
#ready

Called between investigations.

#relevant_file?, #target_rails_version, #target_ruby_version, #annotate, #apply_correction, #attempt_correction,
#callback_argument

Reserved for Cop::Cop.

#complete_investigation

Called to complete an investigation.

#correct, #current_corrector,
#current_offense_locations

Reserved for Commissioner:

#current_offenses, #currently_disabled_lines, #custom_severity, #default_severity, #disable_uncorrectable, #enabled_line?, #file_name_matches_any?, #find_message, #find_severity, #range_for_original, #range_from_node_or_range,
#reset_investigation

Actually private methods.

#use_corrector

::RuboCop::Cop::AutocorrectLogic - Included

::RuboCop::Cop::IgnoredNode - Included

Constructor Details

This class inherits a constructor from RuboCop::Cop::Base

Instance Method Details

#called_on_string?(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 100

def_node_matcher :called_on_string?, <<~PATTERN
  {(send {nil? const_type?} _ {str dstr} ...)
   (send {str dstr} ...)}
PATTERN

#count_format_matches(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 137

def count_format_matches(node)
  [node.arguments.count - 1, expected_fields_count(node.first_argument)]
end

#count_matches(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 119

def count_matches(node)
  if countable_format?(node)
    count_format_matches(node)
  elsif countable_percent?(node)
    count_percent_matches(node)
  else
    [:unknown] * 2
  end
end

#count_percent_matches(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 141

def count_percent_matches(node)
  [node.first_argument.child_nodes.count,
   expected_fields_count(node.receiver)]
end

#countable_format?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 129

def countable_format?(node)
  (sprintf?(node) || format?(node)) && !heredoc?(node)
end

#countable_percent?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 133

def countable_percent?(node)
  percent?(node) && node.first_argument.array_type?
end

#expected_fields_count(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 153

def expected_fields_count(node)
  return :unknown unless string_type?(node)

  format_string = RuboCop::Cop::Utils::FormatString.new(node.source)
  return 1 if format_string.named_interpolation?

  max_digit_dollar_num = format_string.max_digit_dollar_num
  return max_digit_dollar_num if max_digit_dollar_num&.nonzero?

  format_string
    .format_sequences
    .reject(&:percent?)
    .reduce(0) { |acc, seq| acc + seq.arity }
end

#format?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 168

def format?(node)
  format_method?(:format, node)
end

#format_method?(name, node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 146

def format_method?(name, node)
  return false if node.const_receiver? && !node.receiver.loc.name.is?(KERNEL)
  return false unless node.method?(name)

  node.arguments.size > 1 && string_type?(node.first_argument)
end

#format_string?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 64

def format_string?(node)
  called_on_string?(node) && method_with_format_args?(node)
end

#heredoc?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 115

def heredoc?(node)
  node.first_argument.source[0, 2] == SHOVEL
end

#invalid_format_string?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 68

def invalid_format_string?(node)
  string = if sprintf?(node) || format?(node)
             node.first_argument.source
           else
             node.receiver.source
           end
  !RuboCop::Cop::Utils::FormatString.new(string).valid?
end

#matched_arguments_count?(expected, passed) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 91

def matched_arguments_count?(expected, passed)
  if passed.negative?
    expected < passed.abs
  else
    expected != passed
  end
end

#message(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 186

def message(node)
  num_args_for_format, num_expected_fields = count_matches(node)

  method_name = node.method?(:%) ? 'String#%' : node.method_name

  format(MSG, arg_num: num_args_for_format, method: method_name,
              field_num: num_expected_fields)
end

#method_with_format_args?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 105

def method_with_format_args?(node)
  sprintf?(node) || format?(node) || percent?(node)
end

#offending_node?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 77

def offending_node?(node)
  return false if splat_args?(node)

  num_of_format_args, num_of_expected_fields = count_matches(node)

  return false if num_of_format_args == :unknown

  first_arg = node.first_argument
  return false if num_of_expected_fields.zero? &&
                  (first_arg.dstr_type? || first_arg.array_type?)

  matched_arguments_count?(num_of_expected_fields, num_of_format_args)
end

#on_send(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 49

def on_send(node)
  return unless format_string?(node)

  if invalid_format_string?(node)
    add_offense(node.loc.selector, message: MSG_INVALID)
    return
  end

  return unless offending_node?(node)

  add_offense(node.loc.selector, message: message(node))
end

#percent?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 176

def percent?(node)
  receiver = node.receiver

  percent = node.method?(:%) && (string_type?(receiver) || node.first_argument.array_type?)

  return false if percent && string_type?(receiver) && heredoc?(node)

  percent
end

#splat_args?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 109

def splat_args?(node)
  return false if percent?(node)

  node.arguments.drop(1).any?(&:splat_type?)
end

#sprintf?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 172

def sprintf?(node)
  format_method?(:sprintf, node)
end

#string_type?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/lint/format_parameter_mismatch.rb', line 195

def string_type?(node)
  STRING_TYPES.include?(node.type)
end