best-it/php_codesniffer

PHP_CodeSniffer ruleset and custom rules from best it.

4.0.0 2023-04-13 09:25 UTC

README

Build Status Build Status Scrutinizer Code Quality Code Coverage

This package contains a default rule set and custom rules which are used in all best it projects.

Installation

Our PHP_CodeSniffer package can be installed with composer with the following command:

composer require best-it/php_codesniffer --dev

Please use Version 1 for PHP 7.0!

Usage

Create a PHP_CodeSniffer configuration (phpcs.xml.dist / phpcs.xml) like this:

<?xml version="1.0"?>
<ruleset name="PROJECT-X">
    <description>The coding standard for project x.</description>

    <!-- Path to best it ruleset. -->
    <rule ref="./vendor/best-it/php_codesniffer/src/Standards/BestIt/ruleset.xml" />

    <!-- Path to directory which are checked. -->
    <file>src/</file>
    <file>tests/</file>
</ruleset>

If you want to sniff for special PHP Versions just declare a "testVersion" like:

<config name="testVersion" value="7.1" />

and include

<rule ref="PHPCompatibility" />

after you included our ruleset. (Don't forget to dev-require phpcompatibility/php-compatibility:^9.0.)

Execute the PHP_CodeSniffer (path can vary on your composer configuration):

./vendor/bin/phpcs

We use warnings for things which a human person should check, but which must not fail an automatic build. If you want to see warnings but get successful builds, call the code sniffer with the special config option ignore_warnings_on_exit:

./vendor/bin/phpcs --config-set ignore_warnings_on_exit 1

or, if you want the option only for a single run:

./vendor/bin/phpcs --runtime-set ignore_warnings_on_exit 1

Check original docs for more info.

If you want to ignore warnings altogether, you can provide the cli argument n:

./vendor/bin/phpcs -n

We suggest that you do not ignore warnings, but only check them in a manual pull/merge request.

Use in PHPStorm

How to use it in our favorite IDE?

/File/Settings:

  1. Choose executable

  2. Set up inspection

  3. ... and choose our ruleset.

Used sniffs

The base for the BestIt Standard is PSR-12.

Development

Introduction

The code sniffer is based on a token-based sniff mechanism.

You can use the verbose-options of the code sniffer cli, to see which tokens are parsed on which position in your file. Please consult the official code sniffer tutorial to know the theory how to code a custom sniff.

The gist of this tutorial is, that you need to implement the interface PHP_CodeSniffer\Sniffs\Sniff which enforces two methods:

1. Token Registration

The register-method should return the parser token id of the tokens, which you want to sniff.

2. Process

The process-method is called with the position of your registered token in the token stack of the given file object.

best it "Helpers"

BestIt\Sniffs\AbstractSniff

We refactored our basic work into this abstract. You can use an extended api to get a cleaner api and skips simple boilerplating.

  • areRequirementsMet: If this method returns false, the sniff is "skipped".
  • isSniffSuppressed: If this method returns true, the annotation for suppression is given and the sniff should be "skipped".
  • processToken: The boilerplate of the normal api is saved in object properties and you can sniff you token without the need to "re-implement" the basic interface with its method arguments.
  • setUp: You can set up the test, before the requirements are checked with areRequirementsMet.
  • tearDown: If you want to "destroy" this sniff, you can tear it down after the sniff processing.

The CodeSniffer is meant to be stateless even if the sniff-classes are used as "singletons". So if you save a state in your sniff, you MUST tearDown afterwards or initialize correctly in the setUp.

BestIt\Sniffs\DocTags\AbstractTagSniff

This abstract class helps you sniff for single doc tags. Just implement the given abstract methods and you can register and sniff for a specific doc tag and check for its content till a newline.

BestIt\Sniffs\DocTags\TagContentFormatTrait

This trait is meant in addition to the AbstractTagSniff and provides you with an api, which checks the tag content automatically against a regex pattern, which leads to an error or warning with the code TagContentFormatInvalid.

BestIt\Sniffs*RegistrationTrait

We provide some traits, which make it easier, to register sniffs for class-like structures, constants, methods and properties.

Testing

Requirements

To be able to test our written sniffs ensure that composer is installed with the option --prefer-source. This is needed because we use the TestCase of the SlevomatCodingStandard.

Error code as public constant

In additional to readable/clean clode our test base requires you to provide your error codes as a public constant prefixed with CODE_ in your sniff class.

No "Test"-Namespace

Our test base expects you to provide everything in the "normal" namespace: BestIt.

Helping test traits

Token Registration

The trait BestIt\Sniffs\TestTokenRegistrationTrait helps you with testing of the registered tokens.

Public error codes

The trait BestIt\Sniffs\TestRequiredConstantsTrait helps you to test the errors codes. We suggest that you test the values as well, because they could be part of a "foreign ruleset" out of your control from your "customer." So we enforce that the constant values stay api-stable!

Default Integration Tests

The trait BestIt\Sniffs\DefaultSniffIntegrationTestTrait provides you with three tests to test the usually use cases of a sniff based on sniff-individual test files:

  1. testCorrect
  2. testErrors
  3. testWarnings
Requirements
  • Your test-file should be called exactly like your sniff (including the namespace) but suffixed with Test
  • Provide a folder Fixtures as a sibling to your test file.
  • Put a folder into your "Fixtures" directory, which is called exactly like the short name of the sniff.
testCorrect

Create a correct folder into your fixtures directory. Every php file in this folder will be checked against your sniff. The sniff may not populate errors and warning for a successful test!

testErrors

Create a with_errors folder into your fixtures directory. Every php file in this folder should trigger an error in your sniff. You must provide the error structure through the file name. The file name must match the following pregex:

/(?P<code>[\w]+)(\(\w*\))?\.(?P<errorLines>[\d-\,]+)(?P<fixedSuffix>\.fixed)?\.php/

The file name gives information about which errors in which line should occur. Example files would be ErrorCode.1.php, ErrorCode.1,2,3.php, ErrorCode.1,2,3.fixed.php, ErrorCode(2).1,2,3.php, ErrorCode(2).1,2,3.fixed.php_. The error code must be the original code value from your sniff, the numbers after the first dot are the erroneous lines.

If you provide an additional file which is suffixed with "fixed" then this is the correct formatted file for its erroneous sibling.

testWarnings

Create a with_warnings folder into your fixtures directory. Every php file in this folder should trigger a warning in your sniff. You must provide the warning structure through the file name. The file name must match the following pregex:

/(?P<code>[\w]+)(\(\w*\))?\.(?P<errorLines>[\d-\,]+)(?P<fixedSuffix>\.fixed)?\.php/

The file name gives information about which warning in which line should occur. Example files would be WarningCode.1.php, WarningCode.1,2,3.php, WarningCode.1,2,3.fixed.php, WarningCode(2).1,2,3.php, WarningCode(2).1,2,3.fixed.php_. The warning code must be the original code value from your sniff, the numbers after the first dot are the lines with warnings.

If you provide an additional file which is suffixed with "fixed" then this is the correct formatted file for its erroneous sibling.

Contributing

See CONTRIBUTING.md.

Semantic Versioning

We use semantic versioning in correlation with the style enforced by our ruleset and not directly with our source code. This means, that there could be breaking change in our code, but as long as the style is not changed heavily, then the breaking change will not be mirrored in our version number:

  • Patch-Version (last number): backwards-compatible bugfixes in our ruleset
  • Minor-Version (middle number): backwards-compatible features in our ruleset (just warnings, deletions or auto-fixable errors)
  • Major-Version (first number): breaking change in our ruleset

We optimize our versioning for the usage of the sniffer, not for the development!

TODO

  • Remove further slevomat dependencies for internal apis.