| Filename | /Users/timbo/perl5/perlbrew/perls/perl-5.18.2/lib/site_perl/5.18.2/Perl/Critic/Policy/InputOutput/RequireBriefOpen.pm |
| Statements | Executed 28 statements in 1.05ms |
| Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
|---|---|---|---|---|---|
| 1 | 1 | 1 | 16µs | 16µs | Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@10 |
| 1 | 1 | 1 | 8µs | 144µs | Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@16 |
| 1 | 1 | 1 | 8µs | 56µs | Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@21 |
| 1 | 1 | 1 | 7µs | 453µs | Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@18 |
| 1 | 1 | 1 | 7µs | 9µs | Perl::Critic::Policy::InputOutput::RequireBriefOpen::default_severity |
| 1 | 1 | 1 | 7µs | 28µs | Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@14 |
| 1 | 1 | 1 | 7µs | 10µs | Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@12 |
| 1 | 1 | 1 | 6µs | 18µs | Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@11 |
| 1 | 1 | 1 | 5µs | 5µs | Perl::Critic::Policy::InputOutput::RequireBriefOpen::supported_parameters |
| 0 | 0 | 0 | 0s | 0s | Perl::Critic::Policy::InputOutput::RequireBriefOpen::__ANON__[:102] |
| 0 | 0 | 0 | 0s | 0s | Perl::Critic::Policy::InputOutput::RequireBriefOpen::__ANON__[:141] |
| 0 | 0 | 0 | 0s | 0s | Perl::Critic::Policy::InputOutput::RequireBriefOpen::_find_close_invocations_or_return |
| 0 | 0 | 0 | 0s | 0s | Perl::Critic::Policy::InputOutput::RequireBriefOpen::_get_opened_fh |
| 0 | 0 | 0 | 0s | 0s | Perl::Critic::Policy::InputOutput::RequireBriefOpen::_get_scope |
| 0 | 0 | 0 | 0s | 0s | Perl::Critic::Policy::InputOutput::RequireBriefOpen::_unwrap_block |
| 0 | 0 | 0 | 0s | 0s | Perl::Critic::Policy::InputOutput::RequireBriefOpen::applies_to |
| 0 | 0 | 0 | 0s | 0s | Perl::Critic::Policy::InputOutput::RequireBriefOpen::default_themes |
| 0 | 0 | 0 | 0s | 0s | Perl::Critic::Policy::InputOutput::RequireBriefOpen::violates |
| Line | State ments |
Time on line |
Calls | Time in subs |
Code |
|---|---|---|---|---|---|
| 1 | ############################################################################## | ||||
| 2 | # $URL$ | ||||
| 3 | # $Date$ | ||||
| 4 | # $Author$ | ||||
| 5 | # $Revision$ | ||||
| 6 | ############################################################################## | ||||
| 7 | |||||
| 8 | package Perl::Critic::Policy::InputOutput::RequireBriefOpen; | ||||
| 9 | |||||
| 10 | 2 | 38µs | 1 | 16µs | # spent 16µs within Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@10 which was called:
# once (16µs+0s) by Module::Pluggable::Object::_require at line 10 # spent 16µs making 1 call to Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@10 |
| 11 | 2 | 19µs | 2 | 29µs | # spent 18µs (6+11) within Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@11 which was called:
# once (6µs+11µs) by Module::Pluggable::Object::_require at line 11 # spent 18µs making 1 call to Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@11
# spent 11µs making 1 call to strict::import |
| 12 | 2 | 18µs | 2 | 14µs | # spent 10µs (7+4) within Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@12 which was called:
# once (7µs+4µs) by Module::Pluggable::Object::_require at line 12 # spent 10µs making 1 call to Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@12
# spent 4µs making 1 call to warnings::import |
| 13 | |||||
| 14 | 2 | 26µs | 2 | 49µs | # spent 28µs (7+21) within Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@14 which was called:
# once (7µs+21µs) by Module::Pluggable::Object::_require at line 14 # spent 28µs making 1 call to Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@14
# spent 21µs making 1 call to Exporter::import |
| 15 | |||||
| 16 | 2 | 26µs | 2 | 281µs | # spent 144µs (8+137) within Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@16 which was called:
# once (8µs+137µs) by Module::Pluggable::Object::_require at line 16 # spent 144µs making 1 call to Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@16
# spent 137µs making 1 call to Exporter::Tiny::import |
| 17 | |||||
| 18 | 1 | 200ns | # spent 453µs (7+446) within Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@18 which was called:
# once (7µs+446µs) by Module::Pluggable::Object::_require at line 20 | ||
| 19 | hashify parse_arg_list | ||||
| 20 | 1 | 24µs | 2 | 899µs | }; # spent 453µs making 1 call to Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@18
# spent 446µs making 1 call to Exporter::import |
| 21 | 2 | 871µs | 2 | 104µs | # spent 56µs (8+48) within Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@21 which was called:
# once (8µs+48µs) by Module::Pluggable::Object::_require at line 21 # spent 56µs making 1 call to Perl::Critic::Policy::InputOutput::RequireBriefOpen::BEGIN@21
# spent 48µs making 1 call to base::import |
| 22 | |||||
| 23 | 1 | 600ns | our $VERSION = '1.121'; | ||
| 24 | |||||
| 25 | #----------------------------------------------------------------------------- | ||||
| 26 | |||||
| 27 | 1 | 2µs | 1 | 30µs | Readonly::Scalar my $DESC => q<Close filehandles as soon as possible after opening them..>; # spent 30µs making 1 call to Readonly::Scalar |
| 28 | 1 | 2µs | 1 | 42µs | Readonly::Scalar my $EXPL => [209]; # spent 42µs making 1 call to Readonly::Scalar |
| 29 | |||||
| 30 | 1 | 900ns | 1 | 20µs | Readonly::Scalar my $SCALAR_SIGIL => q<$>; # spent 20µs making 1 call to Readonly::Scalar |
| 31 | 1 | 1µs | 1 | 20µs | Readonly::Scalar my $GLOB_SIGIL => q<*>; # spent 20µs making 1 call to Readonly::Scalar |
| 32 | |||||
| 33 | # Identify the builtins that are equivalent to 'open' and 'close'. Note that | ||||
| 34 | # 'return' is considered equivalent to 'close'. | ||||
| 35 | 1 | 2µs | 2 | 27µs | Readonly::Hash my %CLOSE_BUILTIN => hashify( qw{ # spent 24µs making 1 call to Readonly::Hash
# spent 3µs making 1 call to Perl::Critic::Utils::hashify |
| 36 | close | ||||
| 37 | CORE::close | ||||
| 38 | CORE::GLOBAL::close | ||||
| 39 | return | ||||
| 40 | } ); | ||||
| 41 | 1 | 2µs | 2 | 23µs | Readonly::Hash my %OPEN_BUILTIN => hashify( qw{ # spent 21µs making 1 call to Readonly::Hash
# spent 2µs making 1 call to Perl::Critic::Utils::hashify |
| 42 | open | ||||
| 43 | CORE::open | ||||
| 44 | CORE::GLOBAL::open | ||||
| 45 | } ); | ||||
| 46 | |||||
| 47 | # Possible values for $is_lexical | ||||
| 48 | 1 | 900ns | 1 | 20µs | Readonly::Scalar my $NOT_LEXICAL => 0; # Guaranteed only false value # spent 20µs making 1 call to Readonly::Scalar |
| 49 | 1 | 800ns | 1 | 19µs | Readonly::Scalar my $LOCAL_LEXICAL => 1; # spent 19µs making 1 call to Readonly::Scalar |
| 50 | 1 | 900ns | 1 | 19µs | Readonly::Scalar my $NON_LOCAL_LEXICAL => 2; # spent 19µs making 1 call to Readonly::Scalar |
| 51 | |||||
| 52 | 1 | 800ns | 1 | 19µs | Readonly::Scalar my $LAST_ELEMENT => -1; # spent 19µs making 1 call to Readonly::Scalar |
| 53 | |||||
| 54 | #----------------------------------------------------------------------------- | ||||
| 55 | |||||
| 56 | # spent 5µs within Perl::Critic::Policy::InputOutput::RequireBriefOpen::supported_parameters which was called:
# once (5µs+0s) by Perl::Critic::Policy::new at line 88 of Perl/Critic/Policy.pm | ||||
| 57 | return ( | ||||
| 58 | { | ||||
| 59 | 1 | 7µs | name => 'lines', | ||
| 60 | description => 'The maximum number of lines between an open() and a close().', | ||||
| 61 | default_string => '9', | ||||
| 62 | behavior => 'integer', | ||||
| 63 | integer_minimum => 1, | ||||
| 64 | }, | ||||
| 65 | ); | ||||
| 66 | } | ||||
| 67 | |||||
| 68 | 1 | 2µs | # spent 9µs (7+2) within Perl::Critic::Policy::InputOutput::RequireBriefOpen::default_severity which was called:
# once (7µs+2µs) by Perl::Critic::Policy::get_severity at line 331 of Perl/Critic/Policy.pm | ||
| 69 | sub default_themes { return qw< core pbp maintenance > } | ||||
| 70 | sub applies_to { return 'PPI::Token::Word' } | ||||
| 71 | |||||
| 72 | #----------------------------------------------------------------------------- | ||||
| 73 | |||||
| 74 | sub violates { | ||||
| 75 | my ( $self, $elem, undef ) = @_; | ||||
| 76 | |||||
| 77 | # Is it a call to open? | ||||
| 78 | $OPEN_BUILTIN{$elem->content()} or return; | ||||
| 79 | return if ! is_function_call($elem); | ||||
| 80 | my @open_args = parse_arg_list($elem); | ||||
| 81 | return if 2 > @open_args; # not a valid call to open() | ||||
| 82 | |||||
| 83 | my ($is_lexical, $fh) = _get_opened_fh($open_args[0]); | ||||
| 84 | return if not $fh; | ||||
| 85 | return if $fh =~ m< \A [*]? STD (?: IN|OUT|ERR ) \z >xms; | ||||
| 86 | |||||
| 87 | for my $close_token ( $self->_find_close_invocations_or_return( | ||||
| 88 | $elem, $is_lexical ) ) { | ||||
| 89 | # The $close_token might be a close() or a return() | ||||
| 90 | # It doesn't matter which -- both satisfy this policy | ||||
| 91 | if (is_function_call($close_token)) { | ||||
| 92 | my @close_args = parse_arg_list($close_token); | ||||
| 93 | |||||
| 94 | my $close_parameter = $close_args[0]; | ||||
| 95 | if ('ARRAY' eq ref $close_parameter) { | ||||
| 96 | $close_parameter = ${$close_parameter}[0]; | ||||
| 97 | } | ||||
| 98 | if ( $close_parameter ) { | ||||
| 99 | $close_parameter = "$close_parameter"; | ||||
| 100 | return if $fh eq $close_parameter; | ||||
| 101 | |||||
| 102 | if ( any { m< \A [*] >xms } ($fh, $close_parameter) ) { | ||||
| 103 | (my $stripped_fh = $fh) =~ s< \A [*] ><>xms; | ||||
| 104 | (my $stripped_parameter = $close_parameter) =~ | ||||
| 105 | s< \A [*] ><>xms; | ||||
| 106 | |||||
| 107 | return if $stripped_fh eq $stripped_parameter; | ||||
| 108 | } | ||||
| 109 | } | ||||
| 110 | } | ||||
| 111 | elsif ($is_lexical && is_method_call($close_token)) { | ||||
| 112 | my $tok = $close_token->sprevious_sibling->sprevious_sibling; | ||||
| 113 | return if $fh eq $tok; | ||||
| 114 | } | ||||
| 115 | } | ||||
| 116 | |||||
| 117 | return $self->violation( $DESC, $EXPL, $elem ); | ||||
| 118 | } | ||||
| 119 | |||||
| 120 | sub _find_close_invocations_or_return { | ||||
| 121 | my ($self, $elem, $is_lexical) = @_; | ||||
| 122 | |||||
| 123 | my $parent = $self->_get_scope( $elem, $is_lexical ); | ||||
| 124 | return if !$parent; # I can't think of a scenario where this would happen | ||||
| 125 | |||||
| 126 | my $open_loc = $elem->location; | ||||
| 127 | # we don't actually allow _lines to be zero or undef, but maybe we will | ||||
| 128 | my $end_line = $self->{_lines} ? $open_loc->[0] + $self->{_lines} : undef; | ||||
| 129 | |||||
| 130 | my $closes = $parent->find(sub { | ||||
| 131 | ##no critic (ProhibitExplicitReturnUndef) | ||||
| 132 | my ($parent, $candidate) = @_; ## no critic(Variables::ProhibitReusedNames) | ||||
| 133 | return undef if $candidate->isa('PPI::Statement::Sub'); | ||||
| 134 | my $candidate_loc = $candidate->location; | ||||
| 135 | return undef if !defined $candidate_loc->[0]; | ||||
| 136 | return 0 if $candidate_loc->[0] < $open_loc->[0]; | ||||
| 137 | return 0 if $candidate_loc->[0] == $open_loc->[0] && $candidate_loc->[1] <= $open_loc->[1]; | ||||
| 138 | return undef if defined $end_line && $candidate_loc->[0] > $end_line; | ||||
| 139 | return 0 if !$candidate->isa('PPI::Token::Word'); | ||||
| 140 | return $CLOSE_BUILTIN{ $candidate->content() } || 0; | ||||
| 141 | }); | ||||
| 142 | return @{$closes || []}; | ||||
| 143 | } | ||||
| 144 | |||||
| 145 | sub _get_scope { | ||||
| 146 | my ( $self, $elem, $is_lexical ) = @_; | ||||
| 147 | |||||
| 148 | my $open_loc = $elem->location; | ||||
| 149 | my $end_line = ( $self->{_lines} && defined $open_loc->[0] ) ? | ||||
| 150 | $open_loc->[0] + $self->{_lines} : | ||||
| 151 | undef; | ||||
| 152 | |||||
| 153 | while ( my $dad = $elem->parent) { | ||||
| 154 | $elem = $dad; | ||||
| 155 | next if not $elem->scope; | ||||
| 156 | |||||
| 157 | # If we are analyzing something like 'open my $fh ...', the | ||||
| 158 | # most-local scope suffices. RT #64437 | ||||
| 159 | return $elem if $LOCAL_LEXICAL == $is_lexical; | ||||
| 160 | next if not defined $end_line; # Presume search everywhere | ||||
| 161 | |||||
| 162 | # If we are analyzing something like 'open $fh ...', 'open FH | ||||
| 163 | # ...', or 'open *FH ...' we need to use a scope that includes | ||||
| 164 | # the end of the legal range. We just give up and return the | ||||
| 165 | # current scope if we can not determine any of the locations | ||||
| 166 | # involved. RT #64437 | ||||
| 167 | return $elem if not $open_loc; | ||||
| 168 | my $elem_loc = $elem->location | ||||
| 169 | or return $elem; | ||||
| 170 | my $last_kid = $elem->child( $LAST_ELEMENT ) | ||||
| 171 | or return $elem; # What? no children? | ||||
| 172 | my $last_kid_loc = $last_kid->location | ||||
| 173 | or return $elem; | ||||
| 174 | # At this point, the scope we have, even if it is not the | ||||
| 175 | # correct scope for the file handle, is big enough that if the | ||||
| 176 | # corresponding close() is outside it, it must be a violation. | ||||
| 177 | # RT #64437 | ||||
| 178 | return $elem if $last_kid_loc->[0] > $end_line; | ||||
| 179 | } | ||||
| 180 | return $elem; # Whatever the top-level PPI::Node was. | ||||
| 181 | } | ||||
| 182 | |||||
| 183 | sub _get_opened_fh { | ||||
| 184 | my ($tokens) = shift; | ||||
| 185 | |||||
| 186 | my $is_lexical; | ||||
| 187 | my $fh; | ||||
| 188 | |||||
| 189 | if ( 2 == @{$tokens} ) { | ||||
| 190 | if ('my' eq $tokens->[0] && | ||||
| 191 | $tokens->[1]->isa('PPI::Token::Symbol') && | ||||
| 192 | $SCALAR_SIGIL eq $tokens->[1]->raw_type) { | ||||
| 193 | |||||
| 194 | $is_lexical = $LOCAL_LEXICAL; | ||||
| 195 | $fh = $tokens->[1]; | ||||
| 196 | } | ||||
| 197 | } | ||||
| 198 | elsif (1 == @{$tokens}) { | ||||
| 199 | my $argument = _unwrap_block( $tokens->[0] ); | ||||
| 200 | if ( $argument->isa('PPI::Token::Symbol') ) { | ||||
| 201 | my $sigil = $argument->raw_type(); | ||||
| 202 | if ($SCALAR_SIGIL eq $sigil) { | ||||
| 203 | $is_lexical = $NON_LOCAL_LEXICAL; # We need to | ||||
| 204 | # distinguish between | ||||
| 205 | # 'open my $fh ...' and | ||||
| 206 | # 'open $fh ...'. RT #64437 | ||||
| 207 | $fh = $argument; | ||||
| 208 | } | ||||
| 209 | elsif ($GLOB_SIGIL eq $sigil) { | ||||
| 210 | $is_lexical = $NOT_LEXICAL; | ||||
| 211 | $fh = $argument; | ||||
| 212 | } | ||||
| 213 | } | ||||
| 214 | elsif ($argument->isa('PPI::Token::Word') && $argument eq uc $argument) { | ||||
| 215 | $is_lexical = $NOT_LEXICAL; | ||||
| 216 | $fh = $argument; | ||||
| 217 | } | ||||
| 218 | } | ||||
| 219 | |||||
| 220 | return ($is_lexical, $fh); | ||||
| 221 | } | ||||
| 222 | |||||
| 223 | sub _unwrap_block { | ||||
| 224 | my ($element) = @_; | ||||
| 225 | |||||
| 226 | return $element if not $element->isa('PPI::Structure::Block'); | ||||
| 227 | |||||
| 228 | my @children = $element->schildren(); | ||||
| 229 | return $element if 1 != @children; | ||||
| 230 | my $child = $children[0]; | ||||
| 231 | |||||
| 232 | return $child if not $child->isa('PPI::Statement'); | ||||
| 233 | |||||
| 234 | my @grandchildren = $child->schildren(); | ||||
| 235 | return $element if 1 != @grandchildren; | ||||
| 236 | |||||
| 237 | return $grandchildren[0]; | ||||
| 238 | } | ||||
| 239 | |||||
| 240 | 1 | 7µs | 1; | ||
| 241 | |||||
| 242 | __END__ |