Pre-release

@gsherwood gsherwood released this Feb 5, 2014 · 3192 commits to master since this release

Assets 2

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

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 addError() 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');

To 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/src/Fixer.php

Removal of the Comment Parser

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

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.

Other Changes

  • Added the phpcbf script to automatically fix many errors found by the phpcs script
  • Added report type --report=diff to show suggested changes to fix coding standard violations
  • The --report argument now allows for custom reports to be used
    • Use the full path to your custom report class as the report name
  • The --extensions argument is now respected when passing filenames; not just with directories
  • The --extensions argument now allows you to specify the tokenizer for each extension
    • e.g., --extensions=module/php,es/js
  • Command line arguments can now be set in ruleset files
    • e.g., arg name="report" value="summary" (print summary report; same as --report=summary)
    • e.g., arg value="sp" (print source and progress information; same as -sp)
    • The -vvv, --sniffs, --standard and -l command line arguments cannot be set in this way
  • Sniff process() methods can not optionally return a token to ignore up to
    • If returned, the sniff will not be executed again until the passed token is reached in the file
    • Useful if you are looking for tokens like T_OPEN_TAG but only want to process the first one
  • Removed the comment parser classes and replaced it with a simple comment tokenier
    • T_DOC_COMMENT tokens are now tokenized into T_DOC_COMMENT_* tokens so they can be used more easily
    • This change requires a significant rewrite of sniffs that use the comment parser
    • This change requires minor changes to sniffs that listen for T_DOC_COMMENT tokens directly
  • Added Generic DocCommentSniff to check generic doc block formatting
    • Removed doc block formatting checks from PEAR ClassCommentSniff
    • Removed doc block formatting checks from PEAR FileCommentSniff
    • Removed doc block formatting checks from PEAR FunctionCommentSniff
    • Removed doc block formatting checks from Squiz ClassCommentSniff
    • Removed doc block formatting checks from Squiz FileCommentSniff
    • Removed doc block formatting checks from Squiz FunctionCommentSniff
    • Removed doc block formatting checks from Squiz VariableCommentSniff
  • Squiz DocCommentAlignmentSniff has had its error codes changed
    • NoSpaceBeforeTag becomes NoSpaceAfterStar
    • SpaceBeforeTag becomes SpaceAfterStar
    • SpaceBeforeAsterisk becomes SpaceBeforeStar
  • Generic MultipleStatementAlignment now aligns assignments within a block so they fit within their max padding setting
    • The sniff previously requested the padding as 1 space if max padding was exceeded
    • It now aligns the assignment with surrounding assignments if it can
    • Removed property ignoreMultiline as multi-line assignments are now handled correctly and should not be ignored
  • Squiz FunctionClosingBraceSpaceSniff now requires a blank line before the brace in all cases except function args
  • Added error Squiz.Commenting.ClassComment.SpacingAfter to ensure there are no blank lines after a class comment
  • Added error Squiz.WhiteSpace.MemberVarSpacing.AfterComment to ensure there are no blank lines after a member var comment
    • Fixes have also been corrected to not strip the member var comment or indent under some circumstances
    • Thanks to Mark Scherer for help with this fix
  • Added error Squiz.Commenting.FunctionCommentThrowTag.Missing to ensure a throw is documented
  • Removed error Squiz.Commenting.FunctionCommentThrowTag.WrongType
  • Content passed via STDIN can now specify the filename to use so that sniffs can run the correct filename checks
    • Ensure the first line of the content is: phpcs_input_file: /path/to/file
  • Squiz coding standard now enforces no closing PHP tag at the end of a pure PHP file
  • Squiz coding standard now enforces a single newline character at the end of the file
  • Squiz ClassDeclarationSniff no longer checks for a PHP ending tag after a class definition
  • Squiz ControlStructureSpacingSniff now checks TRY and CATCH statements as well
  • Removed MySource ChannelExceptionSniff