123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Style::FormatStringToken

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

Overview

Use a consistent style for named format string tokens.

Note
unannotated style cop only works for strings which are passed as arguments to those methods: printf, sprintf, format, %. The reason is that unannotated format is very similar to encoded URLs or Date/Time formatting strings.

This cop’s allowed methods can be customized with ::RuboCop::Cop::AllowedMethods. By default, there are no allowed methods.

It is allowed to contain unannotated token if the number of them is less than or equals to MaxUnannotatedPlaceholdersAllowed.

Examples:

EnforcedStyle: annotated (default)

# bad
format('%{greeting}', greeting: 'Hello')
format('%s', 'Hello')

# good
format('%<greeting>s', greeting: 'Hello')

EnforcedStyle: template

# bad
format('%<greeting>s', greeting: 'Hello')
format('%s', 'Hello')

# good
format('%{greeting}', greeting: 'Hello')

EnforcedStyle: unannotated

# bad
format('%<greeting>s', greeting: 'Hello')
format('%{greeting}', greeting: 'Hello')

# good
format('%s', 'Hello')

MaxUnannotatedPlaceholdersAllowed: 0

# bad
format('%06d', 10)
format('%s %s.', 'Hello', 'world')

# good
format('%<number>06d', number: 10)

MaxUnannotatedPlaceholdersAllowed: 1 (default)

# bad
format('%s %s.', 'Hello', 'world')

# good
format('%06d', 10)

AllowedMethods: [] (default)

# bad
redirect('foo/%{bar_id}')

AllowedMethods: [redirect]

# good
redirect('foo/%{bar_id}')

AllowedPatterns: [] (default)

# bad
redirect('foo/%{bar_id}')

AllowedPatterns: ['redirect']

# good
redirect('foo/%{bar_id}')

Constant Summary

::RuboCop::Cop::Base - Inherited

EMPTY_OFFENSES, RESTRICT_ON_SEND

::RuboCop::Cop::ConfigurableEnforcedStyle - Included

SYMBOL_TO_STRING_CACHE

Class Attribute Summary

::RuboCop::Cop::AutoCorrector - Extended

::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 a 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::AllowedPattern - Included

::RuboCop::Cop::AllowedMethods - Included

::RuboCop::Cop::ConfigurableEnforcedStyle - Included

::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

#allowed_unannotated?(detections) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/format_string_token.rb', line 213

def allowed_unannotated?(detections)
  return false unless detections.all? do |detected_sequence,|
                        detected_sequence.style == :unannotated
                      end
  return true if detections.size <= max_unannotated_placeholders_allowed

  detections.any? { |detected_sequence,| !correctable_sequence?(detected_sequence.type) }
end

#autocorrect_sequence(corrector, detected_sequence, token_range) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/format_string_token.rb', line 139

def autocorrect_sequence(corrector, detected_sequence, token_range)
  return if style == :unannotated

  name = detected_sequence.name
  return if name.nil?

  flags = detected_sequence.flags
  width = detected_sequence.width
  precision = detected_sequence.precision
  type = detected_sequence.style == :template ? 's' : detected_sequence.type
  correction = case style
               when :annotated then "%<#{name}>#{flags}#{width}#{precision}#{type}"
               when :template then "%#{flags}#{width}#{precision}{#{name}}"
               end
  corrector.replace(token_range, correction)
end

#check_sequence(detected_sequence, token_range) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/format_string_token.rb', line 124

def check_sequence(detected_sequence, token_range)
  if detected_sequence.style == style
    correct_style_detected
  elsif correctable_sequence?(detected_sequence.type)
    style_detected(detected_sequence.style)
    add_offense(token_range, message: message(detected_sequence.style)) do |corrector|
      autocorrect_sequence(corrector, detected_sequence, token_range)
    end
  end
end

#collect_detections(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/format_string_token.rb', line 203

def collect_detections(node)
  detections = []
  tokens(node) do |detected_sequence, token_range|
    unless unannotated_format?(node, detected_sequence.style)
      detections << [detected_sequence, token_range]
    end
  end
  detections
end

#correctable_sequence?(detected_type) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/format_string_token.rb', line 135

def correctable_sequence?(detected_type)
  detected_type == 's' || style == :annotated || style == :unannotated
end

#format_string_in_typical_context?(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/format_string_token.rb', line 106

def_node_matcher :format_string_in_typical_context?, <<~PATTERN
  {
    ^(send _ {:format :sprintf :printf} %0 ...)
    ^(send %0 :% _)
  }
PATTERN

#format_string_token?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/format_string_token.rb', line 113

def format_string_token?(node)
  !node.value.include?('%') || node.each_ancestor(:xstr, :regexp).any?
end

#max_unannotated_placeholders_allowed (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/format_string_token.rb', line 222

def max_unannotated_placeholders_allowed
  cop_config['MaxUnannotatedPlaceholdersAllowed']
end

#message(detected_style) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/format_string_token.rb', line 160

def message(detected_style)
  "Prefer #{message_text(style)} over #{message_text(detected_style)}."
end

#message_text(style) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/format_string_token.rb', line 165

def message_text(style)
  {
    annotated: 'annotated tokens (like `%<foo>s`)',
    template: 'template tokens (like `%{foo}`)',
    unannotated: 'unannotated tokens (like `%s`)'
  }[style]
end

#on_str(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/format_string_token.rb', line 91

def on_str(node)
  return if format_string_token?(node) || use_allowed_method?(node)

  detections = collect_detections(node)
  return if detections.empty?
  return if allowed_unannotated?(detections)

  detections.each do |detected_sequence, token_range|
    check_sequence(detected_sequence, token_range)
  end
end

#str_contents(source_map) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/format_string_token.rb', line 180

def str_contents(source_map)
  if source_map.is_a?(Parser::Source::Map::Heredoc)
    source_map.heredoc_body
  elsif source_map.begin
    source_map.expression.adjust(begin_pos: +1, end_pos: -1)
  else
    source_map.expression
  end
end

#token_ranges(contents) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/format_string_token.rb', line 190

def token_ranges(contents)
  format_string = RuboCop::Cop::Utils::FormatString.new(contents.source)

  format_string.format_sequences.each do |detected_sequence|
    next if detected_sequence.percent?

    token = contents.begin.adjust(begin_pos: detected_sequence.begin_pos,
                                  end_pos: detected_sequence.end_pos)

    yield(detected_sequence, token)
  end
end

#tokens(str_node, &block) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/format_string_token.rb', line 174

def tokens(str_node, &block)
  return if str_node.source == '__FILE__'

  token_ranges(str_contents(str_node.loc), &block)
end

#unannotated_format?(node, detected_style) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/format_string_token.rb', line 156

def unannotated_format?(node, detected_style)
  detected_style == :unannotated && !format_string_in_typical_context?(node)
end

#use_allowed_method?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/format_string_token.rb', line 117

def use_allowed_method?(node)
  send_parent = node.each_ancestor(:send).first
  send_parent &&
    (allowed_method?(send_parent.method_name) ||
    matches_allowed_pattern?(send_parent.method_name))
end