123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Style::DocumentDynamicEvalDefinition

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/style/document_dynamic_eval_definition.rb

Overview

When using class_eval (or other eval) with string interpolation, add a comment block showing its appearance if interpolated (a practice used in Rails code).

Examples:

# from activesupport/lib/active_support/core_ext/string/output_safety.rb

# bad
UNSAFE_STRING_METHODS.each do |unsafe_method|
  if 'String'.respond_to?(unsafe_method)
    class_eval <<-EOT, __FILE__, __LINE__ + 1
      def #{unsafe_method}(*params, &block)
        to_str.#{unsafe_method}(*params, &block)
      end

      def #{unsafe_method}!(*params)
        @dirty = true
        super
      end
    EOT
  end
end

# good, inline comments in heredoc
UNSAFE_STRING_METHODS.each do |unsafe_method|
  if 'String'.respond_to?(unsafe_method)
    class_eval <<-EOT, __FILE__, __LINE__ + 1
      def #{unsafe_method}(*params, &block)       # def capitalize(*params, &block)
        to_str.#{unsafe_method}(*params, &block)  #   to_str.capitalize(*params, &block)
      end                                         # end

      def #{unsafe_method}!(*params)              # def capitalize!(*params)
        @dirty = true                             #   @dirty = true
        super                                     #   super
      end                                         # end
    EOT
  end
end

# good, block comments in heredoc
class_eval <<-EOT, __FILE__, __LINE__ + 1
  # def capitalize!(*params)
  #   @dirty = true
  #   super
  # end

  def #{unsafe_method}!(*params)
    @dirty = true
    super
  end
EOT

# good, block comments before heredoc
class_eval(
  # def capitalize!(*params)
  #   @dirty = true
  #   super
  # end

  <<-EOT, __FILE__, __LINE__ + 1
    def #{unsafe_method}!(*params)
      @dirty = true
      super
    end
  EOT
)

# bad - interpolated string without comment
class_eval("def #{unsafe_method}!(*params); end")

# good - with inline comment or replace it with block comment using heredoc
class_eval("def #{unsafe_method}!(*params); end # def capitalize!(*params); end")

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.

.builtin?

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

Cops (other than builtin) are encouraged to implement this.

.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, #use_corrector

::RuboCop::Cop::AutocorrectLogic - Included

::RuboCop::Cop::IgnoredNode - Included

Constructor Details

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

Instance Method Details

#comment_block_docs?(arg_node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/document_dynamic_eval_definition.rb', line 107

def comment_block_docs?(arg_node)
  comments = heredoc_comment_blocks(arg_node.loc.heredoc_body.line_span)
             .concat(preceding_comment_blocks(arg_node.parent))

  return false if comments.none?

  regexp = comment_regexp(arg_node)
  comments.any?(regexp) || regexp.match?(comments.join)
end

#comment_regexp(arg_node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/document_dynamic_eval_definition.rb', line 147

def comment_regexp(arg_node)
  # Replace the interpolations with wildcards
  regexp_parts = arg_node.child_nodes.map do |n|
    n.begin_type? ? /.+/ : source_to_regexp(n.source)
  end

  Regexp.new(regexp_parts.join)
end

#heredoc_comment_blocks(heredoc_body) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/document_dynamic_eval_definition.rb', line 126

def heredoc_comment_blocks(heredoc_body)
  # Collect comments inside the heredoc
  line_range = (heredoc_body.begin - 1)..(heredoc_body.end - 1)
  lines = processed_source.lines[line_range]

  lines.each_with_object({}).with_index(line_range.begin) do |(line, hash), index|
    merge_adjacent_comments(line, index, hash)
  end.values
end

#inline_comment_docs?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/document_dynamic_eval_definition.rb', line 100

def inline_comment_docs?(node)
  node.each_child_node(:begin).all? do |begin_node|
    source_line = processed_source.lines[begin_node.first_line - 1]
    source_line.match?(COMMENT_REGEXP)
  end
end

#interpolated?(arg_node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/document_dynamic_eval_definition.rb', line 96

def interpolated?(arg_node)
  arg_node.each_child_node(:begin).any?
end

#merge_adjacent_comments(line, index, hash) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/document_dynamic_eval_definition.rb', line 136

def merge_adjacent_comments(line, index, hash)
  # Combine adjacent comment lines into a single string
  return unless (line = line.dup.gsub!(BLOCK_COMMENT_REGEXP, ''))

  hash[index] = if hash.keys.last == index - 1
                  [hash.delete(index - 1), line].join("\n")
                else
                  line
                end
end

#on_send(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/document_dynamic_eval_definition.rb', line 84

def on_send(node)
  arg_node = node.first_argument

  return unless arg_node&.dstr_type? && interpolated?(arg_node)
  return if inline_comment_docs?(arg_node) ||
            (arg_node.heredoc? && comment_block_docs?(arg_node))

  add_offense(node.loc.selector)
end

#preceding_comment_blocks(node) (private)

[ GitHub ]

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

def preceding_comment_blocks(node)
  # Collect comments in the method call, but outside the heredoc
  comments = processed_source.each_comment_in_lines(node.source_range.line_span)

  comments.each_with_object({}) do |comment, hash|
    merge_adjacent_comments(comment.text, comment.loc.line, hash)
  end.values
end

#source_to_regexp(source) (private)

[ GitHub ]

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

def source_to_regexp(source)
  # Get the source in the heredoc being `eval`ed, without any comments
  # and turn it into a regexp
  return /\s+/ if source.blank?

  source = source.gsub(COMMENT_REGEXP, '')
  return if source.blank?

  /\s*#{Regexp.escape(source.strip)}/
end