File Coverage

blib/lib/Perl/Lint/Policy/NamingConventions/Capitalization.pm
Criterion Covered Total %
statement 243 253 96.0
branch 138 160 86.2
condition 49 60 81.6
subroutine 15 15 100.0
pod 0 1 0.0
total 445 489 91.0


line stmt bran cond sub pod time code
1             package Perl::Lint::Policy::NamingConventions::Capitalization;
2 134     134   78064 use strict;
  134         181  
  134         3349  
3 134     134   427 use warnings;
  134         158  
  134         2553  
4 134     134   1180 use B::Keywords;
  134         1687  
  134         4789  
5 134     134   53407 use String::CamelCase qw/wordsplit/;
  134         53146  
  134         6779  
6 134     134   972 use Perl::Lint::Constants::Type;
  134         156  
  134         58262  
7 134     134   597 use parent "Perl::Lint::Policy";
  134         169  
  134         623  
8              
9             # TODO integrate error messages
10              
11             use constant {
12 134         232116 EXPL => [45, 46],
13 134     134   6682 };
  134         170  
14              
15             my %local_var_declare_token_types = (
16             &VAR_DECL => 1,
17             &STATE_DECL => 1,
18             );
19              
20             my %global_var_declare_token_types = (
21             &OUR_DECL => 1,
22             &LOCAL_DECL => 1,
23             );
24              
25             my %var_declare_token_types = (
26             %local_var_declare_token_types,
27             %global_var_declare_token_types,
28             );
29              
30             my %var_token_types = (
31             &VAR => 1,
32             &CODE_VAR => 1,
33             &ARRAY_VAR => 1,
34             &HASH_VAR => 1,
35             &GLOBAL_VAR => 1,
36             &GLOBAL_ARRAY_VAR => 1,
37             &GLOBAL_HASH_VAR => 1,
38             &LOCAL_VAR => 1,
39             &LOCAL_ARRAY_VAR => 1,
40             &LOCAL_HASH_VAR => 1,
41             );
42              
43             my %globals = map {$_ => 1} ( # TODO integrate?
44             @B::Keywords::Arrays,
45             @B::Keywords::Hashes,
46             @B::Keywords::Scalars,
47             );
48              
49             sub evaluate {
50 933     933 0 2559 my ($class, $file, $tokens, $src, $args) = @_;
51              
52 933         2078 my $packages_rule = '';
53 933         1569 my $package_exemptions = '';
54              
55 933         1392 my $subroutines_rule = '';
56 933         1138 my $subroutine_exemptions = '';
57              
58 933         1230 my $local_lexical_variables_rule = '';
59 933         1456 my $local_lexical_variable_exemptions = '';
60              
61 933         1650 my $global_variables_rule = '';
62 933         1703 my $global_variable_exemptions = '';
63              
64 933         1074 my $labels_rule = '';
65 933         1403 my $label_exemptions = '';
66              
67 933 100       3952 if (my $this_policies_rule = $args->{capitalization}) {
68 854   100     5747 $packages_rule = $this_policies_rule->{packages} || '';
69 854   100     4842 $package_exemptions = $this_policies_rule->{package_exemptions} || '';
70              
71 854   100     4206 $subroutines_rule = $this_policies_rule->{subroutines} || '';
72 854   100     3821 $subroutine_exemptions = $this_policies_rule->{subroutine_exemptions} || '';
73              
74 854   100     3564 $local_lexical_variables_rule = $this_policies_rule->{local_lexical_variables} || '';
75 854   100     3961 $local_lexical_variable_exemptions = $this_policies_rule->{local_lexical_variable_exemptions} || '';
76              
77 854   100     3043 $global_variables_rule = $this_policies_rule->{global_variables} || '';
78 854   100     3192 $global_variable_exemptions = $this_policies_rule->{global_variable_exemptions} || '';
79              
80 854   100     3830 $labels_rule = $this_policies_rule->{labels} || '';
81 854   100     3761 $label_exemptions = $this_policies_rule->{label_exemptions} || '';
82             }
83              
84 933         1149 my @violations;
85              
86 933         3462 TOP: for (my $i = 0, my $token_type, my $token_data; my $token = $tokens->[$i]; $i++) {
87 8040         7916 $token_type = $token->{type};
88 8040         7242 $token_data = $token->{data};
89              
90             # for variables name
91 8040 100       12365 if ($var_declare_token_types{$token_type}) {
92 681         858 my $exemptions;
93 681         762 my $is_global_var = 0;
94              
95 681         1352 my $checker = \&_is_singlecase;
96 681         1261 my $error_message = '"%s" is not all lower case or all upper case';
97              
98 681 100       1953 if ($local_var_declare_token_types{$token_type}) {
99 288 100       981 if (my $condition = $class->_choose_condition_dispenser($local_lexical_variables_rule)) {
100 272         711 $checker = $condition->{checker};
101 272         418 $error_message = $condition->{error_message};
102             }
103 288         611 $exemptions = $local_lexical_variable_exemptions;
104             }
105             else {
106 393 100       1518 if (my $condition = $class->_choose_condition_dispenser($global_variables_rule)) {
107 321         714 $checker = $condition->{checker};
108 321         599 $error_message = $condition->{error_message};
109             }
110 393         486 $exemptions = $global_variable_exemptions;
111 393         767 $is_global_var = 1;
112             }
113              
114 681 50       2279 $token = $tokens->[++$i] or last;
115 681         1140 $token_type = $token->{type};
116              
117             # when multiple variables declared
118 681 100       1939 if ($token_type == LEFT_PAREN) {
119 3         4 my $lpnum = 1;
120 3         6 for ($i++; $token = $tokens->[$i]; $i++) {
121 12         11 $token_type = $token->{type};
122 12 50       27 if ($token_type == LEFT_PAREN) {
    100          
    100          
123 0         0 $lpnum++;
124             }
125             elsif ($token_type == RIGHT_PAREN) {
126 3 50       6 last if --$lpnum <= 0 ;
127             }
128             elsif ($var_token_types{$token_type}) {
129             # To ignore variables from other packages
130             # TODO
131 6   50     10 my $next_token = $tokens->[$i+1] || {};
132 6 50 33     21 if ($next_token->{type} && $next_token->{type} == NAMESPACE_RESOLVER) {
133 0         0 next;
134             }
135              
136 6         6 $token_data = $token->{data};
137              
138 6 50 66     10 if ($is_global_var && $globals{$token_data}) {
139 0         0 next;
140             }
141              
142 6         7 $token_data = substr($token_data, 1); # to exclude sigils
143              
144 6 50       20 if ($token_data =~ /\A$exemptions\Z/) {
145 0         0 next;
146             }
147              
148 6 50       11 if (ref $checker ne 'CODE') {
149 0 0       0 if ($token_data =~ /\A$checker\Z/) {
150 0         0 next;
151             }
152              
153             push @violations, {
154             filename => $file,
155             line => $token->{line},
156 0         0 description => sprintf($error_message, $token_data),
157             explanation => EXPL,
158             policy => __PACKAGE__,
159             };
160 0         0 next;
161             }
162              
163 6         10 for my $part (wordsplit($token_data)) {
164 6 100       30 if (!$checker->($part)) { # include Upper Case
165             push @violations, {
166             filename => $file,
167             line => $token->{line},
168 2         10 description => sprintf($error_message, $token_data),
169             explanation => EXPL,
170             policy => __PACKAGE__,
171             };
172 2         6 last;
173             }
174             }
175             }
176             }
177              
178 3         7 next;
179             }
180              
181             # To ignore variables from other packages
182             # TODO
183 678   50     2036 my $next_token = $tokens->[$i+1] || {};
184 678 100 66     3567 if ($next_token->{type} && $next_token->{type} == NAMESPACE_RESOLVER) {
185 36         121 next;
186             }
187              
188 642         1107 $token_data = $token->{data};
189              
190 642 100 66     2448 if ($is_global_var && $globals{$token_data}) {
191 105         341 next;
192             }
193              
194 537         1285 $token_data = substr($token_data, 1); # to exclude sigils
195              
196 537 100       7091 if ($token_data =~ /\A$exemptions\Z/) {
197 104         413 next;
198             }
199              
200 433 100       1549 if (ref $checker ne 'CODE') {
201 45 100       260 if ($token_data =~ /\A$checker\Z/) {
202 25         87 next;
203             }
204              
205             push @violations, {
206             filename => $file,
207             line => $token->{line},
208 20         139 description => sprintf($error_message, $token_data),
209             explanation => EXPL,
210             policy => __PACKAGE__,
211             };
212 20         54 next;
213             }
214              
215 388         1697 for my $part (wordsplit($token_data)) {
216 1218 100       6953 if (!$checker->($part)) { # include Upper Case
217             push @violations, {
218             filename => $file,
219             line => $token->{line},
220 139         1691 description => sprintf($error_message, $token_data),
221             explanation => EXPL,
222             policy => __PACKAGE__,
223             };
224 139         269 last;
225             }
226             }
227 388         1398 next;
228             }
229              
230             # for subroutines name
231 7359 100       9544 if ($token_type == FUNCTION_DECL) {
232 211 50       620 $token = $tokens->[++$i] or last;
233 211         391 $token_type = $token->{type};
234              
235 211 100       803 if ($token_type == NAMESPACE) {
    50          
236 34         69 my $last_namespace_token;
237              
238 34         122 for ($i++; $token = $tokens->[$i]; $i++) {
239 170         179 $token_type = $token->{type};
240              
241 170 100       383 if ($token_type == NAMESPACE) {
    100          
242 68         111 $last_namespace_token = $token;
243             }
244             elsif ($token_type != NAMESPACE_RESOLVER) {
245 34         56 last;
246             }
247             }
248              
249 34         53 $token = $last_namespace_token;
250             }
251             elsif ($token_type != FUNCTION) {
252 0         0 next;
253             }
254              
255 211         500 my $checker = \&_is_started_with_lower;
256 211         427 my $error_message = '"%s" does not start with a lower case letter';
257 211 100       743 if (my $condition = $class->_choose_condition_dispenser($subroutines_rule)) {
258 204         450 $checker = $condition->{checker};
259 204         379 $error_message = $condition->{error_message};
260             }
261              
262 211         377 $token_data = $token->{data};
263              
264 211 100       2593 if ($token_data =~ /\A$subroutine_exemptions\Z/) {
265 34         158 next;
266             }
267              
268 177 100       694 if (ref $checker ne 'CODE') {
269 6 100       62 if ($token_data =~ /\A$checker\Z/) {
270 4         20 next;
271             }
272              
273             push @violations, {
274             filename => $file,
275             line => $token->{line},
276 2         23 description => sprintf($error_message, $token_data),
277             explanation => EXPL,
278             policy => __PACKAGE__,
279             };
280 2         11 next;
281             }
282              
283 171         772 for my $part (wordsplit($token_data)) { # to exclude sigils
284 413 100       2785 if (!$checker->($part)) {
285             push @violations, {
286             filename => $file,
287             line => $token->{line},
288 26         333 description => sprintf($error_message, $token_data),
289             explanation => EXPL,
290             policy => __PACKAGE__,
291             };
292 26         46 last;
293             }
294             }
295              
296 171         674 next;
297             }
298              
299             # for package's name
300 7148 100       9601 if ($token_type == PACKAGE) {
301 111 50       375 $token = $tokens->[++$i] or last;
302 111         264 $token_type = $token->{type};
303 111         202 $token_data = $token->{data};
304              
305             # special case: main
306 111 100 100     460 if ($token_type == CLASS && $token_data eq 'main') {
307 1         3 next;
308             }
309              
310 110 100 66     622 if ($package_exemptions && $token_data =~ /\A$package_exemptions\Z/) {
311 34         113 next;
312             }
313              
314 76         194 my $checker = \&_is_started_with_upper;
315 76         125 my $error_message = '"%s" does not start with a upper case letter';
316 76 100       289 if (my $condition = $class->_choose_condition_dispenser($packages_rule)) {
317 68         193 $checker = $condition->{checker};
318 68         105 $error_message = $condition->{error_message};
319             }
320              
321 76         119 my $package_full_name = $token_data;
322 76 100       265 if (ref $checker eq 'CODE') {
    100          
323 70         318 for my $part (wordsplit($token_data)) {
324 201 100       1213 if (!$checker->($part)) {
325             push @violations, {
326             filename => $file,
327             line => $token->{line},
328 13         157 description => sprintf($error_message, $token_data),
329             explanation => EXPL,
330             policy => __PACKAGE__,
331             };
332              
333 13         83 next TOP;
334             }
335             }
336             }
337             elsif ($token_type == CLASS) {
338 3 100       46 if ($package_full_name =~ /\A$checker\Z/) {
339 2         8 next;
340             }
341              
342             push @violations, {
343             filename => $file,
344             line => $token->{line},
345 1         11 description => sprintf($error_message, $token_data),
346             explanation => EXPL,
347             policy => __PACKAGE__,
348             };
349 1         5 next;
350             }
351              
352 60 100       180 if ($token_type == NAMESPACE) {
353 38 100       118 if (ref $checker ne 'CODE') {
354 3         15 for ($i++; $token = $tokens->[$i]; $i++) { # TODO
355 9         12 $token_type = $token->{type};
356 9         11 $token_data = $token->{data};
357 9 100 100     32 if ($token_type == NAMESPACE || $token_type == NAMESPACE_RESOLVER) {
358 6         19 $package_full_name .= $token_data;
359             }
360             else {
361 3         6 last;
362             }
363             }
364 3 100       47 if ($package_full_name =~ /\A$checker\Z/) {
365 2         9 next;
366             }
367              
368             push @violations, {
369             filename => $file,
370             line => $token->{line},
371 1         17 description => sprintf($error_message, $token_data),
372             explanation => EXPL,
373             policy => __PACKAGE__,
374             };
375 1         7 next;
376             }
377              
378 35         125 SCAN_NAMESPACE: for ($i++; $token = $tokens->[$i]; $i++) { # TODO
379 92         132 $token_type = $token->{type};
380 92         117 $token_data = $token->{data};
381 92 100       272 if ($token_type == NAMESPACE) {
    100          
382 34         93 for my $part (wordsplit($token_data)) {
383 104 100       455 if (!$checker->($part)) {
384             push @violations, {
385             filename => $file,
386             line => $token->{line},
387 12         153 description => sprintf($error_message, $token_data),
388             explanation => EXPL,
389             policy => __PACKAGE__,
390             };
391 12         36 last SCAN_NAMESPACE;
392             }
393             }
394             }
395             elsif ($token_type != NAMESPACE_RESOLVER) {
396 23         38 last;
397             }
398             }
399             }
400              
401 57         221 next;
402             }
403              
404             # for constant variables
405             # * Readonly::Scalar
406             # * const (From Const::Fast)
407             {
408 7037         4500 my $is_const_decl = 0;
  7037         4861  
409 7037 100 100     21638 if ($token_type == NAMESPACE && $token_data eq 'Readonly') {
    100 100        
410 3         4 $i += 2;
411 3 50       7 $token = $tokens->[$i] or last;
412 3 50 33     11 if ($token->{type} == NAMESPACE && $token->{data} eq 'Scalar') {
413 3         4 $is_const_decl = 1;
414             }
415             }
416             elsif ($token_type == KEY && $token_data eq 'const') {
417 2         3 $is_const_decl = 1;
418             }
419              
420 7037 100       9122 if ($is_const_decl) {
421 5 50       8 $token = $tokens->[++$i] or last;
422 5 50       10 if (!$var_token_types{$token->{type}}) {
423 5 50       8 $token = $tokens->[++$i] or last;
424             }
425              
426 5 50       8 if ($var_token_types{$token->{type}}) {
427 5         6 $token_data = $token->{data};
428 5 100       13 if (uc $token_data ne $token_data) {
429             push @violations, {
430             filename => $file,
431             line => $token->{line},
432 3         14 description => sprintf('"%s" is not all upper case', $token_data),
433             explanation => EXPL,
434             policy => __PACKAGE__,
435             };
436             }
437             }
438              
439 5         5 next;
440             }
441             }
442              
443             # for constants
444             # * use constant
445 7037 100       8435 if ($token_type == USE_DECL) {
446 9 50       16 $token = $tokens->[++$i] or last;
447 9 100 66     34 if ($token->{type} != USED_NAME || $token->{data} ne 'constant') {
448 8         11 next;
449             }
450              
451 1         4 for ($i++; $token = $tokens->[$i]; $i++) {
452 36 100       40 if ($token->{type} != ARROW) {
453 30         35 next;
454             }
455              
456 6         7 my $key = $tokens->[$i-1]->{data};
457 6 50       12 if (uc $key ne $key) {
458             push @violations, {
459             filename => $file,
460             line => $token->{line},
461 0         0 description => sprintf('"%s" is not all upper case', $key),
462             explanation => EXPL,
463             policy => __PACKAGE__,
464             };
465             }
466             }
467              
468 1         2 next;
469             }
470              
471             # for LABELs
472 7028 100       14863 if ($token_type == KEY) {
473 207 50       598 my $next_token = $tokens->[$i+1] or last;
474 207 100       542 if ($next_token->{type} != COLON) {
475 139         282 next;
476             }
477              
478 68 100       2148 if ($token_data =~ /\A$label_exemptions\Z/) {
479 34         142 next;
480             }
481              
482 34         100 my $checker = \&_is_all_upper;
483 34         73 my $error_message = '"%s" is not all upper case';
484 34 50       150 if (my $condition = $class->_choose_condition_dispenser($labels_rule) ) {
485 34         78 $checker = $condition->{checker};
486 34         74 $error_message = $condition->{error_message};
487             }
488              
489 34 100       136 if (ref $checker ne 'CODE') {
490 3 100       31 if ($token_data =~ /\A$checker\Z/) {
491 2         9 next;
492             }
493              
494             push @violations, {
495             filename => $file,
496             line => $token->{line},
497 1         9 description => sprintf($error_message, $token_data),
498             explanation => EXPL,
499             policy => __PACKAGE__,
500             };
501 1         5 next;
502             }
503              
504 31 100       116 if (!$checker->($token_data)) {
505             push @violations, {
506             filename => $file,
507             line => $token->{line},
508 11         150 description => sprintf($error_message, $token_data),
509             explanation => EXPL,
510             policy => __PACKAGE__,
511             };
512             }
513              
514 31         129 next;
515             }
516              
517             }
518              
519 933         4847 return \@violations;
520             }
521              
522             sub _choose_condition_dispenser {
523 1002     1002   1734 my ($self, $rule) = @_;
524              
525 1002 100       5618 if ($rule eq ':single_case') {
    100          
    100          
    100          
    100          
    100          
    100          
526             return {
527 115         772 error_message => '"%s" is not all lower case or all upper case',
528             checker => \&_is_singlecase,
529             };
530             }
531             elsif ($rule eq ':all_lower') {
532             return {
533 221         1363 error_message => '"%s" is not all lower case',
534             checker => \&_is_all_lower,
535             };
536             }
537             elsif ($rule eq ':all_upper') {
538             return {
539 115         907 error_message => '"%s" is not all upper case',
540             checker => \&_is_all_upper,
541             };
542             }
543             elsif ($rule eq ':starts_with_lower') {
544             return {
545 115         877 error_message => '"%s" does not start with a lower case letter',
546             checker => \&_is_started_with_lower,
547             };
548             }
549             elsif ($rule eq ':starts_with_upper') {
550             return {
551 115         829 error_message => '"%s" does not start with a upper case letter',
552             checker => \&_is_started_with_upper,
553             };
554             }
555             elsif ($rule eq ':no_restriction') {
556             return {
557 138         924 error_message => 'There is a bug in Perl::Critic if you are reading this',
558             checker => \&_everything_will_be_alright,
559             }
560             }
561             elsif ($rule) {
562             return {
563 80         499 error_message => qq{"%s" is not matched with $rule},
564             checker => $rule, # XXX
565             };
566             }
567              
568 103         217 return;
569             }
570              
571             sub _is_all_lower {
572 416     416   497 my ($part) = @_;
573 416         1348 return lc($part) eq $part;
574             }
575              
576             sub _is_all_upper {
577 196     196   286 my ($part) = @_;
578 196         643 return uc($part) eq $part;
579             }
580              
581             sub _is_singlecase {
582 332     332   307 my ($part) = @_;
583 332   100     1311 return uc($part) eq $part || lc($part) eq $part;
584             }
585              
586             sub _is_started_with_lower {
587 301     301   269 my ($part) = @_;
588 301         909 return lcfirst($part) eq $part;
589             }
590              
591             sub _is_started_with_upper {
592 304     304   354 my ($part) = @_;
593 304         852 return ucfirst($part) eq $part;
594             }
595              
596             sub _everything_will_be_alright {
597 424     424   891 return 1;
598             }
599              
600             1;
601