#: * `style` [`--fix`] [`--display-cop-names`] [|]: #: Check formulae or files for conformance to Homebrew style guidelines. #: #: is a list of formula names. #: #: is a list of file names. #: #: and may not be combined. If both are omitted, style will run #: style checks on the whole Homebrew `Library`, including core code and all #: formulae. #: #: If `--fix` is passed and `HOMEBREW_DEVELOPER` is set, style violations #: will be automatically fixed using RuboCop's `--auto-correct` feature. #: #: If `--display-cop-names` is passed, the RuboCop cop name for each violation #: is included in the output. #: #: Exits with a non-zero status if any style violations are found. require "utils" require "utils/json" module Homebrew def style target = if ARGV.named.empty? [HOMEBREW_LIBRARY] elsif ARGV.named.any? { |file| File.exist? file } ARGV.named else ARGV.formulae.map(&:path) end Homebrew.failed = check_style_and_print(target, :fix => ARGV.flag?("--fix")) end # Checks style for a list of files, printing simple RuboCop output. # Returns true if violations were found, false otherwise. def check_style_and_print(files, options = {}) check_style_impl(files, :print, options) end # Checks style for a list of files, returning results as a RubocopResults # object parsed from its JSON output. def check_style_json(files, options = {}) check_style_impl(files, :json, options) end def check_style_impl(files, output_type, options = {}) fix = options[:fix] Homebrew.install_gem_setup_path! "rubocop", "0.39" args = %W[ --force-exclusion --config #{HOMEBREW_LIBRARY}/.rubocop.yml ] args << "--auto-correct" if ARGV.homebrew_developer? && fix args += files case output_type when :print args << "--display-cop-names" if ARGV.include? "--display-cop-names" system "rubocop", "--format", "simple", *args !$?.success? when :json json = Utils.popen_read_text("rubocop", "--format", "json", *args) # exit status of 1 just means violations were found; others are errors raise "Error while running rubocop" if $?.exitstatus > 1 RubocopResults.new(Utils::JSON.load(json)) else raise "Invalid output_type for check_style_impl: #{output_type}" end end class RubocopResults def initialize(json) @metadata = json["metadata"] @file_offenses = {} json["files"].each do |f| next if f["offenses"].empty? file = File.realpath(f["path"]) @file_offenses[file] = f["offenses"].map { |x| RubocopOffense.new(x) } end end def file_offenses(path) @file_offenses[path.to_s] end end class RubocopOffense attr_reader :severity, :message, :corrected, :location, :cop_name def initialize(json) @severity = json["severity"] @message = json["message"] @cop_name = json["cop_name"] @corrected = json["corrected"] @location = RubocopLineLocation.new(json["location"]) end def severity_code @severity[0].upcase end def to_s(options = {}) if options[:display_cop_name] "#{severity_code}: #{location.to_short_s}: #{cop_name}: #{message}" else "#{severity_code}: #{location.to_short_s}: #{message}" end end end class RubocopLineLocation attr_reader :line, :column, :length def initialize(json) @line = json["line"] @column = json["column"] @length = json["length"] end def to_s "#{line}: col #{column} (#{length} chars)" end def to_short_s "#{line}: col #{column}" end end end