Class: Necropsy::Guardrail::Quarantine

Inherits:
Object
  • Object
show all
Defined in:
lib/necropsy/guardrail/quarantine.rb

Constant Summary collapse

ANNOTATION_PREFIX =
'# necropsy:quarantine'

Instance Method Summary collapse

Constructor Details

#initialize(report:, root:, clock: Clock.new) ⇒ Quarantine

Returns a new instance of Quarantine.



12
13
14
15
16
# File 'lib/necropsy/guardrail/quarantine.rb', line 12

def initialize(report:, root:, clock: Clock.new)
  @report = report
  @root = root
  @clock = clock
end

Instance Method Details

#suggestions(min_confidence: :high) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/necropsy/guardrail/quarantine.rb', line 18

def suggestions(min_confidence: :high)
  findings = report.dead_methods(min_confidence: min_confidence)
  grouped = findings.group_by { |finding| [finding.node.file, finding.node.line] }
  source_digests = {}
  grouped.sort_by { |(file, line), _entries| [file, line] }.map do |(file, line), entries|
    entries = entries.sort_by { |finding| finding.node.definition_id }
    fingerprints = entries.map(&:physical_fingerprint).uniq
    path = source_path(file)
    source_digests[path] ||= Digest::SHA256.file(path).hexdigest
    {
      finding: entries.first,
      findings: entries.freeze,
      annotation: "#{ANNOTATION_PREFIX} since=#{clock.date.iso8601} #{fingerprint_field(fingerprints)}",
      fingerprints: fingerprints.freeze,
      source_sha256: source_digests.fetch(path),
      path: path,
      line: line
    }
  end
end

#write(min_confidence: :high) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/necropsy/guardrail/quarantine.rb', line 39

def write(min_confidence: :high)
  grouped = suggestions(min_confidence: min_confidence).group_by { |suggestion| suggestion[:path] }
  grouped.each do |path, entries|
    source = File.binread(path)
    verify_source_digest!(path, source, entries)
    newline = source[/\r\n|\n/] || "\n"
    trailing_newline = source.end_with?("\r\n", "\n")
    lines = source.split(/\r\n|\n/, -1)
    lines.pop if trailing_newline
    entries.sort_by { |entry| -entry[:line] }.each do |entry|
      index = [entry[:line] - 1, 0].max
      if index.positive? && lines[index - 1]&.include?(ANNOTATION_PREFIX)
        upgrade_existing_annotation!(lines, index - 1, entry)
        next
      end

      indent = lines[index][/^\s*/] || ''
      lines.insert(index, "#{indent}#{entry[:annotation]}")
    end
    rewritten = lines.join(newline)
    rewritten << newline if trailing_newline
    atomic_replace(path, rewritten, expected_sha256: entries.first.fetch(:source_sha256))
  end
end