class RuboCop::Cop::Require::MissingRequireStatement

Checks for missing require statements in your code

@example

# bad
Faraday.new

# good
require 'faraday'

Faraday.new

Constants

MSG

Attributes

timeline[W]

Public Instance Methods

add_offense(node, location: nil, message:) click to toggle source
Calls superclass method
# File lib/rubocop/cop/require/missing_require_statement.rb, line 66
def add_offense(node, location: nil, message:)
  # Work around breaking API changes between rubocop 0.49.1 and later (...)
  signature_old = %i[node loc message severity]
  param_info = RuboCop::Cop::Cop.instance_method(:add_offense).parameters
  if param_info.map(&:last) == signature_old
    super(node, location || :expression, message)
  elsif location
    super(node, location: location, message: message)
  else
    super(node, message: message)
  end
end
find_consts(node) click to toggle source
# File lib/rubocop/cop/require/missing_require_statement.rb, line 87
def find_consts(node)
  inner = node
  outer_const = extract_const(node)
  return unless outer_const
  consts = [outer_const]
  while (inner = extract_inner_const(inner))
    const = extract_const(inner)
    consts << const
  end
  consts.reverse
end
investigate(processed_source) click to toggle source

Builds

# File lib/rubocop/cop/require/missing_require_statement.rb, line 29
def investigate(processed_source)
  processing_methods = self.methods.select { |m| m.to_s.start_with? 'process_' }

  stack = [processed_source.ast]
  skip = Set.new
  until stack.empty?
    node = stack.pop
    next unless node

    results = processing_methods.map { |m| self.send(m, node, processed_source) }.compact

    next if node.kind_of? Hash

    to_skip, to_push = %i[skip push].map { |mode| results.flat_map { |r| r[mode] }.compact }

    skip.merge(to_skip)

    children_to_explore = node.children
                              .select { |c| c.kind_of? RuboCop::AST::Node }
                              .reject { |c| skip.include? c }
                              .reverse
    stack.push(*to_push)
    stack.push(*children_to_explore)
  end

  err_events = check_timeline(timeline).group_by { |e| e[:name] }.values
  err_events.each do |events|
    first = events.first
    node = first[:node]
    message = format(
      MSG,
      constant: first[:name]
    )
    add_offense(node, message: message)
  end
end
process_const(node, _source) click to toggle source
# File lib/rubocop/cop/require/missing_require_statement.rb, line 99
def process_const(node, _source)
  return unless node.kind_of? RuboCop::AST::Node
  consts = find_consts(node)
  return unless consts
  const_name = consts.join('::')

  self.timeline << { event: :const_access, name: const_name, node: node }

  { skip: node.children }
end
process_const_assign(node, _source) click to toggle source
# File lib/rubocop/cop/require/missing_require_statement.rb, line 114
def process_const_assign(node, _source)
  return unless node.kind_of? RuboCop::AST::Node
  const_assign_name = extract_const_assignment(node)
  return unless const_assign_name

  self.timeline << { event: :const_assign, name: const_assign_name }

  { skip: node.children }
end
process_definition(node, _source) click to toggle source
# File lib/rubocop/cop/require/missing_require_statement.rb, line 132
def process_definition(node, _source)
  if node.kind_of? Hash
    self.timeline << node
    return
  end

  return unless is_module_or_class?(node)
  name = find_consts(node.children.first).join('::')
  inherited = find_consts(node.children[1]).join('::') if has_superclass?(node)

  # Inheritance technically has to happen before the actual class definition
  self.timeline << { event: :const_inherit, name: inherited, node: node } if inherited

  self.timeline << { event: :const_def, name: name }

  # First child is the module/class name => skip or it'll be picked up by `process_const`
  skip_list = [node.children.first]
  skip_list << node.children[1] if inherited

  push_list = []
  push_list << { event: :const_undef, name: name }

  { skip: skip_list, push: push_list }
end
process_require(node, source) click to toggle source
# File lib/rubocop/cop/require/missing_require_statement.rb, line 161
def process_require(node, source)
  return unless node.kind_of? RuboCop::AST::Node
  required = extract_require(node)
  return unless required && required.length == 2
  method, file = required
  self.timeline << { event: method, file: file, path: source.path }

  { skip: node.children }
end
timeline() click to toggle source
# File lib/rubocop/cop/require/missing_require_statement.rb, line 24
def timeline
  @timeline ||= []
end

Private Instance Methods

check_timeline(timeline) click to toggle source

Returns the problematic events from the timeline, i.e. those for which a require might be missing

# File lib/rubocop/cop/require/missing_require_statement.rb, line 174
def check_timeline(timeline)
  return [] unless Process.respond_to?(:fork)

  # To avoid having to marshal/unmarshal the nodes, the fork will just return indices with an error
  err_indices = perform_in_fork do
    state = RuboCop::RequireTools::State.new
    err_indices = []
    timeline.each_with_index do |event, i|
      case event[:event]
      when :require
        state.require(file: event[:file])
      when :require_relative
        path_to_investigated_file = event[:path]
        relative_path = File.expand_path(File.join(File.dirname(path_to_investigated_file), event[:file]))
        state.require_relative(relative_path: relative_path)
      when :const_access
        err_indices << i unless state.access_const(const_name: event[:name])
      when :const_def
        state.define_const(const_name: event[:name])

        outdated = outdated_errors(err_indices.map { |e| timeline[e] }, state)
        err_indices = err_indices.reject { |e| outdated.include?(timeline[e]) }
      when :const_undef
        state.undefine_const(const_name: event[:name])
      when :const_assign
        state.const_assigned(const_name: event[:name])

        previous_errors = err_indices.map { |e| timeline[e] }
        outdated = outdated_errors(previous_errors, state)
        err_indices = err_indices.reject { |e| outdated.include?(timeline[e]) }
      when :const_inherit
        success = state.access_const(const_name: event[:name])
        if success
          state.define_const(const_name: event[:name], is_part_of_stack: false)
        else
          err_indices << i
        end
      end
    end
    err_indices
  end

  err_indices.map { |i| timeline[i] }
end
outdated_errors(error_events, state) click to toggle source
# File lib/rubocop/cop/require/missing_require_statement.rb, line 219
def outdated_errors(error_events, state)
  error_events
    .select { |e| %i[const_access const_inherit].include? e[:event] } # Only these types can be resolved by definitions later in the file
    .select { |e| state.access_const(const_name: e[:name], local_only: true) }
end
perform_in_fork() { || ... } click to toggle source
# File lib/rubocop/cop/require/missing_require_statement.rb, line 225
def perform_in_fork
  r, w = IO.pipe

  # The close statements are as they are used in the IO#pipe documentation
  pid = Process.fork do
    r.close
    result = yield
    Marshal.dump(result, w)
    w.close
  end

  w.close
  result = Marshal.load(r)
  r.close
  _, status = Process.waitpid2(pid)

  raise 'An error occured while forking' unless status.to_i.zero?

  return result
end