lib/cc/engine/analyzers/reporter.rb
# frozen_string_literal: true
require "cc/engine/analyzers/violations"
require "cc/engine/analyzers/file_thread_pool"
require "thread"
require "concurrent"
require "ccflay"
module CC
module Engine
module Analyzers
class Reporter
def initialize(engine_config, language_strategy, io)
@engine_config = engine_config
@language_strategy = language_strategy
@io = io
@reports = Set.new
end
def run
CC.logger.debug("Processing #{language_strategy.files.count} #{lang} files concurrency=#{engine_config.concurrency}")
process_files
if engine_config.dump_ast?
dump_ast
else
report
CC.logger.debug("Reported #{reports.size} violations...")
end
end
def dump_ast
require "pp"
issues = flay.analyze
return if issues.empty?
CC.logger.debug("Sexps for issues:")
issues.each_with_index do |issue, idx1|
CC.logger.debug(
format(
"#%2d) %s#%d mass=%d:",
idx1 + 1,
issue.name,
issue.structural_hash,
issue.mass,
),
)
locs = issue.locations.map.with_index do |loc, idx2|
format("# %d.%d) %s:%s", idx1 + 1, idx2 + 1, loc.file, loc.line)
end
locs.zip(flay.hashes[issue.structural_hash]).each do |loc, sexp|
CC.logger.debug(loc)
CC.logger.debug(sexp.pretty_inspect)
end
end
end
def process_files
pool = FileThreadPool.new(
language_strategy.files,
concurrency: engine_config.concurrency,
)
processed_files_count = Concurrent::AtomicFixnum.new
pool.run do |file|
begin
CC.logger.debug("Processing #{lang} file: #{file}")
sexp = language_strategy.run(file)
process_sexp(sexp)
processed_files_count.increment
rescue Exception => ex
CC.logger.warn("Error processing file: #{file}")
raise ex
end
end
pool.join
CC.logger.debug("Processed #{processed_files_count.value} #{lang} files")
end
def lang
CC::Engine::Duplication::LANGUAGES.invert[language_strategy.class]
end
def report
flay.analyze.each do |issue|
violations = new_violations(issue)
violations.each do |violation|
next if skip?(violation)
CC.logger.debug("Violation name=#{violation.report_name} mass=#{violation.mass}")
unless reports.include?(violation.report_name)
reports.add(violation.report_name)
io.puts "#{violation.format.to_json}\0"
end
end
end
end
def process_sexp(sexp)
return unless sexp
flay.process_sexp(language_strategy.transform_sexp(sexp))
end
private
attr_reader :reports
def flay
@flay ||= CCFlay.new(flay_options)
end
attr_reader :engine_config, :language_strategy, :io
def new_violations(issue)
hashes = flay.hashes[issue.structural_hash]
Violations.new(language_strategy, issue, hashes)
end
def flay_options
changes = {
mass: language_strategy.mass_threshold,
filters: language_strategy.filters,
post_filters: language_strategy.post_filters,
}
CCFlay.default_options.merge changes
end
def skip?(violation)
below_threshold?(violation) ||
insufficient_occurrence?(violation) ||
check_disabled?(violation)
end
def below_threshold?(violation)
violation.mass < language_strategy.check_mass_threshold(violation.check_name)
end
def insufficient_occurrence?(violation)
(violation.occurrences + 1) < language_strategy.count_threshold
end
def check_disabled?(violation)
!engine_config.check_enabled?(
violation.fingerprint_check_name,
violation.check_name,
)
end
end
end
end
end