File Coverage

blib/lib/Perl/Critic/Policy/Documentation/ProhibitUnbalancedParens.pm
Criterion Covered Total %
statement 94 95 98.9
branch 30 32 93.7
condition 11 14 78.5
subroutine 17 17 100.0
pod 1 1 100.0
total 153 159 96.2


line stmt bran cond sub pod time code
1             # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019 Kevin Ryde
2              
3             # This file is part of Perl-Critic-Pulp.
4              
5             # Perl-Critic-Pulp is free software; you can redistribute it and/or modify
6             # it under the terms of the GNU General Public License as published by the
7             # Free Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Perl-Critic-Pulp is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Perl-Critic-Pulp. If not, see <http://www.gnu.org/licenses/>.
17              
18              
19             # perlcritic -s ProhibitUnbalancedParens ProhibitUnbalancedParens.pm
20              
21             # unclosed:
22             # perlcritic -s ProhibitUnbalancedParens /usr/share/perl/5.12/CGI.pm
23              
24             # smiley close:
25             # perlcritic -s ProhibitUnbalancedParens /usr/share/perl5/accessors.pm
26              
27              
28             package Perl::Critic::Policy::Documentation::ProhibitUnbalancedParens;
29 40     40   33558 use 5.006;
  40         165  
30 40     40   236 use strict;
  40         128  
  40         906  
31 40     40   210 use warnings;
  40         93  
  40         1175  
32 40     40   221 use base 'Perl::Critic::Policy';
  40         83  
  40         4732  
33 40     40   180612 use Perl::Critic::Utils;
  40         89  
  40         769  
34              
35             # uncomment this to run the ### lines
36             # use Smart::Comments;
37              
38             our $VERSION = 98;
39              
40 40     40   36245 use constant supported_parameters => ();
  40         138  
  40         2745  
41 40     40   270 use constant default_severity => $Perl::Critic::Utils::SEVERITY_LOW;
  40         114  
  40         2369  
42 40     40   281 use constant default_themes => qw(pulp cosmetic);
  40         90  
  40         2334  
43 40     40   255 use constant applies_to => 'PPI::Document';
  40         109  
  40         5250  
44              
45             sub violates {
46 75     75 1 642093 my ($self, $elem, $document) = @_;
47             ### ProhibitUnbalancedParens on: $elem->content
48              
49 75         997 my $parser = Perl::Critic::Pulp::PodParser::ProhibitUnbalancedParens->new
50             (policy => $self);
51 75         314 $parser->parse_from_elem ($elem);
52 75         379 return $parser->violations;
53             }
54              
55             package Perl::Critic::Pulp::PodParser::ProhibitUnbalancedParens;
56 40     40   309 use strict;
  40         93  
  40         1081  
57 40     40   231 use warnings;
  40         98  
  40         1518  
58 40     40   18519 use Pod::ParseLink;
  40         31536  
  40         2376  
59 40     40   290 use base 'Perl::Critic::Pulp::PodParser';
  40         97  
  40         37909  
60              
61             sub command {
62 79     79   7051 my $self = shift;
63 79         210 my ($command, $text, $linenum, $paraobj) = @_;
64 79         384 $self->SUPER::command(@_); # maintain 'in_begin'
65              
66 79 100 66     260 if ($command eq 'for'
67             && $text =~ /^ProhibitUnbalancedParens\b\s*(.*)/) {
68 4         12 my $directive = $1;
69             ### $directive
70 4 50       19 if ($directive =~ /^allow next( (\d+))?/) {
71             # numbered "allow next 5" means up to that many following
72             # unnumbered "allow next" means one following
73 4 100       18 $self->{'allow_next'} = (defined $2 ? $2 : 1);
74             }
75             }
76 79         285 return $self->command_as_textblock(@_);
77             }
78              
79             my %open_to_close = ('(' => ')',
80             '[' => ']',
81             '{' => '}');
82             my %close_to_open = reverse %open_to_close;
83              
84             sub textblock {
85 152     152   5342 my ($self, $text, $linenum, $paraobj) = @_;
86             ### textblock: "linenum=$linenum"
87              
88 152 100 100     701 if (($self->{'allow_next'}||0) > 0) {
89 6         13 $self->{'allow_next'}--;
90 6         67 return '';
91             }
92              
93             # process outside =begin, and inside =begin which is ":" markup
94 146 100 66     441 unless ($self->{'in_begin'} eq '' || $self->{'in_begin'} =~ /^:/) {
95 1         41 return '';
96             }
97              
98 145         6601 my $interpolated = $self->interpolate($text, $linenum);
99             ### $text
100             ### $interpolated
101              
102 145         728 my @parens;
103 145         1189 while ($interpolated
104             =~ m/
105             ([][({})]) # $1 open or close
106             |([:;]-?\) # $2 smiley face optional close
107             |\b[a-zA-Z1-9]\) # "middle a) or 1) item"
108             |(?<!\$)\$\) # perlvar $), and not $$
109             )
110             |(["'])[][(){}]+\3 # $3 "(" quoted
111             |[:;]-?[(] # smiley face not an open
112             |(?<!\$)\$\$ # perlvar $$ consumed
113             |\$\(\w*\) # makefile var $(abc)
114             |\$\[\w*\] # perhaps template $[abc]
115             |(?<!\$)\$[][(] # perlvars $[, $(, $], and not $$
116             |^\s*(\d+|[A-Za-z])\.?\) # initial "1.5) something"
117             /xg) {
118             ### match: $&
119             ### $1
120             ### $2
121             ### $3
122 117 100       476 if (defined $1) {
    100          
123 65         573 push @parens, { char => $1,
124             pos => pos($interpolated)-1,
125             };
126              
127             } elsif (defined $2) {
128 25         196 push @parens, { char => ')',
129             pos => pos($interpolated)-1,
130             optional => 1,
131             };
132             }
133             }
134             ### @parens
135              
136             # sort optional closes to after hard closes
137             {
138 145         296 my @optional;
  145         266  
139             my @new;
140 145         278 foreach my $p (@parens) {
141 90 100 100     261 if (@optional && $optional[0]->{'char'} ne $p->{'char'}) {
142 5         12 push @new, splice @optional;
143             }
144 90 100       207 if ($p->{'optional'}) {
145 25         47 push @optional, $p;
146             } else {
147 65         145 push @new, $p;
148             }
149             }
150 145         333 @parens = (@new, @optional);
151             }
152             ### sorted: @parens
153              
154 145         283 my @opens;
155 145         263 foreach my $p (@parens) {
156             ### $p
157 90         164 my $char = $p->{'char'};
158 90 100       223 if (my $want_openchar = $close_to_open{$char}) {
159             # a close
160 42 100       99 if (my $o = pop @opens) {
161 28         61 my $openchar = $o->{'char'};
162 28 100       82 if ($openchar ne $want_openchar) {
163 2 50       8 if ($p->{'optional'}) {
164             ### mismatched optional close, skip
165 2         5 push @opens, $o;
166 2         6 next;
167             }
168             $self->violation_at_linenum_and_textpos
169             ("Mismatched closing paren \"$char\" expected \"$open_to_close{$openchar}\"",
170 0         0 $linenum, $interpolated, $p->{'pos'});
171             }
172              
173             } else {
174 14 100       40 if ($p->{'optional'}) {
175             ### unopened optional close, skip
176 12         31 next;
177             }
178             $self->violation_at_linenum_and_textpos
179             ("Unopened close paren \"$char\"",
180 2         20 $linenum, $interpolated, $p->{'pos'});
181             }
182              
183             } else {
184             # an open
185 48         102 push @opens, $p;
186             }
187             }
188 145         285 foreach my $p (@opens) {
189             $self->violation_at_linenum_and_textpos
190             ("Unclosed paren \"$p->{'char'}\"",
191 22         133 $linenum, $interpolated, $p->{'pos'});
192             }
193 145         1963 return '';
194             }
195              
196             *interior_sequence = \&interior_sequence_as_displayed_noncode_text;
197              
198             sub interior_sequence_as_displayed_noncode_text {
199 12     12   48 my ($self, $cmd, $text, $pod_seq) = @_;
200              
201 12 100 66     70 if ($cmd eq 'X' || $cmd eq 'C') {
    100          
202             ### $cmd
203             ### X,C keep only the newlines: $text
204 7         18 $text =~ tr/\n//cd;
205              
206             } elsif ($cmd eq 'L') {
207 3         20 my ($display, $inferred, $name, $section, $type)
208             = Pod::ParseLink::parselink ($text);
209             ### $text
210             ### $display
211             ### $inferred
212             ### $name
213 3         254 return $inferred; # the display part, or the name part if no display
214             }
215 9         384 return $text;
216             }
217              
218             1;
219             __END__
220              
221             =for stopwords Ryde paren parens ie deref there'd backslashing Parens
222              
223             =head1 NAME
224              
225             Perl::Critic::Policy::Documentation::ProhibitUnbalancedParens - don't leave an open bracket or paren
226              
227             =head1 DESCRIPTION
228              
229             This policy is part of the L<C<Perl::Critic::Pulp>|Perl::Critic::Pulp>
230             add-on. It reports unbalanced or mismatched parentheses, brackets and braces
231             in POD text paragraphs,
232              
233             Blah blah (and something. # bad
234              
235             Blah blah ( [ ). # bad
236              
237             Blah blah brace }. # bad
238              
239             This is only cosmetic and normally only a minor irritant to readability so
240             this policy is low severity and under the "cosmetic" theme (see
241             L<Perl::Critic/POLICY THEMES>).
242              
243             Text and command paragraphs are checked, but verbatim paragraphs can have
244             anything. There are some exceptions to paren balancing. The intention is
245             to be forgiving of common or reasonable constructs. Currently this means,
246              
247             =over
248              
249             =item *
250              
251             Anything in C<CE<lt>E<gt>> code markup is ignored
252              
253             =for ProhibitVerbatimMarkup allow next
254              
255             In code C<anything [ is allowed>. # ok
256              
257             Perhaps this will change, though there'd have to be extra exceptions in
258             C<CE<lt>E<gt>>, such as various backslashing.
259              
260             Sometimes a prematurely ending C<CE<lt>E<gt>> may look like an unbalanced
261             paren, for example
262              
263             =for ProhibitVerbatimMarkup allow next
264              
265             Call C<foo(key=>value)> ... # bad
266              
267             =for ProhibitUnbalancedParens allow next
268              
269             This is bad because the C<CE<lt>E<gt>> ends at the C<=E<gt>>, leaving
270             "value)" unbalanced plain text. This is an easy mistake to make. (The
271             author's C<perl-pod-gt.el> can show warning face on this in Emacs.)
272              
273             =item *
274              
275             Quoted "(" is taken to be describing the char and is not an open or close.
276              
277             Any of "(" or '[' or "[{]". # ok
278              
279             This only applies to quoted parens alone (one or more), not larger quoted
280             text.
281              
282             =item *
283              
284             Item parens
285              
286             a) the first thing, or b) the second thing # ok
287              
288             1) one, 2) two # ok
289              
290             Exactly how much is recognised as an "a)" etc is not quite settled. In the
291             current code a "1.5)" is recognised at the start of a paragraph, but in the
292             middle only "1)" style.
293              
294             =item *
295              
296             Smiley faces are an "optional" close,
297              
298             (Some thing :-). # ok
299              
300             Bare smiley :). # ok
301              
302             (Or smile :-) and also close.) # ok
303              
304             =item *
305              
306             Sad smiley faces are not an opening paren,
307              
308             :( :-(. # ok
309              
310             =item *
311              
312             Perl variables C<$(> and C<$[> are not opening parens,
313              
314             Default is group $( blah blah. # ok
315              
316             C<${> brace is still an open and expected to have a matching close, because
317             it's likely to be a deref or delimiter,
318              
319             Deref with ${foo()} etc etc.
320              
321             Variables or expressions like this will often be in C<CE<lt>E<gt>> markup
322             and skipped for that reason instead, as described above.
323              
324             =item *
325              
326             C<$)> and C<$]> are optional closes, since they might be Perl variables to
327             skip, or might be "$" at the end of a parens,
328              
329             blah blah (which in TeX is $1\cdot2$).
330              
331             Perhaps the conditions for these will be restricted a bit, though again
332             C<CE<lt>E<gt>> markup around sample code like this will be usual.
333              
334             =item *
335              
336             C<LE<lt>display|linkE<gt>> links are processed as the "display" text part.
337             The link target (POD document name and section) can have anything.
338              
339             =back
340              
341             C<=begin :foo> ... C<=end :foo> sections with a format name ":foo" starting
342             with a ":" are POD markup and are processed accordingly. Other C<=begin>
343             sections are skipped.
344              
345             =head2 Unrecognised Forms
346              
347             A mathematical half-open range like the following is not recognised.
348              
349             [1,2) # bad, currently
350              
351             Perhaps just numbers like this would be unambiguous, but if it's an
352             expression then it's hard to distinguish a parens typo from some
353             mathematics. The suggestion for now is an C<=for> per below to flag it as
354             an exception. Another way would be to write S<1 E<lt>= X E<lt> 2>, which
355             might be clearer to mathematically unsophisticated readers.
356              
357             Parens spanning multiple paragraphs are not recognised,
358              
359             (This is some # bad
360              
361             thing.) # bad
362              
363             Hopefully this is uncommon, and probably better style not to be
364             parenthetical about something so big that it runs to multiple paragraphs or
365             has a verbatim block in the middle etc.
366              
367             =head2 Disabling
368              
369             If an unbalanced paren is intended you can add an C<=for> to tell
370             C<ProhibitUnbalancedParens> to allow it.
371              
372             =for ProhibitUnbalancedParens allow next
373              
374             Something ( deliberately unclosed.
375              
376             Or with a count of paragraphs to ignore,
377              
378             =for ProhibitUnbalancedParens allow next 2
379              
380             First deliberate [ unclosed.
381              
382             Second (.
383              
384             The usual no critic
385              
386             ## no critic (ProhibitUnbalancedParens)
387              
388             works too as a whole-file disable, but the annotation must be before any
389             C<__END__> token, and if the POD is after the C<__END__> then
390             C<Perl::Critic> 1.112 up is required. Individual C<=for> has the advantage
391             of being with an exception.
392              
393             As always if you don't care about this at all you can disable
394             C<ProhibitUnbalancedParens> completely from your F<.perlcriticrc> in
395             the usual way (see L<Perl::Critic/CONFIGURATION>),
396              
397             [-Documentation::ProhibitUnbalancedParens]
398              
399             =head1 SEE ALSO
400              
401             L<Perl::Critic::Pulp>,
402             L<Perl::Critic>
403              
404             L<http://user42.tuxfamily.org/perl-pod-gt/index.html>
405              
406             =head1 HOME PAGE
407              
408             http://user42.tuxfamily.org/perl-critic-pulp/index.html
409              
410             =head1 COPYRIGHT
411              
412             Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019 Kevin Ryde
413              
414             Perl-Critic-Pulp is free software; you can redistribute it and/or modify it
415             under the terms of the GNU General Public License as published by the Free
416             Software Foundation; either version 3, or (at your option) any later
417             version.
418              
419             Perl-Critic-Pulp is distributed in the hope that it will be useful, but
420             WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
421             or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
422             more details.
423              
424             You should have received a copy of the GNU General Public License along with
425             Perl-Critic-Pulp. If not, see <http://www.gnu.org/licenses/>.
426              
427             =cut