package Perl::Critic::Policy::BuiltinFunctions::ProhibitUselessTopic; use strict; use warnings; use Readonly; use Perl::Critic::Utils qw{ :severities :classification :ppi hashify }; use parent 'Perl::Critic::Policy'; our $VERSION = '1.152'; ## no critic ( ValuesAndExpressions::RequireInterpolationOfMetachars ) ## The numerous $_ variables make false positives. Readonly::Scalar my $DESC => q{Useless use of $_}; Readonly::Scalar my $EXPL_FILETEST => q{$_ should be omitted when using a filetest operator}; Readonly::Scalar my $EXPL_FUNCTION => q{$_ should be omitted when calling "%s"}; Readonly::Scalar my $EXPL_FUNCTION_SPLIT => q{$_ should be omitted when calling "split" with two arguments}; sub supported_parameters { return () } sub default_severity { return $SEVERITY_LOW } sub default_themes { return qw( core ) } sub applies_to { return 'PPI::Token::Operator', 'PPI::Token::Word' } Readonly::Array my @FILETEST_OPERATORS => qw( -r -w -x -o -R -W -X -O -e -z -s -f -d -l -p -S -b -c -u -g -k -T -B -M -A -C ); Readonly::Hash my %FILETEST_OPERATORS => hashify( @FILETEST_OPERATORS ); Readonly::Array my @TOPICAL_FUNCS => qw( abs alarm chomp chop chr chroot cos defined eval exp fc glob hex int lc lcfirst length log lstat mkdir oct ord pos print quotemeta readlink readpipe ref require reverse rmdir say sin split sqrt stat study uc ucfirst unlink unpack ); Readonly::Hash my %TOPICAL_FUNCS => hashify( @TOPICAL_FUNCS ); Readonly::Hash my %APPLIES_TO => ( %TOPICAL_FUNCS, %FILETEST_OPERATORS ); sub violates { my ( $self, $elem, undef ) = @_; return if not exists $APPLIES_TO{ $elem->content }; my $content = $elem->content; # Are we looking at a filetest? if ( $FILETEST_OPERATORS{ $content } ) { # Is there a $_ following it? my $op_node = $elem->snext_sibling; if ( $op_node && $op_node->isa('PPI::Token::Magic') ) { my $op = $op_node->content; if ( $op eq '$_' ) { return $self->violation( $DESC, $EXPL_FILETEST, $elem ); } } return; } if ( $TOPICAL_FUNCS{ $content } && is_perl_builtin( $elem ) ) { my $is_split = $content eq 'split'; my @args = parse_arg_list( $elem ); my $nth_arg_for_topic; if ( $is_split ) { return if @args != 2; # Ignore split( /\t/ ) or split( /\t/, $_, 3 ) $nth_arg_for_topic = 2; } else { $nth_arg_for_topic = 1; } if ( @args == $nth_arg_for_topic ) { my $topic_arg = $args[ $nth_arg_for_topic - 1 ]; my @tokens = @{$topic_arg}; if ( (@tokens == 1) && ($tokens[0]->content eq '$_') ) { my $msg = $is_split ? $EXPL_FUNCTION_SPLIT : (sprintf $EXPL_FUNCTION, $content); return $self->violation( $DESC, $msg, $elem ); } } return; } return; } 1; __END__ =pod =for stopwords filetest =head1 NAME Perl::Critic::Policy::BuiltinFunctions::ProhibitUselessTopic - Don't pass $_ to built-in functions that assume it, or to most filetest operators. =head1 AFFILIATION This Policy is part of the L distribution. =head1 DESCRIPTION There are a number of places where C<$_>, or "the topic" variable, is unnecessary. =head2 Topic unnecessary for certain Perl built-in functions Many Perl built-in functions will operate on C<$_> if no argument is passed. For example, the C function will operate on C<$_> by default. This snippet: for ( @list ) { if ( length( $_ ) == 4 ) { ... is more idiomatically written as: for ( @list ) { if ( length == 4 ) { ... In the case of the C function, the second argument is the one that defaults to C<$_>. This snippet: for ( @list ) { my @args = split /\t/, $_; is better written as: for ( @list ) { my @args = split /\t/; There is one built-in that this policy does B check for: C called with C<$_>. The C function only operates on C<$_> if called in scalar context. Therefore: for ( @list ) { my $backwards = reverse $_; is better written as: for ( @list ) { my $backwards = reverse; However, the distinction for scalar vs. list context on C is not yet working. See L below. =head2 Topic unnecessary for most filetest operators Another place that C<$_> is unnecessary is with a filetest operator. # These are identical. my $size = -s $_; my $size = -s; # These are identical. if ( -r $_ ) { ... if ( -r ) { ... The exception is after the C<-t> filetest operator, which instead of defaulting to C<$_> defaults to C. # These are NOT identical. if ( -t $_ ) { ... if ( -t ) { ... # Checks STDIN, not $_ =head1 KNOWN BUGS This policy flags a false positive on C called in list context, since C in list context does I assume C<$_>. my $s = reverse( $_ ); # $_ is useless. my @a = reverse( $_ ); # $_ is not useless here. =head1 CONFIGURATION This Policy is not configurable except for the standard options. =head1 AUTHOR Andy Lester =head1 COPYRIGHT Copyright (c) 2013-2022 Andy Lester This library is free software; you can redistribute it and/or modify it under the terms of the Artistic License 2.0. =cut # Local Variables: # mode: cperl # cperl-indent-level: 4 # fill-column: 78 # indent-tabs-mode: nil # c-indentation-style: bsd # End: # ex: set ts=8 sts=4 sw=4 tw=78 ft=perl expandtab shiftround :