File "NonPrintableCharacterFixer.php"
Full Path: /var/www/html/back/vendor/friendsofphp/php-cs-fixer/src/Fixer/Basic/NonPrintableCharacterFixer.php
File size: 6.7 KB
MIME-type: text/x-php
Charset: utf-8
<?php
declare(strict_types=1);
/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz RumiĆski <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace PhpCsFixer\Fixer\Basic;
use PhpCsFixer\AbstractFixer;
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
use PhpCsFixer\Fixer\ConfigurableFixerTrait;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Removes Zero-width space (ZWSP), Non-breaking space (NBSP) and other invisible unicode symbols.
*
* @phpstan-type _AutogeneratedInputConfiguration array{
* use_escape_sequences_in_strings?: bool,
* }
* @phpstan-type _AutogeneratedComputedConfiguration array{
* use_escape_sequences_in_strings: bool,
* }
*
* @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
*
* @author Ivan Boprzenkov <ivan.borzenkov@gmail.com>
*
* @no-named-arguments Parameter names are not covered by the backward compatibility promise.
*/
final class NonPrintableCharacterFixer extends AbstractFixer implements ConfigurableFixerInterface
{
/** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
use ConfigurableFixerTrait;
/**
* @var non-empty-list<int>
*/
private const TOKENS = [
\T_STRING_VARNAME,
\T_INLINE_HTML,
\T_VARIABLE,
\T_COMMENT,
\T_ENCAPSED_AND_WHITESPACE,
\T_CONSTANT_ENCAPSED_STRING,
\T_DOC_COMMENT,
];
/**
* @var array<string, array{string, string}>
*/
private array $symbolsReplace;
public function __construct()
{
parent::__construct();
$this->symbolsReplace = [
pack('H*', 'e2808b') => ['', '200b'], // ZWSP U+200B
pack('H*', 'e28087') => [' ', '2007'], // FIGURE SPACE U+2007
pack('H*', 'e280af') => [' ', '202f'], // NBSP U+202F
pack('H*', 'e281a0') => ['', '2060'], // WORD JOINER U+2060
pack('H*', 'c2a0') => [' ', 'a0'], // NO-BREAK SPACE U+A0
];
}
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'Remove Zero-width space (ZWSP), Non-breaking space (NBSP) and other invisible unicode symbols.',
[
new CodeSample(
'<?php echo "'.pack('H*', 'e2808b').'Hello'.pack('H*', 'e28087').'World'.pack('H*', 'c2a0')."!\";\n",
),
new CodeSample(
'<?php echo "'.pack('H*', 'e2808b').'Hello'.pack('H*', 'e28087').'World'.pack('H*', 'c2a0')."!\";\n",
['use_escape_sequences_in_strings' => false],
),
],
null,
'Risky when strings contain intended invisible characters.',
);
}
public function isRisky(): bool
{
return true;
}
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isAnyTokenKindsFound(self::TOKENS);
}
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
{
return new FixerConfigurationResolver([
(new FixerOptionBuilder('use_escape_sequences_in_strings', 'Whether characters should be replaced with escape sequences in strings.'))
->setAllowedTypes(['bool'])
->setDefault(true)
->getOption(),
]);
}
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
$replacements = [];
$escapeSequences = [];
foreach ($this->symbolsReplace as $character => [$replacement, $codepoint]) {
$replacements[$character] = $replacement;
$escapeSequences[$character] = '\u{'.$codepoint.'}';
}
foreach ($tokens as $index => $token) {
$content = $token->getContent();
if (
true === $this->configuration['use_escape_sequences_in_strings']
&& $token->isGivenKind([\T_CONSTANT_ENCAPSED_STRING, \T_ENCAPSED_AND_WHITESPACE])
) {
if (!Preg::match('/'.implode('|', array_keys($escapeSequences)).'/', $content)) {
continue;
}
$previousToken = $tokens[$index - 1];
$stringTypeChanged = false;
$swapQuotes = false;
if ($previousToken->isGivenKind(\T_START_HEREDOC)) {
$previousTokenContent = $previousToken->getContent();
if (str_contains($previousTokenContent, '\'')) {
$tokens[$index - 1] = new Token([\T_START_HEREDOC, str_replace('\'', '', $previousTokenContent)]);
$stringTypeChanged = true;
}
} elseif (str_starts_with($content, "'")) {
$stringTypeChanged = true;
$swapQuotes = true;
}
if ($swapQuotes) {
$content = str_replace("\\'", "'", $content);
}
if ($stringTypeChanged) {
$content = Preg::replace('/(\\\{1,2})/', '\\\\\\\\', $content);
$content = str_replace('$', '\$', $content);
}
if ($swapQuotes) {
$content = str_replace('"', '\"', $content);
$content = Preg::replace('/^\'(.*)\'$/s', '"$1"', $content);
}
$tokens[$index] = new Token([$token->getId(), strtr($content, $escapeSequences)]);
continue;
}
if ($token->isGivenKind(self::TOKENS)) {
$newContent = strtr($content, $replacements);
// variable name cannot contain space
if ($token->isGivenKind([\T_STRING_VARNAME, \T_VARIABLE]) && str_contains($newContent, ' ')) {
continue;
}
// multiline comment must have "*/" only at the end
if ($token->isGivenKind([\T_COMMENT, \T_DOC_COMMENT]) && str_starts_with($newContent, '/*') && strpos($newContent, '*/') !== \strlen($newContent) - 2) {
continue;
}
$tokens[$index] = new Token([$token->getId(), $newContent]);
}
}
}
}