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