File Coverage

blib/lib/Perl/Critic/Policy/Moo/ProhibitMakeImmutable.pm
Criterion Covered Total %
statement 45 46 97.8
branch 14 20 70.0
condition 18 36 50.0
subroutine 12 13 92.3
pod 4 5 80.0
total 93 120 77.5


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::Moo::ProhibitMakeImmutable;
2 1     1   401827 use 5.008001;
  1         5  
3 1     1   7 use strict;
  1         2  
  1         21  
4 1     1   5 use warnings;
  1         2  
  1         42  
5             our $VERSION = '0.05';
6              
7 1     1   6 use Readonly;
  1         2  
  1         51  
8 1     1   7 use Perl::Critic::Utils qw{ :severities :classification :ppi };
  1         2  
  1         81  
9              
10 1     1   434 use base 'Perl::Critic::Policy';
  1         3  
  1         580  
11              
12             Readonly::Scalar my $DESC => q{Moo class should not call ->make_immutable};
13             Readonly::Scalar my $EXPL => q{When migrating from Moose to Moo it is easy to leave in __PACKAGE__->meta->make_immutable; statements which will cause Moose to be loaded and a metaclass created};
14              
15 6     6 0 1212234 sub supported_parameters { return() }
16 2     2 1 49 sub default_severity { return $SEVERITY_MEDIUM }
17 0     0 1 0 sub default_themes { return qw( performance ) }
18 6     6 1 1258927 sub applies_to { return 'PPI::Token::Word' }
19              
20             sub violates {
21 59     59 1 1181 my ($self, $start_word, $doc) = @_;
22              
23 59 100       135 return unless $start_word->content() eq '__PACKAGE__';
24 6         51 my $element = $start_word;
25              
26 6         35 $element = $element->snext_sibling();
27 6 50 33     243 return unless $element
      33        
28             and $element->isa('PPI::Token::Operator')
29             and $element->content() eq '->';
30              
31 6         75 $element = $element->snext_sibling();
32 6 50 33     219 return unless $element
      33        
33             and $element->isa('PPI::Token::Word')
34             and $element->content() eq 'meta';
35              
36 6         58 $element = $element->snext_sibling();
37 6 50 33     164 return unless $element
      33        
38             and $element->isa('PPI::Token::Operator')
39             and $element->content() eq '->';
40              
41 6         49 $element = $element->snext_sibling();
42 6 50 33     153 return unless $element
      33        
43             and $element->isa('PPI::Token::Word')
44             and $element->content() eq 'make_immutable';
45              
46 6         52 my $package = _find_package( $start_word );
47              
48             my $included = $doc->find_any(sub{
49 277 100 66 277   3857 $_[1]->isa('PPI::Statement::Include')
      100        
      66        
50             and
51             defined( $_[1]->module() )
52             and
53             $_[1]->module() eq 'Moo'
54             and
55             $_[1]->type() eq 'use'
56             and
57             _find_package( $_[1] ) eq $package
58 6         186 });
59              
60 6 100       151 return if !$included;
61              
62 2         17 return $self->violation( $DESC, $EXPL, $start_word );
63             }
64              
65             sub _find_package {
66 14     14   583 my ($element) = @_;
67              
68 14         30 my $original = $element;
69              
70 14         70 while ($element) {
71 43 100       1083 if ($element->isa('PPI::Statement::Package')) {
72             # If this package statements is a block package, meaning: package { # stuff in package }
73             # then if we're a descendant of it its our package.
74 11 50       41 return $element->namespace() if $element->ancestor_of( $original );
75              
76             # If we've hit a non-block package then thats our package.
77 11         215 my $blocks = $element->find_any('PPI::Structure::Block');
78 11 50       3287 return $element->namespace() if !$blocks;
79             }
80              
81             # Keep walking backwards until we match the above logic or we get to
82             # the document root (main).
83 32   100     101 $element = $element->sprevious_sibling() || $element->parent();
84             }
85              
86 3         41 return 'main';
87             }
88              
89             1;
90             __END__
91              
92             =head1 NAME
93              
94             Perl::Critic::Policy::Moo::ProhibitMakeImmutable - Makes sure that Moo classes
95             do not contain calls to make_immutable.
96              
97             =head1 DESCRIPTION
98              
99             When migrating from L<Moose> to L<Moo> it can be a common issue to accidentally
100             leave in:
101              
102             __PACKAGE__->meta->make_immutable;
103              
104             This policy complains if this exists in a Moo class as it triggers Moose to be
105             loaded and metaclass created, which defeats some of the benefits you get using
106             Moo instead of Moose.
107              
108             =head1 AUTHORS
109              
110             Aran Clary Deltac <bluefeet@gmail.com>
111             Kivanc Yazan <kyzn@users.noreply.github.com>
112             Graham TerMarsch <graham@howlingfrog.com>
113              
114             =head1 ACKNOWLEDGEMENTS
115              
116             Thanks to L<ZipRecruiter|https://www.ziprecruiter.com/>
117             for encouraging their employees to contribute back to the open
118             source ecosystem. Without their dedication to quality software
119             development this distribution would not exist.
120              
121             =head1 LICENSE
122              
123             This library is free software; you can redistribute it and/or modify
124             it under the same terms as Perl itself.
125              
126             =cut
127