File Coverage

blib/lib/Perl/Critic/Policy/TooMuchCode/ProhibitUnusedInclude.pm
Criterion Covered Total %
statement 79 80 98.7
branch 27 32 84.3
condition 14 15 93.3
subroutine 13 14 92.8
pod 3 7 42.8
total 136 148 91.8


line stmt bran cond sub pod time code
1             package Perl::Critic::Policy::TooMuchCode::ProhibitUnusedInclude;
2              
3 4     4   3611 use strict;
  4         11  
  4         123  
4 4     4   21 use warnings;
  4         8  
  4         140  
5 4     4   24 use Scalar::Util qw(refaddr);
  4         8  
  4         224  
6 4     4   27 use Perl::Critic::Utils;
  4         8  
  4         89  
7 4     4   3708 use parent 'Perl::Critic::Policy';
  4         11  
  4         30  
8              
9 0     0 1 0 sub default_themes { return qw( maintenance ) }
10 19     19 1 147980 sub applies_to { return 'PPI::Document' }
11              
12             sub supported_parameters {
13             return (
14             +{
15 22     22 0 75501 name => 'ignore',
16             description => 'List of modules to be disregarded. Separated by whitespaces.',
17             behavior => 'string list',
18             }
19             )
20             }
21              
22             #---------------------------------------------------------------------------
23              
24             use constant {
25             ## Some modules works like pragmas -- their very existence in the code implies that they are used.
26             PRAGMATIST => {
27 20         70 map { $_ => 1 }
28             qw(
29             Moose
30             Mouse
31             Moo
32             Mo
33             Test::NoWarnings
34             )
35             },
36              
37             TRY_FAMILY => {
38 20         67 map { $_ => 1 }
39             qw(Try::Tiny Try::Catch Try::Lite TryCatch Try)
40             },
41              
42             ## These are the modules that, when used, the module name itself appears in the code.
43             USE_BY_MODULE_NAME => {
44 4         15 map { $_ => 1 }
  20         4879  
45             qw(Hijk HTTP::Tiny HTTP::Lite LWP::UserAgent File::Spec)
46             },
47              
48             ## this mapping fines a set of modules with behaviour that introduce
49             ## new words as subroutine names or method names when they are `use`ed
50             ## without arguments.
51             #### for mod in $(perlbrew list-modules) Test2::V0; do perl -M${mod} -l -e 'if (my @e = grep /\A\w+\z/, (@'$mod'::EXPORT) ) { print "### \x27'$mod'\x27 => [qw(@e)],"; }' \; 2>/dev/null | grep '^### ' | cut -c 5- ; done
52             DEFAULT_EXPORT => {
53             'App::ModuleBuildTiny' => [qw(modulebuildtiny)],
54             'B::Hooks::EndOfScope' => [qw(on_scope_end)],
55             'Carp::Assert' => [qw(assert affirm should shouldnt DEBUG assert affirm should shouldnt DEBUG)],
56             'Carp::Assert::More' => [qw(assert_all_keys_in assert_arrayref assert_coderef assert_defined assert_empty assert_exists assert_fail assert_hashref assert_in assert_integer assert_is assert_isa assert_isa_in assert_isnt assert_lacks assert_like assert_listref assert_negative assert_negative_integer assert_nonblank assert_nonempty assert_nonnegative assert_nonnegative_integer assert_nonref assert_nonzero assert_nonzero_integer assert_numeric assert_positive assert_positive_integer assert_undefined assert_unlike)],
57             'Class::Method::Modifiers' => [qw(before after around)],
58             'Compress::Raw::Bzip2' => [qw(BZ_RUN BZ_FLUSH BZ_FINISH BZ_OK BZ_RUN_OK BZ_FLUSH_OK BZ_FINISH_OK BZ_STREAM_END BZ_SEQUENCE_ERROR BZ_PARAM_ERROR BZ_MEM_ERROR BZ_DATA_ERROR BZ_DATA_ERROR_MAGIC BZ_IO_ERROR BZ_UNEXPECTED_EOF BZ_OUTBUFF_FULL BZ_CONFIG_ERROR)],
59             'Compress::Raw::Zlib' => [qw(ZLIB_VERSION ZLIB_VERNUM OS_CODE MAX_MEM_LEVEL MAX_WBITS Z_ASCII Z_BEST_COMPRESSION Z_BEST_SPEED Z_BINARY Z_BLOCK Z_BUF_ERROR Z_DATA_ERROR Z_DEFAULT_COMPRESSION Z_DEFAULT_STRATEGY Z_DEFLATED Z_ERRNO Z_FILTERED Z_FIXED Z_FINISH Z_FULL_FLUSH Z_HUFFMAN_ONLY Z_MEM_ERROR Z_NEED_DICT Z_NO_COMPRESSION Z_NO_FLUSH Z_NULL Z_OK Z_PARTIAL_FLUSH Z_RLE Z_STREAM_END Z_STREAM_ERROR Z_SYNC_FLUSH Z_TREES Z_UNKNOWN Z_VERSION_ERROR WANT_GZIP WANT_GZIP_OR_ZLIB crc32 adler32 DEF_WBITS)],
60             'Cookie::Baker' => [qw(bake_cookie crush_cookie)],
61             'Cpanel::JSON::XS' => [qw(encode_json decode_json to_json from_json)],
62             'Crypt::RC4' => [qw(RC4)],
63             'DBIx::DSN::Resolver::Cached' => [qw(dsn_resolver)],
64             'DBIx::DisconnectAll' => [qw(dbi_disconnect_all)],
65             'Data::Clone' => [qw(clone)],
66             'Data::Compare' => [qw(Compare)],
67             'Data::Dump' => [qw(dd ddx)],
68             'Data::NestedParams' => [qw(expand_nested_params collapse_nested_params)],
69             'Data::UUID' => [qw(NameSpace_DNS NameSpace_OID NameSpace_URL NameSpace_X500)],
70             'Data::Validate::Domain' => [qw(is_domain is_hostname is_domain_label)],
71             'Data::Validate::IP' => [qw(is_ip is_ipv4 is_ipv6 is_innet_ipv4 is_multicast_ipv4 is_testnet_ipv4 is_anycast_ipv4 is_loopback_ipv4 is_private_ipv4 is_unroutable_ipv4 is_linklocal_ipv4 is_public_ipv4 is_loopback_ipv6 is_orchid_ipv6 is_special_ipv6 is_multicast_ipv6 is_private_ipv6 is_linklocal_ipv6 is_ipv4_mapped_ipv6 is_documentation_ipv6 is_teredo_ipv6 is_discard_ipv6 is_public_ipv6 is_linklocal_ip is_loopback_ip is_multicast_ip is_private_ip is_public_ip)],
72             'Data::Walk' => [qw(walk walkdepth)],
73             'Devel::CheckCompiler' => [qw(check_c99 check_c99_or_exit check_compile)],
74             'Devel::CheckLib' => [qw(assert_lib check_lib_or_exit check_lib)],
75             'Devel::GlobalDestruction' => [qw(in_global_destruction)],
76             'Dist::CheckConflicts' => [qw(conflicts check_conflicts calculate_conflicts dist)],
77             'Email::MIME::ContentType' => [qw(parse_content_type parse_content_disposition)],
78             'Encode' => [qw(decode decode_utf8 encode encode_utf8 str2bytes bytes2str encodings find_encoding find_mime_encoding clone_encoding)],
79             'Eval::Closure' => [qw(eval_closure)],
80             'ExtUtils::MakeMaker' => [qw(WriteMakefile prompt os_unsupported)],
81             'File::HomeDir' => [qw(home)],
82             'File::Listing' => [qw(parse_dir)],
83             'File::Path' => [qw(mkpath rmtree)],
84             'File::ShareDir::Install' => [qw(install_share delete_share)],
85             'File::Which' => [qw(which)],
86             'File::Zglob' => [qw(zglob)],
87             'File::pushd' => [qw(pushd tempd)],
88             'Graphics::ColorUtils' => [qw(rgb2yiq yiq2rgb rgb2cmy cmy2rgb rgb2hls hls2rgb rgb2hsv hsv2rgb)],
89             'HTML::Escape' => [qw(escape_html)],
90             'HTTP::Date' => [qw(time2str str2time)],
91             'HTTP::Negotiate' => [qw(choose)],
92             'IO::All' => [qw(io)],
93             'IO::HTML' => [qw(html_file)],
94             'IO::Socket::SSL' => [qw(SSL_WANT_READ SSL_WANT_WRITE SSL_VERIFY_NONE SSL_VERIFY_PEER SSL_VERIFY_FAIL_IF_NO_PEER_CERT SSL_VERIFY_CLIENT_ONCE SSL_OCSP_NO_STAPLE SSL_OCSP_TRY_STAPLE SSL_OCSP_MUST_STAPLE SSL_OCSP_FAIL_HARD SSL_OCSP_FULL_CHAIN GEN_DNS GEN_IPADD)],
95             'IPC::Run3' => [qw(run3)],
96             'JSON' => [qw(from_json to_json jsonToObj objToJson encode_json decode_json)],
97             'JSON::MaybeXS' => [qw(encode_json decode_json JSON)],
98             'JSON::PP' => [qw(encode_json decode_json from_json to_json)],
99             'JSON::Types' => [qw(number string bool)],
100             'JSON::XS' => [qw(encode_json decode_json)],
101             'LWP::MediaTypes' => [qw(guess_media_type media_suffix)],
102             'Lingua::JA::Regular::Unicode' => [qw(hiragana2katakana alnum_z2h alnum_h2z space_z2h katakana2hiragana katakana_h2z katakana_z2h space_h2z)],
103             'Locale::Currency::Format' => [qw(currency_format currency_name currency_set currency_symbol decimal_precision decimal_separator thousands_separator FMT_NOZEROS FMT_STANDARD FMT_COMMON FMT_SYMBOL FMT_HTML FMT_NAME SYM_UTF SYM_HTML)],
104             'Log::Minimal' => [qw(critf critff warnf warnff infof infoff debugf debugff croakf croakff ddf)],
105             'MIME::Charset' => [qw(body_encoding canonical_charset header_encoding output_charset body_encode encoded_header_len header_encode)],
106             'Math::Round' => [qw(round nearest)],
107             'Module::Build::Tiny' => [qw(Build Build_PL)],
108             'Module::Find' => [qw(findsubmod findallmod usesub useall setmoduledirs)],
109             'Module::Functions' => [qw(get_public_functions)],
110             'Module::Spy' => [qw(spy_on)],
111             'PLON' => [qw(encode_plon decode_pson)],
112             'Path::Class' => [qw(file dir)],
113             'Path::Tiny' => [qw(path)],
114             'Proc::Wait3' => [qw(wait3)],
115             'Readonly' => [qw(Readonly)],
116             'SQL::QueryMaker' => [qw(sql_op sql_raw sql_and sql_or sql_in sql_not_in sql_ne sql_not sql_like sql_is_not_null sql_is_null sql_ge sql_gt sql_eq sql_lt sql_le sql_between sql_not_between)],
117             'Smart::Args' => [qw(args args_pos)],
118             'Socket' => [qw(PF_802 PF_AAL PF_APPLETALK PF_CCITT PF_CHAOS PF_CTF PF_DATAKIT PF_DECnet PF_DLI PF_ECMA PF_GOSIP PF_HYLINK PF_IMPLINK PF_INET PF_INET6 PF_ISO PF_KEY PF_LAST PF_LAT PF_LINK PF_MAX PF_NBS PF_NIT PF_NS PF_OSI PF_OSINET PF_PUP PF_ROUTE PF_SNA PF_UNIX PF_UNSPEC PF_USER PF_WAN PF_X25 AF_802 AF_AAL AF_APPLETALK AF_CCITT AF_CHAOS AF_CTF AF_DATAKIT AF_DECnet AF_DLI AF_ECMA AF_GOSIP AF_HYLINK AF_IMPLINK AF_INET AF_INET6 AF_ISO AF_KEY AF_LAST AF_LAT AF_LINK AF_MAX AF_NBS AF_NIT AF_NS AF_OSI AF_OSINET AF_PUP AF_ROUTE AF_SNA AF_UNIX AF_UNSPEC AF_USER AF_WAN AF_X25 SOCK_DGRAM SOCK_RAW SOCK_RDM SOCK_SEQPACKET SOCK_STREAM SOL_SOCKET SO_ACCEPTCONN SO_ATTACH_FILTER SO_BACKLOG SO_BROADCAST SO_CHAMELEON SO_DEBUG SO_DETACH_FILTER SO_DGRAM_ERRIND SO_DOMAIN SO_DONTLINGER SO_DONTROUTE SO_ERROR SO_FAMILY SO_KEEPALIVE SO_LINGER SO_OOBINLINE SO_PASSCRED SO_PASSIFNAME SO_PEERCRED SO_PROTOCOL SO_PROTOTYPE SO_RCVBUF SO_RCVLOWAT SO_RCVTIMEO SO_REUSEADDR SO_REUSEPORT SO_SECURITY_AUTHENTICATION SO_SECURITY_ENCRYPTION_NETWORK SO_SECURITY_ENCRYPTION_TRANSPORT SO_SNDBUF SO_SNDLOWAT SO_SNDTIMEO SO_STATE SO_TYPE SO_USELOOPBACK SO_XOPEN SO_XSE IP_HDRINCL IP_OPTIONS IP_RECVOPTS IP_RECVRETOPTS IP_RETOPTS IP_TOS IP_TTL MSG_BCAST MSG_BTAG MSG_CTLFLAGS MSG_CTLIGNORE MSG_CTRUNC MSG_DONTROUTE MSG_DONTWAIT MSG_EOF MSG_EOR MSG_ERRQUEUE MSG_ETAG MSG_FASTOPEN MSG_FIN MSG_MAXIOVLEN MSG_MCAST MSG_NOSIGNAL MSG_OOB MSG_PEEK MSG_PROXY MSG_RST MSG_SYN MSG_TRUNC MSG_URG MSG_WAITALL MSG_WIRE SHUT_RD SHUT_RDWR SHUT_WR INADDR_ANY INADDR_BROADCAST INADDR_LOOPBACK INADDR_NONE SCM_CONNECT SCM_CREDENTIALS SCM_CREDS SCM_RIGHTS SCM_TIMESTAMP SOMAXCONN IOV_MAX UIO_MAXIOV sockaddr_family pack_sockaddr_in unpack_sockaddr_in sockaddr_in pack_sockaddr_in6 unpack_sockaddr_in6 sockaddr_in6 pack_sockaddr_un unpack_sockaddr_un sockaddr_un inet_aton inet_ntoa)],
119             'String::Format' => [qw(stringf)],
120             'String::ShellQuote' => [qw(shell_quote shell_quote_best_effort shell_comment_quote)],
121             'Sub::Name' => [qw(subname)],
122             'Sub::Quote' => [qw(quote_sub unquote_sub quoted_from_sub qsub)],
123             'Sub::Retry' => [qw(retry)],
124             'Teng::Plugin::TextTable' => [qw(draw_text_table)],
125             'Test2::V0' => [qw(ok pass fail diag note todo skip plan skip_all done_testing bail_out intercept context gen_event def do_def cmp_ok warns warning warnings no_warnings subtest can_ok isa_ok DOES_ok set_encoding imported_ok not_imported_ok ref_ok ref_is ref_is_not mock mocked dies lives try_ok is like isnt unlike match mismatch validator hash array bag object meta meta_check number float rounded within string subset bool in_set not_in_set check_set item field call call_list call_hash prop check all_items all_keys all_vals all_values etc end filter_items T F D DF E DNE FDNE U event fail_events exact_ref)],
126             'Test::BinaryData' => [qw(is_binary)],
127             'Test::Deep' => [qw(Isa blessed obj_isa all any array array_each arrayelementsonly arraylength arraylengthonly bag bool cmp_bag cmp_deeply cmp_methods cmp_set code eq_deeply hash hash_each hashkeys hashkeysonly ignore isa listmethods methods noclass none noneof num re reftype regexpmatches regexponly regexpref regexprefonly scalarrefonly scalref set shallow str subbagof subhashof subsetof superbagof superhashof supersetof useclass)],
128             'Test::Differences' => [qw(eq_or_diff eq_or_diff_text eq_or_diff_data unified_diff context_diff oldstyle_diff table_diff)],
129             'Test::Exception' => [qw(dies_ok lives_ok throws_ok lives_and)],
130             'Test::Fatal' => [qw(exception)],
131             'Test::Kantan' => [qw(Feature Scenario Given When Then subtest done_testing setup teardown describe context it before_each after_each expect ok diag ignore spy_on skip_all)],
132             'Test::LongString' => [qw(is_string is_string_nows like_string unlike_string contains_string lacks_string)],
133             'Test::Mock::Guard' => [qw(mock_guard)],
134             'Test::More' => [qw(ok use_ok require_ok is isnt like unlike is_deeply cmp_ok skip todo todo_skip pass fail eq_array eq_hash eq_set plan done_testing can_ok isa_ok new_ok diag note explain subtest BAIL_OUT)],
135             'Test::Object' => [qw(object_ok)],
136             'Test::Output' => [qw(output_like stderr_from output_isnt stderr_is stdout_unlike combined_isnt output_is combined_is stdout_is stderr_isnt stdout_like combined_unlike stderr_unlike output_from combined_from stdout_isnt output_unlike combined_like stdout_from stderr_like)],
137             'Test::Simple' => [qw(ok)],
138             'Test::Spec' => [qw(runtests describe xdescribe context xcontext it xit they xthey before after around yield spec_helper share shared_examples_for it_should_behave_like)],
139             'Test::Stub' => [qw(stub make_stub)],
140             'Test::SubCalls' => [qw(sub_track sub_calls sub_reset sub_reset_all)],
141             'Test::TempDir::Tiny' => [qw(tempdir in_tempdir)],
142             'Test::TCP' => [qw(empty_port test_tcp wait_port)],
143             'Test::Warn' => [qw(warning_is warnings_are warning_like warnings_like warnings_exist)],
144             'Text::Diff' => [qw(diff)],
145             'Time::Piece' => [qw(localtime gmtime)],
146             'Try::Tiny' => [qw(try catch finally)],
147             'URI::Find' => [qw(find_uris)],
148             'URL::Builder' => [qw(build_url build_url_utf8)],
149             'UUID::Tiny' => [qw(UUID_NIL UUID_NS_DNS UUID_NS_URL UUID_NS_OID UUID_NS_X500 UUID_V1 UUID_V3 UUID_V4 UUID_V5 UUID_SHA1_AVAIL create_UUID create_UUID_as_string is_UUID_string UUID_to_string string_to_UUID version_of_UUID time_of_UUID clk_seq_of_UUID equal_UUIDs)],
150             'Want' => [qw(want rreturn lnoreturn)],
151             'XML::Simple' => [qw(XMLin XMLout)],
152             'YAML' => [qw(Dump Load)],
153             }
154 4     4   4108 };
  4         32  
155              
156             sub violates {
157 19     19 1 211 my ( $self, $elem, $doc ) = @_;
158              
159             my @includes = grep {
160 25         647 my $mod = $_->module;
161 25 100 100     675 !$_->pragma && $mod && (! $self->{_ignore}{$mod})
162 19 50       42 } @{ $doc->find('PPI::Statement::Include') ||[] };
  19         58  
163              
164 19 100       669 return () unless @includes;
165              
166 17 50       45 return () if grep { $_->module eq 'Module::Functions' } @includes;
  23         181  
167              
168 17         385 my %uses;
169 17         70 $self->gather_uses_pragmatists(\@includes, $doc, \%uses);
170 17         405 $self->gather_uses_try_family(\@includes, $doc, \%uses);
171 17         61 $self->gather_uses_generic(\@includes, $doc, \%uses);
172              
173             return map {
174 9         239 $self->violation(
175             "Unused include: " . $_->module,
176             "A module is `use`-ed but not really consumed in other places in the code",
177             $_
178             )
179             } grep {
180 17         310 my $mod = $_->module;
  23         62  
181 23 100 100     656 (! $uses{refaddr($_)}) && (TRY_FAMILY->{$mod} || DEFAULT_EXPORT->{$mod} || USE_BY_MODULE_NAME->{$mod})
      100        
182             } @includes;
183             }
184              
185             sub gather_uses_pragmatists {
186 17     17 0 46 my ( $self, $includes, $doc, $uses ) = @_;
187 17         39 for (grep { PRAGMATIST->{$_->module} } @$includes) {
  23         258  
188 2         57 my $r = refaddr($_);
189 2         7 $uses->{$r} = 1;
190             }
191             }
192              
193             sub gather_uses_generic {
194 17     17 0 39 my ( $self, $includes, $doc, $uses ) = @_;
195              
196 17 50       33 my @words = grep { ! $_->statement->isa('PPI::Statement::Include') } @{ $doc->find('PPI::Token::Word') || []};
  78         1137  
  17         51  
197 17         247 my @mods = grep { !$uses->{$_} } map { $_->module } @$includes;
  23         423  
  23         188  
198              
199 17         31 my @inc_without_args;
200 17         46 for my $inc (@$includes) {
201 23 100       78 if ($inc->arguments) {
202 1         38 my $r = refaddr($inc);
203 1         4 $uses->{$r} = -1;
204             } else {
205 22         608 push @inc_without_args, $inc;
206             }
207             }
208              
209 17         39 for my $word (@words) {
210 32         320 for my $inc (@inc_without_args) {
211 45         338 my $mod = $inc->module;
212 45         981 my $r = refaddr($inc);
213 45 100       132 next if $uses->{$r};
214 30 100 100     65 $uses->{$r} = 1 if ($word->content =~ /\A $mod (\z|::)/x) || (grep { $_ eq $word } @{DEFAULT_EXPORT->{$mod} ||[]}) || ("$word" eq "$inc");
  18 100 66     203  
  25         490  
215             }
216             }
217             }
218              
219             sub gather_uses_try_family {
220 17     17 0 39 my ( $self, $includes, $doc, $uses ) = @_;
221              
222 17         53 my @uses_tryish_modules = grep { TRY_FAMILY->{$_->module} } @$includes;
  23         175  
223 17 100       403 return unless @uses_tryish_modules;
224              
225 6         21 my $has_try_block = 0;
226 6 100   145   16 for my $try_keyword (@{ $doc->find(sub { $_[1]->isa('PPI::Token::Word') && $_[1]->content eq 'try' }) ||[]}) {
  6 100       32  
  145         1857  
227 2 50       35 my $try_block = $try_keyword->snext_sibling or next;
228 2 50       71 next unless $try_block->isa('PPI::Structure::Block');
229 2         7 $has_try_block = 1;
230 2         3 last;
231             }
232 6 100       81 return unless $has_try_block;
233              
234 2         12 $uses->{refaddr($_)} = 1 for @uses_tryish_modules;
235             }
236              
237             1;
238              
239             =encoding utf-8
240              
241             =head1 NAME
242              
243             TooMuchCode::ProhibitUnusedInclude -- Find unused include statements.
244              
245             =head1 DESCRIPTION
246              
247             This critic policy scans for unused include statement according to their documentation.
248              
249             For example, L<Try::Tiny> implicitly introduce a C<try> subroutine that takes a block. Therefore, a
250             lonely C<use Try::Tiny> statement without a C<try { .. }> block somewhere in its scope is considered
251             to be an "Unused Include".
252              
253             Notice: This module use a hard-coded list of commonly-used CPAN
254             modules with symbols exported from them. Although it is relatively
255             static, it needs to be revised from time to time.
256              
257             =cut