File Coverage

blib/lib/App/PerlNitpick/Rule/RemoveUnusedImport.pm
Criterion Covered Total %
statement 76 80 95.0
branch 14 22 63.6
condition 9 16 56.2
subroutine 10 11 90.9
pod 0 3 0.0
total 109 132 82.5


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   227795 use Moose;
  1         473713  
  1         6  
21 1     1   7914 use PPI::Document;
  1         110391  
  1         44  
22 1     1   507 use PPIx::Utils qw(is_function_call);
  1         14978  
  1         1147  
23              
24             has idx => (
25             is => 'rw',
26             required => 0,
27             );
28              
29             sub rewrite {
30 3     3 0 20731 my ($self, $doc) = @_;
31              
32 3         12 $self->_build_idx($doc);
33 3         36 my @violations = $self->find_violations($doc);
34 3         7 for my $tuple (@violations) {
35 3         7 my ($word, $import) = @$tuple;
36 3         12 my @args_literal = $import->{expr_qw}->literal;
37 3         162 my @new_args_literal = grep { $_ ne $word } @args_literal;
  4         11  
38              
39 3 100       10 if (@new_args_literal == 0) {
40 2         5 $import->{expr_qw}{content} = 'qw()';
41 2         7 $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 1         13 $import->{expr_qw}{content} =~ s/\s ${word} \s/ /gsx;
45 1         20 $import->{expr_qw}{content} =~ s/\b ${word} \s//gsx;
46 1         12 $import->{expr_qw}{content} =~ s/\s ${word} \b//gsx;
47 1         9 $import->{expr_qw}{content} =~ s/\b ${word} \b//gsx;
48 1         5 $import->{expr_qw}{sections}[0]{size} = length($import->{expr_qw}{content});
49              
50 1         5 my @new_args_literal = $import->{expr_qw}->literal;
51 1 50       46 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 3         10 return $doc;
59             }
60              
61             sub _build_idx {
62 3     3   6 my ($self, $doc) = @_;
63 3         9 my $idx = {
64             used_count => {},
65             };
66              
67 3 50   52   6 for my $el (@{ $doc->find( sub { $_[1]->isa('PPI::Token::Word') }) ||[]}) {
  3         22  
  52         665  
68 11 100 66     372 unless ($el->parent->isa('PPI::Statement::Include') && (!$el->sprevious_sibling || $el->sprevious_sibling eq "use")) {
      66        
69 5         34 $idx->{used_count}{"$el"}++;
70 5 100 66     26 if ($el =~ /::/ && is_function_call($el)) {
71 1         382 my ($module_name, $func_name) = $el =~ m/\A(.+)::([^:]+)\z/;
72 1         13 $idx->{used_count}{$module_name}++;
73 1         5 $idx->{used_count}{$func_name}++;
74             }
75             }
76             }
77 3         137 $self->idx($idx);
78 3         7 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 3     3 0 8 my ($self, $elem) = @_;
88              
89 3         4 my %imported;
90 3         7 my %is_special = map { $_ => 1 } qw(MouseX::Foreign);
  3         14  
91              
92 3   50 52   18 my $include_statements = $elem->find(sub { $_[1]->isa('PPI::Statement::Include') }) || [];
  52         609  
93 3         42 for my $st (@$include_statements) {
94 3 50       11 next unless $st->type eq 'use';
95 3         92 my $included_module = $st->module;
96 3 50 33     92 next if $included_module =~ /\A[a-z0-9:]+\Z/ || $is_special{"$included_module"};
97              
98 3 50   18   19 my $expr_qw = $st->find( sub { $_[1]->isa('PPI::Token::QuoteLike::Words'); }) or next;
  18         201  
99              
100 3 50       41 if (@$expr_qw == 1) {
101 3         6 my $expr = $expr_qw->[0];
102              
103 3         7 my $expr_str = "$expr";
104              
105             # Remove the quoting characters.
106 3         17 substr($expr_str, 0, 3) = '';
107 3         5 substr($expr_str, -1, 1) = '';
108              
109 3         11 my @words = split ' ', $expr_str;
110 3         7 for my $w (@words) {
111 4 50       14 next if $w =~ /\A [:\-\+\$@%]/x;
112 4   50     8 push @{ $imported{$w} //=[] }, {
  4         40  
113             statement => $st,
114             expr_qw => $expr,
115             };
116             }
117             }
118             }
119              
120 3         7 my %used;
121 3 50   52   6 for my $el_word (@{ $elem->find( sub { $_[1]->isa('PPI::Token::Word') }) ||[]}) {
  3         13  
  52         598  
122 11         86 $used{"$el_word"}++;
123             }
124              
125 3         16 my @violations;
126 3         12 my @to_report = grep { !$used{$_} } (sort keys %imported);
  4         15  
127              
128 3         8 for my $tok (@to_report) {
129 3         4 for my $import (@{ $imported{$tok} }) {
  3         7  
130 3         10 push @violations, [ $tok, $import ];
131             }
132             }
133              
134 3         14 return @violations;
135             }
136              
137             1;