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   79180 use strict;
  134         190  
  134         3292  
3 134     134   432 use warnings;
  134         166  
  134         2646  
4 134     134   1242 use B::Keywords;
  134         2262  
  134         4436  
5 134     134   52958 use String::CamelCase qw/wordsplit/;
  134         52870  
  134         6751  
6 134     134   1072 use Perl::Lint::Constants::Type;
  134         155  
  134         59094  
7 134     134   610 use parent "Perl::Lint::Policy";
  134         180  
  134         592  
8              
9             # TODO integrate error messages
10              
11             use constant {
12 134         230795 EXPL => [45, 46],
13 134     134   6797 };
  134         182  
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 1735 my ($class, $file, $tokens, $src, $args) = @_;
51              
52 933         993 my $packages_rule = '';
53 933         1132 my $package_exemptions = '';
54              
55 933         1275 my $subroutines_rule = '';
56 933         917 my $subroutine_exemptions = '';
57              
58 933         989 my $local_lexical_variables_rule = '';
59 933         1221 my $local_lexical_variable_exemptions = '';
60              
61 933         953 my $global_variables_rule = '';
62 933         957 my $global_variable_exemptions = '';
63              
64 933         1275 my $labels_rule = '';
65 933         1052 my $label_exemptions = '';
66              
67 933 100       2718 if (my $this_policies_rule = $args->{capitalization}) {
68 854   100     4549 $packages_rule = $this_policies_rule->{packages} || '';
69 854   100     3756 $package_exemptions = $this_policies_rule->{package_exemptions} || '';
70              
71 854   100     3652 $subroutines_rule = $this_policies_rule->{subroutines} || '';
72 854   100     3959 $subroutine_exemptions = $this_policies_rule->{subroutine_exemptions} || '';
73              
74 854   100     4124 $local_lexical_variables_rule = $this_policies_rule->{local_lexical_variables} || '';
75 854   100     3595 $local_lexical_variable_exemptions = $this_policies_rule->{local_lexical_variable_exemptions} || '';
76              
77 854   100     2750 $global_variables_rule = $this_policies_rule->{global_variables} || '';
78 854   100     3664 $global_variable_exemptions = $this_policies_rule->{global_variable_exemptions} || '';
79              
80 854   100     3479 $labels_rule = $this_policies_rule->{labels} || '';
81 854   100     3579 $label_exemptions = $this_policies_rule->{label_exemptions} || '';
82             }
83              
84 933         797 my @violations;
85              
86 933         3075 TOP: for (my $i = 0, my $token_type, my $token_data; my $token = $tokens->[$i]; $i++) {
87 8040         6345 $token_type = $token->{type};
88 8040         6160 $token_data = $token->{data};
89              
90             # for variables name
91 8040 100       11748 if ($var_declare_token_types{$token_type}) {
92 681         657 my $exemptions;
93 681         984 my $is_global_var = 0;
94              
95 681         1209 my $checker = \&_is_singlecase;
96 681         897 my $error_message = '"%s" is not all lower case or all upper case';
97              
98 681 100       1738 if ($local_var_declare_token_types{$token_type}) {
99 288 100       909 if (my $condition = $class->_choose_condition_dispenser($local_lexical_variables_rule)) {
100 272         396 $checker = $condition->{checker};
101 272         441 $error_message = $condition->{error_message};
102             }
103 288         599 $exemptions = $local_lexical_variable_exemptions;
104             }
105             else {
106 393 100       1131 if (my $condition = $class->_choose_condition_dispenser($global_variables_rule)) {
107 321         620 $checker = $condition->{checker};
108 321         576 $error_message = $condition->{error_message};
109             }
110 393         465 $exemptions = $global_variable_exemptions;
111 393         752 $is_global_var = 1;
112             }
113              
114 681 50       1883 $token = $tokens->[++$i] or last;
115 681         950 $token_type = $token->{type};
116              
117             # when multiple variables declared
118 681 100       1380 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       30 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     23 if ($next_token->{type} && $next_token->{type} == NAMESPACE_RESOLVER) {
133 0         0 next;
134             }
135              
136 6         5 $token_data = $token->{data};
137              
138 6 50 66     11 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       22 if ($token_data =~ /\A$exemptions\Z/) {
145 0         0 next;
146             }
147              
148 6 50       10 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       34 if (!$checker->($part)) { # include Upper Case
165             push @violations, {
166             filename => $file,
167             line => $token->{line},
168 2         11 description => sprintf($error_message, $token_data),
169             explanation => EXPL,
170             policy => __PACKAGE__,
171             };
172 2         8 last;
173             }
174             }
175             }
176             }
177              
178 3         6 next;
179             }
180              
181             # To ignore variables from other packages
182             # TODO
183 678   50     2026 my $next_token = $tokens->[$i+1] || {};
184 678 100 66     3437 if ($next_token->{type} && $next_token->{type} == NAMESPACE_RESOLVER) {
185 36         81 next;
186             }
187              
188 642         924 $token_data = $token->{data};
189              
190 642 100 66     1630 if ($is_global_var && $globals{$token_data}) {
191 105         260 next;
192             }
193              
194 537         931 $token_data = substr($token_data, 1); # to exclude sigils
195              
196 537 100       5661 if ($token_data =~ /\A$exemptions\Z/) {
197 104         369 next;
198             }
199              
200 433 100       902 if (ref $checker ne 'CODE') {
201 45 100       260 if ($token_data =~ /\A$checker\Z/) {
202 25         72 next;
203             }
204              
205             push @violations, {
206             filename => $file,
207             line => $token->{line},
208 20         188 description => sprintf($error_message, $token_data),
209             explanation => EXPL,
210             policy => __PACKAGE__,
211             };
212 20         59 next;
213             }
214              
215 388         1525 for my $part (wordsplit($token_data)) {
216 1218 100       5902 if (!$checker->($part)) { # include Upper Case
217             push @violations, {
218             filename => $file,
219             line => $token->{line},
220 139         1117 description => sprintf($error_message, $token_data),
221             explanation => EXPL,
222             policy => __PACKAGE__,
223             };
224 139         227 last;
225             }
226             }
227 388         1183 next;
228             }
229              
230             # for subroutines name
231 7359 100       8976 if ($token_type == FUNCTION_DECL) {
232 211 50       585 $token = $tokens->[++$i] or last;
233 211         287 $token_type = $token->{type};
234              
235 211 100       705 if ($token_type == NAMESPACE) {
    50          
236 34         46 my $last_namespace_token;
237              
238 34         122 for ($i++; $token = $tokens->[$i]; $i++) {
239 170         150 $token_type = $token->{type};
240              
241 170 100       346 if ($token_type == NAMESPACE) {
    100          
242 68         121 $last_namespace_token = $token;
243             }
244             elsif ($token_type != NAMESPACE_RESOLVER) {
245 34         49 last;
246             }
247             }
248              
249 34         50 $token = $last_namespace_token;
250             }
251             elsif ($token_type != FUNCTION) {
252 0         0 next;
253             }
254              
255 211         362 my $checker = \&_is_started_with_lower;
256 211         279 my $error_message = '"%s" does not start with a lower case letter';
257 211 100       637 if (my $condition = $class->_choose_condition_dispenser($subroutines_rule)) {
258 204         375 $checker = $condition->{checker};
259 204         329 $error_message = $condition->{error_message};
260             }
261              
262 211         341 $token_data = $token->{data};
263              
264 211 100       2073 if ($token_data =~ /\A$subroutine_exemptions\Z/) {
265 34         141 next;
266             }
267              
268 177 100       456 if (ref $checker ne 'CODE') {
269 6 100       52 if ($token_data =~ /\A$checker\Z/) {
270 4         18 next;
271             }
272              
273             push @violations, {
274             filename => $file,
275             line => $token->{line},
276 2         27 description => sprintf($error_message, $token_data),
277             explanation => EXPL,
278             policy => __PACKAGE__,
279             };
280 2         10 next;
281             }
282              
283 171         743 for my $part (wordsplit($token_data)) { # to exclude sigils
284 413 100       2440 if (!$checker->($part)) {
285             push @violations, {
286             filename => $file,
287             line => $token->{line},
288 26         214 description => sprintf($error_message, $token_data),
289             explanation => EXPL,
290             policy => __PACKAGE__,
291             };
292 26         51 last;
293             }
294             }
295              
296 171         655 next;
297             }
298              
299             # for package's name
300 7148 100       8378 if ($token_type == PACKAGE) {
301 111 50       311 $token = $tokens->[++$i] or last;
302 111         123 $token_type = $token->{type};
303 111         147 $token_data = $token->{data};
304              
305             # special case: main
306 111 100 100     401 if ($token_type == CLASS && $token_data eq 'main') {
307 1         3 next;
308             }
309              
310 110 100 66     514 if ($package_exemptions && $token_data =~ /\A$package_exemptions\Z/) {
311 34         102 next;
312             }
313              
314 76         102 my $checker = \&_is_started_with_upper;
315 76         86 my $error_message = '"%s" does not start with a upper case letter';
316 76 100       231 if (my $condition = $class->_choose_condition_dispenser($packages_rule)) {
317 68         122 $checker = $condition->{checker};
318 68         91 $error_message = $condition->{error_message};
319             }
320              
321 76         114 my $package_full_name = $token_data;
322 76 100       203 if (ref $checker eq 'CODE') {
    100          
323 70         238 for my $part (wordsplit($token_data)) {
324 201 100       1015 if (!$checker->($part)) {
325             push @violations, {
326             filename => $file,
327             line => $token->{line},
328 13         104 description => sprintf($error_message, $token_data),
329             explanation => EXPL,
330             policy => __PACKAGE__,
331             };
332              
333 13         61 next TOP;
334             }
335             }
336             }
337             elsif ($token_type == CLASS) {
338 3 100       36 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         8 description => sprintf($error_message, $token_data),
346             explanation => EXPL,
347             policy => __PACKAGE__,
348             };
349 1         4 next;
350             }
351              
352 60 100       197 if ($token_type == NAMESPACE) {
353 38 100       89 if (ref $checker ne 'CODE') {
354 3         9 for ($i++; $token = $tokens->[$i]; $i++) { # TODO
355 9         12 $token_type = $token->{type};
356 9         6 $token_data = $token->{data};
357 9 100 100     33 if ($token_type == NAMESPACE || $token_type == NAMESPACE_RESOLVER) {
358 6         13 $package_full_name .= $token_data;
359             }
360             else {
361 3         4 last;
362             }
363             }
364 3 100       38 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         15 description => sprintf($error_message, $token_data),
372             explanation => EXPL,
373             policy => __PACKAGE__,
374             };
375 1         4 next;
376             }
377              
378 35         100 SCAN_NAMESPACE: for ($i++; $token = $tokens->[$i]; $i++) { # TODO
379 92         83 $token_type = $token->{type};
380 92         106 $token_data = $token->{data};
381 92 100       212 if ($token_type == NAMESPACE) {
    100          
382 34         84 for my $part (wordsplit($token_data)) {
383 104 100       417 if (!$checker->($part)) {
384             push @violations, {
385             filename => $file,
386             line => $token->{line},
387 12         100 description => sprintf($error_message, $token_data),
388             explanation => EXPL,
389             policy => __PACKAGE__,
390             };
391 12         28 last SCAN_NAMESPACE;
392             }
393             }
394             }
395             elsif ($token_type != NAMESPACE_RESOLVER) {
396 23         34 last;
397             }
398             }
399             }
400              
401 57         184 next;
402             }
403              
404             # for constant variables
405             # * Readonly::Scalar
406             # * const (From Const::Fast)
407             {
408 7037         4518 my $is_const_decl = 0;
  7037         4680  
409 7037 100 100     20297 if ($token_type == NAMESPACE && $token_data eq 'Readonly') {
    100 100        
410 3         3 $i += 2;
411 3 50       9 $token = $tokens->[$i] or last;
412 3 50 33     9 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       8453 if ($is_const_decl) {
421 5 50       10 $token = $tokens->[++$i] or last;
422 5 50       12 if (!$var_token_types{$token->{type}}) {
423 5 50       8 $token = $tokens->[++$i] or last;
424             }
425              
426 5 50       11 if ($var_token_types{$token->{type}}) {
427 5         5 $token_data = $token->{data};
428 5 100       14 if (uc $token_data ne $token_data) {
429             push @violations, {
430             filename => $file,
431             line => $token->{line},
432 3         15 description => sprintf('"%s" is not all upper case', $token_data),
433             explanation => EXPL,
434             policy => __PACKAGE__,
435             };
436             }
437             }
438              
439 5         6 next;
440             }
441             }
442              
443             # for constants
444             # * use constant
445 7037 100       7681 if ($token_type == USE_DECL) {
446 9 50       19 $token = $tokens->[++$i] or last;
447 9 100 66     30 if ($token->{type} != USED_NAME || $token->{data} ne 'constant') {
448 8         14 next;
449             }
450              
451 1         4 for ($i++; $token = $tokens->[$i]; $i++) {
452 36 100       43 if ($token->{type} != ARROW) {
453 30         35 next;
454             }
455              
456 6         5 my $key = $tokens->[$i-1]->{data};
457 6 50       14 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         3 next;
469             }
470              
471             # for LABELs
472 7028 100       13229 if ($token_type == KEY) {
473 207 50       456 my $next_token = $tokens->[$i+1] or last;
474 207 100       412 if ($next_token->{type} != COLON) {
475 139         250 next;
476             }
477              
478 68 100       1424 if ($token_data =~ /\A$label_exemptions\Z/) {
479 34         114 next;
480             }
481              
482 34         117 my $checker = \&_is_all_upper;
483 34         53 my $error_message = '"%s" is not all upper case';
484 34 50       128 if (my $condition = $class->_choose_condition_dispenser($labels_rule) ) {
485 34         65 $checker = $condition->{checker};
486 34         53 $error_message = $condition->{error_message};
487             }
488              
489 34 100       117 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         13 description => sprintf($error_message, $token_data),
498             explanation => EXPL,
499             policy => __PACKAGE__,
500             };
501 1         4 next;
502             }
503              
504 31 100       77 if (!$checker->($token_data)) {
505             push @violations, {
506             filename => $file,
507             line => $token->{line},
508 11         101 description => sprintf($error_message, $token_data),
509             explanation => EXPL,
510             policy => __PACKAGE__,
511             };
512             }
513              
514 31         124 next;
515             }
516              
517             }
518              
519 933         3810 return \@violations;
520             }
521              
522             sub _choose_condition_dispenser {
523 1002     1002   1646 my ($self, $rule) = @_;
524              
525 1002 100       4740 if ($rule eq ':single_case') {
    100          
    100          
    100          
    100          
    100          
    100          
526             return {
527 115         680 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         1104 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         676 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         696 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         618 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         786 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         480 error_message => qq{"%s" is not matched with $rule},
564             checker => $rule, # XXX
565             };
566             }
567              
568 103         205 return;
569             }
570              
571             sub _is_all_lower {
572 416     416   463 my ($part) = @_;
573 416         1096 return lc($part) eq $part;
574             }
575              
576             sub _is_all_upper {
577 196     196   239 my ($part) = @_;
578 196         531 return uc($part) eq $part;
579             }
580              
581             sub _is_singlecase {
582 332     332   338 my ($part) = @_;
583 332   100     1219 return uc($part) eq $part || lc($part) eq $part;
584             }
585              
586             sub _is_started_with_lower {
587 301     301   320 my ($part) = @_;
588 301         732 return lcfirst($part) eq $part;
589             }
590              
591             sub _is_started_with_upper {
592 304     304   310 my ($part) = @_;
593 304         686 return ucfirst($part) eq $part;
594             }
595              
596             sub _everything_will_be_alright {
597 424     424   675 return 1;
598             }
599              
600             1;
601