File Coverage

blib/lib/Devel/PerlySense/Document.pm
Criterion Covered Total %
statement 330 336 98.2
branch 100 118 84.7
condition 20 26 76.9
subroutine 52 53 98.1
pod 34 34 100.0
total 536 567 94.5


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Devel::PerlySense::Document - A Perl file/document
4              
5             =head1 SYNOPSIS
6              
7              
8              
9              
10             =head1 DESCRIPTION
11              
12             The document contains a PPI parsed document, etc. along with a
13             metadata object.
14              
15              
16             =head2 Caching
17              
18             Caching is done on a per file + mod timestamp basis. Things that are
19             cached are: PPI documents, Document::Api and Document::Meta objects.
20              
21             Currently Cache::Cache is used. This isn't great (duh), since there is
22             no good way to expire obsolete files.
23              
24              
25             =cut
26              
27              
28              
29              
30              
31 68     68   6721 use strict;
  68         81  
  68         1601  
32 68     68   202 use warnings;
  68         75  
  68         1417  
33 68     68   7195 use utf8;
  68         184  
  68         290  
34              
35             package Devel::PerlySense::Document;
36             $Devel::PerlySense::Document::VERSION = '0.0217';
37              
38              
39              
40              
41 68     68   7650 use Spiffy -Base;
  68         52124  
  68         312  
42 68     68   222922 use Carp;
  68     68   94  
  68     68   1095  
  68         196  
  68         78  
  68         1316  
  68         208  
  68         74  
  68         2838  
43 68     68   4503 use Data::Dumper;
  68         37677  
  68         2438  
44 68     68   10420 use PPI 1.003;
  68         1546072  
  68         1604  
45 68     68   299 use File::Basename;
  68         79  
  68         3403  
46 68     68   235 use List::MoreUtils qw/ uniq /;
  68         77  
  68         443  
47              
48 68     68   31580 use Devel::PerlySense;
  68         116  
  68         392  
49 68     68   12268 use Devel::PerlySense::Util;
  68         89  
  68         3032  
50 68     68   246 use Devel::PerlySense::Util::Log;
  68         71  
  68         2158  
51 68     68   12925 use Devel::PerlySense::Document::Location;
  68         100  
  68         331  
52 68     68   24667 use Devel::PerlySense::Document::Api;
  68         108  
  68         324  
53 68     68   24968 use Devel::PerlySense::Document::Meta;
  68         114  
  68         391  
54              
55 68     68   12187 use Devel::TimeThis;
  68         105  
  68         194677  
56              
57              
58              
59              
60              
61             =head1 PROPERTIES
62              
63             =head2 oPerlySense
64              
65             Devel::PerlySense object.
66              
67             Default: set during new()
68              
69             =cut
70             field "oPerlySense" => undef;
71              
72              
73              
74              
75              
76             =head2 file
77              
78             The absolute file name of the parsed file, or "" if none was parsed.
79              
80             Default: ""
81              
82             =cut
83             field "file" => "";
84              
85              
86              
87              
88              
89             =head2 oDocument
90              
91             The PPI::Document object from the parse(), or undef if none was
92             parsed.
93              
94             Default: undef
95              
96             =cut
97             field "oDocument" => undef;
98             # sub oDocument {
99             # @_ or (Carp::longmess =~ /Document::parse/s or cluck("\n\n\n\n\nODOCUMENT FOR (" . $self->file . ")\n"));
100             # use Carp qw/cluck/;
101              
102             # @_ and $self->{odocument} = $_[0];
103              
104             # $self->{odocument};
105             # }
106              
107              
108              
109              
110              
111             =head2 oMeta
112              
113             The Devel::PerlySense::Document::Meta object from the parse(), or
114             undef if none was parsed.
115              
116             Default: undef
117              
118             =cut
119             field "oMeta" => undef;
120              
121              
122              
123              
124              
125             =head2 rhPackageApiLikely
126              
127             Hash ref with (keys: package names; Document::Api objects).
128              
129             Default: {}
130              
131             =cut
132             field "rhPackageApiLikely" => {};
133              
134              
135              
136              
137              
138             =head1 API METHODS
139              
140             =head2 new(oPerlySense => $oPerlySense)
141              
142             Create new PearlySense::Document object. Associate it with $oPerlySense.
143              
144             =cut
145 345     345 1 3011 sub new {
146 345         1516 my ($oPerlySense) = Devel::PerlySense::Util::aNamedArg(["oPerlySense"], @_);
147              
148 345         891 $self = bless {}, $self; #Create the object. It looks weird because of Spiffy
149 345         5364 $self->oPerlySense($oPerlySense);
150              
151 345         2536 return($self);
152             }
153              
154              
155              
156              
157              
158             =head2 fileFindModule(nameModule => $nameModule)
159              
160             Find the file containing the $nameModule given the file property of
161             the document.
162              
163             Return the absolute file name, or undef if none could be found. Die on
164             errors.
165              
166             =cut
167 526     526 1 735 sub fileFindModule {
168 526         1470 my ($nameModule) = Devel::PerlySense::Util::aNamedArg(["nameModule"], @_);
169              
170 526 50       12158 my $file = $self->file or return(undef);
171              
172             return(
173 526         10124 $self->oPerlySense->fileFindModule(
174             nameModule => $nameModule,
175             dirOrigin => dirname($self->file)
176             )
177             );
178             }
179              
180              
181              
182              
183              
184             =head2 parse(file => $file)
185              
186             Parse the $file and store the metadata.
187              
188             Return 1 on success, else die.
189              
190             Cached on the usual.
191              
192             =cut
193             ###TODO: Rearrange these so they are write cached here, but read
194             ###cached on first access instead.
195 347     347 1 62617 sub parse {
196 347         1143 my ($file) = Devel::PerlySense::Util::aNamedArg(["file"], @_);
197              
198 346         604 my $keyCache = "document";
199 346 100       1019 if(my $oDocument = $self->cacheGet($keyCache, $file)) {
200 9         270 $self->oDocument($oDocument);
201             } else {
202 337         1124 $self->parse0(file => $file);
203 335         4937 $self->cacheSet($keyCache, $file, $self->oDocument);
204             }
205              
206 344         8120 $self->file($file);
207              
208              
209 344         3922 $keyCache = "document-meta";
210 344 100       1083 if(my $oMeta = $self->cacheGet($keyCache, $file)) {
211 9         187 $self->oMeta($oMeta);
212             } else {
213 335         3304 $oMeta = Devel::PerlySense::Document::Meta->new();
214              
215 335         1380 $oMeta->parse($self);
216              
217 335         6097 $self->oMeta($oMeta);
218 335         5827 $self->cacheSet($keyCache, $file, $self->oMeta);
219             }
220              
221 344         3609 return(1);
222             }
223              
224              
225              
226              
227              
228             =head2 parse0(file => $file)
229              
230             Parse the $file and store the metadata.
231              
232             Return 1 on success, else die.
233              
234             =cut
235 337     337 1 734 sub parse0 {
236 337         1024 my ($file) = Devel::PerlySense::Util::aNamedArg(["file"], @_);
237             #print " Parsing: ((($file)))\n";
238 337 100       3202 my $oDocument = PPI::Document->new($file) or die("Could not parse file ($file): " . PPI::Document->errstr . "\n");
239 335         30499415 $oDocument->index_locations();
240              
241 335         6115704 $self->oDocument($oDocument);
242              
243 335         3197 return(1);
244             }
245              
246              
247              
248              
249              
250             =head2 aNamePackage()
251              
252             Return list of package names in this document.
253              
254             =cut
255 61     61 1 108 sub aNamePackage {
256 61         111 return( sort uniq map { $_->namespace } @{$self->oMeta->raPackage} );
  58         1159  
  61         809  
257             }
258              
259              
260              
261              
262              
263             =head2 aNameBase()
264              
265             Return list of names of modules that are base classes, according to
266             either "use base" or an assignment to @ISA.
267              
268             Dir on errors.
269              
270             =cut
271 199     199 1 3598 sub aNameBase {
272              
273             #TODO: Should be centralized in PerlySense and made configurable
274 199         378 my %hStop = map { $_ => 1 } qw(Exporter DynaLoader);
  398         1019  
275              
276 199 100       305 my @aBase = grep { (! $hStop{$_}) && $_ =~ /[A-Z]/ } @{$self->oMeta->raNameModuleBase};
  184         3369  
  199         2850  
277              
278 199         1869 return(@aBase);
279             }
280              
281              
282              
283              
284              
285             =head2 hasBaseClass($nameClass)
286              
287             Return true if $nameClass is an immediate base class to this one, else
288             false.
289              
290             =cut
291 20     20 1 34 sub hasBaseClass {
292 20         32 my ($nameClass) = @_;
293              
294 20         25 return( (grep { $_ eq $nameClass } @{$self->oMeta->raNameModuleBase}) > 0 );
  14         278  
  20         275  
295             }
296              
297              
298              
299              
300              
301             =head2 aNameModuleUse()
302              
303             Find modules that are used in this document.
304              
305             Don't find pragmas. Don't find very common infrastructure
306             modules. Only report modules used in this actual document.
307              
308             Return list of unique module names.
309              
310             Dir on errors.
311              
312             =cut
313 9     9 1 16 sub aNameModuleUse {
314              
315 9         22 my %hStop = map { $_ => 1 } qw(Exporter DynaLoader); #TODO: Should be centralized in PerlySense and made configurable
  18         41  
316 9         14 my @aModule = grep { (! $hStop{$_}) } @{$self->oMeta->raNameModuleUse};
  47         230  
  9         146  
317              
318 9         78 return(@aModule);
319             }
320              
321              
322              
323              
324              
325             =head2 packageAt(row => $row)
326              
327             Return the package name that is active on line $row (1..), or die on
328             errors.
329              
330             =cut
331 26     26 1 1804 sub packageAt {
332 26         90 my ($row) = Devel::PerlySense::Util::aNamedArg(["row"], @_);
333 26 100       114 $row > 0 or croak("Parameter row ($row) must be 1..");
334              
335             my @aPackage =
336 24 50       548 grep { $_->namespace && $_->location->[0] <= $row }
337 24 100       37 @{$self->oMeta->raPackage}
  24         396  
338             or return("main");
339              
340 20         914 my $oPackage = $aPackage[-1];
341 20         56 return($oPackage->namespace);
342             }
343              
344              
345              
346              
347             =head2 isEmptyAt(row => $row, col => $col)
348              
349             Determine whether the position at $row, $col is empty (ther is no known
350             content, no:
351              
352             modules
353             methods
354             variables?
355              
356             ).
357              
358             Return 1 if empty, else 0.
359              
360             Die on errors.
361              
362             =cut
363 16     16 1 23 sub isEmptyAt {
364 16         46 my ($row, $col) = Devel::PerlySense::Util::aNamedArg(["row", "col"], @_);
365              
366 16 100       577 $self->oMeta->moduleAt(row => $row, col => $col) and return(0);
367 12 100       149 $self->oMeta->rhMethodAt(row => $row, col => $col) and return(0);
368              
369 11         35 return(1);
370             }
371              
372              
373              
374              
375              
376             =head2 moduleAt(row => $row, col => $col)
377              
378             Find the module mentioned on line $row (1..) at $col (1..). Don't
379             recognize modules that isn't ucfirst(). There may be false positives,
380             if it looks like a module. (examples?)
381              
382             Return string like "My::Module" or "Module", or undef if none was
383             found.
384              
385             Die on errors.
386              
387             =cut
388 22     22 1 27 sub moduleAt {
389 22         62 my ($row, $col) = Devel::PerlySense::Util::aNamedArg(["row", "col"], @_);
390 22         362 return($self->oMeta->moduleAt(row => $row, col => $col));
391             }
392              
393              
394              
395              
396              
397             =head2 methodCallAt(row => $row, col => $col)
398              
399             Return the method call Perl code is on line $row (1..) at $col (1..),
400             or die on errors.
401              
402             In scalar context, return string like "$self->fooBar". Don't include
403             the parameter list or parens, only the "$object->method".
404              
405             In list context, return two item list with (object, method).
406              
407             The object may be undef/"" if it's an expression rather than a simple
408             variable.
409              
410             Return undef or () if none was found. Die on errors.
411              
412             =cut
413 76     76 1 2572 sub methodCallAt {
414 76         178 my ($row, $col) = Devel::PerlySense::Util::aNamedArg(["row", "col"], @_);
415              
416 76 100       1204 my $rhMethod = $self->oMeta->rhMethodAt(row => $row, col => $col) or return;
417 44         81 my ($oMethod, $oObject) = ($rhMethod->{oNode}, $rhMethod->{oNodeObject});
418              
419 44 100       110 wantarray and return($oObject, $oMethod);
420 8 100       26 return((defined($oObject) ? $oObject : "") . "->$oMethod");
421             }
422              
423              
424              
425              
426              
427             =head2 selfMethodCallAt(row => $row, row => $col)
428              
429             Return the name of the $self->method at $row, $col in this document.
430              
431             Also matches shift->method, if there is no $self in this sub at all.
432              
433             If no method call is found, maybe warn and return undef.
434              
435             Die on errors.
436              
437             =cut
438 25     25 1 40 sub selfMethodCallAt {
439 25         84 my ($row, $col) = Devel::PerlySense::Util::aNamedArg(["row", "col"], @_);
440              
441 25         100 my ($object, $method) = $self->methodCallAt(row => $row, col => $col);
442 25 100       103 $method or return(undef);
443 15 50       41 $object or return(undef);
444 15 100       41 $object eq '$self' and return($method);
445              
446             # If the object is "shift" and there is no mention of a $self in
447             # the sub, assume it's $self being shifted off @_
448 11 100       154 if($object eq "shift") {
449 2 100       23 $self->isThereSelfInSubAt(row => $row, col => $col) and return undef;
450 1         5 return($method);
451             }
452              
453 9         102 return(undef);
454             }
455              
456             =head2 isThereSelfInSubAt(row => $row, col => $col) : Bool
457              
458             Whether there is a mention of $self in the sub surrounding $row, $col.
459              
460             =cut
461 2     2 1 4 sub isThereSelfInSubAt {
462 2         6 my ($row, $col) = Devel::PerlySense::Util::aNamedArg(["row", "col"], @_);
463              
464 2 50       9 my $oLocationSubAt = $self->oLocationSubAt(row => $row, col => $col)
465             or return(0);
466              
467 2 50       40 my $source = $oLocationSubAt->rhProperty->{source} or return(0);
468              
469 2 100       19 if ( $source =~ / \$self \b /smx ) {
470             # There is a $self somewhere in this sub (could be false
471             # positive in a comment or string), so shift isn't $self
472 1         9 return(1);
473             }
474              
475 1         5 return(0);
476             }
477              
478             =head2 moduleMethodCallAt(row => $row, row => $col)
479              
480             Find the My::Module->method call at $row, $col in this document.
481              
482             In list context, return two item list with (module, method). In scalar
483             context, return "My::Module->method".
484              
485             Return undef or () if none was found. Die on errors.
486              
487             =cut
488 23     23 1 449 sub moduleMethodCallAt {
489 23         90 my ($row, $col) = Devel::PerlySense::Util::aNamedArg(["row", "col"], @_);
490              
491 23         68 my ($module, $method) = $self->methodCallAt(row => $row, col => $col);
492 23 100 66     126 $module && $method or return(undef);
493 13 100       26 $module =~ /[^\w:]/ and return(undef); #only allow bareword modules
494              
495 8 100       55 wantarray() and return($module, $method);
496 2         5 return("$module->$method");
497             }
498              
499              
500              
501              
502              
503             =head2 aObjectMethodCallAt(row => $row, row => $col)
504              
505             Return three item array with (object name, method name, $oLocation of the
506             surrounding sub) of the $self->method at $row, $col in this
507             document. The object may be '$self'.
508              
509             If no method call is found, maybe warn and return ().
510              
511             Die on errors.
512              
513             =cut
514 18     18 1 538 sub aObjectMethodCallAt {
515 18         60 my ($row, $col) = Devel::PerlySense::Util::aNamedArg(["row", "col"], @_);
516              
517 18         54 my ($oObject, $oMethod) = $self->methodCallAt(row => $row, col => $col);
518 18 100 66     96 $oObject && $oMethod or return();
519 8 100       16 $oObject =~ /^\$\w+$/ or return();
520              
521 7 50       54 my $oLocationSub = $self->oLocationEnclosingSub($oMethod) or return();
522              
523 7         28 return($oObject, $oMethod, $oLocationSub);
524             }
525              
526              
527              
528              
529              
530             =head2 rhRegexExample(row => $row, col => $col)
531              
532             Look in $file at location $row/$col and find the regex located there,
533             and possibly the example comment preceeding it.
534              
535             Return hash ref with (keys: regex, example; values: source
536             string). The source string is an empty string if nothing found.
537              
538             If there is an example string in a comment, return the example without
539             the comment #
540              
541             Die if $file doesn't exist, or on other errors.
542              
543             =cut
544 0     0 1 0 sub rhRegexExample {
545 0         0 my ($row, $col) = Devel::PerlySense::Util::aNamedArg(["row", "col"], @_);
546              
547 0         0 return { regex => "", example => "" };
548             }
549              
550              
551              
552              
553              
554             =head2 oLocationSub(name => $name, [package => "main"])
555              
556             Return a Devel::PerlySense::Document::Location object with the
557             location of the sub declaration called $name in $package, or undef if
558             it wasn't found.
559              
560             Die on errors.
561              
562             =cut
563 23     23 1 3016 sub oLocationSub {
564 23         75 my ($name) = Devel::PerlySense::Util::aNamedArg(["name"], @_);
565 23         51 my (%p) = @_;
566 23   100     74 my $package = $p{package} || "main";
567              
568 23         32 for my $oLocation (@{$self->oMeta->raLocationSub}) {
  23         411  
569             # debug("JPL: " . $oLocation->rhProperty->{nameSub} . " eq $name && " . $oLocation->rhProperty->{namePackage} . " eq $package");
570             # defined $oLocation->rhProperty->{nameSub} or debug("SANITY FAILED: " . Dumper($oLocation));
571             # defined $oLocation->rhProperty->{namePackage} or debug("SANITY FAILED: " . Dumper($oLocation));
572 138 100 100     2741 if( $oLocation->rhProperty->{nameSub} eq $name
573             && $oLocation->rhProperty->{namePackage} eq $package) {
574 16         405 debug("Document->oLocation found ($name) in ($oLocation)");
575 16         57 return($oLocation);
576             }
577             }
578              
579 7         49 return(undef);
580             }
581              
582              
583              
584              
585              
586             =head2 oLocationSubAt(row => $row, col => $col)
587              
588             Return a Devel::PerlySense::Document::Location object with the
589             location of the sub definition at $row/$col, or undef if it row/col
590             isn't inside a sub definition.
591              
592             Note: Currently, col is ignored, and the sub is presumed to occupy the
593             entire row.
594              
595             Die on errors.
596              
597             =cut
598 7     7 1 3864 sub oLocationSubAt {
599 7         22 my ($row, $col) = Devel::PerlySense::Util::aNamedArg(["row", "col"], @_);
600              
601 7         9 for my $oLocation (@{$self->oMeta->raLocationSub}) {
  7         136  
602 134 100 100     3164 if( $row >= $oLocation->row
603             && $row <= $oLocation->rhProperty->{oLocationEnd}->row
604             ) {
605 4         161 debug("Sub " . $oLocation->rhProperty->{namePackage} . "->" . $oLocation->rhProperty->{nameSub} . " found at (" . $oLocation->file . ":$row)");
606 4         16 return($oLocation->clone);
607             }
608             }
609              
610 3         17 return(undef);
611             }
612              
613              
614              
615              
616              
617             =head2 oLocationSubDefinition(name => $name, [row => $row], [package => $package])
618              
619             Return a Devel::PerlySense::Document::Location object with the
620             location of the sub "definition" for $name, or undef if it wasn't
621             found. The definition can be the sub declaration, or a POD entry.
622              
623             If $row is passed, use it to determine which package is active at
624             $row. If $package is passed, use that instead. Default to package
625             "main" if neither is passed.
626              
627             If no definition can be found in this document, and the module has one
628             or more base classes, look in the @ISA (depth-first, just like Perl
629             (see perldoc perltoot)).
630              
631             Warn on some failures to find the location. Die on errors.
632              
633             =cut
634 19     19 1 4570 sub oLocationSubDefinition {
635 19         61 my ($name) = Devel::PerlySense::Util::aNamedArg(["name"], @_);
636 19         48 my %p = @_; my ($row, $package) = ($p{row}, $p{package});
  19         52  
637              
638 19 100       50 if(! $package) {
639 12 100       32 if($row) {
640 11 50       37 $package = $self->packageAt(row => $row)
641             or warn("Could not find active package at row ($row)\n"), return(undef);
642             } else {
643 1         2 $package = "main";
644             }
645             }
646 19         251 debug("Document->oLocationSubDefinition name($name) package($package)");
647              
648             #Look for the sub definition
649 19         71 my $oLocation = $self->oLocationSub(name => $name, package => $package);
650 19 100       85 $oLocation and return($oLocation);
651              
652             #Fail to POD in same file
653 6         23 $oLocation = $self->oLocationPod(name => $name, lookFor => "method", ignoreBaseModules => 1);
654 6 100       23 $oLocation and return($oLocation);
655              
656             #Fail to base classes
657 2         10 for my $moduleBase ($self->aNameBase) {
658 2 50       30 my $oDocumentBase = $self->oPerlySense->oDocumentFindModule(
659             nameModule => $moduleBase,
660             dirOrigin => dirname($self->file),
661             ) or debug("Could not find module ($moduleBase)\n"), next;
662 2         15 $oLocation = $oDocumentBase->oLocationSubDefinition(name => $name, package => $moduleBase);
663 2 50       40 $oLocation and return($oLocation);
664             }
665              
666 0         0 return(undef);
667             }
668              
669              
670              
671              
672              
673             =head2 oLocationPod(name => $name, lookFor => $lookFor, [ignoreBaseModules => 0])
674              
675             Return a Devel::PerlySense::Document::Location object with the "best"
676             location of the pod =head? or =item where $name is present, or undef
677             if it wasn't found.
678              
679             $lookFor can be "method", i.e. what the search was looking for.
680              
681             If $lookFor is "method" and the POD isn't found, try in the base
682             classes, unless $ignoreBaseModules is true.
683              
684             If the method POD is found in a base class, make sure that notice is
685             in the rhProperty->{pod} (once).
686              
687             Set the rhProperty keys of the Location:
688              
689             found - $lookFor
690             docType - "hint"
691             name - the $name
692             pod - the POD describing $name (includes podSection)
693             podSection - the POD section the name is located in
694              
695             pod will be munged to include podSection, and if the original pod
696             consisted of an "=item", it will be surrounded by "=over" 4 and
697             "=back".
698              
699             Die on errors.
700              
701             =cut
702 194     194 1 8275 sub oLocationPod {
703 194         626 my ($name, $lookFor) = Devel::PerlySense::Util::aNamedArg(["name", "lookFor"], @_);
704 194         531 my %p = @_;
705 194   100     817 my $ignoreBaseModules = $p{ignoreBaseModules} || 0;
706 194 50       486 $lookFor eq "method" or croak("Invalid value for lookFor ($lookFor). Valid values are: 'method'.");
707              
708 194         406 my $rexName = quotemeta($name);
709 194         220 for my $oLocationCur (@{$self->oMeta->raLocationPod}) {
  194         2857  
710              
711             ###TODO: ignore name if it has a sigil, i.e "$name"/"%name"/"@name"
712             #First match, this may have to be refined (go for the earliest occurence on the line, or the shortest line)
713 2853 100       51357 if($oLocationCur->rhProperty->{pod} =~ /^= \w+ \s+ [^\n]*? \b $rexName \b /x) {
714 87         832 my $oLocation = $oLocationCur->clone;
715 87         1557 $oLocation->rhProperty->{found} = $lookFor;
716 87         1365 $oLocation->rhProperty->{docType} = "hint";
717 87         1272 $oLocation->rhProperty->{name} = "$name";
718              
719 87         1337 my $pod = $oLocation->rhProperty->{pod};
720 87 100       524 $pod =~ /^=item\s/ and $pod = "=over 4\n\n$pod\n\n=back\n";
721 87         1064 $oLocation->rhProperty->{pod} = $oLocation->rhProperty->{podSection} . $pod;
722              
723 87         924 return($oLocation);
724             }
725             }
726              
727              
728 107 100       909 $ignoreBaseModules and return(undef);
729             #Fail to base classes, maybe
730              
731 104         358 for my $moduleBase ($self->aNameBase) {
732 98 100       1357 my $oDocumentBase = $self->oPerlySense->oDocumentFindModule(
733             nameModule => $moduleBase,
734             dirOrigin => dirname($self->file),
735             ) or warn("Could not find module ($moduleBase)\n"), next;
736 97 100       628 if(my $oLocation = $oDocumentBase->oLocationPod(
737             name => $name,
738             lookFor => $lookFor,
739             )) {
740              
741 53 100       691 if( $oLocation->rhProperty->{pod} !~ /\n=head1 From <[\w:]+>\n$/) {
742 47         815 $oLocation->rhProperty->{pod} .= "\n=head1 From <$moduleBase>\n";
743             }
744              
745 53         466 return($oLocation);
746             }
747             }
748              
749 51         254 return(undef);
750             }
751              
752              
753              
754              
755              
756             =head2 aMethodCallOf(nameObject => $nameObject, oLocationWithin => $oLocationWithin)
757              
758             Find all the method calls of $nameObject in the $oLocationWithin.
759              
760             Shortcut: assume the $oLocationWithin is the entire interesting
761             scope. Ignore morons who re-define their vars in inner scopes with a
762             different type. If this turns out to be a problem, fix the problem
763             then. Or smack them over the head with a trout.
764              
765             Return sorted array with the method names called.
766              
767             Die on errors.
768              
769             =cut
770 7     7 1 2387 sub aMethodCallOf {
771 7         41 my ($nameObject, $oLocationWithin) = Devel::PerlySense::Util::aNamedArg(["nameObject", "oLocationWithin"], @_);
772              
773              
774             #Stop methods
775 7         24 my %hMethodStop = (isa => 1, can => 1); #TODO: Move to property and config
776              
777              
778 7         17 my $rexObject = quotemeta($nameObject);
779             my %hMethod =
780 24         43 map { $_ => 1 }
781 25         168 grep { ! exists $hMethodStop{$_} } (
782 7         153 $oLocationWithin->rhProperty->{source} =~ /
783             $rexObject
784             \s* -> \s*
785             ( \w+ )
786             /gsx
787             );
788              
789 7         59 return(sort keys %hMethod);
790             }
791              
792              
793              
794              
795              
796             =head2 determineLikelyApi(nameModule => $nameModule)
797              
798             Look in the document for sub declarations, $self->method calls, and
799             $self->{hash_key} in order to determine what is the likely API of the
800             packages of this document. Focus on the $nameModule and its base
801             classes.
802              
803             Set the rhPackageApiLikely property with new
804             Devel::PerlySense::Document::Api objects for each package.
805              
806             Return 1 on success. Die on errors.
807              
808             Cached on the usual + $nameModule.
809              
810             =cut
811 66     66 1 123 sub determineLikelyApi {
812 66         245 my ($nameModule) = Devel::PerlySense::Util::aNamedArg(["nameModule"], @_);
813              
814 66         181 my $keyCache = "likelyApi\t$nameModule";
815 66 100       1168 if(my $rhPackageApi = $self->cacheGet($keyCache, $self->file)) {
816 7         146 $self->rhPackageApiLikely($rhPackageApi);
817             } else {
818 59         227 $self->determineLikelyApi0(nameModule => $nameModule);
819 59         791 $self->cacheSet($keyCache, $self->file, $self->rhPackageApiLikely);
820             }
821              
822 66         498 return(1);
823             }
824              
825              
826              
827              
828              
829             =head2 determineLikelyApi0(nameModule => $nameModule)
830              
831             Implementation for determineLikelyApi()
832              
833             =cut
834 59     59 1 91 sub determineLikelyApi0 {
835 59         214 my ($nameModule) = Devel::PerlySense::Util::aNamedArg(["nameModule"], @_);
836              
837              
838 59         123 my $rhPackageApi = {};
839              
840 59         591 my $oApiCur = Devel::PerlySense::Document::Api->new();
841 59         81 my $packageCur = "main";
842 59         81 my $sourcePackage = "";
843 59         93 my @aNodeSub = ();
844 59         967 for my $oNode ($self->oDocument->elements) {
845 6754 100       149384 if ($oNode->isa("PPI::Statement::Package")) {
846 59         263 $oApiCur->parsePackageSetSub(oDocument => $self, raNodeSub => \@aNodeSub, source => $sourcePackage);
847 59 50       63 (keys %{$oApiCur->rhSub}) and $rhPackageApi->{$packageCur} = $oApiCur;
  59         1043  
848              
849              
850 59         681 $oApiCur = Devel::PerlySense::Document::Api->new();
851 59         345 $packageCur = $oNode->namespace;
852 59         1432 $sourcePackage = "";
853 59         107 @aNodeSub = ();
854             }
855              
856             ###TODO: push this down into the API class?
857 6754 100 66     14408 if ($oNode->isa("PPI::Statement::Sub") && ! $oNode->forward) {
858 412         5761 push(@aNodeSub, $oNode);
859 412         672 $sourcePackage .= $oNode;
860             }
861             }
862 59         441 $oApiCur->parsePackageSetSub(oDocument => $self, raNodeSub => \@aNodeSub, source => $sourcePackage);
863 59 50       76 (keys %{$oApiCur->rhSub}) and $rhPackageApi->{$packageCur} = $oApiCur;
  59         768  
864              
865              
866              
867             #Look in base classes
868 59         570 for my $nameBase ($self->aNameBase) {
869 20 50       285 my $oDocumentBase = $self->oPerlySense->oDocumentFindModule(
870             nameModule => $nameBase,
871             dirOrigin => dirname($self->file),
872             ) or next;
873              
874 20         173 debug("($nameModule) looking in base class ($nameBase)");
875 20 50       57 $nameModule eq $nameBase and next;
876             ###TODO: look for longer recursive chains
877              
878 20         107 $oDocumentBase->determineLikelyApi(nameModule => $nameBase);
879              
880 20         398 $self->mergePackageApiWithBase(
881             nameModule => $nameModule,
882             rhPackageApi => $rhPackageApi,
883             nameModuleBase => $nameBase,
884             rhPackageApiBase => $oDocumentBase->rhPackageApiLikely,
885             );
886              
887             }
888              
889              
890 59         830 $self->rhPackageApiLikely($rhPackageApi);
891              
892 59         484 return(1);
893             }
894              
895              
896              
897              
898              
899             =head2 mergePackageApiWithBase(nameModule => $nameModule, rhPackageApi => $rhPackageApi, nameModuleBase => $nameModuleBase, rhPackageApiBase => $rhPackageApiBase)
900              
901             Merge the $rhPackageApiBase of the base class with the existing
902             $rhPackageApi. Modify $rhPackageApi.
903              
904             Only merge the API of the $nameModule.
905              
906             Document::Api objects are cloned, not reused, but individual
907             Document::Location objects may be shared between documents and apis.
908              
909             Return 1 on success, or 0 if the package wasn't found. Die on errors.
910              
911             =cut
912 20     20 1 112 sub mergePackageApiWithBase {
913 20         74 my ($nameModule, $rhPackageApi, $nameModuleBase, $rhPackageApiBase) = Devel::PerlySense::Util::aNamedArg(["nameModule", "rhPackageApi", "nameModuleBase", "rhPackageApiBase"], @_);
914              
915 20 50       72 my $oApiBase = $rhPackageApiBase->{$nameModuleBase} or return(0);
916              
917 20         35 my $oApi = $rhPackageApi->{$nameModule};
918 20 50       52 $oApi or $oApi = $rhPackageApi->{$nameModule} = Devel::PerlySense::Document::Api->new();
919              
920 20         65 $oApi->mergeWithBase($oApiBase);
921              
922 20         39 return(1);
923             }
924              
925              
926              
927              
928              
929             =head2 scoreInterfaceMatch(nameModule => $nameModule, raMethodRequired => $raMethodRequired, raMethodNice => $raMethodNice)
930              
931             Rate the interface match between the document and the wanted interface
932             of the method names in $raMethodRequired + $raMethodNice.
933              
934             If not all method names in $raMethodRequired are supported, the score
935             is 0, and this document should not be considered to support the
936             requirements.
937              
938             The score is calculated like this:
939              
940             % of ($raMethod*) that is supported, except
941             all required must be there.
942              
943             +
944              
945             % of the api that consists of $raMethod*. This will favour smaller
946             interfaces in base classes.
947              
948             Return score on success. Die on errors.
949              
950             =cut
951 42     42 1 63 sub scoreInterfaceMatch {
952 42         124 my ($nameModule, $raMethodRequired, $raMethodNice) = Devel::PerlySense::Util::aNamedArg(["nameModule", "raMethodRequired", "raMethodNice"], @_);
953              
954 42 50       647 my $oApi = $self->rhPackageApiLikely->{$nameModule} or return(0);
955              
956 42         262 for my $method (@$raMethodRequired) {
957 44 100       151 $oApi->isSubSupported($method) or return(0);
958             }
959              
960 12         67 my %hSeen;
961 12         26 my @aMethod = grep { ! $hSeen{$_}++ } (@$raMethodRequired, @$raMethodNice);
  67         114  
962              
963 12         15 my $supportedMultiplier = 5; #TODO: move to config
964 12         37 my $score = ($oApi->percentSupportedOf(\@aMethod) * $supportedMultiplier) +
965             $oApi->percentConsistsOf(\@aMethod);
966              
967 12         183 my $percentScore = sprintf("%.02f", ($score / ($supportedMultiplier + 1))) + 0;
968              
969 12         57 return($percentScore);
970             }
971              
972              
973              
974              
975              
976             =head2 stringSignatureSurveyFromFile()
977              
978             Calculate a Signature Survey string for the source in the document.
979              
980             Return the string. Die on errors.
981              
982             =cut
983 1     1 1 2 sub stringSignatureSurveyFromFile {
984 1         20 return $self->stringSignatureSurveyFromSource( slurp($self->file) );
985             }
986              
987              
988              
989              
990              
991             =head2 stringSignatureSurveyFromSource($stringSource)
992              
993             Calculate a Signature Survey string for the $stringSource, based on
994             the idea in http://c2.com/doc/SignatureSurvey/ .
995              
996             The idea is not to get an exact representation of the source but a
997             good feel for what it contains.
998              
999             Return the survey string. Die on errors.
1000              
1001             =cut
1002             my $matchReplace = {
1003             q/{/ => q/{/,
1004             q/}/ => q/}/,
1005             q/"/ => q/"/,
1006             q/'/ => q/'/,
1007             q/;/ => q/;/,
1008             q/sub\s+\w+\s*{/ => q/SPECIAL/,
1009             q/sub\s+\w+\s*:\s*\w+[^{]+{/ => q/SPECIAL/,
1010             q/^=(?:head|item|for|pod)/ => q/SPECIAL/,
1011             };
1012             my $rexMatch = join("|", keys %$matchReplace );
1013 695     695   426 sub _stringReplace {
1014 695         402 my ($match) = @_;
1015              
1016 695 100       762 if(index($match, "sub") > -1) {
1017 33 100       38 index($match, ":") > -1 and return "SA{";
1018 32         30 return "S{";
1019             }
1020 662 100       703 index($match, "=") > -1 and return "=";
1021              
1022 586         547 return $matchReplace->{$match};
1023             }
1024 1     1 1 1 sub stringSignatureSurveyFromSource {
1025 1         3 my ($source) = @_;
1026              
1027 1         3567 my @aToken = $source =~ /($rexMatch)/gm;
1028             # print Dumper(\@aToken);
1029             my $signature = join(
1030             "",
1031 1         18 map { $self->_stringReplace($_) } @aToken,
  695         566  
1032             );
1033              
1034             #Remove closing " and ', they just clutter things up
1035 1         56 $signature =~ s/(["'])\1/$1/gsm;
1036              
1037             #Remove empty {}, they most often indicate hash accesses or derefs
1038 1         9 $signature =~ s/{}//gsm;
1039              
1040             #Remove =['"]+ that's a sign of quotes inside POD text
1041 1         18 $signature =~ s/=['"]+/=/gsm;
1042              
1043 1         31 return($signature);
1044             }
1045              
1046              
1047              
1048              
1049              
1050             =head1 IMPLEMENTATION METHODS
1051              
1052              
1053             =head2 oLocationOfNode($oNode, [$extraRow = 0, $extraCol = 0])
1054              
1055             Return Devel::PerlySense::Document::Location object for $oNode.
1056              
1057             If $extraRow or $extraCol are passed, add that to the location.
1058              
1059             =cut
1060 415     415 1 345 sub oLocationOfNode {
1061 415         373 my ($oNode, $extraRow, $extraCol) = @_;
1062 415   50     1066 $extraRow ||= 0;
1063 415   50     853 $extraCol ||= 0;
1064              
1065             return(
1066 415         6102 Devel::PerlySense::Document::Location->new(
1067             file => $self->file,
1068             row => $oNode->location->[0] + $extraRow,
1069             col => $oNode->location->[1] + $extraCol,
1070             )
1071             );
1072             }
1073              
1074              
1075              
1076              
1077              
1078             =head2 aDocumentFind($what)
1079              
1080             Convenience wrapper around $self->$oDocument->find($what) to account
1081             for the unusable api.
1082              
1083             Return list of matching nodes, or an empty list if none was found.
1084              
1085             =cut
1086 337     337 1 10502 sub aDocumentFind {
1087 337         551 my ($what) = @_;
1088 337         5551 return($self->aNodeFind($self->oDocument, $what));
1089             }
1090              
1091              
1092              
1093              
1094              
1095             =head2 aNodeFind($oNode, $what)
1096              
1097             Convenience wrapper around $oNode->find($what) to account
1098             for the unusable api.
1099              
1100             Return list of matching nodes, or an empty list if none was found.
1101              
1102             =cut
1103 337     337 1 1665 sub aNodeFind {
1104 337         586 my ($oNode, $what) = @_;
1105 337 50       1757 my $raList = $oNode->find($what) or return();
1106 0         0 return(@$raList);
1107             }
1108              
1109              
1110              
1111              
1112              
1113             =head2 oLocationEnclosingSub($oNode)
1114              
1115             Return a Document::Location object that is the enclosing sub of
1116             $oNode, i.e. $oNode is located within the sub block. The Location
1117             object has the following rhProperty keys:
1118              
1119             nameSub
1120             source
1121             oLocationEnd with: row and col
1122              
1123             Return Location object with the sub, or undef if none was found. Die on
1124             errors.
1125              
1126             =cut
1127 7     7 1 11 sub oLocationEnclosingSub {
1128 7         10 my ($oNode) = @_;
1129              
1130             #Simplification: assume there is only one sub on each row
1131              
1132 7         9 my ($row, $col) = @{$oNode->location};
  7         22  
1133 7         68 for my $oLocation (@{$self->oMeta->raLocationSub}) {
  7         104  
1134 85 100 66     3285 if($row >= $oLocation->row && $row <= $oLocation->rhProperty->{oLocationEnd}->row) {
1135 7         213 return($oLocation);
1136             }
1137             }
1138              
1139              
1140 0         0 return(undef);
1141             }
1142              
1143              
1144              
1145              
1146              
1147             =head1 CACHE METHODS
1148              
1149              
1150             =head2 cacheSet($key, $file, $rValue)
1151              
1152             If a cache is active, store the $value in the cache under the total
1153             key of ($file, $file's timestamp, $key).
1154              
1155             $value should be a scalar or reference which can be freezed.
1156              
1157             $file must be an existing file.
1158              
1159             Return 1 if the $value was stored, else 0. Die on errors.
1160              
1161             =cut
1162 729     729 1 4668 sub cacheSet {
1163 729         1531 my ($key, $file, $rValue) = @_;
1164 729         10179 return( $self->oPerlySense->cacheSet(file => $file, key => $key, value => $rValue) );
1165             }
1166              
1167              
1168              
1169              
1170              
1171             =head2 cacheGet($key, $file)
1172              
1173             If a cache is active, get the value in the cache under the total key
1174             of ($file, $file's timestamp, $key).
1175              
1176             $file must be an existing file.
1177              
1178             Return the value, or undef if the value could not be fetched. Die on
1179             errors.
1180              
1181             =cut
1182 756     756 1 1350 sub cacheGet {
1183 756         1032 my ($key, $file) = @_;
1184 756         10819 my $rValue = $self->oPerlySense->cacheGet(file => $file, key => $key);
1185 756         8864 return($rValue);
1186             }
1187              
1188              
1189              
1190              
1191              
1192             1;
1193              
1194              
1195              
1196              
1197              
1198             __END__
1199              
1200             =encoding utf8
1201              
1202             =head1 AUTHOR
1203              
1204             Johan Lindstrom, C<< <johanl@cpan.org> >>
1205              
1206             =head1 BUGS
1207              
1208             Please report any bugs or feature requests to
1209             C<bug-devel-perlysense@rt.cpan.org>, or through the web interface at
1210             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Devel-PerlySense>.
1211             I will be notified, and then you'll automatically be notified of progress on
1212             your bug as I make changes.
1213              
1214             =head1 ACKNOWLEDGEMENTS
1215              
1216             =head1 COPYRIGHT & LICENSE
1217              
1218             Copyright 2005 Johan Lindstrom, All Rights Reserved.
1219              
1220             This program is free software; you can redistribute it and/or modify it
1221             under the same terms as Perl itself.
1222              
1223             =cut