GithubHelp home page GithubHelp logo

gskema / phpcs-type-sniff Goto Github PK

View Code? Open in Web Editor NEW
40.0 2.0 3.0 585 KB

PHP CodeSniffer sniff to enforce PHP7, PHP8 types and documentation of array variables

License: MIT License

PHP 99.72% Shell 0.28%
phpcs php7 type-safety phpdoc codesniffer php php8

phpcs-type-sniff's Introduction

PHPCS Type Sniff

Latest Version on Packagist Software License Build Status Coverage Status Quality Score Total Downloads

Custom phpcs CodeSniffer rule that:

  • Enforces usage of PHP8, PHP7 type declarations (where possible)
  • Enforces documenting array types with more specific types (e.g. int[])
  • Checks for useless PHPDoc blocks (no repeated information)
  • Many more other checks

Example PHP class (comments on the right = phpcs warnings):

<?php

namespace Fruits;

/**
 * Class Banana                     // useless description
 * @package Fruits                  // useless tag
 */
class Banana
{
    public const C1 = [];           // missing typed array doc type

    /** @var array */               // must use typed array doc type
    public const C2 = [];

    /** @var array[] */             // must use specific typed array doc type
    public const C3 = [[]];

    /** @var bool|false */          // redundant false type
    public const C4 = false;

    /**
     * @var int                     // incompatible int type, missing null type
     */
    public const C5 = null;

    /** @var int */
    public const C6 = 1;            // useless PHPDoc

    #[ArrayShape(['foo' => 'int'])]
    public const C7 = ['foo' => 1]; // ArrayShape supported

    public $prop1 = [];             // missing typed array doc type + array type decl.

    /** @var array */               // must use typed array doc type + array type decl.
    public $prop2 = [];

    public $prop3;                  // missing type declaration

    /** @var */                     // missing doc type, missing type declaration
    public $prop4;

    /** @var array[][] */           // must use specific typed array doc type
    public $prop5;                  // + missing type declaration

    /** @var array|string[] */      // redundant array type, missing type declaration
    public $prop6;

    /** @var int|string */          // missing null doc type, missing type declaration
    public $prop7 = null;

    /** @var int $prop8 */          // prop name must be removed, missing type decl.
    public $prop8;

    public int $prop9;

    public ?int $prop10;

    /** @var int|null */            // Useless PHPDoc
    public ?int $prop11;

    /** @var string|int */          // missing null type, wrong string type
    public ?int $prop12;

    /** @var string[] */
    public array $prop13;

    #[ArrayShape(['foo' => 'int'])]
    public $prop14 = ['foo' => 1];  // ArrayShape supported

    /** @var class-string */
    public string $prop15;

    /** @var iterable<int, Acme> */
    public iterable $prop16;

    public function __construct(
        $param1,                    // missing param type decl. in method PHPDoc
        public $param2,             // missing param type decl. (in method PHPDoc or inline PHPDoc)
        public array $param3,       // missing typed array doc type (in method PHPDoc or inline PHPDoc)
        public int $param4,
        public int|null             // must use shorthand nullable syntax: ?int
        public readonly $obj = new stdClass(),
        public Iterator&Countable $obj2,
    ) {}

    /**
     * @return $this                // missing type decl. e.g. 'static'
     */
    public function setThing()
    {
        return $this;
    }

    /**
     * @param array|string          // must used typed array + wrong type: string + missing: int 
     * @param mixed $id 
     * @return $this                // useless PHPDoc (does not provide additional code intel over 'static')
     */
    public function setSomething(
        array|int
        $id = null                  // missing type decl. "mixed"
    ): self {
        return $this;
    }

    public function func1(
        $param1,                    // missing param type decl.
        int $param2
    ) {                             // missing return type decl.
    }

    /**
     * @param int|null  $param1
     * @param int|null  $param2
     * @param array     $param3     // must use typed array doc type
     *
     * @param           $param5     // suggested int doc type
     * @param           $param6     // missing doc type
     * @param array[]   $param7     // must use specific typed array doc type
     * @param bool|true $param8     // remove true doc type
     * @param null      $param9     // suggested compound doc type, e.g. int|null
     * @param string    $param10    // incompatible string type, missing int, null types
     * @param stdClass  $param11
     * @param bool|int  $param12
     *
     * @return void                 // useless tag
     */
    public function func2(
        $param1,                    // suggested ?int type decl.
        int $param2 = null,         // suggested ?int type decl.
        array $param3,
        $param4,                    // missing @param tag
        int $param5,
        $param6,
        array $param7,
        bool $param8,
        $param9 = null,             // missing type decl.
        ?int $param10 = null,
        stdClass $param11,
        $param12
    ): void {
    }

    /**
     * @return int
     */
    public function func3(): int    // useless PHPDoc
    {
    }

    /**
     * @param array<int, bool>           $arg1 // alternative array documentation
     * @param array{foo: bool, bar: int} $arg2 // supported, no warning
     * @param (int|string)[]             $arg3 //
     * @param array('key1' => int, ...)  $arg4 //
     */
    public function func4(
        array $arg1,
        array $arg2,
        array $arg3,
        array $arg4
    ): void {
    }

    /**
     * Description
     */ 
    #[ArrayShape(['foo' => 'int'])]     // ArrayShape supported
    public function func5(
        #[ArrayShape(['foo' => 'int'])] // ArrayShape supported
        array $arg1
    ): array {
        return ['foo' => 1];
    }
    
    /**
     * @return Generator<int, string>   // supported
     */
    public function func6(): Generator;
}

Install

Via Composer

$ composer require --dev gskema/phpcs-type-sniff

Usage

This is a standalone sniff file, you need to add it to your phpcs.xml file.

Usage Without Reflection

Inspections for methods with @inheritdoc tag are skipped. If a method does not have this tag, it is inspected. This is the recommended setup.

<ruleset name="your_ruleset">
    <!-- your configuration -->
    <rule ref="PSR2"/>

    <!-- phpcs-type-sniff configuration -->   
    <rule ref="./vendor/gskema/phpcs-type-sniff/src/Sniffs/CompositeCodeElementSniff.php"/>
</ruleset>

Usage With Reflection

With reflection enabled, this sniff can assert if @inheritoc tag is needed. Inspections for extended/implemented methods are skipped. Reflections need to load actual classes, which is why we need to include the autoloader. This option is good for inspecting extended methods, however using ReflectionClass may cause phpcs crashes while editing (not possible to catch FatalError).

<ruleset name="your_ruleset">
    <!-- your base configuration -->
    <rule ref="PSR12"/>

    <!-- phpcs-type-sniff configuration -->   
    <autoload>./vendor/autoload.php</autoload>
    <rule ref="./vendor/gskema/phpcs-type-sniff/src/Sniffs/CompositeCodeElementSniff.php">
        <properties>
            <property name="useReflection" value="true"/>
        </properties>
    </rule>
</ruleset>

PHPCS Baseline

It may be hard to integrate new rules/standards into old projects because too many warnings may be detected. If you would like to fix them later, but use the standard for all new code, you can "save" warnings detected on current code, then ignore them on subsequent builds. The code standard remains the same, but on subsequent builds you "subtract" the old (baseline) warnings:

# Add this configuration option to phpcs.xml (see [configuration](#Configuration)):
# <property name="addViolationId" value="true"/>

# Generate report with ignored warnings.
# You may want to commit this file to your repository until you fix all the warnings.
# You may also update this file once in a while. 
./vendor/bin/phpcs --standard=phpcs.xml --report=checkstyle --report-file=baseline.xml

# Run you main code style check command (on build) to generate a report.
# This will contain all warnings, the ignored errors will be subtracted using a command below.
./vendor/bin/phpcs --standard=phpcs.xml --report=checkstyle --report-file=report.xml

# Run a custom PHP script (on build) that subtracts ignored warnings.
# First argument is target report file (that was just built).
# second argument is the baseline report file with ignored warnings which we want to subtract.
# "Subtract baseline.xml warnings from report.xml (report - baseline)":
./bin/phpcs-subtract-baseline ./report.xml ./baseline.xml

# If you generate baseline file locally, but execute build on a different environment,
# you may need to specify additional options to trim filename base paths
# to ensure same relative filename is used when subtracting baseline:
./bin/phpcs-subtract-baseline ./report.xml ./baseline.xml \
  --trim-basepath="/remote/project1/" --trim-basepath="/local/project1/"

# Or you may add this option to generate phpcs report with relative paths, which will
# generate same relative paths. However, some tools may not fully work with relative paths (e.g. Jenkins checkstyle) 
<arg line="-p --basepath=."/>

Note: By default all baseline warnings are tracked by filename + line + column + message hash. By default, all warnings detected by this sniff (CompositeCodeElementSniff) will have a violation ID, which is used to track and compare baseline warnings independently of line and column. This allows you to change other things in the same file where these warnings are detected, the violation ID does not change because of line numbers / code style.

Configuration

Sniffs are registered and saved by their short class name. This allows easily specifying configuration options for a specific code element sniff, e.g. FqcnMethodSniff.invalidTags. All custom code sniff classes must have unique short class names.

String true/false values are automatically converted to booleans.

<ruleset name="your_ruleset">
    <!-- your configuration -->
    <rule ref="PSR12"/>

    <!-- phpcs-type-sniff configuration -->   

    <!-- Includes an autoloader which is needed when using reflection API -->
    <!-- or custom code element sniff(s) -->
    <autoload>./vendor/autoload.php</autoload>

    <!-- Includes a standalone sniff to your custom coding standard -->
    <rule ref="./vendor/gskema/phpcs-type-sniff/src/Sniffs/CompositeCodeElementSniff.php">
        <properties>

            <!-- Enables usage of reflection API when inspecting extended classes. -->
            <!-- Autoloader is needed. -->
            <property name="useReflection" value="true"/>

            <!-- Appends violation ID to each error/warning detected by this sniff. -->
            <!-- Used to track baseline warnings of this sniff -->
            <!-- independently of line/column. Default is true. -->
            <property name="addViolationId" value="false"/>

            <!-- Disables one of the default code element sniffs -->
            <property name="FqcnConstSniff.enabled" value="false" />
            <property name="FqcnMethodSniff.enabled" value="false" />
            <property name="FqcnPropSniff.enabled" value="false" />
            <property name="FqcnDescriptionSniff.enabled" value="false" />
            <property name="IteratorItemTypeSniff.enabled" value="false" />

            <!-- Change violation report type for all sniffs. Default is warning. -->
            <property name="reportType" value="error" />

            <!-- Or change violation report type for individual sniffs. Default is warning. -->
            <property name="FqcnConstSniff.reportType" value="error" />
            <property name="FqcnMethodSniff.reportType" value="error" />
            <property name="FqcnPropSniff.reportType" value="warning" />
            <property name="FqcnDescriptionSniff.reportType" value="warning" />
            <property name="IteratorItemTypeSniff.reportType" value="warning" />

            <!-- Tags that should be removed from method PHPDoc -->
            <property name="FqcnMethodSniff.invalidTags" type="array">
                <element value="@someTag1"/>
                <element value="@someTag2"/>
            </property>

            <!-- Description lines and tags that should be removed from FQCN PHPDoc -->
            <property name="FqcnDescriptionSniff.invalidPatterns" type="array">
                <element value="^Nothing.+Useful$"/>
            </property>
            <property name="FqcnDescriptionSniff.invalidTags" type="array">
                <element value="@api"/>
            </property>

            <!-- Enables reporting missing @param, @return tags in non-empty method PHPDoc -->
            <!-- when method type declarations are present -->
            <property name="FqcnMethodSniff.reportMissingTags" value="true"/>

            <!-- When `useReflection:true`, extended methods will be detected -->
            <!-- and sniff will require adding `inheritDoc` tag to these methods. Default: false -->
            <property name="FqcnMethodSniff.requireInheritDoc" value="true"/>

            <!-- Disables reporting missing null type in basic getter return PHPDoc -->
            <!-- and return type declaration -->
            <property name="FqcnPropSniff.reportNullableBasicGetter" value="false"/>

            <!-- Promoted properties in __construct can be inspected as: 'prop' or 'param'. Default: 'prop' -->
            <!-- If set to 'prop', all prop sniffs will apply (but not FqcnMethodSniff parameter sniffs). -->
            <!-- If set to 'param', all parameter sniffs  will apply (but not FqcnPropSniff prop sniffs). -->
            <property name="inspectPromotedConstructorPropertyAs" value="prop"/>

            <!-- Your own custom code element sniff(s). Autoloader is needed. -->
            <!-- These classes implement CodeElementSniffInterface -->
            <property name="sniffs" type="array">
                <element value="\Acme\CustomCodeElementSniff" />
                <element value="\Acme\AnotherCustomMethodSniff" />
            </property>

            <!-- Configuration options for custom code element sniffs -->
            <property name="CustomCodeElementSniff.opt1" value="str1" />
            <!-- Specifying element key(s) will create an associative array -->
            <property name="AnotherCustomMethodSniff.arrayOpt1" type="array">
                <element key="key1" value="str1"/>
                <element key="key2" value="str2"/>
            </property>

        </properties>
    </rule>
</ruleset>

Change log

Please see CHANGELOG for more information on what has changed recently.

Testing

$ ./vendor/bin/phpunit

License

The MIT License (MIT). Please see License File for more information.

phpcs-type-sniff's People

Contributors

gskema avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

phpcs-type-sniff's Issues

PHP 8.2 Support

The project appears to run flawlessly on PHP 8.2 as-is. I have tested it a project running PHP 8.2 as well as running the unit tests in PHP 8.2 with the current dependencies. Adding support should be as easy as updating composer.json to allow installation on a wider variety of PHP versions.

In preparation for PHP8

<error severity="error" message="An error occurred during processing; checking has been aborted. The error message was: trim(): Passing null to parameter #1 ($string) of type string is deprecated in vendor/gskema/phpcs-type-sniff/src/Core/Type/TypeFactory.php on line 216" source="Internal.Exception"/>

Cannot Exclude Rules

As a developer I do not want to require return types to be declared for test methods since all test methods always return void and this does not add value.

I expect to be able to use the standard exclude syntax to exclude the sniffs for my ruleset in one of the following formats:

Syntax #1

<rule ref="Gskema.Sniffs.CompositeCodeElement.FqcnMethodSniff">
    <exclude-pattern>*/tests/*</exclude-pattern>
</rule>

The above syntax causes an error to be thrown:

ERROR: Referenced sniff "Gskema.Sniffs.CompositeCodeElement.FqcnMethodSniff" does not exist

Syntax #2

<rule ref="./vendor/gskema/phpcs-type-sniff/src/Sniffs/CompositeCodeElementSniff.php">
    <exclude-pattern>*/tests/*</exclude-pattern>
</rule>

The above syntax causes the exclude-pattern key to be ignored entirely.

Expected behavior is that the sniffs are not applied at the specified path.

Support for splat operator

/**
 * @param mixed[] ...$args
 * @return Item
 */
public function getGraphItem(...$args): Item
{
    return new Item(...$args);
}

Symfony controller: Add type hint in PHPDoc tag for return value

Hi, thanks for the package, enforcing PHPDoc on arrays and removing useless PHPDoc is exactly what I need.

However there seems to be an issue with annotation PHPDoc:

    /**
     * @Route("/things")
     */
    public function getThings(): Response
    {
        return new JsonResponse();
    }

generates

------------------------------------------------------------------------------
FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
------------------------------------------------------------------------------
 24 | WARNING | Add type hint in PHPDoc tag for return value, e.g. "Response"
------------------------------------------------------------------------------

While

    /**
     * @Route("/things")
     * @return Response
     */
    public function getThings(): Response
    {
        return new JsonResponse();
    }

generates

----------------------------------------------------------------------
FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
----------------------------------------------------------------------
 25 | WARNING | Useless PHPDoc
----------------------------------------------------------------------

The first report looks like a bug. The warning goes away if I remove the PHPDoc entirely, but I want to be able to use annotations.

I use a basic config as I'm just trying this:

<?xml version="1.0" encoding="UTF-8"?>

<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">

    <arg name="basepath" value="."/>
    <arg name="cache" value=".phpcs-cache"/>
    <arg name="colors"/>
    <arg name="extensions" value="php"/>

    <rule ref="PSR2"/>
    <rule ref="./vendor/gskema/phpcs-type-sniff/src/Sniffs/CompositeCodeElementSniff.php"/>

    <file>src/</file>
</ruleset>

Add type declaration for parameter: resource type

Hi,

Unless I'm missing something, the sniffer is asking for a resource type hint which unfortunately doesn't exist in PHP.

    /**
     * @param resource $image
     *
     * @return bool
     */
    protected static function imageDestroy($image): bool
FILE: src/WebThumbnailer/Utils/ImageUtils.php                                                                         │
----------------------------------------------------------------------------------------------------------------------│
FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE                                                                         │
----------------------------------------------------------------------------------------------------------------------│
 200 | WARNING | Add type declaration for parameter $image or create PHPDoc with type hint                            │
     |         | (Gskema.Sniffs.CompositeCodeElement.FqcnMethodSniff)                                                 │
----------------------------------------------------------------------------------------------------------------------│
                                                                                                                      │
Time: 76ms; Memory: 8MB

Fatal error: Uncaught Error: Call to a member function toString() on null in FqcnMethodSniff.php:250

I tried this sniff by following the installation instructions. I only required the package and added <rule ref="./vendor/gskema/phpcs-type-sniff/src/Sniffs/CompositeCodeElementSniff.php"/> to phpcs.xml.

When I ran phpcs, I got the following error:

Fatal error: Uncaught Error: Call to a member function toString() on null in vendor/gskema/phpcs-type-sniff/src/Sniffs/CodeElement/FqcnMethodSniff.php:250
Stack trace:
#0 vendor/gskema/phpcs-type-sniff/src/Sniffs/CodeElement/FqcnMethodSniff.php(118): Gskema\TypeSniff\Sniffs\CodeElement\FqcnMethodSniff::reportNullableBasicGetter(Object(PHP_CodeSniffer\Files\LocalFile), Object(Gskema\TypeSniff\Inspection\Subject\ReturnTypeSubject), Object(Gskema\TypeSniff\Core\CodeElement\Element\ClassMethodElement), Object(Gskema\TypeSniff\Core\CodeElement\Element\ClassElement))
#1 vendor/gskema/phpcs-type-sniff/src/Sniffs/CodeElement/FqcnMethodSniff.php(77): Gskema\TypeSniff\Sniffs\CodeElement\FqcnMethodSniff->processMethod(Object(PHP_CodeSniffer\Files\LocalFile), Object(Gskema\TypeSniff\Core\CodeElement\Element\ClassMethodElement), Object(Gskema\TypeSniff\Core\CodeElement\Element\ClassElement))
#2 vendor/gskema/phpcs-type-sniff/src/Sniffs/CompositeCodeElementSniff.php(99): Gskema\TypeSniff\Sniffs\CodeE in vendor/gskema/phpcs-type-sniff/src/Sniffs/CodeElement/FqcnMethodSniff.php on line 250

Note: I'm using version 0.14.0.

Change "warning" level to "error" level doesn't work

Hi,

I want to change the "warning" level to an "error" level to stop the build if the sniff is not respected. I use the "classic" phpcs rules tag "error", but this value seems to be ignored. Is there a way to add this behavior ? Thanks.
<rule ref="Gskema.Sniffs.CompositeCodeElement.FqcnConstSniff"> <type>error</type> </rule>

Incorrect rule with splat operator

The lib doesn't seems to be handling type hinting with splat operator properly.

I think that the following syntax/PHPDoc is correct:

    /**
     * @param string[] $args Suite of path/folders.
     */
    public static function getPath(string ...$args): void
FILE: src/WebThumbnailer/Utils/FileUtils.php
----------------------------------------------------------------------------------------------------------------------
FOUND 0 ERRORS AND 2 WARNINGS AFFECTING 1 LINE
----------------------------------------------------------------------------------------------------------------------
 17 | WARNING | Type hint "string[]" is not compatible with parameter $args value type
    |         | (Gskema.Sniffs.CompositeCodeElement.FqcnMethodSniff)
 17 | WARNING | Missing "string" type in parameter $args type hint
    |         | (Gskema.Sniffs.CompositeCodeElement.FqcnMethodSniff)
----------------------------------------------------------------------------------------------------------------------

Time: 54ms; Memory: 8MB

Add a Sniff for Object Like Arrays (Ie. Array Shapes)

Hi! I'm coming here from r/php. To get here I clicked "I wrote a PHPCS sniff to enforce type declaration and documenting arrays"

It would be nice if this sniff encouraged Psalm type PhpDoc blocks for mixed arrays. Also known as array shapes. Maybe a warning with less severity than the others. Typed arrays being preferred. The syntax I am familiar with is Psalm:

Psalm has support for object-like arrays, allowing you to specify types for all keys of an array if you so wish.

Examples of the syntax:

Maybe there are other syntax, but this is the one I am familiar with.

Thanks!

Useless @InheritDoc comment

Hello there, thanks for your great tool. I'm very like it, clever solution.
I was searching for tool like this and I finally managed to find something good.

I have a change request, maybe you will like it too or it can be helpful for others - I mean, I think that @inheritDoc parameter is no needed, when you are care of types, not documentation. I think that could be an option in configuration, if you want to force docs like this in your code or not.

In my project I'm not using tools like PHPDocumentor to create a technical description of my API, it is internal project, but I still want to check and maintain types of my application. Your tool omits parts of code, which are fully typed and there is no doubts on them but for some reason, it want to force on me @inheritDoc always when I'm implementing a fully typed method, or even void without parameters.

Simple example:

use Countable;

class Gskema implements Countable
{
    public function count(): int
    {
        // TODO: Implement count() method.
    }
}

It will generate error: phpcs: Gskema.Sniffs.CompositeCodeElement.FqcnMethodSniff: Missing @inheritDoc tag. Remove duplicated parent PHPDoc content.

I'm also using slevomat/coding-standard and these guys has add a rule for useless @InheritDoc comment

Could you please reconsider my request and possibly add such a configuration condition - I would be very grateful :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.