<commit_before><?php

/**
 * MIT License
 * For full license information, please view the LICENSE file that was distributed with this source code.
 */

namespace Spryker\Sniffs\Commenting;

use PHP_CodeSniffer\Files\File;
use Spryker\Sniffs\AbstractSniffs\AbstractSprykerSniff;
use Spryker\Tools\Traits\CommentingTrait;
use Spryker\Tools\Traits\SignatureTrait;

/**
 * Makes sure doc block param/return types have the right order and don't duplicate.
 *
 * @author <NAME>
 * @license MIT
 */
class DocBlockTypeOrderSniff extends AbstractSprykerSniff
{
    use CommentingTrait;
    use SignatureTrait;

    /**
     * Highest/First element will be last in list of param or return tag.
     *
     * @var array
     */
    protected $sortMap = [
        'void',
        'null',
        'false',
    ];

    /**
     * @inheritDoc
     */
    public function register()
    {
        return [
            T_FUNCTION,
        ];
    }

    /**
     * @inheritDoc
     */
    public function process(File $phpCsFile, $stackPointer)
    {
        $tokens = $phpCsFile->getTokens();

        $docBlockEndIndex = $this->findRelatedDocBlock($phpCsFile, $stackPointer);

        if (!$docBlockEndIndex) {
            return;
        }

        $docBlockStartIndex = $tokens[$docBlockEndIndex]['comment_opener'];

        if ($this->hasInheritDoc($phpCsFile, $docBlockStartIndex, $docBlockEndIndex)) {
            return;
        }

        $docBlockParams = [];
        for ($i = $docBlockStartIndex + 1; $i < $docBlockEndIndex; $i++) {
            if ($tokens[$i]['type'] !== 'T_DOC_COMMENT_TAG') {
                continue;
            }
            if (!in_array($tokens[$i]['content'], ['@param', '@return'])) {
                continue;
            }

            $classNameIndex = $i + 2;

            if ($tokens[$classNameIndex]['type'] !== 'T_DOC_COMMENT_STRING') {
                continue;
            }

            $content = $tokens[$classNameIndex]['content'];

            $appendix = '';
            $spacePos = strpos($content, ' ');
            if ($spacePos) {
                $appendix = substr($content, $spacePos);
                $content = substr($content, 0, $spacePos);
            }

            preg_match('/\$[^\s]+/', $appendix, $matches);
            $variable = $matches ? $matches[0] : '';

            $docBlockParams[] = [
                'index' => $classNameIndex,
                'type' => $content,
                'variable' => $variable,
                'appendix' => $appendix,
            ];
        }

        $this->assertOrder($phpCsFile, $docBlockParams);
    }

    /**
     * @param \PHP_CodeSniffer\Files\File $phpCsFile
     * @param array $docBlockParams
     *
     * @return void
     */
    protected function assertOrder(File $phpCsFile, array $docBlockParams): void
    {
        foreach ($docBlockParams as $docBlockParam) {
            if (strpos($docBlockParam['type'], '$') !== false) {
                continue;
            }

            $pieces = explode('|', $docBlockParam['type']);
            if (count($pieces) === 1) {
                continue;
            }

            $unique = array_unique($pieces);
            if (count($pieces) !== count($unique)) {
                $phpCsFile->addError('Duplicate type in `' . $docBlockParam['type'] . '`', $docBlockParam['index'], 'Duplicate');
                continue;
            }
            $expectedOrder = $this->getExpectedOrder($pieces);
            if ($expectedOrder === $pieces) {
                continue;
            }

            $fix = $phpCsFile->addFixableError('`null` and falsey values should be the last element', $docBlockParam['index'], 'WrongOrder');
            if (!$fix) {
                continue;
            }

            $phpCsFile->fixer->beginChangeset();

            $content = implode('|', $expectedOrder) . $docBlockParam['appendix'];
            $phpCsFile->fixer->replaceToken($docBlockParam['index'], $content);

            $phpCsFile->fixer->endChangeset();
        }
    }

    /**
     * @param array $pieces
     *
     * @return array
     */
    protected function getExpectedOrder(array $pieces): array
    {
        global $sortOrder;

        $sortOrder = array_reverse($this->sortMap);
        usort($pieces, [$this, "compare"]);

        return $pieces;
    }

    /**
     * @param string $a
     * @param string $b
     *
     * @return int
     */
    protected function compare(string $a, string $b): int
    {
        global $sortOrder;

        $ai = array_search($a, $sortOrder);
        $bi = array_search($b, $sortOrder);
        if ($ai === false && $bi === false) {
            return -1;
        }
        if ($ai !== false && $bi === false) {
            return 1;
        }
        if ($bi !== false && $ai === false) {
            return -1;
        }
        if ($ai !== false && $bi !== false) {
            return $ai - $bi;
        }

        return $a < $b ? -1 : 1;
    }
}<commit_msg>Changes as per code review.<commit_after><?php

/**
 * MIT License
 * For full license information, please view the LICENSE file that was distributed with this source code.
 */

namespace Spryker\Sniffs\Commenting;

use PHP_CodeSniffer\Files\File;
use Spryker\Sniffs\AbstractSniffs\AbstractSprykerSniff;
use Spryker\Tools\Traits\CommentingTrait;
use Spryker\Tools\Traits\SignatureTrait;

/**
 * Makes sure doc block param/return types have the right order and don't duplicate.
 *
 * @author <NAME>
 * @license MIT
 */
class DocBlockTypeOrderSniff extends AbstractSprykerSniff
{
    use CommentingTrait;
    use SignatureTrait;

    /**
     * Highest/First element will be last in list of param or return tag.
     *
     * @var string[]
     */
    protected $sortMap = [
        'void',
        'null',
        'false',
    ];

    /**
     * @inheritDoc
     */
    public function register()
    {
        return [
            T_FUNCTION,
        ];
    }

    /**
     * @inheritDoc
     */
    public function process(File $phpCsFile, $stackPointer)
    {
        $tokens = $phpCsFile->getTokens();

        $docBlockEndIndex = $this->findRelatedDocBlock($phpCsFile, $stackPointer);

        if (!$docBlockEndIndex) {
            return;
        }

        $docBlockStartIndex = $tokens[$docBlockEndIndex]['comment_opener'];

        if ($this->hasInheritDoc($phpCsFile, $docBlockStartIndex, $docBlockEndIndex)) {
            return;
        }

        $docBlockParams = $this->getDocBlockParams($tokens, $docBlockStartIndex, $docBlockEndIndex);

        $this->assertOrder($phpCsFile, $docBlockParams);
    }

    /**
     * @param \PHP_CodeSniffer\Files\File $phpCsFile
     * @param array $docBlockParams
     *
     * @return void
     */
    protected function assertOrder(File $phpCsFile, array $docBlockParams): void
    {
        foreach ($docBlockParams as $docBlockParam) {
            if (strpos($docBlockParam['type'], '$') !== false) {
                continue;
            }

            $docBlockParamTypes = explode('|', $docBlockParam['type']);
            if (count($docBlockParamTypes) === 1) {
                continue;
            }

            $unique = array_unique($docBlockParamTypes);
            if (count($docBlockParamTypes) !== count($unique)) {
                $phpCsFile->addError('Duplicate type in `' . $docBlockParam['type'] . '`', $docBlockParam['index'], 'Duplicate');
                continue;
            }
            $expectedOrder = $this->getExpectedOrder($docBlockParamTypes);
            if ($expectedOrder === $docBlockParamTypes) {
                continue;
            }

            $fix = $phpCsFile->addFixableError('`null` and falsey values should be the last element', $docBlockParam['index'], 'WrongOrder');
            if (!$fix) {
                continue;
            }

            $phpCsFile->fixer->beginChangeset();

            $content = implode('|', $expectedOrder) . $docBlockParam['appendix'];
            $phpCsFile->fixer->replaceToken($docBlockParam['index'], $content);

            $phpCsFile->fixer->endChangeset();
        }
    }

    /**
     * @uses DocBlockTypeOrderSniff::compare()
     *
     * @param string[] $elements
     *
     * @return string[]
     */
    protected function getExpectedOrder(array $elements): array
    {
        global $sortOrder;

        $sortOrder = array_reverse($this->sortMap);
        usort($elements, [$this, "compare"]);

        return $elements;
    }

    /**
     * @param string $a
     * @param string $b
     *
     * @return int
     */
    protected function compare(string $a, string $b): int
    {
        global $sortOrder;

        $ai = array_search($a, $sortOrder);
        $bi = array_search($b, $sortOrder);
        if ($ai === false && $bi === false) {
            return -1;
        }
        if ($ai !== false && $bi === false) {
            return 1;
        }
        if ($bi !== false && $ai === false) {
            return -1;
        }

        return $ai - $bi;
    }

    /**
     * @param array $tokens
     * @param int $docBlockStartIndex
     * @param int $docBlockEndIndex
     *
     * @return array
     */
    protected function getDocBlockParams(array $tokens, int $docBlockStartIndex, int $docBlockEndIndex): array
    {
        $docBlockParams = [];
        for ($i = $docBlockStartIndex + 1; $i < $docBlockEndIndex; $i++) {
            if ($tokens[$i]['type'] !== 'T_DOC_COMMENT_TAG') {
                continue;
            }
            if (!in_array($tokens[$i]['content'], ['@param', '@return'])) {
                continue;
            }

            $classNameIndex = $i + 2;

            if ($tokens[$classNameIndex]['type'] !== 'T_DOC_COMMENT_STRING') {
                continue;
            }

            $content = $tokens[$classNameIndex]['content'];

            $appendix = '';
            $spacePos = strpos($content, ' ');
            if ($spacePos) {
                $appendix = substr($content, $spacePos);
                $content = substr($content, 0, $spacePos);
            }

            preg_match('/\$[^\s]+/', $appendix, $matches);
            $variable = $matches ? $matches[0] : '';

            $docBlockParams[] = [
                'index' => $classNameIndex,
                'type' => $content,
                'variable' => $variable,
                'appendix' => $appendix,
            ];
        }

        return $docBlockParams;
    }
}
{'fraction_non_alphanumeric': 0.14396999422965956, 'fraction_numerical': 0.0017311021350259665, 'mean_word_length': 1.8742399115533444, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 42, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1365661', 'n_tokens_mistral': 3083, 'n_tokens_neox': 2932, 'n_words': 626}