]> git.donarmstrong.com Git - perltidy.git/commitdiff
add option --want-call-parens=s, see git #128
authorSteve Hancock <perltidy@users.sourceforge.net>
Sat, 30 Dec 2023 22:12:30 +0000 (14:12 -0800)
committerSteve Hancock <perltidy@users.sourceforge.net>
Sat, 30 Dec 2023 22:12:30 +0000 (14:12 -0800)
bin/perltidy
dev-bin/perltidy_random_setup.pl
lib/Perl/Tidy.pm
lib/Perl/Tidy/Formatter.pm

index b04dccc689b0fdb271b6464c6cbbbb4be8db5f6b..aae89b24f280d0a8a1ddf5faacdee9cebf521977 100755 (executable)
@@ -5602,6 +5602,57 @@ can be used to skip warning checks for a list of variables.  For example,
 
 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>
+
+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 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. 
+
+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.
+
+For example
+
+   perltidy -wcp='open close ! print' somefile.pl
+
+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
+
+   perltidy -wcp='open close!print' somefile.pl
+
+or
+
+   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
+
+   perltidy -wcp='open ! print + close' somefile.pl
+
+If the symbol B<&> is entered instead of a function name, it means all user defined subs not in the list.  So for example
+
+   perltidy -wcp='&'
+
+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
+operator precedence issues.
+
 =item B<Working with MakeMaker, AutoLoader and SelfLoader>
 
 The first $VERSION line of a file which might be eval'd by MakeMaker
index ab104d491ac7aadb0c4a2efbe9530c656d721d24..12c06c4730c782b781340d89375793cc3db1aa39 100755 (executable)
@@ -902,6 +902,8 @@ EOM
             'space-signature-paren' => [ 0, 2 ],
             'break-after-labels'    => [ 0, 2 ],
 
+            'want-call-parens' => [ '&', 'open!close' ],
+
             'want-trailing-commas' => [ '0', '*', 'm', 'b', 'h', 'i', ' ' ],
             'one-line-block-exclusion-list' =>
               [ 'sort', 'map', 'grep', 'eval', '*', 'zzyzx' ],
@@ -1084,7 +1086,7 @@ EOM
                         if ( $count > 10 ) { $count = 10 }
                     }
                     foreach my $i ( 1 .. $count ) {
-                        my $index = int( rand($imax) + 0.5 );
+                        my $index = $imax > 0 ? int( rand($imax) + 0.5 ) : 0;
                         if ( $i > 1 ) { $string .= ' ' }
                         $string .= $rrange->[$index];
                     }
index 4125ac70b0be8142e3cf86e5329a88217aa53570..d891f34ce61d2486d1aa1985f4ae1f4ceb3b7450 100644 (file)
@@ -3,7 +3,7 @@
 #
 #    perltidy - a perl script indenter and formatter
 #
-#    Copyright (c) 2000-2023 by Steve Hancock
+#    Copyright (c) 2000-2024 by Steve Hancock
 #    Distributed under the GPL license agreement; see file COPYING
 #
 #    This program is free software; you can redistribute it and/or modify
@@ -2104,8 +2104,9 @@ sub process_all_files {
             if ( $in_place_modify && !-w $input_file ) {
                 my $backup_method = $rOpts->{'backup-method'};
                 if ( defined($backup_method) && $backup_method eq 'copy' ) {
-                    Warn
-"skipping file '$input_file' for -b option: file reported as non-writable\n";
+                    Warn(
+"skipping file '$input_file' for -b option: file reported as non-writable\n"
+                    );
                     next;
                 }
             }
@@ -3704,6 +3705,7 @@ sub generate_options {
     $add_option->( 'pass-version-line',            'pvl',  '!' );
     $add_option->( 'warn-variable-types',          'wvt',  '=s' );
     $add_option->( 'warn-variable-exclusion-list', 'wvxl', '=s' );
+    $add_option->( 'want-call-parens',             'wcp',  '=s' );
 
     ########################################
     $category = 13;    # Debugging
index 5da85f1fa7dfc8998fce1183c00f50d7cccacb8a..02ccc76b0c1b800706eb4913707f2331ddd9e5f3 100644 (file)
@@ -376,6 +376,9 @@ my (
     # INITIALIZER: sub initialize_trailing_comma_rules
     %trailing_comma_rules,
 
+    # INITIALIZER: sub initialize_call_paren_style
+    %call_paren_style,
+
     # regex patterns for text identification.
     # Most can be configured by user parameters.
     # Most are initialized in a sub make_**_pattern during configuration.
@@ -1427,6 +1430,8 @@ sub check_options {
 
     initialize_missing_else_comment();
 
+    initialize_call_paren_style();
+
     make_bli_pattern();
 
     make_bl_pattern();
@@ -6425,6 +6430,8 @@ EOM
       if ( $rOpts->{'warn-variable-types'}
         && $self->[_logger_object_] );
 
+    $self->scan_call_parens();
+
     $self->examine_vertical_tightness_flags();
 
     $self->set_excluded_lp_containers();
@@ -9695,6 +9702,179 @@ EOM
     return;
 } ## end sub warn_variable_types
 
+sub initialize_call_paren_style {
+
+    # parse the flag --want-call-parens=string
+
+    # Rules:
+    # - we scan from left to right, so if there are multiple entries for
+    #   a word, the last entry is used
+    # - starting mode is WANT PARENS
+    # - '!' switches to DO NOT WANT PARENS mode
+    # - '+' resets to WANT PARENS mode (if needed for complex lists)
+    # - '&' is default for user-defined subs
+    # - '*' is reserved as possible FUTURE default for SELECTED keywords
+    # - ',' same as space
+
+    # To create an input string:
+    # - first list all words which SHOULD have parens
+    # - then, if some do not get parens, add a '!'
+    # - then list all words which SHOULD NOT have parens;
+    # - Enter '&' instead of a word to indicate a default for sub calls
+
+    # Examples:
+    # wcp='&'            - means all sub calls should have parens
+    # wcp='open close'   - 'open' and 'close' have parens
+    # wcp='! open close' - 'open' and 'close' do NOT have parens
+    # wcp='& ! myfun '   - all subs have perens except 'myfun'
+
+    %call_paren_style = ();
+    my $opt = 'want-call-parens';
+    my $str = $rOpts->{$opt};
+    return unless $str;
+
+    my $want_parens = 1;
+    my $err_msg;
+    while (
+        $str =~ m{
+             \G(
+               (\s+) #     whitespace - this must come before \W
+             | (\W)  #  or single-character, non-whitespace punct
+             | (\d+) #  or sequence of digits - must come before \w
+             | (\w+) #  or words not starting with a digit
+             )
+            }gcx
+      )
+    {
+        # skip blanks
+        if ( defined($2) ) { next }
+
+        if ( defined($3) ) {
+            if    ( $3 eq '!' ) { $want_parens          = 0; }
+            elsif ( $3 eq '+' ) { $want_parens          = 1; }
+            elsif ( $3 eq '*' ) { $call_paren_style{$3} = $want_parens }
+            elsif ( $3 eq '&' ) { $call_paren_style{$3} = $want_parens }
+            elsif ( $3 eq ',' ) { next }
+            else                { $err_msg = "Unexpected symbol '$3'"; last }
+        }
+        elsif ( defined($4) ) {
+            $err_msg = "Unexpected digit '$4'";
+            last;
+        }
+        elsif ( defined($5) ) {
+            if ( defined( $call_paren_style{$5} ) ) {
+                Warn("--$opt has multiple entries for '$5', last is used\n");
+            }
+            $call_paren_style{$5} = $want_parens;
+        }
+        else {
+            ## should never get here
+            $err_msg = "Unexpected token '$1'";
+            last;
+        }
+    }
+    if ($err_msg) {
+        Perl::Tidy::Die("Error parsing --$opt: $err_msg\n");
+    }
+    return;
+} ## end sub initialize_call_paren_style
+
+sub scan_call_parens {
+    my ($self) = @_;
+
+    # Perform a scan requesed by --want-call-parens
+    # We search for selected functions or keywords and for a following paren.
+    # A warning is issued if the paren existence is not what is wanted
+    # according to the setting --want-call-parens.
+
+    # This routine does not attempt to add or remove parens, it merely
+    # 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};
+    return unless ($rOpts_warn_call_parens);
+
+    # if no hash, user may have just entered -wcp='!'
+    return unless (%call_paren_style);
+
+    my $rwarnings = [];
+
+    #---------------------
+    # Loop over all tokens
+    #---------------------
+    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 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;
+        }
+    }
+
+    # Report any warnings
+    if ( @{$rwarnings} ) {
+        my $message = "Begin scan for --$wcp_name\n";
+        $message .= <<EOM;
+Line:text:
+EOM
+        foreach my $item ( @{$rwarnings} ) {
+            my $token      = $item->{token};
+            my $token_next = $item->{token_next};
+            my $note       = $item->{note};
+            my $lno        = $item->{line_number};
+
+            # trim long tokens for the output line
+            if ( length($token_next) > 23 ) {
+                $token_next = substr( $token_next, 0, 20 ) . '...';
+            }
+            $message .= "$lno:$token $token_next: $note\n";
+        }
+        $message .= "End scan for --$wcp_name\n";
+
+        # Note that this is sent in a single call to warning() in order
+        # to avoid triggering a stop on large warning count
+        warning($message);
+    }
+    return;
+} ## end sub scan_call_parens
+
 sub find_non_indenting_braces {
 
     my ( $self, $rix_side_comments ) = @_;