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   29622 use 5.010001;
  40         201  
4 40     40   275 use strict;
  40         132  
  40         908  
5 40     40   235 use warnings;
  40         122  
  40         973  
6 40     40   253 use Readonly;
  40         143  
  40         2858  
7 40     40   354 use English qw< -no_match_vars >;
  40         127  
  40         403  
8              
9 40     40   17590 use PPI::Token::Magic;
  40         117  
  40         1983  
10              
11 40         2688 use Perl::Critic::Utils qw<
12             :characters :severities :data_conversion :booleans
13 40     40   274 >;
  40         114  
14              
15 40     40   15273 use PPIx::Regexp;
  40         180  
  40         2010  
16 40         2349 use PPIx::Regexp::Util 0.068 qw<
17             is_ppi_regexp_element
18 40     40   308 >;
  40         1067  
19              
20 40     40   326 use parent 'Perl::Critic::Policy';
  40         141  
  40         341  
21              
22             our $VERSION = '1.146';
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 3343 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 801 sub default_severity { return $SEVERITY_LOW }
55 84     84 1 370 sub default_themes { return qw< core pbp cosmetic > }
56              
57             sub applies_to {
58 61     61 1 322 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 371 my ( $self, $elem, $doc ) = @_;
108              
109 167 100       1313 if ( $elem->isa('PPI::Token::Magic') ) {
    100          
    100          
110 29         74 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         86 return _violates_regexp( $self, $elem, $doc );
117             }
118              
119             #the remaining applies_to() classes are all interpolated strings
120 123         2291 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   82 my ( $self, $elem, undef ) = @_;
130              
131 29 100       85 if ( !exists $self->{_allow}->{$elem} ) {
132 20         124 return $self->_make_violation( $DESC, $EXPL, $elem );
133             }
134              
135 9         60 return; # no violation
136             }
137              
138             sub _violates_string {
139 123     123   305 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         236 my $string;
147 123 100       585 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         362 $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         26 $string = $elem->content();
158 6         47 $string =~ s/ \A \w* \s* . //smx;
159 6         23 chop $string;
160             }
161              
162 123         954 my %matches = _strings_helper( $self, $string );
163 123 100       368 if (%matches) {
164 90         254 my $DESC = qq<$DESC in interpolated string>;
165 90         301 return $self->_make_violation( $DESC, $EXPL, $elem, \%matches );
166             }
167              
168 33         141 return; # no violation
169             }
170              
171             sub _violates_heredoc {
172 11     11   26 my ( $self, $elem, undef ) = @_;
173              
174 11 100 100     53 if ( $elem->{_mode} eq 'interpolate' or $elem->{_mode} eq 'command' ) {
175 10         36 my $heredoc_string = join "\n", $elem->heredoc();
176 10         87 my %matches = _strings_helper( $self, $heredoc_string );
177 10 100       31 if (%matches) {
178 9         54 my $DESC = qq<$DESC in interpolated here-document>;
179 9         36 return $self->_make_violation( $DESC, $EXPL, $elem, \%matches );
180             }
181             }
182              
183 2         10 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       17 return if ( $self->{_string_mode} eq 'disable' );
190              
191 4 50       19 my $pre = $doc->ppix_regexp_from_element( $elem )
192             or return;
193 4 50       15625 $pre->failures()
194             and return;
195              
196 4         31 my @raw_matches;
197 4 100       10 foreach my $code ( @{ $pre->find( 'PPIx::Regexp::Token::Code' ) || [] } ) {
  4         18  
198 1 50       185 my $code_doc = $code->ppi()
199             or next;
200 1         214 push @raw_matches, map { $_->symbol() } @{
201 1 50       13 $code_doc->find( 'PPI::Token::Magic' ) || [] };
  1         5  
202             }
203              
204 4         743 my %matches = hashify( @raw_matches );
205 4         11 delete @matches{ keys %{ $self->{_allow} } };
  4         32  
206 4 50       23 if ( $self->{_string_mode} eq 'simple' ) {
207 0         0 delete @matches{@IGNORE_FOR_INTERPOLATION};
208             }
209              
210 4 100       15 if ( keys %matches ) {
211 1         5 my $DESC = qq<$DESC in interpolated Regexp>;
212 1         7 return $self->_make_violation( $DESC, $EXPL, $elem, \%matches );
213             }
214              
215 3         19 return;
216             }
217              
218             #-----------------------------------------------------------------------------
219              
220             # Helper functions specific to interpolated strings
221              
222             sub _strings_helper {
223 133     133   317 my ( $self, $target_string, undef ) = @_;
224              
225 133 100       439 return if ( $self->{_string_mode} eq 'disable' );
226             return _strings_thorough( $self, $target_string )
227 132 100       616 if $self->{_string_mode} eq 'thorough';
228              
229             # we are in string_mode = simple
230              
231 9         81 my @raw_matches = map { _unbracket_variable_name( $_ ) }
  10         33  
232             $target_string =~ m/$MAGIC_REGEX/goxms;
233 9 50       31 return if not @raw_matches;
234              
235 9         30 my %matches = hashify(@raw_matches);
236              
237 9         23 delete @matches{ keys %{ $self->{_allow} } };
  9         48  
238 9         50 delete @matches{@IGNORE_FOR_INTERPOLATION};
239              
240 9         225 return %matches;
241             }
242              
243             sub _strings_thorough {
244 123     123   286 my ( $self, $target_string, undef ) = @_;
245 123         236 my %matches;
246              
247             MATCH:
248 123         1342 while ( my ($match) = $target_string =~ m/$MAGIC_REGEX/gcxms ) {
249 109         441 my $nextchar = substr $target_string, $LAST_MATCH_END[0], 1;
250 109         322 my $vname = _unbracket_variable_name( $match );
251 109         275 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       503 if ($c =~ m/ ^ \$ .* [ \w : \$ { ] $ /xms) {
260             ## no critic (RequireInterpolationOfMetachars)
261              
262 48 100 100     279 if (
263             $c =~ m/ ^(\$(?:\_[\w:]|::)) /xms
264             or $c =~ m/ ^\$\'[\w] /xms )
265             {
266             next MATCH
267 7 100       57 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       128 if $c =~ m/ ^\$\$\w /xms; # It's a scalar dereference
274             next MATCH
275 41 100 100     204 if $c eq '$#$'
276             or $c eq '$#{'; # It's an index dereferencing cast
277             next MATCH
278 39 100       120 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       114 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         572 $matches{$vname} = 1;
293             }
294              
295 123         256 delete @matches{ keys %{ $self->{_allow} } };
  123         769  
296              
297 123         540 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   4343 my ( $name ) = @_;
305 2640 50       5064 length $name > 1
306             or return ( $name );
307 2640         3937 my $brktd = $name;
308 2640         4190 substr $brktd, 1, 0, '{';
309 2640         3892 $brktd .= '}';
310 2640         6326 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   297 my ( $name ) = @_;
318 119 100       316 $name =~ m/ \A ( . ) [{] ( .+ ) [}] \z /smx
319             and return "$1$2";
320 118         295 return $name;
321             }
322              
323             #-----------------------------------------------------------------------------
324              
325             sub _create_magic_detector {
326 40     40   132 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         10220 map { quotemeta }
339 25000         36423 reverse sort { length $a <=> length $b }
340 2640         4665 map { _bracketed_form_of_variable_name( $_ ) }
341 40         187 grep { q<%> ne substr $_, 0, 1 }
  2800         17609  
342             @MAGIC_VARIABLES
343             )
344             . ')';
345              
346 40         20103 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   320 my ( $self, $desc, $expl, $elem, $vars ) = @_;
355              
356             my $vname = 'HASH' eq ref $vars ?
357 120 100       373 join ', ', sort keys %{ $vars } :
  100         336  
358             $elem->content();
359 120         846 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 :