YAPC::NA 2007 - Customizing And Extending Perl Critic

download YAPC::NA 2007 - Customizing And Extending Perl Critic

If you can't read please download the document

Transcript of YAPC::NA 2007 - Customizing And Extending Perl Critic

PowerPoint Presentation

Customizing and Extending Perl Critic

YAPC::NA 2007

Houston, TX

Josh McAdams

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 maps in addition to greps.

Extending Perl Critic

use strict;use warnings;use Test::More tests => 1;

use_ok( 'Perl::Critic::Policy::' . 'BuiltinFunctions::RequireBlockGrepAndMap');

t/initial-setup.t

Extending Perl Critic

--(0)> prove -Ilib t/initial-setup.tt/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% okayFailed Test Stat Wstat Total Fail List of Failed-------------------------------------------------------------------------------t/initial-setup.t 1 256 1 1 1Failed 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.

Extending Perl Critic

package Perl::Critic::Policy::BuiltinFunctions::RequireBlockGrepAndMap;

use warnings;use strict;

1;

lib/Perl/Critic/Policy/BuiltinFunctions/RequireBlockGrepAndMap.pm

Extending Perl Critic

--(0)> prove -Ilib t/*t/initial-setup....ok All tests successful.

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

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% okayFailed Test Stat Wstat Total Fail List of Failed-------------------------------------------------------------------------------t/initial-setup.t 255 65280 2 2 2Failed 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.

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

Extending Perl Critic

--(0)> prove -Ilib t/*t/initial-setup....ok All tests successful.Files=1, Tests=2, 0 wallclock secs ( 0.12 cusr + 0.03 csys = 0.15 CPU)

Extending Perl Critic

Severity Level Variables Exported By Perl::Critic::Utils

$SEVERITY_HIGHEST == 5$SEVERITY_HIGH == 4$SEVERITY_MEDIUM == 3$SEVERITY_LOW == 2$SEVERITY_LOWEST == 1

Extending Perl Critic

...

is_deeply( [$policy->get_themes()], [qw(almost_core pbp)], 'proper themes set');

t/initial-setup.t

Extending Perl Critic

--(0)> prove -Ilib t/*t/initial-setup....NOK 3 # Failed test 'proper themes set'# at t/initial-setup.t line 16.# Structures begin differing at:# $got->[0] = Does not exist# $expected->[0] = 'almost_core'# Looks like you failed 1 test of 3.t/initial-setup....dubious Test returned status 1 (wstat 256, 0x100)DIED. FAILED test 3 Failed 1/3 tests, 66.67% okayFailed Test Stat Wstat Total Fail List of Failed-------------------------------------------------------------------------------t/initial-setup.t 1 256 3 1 3Failed 1/1 test scripts. 1/3 subtests failed.Files=1, Tests=3, 0 wallclock secs ( 0.12 cusr + 0.03 csys = 0.15 CPU)Failed 1/1 test programs. 1/3 subtests failed.

Extending Perl Critic

sub default_themes { return qw( almost_core pbp ); }

lib/Perl/Critic/Policy/BuiltinFunctions/RequireBlockGrepAndMap.pm

Extending Perl Critic

--(0)> prove -Ilib t/*t/initial-setup....ok All tests successful.Files=1, Tests=3, 0 wallclock secs ( 0.12 cusr + 0.03 csys = 0.15 CPU)

Extending Perl Critic

...

is_deeply( [$policy->applies_to()], [qw(PPI::Token::Word)], 'applies only to words');

t/initial-setup.t

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% okayFailed Test Stat Wstat Total Fail List of Failed-------------------------------------------------------------------------------t/initial-setup.t 1 256 4 1 4Failed 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.

Extending Perl Critic

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

lib/Perl/Critic/Policy/BuiltinFunctions/RequireBlockGrepAndMap.pm

Extending Perl Critic

--(0)> prove -Ilib t/*t/initial-setup....ok All tests successful.Files=1, Tests=4, 0 wallclock secs ( 0.12 cusr + 0.03 csys = 0.15 CPU)

Extending Perl Critic

PPI::DocumentPPI::Document::FilePPI::Document::FragmentPPI::Document::NormalizedPPI::ElementPPI::StatementPPI::Statement::BreakPPI::Statement::CompoundPPI::Statement::DataPPI::Statement::EndPPI::Statement::ExpressionPPI::Statement::IncludePPI::Statement::NullPPI::Statement::PackagePPI::Statement::ScheduledPPI::Statement::SubPPI::Statement::UnknownPPI::Statement::UnmatchedBracePPI::Statement::VariablePPI::StructurePPI::Structure::BlockPPI::Structure::ConditionPPI::Structure::Constructor

PPI::Token::Quote::SinglePPI::Token::QuoteLikePPI::Token::QuoteLike::BacktickPPI::Token::QuoteLike::CommandPPI::Token::QuoteLike::ReadlinePPI::Token::QuoteLike::RegexpPPI::Token::QuoteLike::WordsPPI::Token::RegexpPPI::Token::Regexp::MatchPPI::Token::Regexp::SubstitutePPI::Token::Regexp::TransliteratePPI::Token::SeparatorPPI::Token::StructurePPI::Token::SymbolPPI::Token::UnknownPPI::Token::WhitespacePPI::Token::Word

PPI::Structure::ForLoopPPI::Structure::ListPPI::Structure::SubscriptPPI::Structure::UnknownPPI::TokenPPI::Token::ArrayIndexPPI::Token::AttributePPI::Token::CastPPI::Token::CommentPPI::Token::DashedWordPPI::Token::DataPPI::Token::EndPPI::Token::HereDocPPI::Token::LabelPPI::Token::MagicPPI::Token::NumberPPI::Token::OperatorPPI::Token::PodPPI::Token::PrototypePPI::Token::QuotePPI::Token::Quote::DoublePPI::Token::Quote::InterpolatePPI::Token::Quote::Literal

PPI Modules

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

Extending Perl Critic

Some Perl::Critic::TestUtils Help Methods

- block_perlcriticrc- critique- pcritique- fcritique

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% okayt/initial-setup......ok Failed Test Stat Wstat Total Fail List of Failed-------------------------------------------------------------------------------t/exercise-policy.t 255 65280 1 2 1Failed 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.

Extending Perl Critic

sub violates {}

lib/Perl/Critic/Policy/BuiltinFunctions/RequireBlockGrepAndMap.pm

Extending Perl Critic

--(0)> prove -Ilib t/*t/exercise-policy....okt/initial-setup......okAll tests successful.Files=2, Tests=5, 2 wallclock secs ( 0.98 cusr + 0.22 csys = 1.20 CPU)

Extending Perl Critic

my $custom_policy = 'BuiltinFunctions::RequireBlockGrepAndMap';

{ my $code = 'grep { $_ }'; my $violation_count = pcritique( $custom_policy, \$code ); is($violation_count, 0, 'allowed block grep');}

{ my $code = 'grep "$_"'; my $violation_count = pcritique( $custom_policy, \$code ); is($violation_count, 1, 'disallowed string grep');}

t/exercise-policy.t

Extending Perl Critic

--(0)> prove -Ilib t/*t/exercise-policy....NOK 2 # Failed test 'disallowed string grep'# at t/exercise-policy.t line 17.# got: '0'# expected: '1'# Looks like you failed 1 test of 2.t/exercise-policy....dubious Test returned status 1 (wstat 256, 0x100)DIED. FAILED test 2 Failed 1/2 tests, 50.00% okayt/initial-setup......ok Failed Test Stat Wstat Total Fail List of Failed-------------------------------------------------------------------------------t/exercise-policy.t 1 256 2 1 2Failed 1/2 test scripts. 1/6 subtests failed.Files=2, Tests=6, 2 wallclock secs ( 1.00 cusr + 0.22 csys = 1.22 CPU)Failed 1/2 test programs. 1/6 subtests failed.

Extending Perl Critic

my $description = q{Expression form of "grep" and "map"};my $explanation = [169];

sub violates { my ( $self, $element, $document ) = @_;

return unless $element eq 'grep';

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

Extending Perl Critic

t/exercise-policy....okt/initial-setup......okAll tests successful.Files=2, Tests=6, 1 wallclock secs ( 1.00 cusr + 0.22 csys = 1.22 CPU)

Extending Perl Critic

Some PPI Utility Methods (from PPI::Element)

- parent- next_sibling- snext_sibling- previous_sibling- sprevious_sibling- first_token- last_token- next_token- previous_token

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

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

Extending Perl Critic

## name allowed block grep## failures 0## cut

grep { $_ }

## name disallowed string grep## failures 1## cut

grep "$_"

t/BuiltinFunctions/RequireBlockGrepAndMap.run

Extending Perl Critic

--(0)> prove -Ilib t/*t/exercise-policy....okt/initial-setup......okAll tests successful.Files=2, Tests=6, 2 wallclock secs ( 1.01 cusr + 0.22 csys = 1.23 CPU)

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

Extending Perl Critic

...

## name grep as a method call## failures 0## cut

$object->grep('this');

t/BuiltinFunctions/RequireBlockGrepAndMap.run

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% okayt/initial-setup......ok Failed Test Stat Wstat Total Fail List of Failed-------------------------------------------------------------------------------t/exercise-policy.t 1 256 3 1 3Failed 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.

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

Extending Perl Critic

--(0)> prove -Ilib t/*t/exercise-policy....okt/initial-setup......okAll tests successful.Files=2, Tests=7, 2 wallclock secs ( 1.01 cusr + 0.24 csys = 1.25 CPU)

Extending Perl Critic

Some Useful Methods From Perl::Critic::Utils

- is_perl_global- is_perl_builtin- is_hash_key- is_method_call- is_subroutine_name- is_function_call- first_arg- parse_arg_list

Extending Perl Critic

...

## name grep as a hash key## failures 0## cut

$my_hash{ grep } = 1;

t/BuiltinFunctions/RequireBlockGrepAndMap.run

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% okayt/initial-setup......okFailed Test Stat Wstat Total Fail List of Failed-------------------------------------------------------------------------------t/exercise-policy.t 255 65280 4 2 4Failed 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.

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

Extending Perl Critic

--(0)> prove -Ilib t/*t/exercise-policy....okt/initial-setup......okAll tests successful.Files=2, Tests=8, 2 wallclock secs ( 1.02 cusr + 0.25 csys = 1.27 CPU)

Extending Perl Critic

...

## name grep is a sub name## failures 0## cut

sub grep { return }

t/BuiltinFunctions/RequireBlockGrepAndMap.run

Extending Perl Critic

--(0)> prove -Ilib t/*t/exercise-policy....okt/initial-setup......okAll tests successful.Files=2, Tests=9, 2 wallclock secs ( 1.02 cusr + 0.22 csys = 1.24 CPU)

Extending Perl Critic

...

## name grep is a sub prototype name## failures 0## cut

sub grep;

t/BuiltinFunctions/RequireBlockGrepAndMap.run

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% okayt/initial-setup......ok Failed Test Stat Wstat Total Fail List of Failed-------------------------------------------------------------------------------t/exercise-policy.t 1 256 6 1 6Failed 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.

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

Extending Perl Critic

--(0)> prove -Ilib t/*t/exercise-policy....okt/initial-setup......okAll tests successful.Files=2, Tests=10, 2 wallclock secs ( 1.02 cusr + 0.22 csys = 1.24 CPU)

Extending Perl Critic

...

## name grep has parens## failures 0## cut

grep ( { $_ } @list );

t/BuiltinFunctions/RequireBlockGrepAndMap.run

Extending Perl Critic

--(0)> prove -Ilib t/*t/exercise-policy....NOK 7# Failed test 'BuiltinFunctions::RequireBlockGrepAndMap - line 37 - grep has parens'# at t/exercise-policy.t line 41.# got: '1'# expected: '0'# Looks like you failed 1 test of 7.t/exercise-policy....dubious Test returned status 1 (wstat 256, 0x100)DIED. FAILED test 7 Failed 1/7 tests, 85.71% okayt/initial-setup......ok Failed Test Stat Wstat Total Fail List of Failed-------------------------------------------------------------------------------t/exercise-policy.t 1 256 7 1 7Failed 1/2 test scripts. 1/11 subtests failed.Files=2, Tests=11, 2 wallclock secs ( 1.03 cusr + 0.22 csys = 1.25 CPU)Failed 1/2 test programs. 1/11 subtests failed.

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(); $sibling = $sibling->schild(0)
if $sibling->isa('PPI::Structure::List');

return if $sibling->isa('PPI::Structure::Block');

return $self->violation( $description, $explanation, $element, );}

lib/Perl/Critic/Policy/BuiltinFunctions/RequireBlockGrepAndMap.pm

Extending Perl Critic

--(0)> prove -Ilib t/*t/exercise-policy....okt/initial-setup......okAll tests successful.Files=2, Tests=11, 2 wallclock secs ( 1.04 cusr + 0.24 csys = 1.28 CPU)

Extending Perl Critic

...

## name block map## failures 0## cut

map { $_ }

t/BuiltinFunctions/RequireBlockGrepAndMap.run

Extending Perl Critic

--(0)> prove -Ilib t/*t/exercise-policy....okt/initial-setup......okAll tests successful.Files=2, Tests=12, 2 wallclock secs ( 1.04 cusr + 0.22 csys = 1.26 CPU)

Extending Perl Critic

...

## name string map## failures 1## cut

map "$_"

t/BuiltinFunctions/RequireBlockGrepAndMap.run

Extending Perl Critic

--(0)> prove -Ilib t/*t/exercise-policy....NOK 9# Failed test 'BuiltinFunctions::RequireBlockGrepAndMap - line 49 - string map'# at t/exercise-policy.t line 41.# got: '0'# expected: '1'# Looks like you failed 1 test of 9.t/exercise-policy....dubious Test returned status 1 (wstat 256, 0x100)DIED. FAILED test 9 Failed 1/9 tests, 88.89% okayt/initial-setup......okFailed Test Stat Wstat Total Fail List of Failed-------------------------------------------------------------------------------t/exercise-policy.t 1 256 9 1 9Failed 1/2 test scripts. 1/13 subtests failed.Files=2, Tests=13, 1 wallclock secs ( 1.05 cusr + 0.22 csys = 1.27 CPU)Failed 1/2 test programs. 1/13 subtests failed.

Extending Perl Critic

sub violates { my ( $self, $element, $document ) = @_;

return unless grep { $element eq $_ } qw[grep map];

return if is_method_call($element); return if is_hash_key($element); return if is_subroutine_name($element);

my $sibling = $element->snext_sibling(); $sibling = $sibling->schild(0) if $sibling->isa('PPI::Structure::List');

return if $sibling->isa('PPI::Structure::Block');

return $self->violation( $description, $explanation, $element, );}

lib/Perl/Critic/Policy/BuiltinFunctions/RequireBlockGrepAndMap.pm

Extending Perl Critic

--(0)> prove -Ilib t/*t/exercise-policy....okt/initial-setup......okAll tests successful.Files=2, Tests=13, 1 wallclock secs ( 1.05 cusr + 0.23 csys = 1.28 CPU)

Extending Perl Critic

...

## name string map allowed## failures 0## parms { allow => ['map'] }## cut

map "$_"

t/BuiltinFunctions/RequireBlockGrepAndMap.run

Extending Perl Critic

--(0)> prove -Ilib t/*t/exercise-policy....NOK 10# Failed test 'BuiltinFunctions::RequireBlockGrepAndMap - line 55 - string map allowed'# at t/exercise-policy.t line 41.# got: '1'# expected: '0'# Looks like you failed 1 test of 10.t/exercise-policy....dubiousTest returned status 1 (wstat 256, 0x100)DIED. FAILED test 10 Failed 1/10 tests, 90.00% okayt/initial-setup......okFailed Test Stat Wstat Total Fail List of Failed-------------------------------------------------------------------------------t/exercise-policy.t 1 256 10 1 10Failed 1/2 test scripts. 1/14 subtests failed.Files=2, Tests=14, 2 wallclock secs ( 1.05 cusr + 0.22 csys = 1.27 CPU)Failed 1/2 test programs. 1/14 subtests failed.

Extending Perl Critic

sub new { my ($class, %args) = @_; my $self = $class->SUPER::new(%args);

$args{allow} = [] unless exists $args{allow};

for my $function (qw(grep map)) { push @{$self->{filter}}, $function unless grep { $_ eq $function } @{$args{allow}}; }

return $self;}

lib/Perl/Critic/Policy/BuiltinFunctions/RequireBlockGrepAndMap.pm

Extending Perl Critic

sub violates { my ( $self, $element, $document ) = @_;

return unless grep { $element eq $_ } @{$self->{filter} || []};

return if is_method_call($element); return if is_hash_key($element); return if is_subroutine_name($element);

my $sibling = $element->snext_sibling(); $sibling = $sibling->schild(0) if $sibling->isa('PPI::Structure::List');

return if $sibling->isa('PPI::Structure::Block');

return $self->violation( $description, $explanation, $element, );}

lib/Perl/Critic/Policy/BuiltinFunctions/RequireBlockGrepAndMap.pm

Extending Perl Critic

--(0)> prove -Ilib t/*t/exercise-policy....okt/initial-setup......okAll tests successful.Files=2, Tests=14, 2 wallclock secs ( 1.06 cusr + 0.24 csys = 1.30 CPU)

Extending Perl Critic

...

## name string grep allowed## failures 0## parms { allow => ['grep'] }## cut

grep "$_"

## name string grep and map allowed## failures 0## parms { allow => ['grep', 'map'] }## cut

grep "$_";map "$_";

## name string grep and map not allowed## failures 2## cut

grep "$_";map "$_";

t/BuiltinFunctions/RequireBlockGrepAndMap.run

Extending Perl Critic

--(0)> prove -Ilib t/*t/exercise-policy....okt/initial-setup......okAll tests successful.Files=2, Tests=17, 2 wallclock secs ( 1.08 cusr + 0.22 csys = 1.30 CPU)

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 and C 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

Extending Perl Critic

... and more tests!

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