File Coverage

blib/lib/App/PerlNitpick/Rule/RemoveUnusedImport.pm
Criterion Covered Total %
statement 74 80 92.5
branch 13 22 59.0
condition 9 16 56.2
subroutine 10 11 90.9
pod 0 3 0.0
total 106 132 80.3


line stmt bran cond sub pod time code
1             package App::PerlNitpick::Rule::RemoveUnusedImport;
2             # ABSTRACT: Remove unused import
3              
4             =encoding UTF-8
5              
6             =head1 DESCRIPTION
7              
8             This nitpicking rule removes imports that are explicitly put there, but
9             not used in the same file.
10              
11             For example, C<Dumper> is not used this simple program:
12              
13             use Data::Dumper 'Dumper';
14             print 42;
15              
16             And it will be removed by this program.
17              
18             =cut
19              
20 1     1   239429 use Moose;
  1         485156  
  1         7  
21 1     1   9365 use PPI::Document;
  1         117606  
  1         50  
22 1     1   502 use PPIx::Utils qw(is_function_call);
  1         15762  
  1         1253  
23              
24             has idx => (
25             is => 'rw',
26             required => 0,
27             );
28              
29             sub rewrite {
30 2     2 0 19869 my ($self, $doc) = @_;
31              
32 2         9 $self->_build_idx($doc);
33 2         13 my @violations = $self->find_violations($doc);
34 2         5 for my $tuple (@violations) {
35 3         6 my ($word, $import) = @$tuple;
36 3         12 my @args_literal = $import->{expr_qw}->literal;
37 3         172 my @new_args_literal = grep { $_ ne $word } @args_literal;
  5         13  
38              
39 3 50       9 if (@new_args_literal == 0) {
40 0         0 $import->{expr_qw}{content} = 'qw()';
41 0         0 $import->{expr_qw}{sections}[0]{size} = length($import->{expr_qw}{content});
42             } else {
43             # These 3 lines should probably be moved to the internal of PPI::Token::QuoteLike::Word
44 3         30 $import->{expr_qw}{content} =~ s/\s ${word} \s/ /gsx;
45 3         23 $import->{expr_qw}{content} =~ s/\b ${word} \s//gsx;
46 3         22 $import->{expr_qw}{content} =~ s/\s ${word} \b//gsx;
47 3         28 $import->{expr_qw}{content} =~ s/\b ${word} \b//gsx;
48 3         11 $import->{expr_qw}{sections}[0]{size} = length($import->{expr_qw}{content});
49              
50 3         11 my @new_args_literal = $import->{expr_qw}->literal;
51 3 50       132 if (@new_args_literal == 0) {
52 0         0 $import->{expr_qw}{content} = 'qw()';
53 0         0 $import->{expr_qw}{sections}[0]{size} = length($import->{expr_qw}{content});
54             }
55             }
56             }
57              
58 2         8 return $doc;
59             }
60              
61             sub _build_idx {
62 2     2   5 my ($self, $doc) = @_;
63 2         6 my $idx = {
64             used_count => {},
65             };
66              
67 2 50   44   5 for my $el (@{ $doc->find( sub { $_[1]->isa('PPI::Token::Word') }) ||[]}) {
  2         15  
  44         567  
68 9 100 66     263 unless ($el->parent->isa('PPI::Statement::Include') && (!$el->sprevious_sibling || $el->sprevious_sibling eq "use")) {
      66        
69 5         30 $idx->{used_count}{"$el"}++;
70 5 100 66     26 if ($el =~ /::/ && is_function_call($el)) {
71 1         442 my ($module_name, $func_name) = $el =~ m/\A(.+)::([^:]+)\z/;
72 1         18 $idx->{used_count}{$module_name}++;
73 1         6 $idx->{used_count}{$func_name}++;
74             }
75             }
76             }
77 2         92 $self->idx($idx);
78 2         3 return $idx;
79             }
80              
81             sub looks_like_unused {
82 0     0 0 0 my ($self, $module_name) = @_;
83 0         0 return ! $self->idx->{used_count}{$module_name};
84             }
85              
86             sub find_violations {
87 2     2 0 7 my ($self, $elem) = @_;
88              
89 2         4 my %imported;
90 2         4 my %is_special = map { $_ => 1 } qw(MouseX::Foreign);
  2         9  
91              
92 2   50 44   27 my $include_statements = $elem->find(sub { $_[1]->isa('PPI::Statement::Include') }) || [];
  44         550  
93 2         32 for my $st (@$include_statements) {
94 2 50       9 next unless $st->type eq 'use';
95 2         65 my $included_module = $st->module;
96 2 50 33     69 next if $included_module =~ /\A[a-z0-9:]+\Z/ || $is_special{"$included_module"};
97              
98 2 50   12   12 my $expr_qw = $st->find( sub { $_[1]->isa('PPI::Token::QuoteLike::Words'); }) or next;
  12         143  
99              
100 2 50       31 if (@$expr_qw == 1) {
101 2         7 my $expr = $expr_qw->[0];
102              
103 2         5 my $expr_str = "$expr";
104              
105             # Remove the quoting characters.
106 2         12 substr($expr_str, 0, 3) = '';
107 2         4 substr($expr_str, -1, 1) = '';
108              
109 2         21 my @words = split ' ', $expr_str;
110 2         13 for my $w (@words) {
111 4 50       16 next if $w =~ /\A [:\-\+\$@%]/x;
112 4   50     6 push @{ $imported{$w} //=[] }, {
  4         31  
113             statement => $st,
114             expr_qw => $expr,
115             };
116             }
117             }
118             }
119              
120 2         6 my %used;
121 2 50   44   4 for my $el_word (@{ $elem->find( sub { $_[1]->isa('PPI::Token::Word') }) ||[]}) {
  2         9  
  44         517  
122 9         58 $used{"$el_word"}++;
123             }
124              
125 2         12 my @violations;
126 2         12 my @to_report = grep { !$used{$_} } (sort keys %imported);
  4         12  
127              
128 2         5 for my $tok (@to_report) {
129 3         7 for my $import (@{ $imported{$tok} }) {
  3         6  
130 3         9 push @violations, [ $tok, $import ];
131             }
132             }
133              
134 2         10 return @violations;
135             }
136              
137             1;