| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Perl::Critic::Policy::ValuesAndExpressions::ProhibitComplexVersion; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 40 |  |  | 40 |  | 28093 | use 5.010001; | 
|  | 40 |  |  |  |  | 187 |  | 
| 4 | 40 |  |  | 40 |  | 293 | use strict; | 
|  | 40 |  |  |  |  | 93 |  | 
|  | 40 |  |  |  |  | 847 |  | 
| 5 | 40 |  |  | 40 |  | 223 | use warnings; | 
|  | 40 |  |  |  |  | 90 |  | 
|  | 40 |  |  |  |  | 1254 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 | 40 |  |  | 40 |  | 241 | use Perl::Critic::Utils qw{ :booleans :characters :severities }; | 
|  | 40 |  |  |  |  | 134 |  | 
|  | 40 |  |  |  |  | 2142 |  | 
| 8 | 40 |  |  |  |  | 2354 | use Perl::Critic::Utils::PPI qw{ | 
| 9 |  |  |  |  |  |  | get_next_element_in_same_simple_statement | 
| 10 |  |  |  |  |  |  | get_previous_module_used_on_same_line | 
| 11 |  |  |  |  |  |  | is_ppi_simple_statement | 
| 12 | 40 |  |  | 40 |  | 13104 | }; | 
|  | 40 |  |  |  |  | 89 |  | 
| 13 | 40 |  |  | 40 |  | 267 | use Readonly; | 
|  | 40 |  |  |  |  | 105 |  | 
|  | 40 |  |  |  |  | 1795 |  | 
| 14 |  |  |  |  |  |  |  | 
| 15 | 40 |  |  | 40 |  | 315 | use parent 'Perl::Critic::Policy'; | 
|  | 40 |  |  |  |  | 103 |  | 
|  | 40 |  |  |  |  | 1768 |  | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | our $VERSION = '1.150'; | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | #----------------------------------------------------------------------------- | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | Readonly::Scalar my $DOLLAR => q<$>; | 
| 22 |  |  |  |  |  |  | # All uses of the $DOLLAR variable below are to prevent false failures in | 
| 23 |  |  |  |  |  |  | # xt/93_version.t. | 
| 24 |  |  |  |  |  |  | Readonly::Scalar my $VERSION_MODULE => q<version>; | 
| 25 |  |  |  |  |  |  | Readonly::Scalar my $VERSION_VARIABLE => $DOLLAR . q<VERSION>; | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | Readonly::Scalar my $DESC => | 
| 28 |  |  |  |  |  |  | $DOLLAR . q<VERSION value should not come from outside module>; | 
| 29 |  |  |  |  |  |  | Readonly::Scalar my $EXPL => | 
| 30 |  |  |  |  |  |  | q<If the version comes from outside the module, you can get everything from unexpected version changes to denial-of-service attacks.>; | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | #----------------------------------------------------------------------------- | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | sub supported_parameters { return ( | 
| 35 |  |  |  |  |  |  | { | 
| 36 | 90 |  |  | 90 | 0 | 2094 | name        => 'forbid_use_version', | 
| 37 |  |  |  |  |  |  | description => | 
| 38 |  |  |  |  |  |  | qq<Make "use version; our ${DOLLAR}VERSION = qv('1.2.3');" a violation of this policy.>, | 
| 39 |  |  |  |  |  |  | default_string  => $FALSE, | 
| 40 |  |  |  |  |  |  | behavior        => 'boolean', | 
| 41 |  |  |  |  |  |  | }, | 
| 42 |  |  |  |  |  |  | ); | 
| 43 |  |  |  |  |  |  | } | 
| 44 | 74 |  |  | 74 | 1 | 319 | sub default_severity     { return $SEVERITY_MEDIUM           } | 
| 45 | 74 |  |  | 74 | 1 | 335 | sub default_themes       { return qw( core maintenance )     } | 
| 46 | 30 |  |  | 30 | 1 | 74 | sub applies_to           { return 'PPI::Token::Symbol'       } | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | #----------------------------------------------------------------------------- | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | sub violates { | 
| 51 | 172 |  |  | 172 | 1 | 325 | my ( $self, $elem, $doc ) = @_; | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | # Any variable other than $VERSION is ignored. | 
| 54 | 172 | 100 |  |  |  | 309 | return if $VERSION_VARIABLE ne $elem->content(); | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | # We are only interested in assignments to $VERSION, but it might be a | 
| 57 |  |  |  |  |  |  | # list assignment, so if we do not find an assignment, we move up the | 
| 58 |  |  |  |  |  |  | # parse tree. If we hit a statement (or no parent at all) we do not | 
| 59 |  |  |  |  |  |  | # understand the code to be an assignment statement, and we simply return. | 
| 60 | 27 |  |  |  |  | 161 | my $operator; | 
| 61 |  |  |  |  |  |  | return if | 
| 62 | 27 | 50 | 33 |  |  | 116 | not $operator = get_next_element_in_same_simple_statement( $elem ) | 
| 63 |  |  |  |  |  |  | or  $EQUAL ne $operator; | 
| 64 |  |  |  |  |  |  |  | 
| 65 |  |  |  |  |  |  | # Find the simple statement we are in. If we can not find it, abandon the | 
| 66 |  |  |  |  |  |  | # attempt to analyze the code. | 
| 67 | 27 | 50 |  |  |  | 1422 | my $statement = $self->_get_simple_statement( $elem ) | 
| 68 |  |  |  |  |  |  | or return; | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | # Check all symbols in the statement for violation. | 
| 71 | 27 |  |  |  |  | 399 | my $exception; | 
| 72 | 27 | 50 |  |  |  | 115 | return $exception if | 
| 73 |  |  |  |  |  |  | $exception = | 
| 74 |  |  |  |  |  |  | $self->_validate_fully_qualified_symbols($elem, $statement, $doc); | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | # At this point we have found no data that is explicitly from outside the | 
| 77 |  |  |  |  |  |  | # file.  If the author wants to use a $VERSION from another module, _and_ | 
| 78 |  |  |  |  |  |  | # wants MM->parse_version to understand it, the other module must be used | 
| 79 |  |  |  |  |  |  | # on the same line. So we assume no violation unless this has been done. | 
| 80 | 27 | 50 |  |  |  | 183 | my $module = get_previous_module_used_on_same_line( $elem ) | 
| 81 |  |  |  |  |  |  | or return; | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | # We make an exception for 'use version' unless configured otherwise; so | 
| 84 |  |  |  |  |  |  | # let it be written, so let it be done. | 
| 85 | 0 | 0 | 0 |  |  | 0 | return if $module eq $VERSION_MODULE and not $self->{_forbid_use_version}; | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | # We assume nefarious intent if we have any other module used on the same | 
| 88 |  |  |  |  |  |  | # line as the $VERSION assignment. | 
| 89 | 0 |  |  |  |  | 0 | return $self->violation( $DESC, $EXPL, $elem ); | 
| 90 |  |  |  |  |  |  | } | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  | #----------------------------------------------------------------------------- | 
| 93 |  |  |  |  |  |  |  | 
| 94 |  |  |  |  |  |  | # Return the simple statement that contains our element. The classification | 
| 95 |  |  |  |  |  |  | # done by is_ppi_simple_statement is not quite good enough in this case -- if | 
| 96 |  |  |  |  |  |  | # our parent is a PPI::Structure::List, we want to keep looking. | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | sub _get_simple_statement { | 
| 99 | 27 |  |  | 27 |  | 117 | my ( $self, $elem ) = @_; | 
| 100 |  |  |  |  |  |  |  | 
| 101 | 27 |  |  |  |  | 58 | my $statement = $elem; | 
| 102 |  |  |  |  |  |  |  | 
| 103 | 27 |  |  |  |  | 101 | while ( $statement) { | 
| 104 | 54 |  |  |  |  | 195 | my $parent; | 
| 105 | 54 | 100 |  |  |  | 178 | if ( is_ppi_simple_statement( $statement ) ) { | 
| 106 | 27 | 50 | 33 |  |  | 208 | return $statement if | 
| 107 |  |  |  |  |  |  | not $parent = $statement->parent() | 
| 108 |  |  |  |  |  |  | or  not $parent->isa( 'PPI::Structure::List' ); | 
| 109 | 0 |  |  |  |  | 0 | $statement = $parent; | 
| 110 |  |  |  |  |  |  | } else { | 
| 111 | 27 |  |  |  |  | 228 | $statement = $statement->parent(); | 
| 112 |  |  |  |  |  |  | } | 
| 113 |  |  |  |  |  |  | } | 
| 114 |  |  |  |  |  |  |  | 
| 115 | 0 |  |  |  |  | 0 | return; | 
| 116 |  |  |  |  |  |  | } | 
| 117 |  |  |  |  |  |  |  | 
| 118 |  |  |  |  |  |  | #----------------------------------------------------------------------------- | 
| 119 |  |  |  |  |  |  |  | 
| 120 |  |  |  |  |  |  | sub _validate_fully_qualified_symbols { | 
| 121 | 27 |  |  | 27 |  | 70 | my ( $self, $elem, $statement, $doc ) = @_; | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | # Find the package(s) in this file. | 
| 124 |  |  |  |  |  |  | my %local_package = | 
| 125 | 27 |  |  |  |  | 118 | map { $_->schild( 1 ) => 1 } | 
| 126 | 27 | 50 |  |  |  | 62 | @{ $doc->find( 'PPI::Statement::Package' ) || [] }; | 
|  | 27 |  |  |  |  | 89 |  | 
| 127 | 27 |  |  |  |  | 615 | $local_package{main} = 1;   # For completeness. | 
| 128 |  |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  | # Check all symbols in the statement for violation. | 
| 130 | 27 |  |  |  |  | 52 | foreach my $symbol ( | 
| 131 | 27 | 50 |  |  |  | 83 | @{ $statement->find( 'PPI::Token::Symbol' ) || [] } | 
| 132 |  |  |  |  |  |  | ) { | 
| 133 | 27 | 50 |  |  |  | 8702 | if ( $symbol->canonical() =~ m< \A [@\$%&] ([\w:]*) :: >smx ) { | 
| 134 | 0 | 0 |  |  |  | 0 | $local_package{ $1 } | 
| 135 |  |  |  |  |  |  | or return $self->violation( $DESC, $EXPL, $elem ); | 
| 136 |  |  |  |  |  |  | } | 
| 137 |  |  |  |  |  |  | } | 
| 138 |  |  |  |  |  |  |  | 
| 139 |  |  |  |  |  |  | # Check all interpolatable strings in the statement for violation. | 
| 140 |  |  |  |  |  |  | # TODO this does not correctly handle "@{[some_expression()]}". | 
| 141 | 27 |  |  |  |  | 426 | foreach my $string ( | 
| 142 |  |  |  |  |  |  | @{ | 
| 143 |  |  |  |  |  |  | $statement->find( | 
| 144 |  |  |  |  |  |  | sub { | 
| 145 |  |  |  |  |  |  | return | 
| 146 | 216 |  | 33 | 216 |  | 2651 | $_[1]->isa('PPI::Token::Quote::Double') | 
| 147 |  |  |  |  |  |  | ||  $_[1]->isa('PPI::Token::Quote::Interpolate'); | 
| 148 |  |  |  |  |  |  | } | 
| 149 |  |  |  |  |  |  | ) | 
| 150 | 27 | 50 |  |  |  | 210 | or  [] | 
| 151 |  |  |  |  |  |  | } | 
| 152 |  |  |  |  |  |  | ) { | 
| 153 | 0 |  |  |  |  | 0 | my $unquoted = $string->string(); | 
| 154 | 0 |  |  |  |  | 0 | while ( | 
| 155 |  |  |  |  |  |  | $unquoted =~ | 
| 156 |  |  |  |  |  |  | m< | 
| 157 |  |  |  |  |  |  | (?: \A | [^\\] ) | 
| 158 |  |  |  |  |  |  | (?: \\{2} )* | 
| 159 |  |  |  |  |  |  | [@\$] | 
| 160 |  |  |  |  |  |  | [{]? | 
| 161 |  |  |  |  |  |  | ([\w:]*) | 
| 162 |  |  |  |  |  |  | :: | 
| 163 |  |  |  |  |  |  | >gsmx | 
| 164 |  |  |  |  |  |  | ) { | 
| 165 | 0 | 0 |  |  |  | 0 | next if $local_package{ $1 }; | 
| 166 |  |  |  |  |  |  |  | 
| 167 | 0 |  |  |  |  | 0 | return $self->violation( $DESC, $EXPL, $elem ); | 
| 168 |  |  |  |  |  |  | } | 
| 169 |  |  |  |  |  |  | } | 
| 170 |  |  |  |  |  |  |  | 
| 171 |  |  |  |  |  |  | # Check all words in the statement for violation. | 
| 172 | 27 | 50 |  |  |  | 469 | foreach my $symbol ( @{ $statement->find( 'PPI::Token::Word' ) || [] } ) { | 
|  | 27 |  |  |  |  | 82 |  | 
| 173 | 27 | 50 |  |  |  | 7483 | if ( $symbol->content() =~ m/ \A ([\w:]*) :: /smx ) { | 
| 174 |  |  |  |  |  |  | return $self->violation( $DESC, $EXPL, $elem ) | 
| 175 | 0 | 0 |  |  |  | 0 | if not $local_package{ $1 }; | 
| 176 |  |  |  |  |  |  | } | 
| 177 |  |  |  |  |  |  | } | 
| 178 |  |  |  |  |  |  |  | 
| 179 | 27 |  |  |  |  | 236 | return; | 
| 180 |  |  |  |  |  |  | } | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | 1; | 
| 183 |  |  |  |  |  |  |  | 
| 184 |  |  |  |  |  |  | __END__ | 
| 185 |  |  |  |  |  |  |  | 
| 186 |  |  |  |  |  |  | #----------------------------------------------------------------------------- | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  | =pod | 
| 189 |  |  |  |  |  |  |  | 
| 190 |  |  |  |  |  |  | =head1 NAME | 
| 191 |  |  |  |  |  |  |  | 
| 192 |  |  |  |  |  |  | Perl::Critic::Policy::ValuesAndExpressions::ProhibitComplexVersion - Prohibit version values from outside the module. | 
| 193 |  |  |  |  |  |  |  | 
| 194 |  |  |  |  |  |  | =head1 AFFILIATION | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | This Policy is part of the core L<Perl::Critic|Perl::Critic> | 
| 197 |  |  |  |  |  |  | distribution. | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | One tempting way to keep a group of related modules at the same version number | 
| 203 |  |  |  |  |  |  | is to have all of them import the version number from a designated module. For | 
| 204 |  |  |  |  |  |  | example, module C<Foo::Master> could be the version master for the C<Foo> | 
| 205 |  |  |  |  |  |  | package, and all other modules could use its C<$VERSION> by | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | use Foo::Master; our $VERSION = $Foo::Master::VERSION; | 
| 208 |  |  |  |  |  |  |  | 
| 209 |  |  |  |  |  |  | This turns out not to be a good idea, because all sorts of unintended things | 
| 210 |  |  |  |  |  |  | can happen - anything from unintended version number changes to | 
| 211 |  |  |  |  |  |  | denial-of-service attacks (since C<Foo::Master> is executed by the 'use'). | 
| 212 |  |  |  |  |  |  |  | 
| 213 |  |  |  |  |  |  | This policy examines statements that assign to C<$VERSION>, and declares a | 
| 214 |  |  |  |  |  |  | violation under two circumstances: first, if that statement uses a | 
| 215 |  |  |  |  |  |  | fully-qualified symbol that did not originate in a package declared in the | 
| 216 |  |  |  |  |  |  | file; second if there is a C<use> statement on the same line that makes the | 
| 217 |  |  |  |  |  |  | assignment. | 
| 218 |  |  |  |  |  |  |  | 
| 219 |  |  |  |  |  |  | By default, an exception is made for C<use version;> because of its | 
| 220 |  |  |  |  |  |  | recommendation by Perl Best Practices. See the C<forbid_use_version> | 
| 221 |  |  |  |  |  |  | configuration variable if you do not want an exception made for C<use | 
| 222 |  |  |  |  |  |  | version;>. | 
| 223 |  |  |  |  |  |  |  | 
| 224 |  |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | =head1 CONFIGURATION | 
| 226 |  |  |  |  |  |  |  | 
| 227 |  |  |  |  |  |  | The construction | 
| 228 |  |  |  |  |  |  |  | 
| 229 |  |  |  |  |  |  | use version; our $VERSION = qv('1.2.3'); | 
| 230 |  |  |  |  |  |  |  | 
| 231 |  |  |  |  |  |  | is exempt from this policy by default, because it is recommended by Perl Best | 
| 232 |  |  |  |  |  |  | Practices. Should you wish to identify C<use version;> as a violation, add the | 
| 233 |  |  |  |  |  |  | following to your perlcriticrc file: | 
| 234 |  |  |  |  |  |  |  | 
| 235 |  |  |  |  |  |  | [ValuesAndExpressions::ProhibitComplexVersion] | 
| 236 |  |  |  |  |  |  | forbid_use_version = 1 | 
| 237 |  |  |  |  |  |  |  | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | =head1 CAVEATS | 
| 240 |  |  |  |  |  |  |  | 
| 241 |  |  |  |  |  |  | This code assumes that the hallmark of a violation is a 'use' on the same line | 
| 242 |  |  |  |  |  |  | as the C<$VERSION> assignment, because that is the way to have it seen by | 
| 243 |  |  |  |  |  |  | L<ExtUtils::MakeMaker|ExtUtils::MakeMaker>->parse_version(). Other ways to get | 
| 244 |  |  |  |  |  |  | a version value from outside the module can be imagined, and this policy is | 
| 245 |  |  |  |  |  |  | currently oblivious to them. | 
| 246 |  |  |  |  |  |  |  | 
| 247 |  |  |  |  |  |  |  | 
| 248 |  |  |  |  |  |  | =head1 AUTHOR | 
| 249 |  |  |  |  |  |  |  | 
| 250 |  |  |  |  |  |  | Thomas R. Wyant, III F<wyant at cpan dot org> | 
| 251 |  |  |  |  |  |  |  | 
| 252 |  |  |  |  |  |  |  | 
| 253 |  |  |  |  |  |  | =head1 COPYRIGHT | 
| 254 |  |  |  |  |  |  |  | 
| 255 |  |  |  |  |  |  | Copyright (c) 2009-2023 Tom Wyant | 
| 256 |  |  |  |  |  |  |  | 
| 257 |  |  |  |  |  |  | This program is free software; you can redistribute it and/or modify | 
| 258 |  |  |  |  |  |  | it under the same terms as Perl itself.  The full text of this license | 
| 259 |  |  |  |  |  |  | can be found in the LICENSE file included with this module. | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | =cut | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  | # Local Variables: | 
| 264 |  |  |  |  |  |  | #   mode: cperl | 
| 265 |  |  |  |  |  |  | #   cperl-indent-level: 4 | 
| 266 |  |  |  |  |  |  | #   fill-column: 78 | 
| 267 |  |  |  |  |  |  | #   indent-tabs-mode: nil | 
| 268 |  |  |  |  |  |  | #   c-indentation-style: bsd | 
| 269 |  |  |  |  |  |  | # End: | 
| 270 |  |  |  |  |  |  | # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround : |