Module: MetzScan::Commands::Scan::ProjectAnalyzerRunner

Defined in:
lib/metz_scan/commands/scan/project_analyzer_runner.rb

Overview

Runs wrapper-level project analyzers and merges their findings into the same report shape produced by RuboCop's JSON formatter.

Constant Summary collapse

ANALYZERS =
[
  Analyzers::RepeatedBranching,
  Analyzers::ServiceSoup,
  Analyzers::InheritanceDescendants,
  Analyzers::PackageDependencyPressure,
  Analyzers::NamespaceLeakPressure,
  Analyzers::ImplicitContextPressure,
  Analyzers::RepeatedQueryCriteria,
  Analyzers::SubclassOverridePressure
].freeze
INDEX_BACKED_ANALYZERS =
[Analyzers::InheritanceDescendants, Analyzers::PackageDependencyPressure,
Analyzers::NamespaceLeakPressure, Analyzers::SubclassOverridePressure].freeze
DEFAULT_OUTPUT_STATUS =
"validated"
DEFAULT_OUTPUT_CONFIDENCE =
"medium"
DEFAULT_OUTPUT_TRIAGE_SEVERITY =
"design pressure"

Class Method Summary collapse

Class Method Details

.analyzer_paths(paths, index: nil, force_default_config: false) ⇒ Object



94
95
96
97
98
# File 'lib/metz_scan/commands/scan/project_analyzer_runner.rb', line 94

def analyzer_paths(paths, index: nil, force_default_config: false)
  return Array(paths) if index

  rubocop_target_files(paths, force_default_config: force_default_config)
end

.analyzers_for(default_output:) ⇒ Object



68
69
70
71
72
# File 'lib/metz_scan/commands/scan/project_analyzer_runner.rb', line 68

def analyzers_for(default_output:)
  return ANALYZERS unless default_output

  ANALYZERS.select { |analyzer| default_output_analyzer?(analyzer) }
end

.append_file(files, path) ⇒ Object



121
122
123
# File 'lib/metz_scan/commands/scan/project_analyzer_runner.rb', line 121

def append_file(files, path)
  { "path" => display_path(path), "offenses" => [] }.tap { |file| files << file }
end

.default_output_analyzer?(analyzer) ⇒ Boolean

Returns:

  • (Boolean)


74
75
76
77
78
79
# File 'lib/metz_scan/commands/scan/project_analyzer_runner.rb', line 74

def default_output_analyzer?(analyzer)
  analyzer.const_defined?(:DEFAULT_OUTPUT_ELIGIBLE) &&
    analyzer::DEFAULT_OUTPUT_ELIGIBLE &&
    analyzer.const_defined?(:PROJECT_ANALYZER_STATUS) &&
    analyzer::PROJECT_ANALYZER_STATUS == DEFAULT_OUTPUT_STATUS
end

.default_output_finding?(finding) ⇒ Boolean

Returns:

  • (Boolean)


88
89
90
91
92
# File 'lib/metz_scan/commands/scan/project_analyzer_runner.rb', line 88

def default_output_finding?(finding)
  finding.project_analyzer_status == DEFAULT_OUTPUT_STATUS &&
    finding.confidence == DEFAULT_OUTPUT_CONFIDENCE &&
    finding.triage_severity == DEFAULT_OUTPUT_TRIAGE_SEVERITY
end

.display_path(path) ⇒ Object



125
126
127
128
129
130
131
# File 'lib/metz_scan/commands/scan/project_analyzer_runner.rb', line 125

def display_path(path)
  expanded_path = File.expand_path(path)
  cwd = "#{File.expand_path(Dir.pwd)}#{File::SEPARATOR}"
  return expanded_path.delete_prefix(cwd) if expanded_path.start_with?(cwd)

  path
end

.file_for(parsed, path) ⇒ Object



112
113
114
115
# File 'lib/metz_scan/commands/scan/project_analyzer_runner.rb', line 112

def file_for(parsed, path)
  files = parsed["files"] ||= []
  files.find { |file| same_path?(file["path"], path) } || append_file(files, path)
end

.merge!(parsed, paths) ⇒ Object



40
41
42
43
44
45
46
# File 'lib/metz_scan/commands/scan/project_analyzer_runner.rb', line 40

def merge!(parsed, paths, **)
  findings = project_findings_for(paths, **)
  return parsed if findings.empty?

  merge_findings(parsed, findings)
  parsed
end

.merge_findings(parsed, findings) ⇒ Object



56
57
58
59
60
# File 'lib/metz_scan/commands/scan/project_analyzer_runner.rb', line 56

def merge_findings(parsed, findings)
  offense_set = ProjectAnalyzerOffenses.build(findings)
  merge_offenses(parsed, offense_set.by_path)
  update_summary(parsed, findings, offense_set.offenses)
end

.merge_offenses(parsed, grouped_offenses) ⇒ Object



106
107
108
109
110
# File 'lib/metz_scan/commands/scan/project_analyzer_runner.rb', line 106

def merge_offenses(parsed, grouped_offenses)
  grouped_offenses.each do |path, offenses|
    file_for(parsed, path)["offenses"].concat(offenses)
  end
end

.project_findings(paths, index: nil, default_output: false) ⇒ Object



62
63
64
65
66
# File 'lib/metz_scan/commands/scan/project_analyzer_runner.rb', line 62

def project_findings(paths, index: nil, default_output: false)
  analyzers = analyzers_for(default_output: default_output)
  index = project_index_for(paths, index, analyzers)
  analyzers.flat_map { |analyzer| analyzer.new(paths: paths, index: index).call }
end

.project_findings_for(paths, index: nil, default_output: false, force_default_config: false) ⇒ Object



48
49
50
51
52
53
54
# File 'lib/metz_scan/commands/scan/project_analyzer_runner.rb', line 48

def project_findings_for(paths, index: nil, default_output: false, force_default_config: false)
  paths = analyzer_paths(paths, index: index, force_default_config: force_default_config)
  return [] if paths.empty? && !index

  findings = project_findings(paths, index: index, default_output: default_output)
  default_output ? findings.select { |finding| default_output_finding?(finding) } : findings
end

.project_index_for(paths, index, analyzers) ⇒ Object



81
82
83
84
85
86
# File 'lib/metz_scan/commands/scan/project_analyzer_runner.rb', line 81

def project_index_for(paths, index, analyzers)
  return index if index
  return nil unless analyzers.any? { |analyzer| INDEX_BACKED_ANALYZERS.include?(analyzer) }

  ProjectIndex.build(paths)
end

.rubocop_target_files(paths, force_default_config: false) ⇒ Object



100
101
102
103
104
# File 'lib/metz_scan/commands/scan/project_analyzer_runner.rb', line 100

def rubocop_target_files(paths, force_default_config: false)
  return TargetFileDiscovery.with_forced_defaults(paths) if force_default_config

  TargetFileDiscovery.for_project_config(paths)
end

.same_path?(left, right) ⇒ Boolean

Returns:

  • (Boolean)


117
118
119
# File 'lib/metz_scan/commands/scan/project_analyzer_runner.rb', line 117

def same_path?(left, right)
  File.expand_path(left) == File.expand_path(right)
end

.update_file_counts(summary, file_count) ⇒ Object



141
142
143
144
# File 'lib/metz_scan/commands/scan/project_analyzer_runner.rb', line 141

def update_file_counts(summary, file_count)
  summary["target_file_count"] = [summary["target_file_count"].to_i, file_count].max
  summary["inspected_file_count"] = [summary["inspected_file_count"].to_i, file_count].max
end

.update_summary(parsed, findings, project_offenses) ⇒ Object



133
134
135
136
137
138
139
# File 'lib/metz_scan/commands/scan/project_analyzer_runner.rb', line 133

def update_summary(parsed, findings, project_offenses)
  summary = parsed["summary"] ||= {}
  files = Array(parsed["files"])
  summary["offense_count"] = files.sum { |file| Array(file["offenses"]).size }
  update_file_counts(summary, files.size)
  summary["project_analyzers"] = ProjectAnalyzerMetadata.summary(findings, project_offenses)
end