File Coverage

blib/lib/App/perlimports/Include.pm
Criterion Covered Total %
statement 178 249 71.4
branch 84 134 62.6
condition 59 86 68.6
subroutine 29 32 90.6
pod 0 1 0.0
total 350 502 69.7


line stmt bran cond sub pod time code
1             package App::perlimports::Include;
2              
3 76     76   2026 use Moo;
  76         9116  
  76         696  
4              
5             our $VERSION = '0.000052';
6              
7 76     76   73811 use Data::Dumper qw( Dumper );
  76         431474  
  76         6588  
8 76     76   682 use List::Util qw( any none uniq );
  76         227  
  76         6611  
9 76     76   49935 use Memoize qw( flush_cache memoize );
  76         198681  
  76         5213  
10 76     76   34929 use MooX::StrictConstructor;
  76         375069  
  76         486  
11 76     76   346802 use PPI::Document ();
  76         8857483  
  76         3162  
12 76     76   45505 use PPIx::Utils::Classification qw( is_function_call is_perl_builtin );
  76         1064935  
  76         7821  
13 76     76   42076 use Ref::Util qw( is_plain_arrayref is_plain_hashref );
  76         46015  
  76         5812  
14 76     76   1083 use Sub::HandlesVia;
  76         7220  
  76         971  
15 76     76   212995 use Try::Tiny qw( catch try );
  76         233  
  76         4699  
16 76     76   591 use Types::Standard qw(ArrayRef Bool HashRef InstanceOf Maybe Object Str);
  76         274  
  76         889  
17              
18             with 'App::perlimports::Role::Logger';
19              
20             memoize('is_function_call');
21              
22             sub BUILD {
23 50     50 0 239486 flush_cache('is_function_call');
24             }
25              
26             has _explicit_exports => (
27             is => 'ro',
28             isa => HashRef,
29             handles_via => 'Hash',
30             handles => {
31             _delete_export => 'delete',
32             _explicit_export_count => 'count',
33             _has_explicit_exports => 'count',
34             _import_name => 'get',
35             _is_importable => 'exists',
36             },
37             lazy => 1,
38             builder => '_build_explicit_exports',
39             );
40              
41             has _document => (
42             is => 'ro',
43             isa => InstanceOf ['App::perlimports::Document'],
44             required => 1,
45             init_arg => 'document',
46             );
47              
48             has _export_inspector => (
49             is => 'ro',
50             isa => InstanceOf ['App::perlimports::ExportInspector'],
51             predicate => '_has_export_inspector', # used in test
52             lazy => 1,
53             builder => '_build_export_inspector',
54             );
55              
56             has formatted_ppi_statement => (
57             is => 'ro',
58             isa => InstanceOf ['PPI::Statement::Include'],
59             lazy => 1,
60             builder => '_build_formatted_ppi_statement',
61             );
62              
63             has _ignored_modules => (
64             is => 'ro',
65             isa => ArrayRef,
66             init_arg => 'ignored_modules',
67             predicate => '_has_ignored_modules',
68             );
69              
70             has _imports => (
71             is => 'ro',
72             isa => ArrayRef,
73             lazy => 1,
74             builder => '_build_imports',
75             );
76              
77             has _include => (
78             is => 'ro',
79             isa => InstanceOf ['PPI::Statement::Include'],
80             init_arg => 'include',
81             required => 1,
82             );
83              
84             has _is_ignored => (
85             is => 'ro',
86             isa => Bool,
87             lazy => 1,
88             builder => '_build_is_ignored',
89             );
90              
91             has _isa_test_builder_module => (
92             is => 'ro',
93             isa => Bool,
94             lazy => 1,
95             default => sub { shift->_export_inspector->isa_test_builder },
96             );
97              
98             has _is_translatable => (
99             is => 'ro',
100             isa => Bool,
101             lazy => 1,
102             builder => '_build_is_translatable',
103             documentation => 'Is this a require which can be converted to a use?',
104             );
105              
106             has module_name => (
107             is => 'ro',
108             isa => Maybe [Str],
109             lazy => 1,
110             default => sub { shift->_include->module },
111             );
112              
113             has _original_imports => (
114             is => 'ro',
115             isa => Maybe [ArrayRef],
116             init_arg => 'original_imports',
117             handles_via => 'Array',
118             handles => {
119             _all_original_imports => 'elements',
120             _has_original_imports => 'count',
121             },
122             );
123              
124             has _pad_imports => (
125             is => 'ro',
126             isa => Bool,
127             init_arg => 'pad_imports',
128             default => sub { 1 },
129             );
130              
131             has _tidy_whitespace => (
132             is => 'ro',
133             isa => Bool,
134             init_arg => 'tidy_whitespace',
135             lazy => 1,
136             default => sub { 1 },
137             );
138              
139             has _will_never_export => (
140             is => 'ro',
141             isa => Bool,
142             lazy => 1,
143             default => sub {
144             my $self = shift;
145             return exists $self->_document->never_exports->{ $self->module_name }
146             || $self->_export_inspector->is_oo_class;
147             },
148             );
149              
150             sub _build_export_inspector {
151 49     49   4836 my $self = shift;
152 49         975 return $self->_document->inspector_for( $self->module_name );
153             }
154              
155             # If we have implicit (but not explicit) exports, we will make a best guess at
156             # what gets exported by using the implicit list.
157             sub _build_explicit_exports {
158 47     47   3736 my $self = shift;
159 47 100       955 return $self->_export_inspector->has_explicit_exports
160             ? $self->_export_inspector->explicit_exports
161             : $self->_export_inspector->implicit_exports;
162             }
163              
164             ## no critic (Subroutines::ProhibitExcessComplexity)
165             sub _build_imports {
166 47     47   3190 my $self = shift;
167              
168             # This is not a real symbol, so we should never be looking for it to appear
169             # in the code.
170 47 100       844 $self->_delete_export('verbose') if $self->module_name eq 'Carp';
171              
172 47         1435 my %found;
173              
174             # Stolen from Perl::Critic::Policy::TooMuchCode::ProhibitUnfoundImport
175 47         110 for my $word ( @{ $self->_document->possible_imports } ) {
  47         1032  
176 727 50       3738 next if exists $found{"$word"};
177              
178             # No need to keep looking if we've found everything that can be
179             # imported
180 727 100       4093 last unless $self->_imports_remain( \%found );
181              
182             # We don't want (for instance) pragma names to be confused with
183             # functions.
184             #
185             # ie:
186             # use warnings;
187             # use Test::Warnings; # exports warnings()
188             #
189             # However, we also want to catch function calls in use statements, like
190             # "use lib catfile( 't', 'lib');"
191             #
192             # or
193             #
194             # use Mojo::File qw( curfile );
195             # use lib curfile->sibling('lib')->to_string;
196 723         22210 my $is_function_call = is_function_call($word);
197 723 100 66     123378 if (
      66        
      100        
198             $word->parent
199             && $word->parent->isa('PPI::Statement::Include')
200             && ( !$is_function_call
201             && !( $word->snext_sibling && $word->snext_sibling eq '->' ) )
202             ) {
203 211         15143 next;
204             }
205              
206             # Don't turn "use POSIX ();" into "use POSIX qw( sprintf );"
207             # If it's a function and it's a builtin function and it's either not
208             # included in original_imports or original imports are not implicit
209             # then skip this.
210 512 100 100     8201 if ( defined $self->_original_imports
      100        
      100        
211 292     292   2066 && ( none { $_ eq $word } @{ $self->_original_imports } )
  236         972  
212             && $is_function_call
213             && is_perl_builtin($word) ) {
214 129         4040 next;
215             }
216              
217 383         2665 my @found_import;
218 383         1192 my $isa_symbol = $word->isa('PPI::Token::Symbol');
219              
220             # Don't confuse my @Foo with a use of @Foo which is exported by a module.
221 383 100 100     1083 if ( $isa_symbol && $word->content =~ m{\A(@|%|$)} ) {
222 25         211 my $previous_sibling = $word->sprevious_sibling;
223 25 100 66     675 if ( $previous_sibling && $previous_sibling->content eq 'my' ) {
224 9         59 next;
225             }
226             }
227              
228             # If a module exports %foo and we find $foo{bar}, $word->canonical
229             # returns $foo and $word->symbol returns %foo
230 374 100 100     2811 if ( $isa_symbol
    50 66        
    100 33        
    100 66        
    50 100        
    50 100        
    50 66        
      33        
231             && $self->_is_importable( $word->symbol ) ) {
232 9         769 @found_import = ( $word->symbol );
233             }
234              
235             # Match on \&is_Str as is_Str
236             elsif ($isa_symbol
237             && $word->symbol_type eq '&'
238             && $self->_is_importable( substr( $word->symbol, 1 ) ) ) {
239 0         0 @found_import = ( substr( $word->symbol, 1 ) );
240             }
241              
242             # Don't catch ${foo} here and mistake it for "foo". We deal with that
243             # elsewhere. Don't catch @{ split_header $str }.
244             elsif (
245             $self->_is_importable("$word")
246             && !(
247             $word =~ m{^\w}
248             && $word->previous_token
249             && $word->previous_token eq '{'
250             && $word->previous_token->previous_token
251             && $word->previous_token->previous_token eq '$'
252             )
253             ) {
254 41         19811 @found_import = ("$word");
255             }
256              
257             # Maybe a subroutine ref has been exported. For instance,
258             # Getopt::Long exports &GetOptions
259             elsif ($is_function_call
260             && $self->_is_importable( '&' . $word ) ) {
261 1         41 @found_import = ( '&' . "$word" );
262             }
263              
264             # Maybe this is an inner package referencing a function in main. We
265             # don't really deal with inner packages otherwise, so this could break
266             # some things.
267             elsif ($is_function_call
268             && $word =~ m{^::\w+}
269             && $self->_is_importable( substr( $word, 2 ) ) ) {
270 0         0 @found_import = ( substr( $word, 2 ) );
271             }
272              
273             # PPI can think that an imported function in a ternary is a label
274             # my $foo = $enabled ? GEOIP_MEMORY_CACHE : 0;
275             # The content of the $word will be "GEOIP_MEMORY_CACHE :"
276             elsif ( $word->isa('PPI::Token::Label') ) {
277 0 0       0 if ( $word->content =~ m{^(\w+)} ) {
278 0         0 my $label = $1;
279 0 0       0 if ( $self->_is_importable($label) ) {
280 0         0 @found_import = ($label);
281 0         0 $found{$label}++;
282             }
283             }
284             }
285              
286             # Sometimes an import is only used to set a default value for a
287             # variable in a signature. Without treating a prototype as a signature,
288             # we would miss the import entirely. I'm not particularly proud of
289             # this, but since PPI doesn't yet support signatures, this will at
290             # least help us cover some cases. If the prototype is actually a
291             # prototype, then this just shouldn't find anything.
292             elsif ( $word->isa('PPI::Token::Prototype') ) {
293 0         0 my $prototype = $word->prototype;
294              
295             # sometimes closing parens don't get included by PPI.
296 0 0       0 if ( substr( $prototype, -1, 1 ) eq '(' ) {
297 0         0 $prototype .= ')';
298             }
299 0         0 $prototype =~ s{,}{;}g;
300              
301 0         0 $prototype .= ';'; # Won't hurt if there's an extra ";"
302 0         0 my $new = PPI::Document->new( \$prototype );
303             my $words = $new->find(
304             sub {
305 0     0   0 $_[1]->isa('PPI::Token::Word')
306             || $_[1]->isa('PPI::Token::Symbol');
307             }
308 0   0     0 ) || [];
309 0         0 for my $word ( @{$words} ) {
  0         0  
310 0 0       0 if ( $self->_is_importable("$word") ) {
311 0         0 push @found_import, "$word";
312             }
313             }
314             }
315              
316 374         72251 for my $found (@found_import) {
317 51 100       217 if ( !$self->_is_already_imported($found) ) {
318 45         205 $found{$found}++;
319             }
320             }
321             }
322              
323             # A used import might be a variable interpolated into quotes.
324 47 100       487 if ( $self->_imports_remain( \%found ) ) {
325 43         520 for my $var ( keys %{ $self->_document->interpolated_symbols } ) {
  43         1116  
326 9 100       238 if ( $self->_is_importable($var) ) {
327 6         139 $found{$var} = 1;
328             }
329             }
330             }
331              
332             # A used import might be just be a symbol that just gets exported. ie. If
333             # it appears as @EXPORT = ( 'SOME_SYMBOL') we don't want to miss it.
334 47 50 100     1186 if ( $self->_imports_remain( \%found )
      66        
335             && $self->_document->my_own_inspector
336             && $self->_document->my_own_inspector->is_exporter ) {
337 0         0 for my $symbol (
338             uniq(
339             $self->_document->my_own_inspector->implicit_export_names,
340             $self->_document->my_own_inspector->explicit_export_names
341             )
342             ) {
343 0 0       0 if ( $self->_is_importable($symbol) ) {
344 0         0 $found{$symbol} = 1;
345             }
346             }
347             }
348              
349             # A used import might just be something that gets re-exported by
350             # Sub::Exporter
351 47 100       1826 if ( $self->_imports_remain( \%found ) ) {
352 43         746 for my $func ( $self->_document->sub_exporter_export_list ) {
353 2 100       38 if ( $self->_is_importable($func) ) {
354 1         23 $found{$func}++;
355             }
356             }
357             }
358              
359 47         1304 my @found = map { $self->_import_name($_) } keys %found;
  52         423  
360              
361             # Some modules have imports which are basically flags, rather than names of
362             # symbols to export. So if a flag is already in the import, we need to
363             # preserve it, rather than risk altering the behaviour of the module.
364 47 100       1477 if ( $self->_export_inspector->has_import_flags ) {
365 10         307 for my $arg ( @{ $self->_export_inspector->import_flags } ) {
  10         223  
366 10 100 100     439 if ( defined $self->_original_imports
367 9     9   49 && ( any { $_ eq $arg } @{ $self->_original_imports } ) ) {
  6         35  
368 3         19 push @found, $arg;
369             }
370             }
371             }
372              
373 47         1228 @found = uniq _sort_symbols(@found);
374 47 100       257 if ( $self->_original_imports ) {
375 18         49 my @preserved = grep { m{\A[!_]} } @{ $self->_original_imports };
  28         134  
  18         93  
376 18         83 @found = uniq( @preserved, @found );
377             }
378 47         1059 return \@found;
379             }
380              
381             ## use critic
382              
383             sub _build_is_ignored {
384 50     50   4289 my $self = shift;
385              
386 50 50       325 if ( $self->_include->type eq 'require' ) {
387 0 0       0 return 1 if !$self->_is_translatable;
388             }
389              
390             # This will be rewritten as "use Foo ();"
391 50 100       2717 return 0 if $self->_will_never_export;
392              
393 49 50       6185 return 1 if $self->_export_inspector->has_fatal_error;
394              
395 49 50       4815 return 0 if $self->_export_inspector->is_oo_class;
396              
397 49 100       2226 return 1 if $self->_export_inspector->is_moose_class;
398              
399 48 50       6235 return 1 if $self->_export_inspector->uses_moose;
400              
401 48 50       4551 return 1 if $self->_export_inspector->is_moo_class;
402              
403             return 1
404 48 100   1   1857 if any { $_ eq 'Moo::Object' } @{ $self->_export_inspector->pkg_isa };
  1         53  
  48         1018  
405              
406 47         2452 return 0;
407             }
408              
409             sub _build_is_translatable {
410 47     47   1844 my $self = shift;
411              
412 47 50       276 return 0 if !$self->_include->type;
413 47 50       1436 return 0 if $self->_include->type ne 'require';
414 0 0       0 return 0 if $self->module_name eq 'Exporter';
415              
416             # We can deal with a top level require.
417             # require Foo; can be changed to use Foo ();
418             # We don't want to touch requires which are inside any kind of a condition.
419              
420             # If there is no parent, then it's likely just a single snippet
421             # provided by a text editor. We can process the snippet. If it's part
422             # of a larger document and the parent is not a PPI::Document, this
423             # would appear not to be a top level require.
424 0 0 0     0 if ( $self->_include->parent
425             && !$self->_include->parent->isa('PPI::Document') ) {
426 0         0 return 0;
427             }
428              
429             # Postfix conditions are a bit harder to find. If the significant
430             # children amount to more than "require Module;", we'll just move on.
431 0         0 my @children = $self->_include->schildren;
432              
433 0         0 my $statement = join q{ }, @children[ 0 .. 2 ];
434 0 0       0 if ( $statement ne 'require ' . $self->module_name . ' ;' ) {
435 0         0 return 0;
436             }
437              
438             # Any other case of "require Foo;" should be translated to "use Foo ();"
439             # as those are functionally equivalent.
440 0         0 return 1;
441             }
442              
443             ## no critic (Subroutines::ProhibitExcessComplexity)
444             sub _build_formatted_ppi_statement {
445 50     50   7653 my $self = shift;
446              
447             # The following steps may seem a bit out of order, but we're trying to
448             # short circuit if at all possible. That means not building an
449             # ExportInspector object unless we really need to.
450              
451             # Nothing to do here. Preserve the original statement.
452 50 100       977 return $self->_include if $self->_is_ignored;
453              
454 48 50       1785 my $maybe_module_version
455             = $self->_include->module_version
456             ? q{ } . $self->_include->module_version
457             : q{};
458              
459             # In this case we either have a module which we know will never export
460             # symbols or a module which can export but for which we haven't found any
461             # imported symbols. In both cases we'll want to rewrite with an empty list
462             # of imports.
463 48 100 66     4267 if ( $self->_will_never_export
      100        
464             || $self->_is_translatable
465 47         3997 || !@{ $self->_imports } ) {
466 15         671 return $self->_maybe_get_new_include(
467             sprintf(
468             'use %s%s ();', $self->module_name, $maybe_module_version
469             )
470             );
471             }
472              
473 33         1102 my $statement;
474              
475 33         238 my @args = $self->_include->arguments;
476              
477             # Don't touch a do { } block.
478 33 100 100     2083 if ( $self->_isa_test_builder_module && @args && $args[0] eq 'do' ) {
      66        
479 1         113 return $self->_include;
480             }
481              
482             # Do some contortions to turn PPI objects back into a data structure so
483             # that we can add or replace an import hash key and then end up with a new
484             # list which is sorted on hash keys. This makes the assumption that the
485             # same key won't get passed twice. This is pretty gross, but I was too lazy
486             # to try to figure out how to do this with PPI and I think it should
487             # *mostly* work. I don't like the formatting that Data::Dumper comes up
488             # with, so we'll run it through perltidy.
489              
490 32 50 66     2695 if ( $self->_isa_test_builder_module
491             && @args ) {
492 0         0 my $all;
493              
494 0 0 0     0 if ( $args[0]->isa('PPI::Token::Word') ) {
    0          
495 0         0 $all = join q{ }, map { "$_" } @args;
  0         0  
496             }
497              
498             elsif ($args[0]->isa('PPI::Structure::List')
499             && $args[0]->braces eq '()' ) {
500 0         0 for my $child ( $args[0]->children ) {
501 0         0 $all .= "$child";
502             }
503             }
504              
505             ## no critic (BuiltinFunctions::ProhibitStringyEval)
506 0         0 my $args;
507             my $error;
508             try {
509 0     0   0 $args = eval( '{' . $all . '}' );
510             }
511             catch {
512 0     0   0 $self->logger->info($_);
513 0         0 $error = 1;
514 0         0 };
515             ## use critic
516              
517 0 0 0     0 if ( !$error && !is_plain_hashref($args) ) {
518 0         0 $self->logger->info( 'Not a hashref: ' . Dumper($args) );
519 0         0 $error = 1;
520             }
521              
522             # We will replace these with our own parsed imports.
523 0         0 delete $args->{import};
524              
525             # Ignore this line if we can't parse it. This will happen if the arg to
526             # test is a do block, for example.
527 0 0       0 return $self->_include if $error;
528              
529 0         0 local $Data::Dumper::Terse = 1;
530 0         0 local $Data::Dumper::Indent = 0;
531 0         0 local $Data::Dumper::Sortkeys = 1;
532 0         0 local $Data::Dumper::Quotekeys = 0;
533 0         0 local $Data::Dumper::Useqq = 0;
534 0         0 local $Data::Dumper::Trailingcomma = 1;
535 0         0 local $Data::Dumper::Deparse = 1;
536              
537 0         0 my $dumped = Dumper($args);
538 0         0 my $non_import_args;
539             my $import_arg;
540              
541 0 0       0 if ( $dumped =~ m/^{(.*)}$/ ) {
542 0         0 $non_import_args = $1;
543             }
544              
545 0 0       0 if ( $self->_imports ) {
546             $import_arg = sprintf(
547             'import => [qw( %s )]',
548 0         0 join( q{ }, @{ $self->_imports } )
  0         0  
549             );
550             }
551              
552             my $all_args = join ', ',
553 0 0       0 grep { $_ && $_ =~ m{\w} } ( $import_arg, $non_import_args );
  0         0  
554              
555 0         0 $statement = sprintf(
556             'use %s%s %s;',
557             $self->module_name,
558             $maybe_module_version,
559             $all_args
560             );
561              
562             # save ~60ms in cases where we don't need Perl::Tidy
563 0         0 require Perl::Tidy; ## no perlimports
564 0         0 Perl::Tidy::perltidy(
565             argv => '-npro',
566             source => \$statement,
567             destination => \$statement
568             );
569             }
570              
571             else {
572 32 100       634 my $padding = $self->_pad_imports ? q{ } : q{};
573 32 100       631 my $template
574             = $self->_isa_test_builder_module
575             ? 'use %s%s import => [ qw(%s%s%s) ];'
576             : 'use %s%s qw(%s%s%s);';
577              
578             $statement = sprintf(
579             $template,
580             $self->module_name,
581             $maybe_module_version,
582             $padding,
583             join(
584             q{ },
585 32         927 @{ $self->_imports }
  32         800  
586             ),
587             $padding,
588             );
589             }
590              
591             # Don't deal with Test::Builder classes here to keep it simple for now
592 32 50 33     595 if ( length($statement) > 78 && !$self->_isa_test_builder_module ) {
593 0         0 $statement = sprintf(
594             "use %s%s qw(\n",
595             $self->module_name,
596             $maybe_module_version,
597             );
598 0         0 for ( @{ $self->_imports } ) {
  0         0  
599 0         0 $statement .= " $_\n";
600             }
601 0         0 $statement .= ');';
602             }
603              
604 32         190 return $self->_maybe_get_new_include($statement);
605             }
606              
607             ## use critic
608              
609             sub _imports_remain {
610 868     868   1432 my $self = shift;
611 868         1288 my $found = shift;
612 868         1622 return keys %{$found} < $self->_explicit_export_count;
  868         2632  
613             }
614              
615             sub _maybe_get_new_include {
616 47     47   293 my $self = shift;
617 47         94 my $statement = shift;
618 47         376 my $doc = PPI::Document->new( \$statement );
619             my $includes
620 47     389   73935 = $doc->find( sub { $_[1]->isa('PPI::Statement::Include'); } );
  389         5475  
621 47         988 my $rewrite = $includes->[0]->clone;
622              
623 47         6752 my $a = $self->_include . q{};
624 47         1459 my $b = $rewrite . q{};
625              
626             # If the only difference is some whitespace before the quotes, we'll not
627             # alter the include. This reduces some of the churn. What we want to avoid
628             # is rewriting imports where the only change is to remove some whitespace
629             # padding which was specifically added by perltidy. If we keep removing
630             # changes made by perltidy this tool will be unfit to be used as a linter,
631             # because it will either force a tidy after every run or it will introduce
632             # tidying errors.
633             #
634             # So "use Foo qw( bar );" should be considered equivalent to
635             # "use Foo qw( bar );" because it might be in the context of
636             #
637             # use AAAAAAA qw( thing );
638             # use Foo qw( bar );
639             # use FFFFFFF qw( other );
640             #
641             # If the existing include is something like
642             # "use Foo 123 qw( foo );"
643             # we should probably rewrite that since perltidy will likely rewrite
644             # this to
645             # "use Foo 123 qw( foo );"
646              
647 47         1645 my $orig = $a;
648 47 100       221 if ( _respace_include($orig) eq $b ) {
649 14         103 return $self->_include;
650             }
651              
652 33 100       940 return $rewrite if $self->_tidy_whitespace;
653              
654             # We will return the rewritten include if a newline has been added or
655             # removed. This is a formatting change that we *probably* want.
656              
657 4         52 $a =~ s{\s}{}g;
658 4         17 $b =~ s{\s}{}g;
659              
660 4 50       24 return ( $a eq $b ) ? $self->_include : $rewrite;
661             }
662              
663             # This function takes the original include and strips away the extra spaces
664             # which might have been added as formatting by perltidy. This makes it easier
665             # to compare the old include with the new and decide if we really need to
666             # replace it.
667             sub _respace_include {
668 50     50   1879 my $include = shift;
669 50         451 $include =~ s{\s+(qw|\()}{ $1};
670 50         251 return $include;
671             }
672              
673             # If there's a different module in this document which has already imported
674             # a symbol of the same name in its original imports, the we should make
675             # sure we don't accidentally create a duplicate import here. For example,
676             # Path::Tiny and Test::TempDir::Tiny both export a tempdir() function.
677             # Without this check we'd add a "tempdir" to both modules if we find it
678             # being used in the document.
679              
680             sub _is_already_imported {
681 51     51   116 my $self = shift;
682 51         101 my $symbol = shift;
683 51         94 my $duplicate = 0;
684              
685 51         94 foreach my $module (
686 98         2388 grep { $_ ne $self->module_name }
687 51         1299 keys %{ $self->_document->original_imports }
688             ) {
689 47         468 $self->logger->debug(
690             "checking $module for previous imports of $symbol");
691 47         3042 my @imports;
692 47 100       1026 if (
693             is_plain_arrayref(
694             $self->_document->original_imports->{$module}
695             )
696             ) {
697 27         269 @imports = @{ $self->_document->original_imports->{$module} };
  27         486  
698 27         342 $self->logger->debug(
699             'Explicit imports found: ' . Dumper( [ sort @imports ] ) );
700             }
701             else {
702 20 50       262 if ( my $inspector = $self->_document->inspector_for($module) ) {
703 20         288 @imports = $inspector->implicit_export_names;
704 20         530 $self->logger->debug( 'Implicit imports found: '
705             . Dumper( [ sort @imports ] ) );
706             }
707             }
708              
709 47 100   298   6950 if ( any { $_ eq $symbol } @imports ) {
  298         474  
710 6         12 $duplicate = 1;
711 6         37 $self->logger->debug("$symbol already imported via $module");
712 6         232 last;
713             }
714             }
715              
716 51         495 return $duplicate;
717             }
718              
719             sub _sort_symbols {
720 47     47   160 my @list = @_;
721              
722             ## no critic (BuiltinFunctions::RequireSimpleSortBlock)
723             my @sorted = sort {
724 47         206 my $A = _transform_before_cmp($a);
  27         82  
725 27         73 my $B = _transform_before_cmp($b);
726 27         137 "\L$A" cmp "\L$B";
727             } @list;
728 47         272 return @sorted;
729             }
730              
731             # This looks a little weird, but basically we want to maintain a stable sort
732             # order with lists that look like (foo, $foo, @foo, %foo). We use "-" to begin
733             # the suffix because it comes earliest in a sorted list of letters and digits.
734             sub _transform_before_cmp {
735 54     54   93 my $thing = shift;
736 54 100       261 if ( $thing =~ m{\A[\$]} ) {
    100          
    100          
737 11         29 $thing = substr( $thing, 1 ) . '-0';
738             }
739             elsif ( $thing =~ m{\A[@]} ) {
740 7         30 $thing = substr( $thing, 1 ) . '-1';
741             }
742             elsif ( $thing =~ m{\A[%]} ) {
743 7         40 $thing = substr( $thing, 1 ) . '-2';
744             }
745 54         124 return $thing;
746             }
747              
748             1;
749              
750             # ABSTRACT: Encapsulate one use statement in a document
751              
752             __END__
753              
754             =pod
755              
756             =encoding UTF-8
757              
758             =head1 NAME
759              
760             App::perlimports::Include - Encapsulate one use statement in a document
761              
762             =head1 VERSION
763              
764             version 0.000052
765              
766             =head1 METHODS
767              
768             =head2 formatted_ppi_statement
769              
770             Returns an L<PPI::Statement::Include> object. This can be stringified into an
771             import statement or used to replace an existing L<PPI::Statement::Include>.
772              
773             =head1 AUTHOR
774              
775             Olaf Alders <olaf@wundercounter.com>
776              
777             =head1 COPYRIGHT AND LICENSE
778              
779             This software is copyright (c) 2020 by Olaf Alders.
780              
781             This is free software; you can redistribute it and/or modify it under
782             the same terms as the Perl 5 programming language system itself.
783              
784             =cut