File Coverage

blib/lib/Perl/Critic/Policy/Subroutines/ProhibitManyArgs.pm
Criterion Covered Total %
statement 42 74 56.7
branch 11 40 27.5
condition 6 18 33.3
subroutine 12 14 85.7
pod 4 5 80.0
total 75 151 49.6


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::Subroutines::ProhibitManyArgs;
2              
3 40     40   24180 use 5.010001;
  40         139  
4 40     40   162 use strict;
  40         67  
  40         693  
5 40     40   126 use warnings;
  40         66  
  40         1376  
6 40     40   169 use Readonly;
  40         95  
  40         2330  
7              
8 40     40   243 use Perl::Critic::Utils qw( :severities split_nodes_on_comma hashify );
  40         88  
  40         2058  
9 40     40   5275 use parent 'Perl::Critic::Policy';
  40         86  
  40         219  
10              
11             our $VERSION = '1.156';
12              
13             #-----------------------------------------------------------------------------
14              
15             Readonly::Scalar my $AT_ARG => q{@_}; ## no critic (InterpolationOfMetachars)
16             Readonly::Scalar my $CLASS => q{$class}; ## no critic (InterpolationOfMetachars)
17             Readonly::Scalar my $SELF => q{$self}; ## no critic (InterpolationOfMetachars)
18              
19             Readonly::Scalar my $DESC => q{Too many arguments};
20             Readonly::Scalar my $EXPL => [182];
21              
22             #-----------------------------------------------------------------------------
23              
24             sub supported_parameters {
25             return (
26             {
27 92     92 0 1155 name => 'max_arguments',
28             description =>
29             'The maximum number of arguments to allow a subroutine to have.',
30             default_string => '5',
31             behavior => 'integer',
32             integer_minimum => 1,
33             },
34             {
35             name => 'skip_object',
36             description => q[Don't count $self or $class first argument], ## no critic (InterpolationOfMetachars)
37             default_string => '0',
38             behavior => 'boolean',
39             },
40             );
41             }
42              
43 75     75 1 243 sub default_severity { return $SEVERITY_MEDIUM }
44 86     86 1 244 sub default_themes { return qw( core pbp maintenance ) }
45 31     31 1 63 sub applies_to { return 'PPI::Statement::Sub' }
46              
47             #-----------------------------------------------------------------------------
48              
49             sub violates {
50 5     5 1 15 my ( $self, $elem, undef ) = @_;
51              
52             # forward declaration?
53 5 50       21 return if !$elem->block;
54              
55 5         125 my $num_args;
56 5 50       20 if ($elem->prototype) {
57 0         0 my $prototype = $elem->prototype();
58 0 0       0 if ($prototype =~ /[[:alpha:]]/smx) { # signature (probably)
59 0 0       0 if ( $self->{_skip_object} ) {
60 0         0 state $c = qr/\Q$CLASS/smx;
61 0         0 state $s = qr/\Q$SELF/smx;
62 0         0 state $invocant = qr/^(?:$c|$s),?/smx;
63 0         0 $prototype =~ s/$invocant//smx;
64             }
65 0         0 $num_args = $prototype =~ tr/$@%/$@%/;
66             } else { # prototype
67 0         0 $prototype =~ s/ \\ [[] .*? []] /*/smxg; # Allow for grouping
68 0         0 $num_args = $prototype =~ tr/$@%&*_+/$@%&*_+/; # RT 56627
69             }
70             } else {
71 5         203 $num_args = _count_args($self->{_skip_object}, $elem->block->schildren);
72             }
73              
74 5 50       18 if ($self->{_max_arguments} < $num_args) {
75 0         0 return $self->violation( $DESC, $EXPL, $elem );
76             }
77 5         17 return; # OK
78             }
79              
80             sub _count_args {
81 5     5   191 my ($skip_object, @statements) = @_;
82              
83             # look for these patterns:
84             # " ... = @_;" => then examine previous variable list
85             # " ... = shift;" => counts as one arg, then look for more
86              
87 5 50       18 return 0 if !@statements; # no statements
88              
89 5         10 my $statement = shift @statements;
90 5         18 my @elements = $statement->schildren();
91 5         65 my $operand = pop @elements;
92 5   66     45 while ($operand && $operand->isa('PPI::Token::Structure') && q{;} eq $operand->content()) {
      66        
93 4         40 $operand = pop @elements;
94             }
95 5 50       21 return 0 if !$operand;
96              
97             #print "pulled off last, remaining: '@elements'\n";
98 5         7 my $operator = pop @elements;
99 5 50       29 return 0 if !$operator;
100 5 100       29 return 0 if !$operator->isa('PPI::Token::Operator');
101 1 50       4 return 0 if q{=} ne $operator->content();
102              
103 1 50 33     22 if ($operand->isa('PPI::Token::Magic') && $AT_ARG eq $operand->content()) {
    50 33        
104 0         0 return _count_list_elements($skip_object, @elements);
105             } elsif ($operand->isa('PPI::Token::Word') && 'shift' eq $operand->content()) {
106 0 0       0 my $count_first = $skip_object ? !_is_object_arg(pop @elements) : 1;
107 0         0 return $count_first + _count_args(0, @statements); # only check for object on first argument
108             }
109              
110 1         3 return 0;
111             }
112              
113             sub _count_list_elements {
114 0     0     my ($skip_object, @elements) = @_;
115              
116 0           my $list = pop @elements;
117 0 0         return 0 if !$list;
118 0 0         return 0 if !$list->isa('PPI::Structure::List');
119 0           my @inner = $list->schildren;
120 0 0 0       if (1 == @inner && $inner[0]->isa('PPI::Statement::Expression')) {
121 0           @inner = $inner[0]->schildren;
122             }
123 0           my @args = split_nodes_on_comma(@inner);
124 0 0 0       return scalar @args if !$skip_object || !@args;
125              
126             # Check if first argument is $self/$class
127 0           my $first_ref = $args[0];
128 0 0         return scalar @args if scalar @{ $first_ref } != 1; # more complex than simple scalar
  0            
129 0           return scalar @args - !!_is_object_arg($first_ref->[0]);
130             }
131              
132             sub _is_object_arg {
133 0     0     my ($symbol) = @_;
134 0 0         return 0 if !$symbol;
135 0 0         return 0 if !$symbol->isa('PPI::Token::Symbol');
136              
137 0           state $is_self_or_class = { hashify( $SELF, $CLASS ) };
138 0           return $is_self_or_class->{$symbol->content()};
139             }
140              
141             1;
142              
143             __END__
144              
145             #-----------------------------------------------------------------------------
146              
147             =pod
148              
149             =for stopwords refactored
150              
151             =head1 NAME
152              
153             Perl::Critic::Policy::Subroutines::ProhibitManyArgs - Too many arguments.
154              
155              
156             =head1 AFFILIATION
157              
158             This Policy is part of the core L<Perl::Critic|Perl::Critic>
159             distribution.
160              
161              
162             =head1 DESCRIPTION
163              
164             Subroutines that expect large numbers of arguments are hard to use
165             because programmers routinely have to look at documentation to
166             remember the order of those arguments. Many arguments is often a sign
167             that a subroutine should be refactored or that an object should be
168             passed to the routine.
169              
170              
171             =head1 CONFIGURATION
172              
173             By default, this policy allows up to 5 arguments without warning. To
174             change this threshold, put entries in a F<.perlcriticrc> file like
175             this:
176              
177             [Subroutines::ProhibitManyArgs]
178             max_arguments = 6
179              
180             To ignore C<$self> or C<$class> in your argument count, as long as they're
181             the first argument, use:
182              
183             [Subroutines::ProhibitManyArgs]
184             skip_object = 1
185              
186              
187             =head1 CAVEATS
188              
189             PPI doesn't currently detect anonymous subroutines, so we don't check
190             those. This should just work when PPI gains that feature.
191              
192             We don't check for C<@ARG>, the alias for C<@_> from English.pm.
193             That's deprecated anyway.
194              
195              
196             =head1 CREDITS
197              
198             Initial development of this policy was supported by a grant from the
199             Perl Foundation.
200              
201              
202             =head1 AUTHOR
203              
204             Chris Dolan <cdolan@cpan.org>
205              
206              
207             =head1 COPYRIGHT
208              
209             Copyright (c) 2007-2023 Chris Dolan. Many rights reserved.
210              
211             This program is free software; you can redistribute it and/or modify
212             it under the same terms as Perl itself. The full text of this license
213             can be found in the LICENSE file included with this module
214              
215             =cut
216              
217             # Local Variables:
218             # mode: cperl
219             # cperl-indent-level: 4
220             # fill-column: 78
221             # indent-tabs-mode: nil
222             # c-indentation-style: bsd
223             # End:
224             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :