2. Extending Perl Critic Perl Critic is a very powerful and customizable system in its off-the-CPAN state; however, there are times when you need to create your own policies to enforce your own coding standards. We will see how to do that by creating a variant of one of the core modules: BuiltinFunctions::RequireBlockGrep . We'll call our policy BuiltinFunctions::RequireBlockGrepAndMap and make it force block map s in addition to grep s.
3. Extending Perl Critic use strict; use warnings; use Test::More tests => 1; use_ok( 'Perl::Critic::Policy::' . 'BuiltinFunctions::RequireBlockGrepAndMap' ); t/initial-setup.t
4. Extending Perl Critic --(0)> prove -Ilib t/initial-setup.t t/initial-setup.... # Failed test 'use Perl::Critic::Policy::BuiltinFunctions::RequireBlockGrepAndMap;' # at t/initial-setup.t line 5. # Tried to use 'Perl::Critic::Policy::BuiltinFunctions::RequireBlockGrepAndMap'. # Error: Can't locate Perl/Critic/Policy/BuiltinFunctions/RequireBlockGrepAndMap.pm in @INC (...) line 2. # BEGIN failed--compilation aborted at t/initial-setup.t line 5. # Looks like you failed 1 test of 1. t/initial-setup....dubious Test returned status 1 (wstat 256, 0x100) DIED. FAILED test 1 Failed 1/1 tests, 0.00% okay Failed Test Stat Wstat Total Fail List of Failed ------------------------------------------------------------------------------- t/initial-setup.t 1 256 1 1 1 Failed 1/1 test scripts. 1/1 subtests failed. Files=1, Tests=1, 0 wallclock secs ( 0.04 cusr + 0.02 csys = 0.06 CPU) Failed 1/1 test programs. 1/1 subtests failed.
5. Extending Perl Critic package Perl::Critic::Policy::BuiltinFunctions::RequireBlockGrepAndMap ; use warnings; use strict; 1; lib/Perl/Critic/Policy/BuiltinFunctions/RequireBlockGrepAndMap.pm
7. Extending Perl Critic use strict; use warnings; use Test::More tests => 2; use_ok( 'Perl::Critic::Policy::' . 'BuiltinFunctions::RequireBlockGrepAndMap' ); my $policy = Perl::Critic::Policy::BuiltinFunctions::RequireBlockGrepAndMap ->new(); is($policy->get_severity(), 4, 'high severity set'); t/initial-setup.t
8. Extending Perl Critic --(0)> prove -Ilib t/* t/initial-setup....Can't locate object method "new" via package "Perl::Critic::Policy::BuiltinFunctions::RequireBlockGrepAndMap" at t/initial-setup.t line 10. # Looks like you planned 2 tests but only ran 1. # Looks like your test died just after 1. t/initial-setup....dubious Test returned status 255 (wstat 65280, 0xff00) DIED. FAILED test 2 Failed 1/2 tests, 50.00% okay Failed Test Stat Wstat Total Fail List of Failed ------------------------------------------------------------------------------- t/initial-setup.t 255 65280 2 2 2 Failed 1/1 test scripts. 1/2 subtests failed. Files=1, Tests=2, 0 wallclock secs ( 0.04 cusr + 0.02 csys = 0.06 CPU) Failed 1/1 test programs. 1/2 subtests failed.
9. Extending Perl Critic package Perl::Critic::Policy::BuiltinFunctions::RequireBlockGrepAndMap; use warnings; use strict; use base qw(Perl::Critic::Policy); use Perl::Critic::Utils; sub new { my ($class, %args) = @_; return $class->SUPER::new(%args); } sub default_severity { return $SEVERITY_HIGH; } 1; lib/Perl/Critic/Policy/BuiltinFunctions/RequireBlockGrepAndMap.pm
16. Extending Perl Critic ... is_deeply( [$policy->applies_to()], [qw(PPI::Token::Word)], 'applies only to words' ); t/initial-setup.t
17. Extending Perl Critic --(0)> prove -Ilib t/* t/initial-setup....NOK 4 # Failed test 'applies only to words' # at t/initial-setup.t line 18. # Structures begin differing at: # $got->[0] = 'PPI::Element' # $expected->[0] = 'PPI::Token::Word' # Looks like you failed 1 test of 4. t/initial-setup....dubious Test returned status 1 (wstat 256, 0x100) DIED. FAILED test 4 Failed 1/4 tests, 75.00% okay Failed Test Stat Wstat Total Fail List of Failed ------------------------------------------------------------------------------- t/initial-setup.t 1 256 4 1 4 Failed 1/1 test scripts. 1/4 subtests failed. Files=1, Tests=4, 0 wallclock secs ( 0.12 cusr + 0.03 csys = 0.15 CPU) Failed 1/1 test programs. 1/4 subtests failed.
21. Extending Perl Critic use warnings; use strict; use Test::More tests => 1; use Perl::Critic::TestUtils qw(pcritique); my $custom_policy = 'BuiltinFunctions::RequireBlockGrepAndMap'; my $code = 'grep { $_ }'; my $violation_count = pcritique( $custom_policy, code ); ok(!$violation_count, 'allowed block grep'); t/exercise-policy.t
22. Extending Perl Critic Some Perl::Critic::TestUtils Help Methods - block_perlcriticrc - critique - pcritique - fcritique
23. Extending Perl Critic --(0)> prove -Ilib t/* t/exercise-policy....Can't call abstract method at /opt/local/lib/perl5/site_perl/5.8.7/Perl/Critic/Policy.pm line 98 Perl::Critic::Policy::violates('Perl::Critic::Policy::BuiltinFunctions::RequireBlockGrepAndMa...', 'PPI::Token::Word=HASH(0x1a01b40)', 'Perl::Critic::Document=HASH(0x183d338)') called at /opt/local/lib/perl5/site_perl/5.8.7/Perl/Critic.pm line 165 Perl::Critic::_critique('Perl::Critic::Policy::BuiltinFunctions::RequireBlockGrepAndMa...', 'Perl::Critic::Document=HASH(0x183d338)', 'HASH(0x19fc194)') called at /opt/local/lib/perl5/site_perl/5.8.7/Perl/Critic.pm line 121 Perl::Critic::critique('Perl::Critic=HASH(0x1800f88)', 'SCALAR(0x1810eac)') called at /opt/local/lib/perl5/site_perl/5.8.7/Perl/Critic/TestUtils.pm line 53 Perl::Critic::TestUtils::pcritique('BuiltinFunctions::RequireBlockGrepAndMap', 'SCALAR(0x1810eac)') called at t/exercise-policy.t line 9 # Looks like your test died before it could output anything. t/exercise-policy....dubious Test returned status 255 (wstat 65280, 0xff00) DIED. FAILED test 1 Failed 1/1 tests, 0.00% okay t/initial-setup......ok Failed Test Stat Wstat Total Fail List of Failed ------------------------------------------------------------------------------- t/exercise-policy.t 255 65280 1 2 1 Failed 1/2 test scripts. 1/5 subtests failed. Files=2, Tests=5, 1 wallclock secs ( 0.99 cusr + 0.24 csys = 1.23 CPU) Failed 1/2 test programs. 1/5 subtests failed.
24. Extending Perl Critic sub violates {} lib/Perl/Critic/Policy/BuiltinFunctions/RequireBlockGrepAndMap.pm
31. Extending Perl Critic use warnings; use strict; use Test::More; use Perl::Critic::TestUtils qw(subtests_in_tree fcritique pcritique); my $custom_policy = 'BuiltinFunctions::RequireBlockGrepAndMap'; my $subtests = subtests_in_tree('t/BuiltinFunctions'); my $test_count = 0; $test_count += @{$subtests->{$_}} for ( keys %{$subtests} ); plan tests => $test_count; ... t/exercise-policy.t
32. Extending Perl Critic for my $policy ( sort keys %$subtests ) { for my $subtest ( @{ $subtests->{$policy} } ) { local $TODO = $subtest->{TODO}; my $desc = join( ' - ', $policy, "line $subtest->{lineno}", $subtest->{name} ); my $violations = $subtest->{filename} ? eval { fcritique( $policy, subtest->{code}, $subtest->{filename}, $subtest->{parms} ); } : eval { pcritique( $policy, subtest->{code}, $subtest->{parms} ) }; my $err = $@; if ( $subtest->{error} ) { if ( 'Regexp' eq ref $subtest->{error} ) { like( $err, $subtest->{error}, $desc ); } else { ok( $err, $desc ); } } else { die $err if $err; is( $violations, $subtest->{failures}, $desc ); } } } t/exercise-policy.t
35. Extending Perl Critic ## name A Test Name ## parms { allow_y => 0 } ## TODO Should pass later ## error 1 ## error /Can't load Foo::Bar/ ## filename lib/Foo/Bar.pm ## cut Options For .run Files
36. Extending Perl Critic ... ## name grep as a method call ## failures 0 ## cut $object->grep('this'); t/BuiltinFunctions/RequireBlockGrepAndMap.run
37. Extending Perl Critic --(0)> prove -Ilib t/* t/exercise-policy....NOK 3 # Failed test 'BuiltinFunctions::RequireBlockGrepAndMap - line 13 - grep as a method call' # at t/exercise-policy.t line 41. # got: '1' # expected: '0' # Looks like you failed 1 test of 3. t/exercise-policy....dubious Test returned status 1 (wstat 256, 0x100) DIED. FAILED test 3 Failed 1/3 tests, 66.67% okay t/initial-setup......ok Failed Test Stat Wstat Total Fail List of Failed ------------------------------------------------------------------------------- t/exercise-policy.t 1 256 3 1 3 Failed 1/2 test scripts. 1/7 subtests failed. Files=2, Tests=7, 2 wallclock secs ( 1.00 cusr + 0.24 csys = 1.24 CPU) Failed 1/2 test programs. 1/7 subtests failed.
38. Extending Perl Critic use Perl::Critic::Utils; ... sub violates { my ( $self, $element, $document ) = @_; return unless $element eq 'grep'; return if is_method_call($element); my $sibling = $element->snext_sibling(); return if $sibling ->isa('PPI::Structure::Block'); return $self->violation( $description, $explanation, $element ); } lib/Perl/Critic/Policy/BuiltinFunctions/RequireBlockGrepAndMap.pm
41. Extending Perl Critic ... ## name grep as a hash key ## failures 0 ## cut $my_hash{ grep } = 1; t/BuiltinFunctions/RequireBlockGrepAndMap.run
42. Extending Perl Critic --(0)> prove -Ilib t/* t/exercise-policy....ok 1/4Can't call method "isa" without a package or object reference at lib/Perl/Critic/Policy/BuiltinFunctions/RequireBlockGrepAndMap.pm line 36. # Looks like you planned 4 tests but only ran 3. # Looks like your test died just after 3. t/exercise-policy....dubious Test returned status 255 (wstat 65280, 0xff00) DIED. FAILED test 4 Failed 1/4 tests, 75.00% okay t/initial-setup......ok Failed Test Stat Wstat Total Fail List of Failed ------------------------------------------------------------------------------- t/exercise-policy.t 255 65280 4 2 4 Failed 1/2 test scripts. 1/8 subtests failed. Files=2, Tests=8, 1 wallclock secs ( 1.00 cusr + 0.22 csys = 1.22 CPU) Failed 1/2 test programs. 1/8 subtests failed.
43. Extending Perl Critic sub violates { my ( $self, $element, $document ) = @_; return unless $element eq 'grep'; return if is_method_call($element); return if is_hash_key($element); my $sibling = $element->snext_sibling(); return if $sibling->isa('PPI::Structure::Block'); return $self->violation( $description, $explanation, $element ); } lib/Perl/Critic/Policy/BuiltinFunctions/RequireBlockGrepAndMap.pm
47. Extending Perl Critic ... ## name grep is a sub prototype name ## failures 0 ## cut sub grep; t/BuiltinFunctions/RequireBlockGrepAndMap.run
48. Extending Perl Critic --(0)> prove -Ilib t/* t/exercise-policy....NOK 6 # Failed test 'BuiltinFunctions::RequireBlockGrepAndMap - line 31 - grep is a sub prototype name' # at t/exercise-policy.t line 41. # got: '1' # expected: '0' # Looks like you failed 1 test of 6. t/exercise-policy....dubious Test returned status 1 (wstat 256, 0x100) DIED. FAILED test 6 Failed 1/6 tests, 83.33% okay t/initial-setup......ok Failed Test Stat Wstat Total Fail List of Failed ------------------------------------------------------------------------------- t/exercise-policy.t 1 256 6 1 6 Failed 1/2 test scripts. 1/10 subtests failed. Files=2, Tests=10, 1 wallclock secs ( 1.04 cusr + 0.22 csys = 1.26 CPU) Failed 1/2 test programs. 1/10 subtests failed.
49. Extending Perl Critic sub violates { my ( $self, $element, $document ) = @_; return unless $element eq 'grep'; return if is_method_call($element); return if is_hash_key($element); return if is_subroutine_name($element); my $sibling = $element->snext_sibling(); return if $sibling->isa('PPI::Structure::Block'); return $self->violation( $description, $explanation, $element, ); } lib/Perl/Critic/Policy/BuiltinFunctions/RequireBlockGrepAndMap.pm
68. Extending Perl Critic That's it for coding our module. We could of course add some POD. =pod =head1 NAME Perl::Critic::Policy::BuiltinFunctions::RequireBlockGrepAndMap =head1 DESCRIPTION The expression form of C<grep> and C<map> is awkward and hard to read. Use the block forms instead. @matches = grep /pattern/, @list; #not ok @matches = grep { /pattern/ } @list; #ok @mapped = map transform($_), @list; #not ok @mapped = map { transform($_) } @list; #ok =cut
70. Extending Perl Critic - Use Perl::Critic::TestUtils to test your policies - Have your policies subclass Perl::Critic::Policy - Set a default severity for your policies - Set a default tag set for your policies - Configure your policy to apply to specific PPI elements - Create a violates subroutine that does your validation and throws a Perl::Critic::Violation when the policy finds a problem - Use Perl::Critic::Utils to help create your policy - User PPI::Element methods to traverse the PPI DOM In Review