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   27125 use 5.010001;
  40         188  
4 40     40   281 use strict;
  40         113  
  40         882  
5 40     40   215 use warnings;
  40         127  
  40         1161  
6              
7 40     40   235 use English qw(-no_match_vars);
  40         132  
  40         253  
8 40     40   14562 use List::SomeUtils qw(all);
  40         113  
  40         1923  
9 40     40   268 use Readonly;
  40         125  
  40         1940  
10              
11 40     40   287 use Perl::Critic::Utils qw{ :booleans :severities };
  40         124  
  40         1919  
12              
13 40     40   6455 use parent 'Perl::Critic::Policy';
  40         108  
  40         257  
14              
15             our $VERSION = '1.148';
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 1687 sub supported_parameters { return qw() }
36 93     93 1 758 sub default_severity { return $SEVERITY_LOWEST }
37 84     84 1 461 sub default_themes { return qw( core pbp cosmetic unicode ) }
38 37     37 1 142 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 59 my ( $self, $elem, $document ) = @_;
47              
48             # optimization: don't bother parsing the regexp if there are no character classes
49 26 100       89 return if $elem !~ m/\[/xms;
50              
51 25 50       200 my $re = $document->ppix_regexp_from_element( $elem ) or return;
52 25 100       127267 $re->failures() and return;
53              
54 24 50       187 my $anyofs = $re->find( 'PPIx::Regexp::Structure::CharClass' )
55             or return;
56 24         7980 foreach my $anyof ( @{ $anyofs } ) {
  24         75  
57 24         36 my $violation;
58 24 100       80 $violation = $self->_get_character_class_violations( $elem, $anyof )
59             and return $violation;
60             }
61              
62 5         23 return; # OK
63             }
64              
65             sub _get_character_class_violations {
66 24     24   56 my ($self, $elem, $anyof) = @_;
67              
68 24         42 my %elements;
69 24         67 foreach my $element ( $anyof->children() ) {
70 63         600 $elements{ _fixup( $element ) } = 1;
71             }
72              
73 24         275 for (my $i = 0; $i < @PATTERNS; $i += 2) { ##no critic (CStyleForLoop)
74 97 100   138   601 if (all { exists $elements{$_} } @{$PATTERNS[$i]}) {
  138         2101  
  97         263  
75 23         72 my $neg = $anyof->negated();
76 23 100       325 my $improvement = $PATTERNS[$i + 1]->[$neg ? 1 : 0];
77 23 100       305 next if !defined $improvement;
78              
79 21 100 100     77 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       61 next if 1 != scalar keys %elements;
83             }
84              
85 19 100       107 my $orig = join q{}, '[', ($neg ? q{^} : ()), @{$PATTERNS[$i]}, ']';
  19         56  
86 19         471 return $self->violation( $DESC . " ($orig vs. $improvement)", $EXPL, $elem );
87             }
88             }
89              
90 5         50 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   222 my ( $element ) = @_;
102 129 100       431 if ( $element->isa( 'PPIx::Regexp::Token::Literal' ) ) {
    100          
103 79         193 my $ord = $element->ordinal();
104 79 100       1570 exists $ORDINALS{$ord} and return $ORDINALS{$ord};
105 56         436 return $element->content();
106             } elsif ( $element->isa( 'PPIx::Regexp::Node' ) ) {
107 22         57 return join q{}, map{ _fixup( $_ ) } $element->elements();
  66         377  
108             } else {
109 28         69 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 :