Quick Start

To use perl_checker, simply use "perl_checker a_file.pl"

To use under emacs, simply add the following line to your .emacs, then when you visit a perl file, you can use Ctrl-Return to run perl_checker on this file

  (global-set-key [(control return)] (lambda () (interactive) (save-some-buffers 1) (compile (concat "perl_checker --restrict-to-files " (buffer-file-name (current-buffer))))))

To use with vim, use something like:

  perl_checker --restrict-to-files scanner.pm > errors.err ; vim -c ':copen 4' -c ':so /usr/share/vim/ftplugin/perl_checker.vim' -q
where /usr/share/vim/ftplugin/perl_checker.vim is
" Error formats
setlocal efm=
  \%EFile\ \"%f\"\\,\ line\ %l\\,\ characters\ %c-%*\\d:,
  \%EFile\ \"%f\"\\,\ line\ %l\\,\ character\ %c:%m,
  \%+EReference\ to\ unbound\ regexp\ name\ %m,
  \%Eocamlyacc:\ e\ -\ line\ %l\ of\ \"%f\"\\,\ %m,
  \%Wocamlyacc:\ w\ -\ %m,
  \%-Zmake%.%#,
  \%C%m

Goals of perl_checker

Compared to PPI and Perl-Critic

Get it

tarball
SVN source
MDK::Common tarball

Implemented features

detect some Perl traps
some Perl expressions are stupid, and one gets a warning when running them with perl -w. The drawback of perl -w is the lack of code coverage, it only detects expressions which are evaluated. (examples)
context checks
Perl has types associated with variables names, the so-called "context". Some expressions mixing contexts are stupid, perl_checker detects them. (examples)
suggest simpler expressions
when there is a simpler way to write an expression, suggest it. It can also help detecting errors. (examples)
function call check
detection of unknown functions or mismatching prototypes (warning: since perl is a dynamic language, some spurious warnings may occur when a function is defined using stashes). (examples)
method call check
detection of unknown methods or mismatching prototypes. perl_checker doesn't have any idea what the object type is, it simply checks if a method with that name and that number of parameters exists. (examples)
return value check
dropping the result of a functionnally pure function is stupid. using the result of a function returning void is stupid too.
(nb: perl_checker enforces && and || are used as boolean operators whereas and and or are used for control flow) (examples)
white space normalization
enforce a similar coding style. In many languages you can find a coding style document (eg: the GNU one). (examples)
disallow complex expressions
perl_checker try to ban some weird-not-used-a-lot features. (examples)

Examples

detect some Perl traps
some Perl expressions are stupid, and one gets a warning when running them with perl -w. The drawback of perl -w is the lack of code coverage, it only detects expressions which are evaluated.

local $xxx ||= $yyy applying ||= on a new initialized variable is wrong
xxx(!my $xxx) applying not on a new initialized variable is wrong
$1 =~ s/xxx/yyy/ do not modify the result of a match (eg: $1)
$xxx[1, 2] you must give only one argument
$xxx[] you must give one argument
my $_x = 'xxx' if $xxx; replace "my $foo = ... if <cond>" with "my $foo = <cond> && ..."
$xxx or my $_x = 'xxx'; replace "<cond> or my $foo = ..." with "my $foo = !<cond> && ..."
'' || 'xxx' <constant> || ... is the same as ...
if ($xxx = '') {} are you sure you did not mean "==" instead of "="?
N("xxx$yyy") don't use interpolated translated string, use %s or %d instead
if ($xxx && $yyy = xxx()) {} invalid lvalue
1 + 2 >> 3 missing parentheses (needed for clarity)
$xxx ? $yyy = 1 : $zzz = 2;
 
missing parentheses (needed for clarity)
invalid lvalue
N_("xxx") . 'yyy' N_("xxx") . "yyy" is dumb since the string "xxx" will never get translated
join(@l) first argument of join() must be a scalar
join(',', 'foo') join('...', $foo) is the same as $foo
if_($xxx) not enough parameters
push @l you must give some arguments to push
push $xxx, 1 push is expecting an array
pop $xxx pop is expecting an array and nothing else
my (@l2, $xxx) = @l; @l2 takes all the arguments, $xxx is undef in any case
$bad undeclared variable $bad
{ my $a } unused variable $a
my $xxx; yyy($xxx); my $xxx; redeclared variable $xxx
{ my $xxx; $xxx = 1 } variable $xxx assigned, but not read
$a undeclared variable $a
use bad; can't find package bad
use pkg3 ':bad';
bad();
package pkg3 doesn't export tag :bad
unknown function bad
use pkg3 ':missing_fs';
f();
name &f is not defined in package pkg3
name &f0 is not defined in package pkg3
use pkg3 'f';
f();
name &f is not defined in package pkg3

context checks
Perl has types associated with variables names, the so-called "context". Some expressions mixing contexts are stupid, perl_checker detects them.

foreach (%h) {}
 
context hash is not compatible with context list
foreach with a hash is usually an error
map { 'xxx' } %h a hash is not a valid parameter to function map
$xxx = ('yyy', 'zzz') context tuple(string, string) is not compatible with context scalar
@l ||= 'xxx' "||=" is only useful with a scalar
length @l never use "length @l", it returns the length of the string int(@l)
%h . 'yyy' context hash is not compatible with context string
'xxx' > 'yyy'
 
 
context string is not compatible with context float
context string is not compatible with context float
1 cmp 2 you should use a number operator, not the string operator "cmp" (or replace the number with a string)
$xxx == undef context undef is not compatible with context float
my ($xxx) = 1 context int is not compatible with context tuple(scalar)
($xxx, $yyy) = 1 context int is not compatible with context tuple(scalar, scalar)
($xxx, $yyy) = (1, 2, 3) context tuple(int, int, int) is not compatible with context tuple(scalar, scalar)
@l eq '3' context array is not compatible with context string
qw(a b) > 2 context tuple(string, string) is not compatible with context float
%h > 0 context hash is not compatible with context float
%h eq 0
 
context hash is not compatible with context string
you should use a number operator, not the string operator "eq" (or replace the number with a string)

suggest simpler expressions
when there is a simpler way to write an expression, suggest it. It can also help detecting errors.

@{$xxx} @{$xxx} can be written @$xxx
$h{"yyy"} {"yyy"} can be written {yyy}
"$xxx" $xxx is better written without the double quotes
$xxx->{yyy}->{zzz} the arrow "->" is unneeded
"xxx\$xxx" you can replace "xxx\$xxx" with 'xxx$xxx', that way you don't need to escape <$>
"xxx\$xxx" you can replace "xxx\$xxx" with 'xxx$xxx', that way you don't need to escape <$>
"xxx\"$xxx" you can replace "xxx\"xxx" with qq(xxx"xxx), that way you don't need to escape <">
"xxx\"xxx'" you can replace "xxx\"xxx" with qq(xxx"xxx), that way you don't need to escape <">
/xxx\'xxx/ you can replace \' with '
/xxx\;xxx/ you can replace \; with ;
/\// change the delimit character / to get rid of this escape
{ nop(); } spurious ";" before closing block
+1 don't use unary +
return ($xxx) unneeded parentheses
if (($xxx eq $yyy) || $zzz) {} unneeded parentheses
if (($xxx =~ /yyy/) || $zzz) {} unneeded parentheses
nop() foreach ($xxx, $yyy); unneeded parentheses
($xxx) ||= 'xxx' remove the parentheses
$o->m0() remove these unneeded parentheses
$o = xxx() if !$o; "$foo = ... if !$foo" can be written "$foo ||= ..."
$o = xxx() unless $o; "$foo = ... unless $foo" can be written "$foo ||= ..."
$o or $o = xxx(); "$foo or $foo = ..." can be written "$foo ||= ..."
$_ =~ s/xxx/yyy/ "$_ =~ s/regexp/.../" can be written "s/regexp/.../"
$xxx =~ /^yyy$/ "... =~ /^yyy$/" is better written "... eq 'yyy'"
/xxx.*/ you can remove ".*" at the end of your regexp
/xxx.*$/ you can remove ".*$" at the end of your regexp
/[^\s]/ you can replace [^\s] with \S
/[^\w]/ you can replace [^\w] with \W
$xxx ? $xxx : $yyy you can replace "$foo ? $foo : $bar" with "$foo || $bar"
my @l = (); no need to initialize variables, it's done by default
$l[$#l] you can replace $#l with -1
$#l == 0 $#x == 0 is better written @x == 1
$#l == -1 $#x == -1 is better written @x == 0
$#l < 0 change your expression to use @xxx instead of $#xxx
$l[@l] = 1 "$a[@a] = ..." is better written "push @a, ..."
xxx(@_) replace xxx(@_) with &xxx
member($xxx, keys %h) you can replace "member($xxx, keys %yyy)" with "exists $yyy{$xxx}"
!($xxx =~ /.../) !($var =~ /.../) is better written $var !~ /.../
!($xxx == 1) !($foo == $bar) is better written $foo != $bar
!($xxx eq 'foo') !($foo eq $bar) is better written $foo ne $bar
grep { !member($_, qw(a b c)) } @l you can replace "grep { !member($_, ...) } @l" with "difference2([ @l ], [ ... ])"
any { $_ eq 'foo' } @l you can replace "any { $_ eq ... } @l" with "member(..., @l)"
foreach (@l) {
  push @l2, $_ if yyy($_);
}
use "push @l2, grep { ... } ..." instead of "foreach (...) { push @l2, $_ if ... }"
  or sometimes "@l2 = grep { ... } ..."
foreach (@l) {
    push @l2, yyy($_);
}
use "push @l2, map { ... } ..." instead of "foreach (...) { push @l2, ... }"
  or sometimes "@l2 = map { ... } ..."
foreach (@l) {
  push @l2, yyy($_) if zzz($_);
}
use "push @l2, map { ... ? ... : () } ..." instead of "foreach (...) { push @l2, ... if ... }"
  or sometimes "@l2 = map { ... ? ... : () } ..."
  or sometimes "@l2 = map { if_(..., ...) } ..."
foreach (@l) {
  if (xxx($_)) {
    $xxx = $_;
    last;
  }
}
use "$xxx = find { ... } ..."




if (grep { xxx() } @l) {} in boolean context, use "any" instead of "grep"
$xxx = grep { xxx() } @l; you may use "find" instead of "grep"
$xxx ? $yyy : ()
 
 
you may use if_() here
  beware that the short-circuit semantic of ?: is not kept
  if you want to keep the short-circuit behaviour, replace () with @{[]} and there will be no warning anymore
system(qq(foo "$xxx")) instead of quoting parameters you should give a list of arguments
system("mkdir", $xxx) you can replace system("mkdir ...") with mkdir(...)

function call check
detection of unknown functions or mismatching prototypes (warning: since perl is a dynamic language, some spurious warnings may occur when a function is defined using stashes).

sub xxx { 'yyy' }
 
if the function doesn't take any parameters, please use the empty prototype.
example "sub foo() { ... }"
sub xxx {
  my ($o_xxx, $yyy) = @_;
  ($o_xxx, $yyy);
}
an non-optional argument must not follow an optional argument


sub xxx {
  my (@xxx, $yyy) = @_;
  @xxx, $yyy;
}
an array must be the last variable in a prototype


bad() unknown function bad
sub f0() {}
f0('yyy')
too many parameters
sub f2 { my ($x, $_y) = @_; $x }
f2('yyy')
not enough parameters
N("xxx %s yyy") not enough parameters

method call check
detection of unknown methods or mismatching prototypes. perl_checker doesn't have any idea what the object type is, it simply checks if a method with that name and that number of parameters exists.

bad->yyy unknown package bad
pkg->bad unknown method bad starting in package pkg
$xxx->bad unknown method bad
$xxx->m1 not enough parameters
$xxx->m0('zzz') too many parameters
$xxx->m0_or_2('zzz') not enough or too many parameters

return value check
dropping the result of a functionnally pure function is stupid. using the result of a function returning void is stupid too.
(nb: perl_checker enforces && and || are used as boolean operators whereas and and or are used for control flow)

die; xxx(); unreachable code
exit 1; xxx(); unreachable code
if ($xxx or $yyy) {}
 
value should be dropped
context () is not compatible with context bool
if ($xxx and $yyy) {}
 
value should be dropped
context () is not compatible with context bool
$xxx && yyy(); value is dropped
`xxx`; value is dropped
/(.*)/; value is dropped
'xxx'; value is dropped
'xxx' if $xxx; value is dropped
map { xxx($_) } @l; if you don't use the return value, use "foreach" instead of "map"
$xxx = chomp;
 
() context not accepted here
context () is not compatible with context scalar
$xxx = push @l, 1
 
() context not accepted here
context () is not compatible with context scalar

white space normalization
enforce a similar coding style. In many languages you can find a coding style document (eg: the GNU one).

sub xxx
{}
you should not have a carriage-return (\n) here
xxx
   ($xxx);
you should not have a carriage-return (\n) here
xxx( $xxx) you should not have a space here
$xxx ++ you should not have a space here
my($_xxx, $_yyy) you should have a space here
xxx ($xxx) you should not have a space here
'foo'.'bar' you should have a space here
if ($xxx) {
   xxx()
}
missing ";"

if ($xxx) {
   xxx();
};
unneeded ";"

disallow complex expressions
perl_checker try to ban some weird-not-used-a-lot features.

$xxx <<= 2 don't use "<<=", use the expanded version instead
m@xxx@ don't use m@...@, replace @ with / ! , or |
s:xxx:yyy: don't use s:...:, replace : with / ! , or |
qw/a b c/ don't use qw/.../, use qw(...) instead
qw{a b c} don't use qw{...}, use qw(...) instead
q{xxx} don't use q{...}, use q(...) instead
qq{xxx} don't use qq{...}, use qq(...) instead
qx(xxx) don't use qx(...), use `...` instead
-xxx don't use -xxx, use '-xxx' instead
not $xxx don't use "not", use "!" instead
$1 =~ /xxx/ do not use the result of a match (eg: $1) to match another pattern
$xxx =~ "yyy" use a regexp, not a string
xxx() =~ s/xxx/yyy/ you can only use s/// on a variable
$1 =~ /xxx/ do not use the result of a match (eg: $1) to match another pattern
grep /xxx/, @l always use "grep" with a block (eg: grep { ... } @list)
for (@l) {} write "foreach" instead of "for"
foreach ($xxx = 0; $xxx < 9; $xxx++) {} write "for" instead of "foreach"
foreach $xxx (@l) {} don't use for without "my"ing the iteration variable
foreach ($xxx) {} you are using the special trick to locally set $_ with a value, for this please use "for" instead of "foreach"
unless ($xxx) {} else {} don't use "else" with "unless" (replace "unless" with "if")
unless ($xxx) {} elsif ($yyy) {} don't use "elsif" with "unless" (replace "unless" with "if")
zzz() unless $xxx || $yyy; don't use "unless" when the condition is complex, use "if" instead
$$xxx{yyy} for complex dereferencing, use "->"
wantarray please use wantarray() instead of wantarray
eval please use "eval $_" instead of "eval"
local *F; open F, "foo"; use a scalar instead of a bareword (eg: occurrences of F with $F)
$xxx !~ s/xxx/yyy/ use =~ instead of !~ and negate the return value
pkg::nop $xxx; use parentheses around argument (otherwise it might cause syntax errors if the package is "require"d and not "use"d
new foo $xxx you must parenthesize parameters: "new Class(...)" instead of "new Class ..."
*xxx = *yyy "*xxx = *yyy" is better written "*xxx = \&yyy"
$_xxx = 1
 
variable $_xxx must not be used
  (variable with name _XXX are reserved for unused variables)
sub f2 { my ($x, $_y) = @_; $x }
f2(@l); # ok
f2(xxx()); # bad
not enough parameters

$xxx = <<"EOF";
foo
EOF
Don't use <<"MARK", use <<MARK instead

Todo

Functionalities that would be nice: