class RuboCop::Cop::Lint::MixedCaseRange
r = /[A-Za-z]/
# good
r = /[A-z]/
# bad
@example
change the behavior of the code).
For this reason, this cop’s autocorrect is unsafe (it will
but it changes the regexp to no longer match symbols it used to include.
In most cases this is probably what was originally intended
by replacing one character range with two: ‘A-z` becomes `A-Za-z`.
The cop autocorrects regexp character classes
@safety
NOTE: Range objects cannot be autocorrected.
as well as range objects like `(’A’..‘z’)‘.
Offenses are registered for regexp character classes like `/[A-z]/`
Checks for mixed-case character ranges since they include likely unintended characters.
def build_source_range(range_start, range_end)
def build_source_range(range_start, range_end) range_between(range_start.expression.begin_pos, range_end.expression.end_pos) end
def each_unsafe_regexp_range(node)
def each_unsafe_regexp_range(node) node.parsed_tree&.each_expression do |expr| next if skip_expression?(expr) range_pairs(expr).reject do |range_start, range_end| next if skip_range?(range_start, range_end) next unless unsafe_range?(range_start.text, range_end.text) yield(build_source_range(range_start, range_end)) end end end
def on_irange(node)
def on_irange(node) return unless node.children.compact.all?(&:str_type?) return if node.begin.nil? || node.end.nil? add_offense(node) if unsafe_range?(node.begin.value, node.end.value) end
def on_regexp(node)
def on_regexp(node) each_unsafe_regexp_range(node) do |loc| next unless (replacement = regexp_range(loc.source)) add_offense(loc) do |corrector| corrector.replace(loc, replacement) end end end
def range_for(char)
def range_for(char) RANGES.detect do |range| range.include?(char) end end
def range_pairs(expr)
def range_pairs(expr) RuboCop::Cop::Utils::RegexpRanges.new(expr).pairs end
def regexp_range(source)
def regexp_range(source) open, close = source.split('-') return unless (open_range = range_for(open)) return unless (close_range = range_for(close)) first = [open, open_range.end] second = [close_range.begin, close] "#{first.uniq.join('-')}#{second.uniq.join('-')}" end
def skip_expression?(expr)
def skip_expression?(expr) !(expr.type == :set && expr.token == :character) end
def skip_range?(range_start, range_end)
def skip_range?(range_start, range_end) [range_start, range_end].any? do |bound| bound.type != :literal end end
def unsafe_range?(range_start, range_end)
def unsafe_range?(range_start, range_end) return false if range_start.length != 1 || range_end.length != 1 range_for(range_start) != range_for(range_end) end