File: ProhibitFixedStringMatches.pm

package info (click to toggle)
libperl-critic-perl 1.156-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,544 kB
  • sloc: perl: 24,092; lisp: 341; makefile: 7
file content (176 lines) | stat: -rw-r--r-- 5,085 bytes parent folder | download
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package Perl::Critic::Policy::RegularExpressions::ProhibitFixedStringMatches;

use 5.010001;
use strict;
use warnings;
use Readonly;

use Perl::Critic::Utils qw( :severities );

use parent 'Perl::Critic::Policy';

our $VERSION = '1.156';

#-----------------------------------------------------------------------------

Readonly::Scalar my $DESC => q{Use 'eq' or hash instead of fixed-pattern regexps};
Readonly::Scalar my $EXPL => [271,272];

#-----------------------------------------------------------------------------

sub supported_parameters { return qw()                       }
sub default_severity     { return $SEVERITY_LOW              }
sub default_themes       { return qw( core pbp performance ) }
sub applies_to           { return qw(PPI::Token::Regexp::Match
                                     PPI::Token::Regexp::Substitute
                                     PPI::Token::QuoteLike::Regexp) }

#-----------------------------------------------------------------------------

sub violates {
    my ( $self, $elem, $doc ) = @_;

    my $re = $elem->get_match_string();

    # only flag regexps that are anchored front and back
    if ($re =~ m{\A \s*
                 (\\A|\^)  # front anchor == $1
                 (.*?)
                 (\\z|\$)  # end anchor == $2
                 \s* \z}xms) {

        my ($front_anchor, $words, $end_anchor) = ($1, $2, $3);

        # If it's a multiline match, then end-of-line anchors don't represent the whole string
        if ($front_anchor eq q{^} || $end_anchor eq q{$}) {
            my $regexp = $doc->ppix_regexp_from_element( $elem )
                or return;
            return if $regexp->modifier_asserted( 'm' );
        }

        # check for grouping and optional alternation.  Grouping may or may not capture
        if ($words =~ m{\A \s*
                        [(]              # start group
                          (?:[?]:)?      # optional non-capturing indicator
                          \s* (.*?) \s*  # interior of group
                        [)]              # end of group
                        \s* \z}xms) {
            $words = $1;
            $words =~ s/[|]//gxms; # ignore alternation inside of parens -- just look at words
        }

        # Regexps that contain metachars are not fixed strings
        return if $words =~ m/[\\#\$()*+.?\@\[\]^{|}]/xms;


        return $self->violation( $DESC, $EXPL, $elem );

    } else {
        return; # OK
    }
}

1;

__END__

#-----------------------------------------------------------------------------

=pod

=head1 NAME

Perl::Critic::Policy::RegularExpressions::ProhibitFixedStringMatches - Use C<eq> or hash instead of fixed-pattern regexps.


=head1 AFFILIATION

This Policy is part of the core L<Perl::Critic|Perl::Critic>
distribution.


=head1 DESCRIPTION

A regular expression that matches just a fixed set of constant strings
is wasteful of performance and is hard on maintainers.  It is much
more readable and often faster to use C<eq> or a hash to match such
strings.

    # Bad
    my $is_file_function = $token =~ m/\A (?: open | close | read ) \z/xms;

    # Faster and more readable
    my $is_file_function = $token eq 'open' ||
                           $token eq 'close' ||
                           $token eq 'read';

For larger numbers of strings, a hash is superior:

    # Bad
    my $is_perl_keyword =
        $token =~ m/\A (?: chomp | chop | chr | crypt | hex | index
                           lc | lcfirst | length | oct | ord | ... ) \z/xms;

    # Better
    Readonly::Hash my %PERL_KEYWORDS => map {$_ => 1} qw(
        chomp chop chr crypt hex index lc lcfirst length oct ord ...
    );
    my $is_perl_keyword = $PERL_KEYWORD{$token};

Conway also suggests using C<lc()> instead of a case-insensitive match.


=head2 VARIANTS

This policy detects both grouped and non-grouped strings.  The
grouping may or may not be capturing.  The grouped body may or may not
be alternating.  C<\A> and C<\z> are always considered anchoring which
C<^> and C<$> are considered anchoring is the C<m> regexp option is
not in use.  Thus, all of these are violations:

    m/^foo$/;
    m/\A foo \z/x;
    m/\A foo \z/xm;
    m/\A(foo)\z/;
    m/\A(?:foo)\z/;
    m/\A(foo|bar)\z/;
    m/\A(?:foo|bar)\z/;

Furthermore, this policy detects violations in C<m//>, C<s///> and
C<qr//> constructs, as you would expect.


=head1 CONFIGURATION

This Policy is not configurable except for the standard options.


=head1 CREDITS

Initial development of this policy was supported by a grant from the
Perl Foundation.


=head1 AUTHOR

Chris Dolan <cdolan@cpan.org>


=head1 COPYRIGHT

Copyright (c) 2007-2023 Chris Dolan

This program is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.  The full text of this license
can be found in the LICENSE file included with this module

=cut

# Local Variables:
#   mode: cperl
#   cperl-indent-level: 4
#   fill-column: 78
#   indent-tabs-mode: nil
#   c-indentation-style: bsd
# End:
# ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :