Module: MetzScan::Commands::Scan::Runner

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

Defined Under Namespace

Classes: Error, Result

Constant Summary collapse

FORMATTER =
"RuboCop::Formatter::MetzJsonFormatter"

Class Method Summary collapse

Class Method Details

.capture_output(argv) ⇒ Object



66
67
68
69
70
71
# File 'lib/metz_scan/commands/scan/runner.rb', line 66

def self.capture_output(argv)
  out = StringIO.new
  err = StringIO.new
  status = with_redirected_io(out, err) { RuboCop::CLI.new.run(argv) }
  Result.new(stdout: out.string, stderr: err.string, status: status)
end

.concise_message(text) ⇒ Object



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

def self.concise_message(text)
  text.to_s.lines.map(&:strip)
      .reject(&:empty?)
      .reject { |line| stack_trace_line?(line) }
      .first(3)
      .join(" ")
end

.cop_selection_argv(all_cops) ⇒ Object



62
63
64
# File 'lib/metz_scan/commands/scan/runner.rb', line 62

def self.cop_selection_argv(all_cops)
  all_cops ? [] : ["--force-default-config", "--enable-all-cops", "--only", "Metz"]
end

.display_path(path) ⇒ Object



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

def self.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)

  expanded_path
end

.empty_reportObject



122
123
124
125
# File 'lib/metz_scan/commands/scan/runner.rb', line 122

def self.empty_report
  { "metadata" => , "files" => [],
    "summary" => { "offense_count" => 0, "target_file_count" => 0, "inspected_file_count" => 0 } }
end

.exit_code_for(parsed) ⇒ Object



118
119
120
# File 'lib/metz_scan/commands/scan/runner.rb', line 118

def self.exit_code_for(parsed)
  Array(parsed["files"]).any? { |f| Array(f["offenses"]).any? } ? 1 : 0
end

.failure_message(result) ⇒ Object



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

def self.failure_message(result)
  detail = concise_message(result.stderr)
  detail = concise_message(result.stdout) if detail.empty?
  return detail unless detail.empty?

  "RuboCop exited with status #{result.status} without JSON output"
end

.inspection_paths(paths, all_cops:) ⇒ Object



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

def self.inspection_paths(paths, all_cops:)
  return paths if all_cops

  TargetFileDiscovery.for_project_config(paths).map { |path| display_path(path) }
end

.invoke(paths, all_cops: false, stderr: $stderr) ⇒ Object



21
22
23
# File 'lib/metz_scan/commands/scan/runner.rb', line 21

def self.invoke(paths, all_cops: false, stderr: $stderr)
  with_errors { perform(paths, all_cops: all_cops, stderr: stderr) }
end

.metadataObject



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

def self.
  { "rubocop_version" => RuboCop::Version::STRING, "ruby_engine" => RUBY_ENGINE,
    "ruby_version" => RUBY_VERSION, "ruby_patchlevel" => RUBY_PATCHLEVEL.to_s,
    "ruby_platform" => RUBY_PLATFORM }
end

.parse_output(result) ⇒ Object



92
93
94
95
96
# File 'lib/metz_scan/commands/scan/runner.rb', line 92

def self.parse_output(result)
  JSON.parse(result.stdout)
rescue JSON::ParserError
  raise Error, failure_message(result)
end

.perform(paths, all_cops:, stderr:) ⇒ Object



25
26
27
28
29
30
# File 'lib/metz_scan/commands/scan/runner.rb', line 25

def self.perform(paths, all_cops:, stderr:)
  ProjectConfigScope.reset_unresolved_inherit_gems! unless all_cops
  paths = inspection_paths(paths, all_cops: all_cops)
  report = paths.empty? ? empty_report : scan(paths, all_cops: all_cops)
  report.tap { ProjectConfigScope.warn_unresolved_inherit_gems(stderr) unless all_cops }
end

.redirect_to(out, err) ⇒ Object



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

def self.redirect_to(out, err)
  previous = [$stdout, $stderr]
  $stdout = out
  $stderr = err
  previous
end

.require_metz_pluginObject



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

def self.require_metz_plugin
  require "rubocop-metz"
rescue LoadError => e
  raise Error, "could not load rubocop-metz: #{e.message}"
end

.restore_io(previous) ⇒ Object



87
88
89
90
# File 'lib/metz_scan/commands/scan/runner.rb', line 87

def self.restore_io(previous)
  $stdout = previous[0]
  $stderr = previous[1]
end

.rubocop_argv(paths, all_cops:) ⇒ Object



45
46
47
48
# File 'lib/metz_scan/commands/scan/runner.rb', line 45

def self.rubocop_argv(paths, all_cops:)
  require_metz_plugin
  ["--plugin", "rubocop-metz", *cop_selection_argv(all_cops), "--format", FORMATTER, *paths]
end

.scan(paths, all_cops:) ⇒ Object



32
33
34
35
36
37
# File 'lib/metz_scan/commands/scan/runner.rb', line 32

def self.scan(paths, all_cops:)
  report = TargetRubyVersion.with_project_config(paths, all_cops: all_cops) do
    parse_output(capture_output(rubocop_argv(paths, all_cops: all_cops)))
  end
  all_cops ? report : ProjectCopScope.honor(report)
end

.stack_trace_line?(line) ⇒ Boolean

Returns:

  • (Boolean)


114
115
116
# File 'lib/metz_scan/commands/scan/runner.rb', line 114

def self.stack_trace_line?(line)
  line == "Traceback (most recent call last):" || line.match?(/:\d+:in [`']/)
end

.with_errorsObject



39
40
41
42
43
# File 'lib/metz_scan/commands/scan/runner.rb', line 39

def self.with_errors
  yield
rescue LoadError, StandardError => e
  raise Error, concise_message(e.message)
end

.with_redirected_io(out, err) ⇒ Object



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

def self.with_redirected_io(out, err)
  original = redirect_to(out, err)
  yield
ensure
  restore_io(original) if original
end