Newer
Older
Gautham Goli
committed
#: * `style` [`--fix`] [`--display-cop-names`] [`--only-cops=`[COP1,COP2..]|`--except-cops=`[COP1,COP2..]] [<files>|<taps>|<formulae>]:
#: Check formulae or files for conformance to Homebrew style guidelines.
#:
#: <formulae> and <files> 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, 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.
#:
Gautham Goli
committed
#: If `--only-cops` is passed, only the given Rubocop cop(s)' violations would be checked.
#:
#: If `--except-cops` is passed, the given Rubocop cop(s)' checks would be skipped.
#:
#: Exits with a non-zero status if any style violations are found.
require "utils"
def style
target = if ARGV.named.empty?
elsif ARGV.named.any? { |file| File.exist? file }
ARGV.named
elsif ARGV.named.any? { |tap| tap.count("/") == 1 }
ARGV.named.map { |tap| Tap.fetch(tap).path }
else
ARGV.formulae.map(&:path)
end
Gautham Goli
committed
only_cops = ARGV.value("only-cops").to_s.split(",")
except_cops = ARGV.value("except-cops").to_s.split(",")
if !only_cops.empty? && !except_cops.empty?
odie "--only-cops and --except-cops cannot be used simultaneously!"
end
options = { fix: ARGV.flag?("--fix") }
if !only_cops.empty?
options[:only_cops] = only_cops
elsif !except_cops.empty?
options[:except_cops] = except_cops
end
Homebrew.failed = check_style_and_print(target, options)
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", HOMEBREW_RUBOCOP_VERSION
require "rubocop"
require_relative "../rubocops"
if fix
args << "--auto-correct"
else
args << "--parallel"
end
Gautham Goli
committed
if options[:except_cops]
options[:except_cops].map! { |cop| RuboCop::Cop::Cop.registry.qualified_cop_name(cop.to_s, "") }
Gautham Goli
committed
cops_to_exclude = options[:except_cops].select do |cop|
RuboCop::Cop::Cop.registry.names.include?(cop) ||
RuboCop::Cop::Cop.registry.departments.include?(cop.to_sym)
end
Gautham Goli
committed
args << "--except" << cops_to_exclude.join(",") unless cops_to_exclude.empty?
elsif options[:only_cops]
options[:only_cops].map! { |cop| RuboCop::Cop::Cop.registry.qualified_cop_name(cop.to_s, "") }
Gautham Goli
committed
cops_to_include = options[:only_cops].select do |cop|
RuboCop::Cop::Cop.registry.names.include?(cop) ||
RuboCop::Cop::Cop.registry.departments.include?(cop.to_sym)
end
Gautham Goli
committed
args << "--only" << cops_to_include.join(",") unless cops_to_include.empty?
args << "--config" << HOMEBREW_LIBRARY_PATH/".rubocop.yml"
args << "--config" << HOMEBREW_LIBRARY/".rubocop.yml"
args += files
end
cache_env = { "XDG_CACHE_HOME" => "#{HOMEBREW_CACHE}/style" }
case output_type
when :print
args << "--display-cop-names" if ARGV.include? "--display-cop-names"
!$?.success?
when :json
json, _, status = Open3.capture3(cache_env, "rubocop", "--format", "json", *args)
# exit status of 1 just means violations were found; other numbers mean
# execution errors.
# exitstatus can also be nil if RuboCop process crashes, e.g. due to
# native extension problems.
# JSON needs to be at least 2 characters.
if !status.success? || json.to_s.length < 2
raise "Error running `rubocop --format json #{args.join " "}`"
end
RubocopResults.new(JSON.parse(json))
else
raise "Invalid output_type for check_style_impl: #{output_type}"
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
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