Skip Menu |

Preferred bug tracker

Please visit the preferred bug tracker to report your issue.

This queue is for tickets about the Perl-Critic CPAN distribution.

Report information
The Basics
Id: 35970
Status: resolved
Priority: 0/
Queue: Perl-Critic

People
Owner: Nobody in particular
Requestors: amir.aharoni [...] mail.huji.ac.il
Cc:
AdminCc:

Bug Information
Severity: Important
Broken in: 1.082
Fixed in: 1.082



Subject: Variables::ProhibitPunctuationVars - ignores variables in interpolated strings
Magic punctuation variables are ignored if they are a part of an interpolated string: This would be reported: say 'the pid is ', $$; This would be ignored: say "the pid is $$"; A simple testcase is attached. I am working with Perl 5.10 on Cygwin and Windows Vista. Thanks.
Subject: test_punct_02.pl
#!/usr/bin/perl use 5.010; use strict; use warnings; our $VERSION = 0.1; say 'the pid is ', $$; # reported say "the pid is $$"; # ignored say 'the OS is ', $^O; # reported say "the OS is $^O"; # ignored exit; __END__
From: amir.aharoni [...] gmail.com
Marked by mistake as "fixed in 1.082".
Subject: Re: [rt.cpan.org #35970] Variables::ProhibitPunctuationVars - ignores variables in interpolated strings
Date: Sat, 17 May 2008 12:27:21 -0500
To: bug-Perl-Critic [...] rt.cpan.org
From: Elliot Shank <perl [...] galumph.com>
Amir E. Aharoni via RT wrote: Show quoted text
> Magic punctuation variables are ignored if they are a part of an > interpolated string:
Added as a TODO.
From: perl-rt [...] misterwhipple.com
Attached is a patch against perlcritic/distributions/Perl-Critic, svn revision 3108. It extends PCP::Variables::ProhibitPunctuationVars to handle interpolated strings, adding the following classes to applies_to(): PPI::Token::Quote::Double PPI::Token::Quote::Interpolate PPI::Token::QuoteLike::Command PPI::Token::QuoteLike::Backtick PPI::Token::QuoteLike::Regexp PPI::Token::QuoteLike::Readline. A number of new tests in t/...ProhibitPunctuationVars.run handle the straightforward cases and various combinations of _allowed_ variables and/or backslashing. I have not implemented handling for PPI::Token::HereDoc . I believe the next step in that regard will be to enhance that class to indicate its quoting mode. If it did so by adding a child object from one of the classes I listed above, PCPV::ProhibitPunctuationVars would almost certainly Just Work(tm) without further modification. clonezone tells me that he may be enhancing PPI with variable extraction from interpolated contexts, which would likely render this patch obsolete. Not that I mind; it's probably a better approach.
Index: t/Variables/ProhibitPunctuationVars.run =================================================================== --- t/Variables/ProhibitPunctuationVars.run (revision 3108) +++ t/Variables/ProhibitPunctuationVars.run (working copy) @@ -43,16 +43,52 @@ #----------------------------------------------------------------------------- -## name Strings -## failures 4 -## TODO We don't look into strings. +## name Strings Interpolation +## parms { allow => '$@ $!' } +## failures 12 ## cut +# Commented numbers in parens indicate expected violations in that section +# The module under test doesn't use them, they're just for reader's convenience + +# PPI::Token::Quote::Double (5) print "$+"; -print qq<$+>; -print qx<$+>; -print qr<$+>; +print "This is my $+; is it not nifty?"; +print "This is my $+; is it not $@?"; +print "this \n should $+\n violate"; +print "as \n$+ should this"; + # should pass (0) +print "\$+"; +print "$@"; +print "$!"; +print "no magic here"; +print "This is my $@; is it not nifty?" +print "but not \n\$+ this"; + +# PPI::Token::Quote::Interpolate (3) +print qq<$+>; #should violate +print qq<\$+>; #should pass +print qq<\\$+>; #should violate +print qq<\\\$+>; #should pass +print qq<\\\\$+>; #should violate + +# PPI::Token::QuoteLike::Command (1) +print qx<$+>; #should violate +print qx<\$+>; #should pass + +# PPI::Token::QuoteLike::Backtick (1) +print `$+`; #should violate +print `\$+`; #should pass + +# PPI::Token::QuoteLike::Regexp (1) +print qr<$+>; #should violate +print qr<\$+>; #should pass + +# PPI::Token::QuoteLike::Readline (1) +while (<$+>) { 1; } #should violate +while (<\$+>) { 1; } #should pass + #----------------------------------------------------------------------------- ############################################################################## Index: lib/Perl/Critic/Policy/Variables/ProhibitPunctuationVars.pm =================================================================== --- lib/Perl/Critic/Policy/Variables/ProhibitPunctuationVars.pm (revision 3108) +++ lib/Perl/Critic/Policy/Variables/ProhibitPunctuationVars.pm (working copy) @@ -39,16 +39,84 @@ sub default_severity { return $SEVERITY_LOW } sub default_themes { return qw(core pbp cosmetic) } -sub applies_to { return 'PPI::Token::Magic' } +sub applies_to { + return qw( PPI::Token::Magic + PPI::Token::Quote::Double + PPI::Token::Quote::Interpolate + PPI::Token::QuoteLike::Command + PPI::Token::QuoteLike::Backtick + PPI::Token::QuoteLike::Regexp + PPI::Token::QuoteLike::Readline + ); +} + #----------------------------------------------------------------------------- +my $_magic_regexp; +INIT { + my %_magic_vars; + + # Magic variables taken from perlvar. + # Several things added separately to avoid warnings. + # adapted from ADAMK's PPI::Token::Magic.pm + foreach ( + qw{ + $1 $2 $3 $4 $5 $6 $7 $8 $9 + $_ $& $` $' $+ @+ %+ $* $. $/ $| + $\\ $" $; $% $= $- @- %- $) + $~ $^ $: $? $! %! $@ $$ $< $> + $( $0 $[ $] @_ @* + + $^L $^A $^E $^C $^D $^F $^H + $^I $^M $^N $^O $^P $^R $^S + $^T $^V $^W $^X %^H + + $::| + }, '$}', '$,', '$#', '$#+', '$#-' + ) + { + $_magic_vars{$_} = $_; + $_magic_vars{$_} =~ + s{ ( [[:punct:]] ) }{\\$1}gox; # add \ before all punctuation + } + + $_magic_regexp = join q(|), values %_magic_vars; +} + + sub violates { my ( $self, $elem, undef ) = @_; - if ( !exists $self->{_allow}->{$elem} ) { - return $self->violation( $DESC, $EXPL, $elem ); + + if ( $elem->isa('PPI::Token::Magic') ) { + if ( !exists $self->{_allow}->{$elem} ) { + return $self->violation( $DESC, $EXPL, $elem ); + } } - return; #ok! + else { + #the remaining applies_to() classes are all interpolated strings + + my @raw_matches = ( + $elem =~ + m/ + (?: \A | [^\\] ) # beginning-of-string or any non-backslash + (?: \\\\ )* # zero or more double-backslashes + ( $_magic_regexp ) # any magic punctuation variable + /goxs + ); + + my %matches; + @matches{@raw_matches} = 1; + + my %allow = %{ $self->{_allow} }; + delete @matches{ keys %allow }; + + if (%matches) { + return $self->violation( $DESC, $EXPL, $elem ); + } + } + + return; #ok! } 1;
From: perl-rt [...] misterwhipple.com
Attached is a revised patch against SVN rev 3113. It adds support for here-documents, and has a better overall design than my previous patch. It also conforms better to Perl::Critic standards, specifically in placing startup code in initialize_if_enabled(). I expanded the tests for interpolated strings to cover every one of the punctuation variables listed in PPI/Token/Magic.pm. A few of them cause problems, apparently because of parsing difficulties in PPI. I commented out those tests, but I believe those issues cannot be repaired within Perl::Critic. The problem vars include, perhaps not surprisingly, $" and $\. I will open a ticket in PPI for this if there isn't one already.
Index: t/Variables/ProhibitPunctuationVars.run =================================================================== --- t/Variables/ProhibitPunctuationVars.run (revision 3113) +++ t/Variables/ProhibitPunctuationVars.run (working copy) @@ -43,18 +43,188 @@ #----------------------------------------------------------------------------- -## name Strings -## failures 4 -## TODO We don't look into strings. +## name Quoted String Interpolation - basic functional tests +## parms { allow => '$@ $!' } +## failures 12 ## cut +# Commented numbers in parens indicate expected violations in that section +# The module under test doesn't use them, they're just for reader's convenience + +# PPI::Token::Quote::Double (5) print "$+"; -print qq<$+>; -print qx<$+>; -print qr<$+>; +print "This is my $+. is it not nifty?"; +print "This is my $+. is it not $@?"; +print "this \n should $+\n violate"; +print "as \n$+ should this"; + # should pass (0) +print "\$+"; +print "$@"; +print "$!"; +print "no magic here"; +print "This is my $@; is it not nifty?" +print "but not \n\$+ this"; + +# PPI::Token::Quote::Interpolate (3) +print qq<$+>; #should violate +print qq<\$+>; #should pass +print qq<\\$+>; #should violate +print qq<\\\$+>; #should pass +print qq<\\\\$+>; #should violate + +# PPI::Token::QuoteLike::Command (1) +print qx<$+>; #should violate +print qx<\$+>; #should pass + +# PPI::Token::QuoteLike::Backtick (1) +print `$+`; #should violate +print `\$+`; #should pass + +# PPI::Token::QuoteLike::Regexp (1) +print qr<$+>; #should violate +print qr<\$+>; #should pass + +# PPI::Token::QuoteLike::Readline (1) +while (<$+>) { 1; } #should violate +while (<\$+>) { 1; } #should pass + #----------------------------------------------------------------------------- +## name Heredoc Interpolation +## parms { allow => '$@ $!' } +## failures 8 +## cut + +print <<DEFAULT # default, implied "" context; should violate +$+ +DEFAULT + +print <<DEFAULT # default, implied "" context; should violate +$+ +fred +wilma +DEFAULT + +print <<DEFAULT # default, implied "" context; should violate +barney +$+ +betty +DEFAULT + +print <<DEFAULT # default, implied "" context; should violate +$+ +pebbles +bambam +DEFAULT + +print <<"DOUBLE_QUOTE" # explicit "" context; should violate +$$ +DOUBLE_QUOTE + +print <<"DQ_VERYVERYVERY_LONG_HEREDOC_EOT_IDENTIFIER" # explicit "" context; should violate +$+ +DQ_VERYVERYVERY_LONG_HEREDOC_EOT_IDENTIFIER + + +print <<"MULTI_MATCHES" # explicit "" context; should violate +$$ +$+ +$\ +$^A +MULTI_MATCHES + +print <<DEFAULT_ALLOWED # default, implied "" but allowed var; should pass +$@ +DEFAULT_ALLOWED + +print <<'SINGLE_QUOTE' # '' context; should pass +$@ +SINGLE_QUOTE + +print <<`BACKTICK` # backtick context; should violate +$+ +BACKTICK + +#----------------------------------------------------------------------------- + +## name Quoted String Interpolation wart cases +## TODO debug wart cases from String Interpolation exhaustive +## failures 0 +## cut + +#print "$""; # 2 of 59 +#print "$\"; # 28 of 59 + +#----------------------------------------------------------------------------- + +## name Quoted String Interpolation - exhaustive tests +## failures 57 +## cut + +print "$!"; # 1 of 59 +#print "$""; # 2 of 59 BROKEN, copied to TODO +print "$#"; # 3 of 59 +print "$#+"; # 4 of 59 +print "$#-"; # 5 of 59 +print "$$"; # 6 of 59 +print "$%"; # 7 of 59 +print "$&"; # 8 of 59 +print "$'"; # 9 of 59 +print "$("; # 10 of 59 +print "$)"; # 11 of 59 +print "$*"; # 12 of 59 +print "$+"; # 13 of 59 +print "$,"; # 14 of 59 +print "$-"; # 15 of 59 +print "$."; # 16 of 59 +print "$/"; # 17 of 59 +print "$0"; # 18 of 59 +print "$:"; # 19 of 59 +print "$::|"; # 20 of 59 +print "$;"; # 21 of 59 +print "$<"; # 22 of 59 +print "$="; # 23 of 59 +print "$>"; # 24 of 59 +print "$?"; # 25 of 59 +print "$@"; # 26 of 59 +print "$["; # 27 of 59 +#print "$\"; # 28 of 59 BROKEN, copied to TODO +print "$]"; # 29 of 59 +print "$^"; # 30 of 59 +print "$^A"; # 31 of 59 +print "$^C"; # 32 of 59 +print "$^D"; # 33 of 59 +print "$^E"; # 34 of 59 +print "$^F"; # 35 of 59 +print "$^H"; # 36 of 59 +print "$^I"; # 37 of 59 +print "$^L"; # 38 of 59 +print "$^M"; # 39 of 59 +print "$^N"; # 40 of 59 +print "$^O"; # 41 of 59 +print "$^P"; # 42 of 59 +print "$^R"; # 43 of 59 +print "$^S"; # 44 of 59 +print "$^T"; # 45 of 59 +print "$^V"; # 46 of 59 +print "$^W"; # 47 of 59 +print "$^X"; # 48 of 59 +print "$`"; # 49 of 59 +print "$|"; # 50 of 59 +print "$}"; # 51 of 59 +print "$~"; # 52 of 59 +print "%!"; # 53 of 59 +print "%+"; # 54 of 59 +print "%-"; # 55 of 59 +print "%^H"; # 56 of 59 +print "@*"; # 57 of 59 +print "@+"; # 58 of 59 +print "@-"; # 59 of 59 + + +#----------------------------------------------------------------------------- + ############################################################################## # $URL$ # $Date$ Index: lib/Perl/Critic/Policy/Variables/ProhibitPunctuationVars.pm =================================================================== --- lib/Perl/Critic/Policy/Variables/ProhibitPunctuationVars.pm (revision 3113) +++ lib/Perl/Critic/Policy/Variables/ProhibitPunctuationVars.pm (working copy) @@ -12,7 +12,7 @@ use warnings; use Readonly; -use Perl::Critic::Utils qw{ :characters :severities :data_conversion }; +use Perl::Critic::Utils qw{ :characters :severities :data_conversion :booleans }; use base 'Perl::Critic::Policy'; our $VERSION = '1.096'; @@ -39,18 +39,153 @@ sub default_severity { return $SEVERITY_LOW } sub default_themes { return qw(core pbp cosmetic) } -sub applies_to { return 'PPI::Token::Magic' } +sub applies_to { + return qw( PPI::Token::Magic + PPI::Token::Quote::Double + PPI::Token::Quote::Interpolate + PPI::Token::QuoteLike::Command + PPI::Token::QuoteLike::Backtick + PPI::Token::QuoteLike::Regexp + PPI::Token::QuoteLike::Readline + PPI::Token::HereDoc + ); +} + #----------------------------------------------------------------------------- +# package state +my $_magic_regexp; + +# private functions +my $_violates_magic; +my $_violates_string; +my $_violates_heredoc; +my $_strings_helper; + +#----------------------------------------------------------------------------- + +sub initialize_if_enabled{ + # my $config = shift; # policy $config not needed at present + + my %_magic_vars; + + # Magic variables taken from perlvar. + # Several things added separately to avoid warnings. + # adapted from ADAMK's PPI::Token::Magic.pm + foreach ( + qw{ + $1 $2 $3 $4 $5 $6 $7 $8 $9 + $_ $& $` $' $+ @+ %+ $* $. $/ $| + $\\ $" $; $% $= $- @- %- $) + $~ $^ $: $? $! %! $@ $$ $< $> + $( $0 $[ $] @_ @* + + $^L $^A $^E $^C $^D $^F $^H + $^I $^M $^N $^O $^P $^R $^S + $^T $^V $^W $^X %^H + + $::| + }, '$}', '$,', '$#', '$#+', '$#-' + ) { + $_magic_vars{$_} = $_; + $_magic_vars{$_} =~ + s{ ( [[:punct:]] ) }{\\$1}gox; # add \ before all punctuation + } + + delete @_magic_vars{ @{supported_parameters()->{list_always_present_values}} }; + + $_magic_regexp = join q(|), values %_magic_vars; + + return $TRUE; +} + sub violates { my ( $self, $elem, undef ) = @_; + + if ( $elem->isa('PPI::Token::Magic') ) { + return $_violates_magic->(@_); + } + elsif ( $elem->isa('PPI::Token::HereDoc') ){ + return $_violates_heredoc->(@_); + } + else { + #the remaining applies_to() classes are all interpolated strings + return $_violates_string->(@_); + } + + die 'Impossible! fall-through error in method violates()'; +} + +#----------------------------------------------------------------------------- + +$_violates_magic = sub { + my ( $self, $elem, undef ) = @_; + if ( !exists $self->{_allow}->{$elem} ) { + return $self->violation( $DESC, $EXPL, $elem ); } - return; #ok! -} + + return; +}; +$_violates_string = sub { + my ( $self, $elem, undef ) = @_; + + my %matches = $_strings_helper->($elem->content(), $self->{_allow} ); + + if (%matches) { + my $DESC = qq{$DESC in interpolated string}; + + return $self->violation( $DESC, $EXPL, $elem ); + } + + return; + +}; + +$_violates_heredoc = sub{ + my ($self, $elem, undef) = @_; + + if ($elem->{_mode} eq 'interpolate' or $elem->{_mode} eq 'command'){ + + my $heredoc_string = join qq{\n}, $elem->heredoc() ; + my %matches = $_strings_helper->($heredoc_string, $self->{_allow}); + + if (%matches) { + my $DESC = qq{$DESC in interpolated here-document}; + + return $self->violation( $DESC, $EXPL, $elem ); + } + } + + return; +}; + +$_strings_helper = sub{ + my ($target_string, $allow_ref, undef) = @_; + + my @raw_matches = ( + $target_string =~ + m/ + (?: \A | [^\\] ) # beginning-of-string or any non-backslash + (?: \\\\ )* # zero or more double-backslashes + ( $_magic_regexp ) # any magic punctuation variable + /goxs + ); + + my %matches; + @matches{@raw_matches} = 1; + delete @matches{ keys %{$allow_ref} }; + + return %matches + if (%matches); + + return; #no matches +}; + + 1; __END__ @@ -102,17 +237,21 @@ =head1 BUGS -This doesn't find punctuation variables in strings. RT #35970. +Punctuation variables that confuse PPI's document parsing may not be +detected correctly or at all, and may prevent detection of subsequent +ones. =head1 AUTHOR Jeffrey Ryan Thalhammer <thaljef@cpan.org> +Edgar Whipple <perlmonk at misterwhipple dot com> =head1 COPYRIGHT Copyright (c) 2005-2009 Jeffrey Ryan Thalhammer. All rights reserved. +Additions for interpolated strings (c) 2009 Edgar Whipple. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. The full text of this license
This appears to me to be resolved. At any rate, the tests are in place and pass.