# Perltidy Change Log
-## 2023 09 12.08
+## 2023 09 12.09
+
+ - Added --dump-mixed-call-parens (-dmcp ) which will dump a list
+ of operators which are sometimes followed by parens and sometimes not.
+ For example
+
+ perltidy -cmcp somefile.pl >out.txt
+
+ produces lines like this, where the first number is the count of
+ uses with parens, and the second number is the count without parens.
+
+ k:caller:2:1
+ k:chomp:3:4
+ k:close:7:4
+
+ - Added --warn-call-parens=s (-wcp=s) option which will warn of paren
+ uses which do not match a selected style. The manual has details. But
+ for example,
+
+ perltidy -wcp='&' somefile.pl
+
+ will format as normal but warn if any user subs are called without parens.
- Added --dump-unusual-variables (-duv) option to dump a list of
variables with certain properties of interest. For example
B<-DEBUG> will write a file with extension F<.DEBUG> for each input file
showing the tokenization of all lines of code.
-=item B<Making a table of information on code blocks with --dump-block-summary>
+=item B<Use --dump-block-summary to make a table of information on code blocks>
A table listing information about the blocks of code in a file can be made with
B<--dump-block-summary>, or B<-dbs>. This causes perltidy to read and parse
=back
-=item B<Finding unused, reused, and other variables of interest with --dump-unusual-variables>
+=item B<use --dump-unusual-variables to find unused, reused, and certain other variables of interest>
Lexical variables with certain properties of interest to a programmer can be
listed with B<--dump-unusual-variables> or B<-duv>. This parameter must be on
These are variables which are re-declared in the scope of a variable with the
identical name. This can be confusing (perhaps not when the code is first
-written, but possibly later when an update is being made). Note that this is
+written, but possibly later during maintenance work). Note that this is
similar to the B<Perl::Critic> policy B<Variables::ProhibitReusedNames>.
=item B<s: sigil change but reused bareword>
These are lexical variables which are declared in one package and still visible
in subroutines of a different package in the same file. This can be confusing.
This check is limited to packages which are not enclosed in block braces in
-order skip some common use cases.
+order skip temporary package changes.
=item B<u: unused variables>
=back
-=item B<Warning about certain variable types with --warn-variable-types>
+=item B<Use --warn-variable-types to warn about certain variable types>
The flag B<--warn-variable-types=string>, or B<-wvt=string>, can be used to to
produce a warning message if certain of the above variable types are
will skip all warnings for variables C<$self> and C<$class>.
-=item B<Warning about missing or extra call parens with --want-call-parens=s>
+=item B<Use --dump-mixed-call-parens to find all mixed paren uses>
Perl allows function call arguments to be placed within parentheses, but it is
not generally required. Styles vary, but often the arguments of user-defined
functions are placed within parens, and the arguments of many builtin function
calls are often written without parens.
-The flag B<--want-call-parens=s>, or B<-wcp=s>, can be used to to produce
-a warning message if the parens, or lack of parens, for a selected function
-does not match a desired style. This information can help a programmer to
-maintain a certain style.
+The parameter B<--dump-mixed-call-parens>, or B<-dmcp>, can be used to to
+make a list of keywords and sub names which have been written both with and
+without following parens. This can be useful for checking for a consistent
+programming style. For example,
-The string argument B<s> is a list of functions which perltidy should check.
-The function names may builtin keywords or user-defined subs.
-Perltidy will scan the text for the listed function names, looks for a
-subsequent opening paren, and warn of any discrepancies with the request.
+ perltidy -dmcp somefile.pl >output.txt
+
+will analyze the text of F<somefile.pl>, write the results to F<output.txt>,
+and then immediately exit.
+
+The output shows a list of operators and the number of times they were
+used with parens and the number of times without parens. For example, here
+is a small section of the output from one program:
-Functions which should enclose their arguments in parens are listed first,
-separated by spaces or commas. Then, if there are functions which should not
-have parens, an exclamation mark, B<!>, should be added followed by the list of
-those functions.
+ k:length:17:9
+ k:open:30:9
+ k:pop:3:4
+
+The 'k' indicates a Perl keyword, and the two numbers should the number of
+times it was called with parens and the number of times without parens.
+
+A detailed list list of each occurance of a particular operator use, either
+with or without parens, can be made with the parameter B<--want-call-parens=s>
+described in the next section.
+
+=item B<Use --want-call-parens=s to warn about specific missing or extra call parens>
+
+The parameter B<--want-call-parens=s>, or B<-wcp=s>, can be used to to produce
+a warning message if the parens, or lack of parens, does not match the desired
+style for a particular function. This information can help a programmer to
+maintain a certain style. Note that this is a regular parameter, not a
+dump, so perltidy will look for a discrepancy from the requested style
+while it does its normal formatting. This allows monitoring a file for
+the introduction of an unwanted style. Before using this parameter, it
+may be helpful to first use B<--dump-mixed-call-parens=s>, described in the
+previous section, to get an overview of the existing paren usage in a file.
+
+The string argument B<s> is a list of functions which perltidy should check.
+The function names may builtin keywords or user-defined subs. The names
+must be simple words (matching the regex C<\w+>) without a package prefix
+or sigil. Perltidy scans the text for the listed function names,
+looks for a subsequent opening paren, and warns of any discrepancies with
+the request.
+
+For builtin keywords such as C<if>, which have either a block form or a
+trailing modifier form, the block form will be ignored when the file is
+scanned since parens are mandetory in that case.
+
+In the input string, functions which should enclose their arguments in parens
+are listed first, separated by spaces or commas. Then, if there are functions
+which should not have parens, an exclamation mark, B<!>, should be added
+and followed by the list of those functions. Thus, the symbol B<!> divides
+the string into two parts, the first part being operators which should have
+parens, and the second part being operators which should not have parens.
For example
perltidy -wcp='open close ! print' somefile.pl
+ <--parens--|--no parens-->
means that the builtin functions C<open> and C<close> should have parens around
-their call args but C<print> should not. Spaces are only needed to separate
-words, so this could also be written
+their call args but C<print> should not. Spaces are okay but only needed to
+separate words, so this could also be written
perltidy -wcp='open close!print' somefile.pl
-or
+or even
perltidy -wcp='open,close!print' somefile.pl
-A symbol B<+> may be placed in the string B<s> to end the scope of a preceding
-B<!>. So this example could also be written
+Though not necessary, a symbol B<+> may be placed in the string B<s> to end the
+scope of a preceding B<!>. So this example could also be written
perltidy -wcp='open ! print + close' somefile.pl
means that calls to all user-defined subs in the file being processed
should have their call arguments enclosed in parens.
-When adding or removing parentheses, it is important to pay attention to
+When adding or removing parentheses, it is essential to pay attention to
operator precedence issues.
=item B<Working with MakeMaker, AutoLoader and SelfLoader>
=head1 VERSION
-This man page documents perltidy version 20230912.08
+This man page documents perltidy version 20230912.09
=head1 BUG REPORTS
=head1 COPYRIGHT
-Copyright (c) 2000-2023 by Steve Hancock
+Copyright (c) 2000-2024 by Steve Hancock
=head1 LICENSE
# then the Release version must be bumped, and it is probably past time for
# a release anyway.
- $VERSION = '20230912.08';
+ $VERSION = '20230912.09';
} ## end BEGIN
sub DESTROY {
# line parameters and is expecting formatted output to stdout. Another
# precaution, added elsewhere, is to ignore these in a .perltidyrc
my $num_files = @Arg_files;
- foreach my $opt_name (qw(dump-block-summary dump-unusual-variables)) {
+ foreach my $opt_name (
+ qw(
+ dump-block-summary
+ dump-unusual-variables
+ dump-mixed-call-parens
+ )
+ )
+ {
if ( $rOpts->{$opt_name} && $num_files != 1 ) {
Die(<<EOM);
--$opt_name expects 1 filename in the arg list but saw $num_files filenames
$add_option->( 'dump-defaults', 'ddf', '!' );
$add_option->( 'dump-integer-option-range', 'dior', '!' );
$add_option->( 'dump-long-names', 'dln', '!' );
+ $add_option->( 'dump-mixed-call-parens', 'dmcp', '!' );
$add_option->( 'dump-options', 'dop', '!' );
$add_option->( 'dump-profile', 'dpro', '!' );
$add_option->( 'dump-short-names', 'dsn', '!' );
dump-want-right-space
dump-block-summary
dump-unusual-variables
+ dump-mixed-call-parens
help
stylesheet
version
print {*STDOUT} <<"EOM";
This is perltidy, v$VERSION
-Copyright 2000-2023, Steve Hancock
+Copyright 2000-2024, Steve Hancock
Perltidy is free software and may be copied under the terms of the GNU
General Public License, which is included in the distribution files.
=head1 VERSION
-This man page documents Perl::Tidy version 20230912.08
+This man page documents Perl::Tidy version 20230912.09
=head1 LICENSE
use strict;
use warnings;
use English qw( -no_match_vars );
-our $VERSION = '20230912.08';
+our $VERSION = '20230912.09';
use constant EMPTY_STRING => q{};
use constant SPACE => q{ };
use strict;
use warnings;
use English qw( -no_match_vars );
-our $VERSION = '20230912.08';
+our $VERSION = '20230912.09';
use constant EMPTY_STRING => q{};
package Perl::Tidy::FileWriter;
use strict;
use warnings;
-our $VERSION = '20230912.08';
+our $VERSION = '20230912.09';
use constant DEVEL_MODE => 0;
use constant EMPTY_STRING => q{};
use Carp;
use English qw( -no_match_vars );
use List::Util qw( min max first ); # min, max first are in Perl 5.8
-our $VERSION = '20230912.08';
+our $VERSION = '20230912.09';
# The Tokenizer will be loaded with the Formatter
##use Perl::Tidy::Tokenizer; # for is_keyword()
@is_if_elsif_else_unless_while_until_for_foreach{@q} =
(1) x scalar(@q);
+ # These can either have the BLOCK form or trailing modifier form:
@q = qw(if unless while until for foreach);
@is_if_unless_while_until_for_foreach{@q} =
(1) x scalar(@q);
if ( $rOpts->{'warn-variable-types'}
&& $self->[_logger_object_] );
+ if ( $rOpts->{'dump-mixed-call-parens'} ) {
+ $self->dump_mixed_call_parens();
+ Exit(0);
+ }
+
$self->scan_call_parens();
$self->examine_vertical_tightness_flags();
return;
} ## end sub warn_variable_types
+sub block_seqno_of_paren_seqno {
+
+ my ( $self, $seqno_paren ) = @_;
+
+ # Given:
+ # $seqno_paren = sequence number of the paren following a keyword which
+ # may either introduce a block or be a trailing statement modifier,
+ # such as 'if',
+ # Return:
+ # - the sequence number of the block, if any, or
+ # - nothing
+
+ # if (...) { ...
+ # ^ ^ ^
+ # | | |
+ # | | K_opening_brace => return sequno of this brace
+ # | K_closing_paren
+ # $seqno_paren = seqno of this paren pair
+
+ # FIXME: sub $seqno_brace_after_paren in sub scan_variable_usage can
+ # eventually be simplified to make use of this sub. But we would need to
+ # first add a check for repeated parens.
+
+ return unless $seqno_paren;
+ my $K_closing_paren = $self->[_K_closing_container_]->{$seqno_paren};
+ return unless ($K_closing_paren);
+ my $K_opening_brace = $self->K_next_code($K_closing_paren);
+ return unless ($K_opening_brace);
+ my $rLL = $self->[_rLL_];
+ my $seqno_block = $rLL->[$K_opening_brace]->[_TYPE_SEQUENCE_];
+ return
+ unless ( $seqno_block
+ && $rLL->[$K_opening_brace]->[_TOKEN_] eq '{'
+ && $self->[_rblock_type_of_seqno_]->{$seqno_block} );
+ return $seqno_block;
+}
+
+sub dump_mixed_call_parens {
+ my ($self) = @_;
+
+ # Implent --dump-mixed-call-parens
+
+ my $opt_name = 'dump-mixed-call-parens';
+ return unless $rOpts->{$opt_name};
+
+ my $rLL = $self->[_rLL_];
+ my $K_closing_container = $self->[_K_closing_container_];
+
+ my %skip_keywords;
+ my @q = qw(my our local state
+ and cmp continue do else elsif eq ge gt le lt ne not or xor );
+ @skip_keywords{@q} = (1) x scalar(@q);
+
+ # Types which will be checked:
+ # 'k'=builtin keyword, 'U'=user defined sub, 'w'=unknown bareword
+ my %is_kwU = ( 'k' => 1, 'w' => 1, 'U' => 1 );
+
+ my %call_counts;
+ foreach my $KK ( 0 .. @{$rLL} - 1 ) {
+
+ next unless ( $is_kwU{ $rLL->[$KK]->[_TYPE_] } );
+
+ my $type = $rLL->[$KK]->[_TYPE_];
+ my $token = $rLL->[$KK]->[_TOKEN_];
+ if ( $type eq 'k' && $skip_keywords{$token} ) { next }
+
+ my $Kn = $self->K_next_code($KK);
+ next unless defined($Kn);
+ my $token_Kn = $rLL->[$Kn]->[_TOKEN_];
+ my $have_paren;
+ if ( $token_Kn eq '=>' ) { next }
+ elsif ( $token_Kn eq '->' ) { next }
+ elsif ( $token_Kn eq '(' ) { $have_paren = 1 }
+ else { $have_paren = 0 }
+
+ # return if this is the block form of 'if', 'unless', ..
+ if ( $have_paren
+ && $is_if_unless_while_until_for_foreach{$token} )
+ {
+ my $seqno = $rLL->[$Kn]->[_TYPE_SEQUENCE_];
+ next if ( $self->block_seqno_of_paren_seqno($seqno) );
+ }
+
+ if ( !defined( $call_counts{$token} ) ) {
+ $call_counts{$token} = [ 0, 0, $type ];
+ }
+ $call_counts{$token}->[$have_paren]++;
+ }
+ my @mixed_counts;
+ foreach my $key ( keys %call_counts ) {
+ my ( $no_count, $yes_count, $type ) = @{ $call_counts{$key} };
+ next unless ( $no_count && $yes_count );
+
+ # display type 'U' as 's' on output for clarity and better sorted order
+ my $type_out = $type eq 'U' ? 's' : $type;
+ push @mixed_counts,
+ {
+ name => $key,
+ type => $type_out,
+ no_count => $no_count,
+ yes_count => $yes_count
+ };
+ }
+ return unless (@mixed_counts);
+ my @sorted =
+ sort { $a->{type} cmp $b->{type} || $a->{name} cmp $b->{name} }
+ @mixed_counts;
+
+ my $output_string = <<EOM;
+mixed paren counts for --dump-mixed-call-parens; see also --want-call-parens=s
+types are 'k'=builtin keyword 's'=user sub 'w'=other word
+type:word:+count:-count
+EOM
+ foreach my $item (@sorted) {
+ my $type = $item->{type};
+ my $name = $item->{name};
+ my $no_count = $item->{no_count};
+ my $yes_count = $item->{yes_count};
+ $output_string .= "$type:$name:$yes_count:$no_count\n";
+ }
+ print {*STDOUT} $output_string;
+ return;
+} ## end sub dump_mixed_call_parens
+
sub initialize_call_paren_style {
# parse the flag --want-call-parens=string
# issues a warning so that the user can make a change if desired.
# It is risky to add or delete parens automatically; see git #128.
- my $wcp_name = 'want-call-parens';
- my $rOpts_warn_call_parens = $rOpts->{$wcp_name};
+ my $opt_name = 'want-call-parens';
+ my $rOpts_warn_call_parens = $rOpts->{$opt_name};
return unless ($rOpts_warn_call_parens);
+ # Types which will be checked:
+ # 'k'=builtin keyword, 'U'=user defined sub, 'w'=unknown bareword
+ my %is_kwU = ( 'k' => 1, 'w' => 1, 'U' => 1 );
+
# if no hash, user may have just entered -wcp='!'
return unless (%call_paren_style);
#---------------------
my $rLL = $self->[_rLL_];
foreach my $KK ( 0 .. @{$rLL} - 1 ) {
- my $type = $rLL->[$KK]->[_TYPE_];
- next if ( $type eq 'b' || $type eq '#' );
-
- # 'k'=builtin keyword, 'U'=user defined sub, 'w'=unknown bareword
- if ( $type eq 'k' || $type eq 'U' || $type eq 'w' ) {
-
- # Are we looking for this word?
- my $token = $rLL->[$KK]->[_TOKEN_];
- my $want_paren = $call_paren_style{$token};
-
- # Only user-defined subs (type 'U') have defaults.
- if ( !defined($want_paren) ) {
- $want_paren =
- $type eq 'k' ? undef
- : $type eq 'U' ? $call_paren_style{'&'}
- : undef;
- }
- next unless defined($want_paren);
-
- # This is a selected word. Look for a '(' at the next token.
- my $Kn = $self->K_next_code($KK);
- next unless defined($Kn);
-
- my $token_Kn = $rLL->[$Kn]->[_TOKEN_];
- if ( $token_Kn eq '=>' ) { next }
- elsif ( $token_Kn eq '->' ) { next }
- elsif ( $token_Kn eq '(' ) { next if ($want_paren) }
- else { next if ( !$want_paren ) }
-
- # This disagrees with the wanted style; issue a warning.
- my $note = $want_paren ? "no call parens" : "has call parens";
- my $rwarning = {
- token => $token,
- token_next => $token_Kn,
- want => $want_paren,
- note => $note,
- line_number => $rLL->[$KK]->[_LINE_INDEX_] + 1,
- KK => $KK,
- Kn => $Kn,
- };
- push @{$rwarnings}, $rwarning;
+
+ next unless ( $is_kwU{ $rLL->[$KK]->[_TYPE_] } );
+
+ # Are we looking for this word?
+ my $type = $rLL->[$KK]->[_TYPE_];
+ my $token = $rLL->[$KK]->[_TOKEN_];
+ my $want_paren = $call_paren_style{$token};
+
+ # Only user-defined subs (type 'U') have defaults.
+ if ( !defined($want_paren) ) {
+ $want_paren =
+ $type eq 'k' ? undef
+ : $type eq 'U' ? $call_paren_style{'&'}
+ : undef;
}
+ next unless defined($want_paren);
+
+ # This is a selected word. Look for a '(' at the next token.
+ my $Kn = $self->K_next_code($KK);
+ next unless defined($Kn);
+
+ my $token_Kn = $rLL->[$Kn]->[_TOKEN_];
+ if ( $token_Kn eq '=>' ) { next }
+ elsif ( $token_Kn eq '->' ) { next }
+ elsif ( $token_Kn eq '(' ) { next if ($want_paren) }
+ else { next if ( !$want_paren ) }
+
+ # return if this is the block form of 'if', 'unless', ..
+ if ( $token_Kn eq '('
+ && $is_if_unless_while_until_for_foreach{$token} )
+ {
+ my $seqno = $rLL->[$Kn]->[_TYPE_SEQUENCE_];
+ next if ( $self->block_seqno_of_paren_seqno($seqno) );
+ }
+
+ # This disagrees with the wanted style; issue a warning.
+ my $note = $want_paren ? "no call parens" : "has call parens";
+ my $rwarning = {
+ token => $token,
+ token_next => $token_Kn,
+ want => $want_paren,
+ note => $note,
+ line_number => $rLL->[$KK]->[_LINE_INDEX_] + 1,
+ KK => $KK,
+ Kn => $Kn,
+ };
+ push @{$rwarnings}, $rwarning;
}
# Report any warnings
if ( @{$rwarnings} ) {
- my $message = "Begin scan for --$wcp_name\n";
+ my $message = "Begin scan for --$opt_name\n";
$message .= <<EOM;
Line:text:
EOM
}
$message .= "$lno:$token $token_next: $note\n";
}
- $message .= "End scan for --$wcp_name\n";
+ $message .= "End scan for --$opt_name\n";
# Note that this is sent in a single call to warning() in order
# to avoid triggering a stop on large warning count
package Perl::Tidy::HtmlWriter;
use strict;
use warnings;
-our $VERSION = '20230912.08';
+our $VERSION = '20230912.09';
use Carp;
use English qw( -no_match_vars );
use strict;
use warnings;
use Carp;
-our $VERSION = '20230912.08';
+our $VERSION = '20230912.09';
use constant DEVEL_MODE => 0;
use constant EMPTY_STRING => q{};
use strict;
use warnings;
use Carp;
-our $VERSION = '20230912.08';
+our $VERSION = '20230912.09';
use constant DEVEL_MODE => 0;
use strict;
use warnings;
-our $VERSION = '20230912.08';
+our $VERSION = '20230912.09';
BEGIN {
package Perl::Tidy::Logger;
use strict;
use warnings;
-our $VERSION = '20230912.08';
+our $VERSION = '20230912.09';
use Carp;
use English qw( -no_match_vars );
use warnings;
use English qw( -no_match_vars );
-our $VERSION = '20230912.08';
+our $VERSION = '20230912.09';
use Carp;
{ #<<< A non-indenting brace to contain all lexical variables
-our $VERSION = '20230912.08';
+our $VERSION = '20230912.09';
use English qw( -no_match_vars );
use Perl::Tidy::VerticalAligner::Alignment;
use Perl::Tidy::VerticalAligner::Line;
use strict;
use warnings;
-our $VERSION = '20230912.08';
+our $VERSION = '20230912.09';
sub new {
my ( $class, $rarg ) = @_;
use strict;
use warnings;
-our $VERSION = '20230912.08';
+our $VERSION = '20230912.09';
use English qw( -no_match_vars );
sub AUTOLOAD {