File Coverage

blib/lib/Perl/Lint/Policy/ValuesAndExpressions/ProhibitMagicNumbers.pm
Criterion Covered Total %
statement 189 194 97.4
branch 124 152 81.5
condition 58 73 79.4
subroutine 10 10 100.0
pod 0 1 0.0
total 381 430 88.6


line stmt bran cond sub pod time code
1             package Perl::Lint::Policy::ValuesAndExpressions::ProhibitMagicNumbers;
2 134     134   71836 use strict;
  134         179  
  134         3151  
3 134     134   409 use warnings;
  134         148  
  134         2833  
4 134     134   385 no warnings qw/numeric/;
  134         143  
  134         3928  
5 134     134   773 use Perl::Lint::Constants::Type;
  134         148  
  134         56183  
6 134     134   530 use parent "Perl::Lint::Policy";
  134         148  
  134         550  
7              
8             use constant {
9 134         170406 DESC => 'Unnamed numeric literals make code less maintainable', # TODO
10             EXPL => 'Unnamed numeric literals make code less maintainable',
11 134     134   6296 };
  134         153  
12              
13             my $file;
14             my $tokens;
15              
16             my %allowed_values;
17             my %allowed_types;
18             my %constant_creator_subroutines;
19             my $allow_to_the_right_of_a_fat_comma;
20             my $is_readonly_array1_ctx;
21              
22             sub evaluate {
23 126     126 0 181 my $class = shift;
24 126         138 $file = shift;
25 126         116 $tokens = shift;
26 126         1748 my ($src, $args) = @_;
27              
28 126         367 %allowed_values = (
29             0 => 1,
30             1 => 1,
31             2 => 1,
32             );
33              
34 126         238 %allowed_types = (
35             Int => 1,
36             Float => 1,
37             );
38              
39 126         273 %constant_creator_subroutines = (
40             plan => 1,
41             Readonly => 1,
42             const => 1,
43             );
44              
45 126         163 $allow_to_the_right_of_a_fat_comma = 1;
46              
47             # initializing
48 126 100       279 if (my $this_policies_arg = $args->{prohibit_magic_numbers}) {
49             $allow_to_the_right_of_a_fat_comma =
50 56   100     176 $this_policies_arg->{allow_to_the_right_of_a_fat_comma} // 1;
51              
52 56         65 my $allowed_values = $this_policies_arg->{allowed_values};
53 56 100       103 if (defined $allowed_values) {
54 29         36 delete $allowed_values{2}; # remove `2` from allowed list when allowed_values is specified
55              
56 29         81 for my $allowed_value (split /\s+/, $allowed_values) {
57 36         65 my ($begin, $end) = split /[.][.]/, $allowed_value; # for range notation (e.g. `1..42`)
58 36 100 66     122 if (defined $begin && defined $end) {
59             # used range notation
60 8         20 my ($delta) = $end =~ /:by [(] (.+) [)] \z/x; # for range notation with by (e.g. `-2.0..2.0:by(0.5)`)
61 8   100     25 $delta //= 1; # default delta
62              
63 8         39 for (my $num = $begin; $num <= $end; $num += $delta) {
64 53         145 $allowed_values{$num} = 1;
65             }
66             }
67             else {
68             # not used range notation
69 28         52 $allowed_values{$allowed_value} = 1;
70             }
71             }
72             }
73              
74 56         75 my $allowed_types = $this_policies_arg->{allowed_types};
75 56 100       103 if (defined $allowed_types) {
76 19         26 delete $allowed_types{Float}; # remove `Float` from allowed types list when allowed_types is specified
77              
78 19         44 for my $allowed_type (split /\s+/, $allowed_types) {
79 15         25 $allowed_types{$allowed_type} = 1;
80             }
81             }
82              
83 56         58 my $constant_creator_subroutines = $this_policies_arg->{constant_creator_subroutines};
84 56 100       106 if (defined $constant_creator_subroutines) {
85 1         14 for my $sub (split /\s+/, $constant_creator_subroutines) {
86 1         4 $constant_creator_subroutines{$sub} = 1;
87             }
88             }
89             }
90              
91 126         128 my @violations;
92 126         295 for (my $i = 0, my $token_type, my $token_data; my $token = $tokens->[$i]; $i++) {
93 834         538 $is_readonly_array1_ctx = 0;
94              
95 834         593 $token_type = $token->{type};
96 834         629 $token_data = $token->{data};
97              
98 834 100 100     2725 if (
      66        
      66        
99             $token_type == USE_DECL ||
100             $token_type == REQUIRE_DECL ||
101             ($token_type == KEY && $constant_creator_subroutines{$token_data})
102             ) {
103 56         88 for ($i++; $token = $tokens->[$i]; $i++) {
104 267         179 $token_type = $token->{type};
105 267 100       436 if ($token_type == SEMI_COLON) {
106 56         63 last;
107             }
108             }
109 56         90 next;
110             }
111              
112 778 100 100     1059 if ($token_type == NAMESPACE && $token_data eq 'Readonly') {
113 8 50       14 $token = $tokens->[++$i] or last;
114              
115 8 50       14 if ($token->{type} == NAMESPACE_RESOLVER) {
116 8 50       12 $token = $tokens->[++$i] or last;
117 8         6 $token_data = $token->{data};
118              
119 8 50       12 if ($token->{type} == NAMESPACE) {
120 8 100 100     31 if ($token_data eq 'Scalar' || $token_data eq 'Array') {
    100          
121             # when `Readonly::Scalar` or `Readonly::Array`,
122             # skip tokens to semi colon (means don't evaluate).
123 2         5 for ($i++; $token = $tokens->[$i]; $i++) {
124 14         11 $token_type = $token->{type};
125 14 100       31 if ($token_type == SEMI_COLON) {
126 2         3 last;
127             }
128             }
129 2         3 next;
130             }
131             elsif ($token_data eq 'Array1') {
132             # when `Readonly::Array1`
133 1         2 $i += 2; # skip to assigning token
134              
135 1         2 $token = $tokens->[$i];
136 1         1 $token_type = $token->{type};
137 1         1 $token_data = $token->{data};
138              
139 1         2 $is_readonly_array1_ctx = 1;
140              
141             # no break!
142             }
143             }
144             }
145             }
146              
147             # for the $VERSION variable
148 776 50 66     1027 if (
      66        
149             $token_data eq '$VERSION' &&
150             ($token_type == VAR || $token_type == GLOBAL_VAR)
151             ) {
152             # skip to end of line. Don't evaluate it.
153 151         198 for ($i++; $token = $tokens->[$i]; $i++) {
154 1518 100       2497 last if $token->{type} == SEMI_COLON;
155             }
156 151         199 next;
157             }
158              
159 625 100 100     1186 if (
      66        
      66        
160             $token_type == ASSIGN ||
161             ($token_type == ARROW && (!$allow_to_the_right_of_a_fat_comma || $is_readonly_array1_ctx))
162             ) {
163 94         75 push @violations, @{$class->_scan(\$i)};
  94         212  
164 94         221 next;
165             }
166              
167 531 100       650 if ($token_type == FUNCTION) {
168 4         6 $token = $tokens->[++$i];
169              
170 4         4 my $statement = [];
171 4         6 my @statements = ();
172              
173 4         3 my $lbnum = 1;
174 4         9 for ($i++; $token = $tokens->[$i]; $i++) {
175 19         13 $token_type = $token->{type};
176              
177 19 50       34 if ($token_type == LEFT_BRACE) {
    100          
    100          
178 0         0 $lbnum++;
179             }
180             elsif ($token_type == RIGHT_BRACE) {
181 4 50       9 last if --$lbnum <= 0;
182             }
183             elsif ($token_type == SEMI_COLON) {
184 5         15 push @statements, $statement;
185 5         13 $statement = [];
186             }
187             else {
188 10         19 push @$statement, $token;
189             }
190             }
191              
192 4 100       7 if (scalar @statements > 1) { # when exists multiple statements in function
193 2         4 my $last_statement = pop @statements;
194              
195 2 50       5 my $return_value_token = pop @$last_statement or next;
196 2 50       6 if ($return_value_token->{type} == RETURN) {
197 0 0       0 $return_value_token = pop @$last_statement or next;
198             }
199              
200 2         2 my $invalid_token;
201 2 50       5 if ($return_value_token->{type} == INT) {
    0          
202 2         5 $invalid_token = $class->_validate_int_token($return_value_token);
203             }
204             elsif ($return_value_token->{type} == DOUBLE) {
205 0         0 $invalid_token = $class->_validate_doble_token($return_value_token);
206             }
207              
208 2 50       6 if ($invalid_token) {
209             push @violations, {
210             filename => $file,
211             line => $token->{line},
212 2         10 description => DESC,
213             explanation => EXPL,
214             policy => __PACKAGE__,
215             };
216             }
217             }
218              
219 4         11 next;
220             }
221              
222 527 100 100     1278 if ($token_type == FOR_STATEMENT || $token_type == FOREACH_STATEMENT) {
223 3         8 for ($i++; $token = $tokens->[$i]; $i++) {
224 22         17 $token_type = $token->{type};
225              
226 22 100 100     83 if ($token_type == SEMI_COLON || $token_type == LEFT_BRACE) {
    100          
227 3         5 last;
228             }
229             elsif ($token_type == SLICE) { # e.g. for my $foo (1..42)
230 2 50       6 my $begin = $tokens->[$i-1] or last;
231 2 50       4 my $end = $tokens->[$i+1] or last;
232 2 100 66     11 if ($begin->{type} == INT && $end->{type} == INT) {
233 1 50 33     7 if (!$allowed_values{$begin->{data}} || !$allowed_values{$end->{data}}) {
234             push @violations, {
235             filename => $file,
236             line => $token->{line},
237 1         6 description => DESC,
238             explanation => EXPL,
239             policy => __PACKAGE__,
240             };
241             }
242             }
243             }
244             }
245 3         5 next;
246             }
247              
248             # for index of array
249 524 100       1088 if ($token_type == LEFT_BRACKET) {
250 4 50       11 my $next_token = $tokens->[$i+1] or last;
251 4 50       10 if ($next_token->{type} == INT) {
252 4         3 my $int_token = $next_token;
253 4 50       8 $next_token = $tokens->[$i+2] or last;
254              
255 4         6 my $invalid_token;
256 4 100       9 if ($next_token->{type} == RIGHT_BRACKET) {
    50          
257 3         6 my $num = $int_token->{data} + 0;
258 3 100 100     13 if (!$allowed_values{$num} && $num ne -1) { # -1 is allowed specially when it is used as index of array
259 1         2 $invalid_token = $int_token;
260             }
261             }
262             elsif ($next_token->{type} != COMMA) { # if it is not enumeration (probably it is any handling for index of array)
263 1         2 $invalid_token = $next_token;
264             }
265              
266 4 100       7 if ($invalid_token) {
267             push @violations, {
268             filename => $file,
269             line => $invalid_token->{line},
270 2         10 description => DESC,
271             explanation => EXPL,
272             policy => __PACKAGE__,
273             };
274             }
275             }
276 4         8 next;
277             }
278             }
279              
280 126         427 return \@violations;
281             }
282              
283             my $is_in_assigning_context;
284             sub _scan {
285 273     273   233 my ($class, $i) = @_;
286              
287 273 50       425 my $token = $tokens->[$$i] or return;
288 273         204 my $token_type = $token->{type};
289 273         224 my $token_data = $token->{data};
290              
291 273 100       397 if ($token_type == ASSIGN) {
    100          
292 89         74 $is_in_assigning_context = 1;
293              
294 89 50       168 $token = $tokens->[++$$i] or return;
295 89         74 $token_type = $token->{type};
296 89         93 $token_data = $token->{data};
297             }
298             elsif ($token_type == ARROW) {
299 13         9 $is_in_assigning_context = 0;
300 13 100 100     31 if (!$allow_to_the_right_of_a_fat_comma || $is_readonly_array1_ctx) {
301 10         8 $is_in_assigning_context = 1;
302             }
303              
304 13 50       23 $token = $tokens->[++$$i] or return;
305 13         11 $token_type = $token->{type};
306 13         10 $token_data = $token->{data};
307             }
308              
309 273         176 my $invalid_token;
310              
311             my @violations;
312 273 100       518 if ($token_type == DOUBLE) {
    100          
    100          
    100          
313 39         87 $invalid_token = $class->_validate_doble_token($token, $$i);
314             }
315             elsif ($token_type == INT) {
316 122         183 $invalid_token = $class->_validate_int_token($token);
317             }
318             elsif ($token_type == LEFT_PAREN) {
319 9         11 my $lpnum = 1;
320 9         19 for ($$i++; $token = $tokens->[$$i]; $$i++) {
321 60         41 $token_type = $token->{type};
322 60 100       91 if ($token_type == LEFT_PAREN) {
    100          
323 1         4 $lpnum++;
324             }
325             elsif ($token_type == RIGHT_PAREN) {
326 10 100       21 last if --$lpnum <= 0;
327             }
328             else {
329 49         24 push @violations, @{$class->_scan($i)};
  49         68  
330             }
331             }
332             }
333             elsif ($token_type == LEFT_BRACKET) {
334 12         14 my $lbnum = 1;
335              
336 12         31 for ($$i++; $token = $tokens->[$$i]; $$i++) {
337 146         113 $token_type = $token->{type};
338 146 100       193 if ($token_type == LEFT_BRACKET) {
    100          
339 2         5 $lbnum++;
340             }
341             elsif ($token_type == RIGHT_BRACKET) {
342 14 100       35 last if --$lbnum <= 0;
343             }
344             else {
345 130         88 push @violations, @{$class->_scan($i)};
  130         163  
346             }
347             }
348             }
349              
350 273 100 100     736 if ($is_in_assigning_context && $invalid_token) {
351             push @violations, {
352             filename => $file,
353             line => $invalid_token->{line},
354 42         181 description => DESC,
355             explanation => EXPL,
356             policy => __PACKAGE__,
357             };
358             }
359              
360 273         577 return \@violations;
361             }
362              
363             sub _validate_int_token {
364 124     124   99 my ($class, $token) = @_;
365              
366 124         111 my $token_data = $token->{data};
367              
368 124 100       315 if (my ($base_type) = $token_data =~ /\A[0-9]([b0xe]).+\z/) {
369 20 100       81 if ($1 eq 'b') {
    100          
    100          
    50          
370 5 100       15 return $token if !$allowed_types{Binary};
371             }
372             elsif ($1 eq '0') {
373 6 100       14 return $token if !$allowed_types{Octal};
374             }
375             elsif ($1 eq 'x') {
376 5 100       15 return $token if !$allowed_types{Hex};
377             }
378             elsif ($1 eq 'e') {
379 4 100       13 return $token if !$allowed_types{Exp};
380             }
381             }
382              
383 114 50       192 if (!$allowed_types{Int}) {
384 0         0 return $token;
385             }
386              
387 114 100 66     343 if (!$allowed_values{all_integers} && !$allowed_values{$token_data+0}) { # `+0` to convert to number
388 27         43 return $token;
389             }
390              
391 87         103 return;
392             }
393              
394             sub _validate_doble_token {
395 39     39   39 my ($class, $token, $i) = @_;
396              
397 39         38 my $token_data = $token->{data};
398              
399 39 100 33     417 if ($i && $allowed_types{Float} && $allowed_values{$token_data+0}) { # `+0` to convert to number
    100 100        
      66        
400 30         41 my $next_token = $tokens->[$i+1];
401 30 50 66     104 if ($next_token && $next_token->{type} == DOUBLE) {
402 0         0 return $next_token;
403             }
404             }
405             elsif (!$allowed_values{all_integers} || $token_data !~ /[.]0+\z/) {
406 8         18 return $token;
407             }
408              
409 31         38 return;
410             }
411              
412             1;
413