File: ProhibitUnusedImport.pm

package info (click to toggle)
libperl-critic-toomuchcode-perl 0.19-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 220 kB
  • sloc: perl: 776; makefile: 2
file content (214 lines) | stat: -rw-r--r-- 6,739 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
package Perl::Critic::Policy::TooMuchCode::ProhibitUnusedImport;

use strict;
use warnings;
use Perl::Critic::Utils;
use parent 'Perl::Critic::Policy';

use Perl::Critic::TooMuchCode;
use Perl::Critic::Policy::Variables::ProhibitUnusedVariables;

sub default_themes       { return qw( maintenance )     }
sub applies_to           { return 'PPI::Document' }
sub supported_parameters {
    return (
        {
            name        => 'ignored_modules',
            description => 'Modules which will be ignored by this policy.',
            behavior    => 'string list',
            list_always_present_values => [
                'Exporter',
                'Getopt::Long',
                'Git::Sub',
                'MooseX::Foreign',
                'MouseX::Foreign',
                'Test::Needs',
                'Test::Requires',
                'Test::RequiresInternet',
            ],
        },
        {
            name        => 'moose_type_modules',
            description => 'Modules which import Moose-like types.',
            behavior    => 'string list',
            list_always_present_values => [
                'MooseX::Types::Moose',
                'MooseX::Types::Common::Numeric',
                'MooseX::Types::Common::String',
            ],
        },
    );
}

#---------------------------------------------------------------------------

sub violates {
    my ( $self, $elem, $doc ) = @_;

    my $moose_types =  $self->{_moose_type_modules};

    my %imported;
    $self->gather_imports_generic( \%imported, $elem, $doc );

    my %used;
    for my $el_word (
        @{
            $elem->find(
                sub {
                    $_[1]->isa('PPI::Token::Word')
                        || ( $_[1]->isa('PPI::Token::Symbol')
                        && $_[1]->symbol_type eq '&' );
                }
                )
                || []
        }
    ) {
        if ( $el_word->isa('PPI::Token::Symbol') ) {
            $el_word =~ s{^&}{};
        }
        $used{"$el_word"}++;
    }

    Perl::Critic::TooMuchCode::__get_symbol_usage(\%used, $doc);

    my @violations;
    my @to_report = grep { !$used{$_} } (keys %imported);

    # Maybe filter out Moose types.
    if ( @to_report ) {
        my %to_report = map { $_ => 1 } @to_report;

        for my $import ( keys %to_report ) {
            if ( exists $used{ 'is_' . $import } || exists $used { 'to_' . $import }
                && exists $moose_types->{$imported{$import}->[0]} ) {
                delete $to_report{$import};
            }
        }
        @to_report = keys %to_report;
    }
    @to_report = sort { $a cmp $b } @to_report;

    for my $tok (@to_report) {
        for my $inc_mod (@{ $imported{$tok} }) {
            push @violations, $self->violation( "Unused import: $tok", "A token is imported but not used in the same code.", $inc_mod );
        }
    }

    return @violations;
}

sub gather_imports_generic {
    my ( $self, $imported, $elem, $doc ) = @_;

    my $is_ignored =  $self->{_ignored_modules};
    my $include_statements = $elem->find(sub { $_[1]->isa('PPI::Statement::Include') && !$_[1]->pragma }) || [];
    for my $st (@$include_statements) {
        next if $st->schild(0) eq 'no';
        my $expr_qw = $st->find( sub { $_[1]->isa('PPI::Token::QuoteLike::Words'); }) or next;

        my $included_module = $st->schild(1);
        next if exists $is_ignored->{$included_module};

        if (@$expr_qw == 1) {
            my $expr = $expr_qw->[0];
            my @words = $expr_qw->[0]->literal;
            for my $w (@words) {
                next if $w =~ /\A [:\-\+]/x;

                push @{ $imported->{$w} //=[] }, $included_module;
            }
        }
    }
}

1;

=encoding utf-8

=head1 NAME

TooMuchCode::ProhibitUnusedImport -- Find unused imports

=head1 DESCRIPTION

An "import" is a subroutine brought by a C<use> statement.

From the documentation of L<use>, there are several forms of calling `use`. This policy scans for the following two forms:

    use Module VERSION LIST
    use Module LIST

... and only the one with LIST written in C<qw()>.

Conventionally the LIST after c<use Module> is known as arguments and
conventionally when it is written with C<qw()>, the LIST is treated as
a import list -- which is a list of symbols that becomes avaiable in
the current namespace.

For example, the word C<baz> in the following statement is one of such:

    use Foo qw( baz );

Symbols in the import list are often subroutine names or variable
names. If they are not used in the following program, they do not neet
to be imported.

Although an experienced perl programmer would know that the
description above is only true by convention, there are many modules
on CPAN that already follows such convetion. Which is a good one to
follow, and I recommend you to follow.

This policy checks only import lists written in C<qw()>, other forms
are ignored, or rather, too complicated to be correctly supported.

The syntax of C<Importer> module is also supported, but only the ones
with C<qw()> at the end.

    use Importer 'Foo' => qw( baz );

Modules with non-trivial form of arguments may have nothing to with
symbol-importing. But it might be used with a C<qw()> LIST at the end.
Should you wish to do so, you may let chose to let perlcritic to
ignore certain modules by setting the C<ignored_modules> in
C<.perlcriticrc>. For example:

    [TooMuchCode::ProhibitUnusedImport]
    ignored_modules = Git::Sub Regexp::Common

Alternatively, you may choose not to write the module arguments as a
C<qw()> list.

=head2 Moose Types

Moose Types can also be imported, but their symbols may not be used
as-is. Instead, some other helper functions are generated with names
based on the Type. For example:

    use My::Type::Library::Numeric qw( PositiveInt );
    use My::Type::Library::String qw( LowerCaseStr );

    my $foo = 'Bar';
    my $ok  = is_PositiveInt($foo);
    my $lower = to_LowerCaseStr($foo);

The module C<My::Type::Library::Numeric> exports
C<is_PositiveInt> as well as C<PositiveInt>. While C<PositiveInt> is
not directly used in the following code, is should be considered as
being used. Similar for C<LowerCaseStr>.

When importing from a Type library, subroutines named like C<is_*> and C<to_*>
are not in the import list, but they are also imported.

By default, the following modules are treated as Type libraries

    * MooseX::Types::Moose
    * MooseX::Types::Common::Numeric
    * MooseX::Types::Common::String

The list can be grown by including your module names to the
C<moose_type_modules> in the C<.perlcriticrc>:

    [TooMuchCode::ProhibitUnusedImport]
    moose_type_modules = My::Type::Library::Numeric My::Type::Library::String

=cut