class Parser::Source::TreeRewriter::Action

@api private

Actions are arranged in a tree and get combined so that:

children are strictly contained by their parent
sibblings all disjoint from one another
only actions with replacement==nil may have children

Attributes

insert_after[R]
insert_before[R]
range[R]
replacement[R]

Public Class Methods

new(range, enforcer, insert_before: '', replacement: nil, insert_after: '', children: [] ) click to toggle source
# File lib/parser_tree_rewriter/source/tree_rewriter/action.rb, line 14
def initialize(range, enforcer,
     insert_before: '',
     replacement: nil,
     insert_after: '',
     children: []
  )
  @range, @enforcer, @children, @insert_before, @replacement, @insert_after =
    range, enforcer, children.freeze, insert_before.freeze, replacement, insert_after.freeze

  freeze
end

Public Instance Methods

combine(action) click to toggle source

Assumes action.children.empty?

# File lib/parser_tree_rewriter/source/tree_rewriter/action.rb, line 27
def combine(action)
  return self unless action.insertion? || action.replacement # Ignore empty action
  do_combine(action)
end
insertion?() click to toggle source
# File lib/parser_tree_rewriter/source/tree_rewriter/action.rb, line 41
def insertion?
  !insert_before.empty? || !insert_after.empty? || (replacement && !replacement.empty?)
end
ordered_replacements() click to toggle source
# File lib/parser_tree_rewriter/source/tree_rewriter/action.rb, line 32
def ordered_replacements
  reps = []
  reps << [@range.begin, @insert_before] unless @insert_before.empty?
  reps << [@range, @replacement] if @replacement
  reps.concat(@children.sort_by(&:range).flat_map(&:ordered_replacements))
  reps << [@range.end, @insert_after] unless @insert_after.empty?
  reps
end

Protected Instance Methods

call_enforcer_for_merge(action) click to toggle source
# File lib/parser_tree_rewriter/source/tree_rewriter/action.rb, line 113
def call_enforcer_for_merge(action)
  @enforcer.call(:different_replacements) do
    if @replacement && action.replacement && @replacement != action.replacement
      {range: @range, replacement: action.replacement, other_replacement: @replacement}
    end
  end
end
do_combine(action) click to toggle source

Assumes range.contains?(action.range) && action.children.empty?

# File lib/parser_tree_rewriter/source/tree_rewriter/action.rb, line 53
def do_combine(action)
  if action.range == @range
    merge(action)
  else
    place_in_hierachy(action)
  end
end
fuse_deletions(action, fusible, other_sibblings) click to toggle source
# File lib/parser_tree_rewriter/source/tree_rewriter/action.rb, line 78
def fuse_deletions(action, fusible, other_sibblings)
  without_fusible = with(children: other_sibblings)
  fused_range = [action, *fusible].map(&:range).inject(:join)
  fused_deletion = action.with(range: fused_range)
  without_fusible.do_combine(fused_deletion)
end
merge(action) click to toggle source

Assumes action.range == range && action.children.empty?

# File lib/parser_tree_rewriter/source/tree_rewriter/action.rb, line 104
def merge(action)
  call_enforcer_for_merge(action)
  with(
    insert_before: "#{action.insert_before}#{insert_before}",
    replacement: action.replacement || @replacement,
    insert_after: "#{insert_after}#{action.insert_after}",
  )
end
place_in_hierachy(action) click to toggle source
# File lib/parser_tree_rewriter/source/tree_rewriter/action.rb, line 61
def place_in_hierachy(action)
  family = @children.group_by { |child| child.relationship_with(action) }

  if family[:fusible]
    fuse_deletions(action, family[:fusible], [*family[:sibbling], *family[:child]])
  else
    extra_sibbling = if family[:parent]  # action should be a descendant of one of the children
      family[:parent][0].do_combine(action)
    elsif family[:child]                 # or it should become the parent of some of the children,
      action.with(children: family[:child])
    else                                 # or else it should become an additional child
      action
    end
    with(children: [*family[:sibbling], extra_sibbling])
  end
end
relationship_with(action) click to toggle source

Returns what relationship self should have with `action`; either of

:sibbling, :parent, :child, :fusible or raises a CloberingError

In case of equal range, returns :parent

# File lib/parser_tree_rewriter/source/tree_rewriter/action.rb, line 88
def relationship_with(action)
  if action.range == @range || @range.contains?(action.range)
    :parent
  elsif @range.contained?(action.range)
    :child
  elsif @range.disjoint?(action.range)
    :sibbling
  elsif !action.insertion? && !insertion?
    @enforcer.call(:crossing_deletions) { {range: action.range, conflict: @range} }
    :fusible
  else
    @enforcer.call(:crossing_insertions) { {range: action.range, conflict: @range} }
  end
end
swallow(children) click to toggle source
# File lib/parser_tree_rewriter/source/tree_rewriter/action.rb, line 121
def swallow(children)
  @enforcer.call(:swallowed_insertions) do
    insertions = children.select(&:insertion?)

    {range: @range, conflict: insertions.map(&:range)} unless insertions.empty?
  end
  []
end
with(range: @range, children: @children, insert_before: @insert_before, replacement: @replacement, insert_after: @insert_after) click to toggle source
# File lib/parser_tree_rewriter/source/tree_rewriter/action.rb, line 47
def with(range: @range, children: @children, insert_before: @insert_before, replacement: @replacement, insert_after: @insert_after)
  children = swallow(children) if replacement
  self.class.new(range, @enforcer, children: children, insert_before: insert_before, replacement: replacement, insert_after: insert_after)
end