Giter Club home page Giter Club logo

codor's Introduction

Code Odor Sniffer

๐Ÿ‘ƒ ๐Ÿ’ฉ Custom PHP Code Sniffer sniffs to help find Code Smells (Odor).

Build Status codecov Scrutinizer Code Quality Code Climate Packagist Packagist License

Inspired by: https://github.com/object-calisthenics/phpcs-calisthenics-rules

What is it?

This package is a set of custom Sniffs for the PHP Code Sniffer that you can use in your CI build to ensure the ingegrity of your code base.

Compatibility

  • PHP 7.1+ please use v1.0.0 and above.
  • PHP 5.6 and below please use any version below v1.0.0.

How to Install?

Install via Composer:

composer require bmitch/codor --dev

How to Use?

Create a PHPCS ruleset XML (codor.xml or whatever filename you want) file in the root of your project.

<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="Project">
    <description>Project Coding Standard</description>

    <rule ref="vendor/bmitch/codor/src/Codor/ruleset.xml"/>
</ruleset>

Then run it with the command:

vendor/bin/phpcs --standard=codor.xml src 

Where src is the location of the source code you want to check.

Omitting Sniffs

You may not want to run all the sniffs provided so you can specify which sniffs you want to exclude with the --exclude flag like:

vendor/bin/phpcs --standard=codor.xml --exclude=Codor.ControlStructures.NoElse src

(if you want to exclude multiple just separate them with a comma)

Running Specific Sniffs

Or you can also specify which sniffs to specifically run:

vendor/bin/phpcs --standard=codor.xml --sniffs=Codor.ControlStructures.NoElse src

Suppressing the sniffs on specific pieces of code

Please see the PHPCS documentation:
https://github.com/squizlabs/PHP_CodeSniffer/wiki/Advanced-Usage#ignoring-files-and-folders

Sniffs Included

Codor.ControlStructures.NoElse

Does not allow for any else or elseif statements.

โŒ

if ($foo) {
    return 'bar';
} else {
    return 'baz';
}

โœ…

if ($foo) {
    return 'bar';
}
return 'baz';

Codor.Files.FunctionLength

Functions/methods must be no more than 20 lines.

โŒ

public function foo()
{
    // more than 20 lines
}

โœ…

public function foo()
{
    // no more than 20 lines
}

Codor.Files.FunctionParameter

Functions/methods must have no more than 3 parameters.

โŒ

public function foo($bar, $baz, $bop, $portugal)
{
    // 
}

โœ…

public function foo($bar, $baz, $bop)
{
    // 
}

Codor.Files.ReturnNull

Functions/methods must not return null.

โŒ

public function getAdapter($bar)
{
    if ($bar === 'baz') {
        return new BazAdapter;
    }
    return null;
}

โœ…

public function getAdapter($bar)
{
    if ($bar === 'baz') {
        return new BazAdapter;
    }
    return NullAdapter;
}

Codor.Files.MethodFlagParameter

Functions/methods cannot have parameters that default to a boolean.

โŒ

public function getCustomers($active = true)
{
    if ($active) {
        // Code to get customers from who are active
    }
    
    // Code to get all customers
}

โœ…

public function getAllCustomers()
{    
    // Code to get all customers
}

public function getAllActiveCustomers()
{    
    // Code to get customers from who are active
}

Codor.Classes.ClassLength

Classes must be no more than 200 lines.

โŒ

class ClassTooLong
{
    // More than 200 lines
}

โœ…

class ClassNotTooLong
{
    // No more than 200 lines
}

Codor.Classes.ConstructorLoop

Class constructors must not contain any loops.

โŒ

public function __construct()
{
    for($index = 1; $index < 100; $index++) {
        // foo
    }
}

โœ…

public function __construct()
{
    $this->someMethod();
}

private function someMethod()
{
    for($index = 1; $index < 100; $index++) {
        // foo
    }
}

Codor.Classes.Extends

Warns if a class extends another class. Goal is to promote composition over inheritance (https://en.wikipedia.org/wiki/Composition_over_inheritance).

โŒ

class GasolineCar extends Vehicle
{
    //
}

class GasolineVehicle extends Vehicle
{
    //
}

โœ…

class Vehicle
{
    private $fuel;
    
    public function __construct(FuelInterface $fuel)
    {
        $this->fuel;    
    }
}

class Gasoline implements FuelInterface
{

}

$gasolineCar = new Vehicle($gasoline);

Codor.Classes.FinalPrivate

Final classes should not contain protected methods or variables. Should use private instead.

โŒ

final class Foo 
{
    protected $baz;

    protected function bar()
    {
        //
    }
}

โœ…

final class Foo 
{
    private $baz;

    private function bar()
    {
        //
    }
}

Codor.Classes.NewInstance

Classes should not instantiate objects. Should use dependency injection.

โŒ

class NewInConstructor
{
    private MyClass $myClass;

    public function __construct()
    {
        $this->myClass = new MyClass();
    }
}

โœ…

class NewInConstructor
{
    private MyClass $myClass;

    public function __construct(MyClass $myClass)
    {
        $this->myClass = $myClass;
    }
}

Codor.Classes.PropertyDeclaration

Produces an error if your class uses undeclared member variables. Only warns if class extends another class.

โŒ

class Foo 
{
    private function bar()
    {
        $this->baz = 13;
    }
}

โœ…

class Foo 
{
    private $baz;
    
    private function bar()
    {
        $this->baz = 13;
    }
}

Codor.Files.FunctionNameContainsAndOr

Functions/methods cannot contain "And" or "Or". This could be a sign of a function that does more than one thing.

โŒ

public function validateStringAndUpdateDatabase()
{
    // code to validate string
    // code to update database
}

โœ…

public function validateString()
{
    // code to validate string
}

public function updateDatabase()
{
    // code to update database
}

Codor.Files.IndentationLevel

Functions/methods cannot have more than 2 level of indentation.

โŒ

public function foo($collection)
{
    foreach ($collection as $bar) {
        foreach ($bar as $baz) {
            //
        }
    }
}

โœ…

public function foo($collection)
{
    foreach ($collection as $bar) {
        $this->process($bar);
    }
}

private function process($bar)
{
    foreach ($bar as $baz) {
        //
    }
}

Codor.ControlStructures.NestedIf

Nested if statements are not allowed.

โŒ

public function allowedToDrink($person)
{
    if ($person->age === 19) {
        if ($person->hasValidId()) {
            return true;
        }
    }
    
    return false;
}

โœ…

public function allowedToDrink($person)
{
    if ($person->age !== 19) {
        return false;
    }
       
    if (! $person->hasValidId()) {
        return false;
    }
    
    return true;
}

Codor.Syntax.NullCoalescing

Produces an error if a line contains a ternary operator that could be converted to a Null Coalescing operator.

โŒ

$username = isset($customer['name']) ? $customer['name'] : 'nobody';

โœ…

$username = $customer['name'] ?? 'nobody';

Codor.Syntax.LinesAfterMethod

Only allows for 1 line between functions/methods. Any more than 1 will produce an error.

โŒ

public function foo()
{
    //
}


public function bar()
{
    //
}

โœ…

public function foo()
{
    //
}

public function bar()
{
    //
}

Codor.TypeHints.MixedReturnType

Prevents you from having a mixed type returned in a doc block.

โŒ

/**
 * @return mixed
 */
public function foo()
{
    //
}

โœ…

/**
 * @return string
 */
public function foo()
{
    //
}

Customizing Sniffs

Some of the sniff rules can be customized to your liking. For example, if you'd want the Codor.Files.FunctionLength to make sure your functions are no more than 30 lines instead of 20, you can do that. Here's an example of a codor.xml file with that customization:

<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="Project">
    <description>Project Coding Standard</description>

    <rule ref="vendor/bmitch/codor/src/Codor/ruleset.xml"/>
	<rule ref="Codor.Files.FunctionLength">
		<properties>
			<property name="maxLength" value="30"/>
		</properties>
	</rule>
</ruleset>

Customizations Available

  • Codor.Files.FunctionLength
  • maxLength: The maximum number of lines a function/method can have (default = 20).
  • Codor.Files.FunctionParameter
  • maxParameters: The maximum number of parameters a function/method can have (default = 3).
  • Codor.Classes.ClassLength
  • maxLength: The maximum number of lines a Class can have (default = 200).
  • Codor.Files.IndentationLevel
  • indentationLimit: Cannot have more than or equal to this number of indentations (default = 2).

Similar Packages

Contributing

Please see CONTRIBUTING.md

License

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

codor's People

Contributors

bmitch avatar d35k avatar kevin-schmitt avatar szepeviktor avatar villfa 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  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

codor's Issues

Sniff to limit number of parameters

From Clean Code:

The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification -- and then shouldn't be used anyway.

Refactor IndentationLevelSniff

This class isn't as clean as I'd like it:

    public $indentationLimit = 2;

This doesn't really make sense because indentation limit is actually 1 but we use 2 in the class.

The function name inspectScope doesn't really explain what it is doing. This function also has more than 1 levels of indentation.

Sniff to prevent if/loops within any controller methods?

Not sure if this is a good idea or not but I guess users can always disable if they don't want to use it.

If the file ends in Controller.php then no methods within should contain any logic. Logic should be delegated to other classes.

  • If file ends in controller.
  • If file contains if, else, or any loop keyword it fails.

Remove any levels of indentation > 1

  • /**
    * Inspect the tokens in the scope of the provided $token.
    * @codingStandardsIgnoreStart
    * @param array $token Token Data.
    * @param array $tokens Tokens.
    * @return void
    */
    protected function inspectScope(array $token, array $tokens)
    {
    $start = $token['scope_opener'];
    $end = $token['scope_closer'];
    $this->relativeScopeLevel = $tokens[$start]['level'];
    for ($index=$start; $index <= $end; $index++) {
    $nestedToken = $tokens[$index];
    if ($nestedToken['type'] === "T_SWITCH") {
    return;
    }
    $this->adjustMaxIndentationFound($nestedToken);
    }
    }
    // @codingStandardsIgnoreEnd

  • /**
    * Processes the tokens that this sniff is interested in.
    * @codingStandardsIgnoreStart
    * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found.
    * @param integer $stackPtr The position in the stack where
    * the token was found.
    * @return void
    */
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
    {
    $tokens = $phpcsFile->getTokens();
    $returnTokenIndex = $stackPtr;
    $returnValueToken = '';
    $numberOfTokens = count($tokens);
    for ($index = $returnTokenIndex; $index < $numberOfTokens; $index++) {
    if ($tokens[$index]['type'] === 'T_SEMICOLON') {
    $returnValueToken = $tokens[$index - 1];
    break;
    }
    }
    // @codingStandardsIgnoreEnd
    if ($returnValueToken['type'] === 'T_NULL') {
    $error = "Return null value found.";
    $phpcsFile->addError($error, $stackPtr);
    }
    }

Once fixed can remove the @codingStandardsIgnoreStart in the method doc block.

Classes for Tokens and Token collections.

Think it would be good to have these classes:

  • Wrapper around the Token array data to make it more OO to obtain information about a token.
  • Perhaps a wrapper around an array of tokens to make it easy to iterate through all found tokens in a sniff.

Changes to CodeSniffRunner

This class and the test classes could be cleaned up:

  • To get the error count and warning count you have to run the sniff twice. Should only have to do it once.
  • Can we get the error and warning message so that can be tested?

The code where it runs a sniff looks messy. Can this be cleaned up?

        $errorCount = $codeSnifferRunner->detectErrorCountInFileForSniff(
            __DIR__.'/Assets/FunctionLengthSniff/FunctionLengthSniff.inc',
            'Codor.Files.FunctionLength'
        );

Fix final class sniff

Need to init variables when starting

        $this->classIsMarkedFinal = false;
        $this->protectedMethods = [];
        $this->protectedVariables = [];

Line numbers reported are also broken.

Sniff for method flag parameters

warns if a boolean method parameter is used in an if statement with the method. Could indicate the method has more than one responsibility.

Doesn't work with Atom Linter-PHPCS?

I tried setting it up to use with Atom's Linter-Phpcs package, but PHPCS just stops running. I don't see any errors or warnings where I should. If I remove the custom config from the Linter-PHPCS configuration, it resumes working normally.

Any suggestions on what I might try to debug this?

Thanks! :)

ignore try-catch block as indentation

Currently any loop or conditional in a try-catch block is reported. The only workaround is to put it in a function, but that doesn't always make sense. It's basically doubling the amount of functions if you need a try-catch block.

try {
    if ($this->eventShouldBeCollected($event)) {
        $this->collectEvent($event);
    }

    $this->fire($event);
} catch (\Exception) {
    // log event errors
}

also in the catch part:

try {
    $this->response = $this->guzzle->request(...);
    // etc..
} catch (\GuzzleHttp\Exception\ClientException $e) {
    if ($e->getResponse()->getStatusCode() == 404) {
        throw VacancyIsClosed::forId($vacancyId);
    }
}

Sniff for anonymous functions

Sometimes you don't want to have anonymous functions. Example is you may not want them in your routes.php file for Laravel.

This sniff will probably be one you only run against a handful of files.

Sniff to remove unwanted blank lines.

  • Blank lines at start/end of function/method
  • Blank lines before/after function/method
  • Blank lines after class declaration.
  • Blank lines around namespaces/use statement.

Class length sniff

Classes should be no more than X lines.

  • Any way to subtract comments from line count?
  • Any way to make X configurable?

Enhance readme

  • Add code examples for each sniff. Done
  • Show how to run individual sniffs or exclude individual sniffs.
  • For each sniff show how to customize if applicable.
  • Explain why, benefits of why you'd want each sniff.

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.