| 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; |