File Coverage

blib/lib/Perl/Critic/Policy/Variables/ProhibitPunctuationVars.pm
Criterion Covered Total %
statement 130 131 99.2
branch 49 58 84.4
condition 9 9 100.0
subroutine 25 25 100.0
pod 4 5 80.0
total 217 228 95.1


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::Variables::ProhibitPunctuationVars;
2              
3 40     40   28574 use 5.010001;
  40         208  
4 40     40   283 use strict;
  40         151  
  40         896  
5 40     40   241 use warnings;
  40         126  
  40         939  
6 40     40   241 use Readonly;
  40         124  
  40         2023  
7 40     40   291 use English qw< -no_match_vars >;
  40         107  
  40         410  
8              
9 40     40   17264 use PPI::Token::Magic;
  40         121  
  40         2075  
10              
11 40         2208 use Perl::Critic::Utils qw<
12             :characters :severities :data_conversion :booleans
13 40     40   318 >;
  40         167  
14              
15 40     40   14726 use PPIx::Regexp;
  40         139  
  40         1865  
16 40         2926 use PPIx::Regexp::Util 0.068 qw<
17             is_ppi_regexp_element
18 40     40   329 >;
  40         1117  
19              
20 40     40   379 use parent 'Perl::Critic::Policy';
  40         142  
  40         324  
21              
22             our $VERSION = '1.148';
23              
24             #-----------------------------------------------------------------------------
25              
26             Readonly::Scalar my $DESC => q<Magic punctuation variable %s used>;
27             Readonly::Scalar my $EXPL => [79];
28              
29             #-----------------------------------------------------------------------------
30              
31             # There is no English.pm equivalent for $].
32             sub supported_parameters {
33             return (
34             {
35 123     123 0 3115 name => 'allow',
36             description => 'The additional variables to allow.',
37             default_string => $EMPTY,
38             behavior => 'string list',
39             list_always_present_values =>
40             [ qw< $_ @_ $1 $2 $3 $4 $5 $6 $7 $8 $9 _ $] > ],
41             },
42             {
43             name => 'string_mode',
44             description =>
45             'Controls checking interpolated strings for punctuation variables.',
46             default_string => 'thorough',
47             behavior => 'enumeration',
48             enumeration_values => [ qw< simple disable thorough > ],
49             enumeration_allow_multiple_values => 0,
50             },
51             );
52             }
53              
54 194     194 1 769 sub default_severity { return $SEVERITY_LOW }
55 84     84 1 339 sub default_themes { return qw< core pbp cosmetic > }
56              
57             sub applies_to {
58 61     61 1 327 return qw<
59             PPI::Token::Magic
60             PPI::Token::Quote::Double
61             PPI::Token::Quote::Interpolate
62             PPI::Token::QuoteLike::Command
63             PPI::Token::QuoteLike::Backtick
64             PPI::Token::QuoteLike::Regexp
65             PPI::Token::QuoteLike::Readline
66             PPI::Token::HereDoc
67             >;
68             }
69              
70             #-----------------------------------------------------------------------------
71              
72              
73             # This list matches the initialization of %PPI::Token::Magic::magic.
74             ## no critic (ValuesAndExpressions::RequireInterpolationOfMetachars)
75             Readonly::Array my @MAGIC_VARIABLES =>
76             qw{
77             $1 $2 $3 $4 $5 $6 $7 $8 $9
78             $_ $& $` $' $+ @+ %+ $* $. $/ $|
79             $\\ $" $; $% $= $- @- %- $)
80             $~ $^ $: $? $! %! $@ $$ $< $>
81             $( $0 $[ $] @_ @*
82              
83             $^L $^A $^E $^C $^D $^F $^H
84             $^I $^M $^N $^O $^P $^R $^S
85             $^T $^V $^W $^X %^H
86              
87             $::|
88             },
89             q<$}>,
90             q<$,>,
91             q<$#>,
92             q<$#+>,
93             q<$#->;
94             ## use critic
95              
96             # The main regular expression for detecting magic variables.
97             Readonly::Scalar my $MAGIC_REGEX => _create_magic_detector();
98              
99             # The magic vars in this array will be ignored in interpolated strings
100             # in simple mode. See CONFIGURATION in the pod.
101             Readonly::Array my @IGNORE_FOR_INTERPOLATION =>
102             ( q{$'}, q{$$}, q{$#}, q{$:}, ); ## no critic ( RequireInterpolationOfMetachars, ProhibitQuotedWordLists )
103              
104             #-----------------------------------------------------------------------------
105              
106             sub violates {
107 167     167 1 361 my ( $self, $elem, $doc ) = @_;
108              
109 167 100       1153 if ( $elem->isa('PPI::Token::Magic') ) {
    100          
    100          
110 29         79 return _violates_magic( $self, $elem );
111             }
112             elsif ( $elem->isa('PPI::Token::HereDoc') ) {
113 11         36 return _violates_heredoc( $self, $elem );
114             }
115             elsif ( is_ppi_regexp_element( $elem ) ) { # GitHub #843
116 4         85 return _violates_regexp( $self, $elem, $doc );
117             }
118              
119             #the remaining applies_to() classes are all interpolated strings
120 123         2144 return _violates_string( $self, $elem );
121             }
122              
123             #-----------------------------------------------------------------------------
124              
125             # Helper functions for the four types of violations: code, quotes, heredoc,
126             # regexp
127              
128             sub _violates_magic {
129 29     29   58 my ( $self, $elem, undef ) = @_;
130              
131 29 100       83 if ( !exists $self->{_allow}->{$elem} ) {
132 20         128 return $self->_make_violation( $DESC, $EXPL, $elem );
133             }
134              
135 9         55 return; # no violation
136             }
137              
138             sub _violates_string {
139 123     123   287 my ( $self, $elem, undef ) = @_;
140              
141             # RT #55604: Variables::ProhibitPunctuationVars gives false-positive on
142             # qr// regexp's ending in '$'
143             # We want to analyze the content of the string in the dictionary sense of
144             # the word 'content'. We can not simply use the PPI content() method to
145             # get this, because content() includes the delimiters.
146 123         189 my $string;
147 123 100       504 if ( $elem->can( 'string' ) ) {
148             # If we have a string() method (currently only the PPI::Token::Quote
149             # classes) use it to extract the content of the string.
150 117         328 $string = $elem->string();
151             } else {
152             # Lacking string(), we fake it under the assumption that the content
153             # of our element represents one of the 'normal' Perl strings, with a
154             # single-character delimiter, possibly preceded by an operator like
155             # 'qx' or 'qr'. If there is a leading operator, spaces may appear
156             # after it.
157 6         23 $string = $elem->content();
158 6         98 $string =~ s/ \A \w* \s* . //smx;
159 6         21 chop $string;
160             }
161              
162 123         902 my %matches = _strings_helper( $self, $string );
163 123 100       334 if (%matches) {
164 90         222 my $DESC = qq<$DESC in interpolated string>;
165 90         272 return $self->_make_violation( $DESC, $EXPL, $elem, \%matches );
166             }
167              
168 33         126 return; # no violation
169             }
170              
171             sub _violates_heredoc {
172 11     11   32 my ( $self, $elem, undef ) = @_;
173              
174 11 100 100     56 if ( $elem->{_mode} eq 'interpolate' or $elem->{_mode} eq 'command' ) {
175 10         32 my $heredoc_string = join "\n", $elem->heredoc();
176 10         76 my %matches = _strings_helper( $self, $heredoc_string );
177 10 100       30 if (%matches) {
178 9         29 my $DESC = qq<$DESC in interpolated here-document>;
179 9         30 return $self->_make_violation( $DESC, $EXPL, $elem, \%matches );
180             }
181             }
182              
183 2         7 return; # no violation
184             }
185              
186             sub _violates_regexp { # GitHub #843 (https://github.com/Perl-Critic/Perl-Critic/issues/843)
187 4     4   14 my ( $self, $elem, $doc ) = @_;
188              
189 4 50       16 return if ( $self->{_string_mode} eq 'disable' );
190              
191 4 50       16 my $pre = $doc->ppix_regexp_from_element( $elem )
192             or return;
193 4 50       15760 $pre->failures()
194             and return;
195              
196 4         51 my @raw_matches;
197 4 100       10 foreach my $code ( @{ $pre->find( 'PPIx::Regexp::Token::Code' ) || [] } ) {
  4         15  
198 1 50       186 my $code_doc = $code->ppi()
199             or next;
200 1         208 push @raw_matches, map { $_->symbol() } @{
201 1 50       13 $code_doc->find( 'PPI::Token::Magic' ) || [] };
  1         4  
202             }
203              
204 4         1079 my %matches = hashify( @raw_matches );
205 4         10 delete @matches{ keys %{ $self->{_allow} } };
  4         36  
206 4 50       20 if ( $self->{_string_mode} eq 'simple' ) {
207 0         0 delete @matches{@IGNORE_FOR_INTERPOLATION};
208             }
209              
210 4 100       17 if ( keys %matches ) {
211 1         5 my $DESC = qq<$DESC in interpolated Regexp>;
212 1         5 return $self->_make_violation( $DESC, $EXPL, $elem, \%matches );
213             }
214              
215 3         17 return;
216             }
217              
218             #-----------------------------------------------------------------------------
219              
220             # Helper functions specific to interpolated strings
221              
222             sub _strings_helper {
223 133     133   280 my ( $self, $target_string, undef ) = @_;
224              
225 133 100       368 return if ( $self->{_string_mode} eq 'disable' );
226             return _strings_thorough( $self, $target_string )
227 132 100       402 if $self->{_string_mode} eq 'thorough';
228              
229             # we are in string_mode = simple
230              
231 9         79 my @raw_matches = map { _unbracket_variable_name( $_ ) }
  10         23  
232             $target_string =~ m/$MAGIC_REGEX/goxms;
233 9 50       31 return if not @raw_matches;
234              
235 9         34 my %matches = hashify(@raw_matches);
236              
237 9         18 delete @matches{ keys %{ $self->{_allow} } };
  9         48  
238 9         40 delete @matches{@IGNORE_FOR_INTERPOLATION};
239              
240 9         242 return %matches;
241             }
242              
243             sub _strings_thorough {
244 123     123   271 my ( $self, $target_string, undef ) = @_;
245 123         199 my %matches;
246              
247             MATCH:
248 123         1247 while ( my ($match) = $target_string =~ m/$MAGIC_REGEX/gcxms ) {
249 109         424 my $nextchar = substr $target_string, $LAST_MATCH_END[0], 1;
250 109         313 my $vname = _unbracket_variable_name( $match );
251 109         241 my $c = $vname . $nextchar;
252              
253             # These tests closely parallel those in PPI::Token::Magic,
254             # from which the regular expressions were taken.
255             # A degree of simplicity is sacrificed to maintain the parallel.
256             # $c is so named by analogy to that module.
257              
258             # possibly *not* a magic variable
259 109 100       470 if ($c =~ m/ ^ \$ .* [ \w : \$ { ] $ /xms) {
260             ## no critic (RequireInterpolationOfMetachars)
261              
262 48 100 100     258 if (
263             $c =~ m/ ^(\$(?:\_[\w:]|::)) /xms
264             or $c =~ m/ ^\$\'[\w] /xms )
265             {
266             next MATCH
267 7 100       56 if $c !~ m/ ^\$\'\d$ /xms;
268             # It not $' followed by a digit.
269             # So it's magic var with something immediately after.
270             }
271              
272             next MATCH
273 42 100       116 if $c =~ m/ ^\$\$\w /xms; # It's a scalar dereference
274             next MATCH
275 41 100 100     194 if $c eq '$#$'
276             or $c eq '$#{'; # It's an index dereferencing cast
277             next MATCH
278 39 100       106 if $c =~ m/ ^(\$\#)\w /xms
279             ; # It's an array index thingy, e.g. $#array_name
280              
281             # PPI's checks for long escaped vars like $^WIDE_SYSTEM_CALLS
282             # appear to be erroneous, and are omitted here.
283             # if ( $c =~ m/^\$\^\w{2}$/xms ) {
284             # }
285              
286 38 50       99 next MATCH if $c =~ m/ ^ \$ \# [{] /xms; # It's a $#{...} cast
287             }
288              
289             # The additional checking that PPI::Token::Magic does at this point
290             # is not necessary here, in an interpolated string context.
291              
292 99         621 $matches{$vname} = 1;
293             }
294              
295 123         224 delete @matches{ keys %{ $self->{_allow} } };
  123         732  
296              
297 123         515 return %matches;
298             }
299              
300             # RT #72910: A magic variable may appear in bracketed form; e.g. "$$" as
301             # "${$}". Generate the bracketed form from the unbracketed form, and
302             # return both.
303             sub _bracketed_form_of_variable_name {
304 2640     2640   4284 my ( $name ) = @_;
305 2640 50       5029 length $name > 1
306             or return ( $name );
307 2640         3801 my $brktd = $name;
308 2640         4180 substr $brktd, 1, 0, '{';
309 2640         3753 $brktd .= '}';
310 2640         6510 return( $name, $brktd );
311             }
312              
313             # RT #72910: Since we loaded both bracketed and unbracketed forms of the
314             # punctuation variables into our detecting regex, we need to detect and
315             # strip the brackets if they are present to recover the canonical name.
316             sub _unbracket_variable_name {
317 119     119   237 my ( $name ) = @_;
318 119 100       295 $name =~ m/ \A ( . ) [{] ( .+ ) [}] \z /smx
319             and return "$1$2";
320 118         273 return $name;
321             }
322              
323             #-----------------------------------------------------------------------------
324              
325             sub _create_magic_detector {
326 40     40   128 my ($config) = @_;
327              
328             # Set up the regexp alternation for matching magic variables.
329             # We can't process $config->{_allow} here because of a quirk in the
330             # way Perl::Critic handles testing.
331             #
332             # The sort is needed so that, e.g., $^ doesn't mask out $^M
333             my $magic_alternation =
334             '(?:'
335             . (
336             join
337             q<|>,
338 5280         10200 map { quotemeta }
339 25000         35999 reverse sort { length $a <=> length $b }
340 2640         4778 map { _bracketed_form_of_variable_name( $_ ) }
341 40         203 grep { q<%> ne substr $_, 0, 1 }
  2800         17608  
342             @MAGIC_VARIABLES
343             )
344             . ')';
345              
346 40         19753 return qr<
347             (?: \A | [^\\] ) # beginning-of-string or any non-backslash
348             (?: \\{2} )* # zero or more double-backslashes
349             ( $magic_alternation ) # any magic punctuation variable
350             >xsm;
351             }
352              
353             sub _make_violation {
354 120     120   284 my ( $self, $desc, $expl, $elem, $vars ) = @_;
355              
356             my $vname = 'HASH' eq ref $vars ?
357 120 100       352 join ', ', sort keys %{ $vars } :
  100         320  
358             $elem->content();
359 120         801 return $self->violation( sprintf( $desc, $vname ), $expl, $elem );
360             }
361              
362             1;
363              
364             __END__
365              
366             #-----------------------------------------------------------------------------
367              
368             =pod
369              
370             =head1 NAME
371              
372             Perl::Critic::Policy::Variables::ProhibitPunctuationVars - Write C<$EVAL_ERROR> instead of C<$@>.
373              
374              
375             =head1 AFFILIATION
376              
377             This Policy is part of the core L<Perl::Critic|Perl::Critic>
378             distribution.
379              
380              
381             =head1 DESCRIPTION
382              
383             Perl's vocabulary of punctuation variables such as C<$!>, C<$.>, and
384             C<$^> are perhaps the leading cause of its reputation as inscrutable
385             line noise. The simple alternative is to use the L<English|English>
386             module to give them clear names.
387              
388             $| = undef; #not ok
389              
390             use English qw(-no_match_vars);
391             local $OUTPUT_AUTOFLUSH = undef; #ok
392              
393             =head1 CONFIGURATION
394              
395             The scratch variables C<$_> and C<@_> are very common and are pretty
396             well understood, so they are exempt from this policy. The same goes
397             for the less-frequently-used default filehandle C<_> used by stat().
398             All the regexp capture variables (C<$1>, C<$2>, ...) are exempt too.
399             C<$]> is exempt because there is no L<English|English> equivalent and
400             L<Module::CoreList|Module::CoreList> is based upon it.
401              
402             You can add more exceptions to your configuration. In your
403             perlcriticrc file, add a block like this:
404              
405             [Variables::ProhibitPunctuationVars]
406             allow = $@ $!
407              
408             The C<allow> property should be a whitespace-delimited list of
409             punctuation variables.
410              
411             Other configuration options control the parsing of interpolated
412             strings in the search for forbidden variables. They have no effect
413             on detecting punctuation variables outside of interpolated strings.
414              
415             [Variables::ProhibitPunctuationVars]
416             string_mode = thorough
417              
418             The option C<string_mode> controls whether and how interpolated
419             strings are searched for punctuation variables. Setting
420             C<string_mode = thorough>, the default, checks for special cases
421             that may look like punctuation variables but aren't, for example
422             C<$#foo>, an array index count; C<$$bar>, a scalar dereference; or
423             C<$::baz>, a global symbol.
424              
425             Setting C<string_mode = disable> causes all interpolated strings to
426             be ignored entirely.
427              
428             Setting C<string_mode = simple> uses a simple regular expression to
429             find matches. In this mode, the magic variables C<$$>, C<$'>, C<$#>
430             and C<$:> are ignored within interpolated strings due to the high
431             risk of false positives. Simple mode is retained from an earlier
432             draft of the interpolated- strings code. Its use is only recommended
433             as a workaround if bugs appear in thorough mode.
434              
435             The C<string_mode> option will go away when the parsing of
436             interpolated strings is implemented in PPI. See L</CAVEATS> below.
437              
438              
439             =head1 BUGS
440              
441             Punctuation variables that confuse PPI's document parsing may not be
442             detected correctly or at all, and may prevent detection of
443             subsequent ones. In particular, C<$"> is known to cause difficulties
444             in interpolated strings.
445              
446              
447             =head1 CAVEATS
448              
449             ProhibitPunctuationVars relies exclusively on PPI to find
450             punctuation variables in code, but does all the parsing itself for
451             interpolated strings. When, at some point, this functionality is
452             transferred to PPI, ProhibitPunctuationVars will cease doing the
453             interpolating and the C<string_mode> option will go away.
454              
455              
456             =head1 AUTHOR
457              
458             Jeffrey Ryan Thalhammer <jeff@imaginative-software.com>
459              
460              
461             =head1 COPYRIGHT
462              
463             Copyright (c) 2005-2011 Imaginative Software Systems. All rights reserved.
464              
465             This program is free software; you can redistribute it and/or modify
466             it under the same terms as Perl itself. The full text of this license
467             can be found in the LICENSE file included with this module.
468              
469             =cut
470              
471             # Local Variables:
472             # mode: cperl
473             # cperl-indent-level: 4
474             # fill-column: 78
475             # indent-tabs-mode: nil
476             # c-indentation-style: bsd
477             # End:
478             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :