File Coverage

blib/lib/Perl/Critic/Policy/Subroutines/ProhibitManyArgs.pm
Criterion Covered Total %
statement 42 67 62.6
branch 11 36 30.5
condition 6 18 33.3
subroutine 12 14 85.7
pod 4 5 80.0
total 75 140 53.5


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