探究PHP_CodeSniffer的代码静态分析原理(二)

使用PHP_CodeSniffer定制规则

在了解了词法分析的原理后(探究PHP_CodeSniffer的代码静态分析原理(一)),我们现在尝试使用PHP_CodeSniffer定制一条简单的规则——“禁止使用#号进行单行注释”。
有问题的测试代码:

<?php
# Check for valid contents.
if ($obj->contentsAreValid($array)) {
    $value = $obj->getValue();
    # Value needs to be an array.
    if (is_array($value) === false) {
        # Error.
        $obj->throwError();
        exit();
    }
}
?>

测试代码中有三处都使用了#号进行单行注释操作,这是不允许的。我们该如何使用PHP_CodeSniffer编写新规则呢?

1. 规则库目录介绍

首先PHP_CodeSniffer的所有规则都存放在/src/Standards/目录下,默认该目录下已经有Generic、PEAR、PSR1、PSR2、PSR12、Squiz、Zend等目录,每一个目录其实就是一个规则库。如果想使用其中某一个规则库,例如PEAR规则库,运行时加入参数--standard=D:/git/PHP_CodeSniffer/src/Standards/PEAR,扫描时就会使用该规则库进行扫描。

2. 创建新规则库目录

我们在/src/Standards/目录下新建一个文件夹,命名为FireLine,即规则库名为FireLine,然后在FireLine文件夹中新建Sniffs文件夹和ruleset.xml文件。其中ruleset.xml的内容如下:

<?xml version="1.0"?>
<ruleset name="FireLine">
 <description>360 FireLine rule for test.</description>
</ruleset>

里面定义了规则库的名称和描述。

3. 创建规则实现文件

然后在Sniffs文件夹中新建Commenting文件夹,代表了一个更细的注解分类,接着这个文件夹里面新建php文件DisallowHashCommentsSniff.php(每个规则实现文件对应一个Sniff结尾的php文件),规则实现的内容如下:

<?php
/**
 * This sniff prohibits the use of Perl style hash comments.
 *
 * PHP version 5
 *
 * @category  PHP
 * @package   PHP_CodeSniffer
 * @author    Your Name <you@domain.net>
 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
 * @link      http://pear.php.net/package/PHP_CodeSniffer
 */

namespace PHP_CodeSniffer\Standards\FireLine\Sniffs\Commenting;

use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;

class DisallowHashCommentsSniff implements Sniff
{
    /**
     * Returns the token types that this sniff is interested in.
     *
     * @return array(int)
     */
    public function register()
    {
        return array(T_COMMENT);
    }//end register()


    /**
     * Processes this sniff, when one of its tokens is encountered.
     *
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being checked.
     * @param int                         $stackPtr  The position of the current token in the
     *                                               stack passed in $tokens.
     *
     * @return void
     */
    public function process(File $phpcsFile, $stackPtr)
    {
        $tokens = $phpcsFile->getTokens();
        if ($tokens[$stackPtr]['content']{0} === '#') {
            $error = '禁止使用#号进行单行注释;扫描发现 %s';
            $data  = array(trim($tokens[$stackPtr]['content']));
            $phpcsFile->addError($error, $stackPtr, 'Found', $data);
        }
    }//end process()
}//end class
?>

4. 规则实现详解

首先每个sniff类必须实现Sniff接口,该接口内有两个必须要实现的方法:register()和process()方法。

首先通过调用register()方法告诉PHP_CodeSniffer我们要检查编码标准哪些方面(也就是我们要查找哪些类型的TOKEN)。然后当词法解析引擎碰到这些TOKEN时就会调用process()方法来做进一步处理。

在该文件中,我们可以看到register()方法中是想查找T_COMMENT类型的TOKEN,通过PHP官网提供的TOKEN列表中得到T_COMMENT对应的PHP语法为// 或 #,以及 PHP 5 下的 /* */,即PHP语法中的单行注释。所以说,当词法解析引擎遇到单行注释类别的TOKEN时,就会自动继续调用process()方法。

我们接着来看process()方法,该方法有两个参数,第一个是$phpcsFile对象,即当前正在被处理的代码文件对象;第二个是$stackPtr参数,这个参数的意思是我们当前关注的TOKEN-即代表着单行注释的TOKEN(T_COMMENT)在TOKEN序列中的索引。这里正好回应了上篇文章提到的PHP词法分析原理,将PHP源文件解析成一个TOKEN序列,而$stackPtr参数表示当前TOKEN在这个TOKEN序列的索引位置。

接下来是process()方法内的实现,首先通过PHP_CodeSniffer封装的getTokens()方法来获得当前文件的TOKEN序列。在通过索引获取到我关注的T_COMMENT对应的TOKEN后,进一步获取TOKEN数组里面的content索引对应的内容。

TOKEN数组里面包含了code、type、content这三种索引,分别对应的内容是TOKEN代号唯一值、TOKEN代号即T_COMMENT、TOKEN所对应的代码。所以判断条件里面的$tokens[$stackPtr]['content']{0}的意思是取TOKEN序列中我们所关注的T_COMMENT对应的TOKEN,然后取这个TOKEN中对应的代码中的第一个字符。如果这个字符是#,说明触发了单行注释禁止使用#号的规则。我们最后通过addError()方法来记录触发规则的TOKEN和对应的代码,以及我们的规则解释。

5. 规则运行

规则实现完成后,我们运行一下看一下效果:

php D:/git/PHP_CodeSniffer/bin/phpcs 
--standard=D:/git/PHP_CodeSniffer/src/Standards/FireLine
D:/git/PHP_CodeSniffer/src/Standards/FireLine/Tests
--report=xml --report-file=E:/RedlineReport/php_report01.xml

其中--standard参数就是指定运行我们自定义的FireLine规则库。
D:/git/PHP_CodeSniffer/src/Standards/FireLine/Tests目录中存放了有问题的测试代码文件。
最后生成的XML报告文件内容:

<?xml version="1.0" encoding="UTF-8"?>
<phpcs version="3.3.1">
xml version="1.0" encoding="UTF-8"?>
<file name="D:\git\PHP_CodeSniffer\src\Standards\FireLine\Tests\Commenting\test01.php" errors="3" warnings="0" fixable="0">
    <error line="3" column="1" source="FireLine.Commenting.DisallowHashComments.Found" severity="5" fixable="0">禁止使用#号进行注释;扫描发现 # Check for valid contents.</error>
    <error line="7" column="5" source="FireLine.Commenting.DisallowHashComments.Found" severity="5" fixable="0">禁止使用#号进行注释;扫描发现 # Value needs to be an array.</error>
    <error line="9" column="9" source="FireLine.Commenting.DisallowHashComments.Found" severity="5" fixable="0">禁止使用#号进行注释;扫描发现 # Error.</error>
</file>
</phpcs>

从报告中可以看到,之前准备测试代码文件中的三处错误,都能成功检查出来。

参考文章:
Coding Standard Tutorial

Qtest是360旗下的专业测试团队!

是WEB平台部测试技术平台化、效率化的先锋力量!


陪伴是最长情的告白

每日为你推送最in的测试技术

识别二维码

关注我们