require 'pathname' require 'colorize' require 'English' require 'pry' require_relative 'analyzer' require_relative 'config' module Fasterer class FileTraverser CONFIG_FILE_NAME = Config::FILE_NAME SPEEDUPS_KEY = Config::SPEEDUPS_KEY EXCLUDE_PATHS_KEY = Config::EXCLUDE_PATHS_KEY attr_reader :config attr_reader :parse_error_paths attr_accessor :offenses_total_count def initialize(path) @path = Pathname(path || '.') @parse_error_paths = [] @config = Config.new @offenses_total_count = 0 end def traverse traverse_files output_parse_errors output_statistics end def config_file config.file end def offenses_found? !!offenses_found end def scannable_files all_files - ignored_files end private attr_accessor :offenses_found def traverse_files if @path.exist? scannable_files.each { |ruby_file| scan_file(ruby_file) } else output_unable_to_find_file(@path) end end def scan_file(path) analyzer = Analyzer.new(path) analyzer.scan rescue RubyParser::SyntaxError, Racc::ParseError, Timeout::Error => e parse_error_paths.push(ErrorData.new(path, e.class, e.message).to_s) else if offenses_grouped_by_type(analyzer).any? output(analyzer) self.offenses_found = true self.offenses_total_count += analyzer.errors.count end end def all_files if @path.directory? Dir[File.join(@path, '**', '*.rb')].map do |ruby_file_path| Pathname(ruby_file_path).relative_path_from(root_dir).to_s end else [@path.to_s] end end def root_dir @root_dir ||= Pathname('.') end def output(analyzer) puts analyzer.file_path.colorize(:red) offenses_grouped_by_type(analyzer).each do |error_group_name, error_occurences| puts "#{Fasterer::Offense::EXPLANATIONS[error_group_name]}."\ " Occurred at lines: #{error_occurences.map(&:line_number).join(', ')}." end puts end def offenses_grouped_by_type(analyzer) analyzer.errors.group_by(&:name).delete_if do |offense_name, _| ignored_speedups.include?(offense_name) end end def output_parse_errors return if parse_error_paths.none? puts 'Fasterer was unable to process some files because the' puts 'internal parser is not able to read some characters or' puts 'has timed out. Unprocessable files were:' puts '-----------------------------------------------------' puts parse_error_paths puts end def output_statistics puts Statistics.new(self) end def output_unable_to_find_file(path) puts "No such file or directory - #{path}".colorize(:red) end def ignored_speedups config.ignored_speedups end def ignored_files config.ignored_files end def nil_config_file config.nil_file end end ErrorData = Struct.new(:file_path, :error_class, :error_message) do def to_s "#{file_path} - #{error_class} - #{error_message}" end end class Statistics def initialize(traverser) @files_inspected_count = traverser.scannable_files.count @offenses_found_count = traverser.offenses_total_count @unparsable_files_count = traverser.parse_error_paths.count end def to_s [ inspected_files_output, offenses_found_output, unparsable_files_output ].compact.join(', ') end def inspected_files_output "#{@files_inspected_count} #{pluralize(@files_inspected_count, 'file')} inspected" .colorize(:green) end def offenses_found_output "#{@offenses_found_count} #{pluralize(@offenses_found_count, 'offense')} detected" .colorize(:red) end def unparsable_files_output return if @unparsable_files_count.zero? "#{@unparsable_files_count} unparsable #{pluralize(@unparsable_files_count, 'file')} found" .colorize(:red) end def pluralize(n, singular, plural = nil) if n == 1 "#{singular}" elsif plural "#{plural}" else "#{singular}s" end end end end