File Coverage

blib/lib/Perl/Critic/Policy/RegularExpressions/ProhibitEnumeratedClasses.pm
Criterion Covered Total %
statement 62 62 100.0
branch 26 28 92.8
condition 3 3 100.0
subroutine 16 16 100.0
pod 4 5 80.0
total 111 114 97.3


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::RegularExpressions::ProhibitEnumeratedClasses;
2              
3 40     40   27831 use 5.010001;
  40         199  
4 40     40   307 use strict;
  40         13086  
  40         1244  
5 40     40   262 use warnings;
  40         135  
  40         1231  
6              
7 40     40   266 use English qw(-no_match_vars);
  40         150  
  40         299  
8 40     40   14761 use List::SomeUtils qw(all);
  40         123  
  40         1869  
9 40     40   279 use Readonly;
  40         125  
  40         1849  
10              
11 40     40   316 use Perl::Critic::Utils qw{ :booleans :severities hashify };
  40         3022  
  40         2011  
12              
13 40     40   6779 use parent 'Perl::Critic::Policy';
  40         111  
  40         326  
14              
15             our $VERSION = '1.146';
16              
17             #-----------------------------------------------------------------------------
18              
19             Readonly::Scalar my $DESC => q{Use named character classes};
20             Readonly::Scalar my $EXPL => [248];
21              
22             Readonly::Array my @PATTERNS => ( # order matters: most to least specific
23             [q{ },'\\t','\\r','\\n'] => ['\\s', '\\S'],
24             ['A-Z','a-z','0-9','_'] => ['\\w', '\\W'], # RT 69322
25             ['A-Z','a-z'] => ['[[:alpha:]]','[[:^alpha:]]'],
26             ['A-Z'] => ['[[:upper:]]','[[:^upper:]]'],
27             ['a-z'] => ['[[:lower:]]','[[:^lower:]]'],
28             ['0-9'] => ['\\d','\\D'],
29             ['\w'] => [undef, '\\W'],
30             ['\s'] => [undef, '\\S'],
31             );
32              
33             #-----------------------------------------------------------------------------
34              
35 96     96 0 1716 sub supported_parameters { return qw() }
36 93     93 1 435 sub default_severity { return $SEVERITY_LOWEST }
37 84     84 1 392 sub default_themes { return qw( core pbp cosmetic unicode ) }
38 37     37 1 177 sub applies_to { return qw(PPI::Token::Regexp::Match
39             PPI::Token::Regexp::Substitute
40             PPI::Token::QuoteLike::Regexp) }
41              
42             #-----------------------------------------------------------------------------
43              
44              
45             sub violates {
46 26     26 1 65 my ( $self, $elem, $document ) = @_;
47              
48             # optimization: don't bother parsing the regexp if there are no character classes
49 26 100       93 return if $elem !~ m/\[/xms;
50              
51 25 50       236 my $re = $document->ppix_regexp_from_element( $elem ) or return;
52 25 100       117604 $re->failures() and return;
53              
54 24 50       204 my $anyofs = $re->find( 'PPIx::Regexp::Structure::CharClass' )
55             or return;
56 24         7042 foreach my $anyof ( @{ $anyofs } ) {
  24         109  
57 24         57 my $violation;
58 24 100       109 $violation = $self->_get_character_class_violations( $elem, $anyof )
59             and return $violation;
60             }
61              
62 5         25 return; # OK
63             }
64              
65             sub _get_character_class_violations {
66 24     24   71 my ($self, $elem, $anyof) = @_;
67              
68 24         41 my %elements;
69 24         71 foreach my $element ( $anyof->children() ) {
70 63         578 $elements{ _fixup( $element ) } = 1;
71             }
72              
73 24         331 for (my $i = 0; $i < @PATTERNS; $i += 2) { ##no critic (CStyleForLoop)
74 97 100   138   598 if (all { exists $elements{$_} } @{$PATTERNS[$i]}) {
  138         1904  
  97         286  
75 23         85 my $neg = $anyof->negated();
76 23 100       348 my $improvement = $PATTERNS[$i + 1]->[$neg ? 1 : 0];
77 23 100       322 next if !defined $improvement;
78              
79 21 100 100     97 if ($neg && ! defined $PATTERNS[$i + 1]->[0]) {
80             # the [^\w] => \W rule only applies if \w is the only token.
81             # that is it does not apply to [^\w\s]
82 4 100       52 next if 1 != scalar keys %elements;
83             }
84              
85 19 100       111 my $orig = join q{}, '[', ($neg ? q{^} : ()), @{$PATTERNS[$i]}, ']';
  19         60  
86 19         511 return $self->violation( $DESC . " ($orig vs. $improvement)", $EXPL, $elem );
87             }
88             }
89              
90 5         39 return; # OK
91             }
92              
93             Readonly::Hash my %ORDINALS => (
94             ord "\n" => '\\n',
95             ord "\f" => '\\f',
96             ord "\r" => '\\r',
97             ord q< > => q< >,
98             );
99              
100             sub _fixup {
101 129     129   258 my ( $element ) = @_;
102 129 100       409 if ( $element->isa( 'PPIx::Regexp::Token::Literal' ) ) {
    100          
103 79         189 my $ord = $element->ordinal();
104 79 100       1450 exists $ORDINALS{$ord} and return $ORDINALS{$ord};
105 56         423 return $element->content();
106             } elsif ( $element->isa( 'PPIx::Regexp::Node' ) ) {
107 22         66 return join q{}, map{ _fixup( $_ ) } $element->elements();
  66         320  
108             } else {
109 28         64 return $element->content();
110             }
111             }
112              
113             1;
114              
115             __END__
116              
117             #-----------------------------------------------------------------------------
118              
119             =pod
120              
121             =head1 NAME
122              
123             Perl::Critic::Policy::RegularExpressions::ProhibitEnumeratedClasses - Use named character classes instead of explicit character lists.
124              
125              
126             =head1 AFFILIATION
127              
128             This Policy is part of the core L<Perl::Critic|Perl::Critic>
129             distribution.
130              
131              
132             =head1 DESCRIPTION
133              
134             This policy is not for everyone! If you are working in pure ASCII,
135             then disable it now or you may see some false violations.
136              
137             On the other hand many of us are working in a multilingual world with
138             an extended character set, probably Unicode. In that world, patterns
139             like C<m/[A-Z]/> can be a source of bugs when you really meant
140             C<m/\p{IsUpper}/>. This policy catches a selection of possible
141             incorrect character class usage.
142              
143             Specifically, the patterns are:
144              
145             B<C<[\t\r\n\f\ ]>> vs. B<C<\s>>
146              
147             B<C<[\t\r\n\ ]>> vs. B<C<\s>> (because many people forget C<\f>)
148              
149             B<C<[A-Za-z0-9_]>> vs. B<C<\w>>
150              
151             B<C<[A-Za-z]>> vs. B<C<\p{IsAlphabetic}>>
152              
153             B<C<[A-Z]>> vs. B<C<\p{IsUpper}>>
154              
155             B<C<[a-z]>> vs. B<C<\p{IsLower}>>
156              
157             B<C<[0-9]>> vs. B<C<\d>>
158              
159             B<C<[^\w]>> vs. B<C<\W>>
160              
161             B<C<[^\s]>> vs. B<C<\S>>
162              
163              
164             =head1 CONFIGURATION
165              
166             This Policy is not configurable except for the standard options.
167              
168              
169             =head1 CREDITS
170              
171             Initial development of this policy was supported by a grant from the
172             Perl Foundation.
173              
174              
175             =head1 AUTHOR
176              
177             Chris Dolan <cdolan@cpan.org>
178              
179              
180             =head1 COPYRIGHT
181              
182             Copyright (c) 2007-2021 Chris Dolan. Many rights reserved.
183              
184             This program is free software; you can redistribute it and/or modify
185             it under the same terms as Perl itself. The full text of this license
186             can be found in the LICENSE file included with this module
187              
188             =cut
189              
190             # Local Variables:
191             # mode: cperl
192             # cperl-indent-level: 4
193             # fill-column: 78
194             # indent-tabs-mode: nil
195             # c-indentation-style: bsd
196             # End:
197             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :