class RuboCop::Cop::Lint::MixedCaseRange

Checks for mixed-case character ranges since they include likely unintended characters.

Offenses are registered for regexp character classes like ‘/[A-z]/` as well as range objects like `(’A’..‘z’)‘.

NOTE: Range objects cannot be autocorrected.

@safety

The cop autocorrects regexp character classes
by replacing one character range with two: `A-z` becomes `A-Za-z`.
In most cases this is probably what was originally intended
but it changes the regexp to no longer match symbols it used to include.
For this reason, this cop's autocorrect is unsafe (it will
change the behavior of the code).

@example

# bad
r = /[A-z]/

# good
r = /[A-Za-z]/

Constants

MSG
RANGES

Public Instance Methods

each_unsafe_regexp_range(node) { |build_source_range(range_start, range_end)| ... } click to toggle source
# File lib/rubocop/cop/lint/mixed_case_range.rb, line 56
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
on_erange(node)
Alias for: on_irange
on_irange(node) click to toggle source
# File lib/rubocop/cop/lint/mixed_case_range.rb, line 37
def on_irange(node)
  return unless node.children.compact.all?(&:str_type?)

  range_start, range_end = node.children

  return if range_start.nil? || range_end.nil?

  add_offense(node) if unsafe_range?(range_start.value, range_end.value)
end
Also aliased as: on_erange
on_regexp(node) click to toggle source
# File lib/rubocop/cop/lint/mixed_case_range.rb, line 48
def on_regexp(node)
  each_unsafe_regexp_range(node) do |loc|
    add_offense(loc) do |corrector|
      corrector.replace(loc, rewrite_regexp_range(loc.source))
    end
  end
end

Private Instance Methods

build_source_range(range_start, range_end) click to toggle source
# File lib/rubocop/cop/lint/mixed_case_range.rb, line 72
def build_source_range(range_start, range_end)
  range_between(range_start.expression.begin_pos, range_end.expression.end_pos)
end
range_for(char) click to toggle source
# File lib/rubocop/cop/lint/mixed_case_range.rb, line 76
def range_for(char)
  RANGES.detect do |range|
    range.include?(char)
  end
end
range_pairs(expr) click to toggle source
# File lib/rubocop/cop/lint/mixed_case_range.rb, line 82
def range_pairs(expr)
  RuboCop::Cop::Utils::RegexpRanges.new(expr).pairs
end
rewrite_regexp_range(source) click to toggle source
# File lib/rubocop/cop/lint/mixed_case_range.rb, line 102
def rewrite_regexp_range(source)
  open, close = source.split('-')
  first = [open, range_for(open).end]
  second = [range_for(close).begin, close]
  "#{first.uniq.join('-')}#{second.uniq.join('-')}"
end
skip_expression?(expr) click to toggle source
# File lib/rubocop/cop/lint/mixed_case_range.rb, line 92
def skip_expression?(expr)
  !(expr.type == :set && expr.token == :character)
end
skip_range?(range_start, range_end) click to toggle source
# File lib/rubocop/cop/lint/mixed_case_range.rb, line 96
def skip_range?(range_start, range_end)
  [range_start, range_end].any? do |bound|
    bound.type == :escape
  end
end
unsafe_range?(range_start, range_end) click to toggle source
# File lib/rubocop/cop/lint/mixed_case_range.rb, line 86
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