File Coverage

blib/lib/Perl/Critic/Policy/ValuesAndExpressions/ProhibitLeadingZeros.pm
Criterion Covered Total %
statement 59 98 60.2
branch 16 68 23.5
condition 3 9 33.3
subroutine 18 18 100.0
pod 4 5 80.0
total 100 198 50.5


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::ValuesAndExpressions::ProhibitLeadingZeros;
2              
3 40     40   26919 use 5.010001;
  40         158  
4 40     40   233 use strict;
  40         97  
  40         955  
5 40     40   212 use warnings;
  40         116  
  40         1023  
6              
7 40     40   209 use Readonly;
  40         144  
  40         1942  
8              
9 40     40   368 use Perl::Critic::Utils qw{ :characters :severities hashify };
  40         98  
  40         2022  
10 40     40   11805 use parent 'Perl::Critic::Policy';
  40         199  
  40         258  
11              
12             our $VERSION = '1.150';
13              
14             #-----------------------------------------------------------------------------
15              
16             Readonly::Scalar my $LEADING_RX => qr<\A [+-]? (?: 0+ _* )+ [1-9]>xms;
17             Readonly::Scalar my $EXPL => [ 58 ];
18              
19             #-----------------------------------------------------------------------------
20              
21             sub supported_parameters {
22             return (
23             {
24 90     90 0 1998 name => 'strict',
25             description =>
26             q<Don't allow any leading zeros at all. Otherwise builtins that deal with Unix permissions, e.g. chmod, don't get flagged.>,
27             default_string => '0',
28             behavior => 'boolean',
29             },
30             );
31             }
32              
33 88     88 1 390 sub default_severity { return $SEVERITY_HIGHEST }
34 92     92 1 406 sub default_themes { return qw< core pbp bugs certrec > }
35 36     36 1 117 sub applies_to { return 'PPI::Token::Number::Octal' }
36              
37             #-----------------------------------------------------------------------------
38              
39             sub violates {
40 14     14 1 42 my ( $self, $elem, undef ) = @_;
41              
42 14 50       93 return if $elem !~ $LEADING_RX;
43 14 50       147 return $self->_create_violation($elem) if $self->{_strict};
44 14 50       66 return if $self->_is_first_argument_of_chmod_or_umask($elem);
45 14 50       404 return if $self->_is_second_argument_of_mkdir($elem);
46 14 50       120 return if $self->_is_second_argument_of_mkfifo($elem);
47 14 50       129 return if $self->_is_third_argument_of_dbmopen($elem);
48 14 50       108 return if $self->_is_fourth_argument_of_sysopen($elem);
49 14         111 return $self->_create_violation($elem);
50             }
51              
52             sub _create_violation {
53 14     14   34 my ($self, $elem) = @_;
54              
55 14         62 return $self->violation(
56             qq<Integer with leading zeros: "$elem">,
57             $EXPL,
58             $elem
59             );
60             }
61              
62             sub _is_first_argument_of_chmod_or_umask {
63 14     14   45 my ($self, $elem) = @_;
64              
65 14         58 my $previous_token = _previous_token_that_isnt_a_parenthesis($elem);
66 14 50       64 return if not $previous_token;
67              
68 14         34 state $is_chmod_or_umask = { hashify( qw( chmod umask ) ) };
69 14         46 return $is_chmod_or_umask->{$previous_token->content()};
70             }
71              
72             sub _is_second_argument_of_mkdir {
73 14     14   37 my ($self, $elem) = @_;
74              
75             # Preceding comma.
76 14         29 my $previous_token = _previous_token_that_isnt_a_parenthesis($elem);
77 14 50       54 return if not $previous_token;
78 14 50       44 return if $previous_token->content() ne $COMMA; # Don't know what it is.
79              
80             # Directory name.
81 0         0 $previous_token =
82             _previous_token_that_isnt_a_parenthesis($previous_token);
83 0 0       0 return if not $previous_token;
84              
85 0         0 $previous_token =
86             _previous_token_that_isnt_a_parenthesis($previous_token);
87 0 0       0 return if not $previous_token;
88              
89 0         0 return $previous_token->content() eq 'mkdir';
90             }
91              
92             sub _is_second_argument_of_mkfifo {
93 14     14   42 my ($self, $elem) = @_;
94              
95             # Preceding comma.
96 14         32 my $previous_token = _previous_token_that_isnt_a_parenthesis($elem);
97 14 50       76 return if not $previous_token;
98 14 50       37 return if $previous_token->content() ne $COMMA; # Don't know what it is.
99              
100             # FIFO name.
101 0         0 $previous_token =
102             _previous_token_that_isnt_a_parenthesis($previous_token);
103 0 0       0 return if not $previous_token;
104              
105 0         0 $previous_token =
106             _previous_token_that_isnt_a_parenthesis($previous_token);
107 0 0       0 return if not $previous_token;
108              
109 0         0 state $is_mkfifo = { hashify( 'mkfifo', 'POSIX::mkfifo' ) };
110 0         0 return $is_mkfifo->{$previous_token->content()};
111             }
112              
113             sub _is_third_argument_of_dbmopen {
114 14     14   39 my ($self, $elem) = @_;
115              
116             # Preceding comma.
117 14         31 my $previous_token = _previous_token_that_isnt_a_parenthesis($elem);
118 14 50       65 return if not $previous_token;
119 14 50       48 return if $previous_token->content() ne $COMMA; # Don't know what it is.
120              
121             # File path.
122 0         0 $previous_token =
123             _previous_token_that_isnt_a_parenthesis($previous_token);
124 0 0       0 return if not $previous_token;
125              
126             # Another comma.
127 0         0 $previous_token =
128             _previous_token_that_isnt_a_parenthesis($previous_token);
129 0 0       0 return if not $previous_token;
130 0 0       0 return if $previous_token->content() ne $COMMA; # Don't know what it is.
131              
132             # Variable name.
133 0         0 $previous_token =
134             _previous_token_that_isnt_a_parenthesis($previous_token);
135 0 0       0 return if not $previous_token;
136              
137 0         0 $previous_token =
138             _previous_token_that_isnt_a_parenthesis($previous_token);
139 0 0       0 return if not $previous_token;
140              
141 0         0 return $previous_token->content() eq 'dbmopen';
142             }
143              
144             sub _is_fourth_argument_of_sysopen {
145 14     14   41 my ($self, $elem) = @_;
146              
147             # Preceding comma.
148 14         28 my $previous_token = _previous_token_that_isnt_a_parenthesis($elem);
149 14 50       44 return if not $previous_token;
150 14 50       43 return if $previous_token->content() ne $COMMA; # Don't know what it is.
151              
152             # Mode.
153 0         0 $previous_token =
154             _previous_token_that_isnt_a_parenthesis($previous_token);
155 0   0     0 while ($previous_token and $previous_token->content() ne $COMMA) {
156 0         0 $previous_token =
157             _previous_token_that_isnt_a_parenthesis($previous_token);
158             }
159 0 0       0 return if not $previous_token;
160 0 0       0 return if $previous_token->content() ne $COMMA; # Don't know what it is.
161              
162             # File name.
163 0         0 $previous_token =
164             _previous_token_that_isnt_a_parenthesis($previous_token);
165 0 0       0 return if not $previous_token;
166              
167             # Yet another comma.
168 0         0 $previous_token =
169             _previous_token_that_isnt_a_parenthesis($previous_token);
170 0 0       0 return if not $previous_token;
171 0 0       0 return if $previous_token->content() ne $COMMA; # Don't know what it is.
172              
173             # File handle.
174 0         0 $previous_token =
175             _previous_token_that_isnt_a_parenthesis($previous_token);
176 0 0       0 return if not $previous_token;
177              
178 0         0 $previous_token =
179             _previous_token_that_isnt_a_parenthesis($previous_token);
180 0 0       0 return if not $previous_token;
181              
182             # GitHub #789
183 0 0       0 if ( $previous_token->content() eq 'my' ) {
184 0         0 $previous_token = _previous_token_that_isnt_a_parenthesis(
185             $previous_token );
186 0 0       0 return if not $previous_token;
187             }
188              
189 0         0 return $previous_token->content() eq 'sysopen';
190             }
191              
192             sub _previous_token_that_isnt_a_parenthesis {
193 70     70   114 my ($elem) = @_;
194              
195 70         104 state $is_paren = { hashify( $LEFT_PAREN, $RIGHT_PAREN ) };
196              
197 70         193 my $previous_token = $elem->previous_token();
198 70   66     1963 while (
      33        
199             $previous_token
200             and (
201             not $previous_token->significant()
202             or $is_paren->{$previous_token->content()}
203             )
204             ) {
205 70         152 $previous_token = $previous_token->previous_token();
206             }
207              
208 70         2035 return $previous_token;
209             }
210              
211             1;
212              
213             __END__
214              
215             #-----------------------------------------------------------------------------
216              
217             =pod
218              
219             =head1 NAME
220              
221             Perl::Critic::Policy::ValuesAndExpressions::ProhibitLeadingZeros - Write C<oct(755)> instead of C<0755>.
222              
223              
224             =head1 AFFILIATION
225              
226             This Policy is part of the core L<Perl::Critic|Perl::Critic>
227             distribution.
228              
229              
230             =head1 DESCRIPTION
231              
232             Perl interprets numbers with leading zeros as octal. If that's what
233             you really want, its better to use C<oct> and make it obvious.
234              
235             $var = 041; # not ok, actually 33
236             $var = oct(41); # ok
237              
238             chmod 0644, $file; # ok by default
239             dbmopen %database, 'foo.db', 0600; # ok by default
240             mkdir $directory, 0755; # ok by default
241             sysopen $filehandle, $filename, O_RDWR, 0666; # ok by default
242             umask 0002; # ok by default
243              
244             use POSIX 'mkfifo';
245             mkfifo $fifo, 0600; # ok by default
246             POSIX::mkfifo $fifo, 0600; # ok by default
247              
248             =head1 CONFIGURATION
249              
250             If you want to ban all leading zeros, set C<strict> to a true value in
251             a F<.perlcriticrc> file.
252              
253             [ValuesAndExpressions::ProhibitLeadingZeros]
254             strict = 1
255              
256              
257             =head1 AUTHOR
258              
259             Jeffrey Ryan Thalhammer <jeff@imaginative-software.com>
260              
261             =head1 COPYRIGHT
262              
263             Copyright (c) 2005-2023 Imaginative Software Systems. All rights reserved.
264              
265             This program is free software; you can redistribute it and/or modify
266             it under the same terms as Perl itself. The full text of this license
267             can be found in the LICENSE file included with this module.
268              
269             =cut
270              
271             # Local Variables:
272             # mode: cperl
273             # cperl-indent-level: 4
274             # fill-column: 78
275             # indent-tabs-mode: nil
276             # c-indentation-style: bsd
277             # End:
278             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :