有没有一种工具可以检查Perl脚本中不必要的use语句?

20

针对 Python,有一个名为 importchecker 的脚本,它可以告诉您是否存在不必要的 import 语句。

那么,Perl 中是否有类似的工具用来检查 userequire 语句?


1
请注意,没有任何解决方案是100%准确的。use Module;可能会产生虚阴性结果。 use strict qw( refs );可能会产生错误阳性结果。use Module qw( :ALL );可能会同时产生虚阴性和错误阳性结果。 - ikegami
1
需要检查什么?(1)简单的use Mod;可能会导入符号 - 检查所有这些吗? (2)还是只针对在use Mod qw(...);中列出的那些? (3)use Module qw();怎么样(代码中没有使用Mod子例程)? 这是关于可以完全删除的use语句,还是与特定(未使用的)符号相关的特定use语句?(我只知道(2)的perlcritic策略。) - zdim
为什么会有实际问题(是什么问题?)而不只是美学问题?(例如符号污染等) - zdim
1
@zwol 我只找到了一些情况(2)的perlcritic“策略”,[TooMuchCode :: ProhibitUnusedImport](https://metacpan.org/pod/Perl::Critic::Policy::TooMuchCode::ProhibitUnusedImport)。看一下。至于其他部分,可能会有点混乱(根据我所见),但是有工具可以帮助解决。(如果有帮助,我可以发布一个带有一些详细说明和示例的答案,但我没有全面的解决方案。) - zdim
@zwol 编辑了有关策略用法的补充说明,并添加了有关如何使用B::Xref的注释。 - zdim
显示剩余5条评论
3个回答

5

1
那似乎展示了正在被加载的内容。我想知道正在被加载但未被使用的内容。 - Dennis Williamson
我不知道你如何在没有一些相当恶心的解析器的情况下进行静态分析,但对于动态分析,你可以将TraceUse与NYTProf结合使用,并编写一些代码来确定所有已使用模块中可用的所有可能方法和子程序,然后从NYTProf列出的调用的方法和子程序中减去。 - Len Jaffe

4

有多种方法可以加载包并导入符号(或不导入)。我不知道有一个工具可以单独直接检查这些符号是否被使用。

但是对于明确给出导入列表的情况,

use Module qw(func1 func2 ...);

有一个Perl::Critic策略 TooMuchCode::ProhibitUnusedImport 可以帮助解决很多相关问题。

其中一个可以在命令行中运行。

perlcritic --single-policy TooMuchCode::ProhibitUnusedImport program.pl

程序会被检查。或者使用不带--single-policy标志的方式运行完成检查,并在输出中寻找严重程度1违规,这个就是。

以一个程序为例:

use warnings;
use strict;
use feature 'say';

use Path::Tiny;                      # a class; but it imports 'path'    
use Data::Dumper;                    # imports 'Dumper' 
use Data::Dump qw(dd pp);            # imports 'dd' and 'pp'
use Cwd qw(cwd);                     # imports only 'cwd'
use Carp qw(carp verbose);           # imports 'carp'; 'verbose' isn't a symbol
use Term::ANSIColor qw(:constants);  # imports a lot of symbols

sub a_func {
    say "\tSome data: ", pp [ 2..5 ];
    carp "\tA warning";
}

say "Current working directory: ", cwd;

a_func();

运行上述perlcritic命令会输出:

Unused import: dd at line 7, column 5.  A token is imported but not used in the same code.  (Severity: 1)
Unused import: verbose at line 9, column 5.  A token is imported but not used in the same code.  (Severity: 1)

我们发现dd被捕捉到了,而来自同一包的pp没有被标记,因为它被使用了(在子例程中),carpcwd也没有被报告,因为它们也被使用了。这是该策略的目标。

但请注意:

  • 使用:constants标签附带的内容未被找到

  • 单词verbose不是函数(并且是隐式使用的),被报告为未使用

  • 如果a_func()未被调用,则其中的ppcarp仍然未被报告,即使它们未被使用。这可能是可以接受的,因为它们在代码中存在,但值得注意

(此错误列表可能不全面。)

请记住,导入列表被传递给一个import子例程,它可能希望并使用模块的设计认为重要的任何内容;这些不一定只是函数名。显式导入只有函数名称的模块是良好实践,而此策略覆盖的是重要的用例。

此外,根据明确规定的策略使用,Dumper(由Data::Dumper导入)和来自Path::Tinypath都未找到。该策略处理了一些奇怪的Moose技巧。

如何做更多?一个有用的工具是Devel::Symdump,它收集符号表。它捕捉到上面程序中导入的所有符号(当然,如果使用了,则不能看到Path::Tiny方法)。包括不存在的“符号”verbose。添加:

use Devel::Symdump;

my $syms = Devel::Symdump->new;
say for $syms->functions;

针对上面的例子,为了处理运行时require的库,我们需要在代码中它们已被加载之后的某个位置进行操作,这个位置可以在程序的任何地方。然后最好放在一个END块中,例如:

END {
    my $ds = Devel::Symdump->new;
    say for $ds->functions;
};

接下来我们需要检查哪些是未被使用的。目前我所知道最好的工具是PPI,可以参考完整示例。另一种选择是使用像Devel::NYTProf这样的分析器。


另一个选项需要进行一些准备,那就是编译器的后端工具B::Xref,它几乎包含了程序中使用到的一切信息。使用方式如下:

perl -MO=Xref,-oreport_Xref.txt find_unused.pl

输出的结果保存在文件report_Xref.txt中。

输出有每个涉及文件的部分,其中包括子例程和其包的子部分。输出的最后一部分对于当前目的直接有用。

对于上面使用的示例程序,我得到的输出文件如下:

文件 /.../perl5/lib/perl5//Data/Dump.pm
  ... 
  (大约3,000行)
  ...
文件 find_unused.pl          --> 这是我们要找的程序的文件
  子例程(定义)
    ... 几十行 ...  
  子例程(main)
    包 main
      &a_func           &43
      &cwd              &27
  子例程 a_func
    包 ?
      @??               14
    包 main
      &carp             &15
      &pp               &14

因此,我们可以看到cwd在子例程a_func中被调用(在第27行),并且carppp也在该子例程中被调用。因此,在导入的符号中,ddpath没有被使用(使用其他方式找到的所有导入符号,例如Devel::Symdump)。这很容易解析。

但是,虽然在使用path时会报告其使用情况,但如果使用new (也是Path::Tiny中的传统构造函数)则不会在这个最后一部分中报告,其他方法也不会报告。

因此,原则上可以使用这种方法找到Devel::Symdump报告存在的哪些符号(用于函数)已被程序使用。


*这里的示例简单易懂,易于处理,但我不知道在考虑到各种奇怪的导入子程序使用方式时,这是否完整或难以解析。


4

这里有一个我写的脚本,可以尝试解决此问题。它非常简单,不能为您自动化任何操作,但可以作为入门参考。

#!/usr/bin/perl

use strict;
use v5.14;

use PPI::Document;
use PPI::Dumper;
use PPI::Find;
use Data::Dumper;

my %import;
my $doc = PPI::Document->new($ARGV[0]);

my $use = $doc->find( sub { $_[1]->isa('PPI::Statement::Include') } );
foreach my $u (@$use) {
    my $node = $u->find_first('PPI::Token::QuoteLike::Words');
    next unless $node;
    $import{$u->module} //= [];
    push $import{$u->module}, $node->literal;
}

my $words = $doc->find( sub { $_[1]->isa('PPI::Token::Word') } );


my @words = map { $_->content } @$words;

my %words;
@words{ @words } = 1;

foreach my $u (keys %import) {
    say $u;
    foreach my $w (@{$import{$u}}) {
        if (exists $words{$w}) {
            say "\t- Found $w";
        }
        else {
            say "\t- Can't find $w";
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接