diff --git a/lib/rubocop/cop/style/redundant_regexp_escape.rb b/lib/rubocop/cop/style/redundant_regexp_escape.rb index 4f13913d6ca..ac0634669ac 100644 --- a/lib/rubocop/cop/style/redundant_regexp_escape.rb +++ b/lib/rubocop/cop/style/redundant_regexp_escape.rb @@ -80,14 +80,30 @@ def delimiter?(node, char) delimiters.include?(char) end - def each_escape(node) - node.parsed_tree&.traverse&.reduce(0) do |char_class_depth, (event, expr)| - yield(expr.text[1], expr.ts, !char_class_depth.zero?) if expr.type == :escape - - if expr.type == :set - char_class_depth + (event == :enter ? 1 : -1) - else - char_class_depth + if Gem::Version.new(Regexp::Parser::VERSION) >= Gem::Version.new('2.0') + def each_escape(node) + node.parsed_tree&.traverse&.reduce(0) do |char_class_depth, (event, expr)| + yield(expr.text[1], expr.ts, !char_class_depth.zero?) if expr.type == :escape + + if expr.type == :set + char_class_depth + (event == :enter ? 1 : -1) + else + char_class_depth + end + end + end + # Please remove this `else` branch when support for regexp_parser 1.8 will be dropped. + # It's for compatibility with regexp_arser 1.8 and will never be maintained. + else + def each_escape(node) + node.parsed_tree&.traverse&.reduce(0) do |char_class_depth, (event, expr)| + yield(expr.text[1], expr.start_index, !char_class_depth.zero?) if expr.type == :escape + + if expr.type == :set + char_class_depth + (event == :enter ? 1 : -1) + else + char_class_depth + end end end end diff --git a/lib/rubocop/ext/regexp_node.rb b/lib/rubocop/ext/regexp_node.rb index adbf67437f5..fd126044e7e 100644 --- a/lib/rubocop/ext/regexp_node.rb +++ b/lib/rubocop/ext/regexp_node.rb @@ -15,17 +15,39 @@ def ANY.==(_) # see `ext/regexp_parser`. attr_reader :parsed_tree - def assign_properties(*) - super + if Gem::Version.new(Regexp::Parser::VERSION) >= Gem::Version.new('2.0') + def assign_properties(*) + super - str = with_interpolations_blanked - @parsed_tree = begin - Regexp::Parser.parse(str, options: options) - rescue StandardError - nil + str = with_interpolations_blanked + @parsed_tree = begin + Regexp::Parser.parse(str, options: options) + rescue StandardError + nil + end + origin = loc.begin.end + @parsed_tree&.each_expression(true) { |e| e.origin = origin } + end + # Please remove this `else` branch when support for regexp_parser 1.8 will be dropped. + # It's for compatibility with regexp_arser 1.8 and will never be maintained. + else + def assign_properties(*) + super + + str = with_interpolations_blanked + begin + @parsed_tree = Regexp::Parser.parse(str, options: options) + rescue StandardError + @parsed_tree = nil + else + origin = loc.begin.end + source = @parsed_tree.to_s + @parsed_tree.each_expression(true) do |e| + e.origin = origin + e.source = source + end + end end - origin = loc.begin.end - @parsed_tree&.each_expression(true) { |e| e.origin = origin } end def each_capture(named: ANY) diff --git a/lib/rubocop/ext/regexp_parser.rb b/lib/rubocop/ext/regexp_parser.rb index 87df5f1832c..d61b1125774 100644 --- a/lib/rubocop/ext/regexp_parser.rb +++ b/lib/rubocop/ext/regexp_parser.rb @@ -22,9 +22,27 @@ module Expression module Base attr_accessor :origin - # Shortcut to `loc.expression` - def expression - @expression ||= origin.adjust(begin_pos: ts, end_pos: ts + full_length) + if Gem::Version.new(Regexp::Parser::VERSION) >= Gem::Version.new('2.0') + # Shortcut to `loc.expression` + def expression + @expression ||= origin.adjust(begin_pos: ts, end_pos: ts + full_length) + end + # Please remove this `else` branch when support for regexp_parser 1.8 will be dropped. + # It's for compatibility with regexp_arser 1.8 and will never be maintained. + else + attr_accessor :source + + def start_index + # ts is a byte index; convert it to a character index + @start_index ||= source.byteslice(0, ts).length + end + + # Shortcut to `loc.expression` + def expression + @expression ||= begin + origin.adjust(begin_pos: start_index, end_pos: start_index + full_length) + end + end end # @returns a location map like `parser` does, with: