File Coverage

blib/lib/Perl/Critic/Policy/Plicease/ProhibitSignaturesAndAtUnderscore.pm
Criterion Covered Total %
statement 48 49 97.9
branch 16 20 80.0
condition 20 46 43.4
subroutine 12 13 92.3
pod 4 5 80.0
total 100 133 75.1


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::Plicease::ProhibitSignaturesAndAtUnderscore;
2              
3 5     5   3924 use strict;
  5         9  
  5         226  
4 5     5   29 use warnings;
  5         18  
  5         246  
5 5     5   83 use 5.010001;
  5         19  
6 5     5   24 use Perl::Critic::Utils qw( $SEVERITY_HIGH );
  5         8  
  5         800  
7 5     5   34 use base qw( Perl::Critic::Policy );
  5         10  
  5         696  
8              
9             # ABSTRACT: Prohibit the use of @_ in subroutine using signatures
10             our $VERSION = '0.10'; # VERSION
11              
12              
13 5     5   31 use constant DESC => 'Using @_ in a function with signatures';
  5         9  
  5         406  
14 5     5   24 use constant EXPL => 'The use of @_ in a subroutine that is also using subroutine signatures is experimental.';
  5         7  
  5         3311  
15              
16             sub supported_parameters {
17             return ({
18 5     5 0 1700658 name => 'signature_enablers',
19             description => 'Non-standard modules to recognize as enabling signatures',
20             behavior => 'string list',
21             });
22             }
23              
24 2     2 1 52 sub default_severity { $SEVERITY_HIGH }
25 0     0 1 0 sub default_themes { () }
26 5     5 1 72327 sub applies_to { 'PPI::Document' }
27              
28             sub violates {
29 5     5 1 71 my($self, $elem) = @_;
30              
31 5         11 my $has_signatures = 0;
32              
33             # Check if signatures are enabled
34 5   100     26 my $includes = $elem->find('PPI::Statement::Include') || [];
35 5         86 foreach my $include (@$includes) {
36 4 50       23 next unless $include->type eq 'use';
37              
38 4 0 33     146 if(($include->version and version->parse($include->version) >= version->parse('v5.36'))
      33        
      33        
      33        
      33        
      0        
      33        
      0        
      0        
      0        
39             || ($include->pragma eq 'feature' and $include =~ m/\bsignatures\b/)
40             || ($include->pragma eq 'experimental' and $include =~ m/\bsignatures\b/)
41             || ($include->module eq 'Mojo::Base' and $include =~ m/-signatures\b/)
42             || ($include->module eq 'Mojolicious::Lite' and $include =~ m/-signatures\b/)
43             || (exists $self->{_signature_enablers}{$include->module})) {
44 4         594 $has_signatures = 1;
45             }
46             }
47              
48 5         13 my @violations;
49              
50 5 100       16 if($has_signatures) {
51              
52 4   50     20 my $subs = $elem->find('PPI::Statement::Sub') || [];
53 4         58 foreach my $sub (@$subs) {
54             next unless( $PPI::Document::VERSION > 1.279 ?
55 5 100       23 @{$sub->find('PPI::Structure::Signature') || []} : defined $sub->prototype );
  5 50       49  
    100          
56              
57 4         5245 foreach my $symbol ( _recurse($sub->schildren) ) {
58 2         17 push @violations, $self->violation(DESC, EXPL, $symbol);
59             }
60             }
61             }
62              
63 5         1109 return @violations;
64             }
65              
66             # since PPI doesn't detect anonymous subroutines...
67             # look to ignore a PPI::Token::Word with `sub` followed by sibling PPI::Structure::Block
68              
69             sub _recurse {
70 21     21   331 my @ret;
71 21         52 my(@children) = @_;
72 21         62 for my $i (0..$#children) {
73 46 100       433 next if $children[$i]->isa('PPI::Statement::Sub');
74 45 100 100     239 next if $i >= 1 && $children[$i]->isa('PPI::Structure::Block') && $children[$i-1]->isa('PPI::Token::Word') && $children[$i-1]->literal eq 'sub';
      100        
      66        
75              
76 44 100 100     365 if($children[$i]->isa('PPI::Token::Symbol') && $children[$i]->symbol eq '@_') {
    100          
77 2         175 push @ret, $children[$i];
78             } elsif($children[$i]->can('schildren')) {
79 17         80 push @ret, _recurse($children[$i]->schildren);
80             }
81             }
82 21         336 return @ret;
83             }
84              
85             1;
86              
87             __END__
88              
89             =pod
90              
91             =encoding UTF-8
92              
93             =head1 NAME
94              
95             Perl::Critic::Policy::Plicease::ProhibitSignaturesAndAtUnderscore - Prohibit the use of @_ in subroutine using signatures
96              
97             =head1 VERSION
98              
99             version 0.10
100              
101             =head1 SYNOPSIS
102              
103             sub foo ($$) { my($x,$y) = @_; } # ok
104             use experimental qw( signatures ); foo ($x, $y) { my($c,$d) = @_; } # not ok
105              
106             =head1 DESCRIPTION
107              
108             When signatures were made non-experimental, C<@_> used in a subroutine that used signatures was kept as
109             experimental. This is a problem for a few reasons, for one you don't see the experimental warning
110             specific to C<@_> unless you are running a Perl after signatures were made non-experimental, for another
111             as of Perl 5.39.10 this is still experimental.
112              
113             =head1 AFFILIATION
114              
115             None.
116              
117             =head1 CONFIGURATION
118              
119             This policy can be configured to recognize additional modules as enabling the signatures feature, by
120             putting an entry in a .perlcriticrc file like this:
121              
122             [Plicease::ProhibitSignaturesAndAtUnderscore]
123             signature_enablers = Foo::Bar
124              
125             =head1 CAVEATS
126              
127             For older versions of L<PPI> (newer version is yet unreleased as of this writing), this module assumes
128             that "prototypes" detected in a source file that has signatures enabled are actually subroutine signatures.
129             This is because through static analysis alone it is not possible to determine if a "prototype" is really a
130             prototype and not a signature. There thus may be false negatives/positives. Future versions of this module
131             will require a L<PPI> with better signature detection.
132              
133             =head1 AUTHOR
134              
135             Author: Graham Ollis E<lt>plicease@cpan.orgE<gt>
136              
137             Contributors:
138              
139             Ville Skyttä (SCOP)
140              
141             Yoshikazu Sawa (yoshikazusawa)
142              
143             Christian Walde (wchristian, MITHALDU)
144              
145             =head1 COPYRIGHT AND LICENSE
146              
147             This software is copyright (c) 2019-2024 by Graham Ollis.
148              
149             This is free software; you can redistribute it and/or modify it under
150             the same terms as the Perl 5 programming language system itself.
151              
152             =cut