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:
- Create the directories
- Create the XML file
- 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”
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