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.