123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::TargetFinder Private

Relationships & Source Files
Inherits: Object
Defined in: lib/rubocop/target_finder.rb

Overview

This class finds target files to inspect by scanning the directory tree and picking ruby files.

Constant Summary

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(config_store, options = {}) ⇒ TargetFinder

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 9

def initialize(config_store, options = {})
  @config_store = config_store
  @options = options
end

Instance Attribute Details

#debug?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 223

def debug?
  @options[:debug]
end

#fail_fast?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 227

def fail_fast?
  @options[:fail_fast]
end

#force_exclusion?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 215

def force_exclusion?
  @options[:force_exclusion]
end

#ignore_parent_exclusion?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 219

def ignore_parent_exclusion?
  @options[:ignore_parent_exclusion]
end

#stdin?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 168

def stdin?
  @options.key?(:stdin)
end

Instance Method Details

#all_cops_include (private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 134

def all_cops_include
  @all_cops_include ||= @config_store.for_pwd.for_all_cops['Include'].map(&:to_s)
end

#combined_exclude_glob_patterns(base_dir) (private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 120

def combined_exclude_glob_patterns(base_dir)
  exclude = @config_store.for(base_dir).for_all_cops['Exclude'] || []
  patterns = exclude.select { |pattern| pattern.is_a?(String) && pattern.end_with?('/**/*') }
                    .map { |pattern| pattern.sub("#{base_dir}/", '') }
  "#{base_dir}/{#{patterns.join(',')}}"
end

#configured_include?(file) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 187

def configured_include?(file)
  @config_store.for_pwd.file_to_include?(file)
end

#find(args, mode) ⇒ Array

Generate a list of target files by expanding globbing patterns (if any). If args is empty, recursively find all Ruby source files under the current directory

Returns:

  • (Array)

    array of file paths

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 17

def find(args, mode)
  return target_files_in_dir if args.empty?

  files = []

  args.uniq.each do |arg|
    files += if File.directory?(arg)
               # Expand the directory so that the traversal can prune directories the
               # config excludes - the exclude patterns are absolute paths, and glued
               # onto a relative base (`rubocop .`) they never match anything.
               target_files_in_dir(File.expand_path(arg.chomp(File::SEPARATOR)))
             else
               process_explicit_path(arg, mode)
             end
  end

  files.map { |f| File.expand_path(f) }.uniq
end

#find_files(base_dir, flags)

Search for files recursively starting at the given base directory using the given flags that determine how the match is made. Excluded files will be removed later by the caller, but as an optimization find_files removes the top level directories that are excluded in configuration in the normal way (dir/*/).

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 63

def find_files(base_dir, flags)
  # get all wanted directories first to improve speed of finding all files
  exclude_pattern = combined_exclude_glob_patterns(base_dir)
  dir_flags = flags | File::FNM_PATHNAME | File::FNM_EXTGLOB
  patterns = wanted_dir_patterns(base_dir, exclude_pattern, dir_flags)
  patterns.map! { |dir| File.join(dir, '*') }
  # We need this special case to avoid creating the pattern
  # /**/* which searches the entire file system.
  patterns = [File.join(base_dir, '**/*')] if patterns.empty?

  Dir.glob(patterns, flags).select { |path| FileTest.file?(path) }
end

#hidden_dir?(dir) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 85

def hidden_dir?(dir)
  basename = File.basename(dir)
  basename.start_with?('.') && basename != '.' && basename != '..'
end

#hidden_file_in_dir?(file, base_dir) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 90

def hidden_file_in_dir?(file, base_dir)
  base_dir = "#{base_dir}#{File::SEPARATOR}" unless base_dir.end_with?(File::SEPARATOR)
  relative = file.delete_prefix(base_dir)
  relative.start_with?('.') || relative.include?(HIDDEN_PATH_SUBSTRING)
end

#included_file?(file) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 160

def included_file?(file)
  ruby_file?(file) || configured_include?(file)
end

#order (private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 206

def order
  if fail_fast?
    # Most recently modified file first.
    ->(path) { -Integer(File.mtime(path)) }
  else
    :itself
  end
end

#process_explicit_path(path, mode) (private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 138

def process_explicit_path(path, mode)
  files = path.include?('*') ? Dir[path] : [path]

  if mode == :only_recognized_file_types || force_exclusion?
    files.select! { |file| included_file?(file) }
  end
  files.reject! { |file| FileTest.directory?(file) }

  force_exclusion? ? without_excluded(files) : files
end

#ruby_executable?(file) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 191

def ruby_executable?(file)
  return false if !File.extname(file).empty? || !File.exist?(file) || File.empty?(file)

  first_line = File.open(file, &:readline)
  /#!.*(#{ruby_interpreters(file).join('|')})/.match?(first_line)
rescue EOFError, ArgumentError => e
  warn("Unprocessable file #{file}: #{e.class}, #{e.message}") if debug?

  false
end

#ruby_extension?(file) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 172

def ruby_extension?(file)
  ruby_extensions.include?(File.extname(file))
end

#ruby_extensions (private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 176

def ruby_extensions
  @ruby_extensions ||= begin
    ext_patterns = all_cops_include.select { |pattern| pattern.start_with?('**/*.') }
    ext_patterns.map { |pattern| pattern.sub('**/*', '') }
  end
end

#ruby_file?(file) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 164

def ruby_file?(file)
  stdin? || ruby_extension?(file) || ruby_filename?(file) || ruby_executable?(file)
end

#ruby_filename?(file) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 183

def ruby_filename?(file)
  ruby_filenames.include?(File.basename(file))
end

#ruby_filenames (private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 127

def ruby_filenames
  @ruby_filenames ||= begin
    file_patterns = all_cops_include.reject { |pattern| pattern.start_with?('**/*.') }
    file_patterns.map { |pattern| pattern.sub('**/', '') }
  end
end

#ruby_interpreters(file) (private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 202

def ruby_interpreters(file)
  @config_store.for(file).for_all_cops['RubyInterpreters']
end

#target_files_in_dir(base_dir = PathUtil.pwd) ⇒ Array

Finds all Ruby source files under the current or other supplied directory. A Ruby source file is defined as a file with the .rb extension or a file with no extension that has a ruby shebang line as its first line. It is possible to specify includes and excludes using the config file, so you can include other Ruby files like Rakefiles and gemspecs.

Parameters:

  • base_dir (defaults to: PathUtil.pwd)

    Root directory under which to search for ruby source files

Returns:

  • (Array)

    Array of filenames

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 44

def target_files_in_dir(base_dir = PathUtil.pwd)
  # Support Windows: Backslashes from command-line -> forward slashes
  base_dir = base_dir.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
  all_files = find_files(base_dir, File::FNM_DOTMATCH)
  base_dir_config = @config_store.for(base_dir)

  target_files = if hidden_dir?(base_dir)
                   all_files.select { |file| ruby_file?(file) }
                 else
                   all_files.select { |file| to_inspect?(file, base_dir, base_dir_config) }
                 end

  target_files.sort_by!(&order)
end

#to_inspect?(file, base_dir, base_dir_config) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 78

def to_inspect?(file, base_dir, base_dir_config)
  return false if base_dir_config.file_to_exclude?(file)
  return true if !hidden_file_in_dir?(file, base_dir) && ruby_file?(file)

  base_dir_config.file_to_include?(file)
end

#wanted_dir_patterns(base_dir, exclude_pattern, flags) (private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 96

def wanted_dir_patterns(base_dir, exclude_pattern, flags)
  # Escape glob characters in base_dir to avoid unwanted behavior.
  base_dir = base_dir.gsub(/[\\{}\[\]*?]/) do |reserved_glob_character|
    "\\#{reserved_glob_character}"
  end

  dirs = Dir.glob(File.join(base_dir, '*/'), flags)
            .reject do |dir|
              next true if dir.end_with?('/./', '/../')
              next true if File.fnmatch?(exclude_pattern, dir, flags)

              symlink_excluded_or_infinite_loop?(base_dir, dir, exclude_pattern, flags)
            end
  dirs.flat_map { |dir| wanted_dir_patterns(dir, exclude_pattern, flags) }.unshift(base_dir)
end

#without_excluded(files) (private)

[ GitHub ]

  
# File 'lib/rubocop/target_finder.rb', line 149

def without_excluded(files)
  files.reject do |file|
    # When --ignore-parent-exclusion is given, we must look at the configuration associated with
    # the file, but in the default case when --ignore-parent-exclusion is not given, can safely
    # look only at the configuration for the current directory, since it's only the Exclude
    # parameters we're going to check.
    config = @config_store.for(ignore_parent_exclusion? ? file : '.')
    config.file_to_exclude?(file)
  end
end