Class: ActiveMutator::SubjectFinder
- Inherits:
-
Prism::Visitor
- Object
- Prism::Visitor
- ActiveMutator::SubjectFinder
- Defined in:
- lib/active_mutator/subject_finder.rb
Constant Summary collapse
- SKIP_MARKER =
/#\s*active_mutator:\s*skip\b/
Instance Attribute Summary collapse
-
#subjects ⇒ Object
readonly
Returns the value of attribute subjects.
Class Method Summary collapse
Instance Method Summary collapse
-
#initialize(file, skip_lines: Set.new) ⇒ SubjectFinder
constructor
A new instance of SubjectFinder.
-
#visit_block_node(node) ⇒ Object
Defs inside blocks (
Data.define do ... end,class_eval do ... end) do not live on the enclosing constant scope, so Inserter would redefine them on the wrong constant and every mutant would falsely survive. -
#visit_class_node(node) ⇒ Object
Classes/modules declared inside
class << selfhang their constant on the SINGLETON class, so a lexically-joined scope like "Foo::Bar" is not reachable via Object.const_get — Inserter would crash. - #visit_def_node(node) ⇒ Object
- #visit_module_node(node) ⇒ Object
-
#visit_singleton_class_node(node) ⇒ Object
class << selfinside a constant scope: defs there are singleton methods of the enclosing constant.
Constructor Details
#initialize(file, skip_lines: Set.new) ⇒ SubjectFinder
Returns a new instance of SubjectFinder.
21 22 23 24 25 26 27 28 |
# File 'lib/active_mutator/subject_finder.rb', line 21 def initialize(file, skip_lines: Set.new) @file = file @skip_lines = skip_lines @stack = [] @subjects = [] @sclass_depth = 0 super() end |
Instance Attribute Details
#subjects ⇒ Object (readonly)
Returns the value of attribute subjects.
19 20 21 |
# File 'lib/active_mutator/subject_finder.rb', line 19 def subjects @subjects end |
Class Method Details
.call(file) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 |
# File 'lib/active_mutator/subject_finder.rb', line 7 def self.call(file) result = Prism.parse(File.read(file)) return [] unless result.success? skip_lines = result.comments .select { |c| c.slice.match?(SKIP_MARKER) } .to_set { |c| c.location.start_line } finder = new(file, skip_lines: skip_lines) finder.visit(result.value) finder.subjects end |
Instance Method Details
#visit_block_node(node) ⇒ Object
Defs inside blocks (Data.define do ... end, class_eval do ... end)
do not live on the enclosing constant scope, so Inserter would redefine
them on the wrong constant and every mutant would falsely survive.
Same v1 limit as class << self: not visited. Note this also hides
classes/modules defined inside blocks (accepted v1 limit).
64 |
# File 'lib/active_mutator/subject_finder.rb', line 64 def visit_block_node(node); end |
#visit_class_node(node) ⇒ Object
Classes/modules declared inside class << self hang their constant on
the SINGLETON class, so a lexically-joined scope like "Foo::Bar" is not
reachable via Object.const_get — Inserter would crash. Skipped entirely.
33 34 35 36 37 |
# File 'lib/active_mutator/subject_finder.rb', line 33 def visit_class_node(node) return if @sclass_depth.positive? with_scope(node.constant_path.slice) { super } end |
#visit_def_node(node) ⇒ Object
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/active_mutator/subject_finder.rb', line 66 def visit_def_node(node) return if @skip_lines.include?(node.location.start_line - 1) sclass = @sclass_depth.positive? singleton = sclass || node.receiver.is_a?(Prism::SelfNode) scope = @stack.empty? ? nil : @stack.join("::") loc = node.location @subjects << Subject.new( name: "#{scope || "Object"}#{singleton ? "." : "#"}#{node.name}", file: @file, byte_range: loc.start_offset...loc.end_offset, line_range: loc.start_line..loc.end_line, constant_scope: scope, kind: singleton ? :singleton : :instance, sclass: sclass ) # No `super`: nested defs get no subject of their own -- their bodies # are mutated via the OUTER def (Engine#walk descends into them). end |
#visit_module_node(node) ⇒ Object
39 40 41 42 43 |
# File 'lib/active_mutator/subject_finder.rb', line 39 def visit_module_node(node) return if @sclass_depth.positive? with_scope(node.constant_path.slice) { super } end |
#visit_singleton_class_node(node) ⇒ Object
class << self inside a constant scope: defs there are singleton
methods of the enclosing constant. class << obj and a top-level
class << self (no constant to hang the method on) stay skipped.
48 49 50 51 52 53 54 55 56 57 |
# File 'lib/active_mutator/subject_finder.rb', line 48 def visit_singleton_class_node(node) return unless node.expression.is_a?(Prism::SelfNode) && !@stack.empty? @sclass_depth += 1 begin super ensure @sclass_depth -= 1 end end |