File Coverage

blib/lib/Perl/Critic/Policy/Variables/ProhibitEvilVariables.pm
Criterion Covered Total %
statement 100 102 98.0
branch 26 34 76.4
condition 12 16 75.0
subroutine 22 22 100.0
pod 5 6 83.3
total 165 180 91.6


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::Variables::ProhibitEvilVariables;
2              
3 40     40   28338 use 5.010001;
  40         197  
4 40     40   245 use strict;
  40         113  
  40         865  
5 40     40   209 use warnings;
  40         110  
  40         1134  
6              
7 40     40   266 use English qw(-no_match_vars);
  40         121  
  40         360  
8 40     40   15403 use List::SomeUtils qw(firstval);
  40         119  
  40         2071  
9 40     40   259 use Readonly;
  40         119  
  40         2017  
10              
11             use Perl::Critic::Exception::Configuration::Option::Policy::ParameterValue
12 40     40   339 qw{ throw_policy_value };
  40         127  
  40         3159  
13 40         2087 use Perl::Critic::Utils qw{
14             :characters :severities :data_conversion
15 40     40   292 };
  40         157  
16              
17 40     40   13651 use parent 'Perl::Critic::Policy';
  40         110  
  40         269  
18              
19             our $VERSION = '1.146';
20              
21             #-----------------------------------------------------------------------------
22              
23             Readonly::Scalar my $EXPL => q{Find an alternative variable};
24              
25             Readonly::Hash my %SUBSCRIPTED_TYPE => hashify(qw{@ %});
26              
27             Readonly::Scalar my $VARIABLE_NAME_REGEX => qr< [\$\@%] \S+ >xms;
28             Readonly::Scalar my $REGULAR_EXPRESSION_REGEX =>
29             qr< [/] ( [^/]+ ) [/] >xms;
30             Readonly::Array my @DESCRIPTION_REGEXES =>
31             qr< [{] ( [^}]+ ) [}] >xms,
32             qr{ < ( [^>]+ ) > }xms,
33             qr{ [[] ( [^]]+ ) []] }xms,
34             qr{ [(] ( [^)]+ ) [)] }xms,
35             ;
36             Readonly::Scalar my $DESCRIPTION_REGEX =>
37             qr< @{[join '|', @DESCRIPTION_REGEXES]} >xms;
38              
39             # It's kind of unfortunate that I had to put capturing parentheses in the
40             # component regexes above, because they're not visible here and so make
41             # figuring out the positions of captures hard. Too bad we can't make the
42             # minimum perl version 5.10. :]
43             Readonly::Scalar my $VARIABLES_REGEX =>
44             qr<
45             \A
46             \s*
47             (?:
48             ( $VARIABLE_NAME_REGEX )
49             | $REGULAR_EXPRESSION_REGEX
50             )
51             (?: \s* $DESCRIPTION_REGEX )?
52             \s*
53             >xms;
54              
55             Readonly::Scalar my $VARIABLES_FILE_LINE_REGEX =>
56             qr<
57             \A
58             \s*
59             (?:
60             ( $VARIABLE_NAME_REGEX )
61             | $REGULAR_EXPRESSION_REGEX
62             )
63             \s*
64             ( \S (?: .* \S )? )?
65             \s*
66             \z
67             >xms;
68              
69             # Indexes in the arrays of regexes for the "variables" option.
70             Readonly::Scalar my $INDEX_REGEX => 0;
71             Readonly::Scalar my $INDEX_DESCRIPTION => 1;
72              
73             #-----------------------------------------------------------------------------
74              
75             sub supported_parameters {
76             return (
77             {
78 106     106 0 2648 name => 'variables',
79             description => 'The names of or patterns for variables to forbid.',
80             default_string => $EMPTY,
81             parser => \&_parse_variables,
82             },
83             {
84             name => 'variables_file',
85             description => 'A file containing names of or patterns for variables to forbid.',
86             default_string => $EMPTY,
87             parser => \&_parse_variables_file,
88             },
89             );
90             }
91              
92 121     121 1 490 sub default_severity { return $SEVERITY_HIGHEST }
93 79     79 1 326 sub default_themes { return qw( core bugs ) }
94 14     14 1 50 sub applies_to { return qw{PPI::Token::Symbol} }
95              
96             #-----------------------------------------------------------------------------
97              
98             sub _parse_variables {
99 104     104   455 my ($self, $parameter, $config_string) = @_;
100              
101 104 100       492 return if not $config_string;
102 13 50       75 return if $config_string =~ m< \A \s* \z >xms;
103              
104 13         35 my $variable_specifications = $config_string;
105              
106 13         311 while ( my ($variable, $regex_string, @descrs) =
107             $variable_specifications =~ m< $VARIABLES_REGEX >xms) {
108              
109 25         121 substr $variable_specifications, 0, $LAST_MATCH_END[0], $EMPTY;
110 25     85   166 my $description = firstval { defined } @descrs;
  85         150  
111              
112 25         120 $self->_handle_variable_specification(
113             variable => $variable,
114             regex_string => $regex_string,
115             description => $description,
116             option_name => 'variables',
117             option_value => $config_string,
118             );
119             }
120              
121 12 50       47 if ($variable_specifications) {
122 0         0 throw_policy_value
123             policy => $self->get_short_name(),
124             option_name => 'variables',
125             option_value => $config_string,
126             message_suffix =>
127             qq{contains unparseable data: "$variable_specifications"};
128             }
129              
130 12         95 return;
131             }
132              
133             sub _parse_variables_file {
134 104     104   456 my ($self, $parameter, $config_string) = @_;
135              
136 104 100       437 return if not $config_string;
137 2 50       14 return if $config_string =~ m< \A \s* \z >xms;
138              
139 2 50       270 open my $handle, '<', $config_string
140             or throw_policy_value
141             policy => $self->get_short_name(),
142             option_name => 'variables_file',
143             option_value => $config_string,
144             message_suffix =>
145             qq<refers to a file that could not be opened: $OS_ERROR>;
146 2         100 while ( my $line = <$handle> ) {
147 14         37 $self->_handle_variable_specification_on_line($line, $config_string);
148             }
149 2 50       48 close $handle or warn qq<Could not close "$config_string": $OS_ERROR\n>;
150              
151 2         17 return;
152             }
153              
154             sub _handle_variable_specification_on_line {
155 14     14   36 my ($self, $line, $config_string) = @_;
156              
157 14         46 $line =~ s< [#] .* \z ><>xms;
158 14         54 $line =~ s< \s+ \z ><>xms;
159 14         30 $line =~ s< \A \s+ ><>xms;
160              
161 14 100       47 return if not $line;
162              
163 6 50       117 if ( my ($variable, $regex_string, $description) =
164             $line =~ m< $VARIABLES_FILE_LINE_REGEX >xms) {
165              
166 6         21 $self->_handle_variable_specification(
167             variable => $variable,
168             regex_string => $regex_string,
169             description => $description,
170             option_name => 'variables_file',
171             option_value => $config_string,
172             );
173             }
174             else {
175 0         0 throw_policy_value
176             policy => $self->get_short_name(),
177             option_name => 'variables_file',
178             option_value => $config_string,
179             message_suffix =>
180             qq{contains unparseable data: "$line"};
181             }
182              
183 6         52 return;
184             }
185              
186             sub _handle_variable_specification {
187 31     31   184 my ($self, %arguments) = @_;
188              
189 31   66     153 my $description = $arguments{description} || $EMPTY;
190              
191 31 100       106 if ( my $regex_string = $arguments{regex_string} ) {
192             # These are variable name patterns (e.g. /acme/)
193 15         31 my $actual_regex;
194              
195 15         232 eval { $actual_regex = qr/$regex_string/sm; ## no critic (ExtendedFormatting)
196 14         53 1 }
197             or throw_policy_value
198             policy => $self->get_short_name(),
199             option_name => $arguments{option_name},
200             option_value => $arguments{option_value},
201 15 100       29 message_suffix =>
202             qq{contains an invalid regular expression: "$regex_string"};
203              
204             # Can't use a hash due to stringification, so this is an AoA.
205             push
206 14   100     37 @{ $self->{_evil_variables_regexes} ||= [] },
  14         89  
207             [ $actual_regex, $description ];
208             }
209             else {
210             # These are literal variable names (e.g. $[)
211 16   100     86 $self->{_evil_variables} ||= {};
212 16         37 my $name = $arguments{variable};
213 16         50 $self->{_evil_variables}{$name} = $description;
214             }
215              
216 30         244 return;
217             }
218              
219             #-----------------------------------------------------------------------------
220              
221             sub initialize_if_enabled {
222 88     88 1 369 my ($self, $config) = @_;
223              
224             # Disable if no variables are specified; there's no point in running if
225             # there aren't any.
226             return
227             exists $self->{_evil_variables}
228 88   66     744 || exists $self->{_evil_variables_regexes};
229             }
230              
231             #-----------------------------------------------------------------------------
232              
233             sub violates {
234 39     39 1 108 my ( $self, $elem, undef ) = @_;
235 39 50       177 return if not $elem;
236              
237 39 50       140 my @names = $self->_compute_symbol_names( $elem )
238             or return;
239              
240 39         110 my $evil_variables = $self->{_evil_variables};
241 39         84 my $evil_variables_regexes = $self->{_evil_variables_regexes};
242              
243 39         95 foreach my $variable (@names) {
244             exists $evil_variables->{$variable}
245             and return $self->_make_violation(
246             $variable,
247 55 100       191 $evil_variables->{$variable},
248             $elem,
249             );
250             }
251              
252 23         49 foreach my $variable (@names) {
253 25         46 foreach my $regex ( @{$evil_variables_regexes} ) {
  25         67  
254 37 100       278 $variable =~ $regex->[$INDEX_REGEX]
255             and return $self->_make_violation(
256             $variable,
257             $regex->[$INDEX_DESCRIPTION],
258             $elem,
259             );
260             }
261             }
262              
263 2         9 return; # ok!
264             }
265              
266             #-----------------------------------------------------------------------------
267              
268             # We are unconditionally interested in the names of the symbol itself. If the
269             # symbol is subscripted, we are interested in the subscripted form as well.
270              
271             sub _compute_symbol_names {
272 39     39   92 my ($self, $elem) = @_;
273              
274 39         72 my @names;
275              
276 39         135 my $name = $elem->symbol();
277 39         3401 push @names, $name;
278              
279 39 100       158 if ($SUBSCRIPTED_TYPE{$elem->symbol_type()}) {
280 20         1918 $name = $elem->content();
281 20         104 my $next = $elem->snext_sibling();
282 20         429 my @subscr;
283 20   66     180 while ($next and $next->isa('PPI::Structure::Subscript')) {
284 19         76 push @subscr, $next->content();
285 19         620 $next = $next->snext_sibling();
286             }
287 20 100       617 if (@subscr) {
288 19         75 push @names, join $EMPTY, $name, @subscr;
289             }
290             }
291              
292 39         1202 return @names;
293             }
294              
295             #-----------------------------------------------------------------------------
296              
297             sub _make_violation {
298 37     37   121 my ($self, $variable, $description, $elem) = @_;
299 37   66     255 return $self->violation(
300             $description || qq<Prohibited variable "$variable" used>,
301             $EXPL,
302             $elem,
303             );
304             }
305              
306             1;
307              
308             __END__
309              
310             #-----------------------------------------------------------------------------
311              
312             =pod
313              
314             =for stopwords subscripted
315              
316             =head1 NAME
317              
318             Perl::Critic::Policy::Variables::ProhibitEvilVariables - Ban variables that aren't blessed by your shop.
319              
320              
321             =head1 AFFILIATION
322              
323             This Policy is part of the core L<Perl::Critic|Perl::Critic>
324             distribution.
325              
326              
327             =head1 DESCRIPTION
328              
329             Use this policy if you wish to prohibit the use of specific variables. These
330             may be global variables warned against in C<perlvar>, or just variables whose
331             names you do not like.
332              
333              
334             =head1 CONFIGURATION
335              
336             The set of prohibited variables is configurable via the C<variables> and
337             C<variables_file> options.
338              
339             The value of C<variables> should be a string of space-delimited, fully
340             qualified variable names and/or regular expressions. An example of
341             prohibiting two specific variables in a F<.perlcriticrc> file:
342              
343             [Variables::ProhibitEvilVariables]
344             variables = $[ $^S $SIG{__DIE__}
345              
346             If you prohibit an array or hash (e.g. C<@INC>), use of elements of the array
347             or hash will be prohibited as well. If you specify a subscripted variable (e.g.
348             C<$SIG{__DIE__}>), only the literal subscript specified will be detected. The
349             above <.perlcritic> file, for example, will cause C<perlcritic (1)> to detect
350             C<$SIG{__DIE__} = \&foo>, but not
351              
352             my $foo = '__DIE__';
353             $SIG{$foo} = \&foo;
354              
355             Regular expressions are identified by values beginning and ending with
356             slashes. Any variable with a name that matches C<m/pattern/sm> will be
357             forbidden. For example:
358              
359             [Variables::ProhibitEvilVariables]
360             variables = /acme/
361              
362             would cause all variables that match C<m/acme/> to be forbidden. If
363             you want a case-blind check, you can use (?i: ... ). For example
364              
365             [Variables::ProhibitEvilVariables]
366             variables = /(?i:acme)/
367              
368             forbids variables that match C<m/acme/smi>.
369              
370             In addition, you can override the default message ("Prohibited variable
371             "I<variable>" used") with your own, in order to give suggestions for
372             alternative action. To do so, put your message in curly braces after
373             the variable name or regular expression. Like this:
374              
375             [Variables::ProhibitEvilVariables]
376             variables = $[ {Found use of $[. Program to base index 0 instead}
377              
378             If your message contains curly braces, you can enclose it in parentheses,
379             angle brackets, or square brackets instead.
380              
381             Similarly, the C<variables_file> option gives the name of a file
382             containing specifications for prohibited variables. Only one variable
383             specification is allowed per line and comments start with an octothorp
384             and run to end of line; no curly braces are necessary for delimiting
385             messages:
386              
387             $[ # Prohibit the "$[" variable and use the default message.
388              
389             # Prohibit the "$^S" variable and give a replacement message.
390             $^S Having to think about $^S in exception handlers is just wrong
391              
392             # Use a regular expression.
393             /acme/ No coyotes allowed.
394              
395             By default, there are no prohibited variables, although I can think of a
396             few that should be. See C<perldoc perlvar> for a few suggestions.
397              
398              
399             =head1 RESTRICTIONS
400              
401             Variables of the form C<${^foo}> are not recognized by PPI as of version
402             1.206. When PPI recognizes these, this policy will Just Work for them too.
403              
404             Only direct references to prohibited variables and literal subscripts will be
405             recognized. For example, if you prohibit $[, the first line in
406              
407             my $foo = \$[;
408             $$foo = 1;
409              
410             will be flagged as a violation, but not the second, even though the second, in
411             fact, assigns to $[. Similarly, if you prohibit $SIG{__DIE__}, this policy
412             will not recognize
413              
414             my $foo = '__DIE__';
415             $SIG{$foo} = sub {warn 'I cannot die!'};
416              
417             as an assignment to $SIG{__DIE__}.
418              
419              
420             =head1 NOTES
421              
422             This policy leans heavily on
423             L<Perl::Critic::Policy::Modules::ProhibitEvilModules|Perl::Critic::Policy::Modules::ProhibitEvilModules>
424             by Jeffrey Ryan Thalhammer.
425              
426              
427             =head1 AUTHOR
428              
429             Thomas R. Wyant, III F<wyant at cpan dot org>
430              
431              
432             =head1 COPYRIGHT
433              
434             Copyright (c) 2009-2011 Thomas R. Wyant, III
435              
436             This program is free software; you can redistribute it and/or modify
437             it under the same terms as Perl itself. The full text of this license
438             can be found in the LICENSE file included with this module.
439              
440             =cut
441              
442             # Local Variables:
443             # mode: cperl
444             # cperl-indent-level: 4
445             # fill-column: 78
446             # indent-tabs-mode: nil
447             # c-indentation-style: bsd
448             # End:
449             # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :