| Perl-Critic-Pulp documentation | Contained in the Perl-Critic-Pulp distribution. |
Perl::Critic::Policy::Documentation::ProhibitUnbalancedParens - don't leave an open bracket or paren
This policy is part of the Perl::Critic::Pulp|Perl::Critic::Pulp
addon. It reports unbalanced or mismatched parentheses, brackets and braces
in POD text paragraphs,
Blah blah (and something. # bad
Blah blah ( [ ). # bad
Blah blah brace }. # bad
This is only cosmetic and only normally a minor irritant to readability so this policy is low priority and under the "cosmetic" theme (see POLICY THEMES in Perl::Critic).
Text and command paragraphs are checked, but verbatim paragraphs can have anything. Not quite every paren must be balanced. The intention is to be forgiving of common or reasonable constructs. Currently this means,
Any of "(" or '[' or "[{]". # ok
a) the first thing, or b) the second thing # ok
1) one, 2) two # ok
(Some thing :-). # ok
Bare smiley :). # ok
(Or smile :-) and also close.) # ok
:( :-(. # ok
$( and $[ are skipped, ie. not opening parens,
Default is group $( blah blah. # ok
${ brace is still an open and expected to have a matching close, as it's
likely to be a deref or delimiter,
Deref with ${foo()} etc etc.
C<> markup and skipped
for that reason per below. $) and $] are optional closes, since they might be Perl variables to
skip, or "$" at the end of a parens,
blah blah (which in tex is $1\cdot2$).
C<> markup around same code like this is probably usual. C<> code markup is ignored
In code C<anything [ is allowed>. # ok
C<> such as various backslashing.
C<> may look like an unbalanced
paren, for example
Call C<foo(key=>value)> ... # bad
C<> ends at the =>, leaving "value)"
unbalanced plain text. L<display|link<gt> links are treated as the "display" text part. The
link target (POD document name and section) can have anything.A mathematical half-open range like
[1,2) # bad
is not recognised. Perhaps just numbers like this would be unambiguous, but
if it's an expression then it's hard to distinguish a parens typo from some
mathematics. The suggestion for now is an =for per below to flag it as
an exception. Another way would be to write 1 <= X < 2, which
might be clearer to mathematically unsophisticated readers anyway.
Parens spanning multiple paragraphs are not recognised,
(This is some # bad
thing.) # bad
Hopefully this is uncommon, and it may be better style not to be parenthetical about something big enough that it runs to multiple paragraphs or has a verbatim block in the middle etc.
If an unbalanced paren is intended you can add an =for to tell
ProhibitUnbalancedParens to allow it.
=for ProhibitUnbalancedParens allow next
Something ( deliberately unclosed.
Or with a count of paragraphs to ignore,
=for ProhibitUnbalancedParens allow next 2
First deliberate [ unclosed.
Second (.
The usual no critic
## no critic (ProhibitUnbalancedParens)
works too as a whole-file disable, but the annotation must be before any
__END__ token, and if the POD is after the __END__ then
Perl::Critic 1.112 is required. Individual =for has the advantage of
being with an exception.
As always if you don't care about this at all you can disable
ProhibitUnbalancedParens completely from your .perlcriticrc in
the usual way (see CONFIGURATION in Perl::Critic),
[-Documentation::ProhibitUnbalancedParens]
What should be done for =begin ... =end sections? Skip probably?
http://user42.tuxfamily.org/perl-critic-pulp/index.html
Copyright 2011 Kevin Ryde
Perl-Critic-Pulp is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version.
Perl-Critic-Pulp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Perl-Critic-Pulp. If not, see <http://www.gnu.org/licenses/>.
| Perl-Critic-Pulp documentation | Contained in the Perl-Critic-Pulp distribution. |
# Copyright 2011 Kevin Ryde # This file is part of Perl-Critic-Pulp. # Perl-Critic-Pulp is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 3, or (at your option) any later # version. # # Perl-Critic-Pulp is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. # # You should have received a copy of the GNU General Public License along # with Perl-Critic-Pulp. If not, see <http://www.gnu.org/licenses/>. # perlcritic -s ProhibitUnbalancedParens ProhibitUnbalancedParens.pm # unclosed: # perlcritic -s ProhibitUnbalancedParens /usr/share/perl/5.12/CGI.pm # smiley close: # perlcritic -s ProhibitUnbalancedParens /usr/share/perl5/accessors.pm package Perl::Critic::Policy::Documentation::ProhibitUnbalancedParens; use 5.006; use strict; use warnings; use base 'Perl::Critic::Policy'; use Perl::Critic::Utils; # uncomment this to run the ### lines #use Smart::Comments; our $VERSION = 61; use constant supported_parameters => (); use constant default_severity => $Perl::Critic::Utils::SEVERITY_LOW; use constant default_themes => qw(pulp cosmetic); use constant applies_to => 'PPI::Document'; sub violates { my ($self, $elem, $document) = @_; ### ProhibitUnbalancedParens on: $elem->content my $parser = Perl::Critic::Pulp::PodParser::ProhibitUnbalancedParens->new (policy => $self); $parser->parse_from_elem ($elem); return $parser->violations; } package Perl::Critic::Pulp::PodParser::ProhibitUnbalancedParens; use strict; use warnings; use Pod::ParseLink; use base 'Perl::Critic::Pulp::PodParser'; sub command { my ($self, $command, $text, $linenum, $paraobj) = @_; if ($command eq 'for' && $text =~ /^ProhibitUnbalancedParens\b\s*(.*)/) { my $directive = $1; ### $directive if ($directive =~ /^allow next( (\d+))?/) { # numbered "allow next 5" means up to that many following # unnumbered "allow next" means one following $self->{'allow_next'} = (defined $2 ? $2 : 1); } } return command_as_textblock(@_); } my %command_non_text = (for => 1, begin => 1, end => 1, cut => 1); sub command_as_textblock { my ($self, $command, $text, $linenum, $paraobj) = @_; ### command: $command unless ($command_non_text{$command}) { # padded to make the column number right, the leading spaces do no harm # for this policy $self->textblock ((' ' x (length($command)+1)) . $text, $linenum, $paraobj); } return ''; } my %open_to_close = ('(' => ')', '[' => ']', '{' => '}'); my %close_to_open = reverse %open_to_close; sub textblock { my ($self, $text, $linenum, $pod_para) = @_; ### textblock: "linenum=$linenum" if (($self->{'allow_next'}||0) > 0) { $self->{'allow_next'}--; return ''; } my $interpolated = $self->interpolate($text, $linenum); ### $text ### $interpolated my @parens; while ($interpolated =~ m/ ([][({})]) # $1 open or close |([:;]-?\) # $2 smiley face optional close |\b[a-zA-Z1-9]\) # "middle a) or 1) item" |(?<!\$)\$\) # perlvar $), and not $$ ) |(["'])[][(){}]+\3 # $3 "(" quoted |[:;]-?[(] # smiley face not an open |(?<!\$)\$\$ # perlvar $$ consumed |\$\(\w*\) # makefile var $(abc) |\$\[\w*\] # perhaps template $[abc] |(?<!\$)\$[][(] # perlvars $[, $(, $], and not $$ |^\s*(\d+|[A-Za-z])\.?\) # initial "1.5) something" /xg) { ### match: $& ### $1 ### $2 ### $3 if (defined $1) { push @parens, { char => $1, pos => pos($interpolated)-1, }; } elsif (defined $2) { push @parens, { char => ')', pos => pos($interpolated)-1, optional => 1, }; } } ### @parens # sort optional closes to after hard closes { my @optional; my @new; foreach my $p (@parens) { if (@optional && $optional[0]->{'char'} ne $p->{'char'}) { push @new, splice @optional; } if ($p->{'optional'}) { push @optional, $p; } else { push @new, $p; } } @parens = (@new, @optional); } ### sorted: @parens my @opens; foreach my $p (@parens) { ### $p my $char = $p->{'char'}; if (my $want_openchar = $close_to_open{$char}) { # a close if (my $o = pop @opens) { my $openchar = $o->{'char'}; if ($openchar ne $want_openchar) { if ($p->{'optional'}) { ### mismatched optional close, skip push @opens, $o; next; } $self->violation_at_linenum_and_textpos ("Mismatched closing paren \"$char\" expected \"$open_to_close{$openchar}\"", $linenum, $interpolated, $p->{'pos'}); } } else { if ($p->{'optional'}) { ### unopened optional close, skip next; } $self->violation_at_linenum_and_textpos ("Unopened close paren \"$char\"", $linenum, $interpolated, $p->{'pos'}); } } else { # an open push @opens, $p; } } foreach my $p (@opens) { $self->violation_at_linenum_and_textpos ("Unclosed paren \"$p->{'char'}\"", $linenum, $interpolated, $p->{'pos'}); } return ''; } *interior_sequence = \&interior_sequence_as_displayed_noncode_text; sub interior_sequence_as_displayed_noncode_text { my ($self, $cmd, $text, $pod_seq) = @_; if ($cmd eq 'X' || $cmd eq 'C') { # keep only the newlines $text =~ tr/\n//cd; } elsif ($cmd eq 'L') { my ($display, $inferred, $name, $section, $type) = Pod::ParseLink::parselink ($text); ### $display ### $inferred ### $name return $inferred; # the display part, or the name part if no display } return $text; } 1; __END__