Squiz Labs Blog - The latest news from the R&D division of Squiz®

Subscribe to our RSS feeds

PHP_CodeSniffer 2.0.0 alpha1 released

I've just released the first alpha of PHP_CodeSniffer version 2.0.0. This update brings an often requested feature; the ability for PHP_CodeSniffer to automatically fix the problems that it finds. It also contains a complete rewrite of the comment parsing sniffs, finally removing what I feel is the poorest code in PHP_CodeSniffer; the comment parsing classes.

Fixing Errors Automatically with PHPCBF

PHP_CodeSniffer now comes with a second script; phpcbf, the PHP Code Beautifier and Fixer. This script piggy-backs off PHP_CodeSniffer to provide fixes for a lot of common errors. It will never fix 100% of the errors PHP_CodeSniffer finds as many require a developer to make a decision (e.g., can you use === here?) or may cause the code to execute differently if changed (e.g., uppercasing a constant name). But there are still a lot of errors that can be corrected without causing issues. Out of the 566 unique error messages that PHP_CodeSniffer can currently produce, version 2.0.0a1 is able to correct 202 of them automatically, which is about 35%. When you run PHP_CodeSniffer and get a report of the errors found, you will now be able to see which of those errors can be automatically corrected.

Along with this new script, PHP_CodeSniffer adds a new report type; the diff report. If you don't like the idea of PHP_CodeSniffer fixing errors automatically, you can instead ask it to output a diff of the fixes that it would make using the command line argument --report=diff. If you like what you see, you can simply change the phpcs command to phpcbf, leave all the command line options the same, and let PHP_CodeSniffer patch your files.

All this new functionality is documented on the wiki, and includes sample output, so please take a read: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Fixing-Errors-Automatically

Plugin for Sublime Text

I use Sublime Text 2 for development, as do some of the other developers at Squiz Labs. I've been working with one of these developers to build a plugin for Sublime Text that will run PHP_CodeSniffer on the current file, show which errors can be fixed automatically, fix the errors for you if you decide to have them fixed, and show a diff of what it did. I've added quite a few features to PHP_CodeSniffer to make this plugin feel as integrated as possible and I find it incredibly useful for tidying up my code before committing.

The plugin is still being documented, and made to work with Sublime Text 3 due to some issues with the diff library, but it will be released through Package Control as soon as they are worked out (or sooner, for ST2 only if needed). In the meantime, you can clone the repository yourself or just watch it for activity if you are interested in knowing when it is ready: https://github.com/squizlabs/sublime-PHP_CodeSniffer

Adding Fixes to Custom Sniffs

If you have your own custom sniffs and want to correct errors automatically, you need to make a couple of changes. The first thing you need to do is call the addFixableError() or addFixableWarning() methods instead of addErorr() and addWarning(), to let PHP_CodeSniffer know the error can be fixed. You can then make use of the new PHP_CodeSniffer_Fixer class to replace token values and add content to tokens, modifying the token stack as you go.

Here is a simple example, to ensure that comments don't appear at the end of a code line ($stackPtr is the comment token):

$error = 'Comments may not appear after statements';
$phpcsFile->addError($error, $stackPtr, 'Found');

The fix this, we just need to add a newline before the comment. Other sniffs will do things like fix alignment and spacing of the comment for us. It is also important we check if the fixer is enabled and if we are supposed to be fixing this specific error message. We end up with code like this:

$error = 'Comments may not appear after statements';
$fix   = $phpcsFile->addFixableError($error, $stackPtr, 'Found');
if ($fix === true && $phpcsFile->fixer->enabled === true) {
    $phpcsFile->fixer->addNewlineBefore($stackPtr);
}

If you are changing multiple tokens in a single fix, using a changeset will ensure that they are either all applied at once, or not applied at all. This is important if a partial change would lead to a parse error, or another equally bad outcome. In the following example, a changeset is used to ensure that all content between the array braces is removed, even if the content spans multiple lines. If one of the tokens to be removed has already been modified by another sniff, the whole changeset will be ignored and PHP_CodeSniffer will attempt to apply this changeset on a second run through the file.

$error = 'Empty declaration must have no space between parentheses';
$fix   = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceFound');
if ($fix === true && $phpcsFile->fixer->enabled === true) {
    $phpcsFile->fixer->beginChangeset();
    for ($i = ($arrayStart + 1); $i < $arrayEnd; $i++) {
        $phpcsFile->fixer->replaceToken($i, '');
    }

    $phpcsFile->fixer->endChangeset();
}

Take a look at the PHP_CodeSniffer_Fixer class for all the methods you can use: https://github.com/squizlabs/PHP_CodeSniffer/blob/master/CodeSniffer/Fixer.php

Removal of the Comment Parser

The other major change is the removal of the comment parsing classes. If you have written custom sniffs that either use the comment parsing classes or extend sniffs that do, you are going to need to review your sniffs and possibly rewrite them. This has already been done for all included sniffs, so there are quite a few examples to help you get started.

The best way to explain the change is to show an example of how PHP_CodeSniffer has changed the way it handles doc comments internally. If PHP_CodeSniffer is processing the following comment:

/**
 * PHP_CodeSniffer tokenises PHP code.
 *
 * @author    Greg Sherwood <gsherwood@squiz.net>
 * @copyright 2006-2012 Squiz Pty Ltd
 */

It would have previously tokenized it like this (spaces have been replaced by periods):

T_DOC_COMMENT => /**\n
T_DOC_COMMENT =>  * PHP_CodeSniffer.tokenises.PHP.code.\n
T_DOC_COMMENT =>  *\n
T_DOC_COMMENT =>  * @author....Greg.Sherwood.<gsherwood@squiz.net>\n
T_DOC_COMMENT =>  * @copyright.2006-2012.Squiz.Pty.Ltd\n
T_DOC_COMMENT =>  */

This format makes it very hard to look for specific things like a comment tag name, and makes it very hard to make fixes to the comment automatically. So PHP_CodeSniffer will now tokenize the comment like this:

T_DOC_COMMENT_OPEN_TAG => /**
T_DOC_COMMENT_WHITESPACE => \n
T_DOC_COMMENT_WHITESPACE => .
T_DOC_COMMENT_STAR => *
T_DOC_COMMENT_WHITESPACE => .
T_DOC_COMMENT_STRING => PHP_CodeSniffer.tokenises.PHP.code.
T_DOC_COMMENT_WHITESPACE => \n
T_DOC_COMMENT_WHITESPACE => .
T_DOC_COMMENT_STAR => *
T_DOC_COMMENT_WHITESPACE => \n
T_DOC_COMMENT_WHITESPACE => .
T_DOC_COMMENT_STAR => *
T_DOC_COMMENT_WHITESPACE => .
T_DOC_COMMENT_TAG => @author
T_DOC_COMMENT_WHITESPACE => ....
T_DOC_COMMENT_STRING => Greg.Sherwood <gsherwood@squiz.net>
T_DOC_COMMENT_WHITESPACE => \n
T_DOC_COMMENT_WHITESPACE => .
T_DOC_COMMENT_STAR => *
T_DOC_COMMENT_WHITESPACE => .
T_DOC_COMMENT_TAG => @copyright
T_DOC_COMMENT_WHITESPACE => .
T_DOC_COMMENT_STRING => 2006-2012.Squiz.Pty.Ltd
T_DOC_COMMENT_WHITESPACE => \n
T_DOC_COMMENT_WHITESPACE => .
T_DOC_COMMENT_CLOSE_TAG => */

As you can see, this is a significant number of extra tokens, but allows for much finer control over how a comment is processed. The T_DOC_COMMENT token has also been removed, replaced instead by T_DOC_COMMENT_OPEN_TAG. If you have a sniff listening for T_DOC_COMMENT, make sure you change it in your register() method and anywhere else it is used throughout your sniffs.

Here is a fairly complex example of a sniff rewrite; the Squiz FunctionComment sniff:

old: f913dbd4540fde475282a43ca4084dc8bc65a7a3
new: 3e408ab1b3202a9cc97685cabf09adb2a0023155

And here is the commit of the changes required for the new comment tokenizer: 2b2afb4fd49071b1b6792611a908a5cda0fc7b38

Essentially, instead of using the comment parser functions to get at tag values, you can use the standard PHP_CodeSniffer functions to navigate the token stack and look for what you need. This allows you to get a precise location of each piece of the comment and make fixes to it if you need to. You can also report more accurately on where an error has occurred.

Everything Else

Besides these major changes, there are a few important new features that have made it into version 2.0.0a1, including the ability to write your own custom report classes, the ability to set default command line argument values in ruleset XML files, and an easier way for custom sniff developers to skip through the token tree.

View the full 2.0.0a1 changelog at PEAR or Github

Stay up to date on all PHP_CodeSniffer changes, including new features and releases, by subscribing to the RSS feed or following me on Twitter.

Squiz Labs

R & D division of Squiz Pty Ltd

Open source web experience management solutions

Squiz Labs is the research and development division of Squiz, the company behind the Squiz Suite of web experience management and development tools.

Our PHP and JavaScript developers are responsible for producing innovative enterprise-quality software while our interface designers and testing team ensure our products both look great and are easy to use.