NAME

perlvars - A command line utility for detecting unused Perl variables

VERSION

version 0.000008

SYNOPSIS

Detect unused variables in Perl code.

perlvars lib/Foo.pm lib/Foo/Bar.pm

PERL5OPT="-I." perlvars Foo.pm Baz.pm

You can also ignore arbitrary variables on a per-package basis, using an ignore file.

perlvars --ignore-file ignore-list.txt lib/Foo.pm lib/Foo/Bar.pm

See the documentation for App::perlvars for the format of the ignore file.

By default, files with no package declaration (most .t and .pl scripts) are skipped. Pass --scripts to lint those too.

perlvars --scripts t/some-test.t script/some-tool.pl

If you'd like to check every .pm file in your lib directory, you can try something like:

find lib | grep pm$ | xargs perlvars

DESCRIPTION

This script (which is based heavily on the code in Code::TidyAll::Plugin::Test::Vars) is a wrapper around Test::Vars, which tries to find unused variables in your Perl code. Because Test::Vars only finds unused variables contained within packages, code without an explicit package declaration is skipped by default.

Pass --scripts to also lint package-less files (such as most .t and .pl scripts). With that flag, perlvars wraps their contents in a synthetic package and subroutine before analysis, so their variables are checked too; reported line numbers refer to the original file. This is opt-in because it turns on linting for files that were never linted before, which can surface pre-existing findings, and because wrapping executes the file's compile-time code (see below).

Wrapping the body in a subroutine means the file's runtime statements are not executed during analysis (a .t script's tests do not actually run), but compile-time code still does: Test::Vars requires the wrapped file, so its use statements and BEGIN blocks are executed as the file is compiled. Only pass --scripts for files you trust, exactly as you would before running them.

A package-less file that cannot be compiled in isolation is skipped silently (no analysis, no error) even when --scripts is given. This includes files that locate a sibling library at runtime (for example via FindBin and use lib) and files that use a module which is not installed in the environment perlvars runs in. A file that contains a #line directive is likewise not analyzed, because the directive changes the filename Test::Vars attributes subroutines to.

USING perlvars WITH precious

precious is a code quality tool that runs multiple linters and tidiers from a single configuration. You can add perlvars as a lint command in your precious.toml:

[commands.perlvars]
type    = "lint"
include = ["**/*.pm"]
cmd     = ["perlvars"]
ok-exit-codes = [0]
# perlvars exits 255 (not 1) when it finds unused/single-use variables.
lint-failure-exit-codes = [255]

The lint-failure-exit-codes setting is important: perlvars exits with 255 (not 1) when it finds unused or single-use variables, so precious needs to be told to treat 255 as a lint failure rather than an error.

To also lint your .t and .pl scripts, add --scripts to cmd and widen include to match them:

[commands.perlvars]
type    = "lint"
include = ["**/*.pm", "**/*.t", "**/*.pl"]
cmd     = ["perlvars", "--scripts"]
ok-exit-codes = [0]
lint-failure-exit-codes = [255]

CAVEATS

As noted above, there are some serious limitations to this script, due to the way that Test::Vars works. You're strongly encouraged to consider using Perl::Critic::Policy::Variables::ProhibitUnusedVarsStricter if that's a possibility for you.

Package-less files (such as most .t and .pl scripts) are supported when you pass --scripts: perlvars wraps their contents in a synthetic package and subroutine before analysis. Only variables that Test::Vars can detect are reported -- in practice, lexicals inside named or anonymous subroutines (including the anonymous subs passed to subtest and similar). File-scope (top-level) lexicals are not reported, matching how Test::Vars already ignores file-scope lexicals in a file that declares a package. Because these findings are reported against the main package, an ignore file uses main as the package name to suppress a variable in a package-less file (for example, main = $wanted). The caveats below apply to files that do declare a package.

Your code needs an explicit package name.

package Foo::Bar;
...
1;

The package name needs to match the file name, so the package above needs to be in a file named Foo/Bar.pm.

The package needs be in @INC or in a ./lib folder. This means that for the example above, either of these should work:

perlvars lib/Foo/Bar.pm

cd lib && PERL5OPT="-I." perlvars Foo/Bar.pm

ACKNOWLEDGEMENTS

The code in this module is largely copied directly from Code::TidyAll::Plugin::Test::Vars.

SEE ALSO

You may also wish to use Perl::Critic::Policy::Variables::ProhibitUnusedVarsStricter which can find some cases which Test::Vars is not able to detect. It also does not require the code to be inside a package.

AUTHOR

Olaf Alders <olaf@wundercounter.com>

COPYRIGHT AND LICENSE

This software is copyright (c) 2022 by MaxMind, Inc.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.