123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Style::EvalWithLocation

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/eval_with_location.rb

Overview

Ensures that eval methods (eval, instance_eval, class_eval and module_eval) are given filename and line number values (_\_FILE\__ and \__LINE\_\_). This data is used to ensure that any errors raised within the evaluated code will be given the correct identification in a backtrace.

The cop also checks that the line number given relative to _\_LINE\_\_ is correct.

This cop will autocorrect incorrect or missing filename and line number values. However, if eval is called without a binding argument, the cop will not attempt to automatically add a binding, or add filename and line values.

This cop works only when a string literal is given as a code string. No offense is reported if a string variable is given as below:

Examples:

# bad
eval <<-RUBY
  def do_something
  end
RUBY

# bad
C.class_eval <<-RUBY
  def do_something
  end
RUBY

# good
eval <<-RUBY, binding, __FILE__, __LINE__ + 1
  def do_something
  end
RUBY

# good
C.class_eval <<-RUBY, __FILE__, __LINE__ + 1
  def do_something
  end
RUBY
# not checked
code = <<-RUBY
  def do_something
  end
RUBY
eval code

Constant Summary

::RuboCop::Cop::Base - Inherited

EMPTY_OFFENSES, RESTRICT_ON_SEND

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.

.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

#add_offense_for_different_line(node, line_node, line_diff) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/eval_with_location.rb', line 186

def add_offense_for_different_line(node, line_node, line_diff)
  sign = line_diff.positive? ? :+ : :-
  return if line_with_offset?(line_node, sign, line_diff.abs)

  add_offense_for_incorrect_line(node.method_name, line_node, sign, line_diff.abs)
end

#add_offense_for_incorrect_line(method_name, line_node, sign, line_diff) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/eval_with_location.rb', line 131

def add_offense_for_incorrect_line(method_name, line_node, sign, line_diff)
  expected = expected_line(sign, line_diff)
  message = format(MSG_INCORRECT_LINE,
                   method_name: method_name,
                   actual: line_node.source,
                   expected: expected)

  add_offense(line_node.source_range, message: message) do |corrector|
    corrector.replace(line_node, expected)
  end
end

#add_offense_for_missing_line(node, code) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/eval_with_location.rb', line 201

def add_offense_for_missing_line(node, code)
  register_offense(node) do |corrector|
    line_str = missing_line(node, code)
    corrector.insert_after(node.last_argument.source_range.end, ", #{line_str}")
  end
end

#add_offense_for_missing_location(node, code) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/eval_with_location.rb', line 208

def add_offense_for_missing_location(node, code)
  if node.method?(:eval) && !with_binding?(node)
    register_offense(node)
    return
  end

  register_offense(node) do |corrector|
    line_str = missing_line(node, code)
    corrector.insert_after(node.last_argument.source_range.end, ", __FILE__, #{line_str}")
  end
end

#add_offense_for_same_line(node, line_node) (private)

[ GitHub ]

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

def add_offense_for_same_line(node, line_node)
  return if special_line_keyword?(line_node)

  add_offense_for_incorrect_line(node.method_name, line_node, nil, 0)
end

#check_file(node, file_node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/eval_with_location.rb', line 143

def check_file(node, file_node)
  return if special_file_keyword?(file_node)

  message = format(MSG_INCORRECT_FILE,
                   method_name: node.method_name,
                   expected: '__FILE__',
                   actual: file_node.source)

  add_offense(file_node, message: message) do |corrector|
    corrector.replace(file_node, '__FILE__')
  end
end

#check_line(node, code) (private)

[ GitHub ]

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

def check_line(node, code)
  line_node = node.last_argument
  return if line_node.variable? || (line_node.send_type? && !line_node.method?(:+))

  line_diff = line_difference(line_node, code)
  if line_diff.zero?
    add_offense_for_same_line(node, line_node)
  else
    add_offense_for_different_line(node, line_node, line_diff)
  end
end

#check_location(node, code) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/eval_with_location.rb', line 95

def check_location(node, code)
  file, line = file_and_line(node)

  if line
    check_file(node, file)
    check_line(node, code)
  elsif file
    check_file(node, file)
    add_offense_for_missing_line(node, code)
  else
    add_offense_for_missing_location(node, code)
  end
end

#expected_line(sign, line_diff) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/eval_with_location.rb', line 193

def expected_line(sign, line_diff)
  if line_diff.zero?
    '__LINE__'
  else
    "__LINE__ #{sign} #{line_diff.abs}"
  end
end

#file_and_line(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/eval_with_location.rb', line 122

def file_and_line(node)
  base = node.method?(:eval) ? 2 : 1
  [node.arguments[base], node.arguments[base + 1]]
end

#line_difference(line_node, code) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/eval_with_location.rb', line 168

def line_difference(line_node, code)
  string_first_line(code) - line_node.source_range.first_line
end

#line_with_offset?(node, sign, num)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/eval_with_location.rb', line 74

def_node_matcher :line_with_offset?, <<~PATTERN
  {
    (send #special_line_keyword? %1 (int %2))
    (send (int %2) %1 #special_line_keyword?)
  }
PATTERN

#missing_line(node, code) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/eval_with_location.rb', line 220

def missing_line(node, code)
  line_diff = line_difference(node.last_argument, code)
  sign = line_diff.positive? ? :+ : :-
  expected_line(sign, line_diff)
end

#on_send(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/eval_with_location.rb', line 81

def on_send(node)
  # Classes should not redefine eval, but in case one does, it shouldn't
  # register an offense. Only `eval` without a receiver and `Kernel.eval`
  # are considered.
  return if node.method?(:eval) && !valid_eval_receiver?(node.receiver)

  code = node.first_argument
  return unless code && (code.str_type? || code.dstr_type?)

  check_location(node, code)
end

#register_offense(node, &block) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/eval_with_location.rb', line 109

def register_offense(node, &block)
  msg = node.method?(:eval) ? MSG_EVAL : format(MSG, method_name: node.method_name)
  add_offense(node, message: msg, &block)
end

#special_file_keyword?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/eval_with_location.rb', line 114

def special_file_keyword?(node)
  node.str_type? && node.source == '__FILE__'
end

#special_line_keyword?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/eval_with_location.rb', line 118

def special_line_keyword?(node)
  node.int_type? && node.source == '__LINE__'
end

#string_first_line(str_node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/eval_with_location.rb', line 172

def string_first_line(str_node)
  if str_node.heredoc?
    str_node.loc.heredoc_body.first_line
  else
    str_node.source_range.first_line
  end
end

#valid_eval_receiver?(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/eval_with_location.rb', line 69

def_node_matcher :valid_eval_receiver?, <<~PATTERN
  { nil? (const {nil? cbase} :Kernel) }
PATTERN

#with_binding?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/eval_with_location.rb', line 127

def with_binding?(node)
  node.method?(:eval) ? node.arguments.size >= 2 : true
end