File Coverage

blib/lib/App/perlimports/Document.pm
Criterion Covered Total %
statement 307 369 83.2
branch 103 140 73.5
condition 69 87 79.3
subroutine 55 61 90.1
pod 3 3 100.0
total 537 660 81.3


line stmt bran cond sub pod time code
1             package App::perlimports::Document;
2              
3 73     73   1216952 use Moo;
  73         570949  
  73         376  
4 73     73   139506 use utf8;
  73         1061  
  73         410  
5              
6             our $VERSION = '0.000050';
7              
8 73     73   34286 use App::perlimports::Annotations ();
  73         338  
  73         2496  
9 73     73   39370 use App::perlimports::ExportInspector ();
  73         377  
  73         2791  
10 73     73   40799 use App::perlimports::Include ();
  73         387  
  73         2896  
11 73     73   714 use App::perlimports::Sandbox ();
  73         199  
  73         1745  
12 73     73   429 use File::Basename qw( fileparse );
  73         193  
  73         8539  
13 73     73   597 use List::Util qw( any uniq );
  73         193  
  73         5322  
14 73     73   525 use Module::Runtime qw( module_notional_filename );
  73         209  
  73         932  
15 73     73   3838 use MooX::StrictConstructor;
  73         208  
  73         934  
16 73     73   146854 use Path::Tiny qw( path );
  73         868354  
  73         4591  
17 73     73   723 use PPI::Document ();
  73         227  
  73         1932  
18 73         4878 use PPIx::Utils::Classification qw(
19             is_function_call
20             is_hash_key
21             is_method_call
22 73     73   504 );
  73         220  
23 73     73   517 use Ref::Util qw( is_plain_arrayref is_plain_hashref );
  73         197  
  73         3820  
24 73     73   542 use Sub::HandlesVia;
  73         182  
  73         867  
25 73     73   62030 use Text::Diff ();
  73         503308  
  73         2222  
26 73     73   646 use Try::Tiny qw( catch try );
  73         1586  
  73         4831  
27 73     73   606 use Types::Standard qw( ArrayRef Bool HashRef InstanceOf Maybe Object Str );
  73         190  
  73         807  
28              
29             with 'App::perlimports::Role::Logger';
30              
31             has _annotations => (
32             is => 'ro',
33             isa => InstanceOf ['App::perlimports::Annotations'],
34             lazy => 1,
35             default => sub {
36             return App::perlimports::Annotations->new(
37             ppi_document => shift->ppi_document );
38             },
39             );
40              
41             has _cache => (
42             is => 'ro',
43             isa => Bool,
44             init_arg => 'cache',
45             lazy => 1,
46             default => 0,
47             );
48              
49             has _cache_dir => (
50             is => 'ro',
51             isa => InstanceOf ['Path::Tiny'],
52             lazy => 1,
53             builder => '_build_cache_dir',
54             );
55              
56             has _filename => (
57             is => 'ro',
58             isa => Str,
59             init_arg => 'filename',
60             required => 1,
61             );
62              
63             has _ignore_modules => (
64             is => 'ro',
65             isa => HashRef,
66             init_arg => 'ignore_modules',
67             default => sub { +{} },
68             );
69              
70             has _ignore_modules_pattern => (
71             is => 'ro',
72             isa => ArrayRef [Str],
73             init_arg => 'ignore_modules_pattern',
74             default => sub { [] },
75             );
76              
77             has includes => (
78             is => 'ro',
79             isa => ArrayRef [Object],
80             handles_via => 'Array',
81             handles => {
82             all_includes => 'elements',
83             },
84             lazy => 1,
85             builder => '_build_includes',
86             );
87              
88             has _inspectors => (
89             is => 'ro',
90             isa => HashRef [ Maybe [Object] ],
91             handles_via => 'Hash',
92             handles => {
93             all_inspector_names => 'keys',
94             _get_inspector_for => 'get',
95             _has_inspector_for => 'exists',
96             _set_inspector_for => 'set',
97             },
98             lazy => 1,
99             default => sub { +{} },
100             );
101              
102             has interpolated_symbols => (
103             is => 'ro',
104             isa => HashRef,
105             lazy => 1,
106             builder => '_build_interpolated_symbols',
107             );
108              
109             has json => (
110             is => 'ro',
111             isa => Bool,
112             lazy => 1,
113             default => 0,
114             );
115              
116             has _json_encoder => (
117             is => 'ro',
118             isa => InstanceOf ['Cpanel::JSON::XS'],
119             lazy => 1,
120             default => sub {
121             require Cpanel::JSON::XS;
122             return Cpanel::JSON::XS->new;
123             },
124             );
125              
126             has lint => (
127             is => 'ro',
128             isa => Bool,
129             lazy => 1,
130             default => 0,
131             );
132              
133             has my_own_inspector => (
134             is => 'ro',
135             isa => Maybe [ InstanceOf ['App::perlimports::ExportInspector'] ],
136             lazy => 1,
137             builder => '_build_my_own_inspector',
138             );
139              
140             has never_exports => (
141             is => 'ro',
142             isa => HashRef,
143             lazy => 1,
144             builder => '_build_never_exports',
145             );
146              
147             has _never_export_modules => (
148             is => 'ro',
149             isa => ArrayRef [Str],
150             init_arg => 'never_export_modules',
151             predicate => '_has_never_export_modules',
152             );
153              
154             has original_imports => (
155             is => 'ro',
156             isa => HashRef,
157             handles_via => 'Hash',
158             handles => {
159             _reset_original_import => 'set',
160             },
161             lazy => 1,
162             builder => '_build_original_imports',
163             );
164              
165             has _padding => (
166             is => 'ro',
167             isa => Bool,
168             init_arg => 'padding',
169             default => 1,
170             );
171              
172             has ppi_document => (
173             is => 'ro',
174             isa => Object,
175             lazy => 1,
176             builder => '_build_ppi_document',
177             );
178              
179             has possible_imports => (
180             is => 'ro',
181             isa => ArrayRef [Object],
182             lazy => 1,
183             builder => '_build_possible_imports',
184             );
185              
186             has _ppi_selection => (
187             is => 'ro',
188             isa => Object,
189             init_arg => 'ppi_selection',
190             lazy => 1,
191             default => sub { $_[0]->ppi_document },
192             );
193              
194             has _preserve_duplicates => (
195             is => 'ro',
196             isa => Bool,
197             init_arg => 'preserve_duplicates',
198             default => 1,
199             );
200              
201             has _preserve_unused => (
202             is => 'ro',
203             isa => Bool,
204             init_arg => 'preserve_unused',
205             default => 1,
206             );
207              
208             has _sub_exporter_export_list => (
209             is => 'ro',
210             isa => ArrayRef,
211             handles_via => 'Array',
212             handles => {
213             sub_exporter_export_list => 'elements',
214             },
215             lazy => 1,
216             builder => '_build_sub_exporter_export_list',
217             );
218              
219             has _sub_names => (
220             is => 'ro',
221             isa => HashRef,
222             handles_via => 'Hash',
223             handles => {
224             is_sub_name => 'exists',
225             },
226             lazy => 1,
227             builder => '_build_sub_names',
228             );
229              
230             has _tidy_whitespace => (
231             is => 'ro',
232             isa => Bool,
233             init_arg => 'tidy_whitespace',
234             lazy => 1,
235             default => sub { 1 },
236             );
237              
238             has _verbose => (
239             is => 'ro',
240             isa => Bool,
241             init_arg => 'verbose',
242             default => sub { 0 },
243             );
244              
245             around BUILDARGS => sub {
246             my ( $orig, $class, @args ) = @_;
247              
248             my %args = @args;
249             if ( my $modules = delete $args{ignore_modules} ) {
250             my %modules = map { $_ => 1 } @{$modules};
251             $args{ignore_modules} = \%modules;
252             }
253              
254             if ( my $selection = delete $args{selection} ) {
255             $args{ppi_selection} = PPI::Document->new( \$selection );
256             }
257              
258             return $class->$orig(%args);
259             };
260              
261             my %default_ignore = (
262             'Carp::Always' => 1,
263             'Constant::Generate' => 1,
264             'Data::Printer' => 1,
265             'DDP' => 1,
266             'Devel::Confess' => 1,
267             'Encode::Guess' => 1,
268             'Env' => 1, # see t/env.t
269             'Exception::Class' => 1,
270             'Exporter' => 1,
271             'Exporter::Lite' => 1,
272             'Feature::Compat::Try' => 1,
273             'Filter::Simple' => 1,
274             'Git::Sub' => 1,
275             'HTTP::Message::PSGI' => 1, # HTTP::Request::(to|from)_psgi
276             'Import::Into' => 1,
277             'MLDBM' => 1,
278             'Modern::Perl' => 1,
279             'Mojo::Base' => 1,
280             'Mojo::Date' => 1,
281             'Mojolicious::Lite' => 1,
282             'Moo' => 1,
283             'Moo::Role' => 1,
284             'Moose' => 1,
285             'Moose::Exporter' => 1,
286             'Moose::Role' => 1,
287             'MooseX::NonMoose' => 1,
288             'MooseX::Role::Parameterized' => 1,
289             'MooseX::SemiAffordanceAccessor' => 1,
290             'MooseX::StrictConstructor' => 1,
291             'MooseX::TraitFor::Meta::Class::BetterAnonClassNames' => 1,
292             'MooseX::Types' => 1,
293             'MooX::StrictConstructor' => 1,
294             'namespace::autoclean' => 1,
295             'PerlIO::gzip' => 1,
296             'Regexp::Common' => 1,
297             'Sort::ByExample' => 1,
298             'Struct::Dumb' => 1,
299             'Sub::Exporter' => 1,
300             'Sub::Exporter::Progressive' => 1,
301             'Sub::HandlesVia' => 1,
302             'Syntax::Keyword::Try' => 1,
303             'Term::Size::Any' => 1,
304             'Test2::Util::HashBase' => 1,
305             'Test::Exception' => 1,
306             'Test::Needs' => 1,
307             'Test::Number::Delta' => 1,
308             'Test::Pod' => 1,
309             'Test::Pod::Coverage' => 1,
310             'Test::Requires::Git' => 1,
311             'Test::RequiresInternet' => 1,
312             'Test::Warnings' => 1,
313             'Test::Whitespaces' => 1,
314             'Test::XML' => 1,
315             'Types::Standard' => 1,
316             'URI::QueryParam' => 1,
317             );
318              
319             # Funky stuff could happen with inner packages.
320             sub _build_my_own_inspector {
321 32     32   1790 my $self = shift;
322             my $pkgs
323             = $self->ppi_document->find(
324 32 100   1763   621 sub { $_[1]->isa('PPI::Statement::Package') && $_[1]->file_scoped } );
  1763         20611  
325              
326 32 100 66     753 if ( !$pkgs || $pkgs->[0]->namespace eq 'main' ) {
327 31         752 return;
328             }
329              
330 1         36 my $pkg = $pkgs->[0];
331              
332             # file_scoped() doesn't seem to be very reliable, so let's just try a crude
333             # check to see if this is a package we might actually find on disk before
334             # we try to require it.
335 1         3 my $notional_file
336             = fileparse( module_notional_filename( $pkg->namespace ) );
337 1         128 my $provided_file = fileparse( $self->_filename );
338 1 50       6 return unless $notional_file eq $provided_file;
339              
340 1         8 return App::perlimports::ExportInspector->new(
341             logger => $self->logger,
342             module_name => $pkg->namespace,
343             );
344             }
345              
346             sub _build_includes {
347 52     52   1400 my $self = shift;
348              
349             # version() returns a value if this a dependency on a version of Perl, e.g
350             # use 5.006;
351             # require 5.006;
352             #
353             # We check for type so that we can filter out undef types or "no".
354              
355             ## no critic (Subroutines::ProhibitCallsToUnexportedSubs)
356             return $self->_ppi_selection->find(
357             sub {
358 2015     2015   260357 $_[1]->isa('PPI::Statement::Include')
359             && !$_[1]->pragma # no pragmas
360             && !$_[1]->version # Perl version requirement
361             && $_[1]->type
362             && ( $_[1]->type eq 'use'
363             || $_[1]->type eq 'require' )
364             && !$self->_is_ignored( $_[1] )
365             && !$self->_has_import_switches( $_[1]->module )
366             && !App::perlimports::Sandbox::eval_pkg(
367             $_[1]->module,
368             "$_[1]"
369             );
370             }
371 52   100     1153 ) || [];
372             ## use critic
373             }
374              
375             sub _build_possible_imports {
376 34     34   647 my $self = shift;
377             my $before = $self->ppi_document->find(
378             sub {
379 1883     1883   33013 $_[1]->isa('PPI::Token::Word')
380             || $_[1]->isa('PPI::Token::Symbol')
381             || $_[1]->isa('PPI::Token::Label')
382             || $_[1]->isa('PPI::Token::Prototype');
383             }
384 34   50     763 ) || [];
385              
386 34         707 my @after;
387 34         183 for my $word ( @{$before} ) {
  34         133  
388              
389             # Without the sub name check, we accidentally turn
390             # use List::Util ();
391             # sub any { }
392             #
393             # into
394             #
395             # use List::Util qw( any );
396             # sub any {}
397 421 100       1500 next if $self->is_sub_name("$word");
398              
399 408         9337 my $isa_symbol = $word->isa('PPI::Token::Symbol');
400              
401 408 100 100     1417 next if !$isa_symbol && is_method_call($word);
402              
403             # A hash key might, for example, be a variable.
404 404 100 100     27109 if (
      100        
405             !$isa_symbol
406             && !(
407             $word->statement
408             && $word->statement->isa('PPI::Statement::Variable')
409             )
410             && is_hash_key($word)
411             ) {
412 6         703 next;
413             }
414              
415 398         34503 push @after, $word;
416             }
417              
418 34         1197 return \@after;
419             }
420              
421             sub _build_ppi_document {
422 53     53   919 my $self = shift;
423 53         673 return PPI::Document->new( $self->_filename );
424             }
425              
426             # Create a key for every included module.
427             # use Carp;
428             # use Data::Dumper qw( Dumper );
429             # use POSIX ();
430             #
431             # becomes:
432             #
433             # {
434             # Carp => undef,
435             # 'Data::Dumper' => ['Dumper'],
436             # POSIX => [],
437             # }
438             #
439             # The name is a bit of a misnomer. It starts out as a list of original imports,
440             # but with each include that gets processed, this list also gets updated. We do
441             # this so that we can keep track of what previous modules are really importing.
442             # Might not be bad to rename this.
443              
444             sub _build_original_imports {
445 41     41   963 my $self = shift;
446              
447             # We're missing requires which could be followed by an import.
448             my $found = $self->ppi_document->find(
449             sub {
450 2195     2195   58059 $_[1]->isa('PPI::Statement::Include')
451             && !$_[1]->pragma # no pragmas
452             && !$_[1]->version # Perl version requirement
453             && $_[1]->type
454             && $_[1]->type eq 'use';
455             }
456 41   50     799 ) || [];
457              
458 41         892 my %imports;
459              
460 41         125 for my $include ( @{$found} ) {
  41         169  
461 64         255 my $pkg = $include->module;
462 64 100       2113 $imports{$pkg} = undef unless exists $imports{$pkg};
463              
464             # this is probably wrong
465             #next if $self->_is_ignored($pkg);
466              
467             # If a module has been included multiple times, we want to have a
468             # cumulative tally of what has been explicitly imported.
469 64         427 my $found_for_include = _imports_for_include($include);
470 64 100       292 if ($found_for_include) {
471 29 100       148 if ( $imports{$pkg} ) {
472 2         4 push @{ $imports{$pkg} }, @{$found_for_include};
  2         6  
  2         7  
473             }
474             else {
475 27         109 $imports{$pkg} = $found_for_include;
476             }
477             }
478             }
479              
480 41         1161 return \%imports;
481             }
482              
483             sub _build_sub_exporter_export_list {
484 32     32   468 my $self = shift;
485              
486             my $sub_ex = $self->ppi_document->find(
487             sub {
488 1763     1763   23964 $_[1]->isa('PPI::Statement::Include')
489             && $_[1]->module eq 'Sub::Exporter';
490             }
491 32   100     682 ) || [];
492 32 100       649 return [] unless @{$sub_ex};
  32         262  
493              
494 1         2 my @found;
495 1         2 for my $include ( @{$sub_ex} ) {
  1         4  
496 1         5 my @arguments = $include->arguments;
497 1         47 for my $arg (@arguments) {
498 3 100       14 if ( $arg->isa('PPI::Structure::Constructor') ) {
499             ## no critic (BuiltinFunctions::ProhibitStringyEval)
500 1         5 my $thing = eval $arg;
501 1 50       8 if ( is_plain_hashref($thing) ) {
502 1 50       4 if ( is_plain_arrayref( $thing->{exports} ) ) {
503 1         2 push @found, @{ $thing->{exports} };
  1         6  
504             }
505             }
506             }
507             }
508             }
509              
510 1         11 return [ uniq @found ];
511             }
512              
513             sub _imports_for_include {
514 100     100   1310 my $include = shift;
515              
516 100         279 my $imports = undef;
517              
518 100         736 for my $child ( $include->schildren ) {
519 402 100 66     6632 if ( $child->isa('PPI::Structure::List')
520             && !defined $imports ) {
521 14         148 $imports = [];
522             }
523 402 100 100     2668 if ( !$child->isa('PPI::Token::QuoteLike::Words')
524             && !$child->isa('PPI::Token::Quote::Single') ) {
525 351         949 next;
526             }
527 51 100       307 if ( defined $imports ) {
528 4         10 push( @{$imports}, $child->literal );
  4         14  
529             }
530             else {
531 47         335 $imports = [ $child->literal ];
532             }
533             }
534 100         534 return $imports;
535             }
536              
537             sub _extract_symbols_from_snippet {
538 15     15   133 my $snippet = shift;
539 15 50       58 return () unless defined $snippet;
540              
541             # Restore line breaks and tabs
542 15         55 $snippet =~ s{\\n}{\n}g;
543 15         46 $snippet =~ s{\\t}{\t}g;
544              
545 15         125 my $doc = PPI::Document->new( \$snippet );
546 15 100       22206 return () unless defined $doc;
547              
548             my @symbols
549 14 100       41 = map { $_ . q{} } @{ $doc->find('PPI::Token::Symbol') || [] };
  6         2508  
  14         66  
550              
551 14   100     3030 my $casts = $doc->find('PPI::Token::Cast') || [];
552 14         4814 for my $cast ( @{$casts} ) {
  14         54  
553              
554             # Optimistically avoid misinterpreting regex assertions as casts
555             # We don't want to match on "A" in the following example:
556             # if ( $thing =~ m{ \A b }x ) { ... }
557 5 100       374 next if $cast eq '\\';
558              
559 3         104 my $full_cast = $cast . $cast->snext_sibling;
560 3         442 my $cast_as_doc = PPI::Document->new( \$full_cast );
561             push @symbols,
562 1         580 map { $_ . q{} }
563 3 100       6099 @{ $cast_as_doc->find('PPI::Token::Symbol') || [] };
  3         20  
564              
565 3   50     1138 my $words = $cast_as_doc->find('PPI::Token::Word') || [];
566              
567             ## Turn ${FOO} into $FOO
568 3 100 66     1620 if ( $words
      100        
569             && scalar @$words == 1
570             && $full_cast =~ m/([\$\@\%])\{$words->[0]}/ ) {
571 1         45 push @symbols, $1 . $words->[0];
572 1         15 next;
573             }
574              
575             # This could likely be a source of false positives.
576 2         33 for my $word (@$words) {
577 3 100       441 push @symbols, "$word" if is_function_call($word);
578             }
579             }
580              
581 14         708 return @symbols;
582             }
583              
584             sub _unnest_quotes {
585 12     12   29 my $self = shift;
586 12         31 my $token = shift;
587 12         44 my @words = @_;
588              
589 12 100 100     113 if ( !$token->isa('PPI::Token::Quote')
590             || $token->isa('PPI::Token::Quote::Single') ) {
591 3         35 return @words;
592             }
593              
594 9         66 push @words, _extract_symbols_from_snippet( $token->string );
595              
596 9         764 my $doc = PPI::Document->new( \$token->string );
597 9 100       12443 return @words unless $doc;
598              
599 8         38 my $quotes = $doc->find('PPI::Token::Quote');
600 8 100       3462 return @words unless $quotes;
601              
602 3         11 for my $q (@$quotes) {
603 3         14 push @words, _extract_symbols_from_snippet("$q");
604 3         131 push @words, $self->_unnest_quotes($q);
605             }
606              
607 3         154 return @words;
608             }
609              
610             sub _build_interpolated_symbols {
611 33     33   891 my $self = shift;
612 33         87 my @symbols;
613              
614 33         110 for my $token (
615             @{
616             $self->ppi_document->find(
617             sub {
618 1799 50 100 1799   55489 ( $_[1]->isa('PPI::Token::Quote')
      66        
      66        
619             && !$_[1]->isa('PPI::Token::Quote::Single') )
620             || $_[1]->isa('PPI::Token::Quote::Interpolate')
621             || $_[1]->isa('PPI::Token::QuoteLike::Regexp')
622             || $_[1]->isa('PPI::Token::Regexp');
623             }
624             )
625 33 100       627 || []
626             }
627             ) {
628 9 100 66     273 if ( $token->isa('PPI::Token::Regexp')
629             || $token->isa('PPI::Token::QuoteLike::Regexp') ) {
630 1         18 for my $snippet (
631             $token->get_match_string,
632             $token->get_substitute_string,
633             ) {
634 1         48 push @symbols, _extract_symbols_from_snippet($snippet);
635             }
636             }
637              
638 9         215 push @symbols, $self->_unnest_quotes($token);
639             }
640              
641             # Crude hack to catch vars like ${FOO_BAR} in heredocs.
642 33         1167 for my $heredoc (
643             @{
644             $self->ppi_document->find(
645             sub {
646 1799     1799   20192 $_[1]->isa('PPI::Token::HereDoc');
647             }
648             )
649 33 100       818 || []
650             }
651             ) {
652 3         106 my $content = join "\n", $heredoc->heredoc;
653 3 100       30 next if $heredoc =~ m{'};
654 2         24 push @symbols, _extract_symbols_from_snippet($content);
655             }
656              
657             # Catch vars like ${FOO_BAR}. This is probably not good enough.
658 33         829 for my $cast (
659             @{
660             $self->ppi_document->find(
661 1799     1799   20313 sub { $_[1]->isa('PPI::Token::Cast'); }
662             )
663 33 100       831 || []
664             }
665             ) {
666 2 50 33     41 if ( !$cast->snext_sibling
667             || !$cast->snext_sibling->isa('PPI::Structure::Block') ) {
668 0         0 next;
669             }
670              
671 2         139 my $sigil = $cast . q{};
672 2         14 my $sibling = $cast->snext_sibling . q{};
673 2 50       136 if ( $sibling =~ m/{(\w+)}/ ) {
674 2         15 push @symbols, $sigil . $1;
675             }
676             }
677 33         835 my %symbols = map { $_ => 1 } @symbols;
  12         59  
678 33         742 return \%symbols;
679             }
680              
681             # Returns a HashRef of modules which will always be converted to avoid imports.
682             # This is mostly for speed and a matter of convenience so that we don't have to
683             # examine modules (like strictly Object Oriented modules) which we know will
684             # not have anything to export.
685              
686             sub _build_never_exports {
687 36     36   693 my $self = shift;
688              
689 36         421 my %modules = (
690             'App::perlimports::Include' => 1,
691             'File::Spec' => 1,
692             'HTTP::Daemon' => 1,
693             'HTTP::Headers' => 1,
694             'HTTP::Response' => 1,
695             'HTTP::Tiny' => 1,
696             'LWP::UserAgent' => 1,
697             'URI' => 1,
698             'WWW::Mechanize' => 1,
699             );
700              
701 36 50       364 if ( $self->_has_never_export_modules ) {
702 0         0 for my $module ( @{ $self->_never_export_modules } ) {
  0         0  
703 0         0 $modules{$module} = 1;
704             }
705             }
706              
707 36         701 return \%modules;
708             }
709              
710             sub _build_sub_names {
711 34     34   80868 my $self = shift;
712              
713 34         95 my %sub_names;
714 34         94 for my $sub (
715             @{
716             $self->ppi_document->find(
717 1883     1883   21382 sub { $_[1]->isa('PPI::Statement::Sub') }
718             )
719 34 100       955 || []
720             }
721             ) {
722 11         192 my @children = $sub->schildren;
723 11 50 33     247 if ( $children[0] eq 'sub'
724             && $children[1]->isa('PPI::Token::Word') ) {
725 11         323 $sub_names{"$children[1]"} = 1;
726             }
727             }
728              
729 34         1170 return \%sub_names;
730             }
731              
732             sub _has_import_switches {
733 54     54   2205 my $self = shift;
734 54         175 my $module_name = shift;
735              
736             # If switches are being passed to import, we can't guess as what is correct
737             # here.
738             #
739             # Getopt::Long uses a leading colon rather than a dash. This overrides
740             # Exporter's defaults. You would normally assume that :config is an export
741             # tag, but instead it's something entirely different.
742             #
743             # use Getopt::Long qw(:config no_ignore_case bundling);
744             #
745             # We will leave this case as broken for the time being. I'm not sure how
746             # common that invocation is.
747              
748 54 100 66     1286 if ( exists $self->original_imports->{$module_name}
749 39     39   465 && any { $_ =~ m{^[\-]} }
750 54 100       2762 @{ $self->original_imports->{$module_name} || [] } ) {
751 1         17 return 1;
752             }
753 53         1031 return 0;
754             }
755              
756             sub _is_used_fully_qualified {
757 13     13   677 my $self = shift;
758 13         30 my $module_name = shift;
759              
760             # We could tighten this up and check that the word following "::" is a sub
761             # which exists in that package.
762             #
763             # Module::function
764             # Module::->new
765             # isa => ArrayRef[Module::]
766             return 1 if $self->ppi_document->find(
767             sub {
768             (
769 2433 100 100 2433   67923 $_[1]->isa('PPI::Token::Word')
      100        
      100        
770             && (
771             $_[1]->content =~ m{\A${module_name}::[a-zA-Z0-9_]*\z}
772             || ( $_[1]->content eq ${module_name}
773             && $_[1]->snext_sibling eq '->' )
774             )
775             )
776             || ( $_[1]->isa('PPI::Token::Symbol')
777             && $_[1] =~ m{\A[*\$\@\%]+${module_name}::[a-zA-Z0-9_]} );
778             }
779 13 100       330 );
780              
781             # We could combine the regexes, but this is easy to read.
782 4         89 for my $key ( keys %{ $self->interpolated_symbols } ) {
  4         110  
783              
784             # package level variable
785 1 50       55 return 1 if $key =~ m{\A[*\$\@\%]+${module_name}::[a-zA-Z0-9_]+\z};
786              
787             # function
788 0 0       0 return 1 if $key =~ m/\A${module_name}::[a-zA-Z0-9_]+\z/;
789             }
790              
791 3         69 return 0;
792             }
793              
794             sub _is_ignored {
795 75     75   19250 my $self = shift;
796 75         187 my $element = shift;
797              
798             my $res
799             = exists $default_ignore{ $element->module }
800             || exists $self->_ignore_modules->{ $element->module }
801             || $self->_annotations->is_ignored($element)
802             || (
803 0     0   0 any { $element->module =~ /$_/ }
804 75   100     296 grep { $_ } @{ $self->_ignore_modules_pattern || [] }
805             )
806             || ( $self->inspector_for( $element->module )
807             && !$self->inspector_for( $element->module )->evals_ok );
808 73         1175 return $res;
809             }
810              
811             sub inspector_for {
812 196     196 1 6732 my $self = shift;
813 196         434 my $module = shift;
814              
815             # This would produce a warning and no helpful information.
816 196 50       625 return undef if $module eq 'Exporter';
817              
818 196 100       953 if ( $self->_has_inspector_for($module) ) {
819 130         3131 return $self->_get_inspector_for($module);
820             }
821              
822 66 50       3692 if ( $self->_cache ) {
823 0         0 require Sereal::Decoder; ## no perlimports
824 0         0 my $decoder = Sereal::Decoder->new( {} );
825 0         0 my $file = $self->_cache_file_for_module($module);
826 0         0 my $inspector;
827 0 0       0 if ( -e $file ) {
828             try {
829 0     0   0 $inspector = $decoder->decode_from_file($file);
830 0         0 $self->_set_inspector_for( $module, $inspector );
831             }
832             catch {
833 0     0   0 $self->logger->error($_);
834 0         0 };
835 0 0       0 if ($inspector) {
836 0         0 $self->logger->info("Using cached version of $module");
837 0         0 $inspector->set_logger( $self->logger );
838 0         0 return $inspector;
839             }
840             }
841             }
842              
843             try {
844 66     66   5310 $self->_set_inspector_for(
845             $module,
846             App::perlimports::ExportInspector->new(
847             logger => $self->logger,
848             module_name => $module,
849             )
850             );
851             }
852             catch {
853 2     2   22893 $self->logger->info( 'inspector_for' . $_ );
854 0         0 $self->_set_inspector_for( $module, undef );
855 66         3195 };
856              
857 64         422037 return $self->_get_inspector_for($module);
858             }
859              
860             sub tidied_document {
861 41     41 1 6893 return shift->_lint_or_tidy_document;
862             }
863              
864             sub linter_success {
865 1     1 1 100 return shift->_lint_or_tidy_document;
866             }
867              
868             # Kind of on odd interface, but right now we return either a tidied document or
869             # the result of linting. Could probably clean this up at some point, but I'm
870             # not sure yet how much the linting will change.
871             sub _lint_or_tidy_document {
872 42     42   120 my $self = shift;
873              
874 42         142 my $linter_error = 0;
875 42         115 my %processed;
876              
877             INCLUDE:
878 42         256 foreach my $include ( $self->all_includes ) {
879              
880             # If a module is used more than once, that's usually a mistake.
881 42 100 100     3354 if ( !$self->_preserve_duplicates
882             && exists $processed{ $include->module } ) {
883              
884 1 50       61 if ( $self->lint ) {
885 0         0 $self->_warn_diff_for_linter(
886             'has already been used and should be removed',
887             $include,
888             $include->content,
889             q{}
890             );
891 0         0 $linter_error = 1;
892 0         0 next INCLUDE;
893             }
894              
895 1         20 $self->logger->info( $include->module
896             . ' has already been used. Removing at line '
897             . $include->line_number );
898 1         145 _remove_with_trailing_characters($include);
899 1         4 next INCLUDE;
900             }
901              
902 41         365 $self->logger->notice( '📦 ' . "Processing include: $include" );
903              
904             my $e = App::perlimports::Include->new(
905             document => $self,
906             include => $include,
907             logger => $self->logger,
908 41         5583 original_imports => $self->original_imports->{ $include->module },
909             pad_imports => $self->_padding,
910             tidy_whitespace => $self->_tidy_whitespace,
911             );
912 41         2472 my $elem;
913             try {
914 41     41   3255 $elem = $e->formatted_ppi_statement;
915             }
916             catch {
917 0     0   0 my $error = $_;
918 0         0 $self->logger->error( 'Error in ' . $self->_filename );
919 0         0 $self->logger->error( 'Trying to format: ' . $include );
920 0         0 $self->logger->error( 'Error is: ' . $error );
921 41         554 };
922              
923 41 50       7981 next INCLUDE unless $elem;
924              
925             # If this is a module with bare imports which is not used anywhere,
926             # maybe we can just remove it.
927 41 100       300 if ( !$self->_preserve_unused ) {
928 15         73 my @args = $elem->arguments;
929              
930 15 100 100     766 if ( $args[0]
      100        
931             && $args[0] eq '()'
932             && !$self->_is_used_fully_qualified( $include->module ) ) {
933              
934 2 50       44 if ( $self->lint ) {
935 0         0 $self->_warn_diff_for_linter(
936             'appears to be unused and should be removed',
937             $include, $include->content,
938             q{}
939             );
940 0         0 $linter_error = 1;
941 0         0 next INCLUDE;
942             }
943              
944 2         91 $self->logger->info( 'Removing '
945             . $include->module
946             . ' as it appears to be unused' );
947 2         290 _remove_with_trailing_characters($include);
948              
949 2         32 next INCLUDE;
950             }
951             }
952              
953             ## no critic (Subroutines::ProhibitCallsToUnexportedSubs)
954             # Let's see if the import itself might break something
955 39 100       418 if ( my $err
956             = App::perlimports::Sandbox::eval_pkg( $elem->module, "$elem" ) )
957             {
958 1         10 $self->logger->warning(
959             sprintf(
960             'New include (%s) triggers error (%s)', $elem, $err
961             )
962             );
963 1         118 next INCLUDE;
964             }
965             ## use critic
966              
967 38         397 my $inserted = $include->replace($elem);
968 38 50       3534 if ( !$inserted ) {
969 0         0 $self->logger->error( 'Could not insert ' . $elem );
970             }
971             else {
972 38         200 $processed{ $include->module } = 1;
973              
974 38 100       2094 if ( $self->lint ) {
975             my $before = join q{ },
976 2         39 map { $_->content } $include->arguments;
  1         39  
977 2         55 my $after = join q{ }, map { $_->content } $elem->arguments;
  2         74  
978              
979 2 50       43 if ( $before ne $after ) {
980 2         7 $self->_warn_diff_for_linter(
981             'import arguments need tidying',
982             $include,
983             $include->content,
984             $elem->content
985             );
986 2         123 $linter_error = 1;
987 2         16 next INCLUDE;
988             }
989             }
990              
991 36         1485 $self->logger->info("resetting imports for |$elem|");
992              
993             # Now reset original_imports so that we can account for any changes
994             # when processing includes further down the list.
995 36         4560 my $doc = PPI::Document->new( \"$elem" );
996              
997 36 50       46198 if ( !$doc ) {
998 0         0 $self->logger->error("PPI could not parse $elem");
999             }
1000             else {
1001             my $new_include
1002             = $doc->find(
1003 36     288   332 sub { $_[1]->isa('PPI::Statement::Include') } );
  288         3959  
1004              
1005 36         646 $self->_reset_original_import(
1006             $include->module,
1007             _imports_for_include( $new_include->[0] )
1008             );
1009             }
1010             }
1011             }
1012              
1013 42         4486 $self->_maybe_cache_inspectors;
1014              
1015             # We need to do serialize in order to preserve HEREDOCs.
1016             # See https://metacpan.org/pod/PPI::Document#serialize
1017 42 100       1572 return $self->lint ? !$linter_error : $self->_ppi_selection->serialize;
1018             }
1019              
1020             sub _warn_diff_for_linter {
1021 2     2   111 my $self = shift;
1022 2         5 my $reason = shift;
1023 2         4 my $include = shift;
1024 2         5 my $before = shift;
1025 2         4 my $after = shift;
1026 2         5 my $after_deleted = !$after;
1027              
1028 2         5 my $json;
1029             my $justification;
1030              
1031 2 50       45 if ( $self->json ) {
1032              
1033 0         0 my $loc = { start => { line => $include->line_number } };
1034 0         0 my $content = $include->content;
1035 0         0 my @lines = split( m{\n}, $content );
1036              
1037 0 0       0 if ( $lines[0] =~ m{[^\s]} ) {
1038 0         0 $loc->{start}->{column} = @-;
1039             }
1040 0         0 $loc->{end}->{line} = $include->line_number + @lines - 1;
1041 0         0 $loc->{end}->{column} = length( $lines[-1] );
1042              
1043 0         0 $json = {
1044             filename => $self->_filename,
1045             location => $loc,
1046             module => $include->module,
1047             reason => $reason,
1048             };
1049             }
1050             else {
1051 2         65 $justification = sprintf(
1052             '❌ %s (%s) at %s line %i',
1053             $include->module, $reason, $self->_filename, $include->line_number
1054             );
1055             }
1056              
1057 2         134 my $padding = $include->line_number - 1;
1058 2         46 $before = sprintf( "%s%s\n", "\n" x $padding, $before );
1059 2         6 $after = sprintf( "%s%s\n", "\n" x $padding, $after );
1060 2 50       6 chomp $after if $after_deleted;
1061              
1062 2         17 my $diff = Text::Diff::diff(
1063             \$before, \$after,
1064             {
1065             CONTEXT => 0,
1066             STYLE => 'Unified',
1067             }
1068             );
1069              
1070 2 50       974 if ( $self->json ) {
1071 0         0 $json->{diff} = $diff;
1072 0         0 $self->logger->error( $self->_json_encoder->encode($json) );
1073             }
1074             else {
1075 2         30 $self->logger->error($justification);
1076 2         187 $self->logger->error($diff);
1077             }
1078             }
1079              
1080             sub _remove_with_trailing_characters {
1081 3     3   7 my $include = shift;
1082              
1083 3         32 while ( my $next = $include->next_sibling ) {
1084 7 50 66     380 if ( !$next->isa('PPI::Token::Whitespace')
1085             && !$next->isa('PPI::Token::Comment') ) {
1086 0         0 last;
1087             }
1088 7         47 $next->remove;
1089 7 100       364 last if $next eq "\n";
1090             }
1091 3         55 $include->remove;
1092 3         140 return;
1093             }
1094              
1095             sub _build_cache_dir {
1096             my $base_path
1097             = defined $ENV{HOME} && -d path( $ENV{HOME}, '.cache' )
1098 0 0 0 0   0 ? path( $ENV{HOME}, '.cache' )
1099             : path('/tmp');
1100              
1101 0         0 my $cache_dir = $base_path->child( 'perlimports', $VERSION );
1102 0         0 $cache_dir->mkpath;
1103              
1104 0         0 return $cache_dir;
1105             }
1106              
1107             sub _cache_file_for_module {
1108 0     0   0 my $self = shift;
1109 0         0 my $module = shift;
1110              
1111 0         0 return $self->_cache_dir->child($module);
1112             }
1113              
1114             sub _maybe_cache_inspectors {
1115 42     42   125 my $self = shift;
1116 42 50       1152 return unless $self->_cache;
1117              
1118 0           my @names = sort $self->all_inspector_names;
1119 0           $self->logger->info('maybe cache');
1120 0 0         return unless @names;
1121              
1122 0           my $append = 0;
1123 0           require Sereal::Encoder; ## no perlimports
1124 0           my $encoder = Sereal::Encoder->new(
1125             { croak_on_bless => 0, undef_unknown => 1, } );
1126              
1127 0           for my $name ( $self->all_inspector_names ) {
1128 0           my $file = $self->_cache_file_for_module($name);
1129 0 0         next if -e $file;
1130              
1131 0           $self->logger->info("I would like to cache $name at $file");
1132 0           $encoder->encode_to_file(
1133             $file,
1134             $self->inspector_for($name),
1135             $append
1136             );
1137             }
1138 0           return;
1139             }
1140              
1141             1;
1142              
1143             # ABSTRACT: Make implicit imports explicit
1144              
1145             __END__
1146              
1147             =pod
1148              
1149             =encoding UTF-8
1150              
1151             =head1 NAME
1152              
1153             App::perlimports::Document - Make implicit imports explicit
1154              
1155             =head1 VERSION
1156              
1157             version 0.000050
1158              
1159             =head1 MOTIVATION
1160              
1161             This module is to be used internally by L<perlimports>. It shouldn't be relied
1162             upon by anything else.
1163              
1164             =head2 inspector_for( $module_name )
1165              
1166             Returns an L<App::perlimports::ExporterInspector> object for the given module.
1167              
1168             =head2 linter_success
1169              
1170             Returns true if document was linted without errors, otherwise false.
1171              
1172             =head2 tidied_document
1173              
1174             Returns a serialized PPI document with (hopefully) tidy import statements.
1175              
1176             =head1 AUTHOR
1177              
1178             Olaf Alders <olaf@wundercounter.com>
1179              
1180             =head1 COPYRIGHT AND LICENSE
1181              
1182             This software is copyright (c) 2020 by Olaf Alders.
1183              
1184             This is free software; you can redistribute it and/or modify it under
1185             the same terms as the Perl 5 programming language system itself.
1186              
1187             =cut