It’s no surprise I like to learn new things. I also like to read things when I can. Okay, a lot of the time I skim through things but I make an effort to learn from it.

The last couple of weeks I’ve looked at working with the PHPCodeSniffer. I installed it and never really played with it. Not until last week did I realize my version was out of date. Easy fix but something to keep in mind. The current version at the time of this writing is 2.7.1 and that’s what I am currently running on my Windows machine as well as my MBP.

So, here are some of the things I learned from attempting to create a standard and a sniff.

Lessons Learned

  • Read the docs
  • Carefully read the docs
  • Experiment

One of the first things to understand is that you need to know where your PHPCS standards reside.  There you will see a series of folders and files. The one that we will focus on is the Standards folder. This is where our newly created Standards will live.

The steps

We will outline the steps needed to create both a standard as well as a sniff. They are:

  1. Create the directories
  2. Create the XML file
  3. Create the sniff or sniffs if you want more

Sounds super simple right?

So let’s move on!

The first thing we will do is create the directories. The first one is the name of our standard. The second directory will be Sniffs and this is where all of our sniffs will reside. For example, WordPress standards looks like the following image:

Inside of the folder we will then create the ruleset.xml file with the needed information for our standard. It will look something like the following image:

Now, we can finally create our sniffs. But wait there is one thing you should know before making this and that is that you need to have a category folder.

Why?

It makes it easier to organize and know what we are looking for as well. For the sake of this post, I created an Example folder and inside of that I created a ListSniff.php file. Pay close attention to that detail as it will matter down the line. I learned this one because of errors I kept getting.

So, our new folder structure should look a little like:

codesniffer\standards\
codesniffer\standards\YourStandard
codesniffer\standards\YourStandard\ruleset.xml
codesniffer\standards\YourStandard\Sniffs
codesniffer\standards\YourStandard\Sniffs\Example
codesniffer\standards\YourStandard\Sniffs\Example\ListSniff.php

Now we can create our sniff!

This is where the fun really begins for some.

The important thing here is both the file and the class inside of the file must end with Sniff. For my example I named it ListSniff.php so the class would look like:

<?php
/** Example class created for demonstration purposes only **/
class YourStandard_Sniffs_Example_ListSniff implements PHP_CodeSniffer_Sniff {

	public function register() {
		return array();
	} // end register()

	public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) {

		// Tokenize the file, right?
		$tokens = $phpcsFile->getTokens();

		// We need this in order show what the error actually is.
		$phpcsFile->addError( $error, $stackPtr, $code = '', $data = array(), $severity = 0, $fixable = false );

		// We can even add a warning if we want.
		$phpcsFile->addWarning( $warning, $stackPtr, $code = '', $data = array(), $severity = 0, $fixable = false );
	} //end process()
}

You will see two very interesting things here. The first one being the naming of the class. It needs to be:

class {Folder of the standard name}_{the Sniffs folder}_{subfolder of Sniffs}_{your sniff}Sniff

And the second being the implements PHP_CodeSniffer_Sniff. This is straight from their documentation:

Each sniff must implement the PHP_CodeSniffer_Sniff interface so that PHP_CodeSniffer knows that it should instantiate the sniff once it’s invoked.

The reason is because the PHP_CodeSniffer_Sniff interface has two methods that need to be utilized. The register() and process() methods.

The methods

The first one – register – simply does that. You register what tokens you want the CodeSniffer to catch. PHP.net does provide a super handy list with all the available tokens.

Now, let us take a look at how the addError and addWarning sort of work so you can get an idea of what you can do.

First the addError:

/**
@param string  $error    The error message.
@param int     $line     The line on which the error occurred.
@param int     $column   The column at which the error occurred.
@param string  $code     A violation code unique to the sniff message.
@param array   $data     Replacements for the error message.
@param int     $severity The severity level for this error. A value of 0
                         will be converted into the default severity level.
@param boolean $fixable  Can the error be fixed by the sniff?
*/

What’s really nice is that the addWarning does nearly the same:

/**
@param string  $warning  The error message.
@param int     $line     The line on which the warning occurred.
@param int     $column   The column at which the warning occurred.
@param string  $code     A violation code unique to the sniff message.
@param array   $data     Replacements for the warning message.
@param int     $severity The severity level for this warning. A value of 0
                         will be converted into the default severity level.
@param boolean $fixable  Can the warning be fixed by the sniff?
*/

With that I created a random test sniff. It turned out a little like:

class Examplee_Sniffs_Example_FunctionSniff implements PHP_CodeSniffer_Sniff {

	public function register() {
		return array( T_FUNCTION );
	} // end register()

	public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr ) {

		// Tokenize it!
		$tokens = $phpcsFile->getTokens();

		// We want to check.
		if ( $tokens[ $stackPtr ]['content'] ) {

			// build up our message to show on the report.
			$warn = 'Make sure %s is prefixed';

			// build up the array of data we want to pass
			$data = array( trim( $phpcsFile->getDeclarationName( $stackPtr ) ) );

			// finally we add our warning. Could use addError().
			$phpcsFile->addWarning( $warn, $stackPtr, 'found', $data );
		}
	} //end process()
}

I did try it out on Twenty Seventeen functions file and got the following:

FOUND 0 ERRORS AND 14 WARNINGS AFFECTING 14 LINES
----------------------------------------------------------------------
  27 | WARNING | Make sure twentyseventeen_setup is prefixed
 200 | WARNING | Make sure twentyseventeen_content_width is prefixed
 222 | WARNING | Make sure twentyseventeen_fonts_url is prefixed
 257 | WARNING | Make sure twentyseventeen_resource_hints is prefixed
 274 | WARNING | Make sure twentyseventeen_widgets_init is prefixed
 315 | WARNING | Make sure twentyseventeen_excerpt_more is prefixed
 336 | WARNING | Make sure twentyseventeen_javascript_detection is
     |         | prefixed
 344 | WARNING | Make sure twentyseventeen_pingback_header is
     |         | prefixed
 354 | WARNING | Make sure twentyseventeen_colors_css_wrap is
     |         | prefixed
 371 | WARNING | Make sure twentyseventeen_scripts is prefixed
 433 | WARNING | Make sure twentyseventeen_content_image_sizes_attr
     |         | is prefixed
 460 | WARNING | Make sure twentyseventeen_header_image_tag is
     |         | prefixed
 479 | WARNING | Make sure twentyseventeen_post_thumbnail_sizes_attr
     |         | is prefixed
 499 | WARNING | Make sure twentyseventeen_front_page_template is
     |         | prefixed
----------------------------------------------------------------------

So you can see some versatility when using the PHPCodeSniffer. If you really want to help out the Theme Review team then I highly recommend heading over to the github repo and helping out.

As for me, I’ll be digging more into how to create random tests because I like dabbling and breaking those things.


Comments

One response to “My tour of creating a PHPCodeSniffer Standard”

  1. Thank you for the blog post.

    The PHPCS project on GitHub has a good wiki to find information: https://github.com/squizlabs/PHP_CodeSniffer/wiki

    Like there is https://github.com/squizlabs/PHP_CodeSniffer/wiki/Annotated-ruleset.xml which explains how you can really extend your custom standard.

    Here are a few other custom sniffs that other people have created.

    https://github.com/humanmade/coding-standards
    https://github.com/Yoast/yoastcs
    https://github.com/10up/10up-code-review