class ERBLint::CLI

Constants

DEFAULT_CONFIG_FILENAME
DEFAULT_LINT_ALL_GLOB

Public Class Methods

new() click to toggle source
# File lib/erb_lint/cli.rb, line 23
def initialize
  @options = {}
  @config = nil
  @files = []
  @stats = Stats.new
end

Public Instance Methods

run(args = ARGV) click to toggle source
# File lib/erb_lint/cli.rb, line 30
def run(args = ARGV)
  dupped_args = args.dup
  load_options(dupped_args)
  @files = @options[:stdin] || dupped_args

  load_config

  if !@files.empty? && lint_files.empty?
    failure!("no files found...\n")
  elsif lint_files.empty?
    failure!("no files found or given, specify files or config...\n#{option_parser}")
  end

  ensure_files_exist(lint_files)

  if enabled_linter_classes.empty?
    failure!("no linter available with current configuration")
  end

  @options[:format] ||= :multiline
  @options[:fail_level] ||= severity_level_for_name(:refactor)
  @stats.files = lint_files.size
  @stats.linters = enabled_linter_classes.size

  reporter = Reporter.create_reporter(@options[:format], @stats, autocorrect?)
  reporter.preview

  runner = ERBLint::Runner.new(file_loader, @config)
  file_content = nil

  lint_files.each do |filename|
    runner.clear_offenses
    begin
      file_content = run_with_corrections(runner, filename)
    rescue => e
      @stats.exceptions += 1
      puts "Exception occurred when processing: #{relative_filename(filename)}"
      puts "If this file cannot be processed by erb-lint, "\
        "you can exclude it in your configuration file."
      puts e.message
      puts Rainbow(e.backtrace.join("\n")).red
      puts
    end
  end

  reporter.show

  if stdin? && autocorrect?
    # When running from stdin, we only lint a single file
    puts "================ #{lint_files.first} ==================\n"
    puts file_content
  end

  @stats.found == 0 && @stats.exceptions == 0
rescue OptionParser::InvalidOption, OptionParser::InvalidArgument, ExitWithFailure => e
  warn(Rainbow(e.message).red)
  false
rescue ExitWithSuccess => e
  puts e.message
  true
rescue => e
  warn(Rainbow("#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}").red)
  false
end

Private Instance Methods

autocorrect?() click to toggle source
# File lib/erb_lint/cli.rb, line 97
def autocorrect?
  @options[:autocorrect]
end
config_filename() click to toggle source
# File lib/erb_lint/cli.rb, line 152
def config_filename
  @config_filename ||= @options[:config] || DEFAULT_CONFIG_FILENAME
end
correct(processed_source, offenses) click to toggle source
# File lib/erb_lint/cli.rb, line 146
def correct(processed_source, offenses)
  corrector = ERBLint::Corrector.new(processed_source, offenses)
  failure!(corrector.diagnostics.join(", ")) if corrector.diagnostics.any?
  corrector
end
enabled_linter_classes() click to toggle source
# File lib/erb_lint/cli.rb, line 233
def enabled_linter_classes
  @enabled_linter_classes ||= ERBLint::LinterRegistry.linters
    .select { |klass| linter_can_run?(klass) && enabled_linter_names.include?(klass.simple_name.underscore) }
end
enabled_linter_names() click to toggle source
# File lib/erb_lint/cli.rb, line 226
def enabled_linter_names
  @enabled_linter_names ||=
    @options[:enabled_linters] ||
    known_linter_names
      .select { |name| @config.for_linter(name.camelize).enabled? }
end
ensure_files_exist(files) click to toggle source
# File lib/erb_lint/cli.rb, line 212
def ensure_files_exist(files)
  files.each do |filename|
    unless File.exist?(filename)
      failure!("#{filename}: does not exist")
    end
  end
end
excluded?(filename) click to toggle source
# File lib/erb_lint/cli.rb, line 197
def excluded?(filename)
  @config.global_exclude.any? do |path|
    expanded_path = File.expand_path(path, Dir.pwd)
    File.fnmatch?(expanded_path, filename)
  end
end
failure!(msg) click to toggle source
# File lib/erb_lint/cli.rb, line 204
def failure!(msg)
  raise ExitWithFailure, msg
end
file_loader() click to toggle source
# File lib/erb_lint/cli.rb, line 170
def file_loader
  @file_loader ||= ERBLint::FileLoader.new(Dir.pwd)
end
format_options_help() click to toggle source
# File lib/erb_lint/cli.rb, line 326
def format_options_help
  "Report offenses in the given format: "\
  "(#{Reporter.available_formats.join(", ")}) (default: multiline)"
end
glob() click to toggle source
# File lib/erb_lint/cli.rb, line 193
def glob
  @config.to_hash["glob"] || DEFAULT_LINT_ALL_GLOB
end
invalid_format_error_message(given_format) click to toggle source
# File lib/erb_lint/cli.rb, line 331
def invalid_format_error_message(given_format)
  formats = Reporter.available_formats.map { |format| "  - #{format}\n" }
  "#{given_format}: is not a valid format. Available formats:\n#{formats.join}"
end
known_linter_names() click to toggle source
# File lib/erb_lint/cli.rb, line 220
def known_linter_names
  @known_linter_names ||= ERBLint::LinterRegistry.linters
    .map(&:simple_name)
    .map(&:underscore)
end
lint_files() click to toggle source
# File lib/erb_lint/cli.rb, line 178
def lint_files
  @lint_files ||=
    if @options[:lint_all]
      pattern = File.expand_path(glob, Dir.pwd)
      Dir[pattern].select { |filename| !excluded?(filename) }
    else
      @files
        .map { |f| Dir.exist?(f) ? Dir[File.join(f, glob)] : f }
        .map { |f| f.include?("*") ? Dir[f] : f }
        .flatten
        .map { |f| File.expand_path(f, Dir.pwd) }
        .select { |filename| !excluded?(filename) }
    end
end
linter_can_run?(klass) click to toggle source
# File lib/erb_lint/cli.rb, line 238
def linter_can_run?(klass)
  !autocorrect? || klass.support_autocorrect?
end
load_config() click to toggle source
# File lib/erb_lint/cli.rb, line 156
def load_config
  if File.exist?(config_filename)
    config = RunnerConfig.new(file_loader.yaml(config_filename), file_loader)
    @config = RunnerConfig.default_for(config)
  else
    warn(Rainbow("#{config_filename} not found: using default config").yellow)
    @config = RunnerConfig.default
  end
rescue Psych::SyntaxError => e
  failure!("error parsing config: #{e.message}")
ensure
  @config.merge!(runner_config_override)
end
load_options(args) click to toggle source
# File lib/erb_lint/cli.rb, line 174
def load_options(args)
  option_parser.parse!(args)
end
option_parser() click to toggle source
# File lib/erb_lint/cli.rb, line 256
def option_parser
  OptionParser.new do |opts|
    opts.banner = "Usage: erblint [options] [file1, file2, ...]"

    opts.on("--config FILENAME", "Config file [default: #{DEFAULT_CONFIG_FILENAME}]") do |config|
      if File.exist?(config)
        @options[:config] = config
      else
        failure!("#{config}: does not exist")
      end
    end

    opts.on("--format FORMAT", format_options_help) do |format|
      unless Reporter.available_format?(format)
        error_message = invalid_format_error_message(format)
        failure!(error_message)
      end

      @options[:format] = format
    end

    opts.on("--lint-all", "Lint all files matching configured glob [default: #{DEFAULT_LINT_ALL_GLOB}]") do |config|
      @options[:lint_all] = config
    end

    opts.on("--enable-all-linters", "Enable all known linters") do
      @options[:enabled_linters] = known_linter_names
    end

    opts.on("--enable-linters LINTER[,LINTER,...]", Array,
      "Only use specified linter", "Known linters are: #{known_linter_names.join(", ")}") do |linters|
      linters.each do |linter|
        unless known_linter_names.include?(linter)
          failure!("#{linter}: not a valid linter name (#{known_linter_names.join(", ")})")
        end
      end
      @options[:enabled_linters] = linters
    end

    opts.on("--fail-level SEVERITY", "Minimum severity for exit with error code") do |level|
      parsed_severity = SEVERITY_CODE_TABLE[level.upcase.to_sym] || (SEVERITY_NAMES & [level.downcase]).first

      if parsed_severity.nil?
        failure!("#{level}: not a valid failure level (#{SEVERITY_NAMES.join(", ")})")
      end
      @options[:fail_level] = severity_level_for_name(parsed_severity)
    end

    opts.on("-a", "--autocorrect", "Correct offenses automatically if possible (default: false)") do |config|
      @options[:autocorrect] = config
    end

    opts.on(
      "-sFILE",
      "--stdin FILE",
      "Pipe source from STDIN. Takes the path to be used to check which rules to apply."
    ) do |file|
      @options[:stdin] = [file]
    end

    opts.on_tail("-h", "--help", "Show this message") do
      success!(opts)
    end

    opts.on_tail("--version", "Show version") do
      success!(ERBLint::VERSION)
    end
  end
end
read_content(filename) click to toggle source
# File lib/erb_lint/cli.rb, line 140
def read_content(filename)
  return File.read(filename, encoding: Encoding::UTF_8) unless stdin?

  $stdin.binmode.read.force_encoding(Encoding::UTF_8)
end
relative_filename(filename) click to toggle source
# File lib/erb_lint/cli.rb, line 242
def relative_filename(filename)
  filename.sub("#{File.expand_path(".", Dir.pwd)}/", "")
end
run_with_corrections(runner, filename) click to toggle source
# File lib/erb_lint/cli.rb, line 101
def run_with_corrections(runner, filename)
  file_content = read_content(filename)

  7.times do
    processed_source = ERBLint::ProcessedSource.new(filename, file_content)
    runner.run(processed_source)
    break unless autocorrect? && runner.offenses.any?

    corrector = correct(processed_source, runner.offenses)
    break if corrector.corrections.empty?
    break if processed_source.file_content == corrector.corrected_content

    @stats.corrected += corrector.corrections.size

    # Don't overwrite the file if the input comes from stdin
    unless stdin?
      File.open(filename, "wb") do |file|
        file.write(corrector.corrected_content)
      end
    end

    file_content = corrector.corrected_content
    runner.clear_offenses
  end
  offenses_filename = relative_filename(filename)
  offenses = runner.offenses || []

  @stats.ignored, @stats.found = offenses.partition do |offense|
    severity_level_for_name(offense.severity) < @options[:fail_level]
  end.map(&:size)
    .zip([@stats.ignored, @stats.found])
    .map(&:sum)

  @stats.processed_files[offenses_filename] ||= []
  @stats.processed_files[offenses_filename] |= offenses

  file_content
end
runner_config_override() click to toggle source
# File lib/erb_lint/cli.rb, line 246
def runner_config_override
  RunnerConfig.new(
    linters: {}.tap do |linters|
      ERBLint::LinterRegistry.linters.map do |klass|
        linters[klass.simple_name] = { "enabled" => enabled_linter_classes.include?(klass) }
      end
    end
  )
end
stdin?() click to toggle source
# File lib/erb_lint/cli.rb, line 336
def stdin?
  @options[:stdin].present?
end
success!(msg) click to toggle source
# File lib/erb_lint/cli.rb, line 208
def success!(msg)
  raise ExitWithSuccess, msg
end