class ERBLint::Utils::BlockMap

def append(code)

def append(code)
  @ruby_code += code
end

def block?(source)

def block?(source)
  # taken from: action_view/template/handlers/erb/erubi.rb
  /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/.match?(source)
end

def build_map

def build_map
  erb_nodes.each do |erb_node|
    indicator_node, _, code_node, _ = *erb_node
    length = code_node.loc.size
    start = current_pos
    if indicator_node.nil?
      append("#{code_node.loc.source}\n")
    elsif block?(code_node.loc.source)
      append("src= #{code_node.loc.source}\n")
      start += 5
    else
      append("src=(#{code_node.loc.source});\n")
      start += 5
    end
    ruby_range = Range.new(start, start + length)
    @entries << Entry.new(erb_node, ruby_range)
  end
  ruby_node = BetterHtml::TestHelper::RubyNode.parse(@ruby_code)
  raise ParseError unless ruby_node
  ruby_node.descendants(:block, :if, :for).each do |node|
    @connections << ConnectedErbNodes.new(
      node.type,
      extract_map_locations(node)
        .map { |loc| find_entry(loc) }
        .compact.map(&:node),
    )
  end
  ruby_node.descendants(:kwbegin).each do |node|
    @connections << ConnectedErbNodes.new(
      :begin,
      (extract_map_locations(node) + rescue_locations(node))
        .map { |loc| find_entry(loc) }
        .compact.map(&:node),
    )
  end
  ruby_node.descendants(:case).each do |node|
    @connections << ConnectedErbNodes.new(
      node.type,
      (extract_map_locations(node) + when_locations(node))
        .map { |loc| find_entry(loc) }
        .compact.map(&:node),
    )
  end
  group_overlapping_connections
end

def current_pos

def current_pos
  @ruby_code.size
end

def erb_ast

def erb_ast
  parser.ast
end

def erb_nodes

def erb_nodes
  erb_ast.descendants(:erb).sort { |a, b| a.loc.begin_pos <=> b.loc.begin_pos }
end

def extract_map_locations(node)

def extract_map_locations(node)
  (
    case node.loc
    when Parser::Source::Map::Collection
      [node.loc.begin, node.loc.end]
    when Parser::Source::Map::Condition
      [node.loc.keyword, node.loc.begin, node.loc.else, node.loc.end]
    when Parser::Source::Map::Constant
      [node.loc.double_colon, node.loc.name, node.loc.operator]
    when Parser::Source::Map::Definition
      [node.loc.keyword, node.loc.operator, node.loc.name, node.loc.end]
    when Parser::Source::Map::For
      [node.loc.keyword, node.loc.in, node.loc.begin, node.loc.end]
    when Parser::Source::Map::Heredoc
      [node.loc.heredoc_body, node.loc.heredoc_end]
    when Parser::Source::Map::Keyword
      [node.loc.keyword, node.loc.begin, node.loc.end]
    when Parser::Source::Map::ObjcKwarg
      [node.loc.keyword, node.loc.operator, node.loc.argument]
    when Parser::Source::Map::RescueBody
      [node.loc.keyword, node.loc.assoc, node.loc.begin]
    when Parser::Source::Map::Send
      [node.loc.dot, node.loc.selector, node.loc.operator, node.loc.begin, node.loc.end]
    when Parser::Source::Map::Ternary
      [node.loc.question, node.loc.colon]
    when Parser::Source::Map::Variable
      [node.loc.name, node.loc.operator]
    end + [node.loc.expression]
  ).compact
end

def find_connected_nodes(other)

def find_connected_nodes(other)
  connection = @connections.find { |conn| conn.include?(other) }
  connection&.nodes
end

def find_entry(range)

def find_entry(range)
  return unless range
  @entries.find do |entry|
    entry.contains_ruby_range?(Range.new(range.begin_pos, range.end_pos))
  end
end

def find_overlapping_pair

def find_overlapping_pair
  @connections.each do |first|
    @connections.each do |second|
      next if first == second
      return [first, second] if (first & second).any?
    end
  end
  nil
end

def group_overlapping_connections

def group_overlapping_connections
  loop do
    first, second = find_overlapping_pair
    break unless first && second
    @connections.delete(second)
    first.concat(second)
  end
end

def initialize(processed_source)

def initialize(processed_source)
  @processed_source = processed_source
  @entries = []
  @connections = []
  @ruby_code = ""
  build_map
end

def parser

def parser
  @processed_source.parser
end

def rescue_locations(node)

def rescue_locations(node)
  node.child_nodes
    .select { |child| child.type?(:rescue) }
    .map(&:child_nodes)
    .flatten
    .select { |child| child.type?(:resbody) }
    .map { |child| extract_map_locations(child) }
    .flatten
end

def when_locations(node)

def when_locations(node)
  node.child_nodes
    .select { |child| child.type?(:when) }
    .map { |child| extract_map_locations(child) }
    .flatten
end