Class: Testprune::SemanticMap

Inherits:
Object
  • Object
show all
Defined in:
lib/testprune/semantic_map.rb

Overview

Parses one source file with Prism and resolves Coverage locations to AST-aware semantic units:

- methods   (DefNode)            -> "Class#method"
- branch arms (IfNode/CaseNode/…) -> "if then-branch", labelled by the
  innermost enclosing control node
- lines collapse to their innermost enclosing method, or a per-file
  top-level unit when outside any def — so straight-line code still
  contributes a stable unit.

Defined Under Namespace

Classes: Unit, Visitor

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path, source, relpath) ⇒ SemanticMap

Returns a new instance of SemanticMap.



26
27
28
29
30
31
32
33
34
# File 'lib/testprune/semantic_map.rb', line 26

def initialize(path, source, relpath)
  @path    = path
  @relpath = relpath
  @units            = {}  # id => Unit
  @methods_by_pos   = {}  # [start_line, start_col] => Unit
  @method_intervals = []  # [start_line, end_line, Unit]
  @controls         = []  # [sl, sc, el, ec, type]
  Visitor.new(self).visit(Prism.parse(source).value)
end

Instance Attribute Details

#unitsObject (readonly)

Returns the value of attribute units.



24
25
26
# File 'lib/testprune/semantic_map.rb', line 24

def units
  @units
end

Class Method Details

.for_file(path, relpath:) ⇒ Object



17
18
19
20
21
22
# File 'lib/testprune/semantic_map.rb', line 17

def self.for_file(path, relpath:)
  # Read with replacement for invalid/undefined bytes so non-UTF-8 content
  # (Latin-1 comments, etc.) never raises an encoding error.
  source = File.read(path, encoding: 'UTF-8:UTF-8', invalid: :replace, undef: :replace)
  new(path, source, relpath)
end

Instance Method Details

#add_control(node, type) ⇒ Object



51
52
53
54
# File 'lib/testprune/semantic_map.rb', line 51

def add_control(node, type)
  loc = node.location
  @controls << [loc.start_line, loc.start_column, loc.end_line, loc.end_column, type]
end

#add_method(node, scope, sep) ⇒ Object

Registration callbacks used by the Visitor.



38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/testprune/semantic_map.rb', line 38

def add_method(node, scope, sep)
  loc = node.location
  sl  = loc.start_line
  sc  = loc.start_column
  owner = scope.empty? ? '' : "#{scope.join('::')}#{sep}"
  id  = "#{@relpath}#m@#{sl}:#{sc}"
  unit = Unit.new(id: id, kind: :method, label: "#{owner}#{node.name} (#{@relpath}:#{sl})",
                  file: @relpath, line: sl)
  @units[id] = unit
  @methods_by_pos[[sl, sc]] = unit
  @method_intervals << [sl, loc.end_line, unit]
end

#branch_unit(type, start_line, start_col, _end_line, _end_col) ⇒ Object



62
63
64
65
66
67
68
69
70
# File 'lib/testprune/semantic_map.rb', line 62

def branch_unit(type, start_line, start_col, _end_line, _end_col)
  id = "#{@relpath}#b:#{type}@#{start_line}:#{start_col}"
  @units[id] ||= begin
    control_type = innermost_control(start_line, start_col) || type
    Unit.new(id: id, kind: :branch,
             label: "#{control_type} #{type}-branch (#{@relpath}:#{start_line})",
             file: @relpath, line: start_line)
  end
end

#line_unit(lineno) ⇒ Object



72
73
74
75
76
77
78
79
80
# File 'lib/testprune/semantic_map.rb', line 72

def line_unit(lineno)
  best = nil
  @method_intervals.each do |sl, el, unit|
    next unless lineno >= sl && lineno <= el

    best = unit if best.nil? || sl > best.line
  end
  best || toplevel_unit
end

#method_unit(start_line, start_col) ⇒ Object

Resolution used when building footprints.



58
59
60
# File 'lib/testprune/semantic_map.rb', line 58

def method_unit(start_line, start_col)
  @methods_by_pos[[start_line, start_col]]
end