# encoding: utf-8 module RuboCop module Cop module Style # This cop enforces the consistent usage of `%`-literal delimiters. class PercentLiteralDelimiters < Cop include PercentLiteral def on_array(node) process(node, '%w', '%W', '%i') end def on_regexp(node) process(node, '%r') end def on_str(node) process(node, '%', '%Q', '%q') end alias_method :on_dstr, :on_str def on_sym(node) process(node, '%s') end def on_xstr(node) process(node, '%x') end def message(node) type = type(node) delimiters = preferred_delimiters(type) "`#{type}`-literals should be delimited by " \ "`#{delimiters[0]}` and `#{delimiters[1]}`" end private def autocorrect(node) type = type(node) opening_delimiter, closing_delimiter = preferred_delimiters(type) first_child, *_middle, last_child = *node opening_newline = new_line(node.loc.begin, first_child) expression_indentation = leading_whitespace(first_child, :expression) closing_newline = new_line(node.loc.end, last_child) closing_indentation = leading_whitespace(node, :end) expression, reg_opt = *contents(node) corrected_source = type + opening_delimiter + opening_newline + expression_indentation + expression + closing_newline + closing_indentation + closing_delimiter + reg_opt @corrections << lambda do |corrector| corrector.replace(node.loc.expression, corrected_source) end end def on_percent_literal(node) type = type(node) return if uses_preferred_delimiter?(node, type) || contains_preferred_delimiter?(node, type) add_offense(node, :expression) end def preferred_delimiters(type) cop_config['PreferredDelimiters'][type].split(//) end def leading_whitespace(object, part) case object when String '' when Parser::AST::Node part_range = object.loc.send(part) left_of_part = part_range.source_line[0...part_range.column] /^(\s*)$/.match(left_of_part) ? left_of_part : '' else fail "Unsupported object #{object}" end end def contents(node) first_child, *middle, last_child = *node last_child ||= first_child if node.type == :regexp *_, next_to_last_child = *middle next_to_last_child ||= first_child expression = source(node, first_child, next_to_last_child) reg_opt = last_child.loc.expression.source else expression = if first_child.is_a?(Parser::AST::Node) source(node, first_child, last_child) else first_child.to_s end reg_opt = '' end [expression, reg_opt] end def source(node, begin_node, end_node) Parser::Source::Range.new( node.loc.expression.source_buffer, begin_node.loc.expression.begin_pos, end_node.loc.expression.end_pos ).source end def uses_preferred_delimiter?(node, type) preferred_delimiters(type)[0] == begin_source(node)[-1] end def contains_preferred_delimiter?(node, type) preferred_delimiters = preferred_delimiters(type) node .children.map { |n| string_source(n) }.compact .any? { |s| preferred_delimiters.any? { |d| s.include?(d) } } end def string_source(node) if node.is_a?(String) node elsif node.respond_to?(:type) && node.type == :str node.loc.expression.source end end def new_line(range, child_node) same_line?(range, child_node) ? '' : "\n" end def same_line?(range, child_node) !child_node.is_a?(Parser::AST::Node) || range.begin.line == child_node.loc.line end end end end end