class RuboCop::Cop::Style::RedundantRegexpCharacterClass
r = /[ab]/
# good
r = %r{/b}
# good
r = %r{/[b]}
# bad
r = /s/
# good
r = /[s]/
# bad
r = /x/
# good
r = /[x]/
# bad
@example
Checks for unnecessary single-element Regexp character classes.
def backslash_b?(elem)
def backslash_b?(elem) # \b's behavior is different inside and outside of a character class, matching word # boundaries outside but backspace (0x08) when inside. elem == '\b' end
def each_redundant_character_class(node)
def each_redundant_character_class(node) each_single_element_character_class(node) do |char_class| next unless redundant_single_element_character_class?(node, char_class) yield char_class.loc.body end end
def each_single_element_character_class(node)
def each_single_element_character_class(node) node.parsed_tree&.each_expression do |expr| next if expr.type != :set || expr.expressions.size != 1 next if expr.negative? next if %i[set posixclass nonposixclass].include?(expr.expressions.first.type) next if multiple_codepoints?(expr.expressions.first) yield expr end end
def multiple_codepoints?(expression)
def multiple_codepoints?(expression) expression.respond_to?(:codepoints) && expression.codepoints.count >= 2 end
def octal_requiring_char_class?(elem)
def octal_requiring_char_class?(elem) # The octal escapes \1 to \7 only work inside a character class # because they would be a backreference outside it. elem.match?(/\A\\[1-7]\z/) end
def on_regexp(node)
def on_regexp(node) each_redundant_character_class(node) do |loc| add_offense( loc, message: format( MSG_REDUNDANT_CHARACTER_CLASS, char_class: loc.source, element: without_character_class(loc) ) ) do |corrector| corrector.replace(loc, without_character_class(loc)) end end end
def redundant_single_element_character_class?(node, char_class)
def redundant_single_element_character_class?(node, char_class) class_elem = char_class.expressions.first.text non_redundant = whitespace_in_free_space_mode?(node, class_elem) || backslash_b?(class_elem) || octal_requiring_char_class?(class_elem) || requires_escape_outside_char_class?(class_elem) !non_redundant end
def requires_escape_outside_char_class?(elem)
def requires_escape_outside_char_class?(elem) REQUIRES_ESCAPE_OUTSIDE_CHAR_CLASS_CHARS.include?(elem) end
def whitespace_in_free_space_mode?(node, elem)
def whitespace_in_free_space_mode?(node, elem) return false unless node.extended? /\s/.match?(elem) end
def without_character_class(loc)
def without_character_class(loc) without_character_class = loc.source[1..-2] # Adds `\` to prevent autocorrection that changes to an interpolated string when `[#]`. # e.g. From `/[#]{0}/` to `/#{0}/` loc.source == '[#]' ? "\\#{without_character_class}" : without_character_class end