wishthis/vendor/phpcsstandards/phpcsextra/Universal/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php
2023-09-20 13:52:46 +02:00

173 lines
5.8 KiB
PHP

<?php
/**
* PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer.
*
* @package PHPCSExtra
* @copyright 2020 PHPCSExtra Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCSStandards/PHPCSExtra
*/
namespace PHPCSExtra\Universal\Sniffs\WhiteSpace;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
use PHPCSExtra\Universal\Helpers\DummyTokenizer;
use PHPCSUtils\BackCompat\Helper;
use PHPCSUtils\Tokens\Collections;
/**
* Enforces using spaces for mid-line alignment.
*
* While tab versus space based indentation is a question of preference, for mid-line
* alignment, spaces should always be preferred, as using tabs will result in inconsistent
* formatting depending on the dev-user's chosen tab width.
*
* This sniff is especially useful for tab-indentation based standards which use the
* `Generic.Whitespace.DisallowSpaceIndent` sniff to enforce this.
*
* **DO** make sure to set the PHPCS native `tab-width` configuration for the best results.
* <code>
* <arg name="tab-width" value="4"/>
* </code>
*
* The PHPCS native `Generic.Whitespace.DisallowTabIndent` sniff oversteps its reach and silently
* does mid-line tab to space replacements as well.
* However, the sister-sniff `Generic.Whitespace.DisallowSpaceIndent` leaves mid-line tabs/spaces alone.
* This sniff fills that gap.
*
* @since 1.0.0
*/
final class DisallowInlineTabsSniff implements Sniff
{
/**
* The --tab-width CLI value that is being used.
*
* @since 1.0.0
*
* @var int
*/
private $tabWidth;
/**
* Tokens to check for mid-line tabs.
*
* @since 1.0.0
*
* @var array<int|string, true>
*/
private $find = [
\T_WHITESPACE => true,
\T_DOC_COMMENT_WHITESPACE => true,
\T_DOC_COMMENT_STRING => true,
\T_COMMENT => true,
];
/**
* Registers the tokens that this sniff wants to listen for.
*
* @since 1.0.0
*
* @return array<int|string>
*/
public function register()
{
return Collections::phpOpenTags();
}
/**
* Processes this test, when one of its tokens is encountered.
*
* @since 1.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return int Integer stack pointer to skip the rest of the file.
*/
public function process(File $phpcsFile, $stackPtr)
{
if (isset($this->tabWidth) === false) {
$this->tabWidth = (int) Helper::getTabWidth($phpcsFile);
}
if (\defined('PHP_CODESNIFFER_IN_TESTS')) {
$this->tabWidth = (int) Helper::getCommandLineData($phpcsFile, 'tabWidth');
}
$tokens = $phpcsFile->getTokens();
$dummy = new DummyTokenizer('', $phpcsFile->config);
for ($i = 0; $i < $phpcsFile->numTokens; $i++) {
// Skip all non-whitespace tokens and skip whitespace at the start of a new line.
if (isset($this->find[$tokens[$i]['code']]) === false
|| (($tokens[$i]['code'] === \T_WHITESPACE
|| $tokens[$i]['code'] === \T_DOC_COMMENT_WHITESPACE)
&& $tokens[$i]['column'] === 1)
) {
continue;
}
// If tabs haven't been converted to spaces by the tokenizer, do so now.
$token = $tokens[$i];
if (isset($token['orig_content']) === false) {
if ($token['content'] === '' || \strpos($token['content'], "\t") === false) {
// If there are no tabs, we can continue, no matter what.
continue;
}
$dummy->replaceTabsInToken($token);
}
/*
* Tokens only have the 'orig_content' key if they contain tabs,
* so from here on out, we **know** there will be tabs in the content.
*/
$origContent = $token['orig_content'];
$commentOnly = '';
$multiLineComment = false;
if (($tokens[$i]['code'] === \T_COMMENT
|| isset(Tokens::$phpcsCommentTokens[$tokens[$i]['code']]))
&& $tokens[$i]['column'] === 1
&& ($tokens[($i - 1)]['code'] === \T_COMMENT
|| isset(Tokens::$phpcsCommentTokens[$tokens[($i - 1)]['code']]))
) {
$multiLineComment = true;
}
if ($multiLineComment === true) {
// This is the subsequent line of a multi-line comment. Account for indentation.
$commentOnly = \ltrim($origContent);
if ($commentOnly === '' || \strpos($commentOnly, "\t") === false) {
continue;
}
}
$fix = $phpcsFile->addFixableError(
'Spaces must be used for mid-line alignment; tabs are not allowed',
$i,
'NonIndentTabsUsed'
);
if ($fix === false) {
continue;
}
$indent = '';
if ($multiLineComment === true) {
// Take the original indent (tabs/spaces) and combine with the tab-replaced comment content.
$indent = \str_replace($commentOnly, '', $origContent);
$token['content'] = \ltrim($token['content']);
}
$phpcsFile->fixer->replaceToken($i, $indent . $token['content']);
}
// Scanned the whole file in one go. Don't scan this file again.
return $phpcsFile->numTokens;
}
}