forked from rubocop/rubocop
/
redundant_regexp_character_class.rb
98 lines (82 loc) · 2.75 KB
/
redundant_regexp_character_class.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# This cop checks for unnecessary single-element Regexp character classes.
#
# @example
#
# # bad
# r = /[x]/
#
# # good
# r = /x/
#
# # bad
# r = /[\s]/
#
# # good
# r = /\s/
#
# # good
# r = /[ab]/
class RedundantRegexpCharacterClass < Base
extend AutoCorrector
REQUIRES_ESCAPE_OUTSIDE_CHAR_CLASS_CHARS = '.*+?{}()|$'.chars.freeze
MSG_REDUNDANT_CHARACTER_CLASS = 'Redundant single-element character class, ' \
'`%<char_class>s` can be replaced with `%<element>s`.'
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
private
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 node.loc.begin.adjust(begin_pos: 1 + char_class.ts, end_pos: char_class.te)
end
end
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)
yield expr
end
end
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) ||
requires_escape_outside_char_class?(class_elem)
!non_redundant
end
def without_character_class(loc)
loc.source[1..-2]
end
def whitespace_in_free_space_mode?(node, elem)
return false unless node.extended?
/\s/.match?(elem)
end
def backslash_b?(elem)
# \b's behaviour is different inside and outside of a character class, matching word
# boundaries outside but backspace (0x08) when inside.
elem == '\b'
end
def requires_escape_outside_char_class?(elem)
REQUIRES_ESCAPE_OUTSIDE_CHAR_CLASS_CHARS.include?(elem)
end
end
end
end
end