# frozen_string_literal: truemoduleRuboCop# This class finds target files to inspect by scanning the directory tree and picking ruby files.# @api privateclassTargetFinderHIDDEN_PATH_SUBSTRING="#{File::SEPARATOR}."definitialize(config_store,options={})@config_store=config_store@options=optionsend# 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# @return [Array] array of file pathsdeffind(args,mode)returntarget_files_in_dirifargs.empty?files=[]args.uniq.eachdo|arg|files+=ifFile.directory?(arg)target_files_in_dir(arg.chomp(File::SEPARATOR))elseprocess_explicit_path(arg,mode)endendfiles.map{|f|File.expand_path(f)}.uniqend# 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.# @param base_dir Root directory under which to search for# ruby source files# @return [Array] Array of filenamesdeftarget_files_in_dir(base_dir=Dir.pwd)# Support Windows: Backslashes from command-line -> forward slashesbase_dir=base_dir.gsub(File::ALT_SEPARATOR,File::SEPARATOR)ifFile::ALT_SEPARATORall_files=find_files(base_dir,File::FNM_DOTMATCH)# use file.include? for performance optimizationhidden_files=all_files.select{|file|file.include?(HIDDEN_PATH_SUBSTRING)}.sortbase_dir_config=@config_store.for(base_dir)target_files=all_files.select{|file|to_inspect?(file,hidden_files,base_dir_config)}target_files.sort_by!(&order)end# 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/**/*).deffind_files(base_dir,flags)# get all wanted directories first to improve speed of finding all filesexclude_pattern=combined_exclude_glob_patterns(base_dir)dir_flags=flags|File::FNM_PATHNAME|File::FNM_EXTGLOBpatterns=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,'**/*')]ifpatterns.empty?Dir.glob(patterns,flags).select{|path|FileTest.file?(path)}endprivatedefto_inspect?(file,hidden_files,base_dir_config)returnfalseifbase_dir_config.file_to_exclude?(file)returntrueif!hidden_files.bsearchdo|hidden_file|file<=>hidden_fileend&&ruby_file?(file)base_dir_config.file_to_include?(file)enddefwanted_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}"enddirs=Dir.glob(File.join(base_dir,'*/'),flags).rejectdo|dir|nexttrueifdir.end_with?('/./','/../')nexttrueifFile.fnmatch?(exclude_pattern,dir,flags)symlink_excluded_or_infinite_loop?(base_dir,dir,exclude_pattern,flags)enddirs.flat_map{|dir|wanted_dir_patterns(dir,exclude_pattern,flags)}.unshift(base_dir)enddefsymlink_excluded_or_infinite_loop?(base_dir,current_dir,exclude_pattern,flags)dir_realpath=File.realpath(current_dir)File.symlink?(current_dir.chomp('/'))&&(File.fnmatch?(exclude_pattern,"#{dir_realpath}/",flags)||File.realpath(base_dir).start_with?(dir_realpath))enddefcombined_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(',')}}"enddefruby_filenames@ruby_filenames||=beginfile_patterns=all_cops_include.reject{|pattern|pattern.start_with?('**/*.')}file_patterns.map{|pattern|pattern.sub('**/','')}endenddefall_cops_include@all_cops_include||=@config_store.for_pwd.for_all_cops['Include'].map(&:to_s)enddefprocess_explicit_path(path,mode)files=path.include?('*')?Dir[path]:[path]ifmode==:only_recognized_file_types||force_exclusion?files.select!{|file|included_file?(file)}endforce_exclusion??without_excluded(files):filesenddefwithout_excluded(files)files.rejectdo|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)endenddefincluded_file?(file)ruby_file?(file)||configured_include?(file)enddefruby_file?(file)stdin?||ruby_extension?(file)||ruby_filename?(file)||ruby_executable?(file)enddefstdin?@options.key?(:stdin)enddefruby_extension?(file)ruby_extensions.include?(File.extname(file))enddefruby_extensions@ruby_extensions||=beginext_patterns=all_cops_include.select{|pattern|pattern.start_with?('**/*.')}ext_patterns.map{|pattern|pattern.sub('**/*','')}endenddefruby_filename?(file)ruby_filenames.include?(File.basename(file))enddefconfigured_include?(file)@config_store.for_pwd.file_to_include?(file)enddefruby_executable?(file)returnfalseunlessFile.extname(file).empty?&&File.exist?(file)first_line=File.open(file,&:readline)/#!.*(#{ruby_interpreters(file).join('|')})/.match?(first_line)rescueEOFError,ArgumentError=>ewarn("Unprocessable file #{file}: #{e.class}, #{e.message}")ifdebug?falseenddefruby_interpreters(file)@config_store.for(file).for_all_cops['RubyInterpreters']enddeforderiffail_fast?# Most recently modified file first.->(path){-Integer(File.mtime(path))}else:itselfendenddefforce_exclusion?@options[:force_exclusion]enddefignore_parent_exclusion?@options[:ignore_parent_exclusion]enddefdebug?@options[:debug]enddeffail_fast?@options[:fail_fast]endendend