File Coverage

blib/lib/Perl/Critic/Policy/RegularExpressions/ProhibitEnumeratedClasses.pm
Criterion Covered Total %
statement 24 59 40.6
branch 0 28 0.0
condition 0 3 0.0
subroutine 11 15 73.3
pod 4 5 80.0
total 39 110 35.4


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