Class: RuboCop::Cop::Style::RedundantFormat
| 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:
self,
::RuboCop::Cop::Base,
::RuboCop::Cop::AutocorrectLogic,
::RuboCop::Cop::IgnoredNode,
::RuboCop::Util,
RuboCop::AST::Sexp
|
|
| Inherits: |
RuboCop::Cop::Base
|
| Defined in: | lib/rubocop/cop/style/redundant_format.rb |
Overview
Checks for calls to Kernel#format or Kernel#sprintf that are redundant.
Calling format with only a single string or constant argument is redundant,
as it can be replaced by the string or constant itself.
Also looks for format calls where the arguments are literals that can be
inlined into a string easily. This applies to the %s, %d, %i, %u, and
%f format specifiers.
Constant Summary
-
ACCEPTABLE_LITERAL_TYPES =
# File 'lib/rubocop/cop/style/redundant_format.rb', line 60%i[str dstr sym dsym numeric boolean nil].freeze
-
MSG =
# File 'lib/rubocop/cop/style/redundant_format.rb', line 57'Use `%<prefer>s` directly instead of `%<method_name>s`.' -
RESTRICT_ON_SEND =
# File 'lib/rubocop/cop/style/redundant_format.rb', line 59%i[format sprintf].to_set.freeze
::RuboCop::Cop::Base - Inherited
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 |
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
::RuboCop::Cop::Base - Inherited
::RuboCop::Cop::AutocorrectLogic - Included
Instance Method Summary
- #complex_number?(node)
- #find_hash_value_node(node, name)
- #format_without_additional_args?(node)
- #on_send(node)
- #rational_number?(node)
- #splatted_arguments?(node)
-
#all_fields_literal?(string, arguments) ⇒ Boolean
private
Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity.
- #argument_value(argument) private
- #argument_values(arguments) private
- #complex_value(complex_node) private
- #detect_unnecessary_fields(node) private
- #dsym_value(dsym_node) private
-
#escape_control_chars(string)
private
Escape any control characters in the string (eg.
- #find_argument(sequence, arguments, hash) private
- #float?(argument) ⇒ Boolean private
- #hash_value(hash_node) private
- #integer?(argument) ⇒ Boolean private
- #matching_argument?(sequence, argument) ⇒ Boolean private
- #message(node, prefer) private
- #numeric?(argument) ⇒ Boolean private
-
#quote(string, node)
private
Add correct quotes to the formatted string, preferring retaining the existing quotes if possible.
- #rational_value(rational_node) private
- #register_all_fields_literal(node, string, arguments) private
- #static_string_value(node) private
-
#string_with_format_sequence?(node) ⇒ Boolean
private
A single-argument
formatwhose string still contains a format sequence is not redundant:format('%s')raises at runtime, andformat('%%')returns'%', so replacing it with the literal would change behavior. - #typed_argument_value(argument) private
-
#unknown_variable_width?(sequence, arguments) ⇒ Boolean
private
If the sequence has a variable (
*) width, it cannot be autocorrected if the width is not given as a numeric literal argument.
::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 |
| #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_gem_version | Returns a gems locked versions (i.e. |
| #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, #matches_absolute_include_pattern?, #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
#all_fields_literal?(string, arguments) ⇒ Boolean (private)
Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
# File 'lib/rubocop/cop/style/redundant_format.rb', line 159
def all_fields_literal?(string, arguments) count = 0 sequences = RuboCop::Cop::Utils::FormatString.new(string).format_sequences return false unless sequences.any? sequences.each do |sequence| next if sequence.percent? next if unknown_variable_width?(sequence, arguments) hash = arguments.detect(&:hash_type?) next unless (argument = find_argument(sequence, arguments, hash)) next unless matching_argument?(sequence, argument) next if (sequence.width || sequence.precision) && argument.dstr_type? count += 1 end sequences.size == count end
#argument_value(argument) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/redundant_format.rb', line 266
def argument_value(argument) argument = argument.children.first if argument.begin_type? # `nil` formats to an empty string (`format('%s', nil) == ''`), not the # literal `'nil'` that `argument.source` would return. return if argument.nil_type? typed_argument_value(argument) end
#argument_values(arguments) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/redundant_format.rb', line 262
def argument_values(arguments) arguments.map { |argument| argument_value(argument) } end
#complex_number?(node)
[ GitHub ]# File 'lib/rubocop/cop/style/redundant_format.rb', line 73
def_node_matcher :complex_number?, <<~PATTERN {complex (send int :+ complex) (begin complex) (begin (send int :+ complex))} PATTERN
#complex_value(complex_node) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/redundant_format.rb', line 306
def complex_value(complex_node) Complex(complex_node.source) end
#detect_unnecessary_fields(node) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/redundant_format.rb', line 129
def detect_unnecessary_fields(node) return unless node.first_argument&.str_type? return if node.first_argument.heredoc? string = node.first_argument.value arguments = node.arguments[1..] return unless string && arguments.any? return if splatted_arguments?(node) register_all_fields_literal(node, string, arguments) end
#dsym_value(dsym_node) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/redundant_format.rb', line 292
def dsym_value(dsym_node) dsym_node.children.first.source end
#escape_control_chars(string) (private)
Escape any control characters in the string (eg. \t or \n become \\t or \\n)
# File 'lib/rubocop/cop/style/redundant_format.rb', line 258
def escape_control_chars(string) string.gsub(/\p{Cc}/) { |s| s.dump[1..-2] } end
#find_argument(sequence, arguments, hash) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/redundant_format.rb', line 190
def find_argument(sequence, arguments, hash) if hash && (sequence.annotated? || sequence.template?) find_hash_value_node(hash, sequence.name.to_sym).first elsif sequence.variable_width? # If the specifier contains `*`, the argument for the width can be ignored. arguments.delete_at(sequence.variable_width_argument_number - 1) arguments.shift elsif sequence.arg_number arguments[sequence.arg_number.to_i - 1] else arguments.shift end end
#find_hash_value_node(node, name)
[ GitHub ]# File 'lib/rubocop/cop/style/redundant_format.rb', line 78
def_node_search :find_hash_value_node, <<~PATTERN (pair (sym %1) $_) PATTERN
#float?(argument) ⇒ Boolean (private)
# File 'lib/rubocop/cop/style/redundant_format.rb', line 233
def float?(argument) numeric?(argument) && Float(argument_value(argument), exception: false) end
#format_without_additional_args?(node)
[ GitHub ]# File 'lib/rubocop/cop/style/redundant_format.rb', line 63
def_node_matcher :format_without_additional_args?, <<~PATTERN (send {(const {nil? cbase} :Kernel) nil?} %RESTRICT_ON_SEND ${str dstr const}) PATTERN
#hash_value(hash_node) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/redundant_format.rb', line 296
def hash_value(hash_node) hash_node.each_pair.with_object({}) do |pair, hash| hash[pair.key.value] = argument_value(pair.value) end end
#integer?(argument) ⇒ Boolean (private)
# File 'lib/rubocop/cop/style/redundant_format.rb', line 229
def integer?(argument) numeric?(argument) && Integer(argument_value(argument), exception: false) end
#matching_argument?(sequence, argument) ⇒ Boolean (private)
# File 'lib/rubocop/cop/style/redundant_format.rb', line 205
def matching_argument?(sequence, argument) # Template specifiers don't give a type, any acceptable literal type is ok. return argument.type?(*ACCEPTABLE_LITERAL_TYPES) if sequence.template? # An argument matches a specifier if it can be easily converted # to that type. case sequence.type when 's' argument.type?(*ACCEPTABLE_LITERAL_TYPES) when 'd', 'i', 'u' integer?(argument) when 'f' float?(argument) else false end end
#message(node, prefer) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/redundant_format.rb', line 125
def (node, prefer) format(MSG, prefer: prefer, method_name: node.method_name) end
#numeric?(argument) ⇒ Boolean (private)
# File 'lib/rubocop/cop/style/redundant_format.rb', line 223
def numeric?(argument) argument.type?(:numeric, :str) || rational_number?(argument) || complex_number?(argument) end
#on_send(node)
[ GitHub ]# File 'lib/rubocop/cop/style/redundant_format.rb', line 90
def on_send(node) format_without_additional_args?(node) do |value| next if string_with_format_sequence?(value) replacement = escape_control_chars(value.source) add_offense(node, message: (node, replacement)) do |corrector| corrector.replace(node, replacement) end return end detect_unnecessary_fields(node) end
#quote(string, node) (private)
Add correct quotes to the formatted string, preferring retaining the existing quotes if possible.
# File 'lib/rubocop/cop/style/redundant_format.rb', line 239
def quote(string, node) str_node = node.first_argument start_delimiter = str_node.loc.begin.source end_delimiter = str_node.loc.end.source # If there is any interpolation, the delimiters need to be changed potentially if node.each_descendant(:dstr, :dsym).any? case start_delimiter when "'" start_delimiter = end_delimiter = '"' when /\A%q(.)/ start_delimiter = "%Q#{Regexp.last_match[1]}" end end "#{start_delimiter}#{escape_control_chars(string)}#{end_delimiter}" end
#rational_number?(node)
[ GitHub ]# File 'lib/rubocop/cop/style/redundant_format.rb', line 68
def_node_matcher :rational_number?, <<~PATTERN {rational (send int :/ rational) (begin rational) (begin (send int :/ rational))} PATTERN
#rational_value(rational_node) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/redundant_format.rb', line 302
def rational_value(rational_node) rational_node.source.to_r end
#register_all_fields_literal(node, string, arguments) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/redundant_format.rb', line 142
def register_all_fields_literal(node, string, arguments) return unless all_fields_literal?(string, arguments.dup) format_arguments = argument_values(arguments) begin formatted_string = format(string, *format_arguments) rescue ArgumentError return end replacement = quote(formatted_string, node) add_offense(node, message: (node, replacement)) do |corrector| corrector.replace(node, replacement) end end
#splatted_arguments?(node)
[ GitHub ]# File 'lib/rubocop/cop/style/redundant_format.rb', line 83
def_node_matcher :splatted_arguments?, <<~PATTERN (send _ %RESTRICT_ON_SEND <{ splat (hash <kwsplat ...>) } ...>) PATTERN
#static_string_value(node) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/redundant_format.rb', line 117
def static_string_value(node) if node.str_type? node.value elsif node.dstr_type? node.children.select(&:str_type?).map(&:value).join end end
#string_with_format_sequence?(node) ⇒ Boolean (private)
A single-argument format whose string still contains a format sequence is
not redundant: format('%s') raises at runtime, and format('%%') returns
'%', so replacing it with the literal would change behavior.
# File 'lib/rubocop/cop/style/redundant_format.rb', line 110
def string_with_format_sequence?(node) string = static_string_value(node) return false unless string RuboCop::Cop::Utils::FormatString.new(string).format_sequences.any? end
#typed_argument_value(argument) (private)
[ GitHub ]# File 'lib/rubocop/cop/style/redundant_format.rb', line 276
def typed_argument_value(argument) if argument.dsym_type? dsym_value(argument) elsif argument.hash_type? hash_value(argument) elsif rational_number?(argument) rational_value(argument) elsif complex_number?(argument) complex_value(argument) elsif argument.respond_to?(:value) argument.value else argument.source end end
#unknown_variable_width?(sequence, arguments) ⇒ Boolean (private)
If the sequence has a variable (*) width, it cannot be autocorrected
if the width is not given as a numeric literal argument
# File 'lib/rubocop/cop/style/redundant_format.rb', line 182
def unknown_variable_width?(sequence, arguments) return false unless sequence.variable_width? argument = arguments[sequence.variable_width_argument_number - 1] !numeric?(argument) end