NAME

PPI::Xref - generate cross-references for Perl code

DESCRIPTION

use PPI::Xref;

my $xref = PPI::Xref->new();  # Constructor.

PPI::Xref can be used to process files of Perl code or Perl code as a string, and then generate cross-references of its contents. The code is never executed, only parsed as a document tree.

NOTE: the cross-reference is not a call graph. Instead, it is a statically generated graph of file inclusions and named sub definitions.

NOTE: all the use/no/require/do are followed, which means that for example optional or platform specific modules will fail to be found. This is expected and fine.

Options

my $xref = PPI::Xref->new($opt);

The $opt is a href of options. Possible options:

recurse

Boolean, whether to recurse or not, default yes.

NOTE: even with false recurse the files referred to at the first level (for example, X.pm for use X) will be looked up. But no further descending into those files will happen, so subs from those files will not be found.

verbose

Boolean, if true, progress messages are shown during the processing.

INC

An aref of directories, default [ @INC ]. You can retrieve the aref (with non-accessible non-directories removed) as $xref->INC.

cache_directory

String, a directory name. For paranoia, the directory must exist. If defined, use for the PPI processing results of source files, including the (SHA-1) hash checksum of the file, and the results derived from PPI parsing.

Processing

Process one or more files. They are looked for in the @INC, or in the INC specified via the constructor.

$xref->process($filename, ...);

Process string of Perl code, pass it in as scalar ref. This will show up as filename '-'.

$xref->process(\$string);

Queries

Once you have processed all the inputs you can query the object for various results. More complex results need to queried using iterators.

files

Fully resolved filenames.

$xref->files

subs

Fully qualified subroutine names.

$xref->subs

packages

Package names in package statements.

$xref->packages

modules

Module names in use/no/require.

$xref->modules

file_count

The total number of times the file is referred to by file inclusion.

$xref->file_count($file)

module_count

The total number of times the module is referred to by use/no/require.

$xref->module_count($module)

file_lines

The number of lines in the file.

$xref->file_lines($file)

total_lines

The total number of lines in all the seen files.

$xref->total_lines()

subs_files_iter

my $sfi = $xref->subs_files_iter;
while (my $sf = $sfi->next) {
  my ($subname, $filename, $linenumber) = $sf->array;
  my $sfs = $sf->string;  # Single string concatenating the columns.
}

NOTE: the subnames are not necessarily unique, for multiple reasons:

BEGIN, END, and similar, can occur multiple times
simply the same sub defined in multiple files

The iter constructor can also take an options href:

separator

String for joining, for the string() method.

column

Boolean, whether the also column numbers should be returned.

finish

Boolean, should also the end line/column of the sub be returned.

packages_files_iter

Like subs_files_iter, but for packages.

my $pfi = $xref->packages_files_iter;

Options as with "subs_files_iter".

NOTE 1: since packages are lexically scoped, and since a single file can declare multiple packages, and since any file may declare any package, the same package name may be listed multiple times.

NOTE 2: the results of the finish option, and specially if combined with the column option, are sometimes slightly debatable: in which package do the whitespace tokens belong, for example?

incs_files_iter

File inclusion: any of use / no / require / do (file).

my $ifi = $xref->incs_files_iter;
while (my $if = $ifi->next) {
  # The $first_file line $linenumber includes the $second_file.
  my (
       $first_file,
       $linenumber,
       $second_file,    # full path filename
       $stmt_content,   # string: use/no/require/do
       $include_string, # module name (use/no/require) or filename (require/do)
       ...
     ) = $if->array;
  my $ifs = $if->string;  # Single string concatenating the columns.
}

Options as with "subs_files_iter". Depending on the column and finish options, the ... can have none to three elements (default none).

incs_chains_iter

Inclusion chains: starting from all the root files, singleton files (if any, see "incs_deps"), and then files given to process(), return the full inclusion chains of files. What this basically means is dependencies.

NOTE 1: this does not return all the possible paths, since that would be ill-defined for inclusion loops, and diamond patterns. What this returns is (starting from the roots and singletons), all the possible paths that do not cause loops, or in general visiting already seen files (counting from the current root). Another way of looking at the returned results is that if they would be overlaid, they would form a DAG (directed acyclic graph).

NOTE 2: all the possible paths does include multiple inclusions. This means that if both the lines A.pm:X and A.pm:Y include B.pm, both paths (the path through X, and the path through Y) are returned.

NOTE 3: there are MANY paths even for the simplest code.

my $ici = $xref->incs_chains_iter;
while (my $ic = $ici->next) {
  # The @ic is either of one element (a file that includes nothing)
  # or 3, 5, 7, ... elements (you can think of this as 2k + 1 where
  # k = 0, 1, 2...)
  # @ic[0, 2, 4, ...] are filenames.
  # @ic[1, 3, 5, ...] are linenumbers.
  my @ic = $ic->array;
  my $ics = $ic->string;  # Single string concatenating the columns.
}

You can specify separator in the options for the iterator constructor. (No column or finish options available, for simplicity.) You can also specify a boolean reverse_chains option to generate reverse dependencies.

incs_deps

Computes the depth-one dependency tree of all the seen files.

my $id = $xref->incs_deps;

$id->files()               # All the files.
$id->successors($file)     # The files included.
$id->predecessors($file)   # The files including.
$id->roots()               # File nobody is including.
$id->leaves()              # Files everybody is just including.
$id->branches()            # Both included and including.
$id->singletons()          # Neither included nor including.
$id->file_kind($file)      # 'root', 'leaf', 'branch', 'singleton', or C<undef>.

Note that the 'root', 'leaf', and 'branch' are relative terms: it all depends on which node you start from.

docs_created

How many PPI documents have been created. If cached results are being used, less documents need be created.

cache_reads

How many PPI processing results have been read from the cache results.

cache_writes

How many PPI processing results have been written to the cache results.

cache_delete

For cache maintenance you may want to delete cache files of removed source files. Detection of removed source files is outside the scope of PPI::Xref (either or both of version control system querying and file system traversal are likely), but if you know the files, you can use

$xref->cache_delete($file, ...)

The specified files have to be either original fully resolved filenames, (not the INC-relative ones), or the fully resolved cache filenames.

Deletions happen quietly: a missing cache file causes no warning.

Returns the number of successful deletions.

missing_modules

@modules = $xref->missing_modules()

Modules that were called for via use/no/require/do but which could not be found in %INC.

Common reasons include:

  • Modules that are conditionally invoked, for example based on the operating system, or configuration options.

  • Modules that are invoked based on a runtime value. PPI does not do runtime.

missing_module_count

$count = $xref->missing_module_count($modulename)

Given a name of a missing module, how many times it was referred.

missing_module_files

@files = $xref->missing_module_files($modulename)

Given a name of a missing module, returns the files that referred it.

missing_module_lines

@lines = $xref->missing_module_lines($modulename)

Given a name of a missing module, returns the lines that referred it.

parse_errors_files

@files = $xref->parse_errors_files()

Files that had parsing errors, which PPI could not handle.

Document incomplete is the most common parsing error. That means that after parsing the file, PPI was left with unbalanced state, like for example opened but not closed brace. This may have been caused by some earlier parsing issue.

file_parse_errors

$xref->file_parse_errors($filename)

Given a filename, return a hash of its parse errors. The keys are the error locations (which can be the whole file), the values are the error details (which can be just "Document incomplete" in the case of the whole file).

PREREQUISITES

perl 5.14, https://search.cpan.org/perldoc?PPI, https://search.cpan.org/perldoc?Sereal