File Coverage

blib/lib/Perl/Critic/Policy/CodeLayout/ProhibitIfIfSameLine.pm
Criterion Covered Total %
statement 45 46 97.8
branch 11 12 91.6
condition 8 12 66.6
subroutine 12 12 100.0
pod 1 1 100.0
total 77 83 92.7


line stmt bran cond sub pod time code
1             # Copyright 2013, 2014, 2015, 2016, 2017, 2019, 2021 Kevin Ryde
2              
3             # Perl-Critic-Pulp is free software; you can redistribute it and/or modify
4             # it under the terms of the GNU General Public License as published by the
5             # Free Software Foundation; either version 3, or (at your option) any later
6             # version.
7             #
8             # Perl-Critic-Pulp is distributed in the hope that it will be useful, but
9             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11             # for more details.
12             #
13             # You should have received a copy of the GNU General Public License along
14             # with Perl-Critic-Pulp. If not, see <http://www.gnu.org/licenses/>.
15              
16              
17             # perlcritic -s ProhibitIfIfSameLine /usr/share/perl5/Pod/Simple.pm
18             # preceded by "return" so actually ok
19             # perlcritic -s ProhibitIfIfSameLine /usr/share/perl5/Tk/AbstractCanvas.pm
20             # two ifs one line
21              
22              
23             package Perl::Critic::Policy::CodeLayout::ProhibitIfIfSameLine;
24 40     40   32234 use 5.006;
  40         167  
25 40     40   225 use strict;
  40         89  
  40         856  
26 40     40   211 use warnings;
  40         88  
  40         1063  
27 40     40   920 use Perl::Critic::Utils;
  40         141632  
  40         779  
28              
29 40     40   36212 use base 'Perl::Critic::Policy';
  40         105  
  40         6164  
30              
31             our $VERSION = 99;
32              
33             # uncomment this to run the ### lines
34             # use Smart::Comments;
35              
36              
37 40     40   51244 use constant supported_parameters => ();
  40         96  
  40         2919  
38 40     40   271 use constant default_severity => $Perl::Critic::Utils::SEVERITY_MEDIUM;
  40         90  
  40         2348  
39 40     40   263 use constant default_themes => qw(pulp bugs);
  40         97  
  40         2473  
40 40     40   273 use constant applies_to => ('PPI::Statement::Compound');
  40         100  
  40         15644  
41              
42             my %compound_type_is_if = (if => 1,
43             unless => 1);
44              
45             sub violates {
46 30     30 1 615959 my ($self, $elem, $document) = @_;
47             ### ProhibitIfIfSameLine elem: "$elem"
48             ### type: $elem->type
49              
50 30 100       73 unless (_compound_statement_is_if($elem)) {
51             ### not an "if" ...
52 14         455 return;
53             }
54              
55 16 50       474 if (_elems_any_separator ($elem->child(0), $elem->schild(0))) {
56             ### leading whitespace in elem itself, so ok ...
57 0         0 return;
58             }
59              
60 16   100     52 my $prev = $elem->sprevious_sibling || return;
61 8 100 66     257 unless ($prev->isa('PPI::Statement::Compound')
62             && $compound_type_is_if{$prev->type}) {
63             ### not preceded by an "if", so ok ...
64 4         187 return;
65             }
66              
67 4 100       133 if (_elems_any_separator ($prev->next_sibling, $elem)) {
68             ### newlines after previous statement, so ok ...
69 1         4 return;
70             }
71              
72 3         17 return $self->violation
73             ('Put a newline in "} if (x)" so it doesn\'t look like possible \"elsif\"',
74             '',
75             $elem);
76             }
77              
78             # $elem is a PPI::Statement::Compound
79             # Return true if it's an "if" statement.
80             # Note this is not simply $elem->type eq "if", since type "if" includes
81             # "unless" statements, but _compound_statement_is_if() is true only on "if"
82             # statements.
83             #
84             sub _compound_statement_is_if {
85 30     30   61 my ($elem) = @_;
86 30   50     80 return (($elem->schild(0)||'') eq 'if');
87             }
88              
89             # Return true if there is a suitable separator in $from or its following
90             # elements up to $to, but not including $to.
91             #
92             sub _elems_any_separator {
93 20     20   334 my ($from, $to) = @_;
94 20         33 for (;;) {
95 24 100       123 if ($from == $to) {
96 19         135 return 0;
97             }
98 5 100 66     31 if ($from =~ /\n/
99             || $from->isa('PPI::Statement::Null')) {
100 1         18 return 1;
101             }
102 4   50     49 $from = $from->next_sibling || return 0;
103             }
104             }
105              
106             1;
107             __END__
108              
109             =for stopwords Ryde
110              
111             =head1 NAME
112              
113             Perl::Critic::Policy::CodeLayout::ProhibitIfIfSameLine - don't put if after if on same line
114              
115             =head1 DESCRIPTION
116              
117             This policy is part of the L<C<Perl::Critic::Pulp>|Perl::Critic::Pulp>
118             add-on. It asks you to not to write an C<if> statement on the same line as
119             a preceding C<if>.
120              
121             if ($x) {
122             ...
123             } if ($y) { # bad
124             ...
125             }
126              
127             if ($x) {
128             ...
129             } elsif ($y) { # was "elsif" intended ?
130             ...
131             }
132              
133             The idea is that an C<if> in the layout of an C<elsif> may be either a
134             mistake or will be confusing to a human reader. On that basis this policy
135             is under the "bugs" theme and medium severity (see L<Perl::Critic/POLICY
136             THEMES>).
137              
138             =head2 Unless
139              
140             An C<unless...if> is treated the same. Perl allows C<unless ... elsif> and
141             so the same potential confusion with an C<elsif> layout arises.
142              
143             unless ($x) {
144             ...
145             } if ($y) { # bad
146             ...
147             }
148              
149             unless ($x) {
150             ...
151             } elsif ($y) { # maybe meant to be "elsif" like this ?
152             ...
153             }
154              
155             Whether C<unless ... elsif> is a good idea at all is another matter.
156             Sometimes it suits a combination of conditions.
157              
158             =head2 Statement Modifiers
159              
160             This policy only applies to a statement followed by a statement. An C<if>
161             as a statement modifier is not affected. It's usual to put that on the same
162             line as the statement it modifies.
163              
164             do {
165             ...
166             } if ($x); # ok, statement modifier
167              
168             =head2 All One Line
169              
170             Two C<if> statements written on the same line will trigger the policy.
171              
172             if(1){one;} if(2){two;} # bad
173              
174             Perhaps there could be an exception or option when both statements are
175             entirely on the one line, or some such, for code which is trying to be
176             compact.
177              
178             =head2 Disabling
179              
180             As always if you don't care about this then you can disable
181             C<ProhibitIfIfSameLine> from your F<.perlcriticrc> (see
182             L<Perl::Critic/CONFIGURATION>),
183              
184             [-CodeLayout::ProhibitIfIfSameLine]
185              
186             =head1 SEE ALSO
187              
188             L<Perl::Critic::Pulp>, L<Perl::Critic>
189              
190             L<Perl::Critic::Policy::ControlStructures::ProhibitCascadingIfElse>,
191             L<Perl::Critic::Policy::ControlStructures::ProhibitUnlessBlocks>
192              
193             =head1 HOME PAGE
194              
195             L<http://user42.tuxfamily.org/perl-critic-pulp/index.html>
196              
197             =head1 COPYRIGHT
198              
199             Copyright 2013, 2014, 2015, 2016, 2017, 2019, 2021 Kevin Ryde
200              
201             Perl-Critic-Pulp is free software; you can redistribute it and/or modify it
202             under the terms of the GNU General Public License as published by the Free
203             Software Foundation; either version 3, or (at your option) any later
204             version.
205              
206             Perl-Critic-Pulp is distributed in the hope that it will be useful, but
207             WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
208             or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
209             more details.
210              
211             You should have received a copy of the GNU General Public License along with
212             Perl-Critic-Pulp. If not, see <http://www.gnu.org/licenses/>.
213              
214             =cut