Skip to content

Conversation

@vv12131415
Copy link
Contributor

I haven't yet found the rule that will help with that (since MultipleStatementAlignmentSniff doesn't work here)

This PR/issue comes from this #103 (comment)

@Ocramius
Copy link
Member

Idea looks OK to me - is there no existing rule handling this?

@vv12131415
Copy link
Contributor Author

@Ocramius, At least I haven't found one. Searched in squizlabs and slevomat rules

@alcaeus alcaeus closed this Feb 15, 2019
@alcaeus alcaeus reopened this Feb 15, 2019
@alcaeus
Copy link
Member

alcaeus commented Feb 15, 2019

Closing/reopening to trigger CI

@vv12131415
Copy link
Contributor Author

@alcaeus notice that there is no rule for that yet

@alcaeus
Copy link
Member

alcaeus commented Feb 15, 2019

@alcaeus notice that there is no rule for that yet

I know that - I was trying to fix the GitHub issue where it didn't trigger a travis-ci build at all. 😉 Reason is simple: I would think that the indentation sniffs should cover this case already.

@vv12131415 vv12131415 changed the title show the idea of new sniff rule show the idea of new sniff rule (force spaces after =) Feb 16, 2019
@vv12131415
Copy link
Contributor Author

As @jrfnl suggested here squizlabs/PHP_CodeSniffer#2425 , we can use Squiz.WhiteSpace.OperatorSpacing but the problem (at least for me) is that there is not only assigment operator, but also bunch of other ones (see vendor/squizlabs/php_codesniffer/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php:42)

@lcobucci
Copy link
Member

@vladyslavstartsev I've built this for the CS in my current company, maybe we can use it as inspiration (it removes all spaces before and after operators, not exactly what we want but can be tweaked):

<?php
declare(strict_types=1);

namespace Castor\CodingStandard\Sniffs\Operators;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Standards\Squiz\Sniffs\WhiteSpace\OperatorSpacingSniff as SquizOperatorSpacingSniff;
use PHP_CodeSniffer\Util\Tokens;
use const T_COMMENT;
use const T_INLINE_ELSE;
use const T_INLINE_THEN;
use const T_INSTANCEOF;
use const T_STRING_CONCAT;
use const T_WHITESPACE;
use function array_merge;
use function array_unique;
use function in_array;
use function strlen;

final class OperatorSpacingSniff extends SquizOperatorSpacingSniff
{
    private const MESSAGE_BEFORE = 'Expected exactly 1 space before "%s"; %d found';
    private const MESSAGE_AFTER = 'Expected exactly 1 space after "%s"; %d found';

    /**
     * {@inheritdoc}
     */
    public function register(): array
    {
        return array_unique(
            array_merge(
                Tokens::$comparisonTokens,
                Tokens::$operators,
                Tokens::$assignmentTokens,
                Tokens::$booleanOperators,
                [
                    T_INLINE_THEN,
                    T_INLINE_ELSE,
                    T_STRING_CONCAT,
                    T_INSTANCEOF,
                ]
            )
        );
    }

    /**
     * {@inheritdoc}
     */
    public function process(File $phpcsFile, $pointer): void
    {
        if (! $this->isOperator($phpcsFile, $pointer)) {
            return;
        }

        $tokens = $phpcsFile->getTokens();

        $this->ensureOneSpaceBeforeOperator($phpcsFile, $pointer, $tokens);
        $this->ensureOneSpaceAfterOperator($phpcsFile, $pointer, $tokens);
    }

    /**
     * @param mixed[] $tokens
     */
    private function ensureOneSpaceBeforeOperator(File $file, int $pointer, array $tokens): void
    {
        if (! $this->shouldValidateBefore($pointer, $tokens)) {
            return;
        }

        $numberOfSpaces = $this->numberOfSpaces($tokens[$pointer - 1]);

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

        if ($numberOfSpaces === 0) {
            $file->fixer->addContentBefore($pointer, ' ');
            return;
        }

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

    /**
     * @param mixed[] $token
     */
    private function recordErrorBefore(File $file, int $pointer, array $token, int $numberOfSpaces): bool
    {
        return $file->addFixableError(
            self::MESSAGE_BEFORE,
            $pointer,
            'NoSpaceBefore',
            [$token['content'], $numberOfSpaces]
        );
    }

    /**
     * @param mixed[] $tokens
     */
    private function shouldValidateBefore(int $pointer, array $tokens): bool
    {
        $currentToken = $tokens[$pointer];
        $previousToken = $tokens[$pointer - 1];

        if ($currentToken['code'] === T_INLINE_ELSE && $previousToken['code'] === T_INLINE_THEN) {
            return false;
        }

        if ($previousToken['code'] === T_COMMENT && $previousToken['line'] < $currentToken['line']) {
            return false;
        }

        return ! in_array($currentToken['code'], Tokens::$booleanOperators, true)
            || $currentToken['line'] === $tokens[$pointer - 2]['line'];
    }

    /**
     * @param mixed[] $tokens
     */
    private function ensureOneSpaceAfterOperator(File $file, int $pointer, array $tokens): void
    {
        if (! $this->shouldValidateAfter($pointer, $tokens)) {
            return;
        }

        $numberOfSpaces = $this->numberOfSpaces($tokens[$pointer + 1]);

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

        if ($numberOfSpaces === 0) {
            $file->fixer->addContent($pointer, ' ');
            return;
        }

        $file->fixer->replaceToken($pointer + 1, ' ');
    }

    /**
     * @param mixed[] $tokens
     */
    private function shouldValidateAfter(int $pointer, array $tokens): bool
    {
        if (! isset($tokens[$pointer + 1])) {
            return false;
        }

        return $tokens[$pointer]['code'] !== T_INLINE_THEN || $tokens[$pointer + 1]['code'] !== T_INLINE_ELSE;
    }

    /**
     * @param mixed[] $token
     */
    private function recordErrorAfter(File $file, int $pointer, array $token, int $numberOfSpaces): bool
    {
        return $file->addFixableError(
            self::MESSAGE_AFTER,
            $pointer,
            'NoSpaceAfter',
            [$token['content'], $numberOfSpaces]
        );
    }

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

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

@vv12131415
Copy link
Contributor Author

@lcobucci I like your idea. But I thought that coding standards will not write own, but use existing one.
I mean it looks great as long as it fits this repository rules.

@SenseException
Copy link
Member

The coding-standard can also have its own written sniffs.

@vv12131415
Copy link
Contributor Author

so, should I put the code that @lcobucci gave into the repo?

@lcobucci
Copy link
Member

@vladyslavstartsev after some modification, sure.

@carusogabriel
Copy link
Contributor

@vladyslavstartsev What about these suggestions?

@vv12131415
Copy link
Contributor Author

@carusogabriel LGTM, sorry for not answering for a long time (currently busy a little bit)

@vv12131415
Copy link
Contributor Author

Ok, I've got some time to implement this rule 🙂

@Ocramius @alcaeus @lcobucci @SenseException by the way, what about forcing not only = operator but also others too (as @lcobucci implemented in his own sniff with almost all of them).
I mean all of those operators

Tokens::$comparisonTokens,
Tokens::$operators,
Tokens::$assignmentTokens,
Tokens::$booleanOperators,
[
T_INLINE_THEN,
T_INLINE_ELSE,
T_STRING_CONCAT,
T_INSTANCEOF,
]

@Ocramius
Copy link
Member

@vladyslavstartsev could you rebase so we can see if the build passes? Can't vote on it for approval otherwise

@vv12131415
Copy link
Contributor Author

@Ocramius done! (forgot about that, sorry)

@malarzm malarzm closed this Apr 30, 2019
@malarzm malarzm reopened this Apr 30, 2019
@malarzm
Copy link
Member

malarzm commented Apr 30, 2019

Trying to get Travis work on this...

@Ocramius
Copy link
Member

I think it's borked: probably best to open a new PR, @vladyslavstartsev :-\

@Ocramius
Copy link
Member

Closing here, will re-open a new PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants