Perl::Critic

Chris Dolan

MediaLandscape Software

cdolan@cpan.org

Andy Lester

Perl Foundation Public Relations

petdance@cpan.org

Perl Best Practices

→ Use code to optimize for you!

What is Perl::Critic?

Static source code analyzer

by Jeffrey Ryan Thalhammer and crew

Bad code

open F, "$ARGV[0]" or die;
$_ = join("",map chomp, <F>);
s/<.*?>/ /g;
print

What it does:

it has a serious bug

Better code

use 5.006;
use warnings;
use strict;
use File::Slurp;
our $VERSION = 0.10;

my $htmlfile = shift
    or die 'Please specify an HTML file';

my $html = read_file($htmlfile);
$html =~ s/ \n //gxms;
$html =~ s/ <.*?>  # Match any html tag
          / /gxms; # replace with a space
print $html;

Policies

Installation

% cpan install Perl::Critic

Relies on PPI, the pure-perl Perl parser.

Usage

% perlcritic PerfectCode.pm
# OK

% perlcritic LegacyCode.pm
Code before strictures are enabled at line 3, column 1. See page 429 of PBP. (Severity: 5)
Expression form of 'eval' at line 4, column 4. See page 161 of PBP. (Severity: 5)

Usage

Lenient (the default)
% perlcritic -5 Foo.pm

Typical
% perlcritic -3 Foo.pm

Pedantic
% perlcritic -1 Foo.pm

Examples:
RequireBarewordIncludes
require "lib/Foo.pm"; vs. require Foo;
ProhibitExplicitISA
@ISA = qw(Foo); vs. use base 'Foo';
ProhibitParensWithBuiltins
print("foo\n"); vs. print "foo\n";

Configuration

Create a ~/.perlcriticrc file

[-CodeLayout::RequireTidyCode]
[-ControlStructures::ProhibitCStyleForLoops]
[-Documentation::RequirePodAtEnd]
[-Documentation::RequirePodSections]
[-Miscellanea::RequireRcsKeywords]
[-NamingConventions::ProhibitMixedCaseSubs]

[ControlStructures::ProhibitCascadingIfElse]
max_elsif = 3

Making Exceptions

Perl::Critic has flags to ignore special cases

Learn more

Policy Creation: Choose Goal

Pick a useful goal: detect calls to undeclared functions.

sub main {
   output_strnig('Hello, World!');
}

sub output_string {
   my ($msg) = @_;
   print $msg, "\n";
}

Caveats...

Policy Creation: Write test

Create a file: t/Subroutines/ProhibitUndeclaredSub.run

## name sub exists
## failures 0
foo();
sub foo { }

## name sub missing
## failures 1
bar();

Policy Creation: Look at PPI DOM

Run: ppidump 'bar();'

PPI::Document
  PPI::Statement
    PPI::Token::Word    'bar'
    PPI::Structure::List        ( ... )
    PPI::Token::Structure       ';'

Policy Creation: Look at PPI DOM

Run: ppidump 'sub foo { print; }'

PPI::Document
  PPI::Statement::Sub
    PPI::Token::Word    'sub'
    PPI::Token::Word    'foo'
    PPI::Structure::Block       { ... }
      PPI::Statement
        PPI::Token::Word        'print'
        PPI::Token::Structure   ';'

Policy Creation: Start the policy

Create file: lib/Perl/Critic/Policy/   Subroutines/ProhibitUndeclaredSub.pm

package Perl::Critic::Policy::
  Subroutines::ProhibitUndeclaredSub;

use warnings;
use strict;
use Perl::Critic::Utils;
use base 'Perl::Critic::Policy';

sub applies_to {
   return 'PPI::Token::Word';
}

Policy Creation: Heart of the policy

sub violates {
   my ( $self, $elem, $doc ) = @_;

   return if !is_function_call($elem);

   my $subs = $doc->find('PPI::Statement::Sub');
   $subs ||= [];
   for my $sub ( @{ $subs } ) {
      return if $sub->name eq $elem;
   }

   return $self->violation($desc, $expl, $elem);
}

Policy Creation: Run tests

% t/20_policies.t Subroutines::ProhibitUndeclaredSub
1..3
ok 1 - Perl::Critic::Policy::Subroutines::
       ProhibitUndeclaredSub->can('violates')
ok 2 - Subroutines::ProhibitUndeclaredSub - 
       line 1 - sub exists
ok 3 - Subroutines::ProhibitUndeclaredSub - 
       line 7 - sub missing

Success!

Conclusion

Perl::Critic is: