Class: RuboCop::Cop::Lint::NonAtomicFileOperation

Inherits:
Base
  • Object
show all
Extended by:
AutoCorrector
Includes:
Alignment
Defined in:
lib/rubocop/cop/lint/non_atomic_file_operation.rb

Overview

Checks for non-atomic file operation. And then replace it with a nearly equivalent and atomic method.

These can cause problems that are difficult to reproduce, especially in cases of frequent file operations in parallel, such as test runs with parallel_rspec.

For examples: creating a directory if there is none, has the following problems

An exception occurs when the directory didn’t exist at the time of exist?, but someone else created it before mkdir was executed.

Subsequent processes are executed without the directory that should be there when the directory existed at the time of exist?, but someone else deleted it shortly afterwards.

Examples:

# bad - race condition with another process may result in an error in `mkdir`
unless Dir.exist?(path)
  FileUtils.mkdir(path)
end

# good - atomic and idempotent creation
FileUtils.mkdir_p(path)

# bad - race condition with another process may result in an error in `remove`
if File.exist?(path)
  FileUtils.remove(path)
end

# good - atomic and idempotent removal
FileUtils.rm_f(path)

Constant Summary collapse

MSG_REMOVE_FILE_EXIST_CHECK =
'Remove unnecessary existence check ' \
'`%<receiver>s.%<method_name>s`.'
MSG_CHANGE_FORCE_METHOD =
'Use atomic file operation method `FileUtils.%<method_name>s`.'
MAKE_FORCE_METHODS =
%i[makedirs mkdir_p mkpath].freeze
MAKE_METHODS =
%i[mkdir].freeze
REMOVE_FORCE_METHODS =
%i[rm_f rm_rf].freeze
REMOVE_METHODS =
%i[remove remove_dir remove_entry remove_entry_secure
delete unlink remove_file rm rmdir safe_unlink].freeze
RESTRICT_ON_SEND =
(MAKE_METHODS + MAKE_FORCE_METHODS + REMOVE_METHODS +
REMOVE_FORCE_METHODS).freeze

Instance Attribute Summary

Attributes inherited from Base

#config, #processed_source

Instance Method Summary collapse

Methods included from AutoCorrector

support_autocorrect?

Methods inherited from Base

#active_support_extensions_enabled?, #add_global_offense, #add_offense, autocorrect_incompatible_with, badge, #begin_investigation, callbacks_needed, #callbacks_needed, #config_to_allow_offenses, #config_to_allow_offenses=, #cop_config, cop_name, #cop_name, department, documentation_url, exclude_from_registry, #excluded_file?, #external_dependency_checksum, inherited, #initialize, #inspect, joining_forces, lint?, match?, #message, #offenses, #on_investigation_end, #on_new_investigation, #on_other_file, #parse, #ready, #relevant_file?, support_autocorrect?, support_multiple_source?, #target_rails_version, #target_ruby_version

Methods included from ExcludeLimit

#exclude_limit

Methods included from AutocorrectLogic

#autocorrect?, #autocorrect_enabled?, #autocorrect_requested?, #autocorrect_with_disable_uncorrectable?, #correctable?, #disable_uncorrectable?, #safe_autocorrect?

Methods included from IgnoredNode

#ignore_node, #ignored_node?, #part_of_ignored_node?

Methods included from Util

silence_warnings

Constructor Details

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

Instance Method Details

#explicit_not_force?(node) ⇒ Object



75
76
77
# File 'lib/rubocop/cop/lint/non_atomic_file_operation.rb', line 75

def_node_search :explicit_not_force?, <<~PATTERN
  (pair (sym :force) (:false))
PATTERN

#force?(node) ⇒ Object



70
71
72
# File 'lib/rubocop/cop/lint/non_atomic_file_operation.rb', line 70

def_node_search :force?, <<~PATTERN
  (pair (sym :force) (:true))
PATTERN

#on_send(node) ⇒ Object



79
80
81
82
83
84
85
86
# File 'lib/rubocop/cop/lint/non_atomic_file_operation.rb', line 79

def on_send(node)
  return unless if_node_child?(node)
  return if explicit_not_force?(node)
  return unless (exist_node = send_exist_node(node.parent).first)
  return unless exist_node.first_argument == node.first_argument

  register_offense(node, exist_node)
end

#receiver_and_method_name(node) ⇒ Object



65
66
67
# File 'lib/rubocop/cop/lint/non_atomic_file_operation.rb', line 65

def_node_matcher :receiver_and_method_name, <<-PATTERN
  (send (const nil? $_) $_ ...)
PATTERN

#send_exist_node(node) ⇒ Object



60
61
62
# File 'lib/rubocop/cop/lint/non_atomic_file_operation.rb', line 60

def_node_search :send_exist_node, <<-PATTERN
  $(send (const nil? {:FileTest :File :Dir :Shell}) {:exist? :exists?} ...)
PATTERN