Skip to content

Add custom sniffs to address spacing of operators #134

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
vendor/
.phpunit.result.cache
phpcs.xml
composer.lock
tests/input2
Expand Down
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ install: travis_retry composer update --prefer-dist
script:
- vendor/bin/phpcs
- vendor/bin/phpcs $(find tests/input/* | sort) --report=summary --report-file=phpcs.log; diff tests/expected_report.txt phpcs.log
- vendor/bin/phpunit

stages:
- Validate against schema
Expand Down
8 changes: 8 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,20 @@
"Doctrine\\Sniffs\\": "lib/Doctrine/Sniffs"
}
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Tests\\": "tests/"
}
},
"require": {
"php": "^7.2",
"dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
"slevomat/coding-standard": "dev-master#63a8186b129ee96d1277e68c80cf87c8cdb356d1",
"squizlabs/php_codesniffer": "^3.5.0"
},
"require-dev": {
"phpunit/phpunit": "^8.1"
},
"config": {
"sort-packages": true
},
Expand Down
114 changes: 114 additions & 0 deletions lib/Doctrine/Sniffs/Operators/NegationOperatorSpacingSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

declare(strict_types=1);

namespace Doctrine\Sniffs\Operators;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use SlevomatCodingStandard\Helpers\TokenHelper;
use function in_array;
use function strlen;
use const T_CLOSE_PARENTHESIS;
use const T_CLOSE_SHORT_ARRAY;
use const T_CLOSE_SQUARE_BRACKET;
use const T_CONSTANT_ENCAPSED_STRING;
use const T_DNUMBER;
use const T_ENCAPSED_AND_WHITESPACE;
use const T_LNUMBER;
use const T_MINUS;
use const T_NUM_STRING;
use const T_STRING;
use const T_VARIABLE;
use const T_WHITESPACE;

final class NegationOperatorSpacingSniff implements Sniff
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need to add this one to ruleset.xml?

Copy link
Author

@grongor grongor Oct 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sniffs from the directory of the standards ruleset.xml are automatically included as far as I know, at least that's how it is with our coding standard: https://github.com/cdn77/coding-standard/tree/master/src/Cdn77CodingStandard

{
public const CODE_INVALID_SPACE_AFTER_MINUS = 'InvalidSpaceAfterMinus';

/** @var bool */
public $requireSpace = false;

/**
* {@inheritdoc}
*/
public function register() : array
{
return [T_MINUS];
}

/**
* @param int $pointer
*
* @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
*/
public function process(File $file, $pointer) : void
{
$tokens = $file->getTokens();

$previousEffective = TokenHelper::findPreviousEffective($file, $pointer - 1);

$possibleOperandTypes = [
T_CONSTANT_ENCAPSED_STRING,
T_CLOSE_PARENTHESIS,
T_CLOSE_SHORT_ARRAY,
T_CLOSE_SQUARE_BRACKET,
T_DNUMBER,
T_ENCAPSED_AND_WHITESPACE,
T_LNUMBER,
T_NUM_STRING,
T_STRING,
T_VARIABLE,
];

if (in_array($tokens[$previousEffective]['code'], $possibleOperandTypes, true)) {
return;
}

$whitespacePointer = $pointer + 1;
if (! isset($tokens[$whitespacePointer])) {
return;
}

$numberOfSpaces = $this->numberOfSpaces($tokens[$whitespacePointer]);
$expectedSpaces = $this->requireSpace ? 1 : 0;

if ($numberOfSpaces === $expectedSpaces
|| ! $this->recordError($file, $pointer, $tokens[$pointer], $expectedSpaces, $numberOfSpaces)) {
return;
}

if ($expectedSpaces > $numberOfSpaces) {
$file->fixer->addContent($pointer, ' ');

return;
}

$file->fixer->replaceToken($whitespacePointer, '');
}

/**
* @param mixed[] $token
*/
private function numberOfSpaces(array $token) : int
{
if ($token['code'] !== T_WHITESPACE) {
return 0;
}

return strlen($token['content']);
}

/**
* @param mixed[] $token
*/
private function recordError(File $file, int $pointer, array $token, int $expected, int $found) : bool
{
return $file->addFixableError(
'Expected exactly %d space after "%s"; %d found',
$pointer,
self::CODE_INVALID_SPACE_AFTER_MINUS,
[$expected, $token['content'], $found]
);
}
}
78 changes: 78 additions & 0 deletions lib/Doctrine/Sniffs/Operators/OperatorSpacingSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace Doctrine\Sniffs\Operators;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Standards\Squiz\Sniffs\WhiteSpace\OperatorSpacingSniff as SquizOperatorSpacingSniff;
use PHP_CodeSniffer\Util\Tokens;
use function in_array;
use const T_ARRAY_CAST;
use const T_BOOL_CAST;
use const T_DOUBLE_CAST;
use const T_ECHO;
use const T_INT_CAST;
use const T_MINUS;
use const T_OBJECT_CAST;
use const T_PLUS;
use const T_PRINT;
use const T_STRING_CAST;
use const T_UNSET_CAST;
use const T_YIELD;

/**
* We need this sniff until Squiz accepts fix for unary operands detection
* https://github.com/squizlabs/PHP_CodeSniffer/pull/2640
*/
final class OperatorSpacingSniff extends SquizOperatorSpacingSniff
{
private const NON_OPERAND_TOKENS = [
T_ARRAY_CAST,
T_BOOL_CAST,
T_DOUBLE_CAST,
T_ECHO,
T_INT_CAST,
T_OBJECT_CAST,
T_PRINT,
T_STRING_CAST,
T_UNSET_CAST,
T_YIELD,
];

/**
* @param int $pointer
*
* @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
*/
public function process(File $file, $pointer) : void
{
if (! $this->isOperator($file, $pointer)) {
return;
}

parent::process($file, $pointer);
}

/**
* @param int $pointer
*
* @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
*/
protected function isOperator(File $file, $pointer) : bool
{
if (! parent::isOperator($file, $pointer)) {
return false;
}

$tokens = $file->getTokens();
if ($tokens[$pointer]['code'] === T_MINUS || $tokens[$pointer]['code'] === T_PLUS) {
$prev = $file->findPrevious(Tokens::$emptyTokens, $pointer - 1, null, true);
if (in_array($tokens[$prev]['code'], self::NON_OPERAND_TOKENS, true)) {
return false;
}
}

return true;
}
}
6 changes: 6 additions & 0 deletions lib/Doctrine/ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
<exclude name="Squiz.Functions.FunctionDeclarationArgumentSpacing.SpacingAfterVariadic"/>
</rule>

<!-- Require spaces around math, comparison, assignment, ... operators -->
<rule ref="Doctrine.Operators.OperatorSpacing">
<properties>
<property name="ignoreNewlines" value="true"/>
</properties>
</rule>
<!-- Force array element indentation with 4 spaces -->
<rule ref="Generic.Arrays.ArrayIndent"/>
<!-- Forbid `array(...)` -->
Expand Down
20 changes: 20 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
beStrictAboutChangesToGlobalState="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
colors="true"
bootstrap="tests/bootstrap.php"
executionOrder="random"
>
<testsuite name="Doctrine Coding Standard">
<directory>tests</directory>
</testsuite>
<filter>
<whitelist>
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
</phpunit>
27 changes: 27 additions & 0 deletions tests/Sniffs/Operators/NegationOperatorSpacingSniffTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Sniffs\Operators;

use Doctrine\Tests\TestCase;

class NegationOperatorSpacingSniffTest extends TestCase
{
public function testNoErrors() : void
{
self::assertNoSniffErrorInFile(self::checkFile(__DIR__ . '/data/NegationOperatorSpacingSniffNoErrors.php'));
}

public function testErrors() : void
{
$file = self::checkFile(
__DIR__ . '/data/NegationOperatorSpacingSniffRequireSpaceErrors.php',
['requireSpace' => true]
);

self::assertSame(97, $file->getErrorCount());

self::assertAllFixedInFile($file);
}
}
32 changes: 32 additions & 0 deletions tests/Sniffs/Operators/OperatorSpacingSniffTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Sniffs\Operators;

use Doctrine\Tests\TestCase;

class OperatorSpacingSniffTest extends TestCase
{
public function testNoErrors() : void
{
self::assertNoSniffErrorInFile(
self::checkFile(
__DIR__ . '/data/OperatorSpacingSniffNoErrors.php',
['ignoreSpacingBeforeAssignments' => false]
)
);
}

public function testErrors() : void
{
$file = self::checkFile(
__DIR__ . '/data/OperatorSpacingSniffErrors.php',
['ignoreSpacingBeforeAssignments' => false]
);

self::assertSame(67, $file->getErrorCount());

self::assertAllFixedInFile($file);
}
}
Loading