Module: Mutineer::MutantId

Defined in:
lib/mutineer/mutant_id.rb

Overview

Content-based stable id for a mutant — NOT byte offsets. Pure function, reused by the Runner (matching the ignore list), the Reporter (emitting a copy- pasteable id per survivor), and #13 baseline gating (diffing id-sets run to run). digest is stdlib, so zero new deps.

Offset-free by design: keyed on the subject's qualified_name (a method, not a byte position) + operator + the normalized mutated token + an occurrence ordinal among same-(operator, token) twins WITHIN the subject. So it survives any edit outside the subject method — where raw start/end offsets shift on every edit earlier in the file and would silently stop matching.

Class Method Summary collapse

Class Method Details

.for(subject, mutation, source, occurrence = 0) ⇒ Object

NUL-joined so token delimiters (||=, spaces, ::, #) can never collide with the separator; SHA256 gives a fixed-length, copy-pasteable key.



21
22
23
24
25
26
# File 'lib/mutineer/mutant_id.rb', line 21

def for(subject, mutation, source, occurrence = 0)
  Digest::SHA256.hexdigest(
    [subject.qualified_name, mutation.operator,
     normalized_token(mutation, source), occurrence].join("\x00")
  )[0, 12]
end

.for_subject(subject, source, mutations) ⇒ Object

Ids for a subject's full mutation list, in input order, assigning each its 0-based occurrence among twins sharing the same (operator, token). This is what disambiguates a + b + c's two + mutants without an offset.



31
32
33
34
35
36
37
38
39
# File 'lib/mutineer/mutant_id.rb', line 31

def for_subject(subject, source, mutations)
  seen = Hash.new(0)
  mutations.map do |m|
    key = [m.operator, normalized_token(m, source)]
    occ = seen[key]
    seen[key] += 1
    self.for(subject, m, source, occ)
  end
end

.normalized_token(mutation, source) ⇒ Object

The exact code being mutated, whitespace-collapsed — same normalization the Reporter's diff_for uses for its token label.



43
44
45
# File 'lib/mutineer/mutant_id.rb', line 43

def normalized_token(mutation, source)
  source.byteslice(mutation.start_offset...mutation.end_offset).gsub(/\s+/, " ").strip
end