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