Skip to content

Commit

Permalink
Attempt to find unused imports
Browse files Browse the repository at this point in the history
  • Loading branch information
JRaspass committed Jan 10, 2017
1 parent 5bef21a commit b062a70
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 5 deletions.
54 changes: 49 additions & 5 deletions lib/Plint.pm
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,10 @@ sub plint {
local ( @ARGV, $/ ) = @_;

my ( $tokens, @errors ) = Compiler::Lexer::tokenize( $lexer, scalar <> );
my @vars = {};

for ( my $i = 0; my $token = $tokens->[$i]; $i++ ) {
my ( @vars, %imports ) = {};

TOKEN: for ( my $i = 0; my $token = $tokens->[$i]; $i++ ) {
my $type = $token->{type};

if ( $type == T_Return ) {
Expand All @@ -105,9 +106,42 @@ sub plint {
&& $token->{data} eq 'undef'
&& ( $j == $#$tokens || $tokens->[ ++$j ]{type} != T_Comma );
}
elsif ( $type == T_UseDecl ) {
my $pkg = $tokens->[ ++$i ]{data};

# Build the used package name up.
$pkg .= $tokens->[ ++$i ]{data} . $tokens->[ ++$i ]{data}
while $tokens->[ $i + 1 ]{type} == T_NamespaceResolver;

# Pragmas are often false positives.
next if $pkg =~ /^[a-z]+$/;

# Skip a few false positives.
next if $pkg eq 'Regexp::Common'
|| $pkg eq 'feature'
|| $pkg eq 'lib';

$type = $tokens->[ $i + 1 ]{type};

my @imports
= $type == T_RawString || $type == T_String
? $tokens->[ $i + 1 ]{data}
: $type == T_RegList
? split ' ', $tokens->[ $i + 3 ]{data}
: ();

# Skip the whole import list if any look like non functions.
for (@imports) {
next TOKEN if !/^[a-z_]+$/ || $_ eq 'import';
}

@imports{@imports} = ("$pkg at line $token->{line}") x @imports;
}
elsif ( $type == T_Key ) {
delete $imports{ $token->{data} };
}
elsif ( $type == T_BuiltinFunc ) {
my $data = $token->{data};
delete $imports{ my $data = $token->{data} };

push @errors,
qq/\$_ should be omitted when calling "$data" at line $token->{line}./
Expand Down Expand Up @@ -303,8 +337,18 @@ sub plint {
}
}

push @errors, qq/"$_" is never read from, declared line $vars[-1]{$_}./
for sort keys %{ $vars[-1] };
while ( my ( $import, $pkg ) = each %imports ) {
push @errors, qq/Unused import of "$import" from $pkg./
}

while ( my ( $var, $line ) = each %{ $vars[-1] } ) {
push @errors, qq/"$var" is never read from, declared line $line./;
}

# Sort errors by line number.
@errors = sort {
( $a =~ /(\d+)\.$/ )[0] <=> ( $b =~ /(\d+)\.$/ )[0] || $a cmp $b
} @errors;

\@errors, $tokens->[-1]{line};
}
Expand Down
25 changes: 25 additions & 0 deletions t/unused-import.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use t;

t q(use Foo 'bar'), 'Unused import of "bar" from Foo at line 1.';
t q(use Foo 'bar'; bar);

t q(use Foo "bar"), 'Unused import of "bar" from Foo at line 1.';
t q(use Foo "bar"; bar);

t q(use Foo qw/bar/), 'Unused import of "bar" from Foo at line 1.';
t q(use Foo qw/bar/; bar);

# Funcs with the same name as builtins are fun.
t q(use Time::HiRes 'time'),
'Unused import of "time" from Time::HiRes at line 1.';
t q(use Time::HiRes 'time'; time);

# False positives.
t q(use lib 'lib');
t q(use warnings 'all');

t q(use Exporter 'import');
t q(use Getopt::Long qw/:config bundling/);
t q(use Regexp::Common 'number');

done;

0 comments on commit b062a70

Please sign in to comment.