File Coverage

blib/lib/App/perlvars.pm
Criterion Covered Total %
statement 39 69 56.5
branch 7 22 31.8
condition 0 3 0.0
subroutine 9 10 90.0
pod 1 2 50.0
total 56 106 52.8


line stmt bran cond sub pod time code
1             package App::perlvars;
2              
3 3     3   7524 use Moo;
  3         29649  
  3         12  
4 3     3   4482 use autodie;
  3         61812  
  3         15  
5              
6             our $VERSION = '0.000003';
7              
8 3     3   17004 use Path::Tiny qw( path );
  3         31170  
  3         156  
9 3     3   1329 use PPI::Document ();
  3         253563  
  3         93  
10 3     3   1302 use Test::Vars import => [qw( test_vars )];
  3         242961  
  3         30  
11              
12             has ignore_file => (
13             is => 'ro',
14             predicate => '_has_ignore_file',
15             );
16              
17             has _ignore_for_package => (
18             is => 'ro',
19             init_arg => undef,
20             lazy => 1,
21             builder => '_build_ignore_for_package',
22             );
23              
24             sub BUILD {
25 6     6 0 28652 my $self = shift;
26              
27             # We need to read the file before we start checking anything so we can die
28             # if it contains bad lines and not have it look like a failure in a
29             # particular file we're tidying.
30 6         127 $self->_ignore_for_package;
31              
32 6         37 return;
33             }
34              
35             sub validate_file {
36 6     6 1 12 my $self = shift;
37 6         44 my $file = path(shift);
38 6 100       305 unless ( $file->exists ) {
39 1         61 return ( 1, "$file could not be found" );
40             }
41 5 50       227 if ( $file->is_dir ) {
42 0         0 return ( 1, "$file is a dir" );
43             }
44              
45 5         90 my $doc = PPI::Document->new("$file");
46 5 50       34920 return ( 1, "$file could not be parsed as Perl" ) unless $doc;
47              
48 5 50       94 my $package_stmt = $doc->find_first('PPI::Statement::Package')
49             or return ( 0, "$file contains no package" );
50              
51             my ( $exit_code, @msgs ) = test_vars(
52             "$file",
53             \&_result_handler,
54 5 50       995 %{ $self->_ignore_for_package->{ $package_stmt->namespace } || {} },
  5         90  
55             );
56              
57 3         127 return $exit_code, undef, @msgs;
58             }
59              
60             sub _build_ignore_for_package {
61 6     6   70 my $self = shift;
62              
63 6 50       44 return {} unless $self->_has_ignore_file;
64              
65 0         0 my %vars;
66             my %regexes;
67              
68 0         0 my $file = path( $self->ignore_file );
69 0         0 my @lines = $file->lines( { chomp => 1 } );
70 0         0 for my $line (@lines) {
71 0 0       0 next unless $line =~ /\S/;
72              
73 0         0 my ( $package, $ignore ) = split( /\s*=\s*/, $line );
74 0 0 0     0 unless ( defined $package && defined $ignore ) {
75 0         0 die 'Invalid line in ' . $self->ignore_file . ": $line\n";
76             }
77              
78 0 0       0 if ( $ignore =~ m{^qr} ) {
79 0         0 local $@ = undef;
80             ## no critic (BuiltinFunctions::ProhibitStringyEval)
81 0         0 $ignore = eval $ignore;
82             ## use critic
83 0 0       0 die $@ if $@;
84              
85 0         0 push @{ $regexes{$package} }, $ignore;
  0         0  
86             }
87             else {
88 0         0 push @{ $vars{$package} }, $ignore;
  0         0  
89             }
90             }
91              
92 0         0 my %ignore;
93 0         0 for my $package ( keys %regexes ) {
94 0         0 my @re = @{ $regexes{$package} };
  0         0  
95             $ignore{$package}{ignore_if} = sub {
96 0     0   0 my $check = shift;
97 0         0 for my $re (@re) {
98 0 0       0 return 1 if $check =~ /$re/;
99             }
100 0         0 return 0;
101 0         0 };
102             }
103              
104 0         0 for my $package ( keys %vars ) {
105 0         0 $ignore{$package}{ignore_vars}{$_} = 1 for @{ $vars{$package} };
  0         0  
106             }
107              
108 0         0 return \%ignore;
109             }
110              
111             sub _result_handler {
112 3     3   646008 shift;
113 3         46 my $exit_code = shift;
114 3         17 my $results = shift;
115              
116 3         13 my @errors = map { $_->[1] } grep { $_->[0] eq 'diag' } @{$results};
  8         34  
  11         75  
  3         131  
117 3         147 return $exit_code, @errors;
118             }
119              
120             1;
121              
122             =pod
123              
124             =encoding UTF-8
125              
126             =head1 NAME
127              
128             App::perlvars - CLI tool to detect unused variables in Perl modules
129              
130             =head1 VERSION
131              
132             version 0.000003
133              
134             =head1 DESCRIPTION
135              
136             You probably don't want to use this class directly. See L for
137             documentation on how to use the command line interface.
138              
139             =head2 ignore_file
140              
141             The path to a file containing a list of variables to ignore on a per-package
142             basis. The pattern is C or C
143             regex/>. For example:
144              
145             Local::Unused = $unused
146             Local::Unused = $one
147             Local::Unused = $two
148             Local::Unused = qr/^\$.*hree$/
149              
150             =head2 validate_file
151              
152             Path to a file which will be validated. Returns an exit code, an error message
153             and a list of unused variables.
154              
155             =head1 AUTHOR
156              
157             Olaf Alders
158              
159             =head1 COPYRIGHT AND LICENSE
160              
161             This software is copyright (c) 2022 by MaxMind, Inc.
162              
163             This is free software; you can redistribute it and/or modify it under
164             the same terms as the Perl 5 programming language system itself.
165              
166             =cut
167              
168             __END__