CodeSniffer Part 3: Writing an example CodeSniffer Standard

Last time we looked at some sample CodeSniffer reports and in the source report we saw that the Zend standard was also reporting errors from other standards.

$ phpcs --report=source --standard=Zend ./_rr

PHP CODE SNIFFER VIOLATION SOURCE SUMMARY
--------------------------------------------------------------------------------
STANDARD    CATEGORY            SNIFF                                      COUNT
--------------------------------------------------------------------------------
Zend        Files               Line length                                92
Generic     White space         Disallow tab indent                        65
Zend        Naming conventions  Valid variable name                        59
PEAR        White space         Scope closing brace                        33
PEAR        Functions           Function call signature                    26
PEAR        Control structures  Control signature                          12
PEAR        Files               Line endings                               11
PEAR        Functions           Function call argument spacing             5
Generic     PHP                 Disallow short open tag                    1
Zend        Files               Closing tag                                1
--------------------------------------------------------------------------------
A TOTAL OF 305 SNIFF VIOLATION(S) WERE FOUND IN 10 SOURCE(S)
--------------------------------------------------------------------------------

Each CodeSniffer standard is comprised of rules or sniffs. A standard can contain a sniff from other standards.

This allows us to quickly and easily create our own custom standard by leveraging those rules that are already contained in existing standards.

To find out what we have available to use, we can looking for all the files that end in Sniff.php within the Standards folder. We can see we have 167 sniff available to use already (with version 1.2.0a1 anyway).

Full list of available Sniffs

cd {your pear path}/PHP/CodeSniffer/Standards
$ find ./ -name "*Sniff.php" -and -not -name "Abstract*"

Clicking here to see the full list of available Sniffs.

Some of these Sniffs are self explanatory some are less so, and would need a cursory glance at the code to see what they are checking  for.

Luckily most of the classes are well documented (as the PHPCS standard dictates!) so looking at the class phpdoc comment usually tells what the sniff is looking for.

Something to note is that you will find some of the sniffs are mutually exclusive:

e.g.

./Generic/Sniffs/Formatting/NoSpaceAfterCastSniff.php
./Generic/Sniffs/Formatting/SpaceAfterCastSniff.php
./Generic/Sniffs/Functions/OpeningFunctionBraceBsdAllmanSniff.php
./Generic/Sniffs/Functions/OpeningFunctionBraceKernighanRitchieSniff.php

Writing the standard

So to start with we need to come up with a name for the standard.

So for this example I’m going to use KingKludge.

Now I prefer to develop in my home dir, but to get this sniff recognised you will need to have the standard available in {your pear path}/PHP/CodeSniffer/Standards/.

So I symlink my standard into the pear path, but you could work directly in the pear folder.

$ mkdir KingKludge
$ ln -s KingKludge {your pear path}/PHP/CodeSniffer/Standards/KingKludge

Then we need to create the standard file. Which follows the naming convention {standard}CodingStandard.php so we end up with the following file.

KingKludgeCodingStandard.php

The underscores in the class name are important as the autoload function splits the path at underscores, so if you miss-type one of the path parts you will get a fatal error when the class attempts to load.

Assuming that you did have your class in the correct folder you can do run.

$ phpcs -i

And you should see your new standard is listed as available for use.

As a timesaving tip, to stop us from having to type the standard name every time you can run CodeSniffer run the following command.

$ phpcs --config-set default_standard KingKludge

Now unless we override with the --standard switch CodeSniffer will default to using the KingKludge standard.

Adding some rules

So now we have a new standard but it doesn’t do anything yet, as it has no rules to apply.

We have two methods in the class to define the rules or sniffs getIncludedSniffs() and getExcludedSniffs(). Not unsurprisingly these methods should return an array of the sniffs to include or exclude.

If we decided we wanted to make life easy for ourselves and base our standard on someone else’s standard, we can quite simply include all of their standard and then swap out the sniffs we don’t want for others we do.

For example, taking the Squiz coding standard.

public function getIncludedSniffs()
{
  return array(
    'Squiz',
  );
}

Now say we want to swap the BSD style bracing rule for K&R style braces, and we don’t want the CodeAnalyzer rule to run either.

So we add the K&R braces Sniff to our include and add the CodeAnalyzer and BSD braces sniffs to the exclude.

public function getIncludedSniffs()
{
  return array(
    'Squiz',
    'Generic/Sniffs/Functions/OpeningFunctionBraceKernighanRitchieSniff.php',
  );
}

public function getExcludedSniffs()
{
  return array(
    'Generic/Sniffs/Functions/OpeningFunctionBraceBsdAllmanSniff.php',
    'Zend/Sniffs/Debug/CodeAnalyzerSniff.php',
  );
}

That seems easy enough!

So now you can see how easily you can create your own standards by picking as few or as many sniffs as you want from existing standards.

Next time, understanding the internals of CodeSniffer and how it works.

This entry was posted in Development, PHP, Software and tagged , , , . Bookmark the permalink.

3 Responses to CodeSniffer Part 3: Writing an example CodeSniffer Standard

  1. Norbert says:

    Really nice. I try this for myself and run into a problem. I include PEAR and the KernighanRitchie Sniff – similar to your explanation. Further I excluded the BSDAllmanSniff. So, I should have the PEAR Standard with changed braces-coding convention.

    But I get the error, that the braces should be on a new line, so the ExcludedSniffs seems not to be noted by the codesniffer.

    Is there some trick, I didn’t got?

    regards

    • Bob says:

      Hmmm, that’s interesting.

      What version of CodeSniffer are you running, I’ll give it a try as well. If I get the same it may well be a bug.
      In which case I’ll raise a bug against it.

      Bob

  2. Etienne Leite Gomide says:

    Hello,

    I have read your 3 articles about codesniffer, but this last one ‘Writing an example CodeSniffer Standard’ I do not understood.

    I need to write a standard with only what I want to check and not adapt from an existing standard.

    For example, I need to check if the “{” and “}” are after the line of the IF command and not in front of it. and other simple checks, but I do not understood on how to find these commands and where to put them to work.

    Could you give me a direction where I’ll find this?

Leave a Reply

Your email address will not be published. Required fields are marked *