YAPC::NA 2007 - Customizing And Extending Perl Critic
Upcoming SlideShare
Loading in...5
×

Like this? Share it with your network

Share

YAPC::NA 2007 - Customizing And Extending Perl Critic

  • 5,457 views
Uploaded on

A presentation on developing a custom Policy for the Perl Critic system using a test-driven development approach.

A presentation on developing a custom Policy for the Perl Critic system using a test-driven development approach.

More in: Technology , Business
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
5,457
On Slideshare
5,457
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
72
Comments
0
Likes
1

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Customizing and Extending Perl Critic YAPC::NA 2007 Houston, TX Josh McAdams
  • 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
  • 6. Extending Perl Critic --(0)> prove -Ilib t/* t/initial-setup....ok All tests successful.
  • 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
  • 10. 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)
  • 11. 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
  • 12. Extending Perl Critic ... is_deeply( [$policy->get_themes()], [qw(almost_core pbp)], 'proper themes set' ); t/initial-setup.t
  • 13. 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% okay Failed Test Stat Wstat Total Fail List of Failed ------------------------------------------------------------------------------- t/initial-setup.t 1 256 3 1 3 Failed 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.
  • 14. Extending Perl Critic sub default_themes { return qw( almost_core pbp ); } lib/Perl/Critic/Policy/BuiltinFunctions/RequireBlockGrepAndMap.pm
  • 15. 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)
  • 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.
  • 18. Extending Perl Critic sub applies_to { return 'PPI::Token::Word'; } lib/Perl/Critic/Policy/BuiltinFunctions/RequireBlockGrepAndMap.pm
  • 19. 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)
  • 20. Extending Perl Critic PPI::Document PPI::Document::File PPI::Document::Fragment PPI::Document::Normalized PPI::Element PPI::Statement PPI::Statement::Break PPI::Statement::Compound PPI::Statement::Data PPI::Statement::End PPI::Statement::Expression PPI::Statement::Include PPI::Statement::Null PPI::Statement::Package PPI::Statement::Scheduled PPI::Statement::Sub PPI::Statement::Unknown PPI::Statement::UnmatchedBrace PPI::Statement::Variable PPI::Structure PPI::Structure::Block PPI::Structure::Condition PPI::Structure::Constructor PPI::Token::Quote::Single PPI::Token::QuoteLike PPI::Token::QuoteLike::Backtick PPI::Token::QuoteLike::Command PPI::Token::QuoteLike::Readline PPI::Token::QuoteLike::Regexp PPI::Token::QuoteLike::Words PPI::Token::Regexp PPI::Token::Regexp::Match PPI::Token::Regexp::Substitute PPI::Token::Regexp::Transliterate PPI::Token::Separator PPI::Token::Structure PPI::Token::Symbol PPI::Token::Unknown PPI::Token::Whitespace PPI::Token::Word PPI::Structure::ForLoop PPI::Structure::List PPI::Structure::Subscript PPI::Structure::Unknown PPI::Token PPI::Token::ArrayIndex PPI::Token::Attribute PPI::Token::Cast PPI::Token::Comment PPI::Token::DashedWord PPI::Token::Data PPI::Token::End PPI::Token::HereDoc PPI::Token::Label PPI::Token::Magic PPI::Token::Number PPI::Token::Operator PPI::Token::Pod PPI::Token::Prototype PPI::Token::Quote PPI::Token::Quote::Double PPI::Token::Quote::Interpolate PPI::Token::Quote::Literal PPI Modules
  • 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
  • 25. Extending Perl Critic --(0)> prove -Ilib t/* t/exercise-policy....ok t/initial-setup......ok All tests successful. Files=2, Tests=5, 2 wallclock secs ( 0.98 cusr + 0.22 csys = 1.20 CPU)
  • 26. 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
  • 27. 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% okay t/initial-setup......ok Failed Test Stat Wstat Total Fail List of Failed ------------------------------------------------------------------------------- t/exercise-policy.t 1 256 2 1 2 Failed 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.
  • 28. 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
  • 29. Extending Perl Critic t/exercise-policy....ok t/initial-setup......ok All tests successful. Files=2, Tests=6, 1 wallclock secs ( 1.00 cusr + 0.22 csys = 1.22 CPU)
  • 30. 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
  • 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
  • 33. Extending Perl Critic ## name allowed block grep ## failures 0 ## cut grep { $_ } ## name disallowed string grep ## failures 1 ## cut grep "$_" t/BuiltinFunctions/RequireBlockGrepAndMap.run
  • 34. Extending Perl Critic --(0)> prove -Ilib t/* t/exercise-policy....ok t/initial-setup......ok All tests successful. Files=2, Tests=6, 2 wallclock secs ( 1.01 cusr + 0.22 csys = 1.23 CPU)
  • 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
  • 39. Extending Perl Critic --(0)> prove -Ilib t/* t/exercise-policy....ok t/initial-setup......ok All tests successful. Files=2, Tests=7, 2 wallclock secs ( 1.01 cusr + 0.24 csys = 1.25 CPU)
  • 40. 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
  • 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
  • 44. Extending Perl Critic --(0)> prove -Ilib t/* t/exercise-policy....ok t/initial-setup......ok All tests successful. Files=2, Tests=8, 2 wallclock secs ( 1.02 cusr + 0.25 csys = 1.27 CPU)
  • 45. Extending Perl Critic ... ## name grep is a sub name ## failures 0 ## cut sub grep { return } t/BuiltinFunctions/RequireBlockGrepAndMap.run
  • 46. Extending Perl Critic --(0)> prove -Ilib t/* t/exercise-policy....ok t/initial-setup......ok All tests successful. Files=2, Tests=9, 2 wallclock secs ( 1.02 cusr + 0.22 csys = 1.24 CPU)
  • 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
  • 50. Extending Perl Critic --(0)> prove -Ilib t/* t/exercise-policy....ok t/initial-setup......ok All tests successful. Files=2, Tests=10, 2 wallclock secs ( 1.02 cusr + 0.22 csys = 1.24 CPU)
  • 51. Extending Perl Critic ... ## name grep has parens ## failures 0 ## cut grep ( { $_ } @list ); t/BuiltinFunctions/RequireBlockGrepAndMap.run
  • 52. 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% okay t/initial-setup......ok Failed Test Stat Wstat Total Fail List of Failed ------------------------------------------------------------------------------- t/exercise-policy.t 1 256 7 1 7 Failed 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.
  • 53. 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
  • 54. Extending Perl Critic --(0)> prove -Ilib t/* t/exercise-policy....ok t/initial-setup......ok All tests successful. Files=2, Tests=11, 2 wallclock secs ( 1.04 cusr + 0.24 csys = 1.28 CPU)
  • 55. Extending Perl Critic ... ## name block map ## failures 0 ## cut map { $_ } t/BuiltinFunctions/RequireBlockGrepAndMap.run
  • 56. Extending Perl Critic --(0)> prove -Ilib t/* t/exercise-policy....ok t/initial-setup......ok All tests successful. Files=2, Tests=12, 2 wallclock secs ( 1.04 cusr + 0.22 csys = 1.26 CPU)
  • 57. Extending Perl Critic ... ## name string map ## failures 1 ## cut map "$_" t/BuiltinFunctions/RequireBlockGrepAndMap.run
  • 58. 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% okay t/initial-setup......ok Failed Test Stat Wstat Total Fail List of Failed ------------------------------------------------------------------------------- t/exercise-policy.t 1 256 9 1 9 Failed 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.
  • 59. 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
  • 60. Extending Perl Critic --(0)> prove -Ilib t/* t/exercise-policy....ok t/initial-setup......ok All tests successful. Files=2, Tests=13, 1 wallclock secs ( 1.05 cusr + 0.23 csys = 1.28 CPU)
  • 61. Extending Perl Critic ... ## name string map allowed ## failures 0 ## parms { allow => ['map'] } ## cut map "$_" t/BuiltinFunctions/RequireBlockGrepAndMap.run
  • 62. 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....dubious Test returned status 1 (wstat 256, 0x100) DIED. FAILED test 10 Failed 1/10 tests, 90.00% okay t/initial-setup......ok Failed Test Stat Wstat Total Fail List of Failed ------------------------------------------------------------------------------- t/exercise-policy.t 1 256 10 1 10 Failed 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.
  • 63. 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
  • 64. 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
  • 65. Extending Perl Critic --(0)> prove -Ilib t/* t/exercise-policy....ok t/initial-setup......ok All tests successful. Files=2, Tests=14, 2 wallclock secs ( 1.06 cusr + 0.24 csys = 1.30 CPU)
  • 66. 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
  • 67. Extending Perl Critic --(0)> prove -Ilib t/* t/exercise-policy....ok t/initial-setup......ok All tests successful. Files=2, Tests=17, 2 wallclock secs ( 1.08 cusr + 0.22 csys = 1.30 CPU)
  • 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
  • 69. Extending Perl Critic ... and more tests!
  • 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