File Coverage

blib/lib/Perl/Critic/Policy/RegularExpressions/ProhibitEnumeratedClasses.pm
Criterion Covered Total %
statement 26 59 44.0
branch 1 28 3.5
condition 0 3 0.0
subroutine 12 15 80.0
pod 4 5 80.0
total 43 110 39.0


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::RegularExpressions::ProhibitEnumeratedClasses;
2              
3 40     40   23540 use 5.010001;
  40         140  
4 40     40   222 use strict;
  40         75  
  40         842  
5 40     40   177 use warnings;
  40         63  
  40         1660  
6              
7 40     40   168 use List::SomeUtils qw(all);
  40         77  
  40         1955  
8 40     40   193 use Readonly;
  40         100  
  40         1700  
9              
10 40     40   188 use Perl::Critic::Utils qw( :severities );
  40         81  
  40         1972  
11              
12 40     40   4473 use parent 'Perl::Critic::Policy';
  40         77  
  40         265  
13              
14             our $VERSION = '1.156';
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 90     90 0 593 sub supported_parameters { return qw() }
35 75     75 1 261 sub default_severity { return $SEVERITY_LOWEST }
36 84     84 1 267 sub default_themes { return qw( core pbp cosmetic unicode ) }
37 31     31 1 97 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 1     1 1 10 my ( $self, $elem, $document ) = @_;
46              
47             # optimization: don't bother parsing the regexp if there are no character classes
48 1 50       4 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 :