File Coverage

blib/lib/Module/Checkstyle.pm
Criterion Covered Total %
statement 129 139 92.8
branch 44 52 84.6
condition 3 5 60.0
subroutine 21 21 100.0
pod 4 4 100.0
total 201 221 90.9


line stmt bran cond sub pod time code
1             package Module::Checkstyle;
2              
3 1     1   54912 use warnings;
  1         2  
  1         37  
4 1     1   6 use strict;
  1         2  
  1         28  
5 1     1   7 use Carp;
  1         5  
  1         209  
6              
7 1     1   1271 use PPI;
  1         210435  
  1         37  
8 1     1   2263 use File::HomeDir qw(home);
  1         6525  
  1         80  
9 1     1   850 use File::Spec::Functions qw(catfile rel2abs);
  1         797  
  1         59  
10 1     1   829 use File::Find::Rule;
  1         7874  
  1         7  
11 1     1   44 use List::Util qw(first);
  1         2  
  1         92  
12 1     1   840 use Module::Pluggable search_path => [qw(Module::Checkstyle::Check)], require => 1;
  1         24086  
  1         8  
13              
14 1     1   967 use Module::Checkstyle::Config;
  1         3  
  1         47  
15 1     1   658 use Module::Checkstyle::Util qw(:problem);
  1         3  
  1         1892  
16              
17             our $VERSION = "0.04";
18              
19             # Controls if we want to be more verbose
20             our $debug = 0;
21              
22             sub new {
23 7     7 1 7760 my ($class, $config) = @_;
24            
25             # Standard remake ref to name for pre 5.8 Perls
26 7   66     62 $class = ref $class || $class;
27            
28             # Load config from ~/.module-checkstyle/config or supplied file
29 7         83 $config = Module::Checkstyle::Config->new($config);
30              
31 7 50       20 if ($debug) {
32 0 0       0 if ($config->get_directive('_', '_config-path')) {
33 0         0 print STDERR "Using configuration from: ", $config->get_directive('_', '_config-path'), "\n";
34             }
35             }
36            
37 7         54 my $self = bless {
38             config => $config,
39             checked => {},
40             problems => [],
41             handlers => {},
42             }, $class;
43            
44             # Config file determines what checks to enable by declaring them as
45 7         39 my %enable_plugin = map { $_ => 1 } $config->get_enabled_sections();
  4         18  
46 7         50 my @plugins = Module::Checkstyle->plugins;
47 7         51883 foreach my $plugin_class (@plugins) {
48 63         137 my $name = $plugin_class;
49 63         195 $name =~ s/^Module::Checkstyle::Check:://;
50 63 100       187 if ($enable_plugin{$name}) {
51 4         32 my %event = $plugin_class->register();
52 4 100       43 if (%event) {
53 3         27 my $plugin = $plugin_class->new($config);
54 3         16 while (my ($event, $handler) = each %event) {
55 7         14 push @{$self->{handlers}->{$event}}, [$plugin, $handler];
  7         49  
56             }
57             }
58             }
59             }
60            
61 7         47 return $self;
62             }
63              
64             sub _check_file {
65 26     26   77 my ($self, $file) = @_;
66            
67             # Check for perl in shebang
68 26 100       330 if ($file !~ /\.(?:pm|pl)$/i) {
69 15         36 my $skip = 0;
70 15         36 eval {
71 15   50     1620 open my $fh, "<", $file || die $!;
72 15         2537 my $shebang = <$fh>;
73              
74 15 50       69 if (defined $shebang) {
75 15         67 chomp $shebang;
76 15 100       158 $skip = 1 if $shebang !~ /^\#\!.*perl/;
77             }
78             else {
79 0         0 $skip = 1;
80             }
81            
82 15         2033 close($fh);
83             };
84 15 100       58 return if $skip;
85            
86 14 50       58 if ($@) {
87 0         0 push @{$self->{problems}}, make_problem('error',
  0         0  
88             $@,
89             undef,
90             $file);
91 0         0 return;
92             }
93             }
94            
95 25         276 my $document = PPI::Document->new($file);
96            
97 25 50       2874298 if (!$document) {
98 0         0 push @{$self->{problems}}, make_problem('error',
  0         0  
99             PPI::Document->errstr,
100             undef,
101             $file);
102 0         0 return;
103             }
104            
105             # Do all the checking
106 25         193 $document->index_locations();
107 25         626890 $self->_traverse_element($document, $file);
108            
109 25         221 1;
110             }
111              
112             # The following who declarations (@exlude) and (@include) are copied from AnnoCPAN
113             # by Ivan Tubert-Brohman Eitub@cpan.orgE
114              
115             # default files to ignore
116             my @excludes = (
117             qr(/inc/), # used by Module::Install bundles
118             qr(/t/),
119             qr(/eg/),
120             qr(/blib/),
121             qr(/pm_to_blib),
122             qr(/?Makefile(.PL)?$),
123             qr(/Build.PL$),
124             qr(/MANIFEST$)i,
125             qr(/README$)i,
126             qr(/Changes$)i,
127             qr(/ChangeLog$)i,
128             qr(/LICENSE$)i,
129             qr(/TODO$)i,
130             qr(/AUTHORS?$)i,
131             qr(/CVS/\w+$),
132             qr(/.svn/),
133             qr(~$), # backup files
134             qr(/\#.*\#$), # backup files
135             );
136              
137             # default files to include
138             my @includes = (
139             qr{.(pm|pl)$}i,
140             qr{/[^./]+$}, # files with no extension (typically scripts)
141             );
142              
143             sub _any_match {
144 171     171   4161 my ($value, $list) = @_;
145            
146 171 100   824   883 if (first { $value =~ $_ } @$list) {
  824         2822  
147 99         496 return 1;
148             }
149            
150 72         340 return 0;
151             }
152              
153             sub _get_files {
154 3     3   388 my ($dir, $ignore_common) = @_;
155            
156 3         133 my @files = File::Find::Rule->file()->ascii()->in($dir);
157            
158 3         18914 @files = map { rel2abs($_) } @files;
  120         2095  
159            
160 3 100       108 if ($ignore_common) {
161 2 100       8 @files = grep { _any_match($_, \@includes) && $_ } @files;
  108         222  
162 2 100       9 @files = grep { !_any_match($_, \@excludes) && $_ } @files;
  62         109  
163             }
164            
165 3         28 return @files;
166             }
167              
168             sub check {
169 5     5 1 22 my $self = shift;
170 5 100       32 my $args = ref $_[-1] eq 'HASH' ? pop : { ignore_common => 1 };
171            
172 5         14 my @check_files;
173              
174             GET_FILES:
175 5         16 foreach my $file (@_) {
176 5 100       22 next GET_FILES if !defined $file;
177            
178 4 100       123 if (!-e $file) {
179 1         183 croak "$file does not exist";
180             }
181            
182 3 100       44 if (-d $file) {
183             # Passing ignore_common => 1 or ommiting it turns on ignoration
184             # of common files usually found in distributions such as README,
185             # blib/*, inc/*, t/*
186 2         10 push @check_files, _get_files($file, $args->{ignore_common});
187             }
188             else {
189 1         6 push @check_files, $file;
190             }
191             }
192            
193 4         16 foreach my $file (@check_files) {
194 26         234442 $self->_check_file($file);
195             }
196            
197 4         30759 return scalar @{$self->{problems}};
  4         50  
198             }
199              
200             sub _post_event {
201 28592     28592   66536 my ($self, $event, @args) = @_;
202            
203 28592 100       125235 if (exists $self->{handlers}->{$event}) {
204 31         62 my @handlers = @{$self->{handlers}->{$event}};
  31         125  
205 31 50       125 if (@handlers) {
206 31         87 foreach my $handler (@handlers) {
207 31         86 my ($object, $callback) = @$handler;
208 31         86 eval {
209 31         165 my @problems = $callback->($object, @args);
210 31         141 push @{$self->{problems}}, @problems;
  31         139  
211             };
212 31 50       204 if ($@) {
213 0         0 croak $@;
214             }
215             }
216             }
217             }
218             }
219              
220             sub _traverse_element {
221 20477     20477   39219 my ($self, $element, $file) = @_;
222            
223 20477         37921 my $event = ref $element;
224 20477         23157 my $post_leave = 0;
225 20477 100       95999 if ($element->isa('PPI::Node')) {
226 4056         16886 $self->_post_event("enter $event", $element, $file);
227 4056         7085 $post_leave = 1;
228             }
229            
230 20477         40898 $self->_post_event($event, $element, $file);
231            
232 20477 100       92943 if ($element->isa('PPI::Node')) {
233 4056         17831 foreach my $child ($element->children) {
234 20451         60463 $self->_traverse_element($child, $file);
235             }
236             }
237            
238 20477 100       62415 if ($post_leave) {
239 4056         10563 $self->_post_event("leave $event", $element, $file);
240             }
241             }
242              
243             sub flush_problems {
244 3     3 1 2637 my ($self) = @_;
245 3         8 my $problems = $self->{problems};
246 3         11 $self->{problems} = [];
247 3 100       24 return wantarray ? @$problems : $problems;
248             }
249              
250             sub get_problems {
251 5     5 1 544 my ($self) = @_;
252 5 100       28 return wantarray ? @{$self->{problems}} : $self->{problems};
  4         17  
253             }
254              
255             1;
256             __END__