File Coverage

blib/lib/MooseX/Role/WarnOnConflict.pm
Criterion Covered Total %
statement 45 47 95.7
branch 11 14 78.5
condition 2 6 33.3
subroutine 9 9 100.0
pod 1 2 50.0
total 68 78 87.1


line stmt bran cond sub pod time code
1              
2             # ABSTRACT: Warn if classes override role methods without excluding them
3              
4             use warnings;
5 2     2   187965 use strict;
  2         13  
  2         68  
6 2     2   10  
  2         4  
  2         70  
7             our $VERSION = '0.01';
8              
9             use MooseX::Meta::Role::WarnOnConflict;
10 2     2   722 use Moose::Role;
  2         6  
  2         78  
11 2     2   1140 use Moose::Exporter;
  2         8611  
  2         6  
12 2     2   9055 Moose::Exporter->setup_import_methods( also => 'Moose::Role' );
  2         6  
  2         7  
13              
14             my ( $class, %opt ) = @_;
15             return Moose::Role->init_meta( ##
16 1     1 0 77 %opt, ##
17 1         5 metaclass => 'MooseX::Meta::Role::WarnOnConflict'
18             );
19             }
20              
21             package # Hide from PAUSE
22             MooseX::Meta::Role::Application::ToClass::WarnOnConflig;
23             use Moose;
24             use Carp 'carp';
25 2     2   221 our @CARP_NOT = (__PACKAGE__);
  2         4  
  2         8  
26 2     2   10424 extends 'Moose::Meta::Role::Application::ToClass';
  2         6  
  2         759  
27              
28             my ( $self, $role, $class ) = @_;
29             my @implicitly_overridden;
30              
31 3     3 1 2147 METHOD: foreach my $method_name ( $role->get_method_list ) {
32 3         11 next METHOD if 'meta' eq $method_name; # Moose auto-exports this
33             unless ( $self->is_method_excluded($method_name) ) {
34 3         12  
35 6 100       881 # it if it has one already
36 3 100       16 if (
37             $class->has_method($method_name)
38             &&
39 1 50 33     61  
40             # and if they are not the same thing ...
41             $class->get_method($method_name)->body !=
42             $role->get_method($method_name)->body
43             )
44             {
45             push @implicitly_overridden => $method_name;
46             next;
47             }
48 1         290 else {
49 1         3 # add method to the required methods
50             $role->add_required_methods($method_name);
51              
52             # add it, although it could be overridden
53 0         0 $class->add_method( $method_name,
54             $role->get_method($method_name) );
55             }
56 0         0 }
57              
58             if ( $self->is_method_aliased($method_name) ) {
59             my $aliased_method_name = $self->get_method_aliases->{$method_name};
60              
61 2 100       99 # it if it has one already
62 1         74 if (
63             $class->has_method($aliased_method_name)
64             &&
65 1 50 33     9  
66             # and if they are not the same thing ...
67             $class->get_method($aliased_method_name)->body !=
68             $role->get_method($method_name)->body
69             )
70             {
71             my $class_name = $class->name;
72             my $role_name = $role->name;
73             carp(
74 1         155 "$class_name should not alias $role_name '$method_name' to '$aliased_method_name' if a local method of the same name exists"
75 1         3 );
76 1         14 }
77             $class->add_method( $aliased_method_name,
78             $role->get_method($method_name) );
79             }
80 1         218 }
81              
82             if (@implicitly_overridden) {
83             my $s = @implicitly_overridden > 1 ? "s" : "";
84              
85 3 100       7 my $class_name = $class->name;
86 1 50       4 my $role_name = $role->name;
87             my $methods = join ', ' => @implicitly_overridden;
88 1         3  
89 1         2 # we use \n because we have no hope of guessing the right stack frame,
90 1         3 # it's almost certainly never going to be the one above us
91             carp(<<" END_ERROR");
92             The class $class_name has implicitly overridden the method$s ($methods) from
93             role $role_name. If this is intentional, please exclude the method$s from
94 1         32 composition to silence this warning (see Moose::Cookbook::Roles::Recipe2)
95             END_ERROR
96             }
97              
98             # we must reset the cache here since
99             # we are just aliasing methods, otherwise
100             # the modifiers go wonky.
101             $class->reset_package_cache_flag;
102             }
103              
104 3         408 1;
105              
106              
107             =pod
108              
109             =encoding UTF-8
110              
111             =head1 NAME
112              
113             MooseX::Role::WarnOnConflict - Warn if classes override role methods without excluding them
114              
115             =head1 VERSION
116              
117             version 0.01
118              
119             =head1 SYNOPSIS
120              
121             This code will warn at composition time:
122              
123             {
124             package My::Role;
125             use MooseX::Role::WarnOnConflict;
126             sub conflict {}
127             }
128             {
129             package My::Class;
130             use Moose;
131             with 'My::Role';
132             sub conflict {}
133             }
134              
135             With an error message similar to the following:
136              
137             The class My::Class has implicitly overridden the method (conflict) from
138             role My::Role ...
139              
140             To resolve this, explicitly exclude the 'conflict' method:
141              
142             {
143             package My::Class;
144             use Moose;
145             with 'My::Role' => { -excludes => [ 'conflict' ] };
146             sub conflict {}
147             }
148              
149             Aliasing a role method to an existing method will also warn:
150              
151             {
152             package My::Class;
153             use Moose;
154             with 'My::Role' => {
155             -excludes => ['conflict'],
156             -alias => { conflict => 'another_method' },
157             };
158             sub conflict { }
159             sub another_method { }
160             }
161              
162             =head1 DESCRIPTION
163              
164             When using L<Moose::Role>, a class which provides a method a role provides
165             will silently override that method. This can cause strange, hard-to-debug
166             errors when the role's methods are not called. Simply use
167             C<MooseX::Role::WarnOnConflict> instead of C<Moose::Role> and overriding a
168             role's method becomes a composition-time warning. See the synopsis for a
169             resolution.
170              
171             =head1 AUTHOR
172              
173             Curtis "Ovid" Poe <curtis.poe@gmail.com>
174              
175             =head1 COPYRIGHT AND LICENSE
176              
177             This software is Copyright (c) 2022 by Curtis "Ovid" Poe.
178              
179             This is free software, licensed under:
180              
181             The Artistic License 2.0 (GPL Compatible)
182              
183             =cut