Class: Ast::Merge::Navigable::InjectionPointFinder

Inherits:
Object
  • Object
show all
Defined in:
lib/ast/merge/navigable/injection_point_finder.rb

Overview

Finds injection points in a document based on matching rules.

This is language-agnostic - the matching rules work on the unified
Statement interface regardless of the underlying parser.

Examples:

Find where to inject constants in a Ruby class

finder = InjectionPointFinder.new(statements)
point = finder.find(
  type: :class,
  text: /class Choo/,
  position: :first_child
)

Find and replace a constant definition

point = finder.find(
  type: :constant_assignment,
  text: /DAR\s*=/,
  position: :replace
)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(statements) ⇒ InjectionPointFinder

Returns a new instance of InjectionPointFinder.



30
31
32
# File 'lib/ast/merge/navigable/injection_point_finder.rb', line 30

def initialize(statements)
  @statements = statements
end

Instance Attribute Details

#statementsArray<Statement> (readonly)

Returns The statement list to search.

Returns:

  • (Array<Statement>)

    The statement list to search



28
29
30
# File 'lib/ast/merge/navigable/injection_point_finder.rb', line 28

def statements
  @statements
end

Instance Method Details

#find(type: nil, text: nil, position:, boundary_type: nil, boundary_text: nil, boundary_matcher: nil, boundary_same_or_shallower: false) {|Statement| ... } ⇒ InjectionPoint?

Find an injection point based on matching criteria.

Parameters:

  • type (Symbol, String, nil) (defaults to: nil)

    Node type to match

  • text (String, Regexp, nil) (defaults to: nil)

    Text pattern to match

  • position (Symbol)

    Where to inject (:before, :after, :first_child, :last_child, :replace)

  • boundary_type (Symbol, String, nil) (defaults to: nil)

    Node type for replacement boundary

  • boundary_text (String, Regexp, nil) (defaults to: nil)

    Text pattern for replacement boundary

  • boundary_matcher (Proc, nil) (defaults to: nil)

    Custom matcher for boundary (receives Statement, returns boolean)

  • boundary_same_or_shallower (Boolean) (defaults to: false)

    If true, boundary is next node at same or shallower tree depth

Yields:

Returns:



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/ast/merge/navigable/injection_point_finder.rb', line 45

def find(type: nil, text: nil, position:, boundary_type: nil, boundary_text: nil, boundary_matcher: nil, boundary_same_or_shallower: false, &block)
  anchor = Statement.find_first(statements, type: type, text: text, &block)
  return unless anchor

  boundary = nil
  if position == :replace && (boundary_type || boundary_text || boundary_matcher || boundary_same_or_shallower)
    # Find boundary starting after anchor
    remaining = statements[(anchor.index + 1)..]

    if boundary_same_or_shallower
      # Find next node at same or shallower tree depth
      # This is language-agnostic: ends section at next sibling or ancestor's sibling
      anchor_depth = anchor.tree_depth
      boundary = remaining.find do |stmt|
        # Must match type if specified
        next false if boundary_type && stmt.type.to_s != boundary_type.to_s
        next false if boundary_text && !stmt.text_matches?(boundary_text)
        # Check tree depth
        stmt.same_or_shallower_than?(anchor_depth)
      end
    elsif boundary_matcher
      # Use custom matcher
      boundary = remaining.find { |stmt| boundary_matcher.call(stmt) }
    else
      boundary = Statement.find_first(
        remaining,
        type: boundary_type,
        text: boundary_text,
      )
    end
  end

  InjectionPoint.new(
    anchor: anchor,
    position: position,
    boundary: boundary,
    match: {type: type, text: text},
  )
end

#find_all(type: nil, text: nil, position:, &block) ⇒ Array<InjectionPoint>

Find all injection points matching criteria.

Parameters:

  • type (Symbol, String, nil) (defaults to: nil)

    Node type to match

  • text (String, Regexp, nil) (defaults to: nil)

    Text pattern to match

  • position (Symbol)

    Where to inject (:before, :after, :first_child, :last_child, :replace)

  • boundary_type (Symbol, String, nil)

    Node type for replacement boundary

  • boundary_text (String, Regexp, nil)

    Text pattern for replacement boundary

  • boundary_matcher (Proc, nil)

    Custom matcher for boundary (receives Statement, returns boolean)

  • boundary_same_or_shallower (Boolean)

    If true, boundary is next node at same or shallower tree depth

Returns:



89
90
91
92
93
94
# File 'lib/ast/merge/navigable/injection_point_finder.rb', line 89

def find_all(type: nil, text: nil, position:, &block)
  anchors = Statement.find_matching(statements, type: type, text: text, &block)
  anchors.map do |anchor|
    InjectionPoint.new(anchor: anchor, position: position)
  end
end