Missing dependancies

This commit is contained in:
tokslaw7
2023-06-21 12:19:22 +00:00
parent fbf3f180c2
commit 421f25c80d
2356 changed files with 342670 additions and 4743 deletions
@@ -0,0 +1,171 @@
<?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\StringNotation;
use PhpCsFixer\AbstractFixer;
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
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;
/**
* @author Filippo Tessarotto <zoeslam@gmail.com>
*/
final class EscapeImplicitBackslashesFixer extends AbstractFixer implements ConfigurableFixerInterface
{
/**
* {@inheritdoc}
*/
public function getDefinition(): FixerDefinitionInterface
{
$codeSample = <<<'EOF'
<?php
$singleQuoted = 'String with \" and My\Prefix\\';
$doubleQuoted = "Interpret my \n but not my \a";
$hereDoc = <<<HEREDOC
Interpret my \100 but not my \999
HEREDOC;
EOF;
return new FixerDefinition(
'Escape implicit backslashes in strings and heredocs to ease the understanding of which are special chars interpreted by PHP and which not.',
[
new CodeSample($codeSample),
new CodeSample(
$codeSample,
['single_quoted' => true]
),
new CodeSample(
$codeSample,
['double_quoted' => false]
),
new CodeSample(
$codeSample,
['heredoc_syntax' => false]
),
],
'In PHP double-quoted strings and heredocs some chars like `n`, `$` or `u` have special meanings if preceded by a backslash '
.'(and some are special only if followed by other special chars), while a backslash preceding other chars are interpreted like a plain '
.'backslash. The precise list of those special chars is hard to remember and to identify quickly: this fixer escapes backslashes '
."that do not start a special interpretation with the char after them.\n"
.'It is possible to fix also single-quoted strings: in this case there is no special chars apart from single-quote and backslash '
.'itself, so the fixer simply ensure that all backslashes are escaped. Both single and double backslashes are allowed in single-quoted '
.'strings, so the purpose in this context is mainly to have a uniformed way to have them written all over the codebase.'
);
}
/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isAnyTokenKindsFound([T_ENCAPSED_AND_WHITESPACE, T_CONSTANT_ENCAPSED_STRING]);
}
/**
* {@inheritdoc}
*
* Must run before HeredocToNowdocFixer, SingleQuoteFixer.
* Must run after BacktickToShellExecFixer.
*/
public function getPriority(): int
{
return 15;
}
/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
static $singleQuotedRegex = '/(?<!\\\\)\\\\((?:\\\\\\\\)*)(?![\\\'\\\\])/';
static $doubleQuotedRegex = '/(?<!\\\\)\\\\((?:\\\\\\\\)*)(?![efnrtv$"\\\\0-7]|x[0-9A-Fa-f]|u{)/';
static $heredocSyntaxRegex = '/(?<!\\\\)\\\\((?:\\\\\\\\)*)(?![efnrtv$\\\\0-7]|x[0-9A-Fa-f]|u{)/';
$doubleQuoteOpened = false;
foreach ($tokens as $index => $token) {
$content = $token->getContent();
if ($token->equalsAny(['"', 'b"', 'B"'])) {
$doubleQuoteOpened = !$doubleQuoteOpened;
}
if (!$token->isGivenKind([T_ENCAPSED_AND_WHITESPACE, T_CONSTANT_ENCAPSED_STRING]) || !str_contains($content, '\\')) {
continue;
}
// Nowdoc syntax
if ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE) && '\'' === substr(rtrim($tokens[$index - 1]->getContent()), -1)) {
continue;
}
$firstTwoCharacters = strtolower(substr($content, 0, 2));
$isSingleQuotedString = $token->isGivenKind(T_CONSTANT_ENCAPSED_STRING) && ('\'' === $content[0] || 'b\'' === $firstTwoCharacters);
$isDoubleQuotedString =
($token->isGivenKind(T_CONSTANT_ENCAPSED_STRING) && ('"' === $content[0] || 'b"' === $firstTwoCharacters))
|| ($token->isGivenKind(T_ENCAPSED_AND_WHITESPACE) && $doubleQuoteOpened)
;
$isHeredocSyntax = !$isSingleQuotedString && !$isDoubleQuotedString;
if (
(false === $this->configuration['single_quoted'] && $isSingleQuotedString)
|| (false === $this->configuration['double_quoted'] && $isDoubleQuotedString)
|| (false === $this->configuration['heredoc_syntax'] && $isHeredocSyntax)
) {
continue;
}
$regex = $heredocSyntaxRegex;
if ($isSingleQuotedString) {
$regex = $singleQuotedRegex;
} elseif ($isDoubleQuotedString) {
$regex = $doubleQuotedRegex;
}
$newContent = Preg::replace($regex, '\\\\\\\\$1', $content);
if ($newContent !== $content) {
$tokens[$index] = new Token([$token->getId(), $newContent]);
}
}
}
/**
* {@inheritdoc}
*/
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
{
return new FixerConfigurationResolver([
(new FixerOptionBuilder('single_quoted', 'Whether to fix single-quoted strings.'))
->setAllowedTypes(['bool'])
->setDefault(false)
->getOption(),
(new FixerOptionBuilder('double_quoted', 'Whether to fix double-quoted strings.'))
->setAllowedTypes(['bool'])
->setDefault(true)
->getOption(),
(new FixerOptionBuilder('heredoc_syntax', 'Whether to fix heredoc syntax.'))
->setAllowedTypes(['bool'])
->setDefault(true)
->getOption(),
]);
}
}
@@ -0,0 +1,175 @@
<?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\StringNotation;
use PhpCsFixer\AbstractFixer;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @author Filippo Tessarotto <zoeslam@gmail.com>
*/
final class ExplicitStringVariableFixer extends AbstractFixer
{
/**
* {@inheritdoc}
*/
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'Converts implicit variables into explicit ones in double-quoted strings or heredoc syntax.',
[new CodeSample(
<<<'EOT'
<?php
$a = "My name is $name !";
$b = "I live in $state->country !";
$c = "I have $farm[0] chickens !";
EOT
)],
'The reasoning behind this rule is the following:'
."\n".'- When there are two valid ways of doing the same thing, using both is confusing, there should be a coding standard to follow'
."\n".'- PHP manual marks `"$var"` syntax as implicit and `"${var}"` syntax as explicit: explicit code should always be preferred'
."\n".'- Explicit syntax allows word concatenation inside strings, e.g. `"${var}IsAVar"`, implicit doesn\'t'
."\n".'- Explicit syntax is easier to detect for IDE/editors and therefore has colors/highlight with higher contrast, which is easier to read'
."\n".'Backtick operator is skipped because it is harder to handle; you can use `backtick_to_shell_exec` fixer to normalize backticks to strings'
);
}
/**
* {@inheritdoc}
*
* Must run after BacktickToShellExecFixer.
*/
public function getPriority(): int
{
return 0;
}
/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isTokenKindFound(T_VARIABLE);
}
/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
$backtickStarted = false;
for ($index = \count($tokens) - 1; $index > 0; --$index) {
$token = $tokens[$index];
if ($token->equals('`')) {
$backtickStarted = !$backtickStarted;
continue;
}
if ($backtickStarted || !$token->isGivenKind(T_VARIABLE)) {
continue;
}
$prevToken = $tokens[$index - 1];
if (!$this->isStringPartToken($prevToken)) {
continue;
}
$distinctVariableIndex = $index;
$variableTokens = [
$distinctVariableIndex => [
'tokens' => [$index => $token],
'firstVariableTokenIndex' => $index,
'lastVariableTokenIndex' => $index,
],
];
$nextIndex = $index + 1;
$squareBracketCount = 0;
while (!$this->isStringPartToken($tokens[$nextIndex])) {
if ($tokens[$nextIndex]->isGivenKind(T_CURLY_OPEN)) {
$nextIndex = $tokens->getNextTokenOfKind($nextIndex, [[CT::T_CURLY_CLOSE]]);
} elseif ($tokens[$nextIndex]->isGivenKind(T_VARIABLE) && 1 !== $squareBracketCount) {
$distinctVariableIndex = $nextIndex;
$variableTokens[$distinctVariableIndex] = [
'tokens' => [$nextIndex => $tokens[$nextIndex]],
'firstVariableTokenIndex' => $nextIndex,
'lastVariableTokenIndex' => $nextIndex,
];
} else {
$variableTokens[$distinctVariableIndex]['tokens'][$nextIndex] = $tokens[$nextIndex];
$variableTokens[$distinctVariableIndex]['lastVariableTokenIndex'] = $nextIndex;
if ($tokens[$nextIndex]->equalsAny(['[', ']'])) {
++$squareBracketCount;
}
}
++$nextIndex;
}
krsort($variableTokens, SORT_NUMERIC);
foreach ($variableTokens as $distinctVariableSet) {
if (1 === \count($distinctVariableSet['tokens'])) {
$singleVariableIndex = key($distinctVariableSet['tokens']);
$singleVariableToken = current($distinctVariableSet['tokens']);
$tokens->overrideRange($singleVariableIndex, $singleVariableIndex, [
new Token([T_CURLY_OPEN, '{']),
new Token([T_VARIABLE, $singleVariableToken->getContent()]),
new Token([CT::T_CURLY_CLOSE, '}']),
]);
} else {
foreach ($distinctVariableSet['tokens'] as $variablePartIndex => $variablePartToken) {
if ($variablePartToken->isGivenKind(T_NUM_STRING)) {
$tokens[$variablePartIndex] = new Token([T_LNUMBER, $variablePartToken->getContent()]);
continue;
}
if ($variablePartToken->isGivenKind(T_STRING) && $tokens[$variablePartIndex + 1]->equals(']')) {
$tokens[$variablePartIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, "'".$variablePartToken->getContent()."'"]);
}
}
$tokens->insertAt($distinctVariableSet['lastVariableTokenIndex'] + 1, new Token([CT::T_CURLY_CLOSE, '}']));
$tokens->insertAt($distinctVariableSet['firstVariableTokenIndex'], new Token([T_CURLY_OPEN, '{']));
}
}
}
}
/**
* Check if token is a part of a string.
*
* @param Token $token The token to check
*/
private function isStringPartToken(Token $token): bool
{
return $token->isGivenKind(T_ENCAPSED_AND_WHITESPACE)
|| $token->isGivenKind(T_START_HEREDOC)
|| '"' === $token->getContent()
|| 'b"' === strtolower($token->getContent())
;
}
}
@@ -0,0 +1,116 @@
<?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\StringNotation;
use PhpCsFixer\AbstractFixer;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @author Gregor Harlan <gharlan@web.de>
*/
final class HeredocToNowdocFixer extends AbstractFixer
{
/**
* {@inheritdoc}
*/
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'Convert `heredoc` to `nowdoc` where possible.',
[
new CodeSample(
<<<'EOF'
<?php $a = <<<"TEST"
Foo
TEST;
EOF
),
]
);
}
/**
* {@inheritdoc}
*
* Must run after EscapeImplicitBackslashesFixer.
*/
public function getPriority(): int
{
return 0;
}
/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isTokenKindFound(T_START_HEREDOC);
}
/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
foreach ($tokens as $index => $token) {
if (!$token->isGivenKind(T_START_HEREDOC) || str_contains($token->getContent(), "'")) {
continue;
}
if ($tokens[$index + 1]->isGivenKind(T_END_HEREDOC)) {
$tokens[$index] = $this->convertToNowdoc($token);
continue;
}
if (
!$tokens[$index + 1]->isGivenKind(T_ENCAPSED_AND_WHITESPACE)
|| !$tokens[$index + 2]->isGivenKind(T_END_HEREDOC)
) {
continue;
}
$content = $tokens[$index + 1]->getContent();
// regex: odd number of backslashes, not followed by dollar
if (Preg::match('/(?<!\\\\)(?:\\\\{2})*\\\\(?![$\\\\])/', $content)) {
continue;
}
$tokens[$index] = $this->convertToNowdoc($token);
$content = str_replace(['\\\\', '\\$'], ['\\', '$'], $content);
$tokens[$index + 1] = new Token([
$tokens[$index + 1]->getId(),
$content,
]);
}
}
/**
* Transforms the heredoc start token to nowdoc notation.
*/
private function convertToNowdoc(Token $token): Token
{
return new Token([
$token->getId(),
Preg::replace('/^([Bb]?<<<)(\h*)"?([^\s"]+)"?/', '$1$2\'$3\'', $token->getContent()),
]);
}
}
@@ -0,0 +1,84 @@
<?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\StringNotation;
use PhpCsFixer\AbstractFixer;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @author ntzm
*/
final class NoBinaryStringFixer extends AbstractFixer
{
/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isAnyTokenKindsFound(
[
T_CONSTANT_ENCAPSED_STRING,
T_START_HEREDOC,
'b"',
]
);
}
/**
* {@inheritdoc}
*/
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'There should not be a binary flag before strings.',
[
new CodeSample("<?php \$a = b'foo';\n"),
new CodeSample("<?php \$a = b<<<EOT\nfoo\nEOT;\n"),
]
);
}
/**
* {@inheritdoc}
*
* Must run before NoUselessConcatOperatorFixer, PhpUnitDedicateAssertInternalTypeFixer, RegularCallableCallFixer, SetTypeToCastFixer.
*/
public function getPriority(): int
{
return 40;
}
/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
foreach ($tokens as $index => $token) {
if ($token->isGivenKind([T_CONSTANT_ENCAPSED_STRING, T_START_HEREDOC])) {
$content = $token->getContent();
if ('b' === strtolower($content[0])) {
$tokens[$index] = new Token([$token->getId(), substr($content, 1)]);
}
} elseif ($token->equals('b"')) {
$tokens[$index] = new Token('"');
}
}
}
}
@@ -0,0 +1,112 @@
<?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\StringNotation;
use PhpCsFixer\AbstractFixer;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @author Gregor Harlan
*/
final class NoTrailingWhitespaceInStringFixer extends AbstractFixer
{
/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isAnyTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE, T_INLINE_HTML]);
}
/**
* {@inheritdoc}
*/
public function isRisky(): bool
{
return true;
}
/**
* {@inheritdoc}
*/
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'There must be no trailing whitespace in strings.',
[
new CodeSample(
"<?php \$a = ' \n foo \n';\n"
),
],
null,
'Changing the whitespaces in strings might affect string comparisons and outputs.'
);
}
/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
for ($index = $tokens->count() - 1, $last = true; $index >= 0; --$index, $last = false) {
/** @var Token $token */
$token = $tokens[$index];
if (!$token->isGivenKind([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE, T_INLINE_HTML])) {
continue;
}
$isInlineHtml = $token->isGivenKind(T_INLINE_HTML);
$regex = $isInlineHtml && $last ? '/\h+(?=\R|$)/' : '/\h+(?=\R)/';
$content = Preg::replace($regex, '', $token->getContent());
if ($token->getContent() === $content) {
continue;
}
if (!$isInlineHtml || 0 === $index) {
$this->updateContent($tokens, $index, $content);
continue;
}
$prev = $index - 1;
if ($tokens[$prev]->equals([T_CLOSE_TAG, '?>']) && Preg::match('/^\R/', $content, $match)) {
$tokens[$prev] = new Token([T_CLOSE_TAG, $tokens[$prev]->getContent().$match[0]]);
$content = substr($content, \strlen($match[0]));
$content = false === $content ? '' : $content; // @phpstan-ignore-line due to https://github.com/phpstan/phpstan/issues/1215 , awaiting PHP8 as min requirement of Fixer
}
$this->updateContent($tokens, $index, $content);
}
}
private function updateContent(Tokens $tokens, int $index, string $content): void
{
if ('' === $content) {
$tokens->clearAt($index);
return;
}
$tokens[$index] = new Token([$tokens[$index]->getId(), $content]);
}
}
@@ -0,0 +1,116 @@
<?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\StringNotation;
use PhpCsFixer\AbstractFixer;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* @author Dave van der Brugge <dmvdbrugge@gmail.com>
*/
final class SimpleToComplexStringVariableFixer extends AbstractFixer
{
/**
* {@inheritdoc}
*/
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'Converts explicit variables in double-quoted strings and heredoc syntax from simple to complex format (`${` to `{$`).',
[
new CodeSample(
<<<'EOT'
<?php
$name = 'World';
echo "Hello ${name}!";
EOT
),
new CodeSample(
<<<'EOT'
<?php
$name = 'World';
echo <<<TEST
Hello ${name}!
TEST;
EOT
),
],
"Doesn't touch implicit variables. Works together nicely with `explicit_string_variable`."
);
}
/**
* {@inheritdoc}
*
* Must run after ExplicitStringVariableFixer.
*/
public function getPriority(): int
{
return -10;
}
/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isTokenKindFound(T_DOLLAR_OPEN_CURLY_BRACES);
}
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
for ($index = \count($tokens) - 3; $index > 0; --$index) {
$token = $tokens[$index];
if (!$token->isGivenKind(T_DOLLAR_OPEN_CURLY_BRACES)) {
continue;
}
$varnameToken = $tokens[$index + 1];
if (!$varnameToken->isGivenKind(T_STRING_VARNAME)) {
continue;
}
$dollarCloseToken = $tokens[$index + 2];
if (!$dollarCloseToken->isGivenKind(CT::T_DOLLAR_CLOSE_CURLY_BRACES)) {
continue;
}
$tokenOfStringBeforeToken = $tokens[$index - 1];
$stringContent = $tokenOfStringBeforeToken->getContent();
if (str_ends_with($stringContent, '$') && !str_ends_with($stringContent, '\\$')) {
$newContent = substr($stringContent, 0, -1).'\\$';
$tokenOfStringBeforeToken = new Token([T_ENCAPSED_AND_WHITESPACE, $newContent]);
}
$tokens->overrideRange($index - 1, $index + 2, [
$tokenOfStringBeforeToken,
new Token([T_CURLY_OPEN, '{']),
new Token([T_VARIABLE, '$'.$varnameToken->getContent()]),
new Token([CT::T_CURLY_CLOSE, '}']),
]);
}
}
}
@@ -0,0 +1,121 @@
<?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\StringNotation;
use PhpCsFixer\AbstractFixer;
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
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;
/**
* @author Gregor Harlan <gharlan@web.de>
*/
final class SingleQuoteFixer extends AbstractFixer implements ConfigurableFixerInterface
{
/**
* {@inheritdoc}
*/
public function getDefinition(): FixerDefinitionInterface
{
$codeSample = <<<'EOF'
<?php
$a = "sample";
$b = "sample with 'single-quotes'";
EOF;
return new FixerDefinition(
'Convert double quotes to single quotes for simple strings.',
[
new CodeSample($codeSample),
new CodeSample(
$codeSample,
['strings_containing_single_quote_chars' => true]
),
]
);
}
/**
* {@inheritdoc}
*
* Must run before NoUselessConcatOperatorFixer.
* Must run after BacktickToShellExecFixer, EscapeImplicitBackslashesFixer.
*/
public function getPriority(): int
{
return 10;
}
/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isTokenKindFound(T_CONSTANT_ENCAPSED_STRING);
}
/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
foreach ($tokens as $index => $token) {
if (!$token->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) {
continue;
}
$content = $token->getContent();
$prefix = '';
if ('b' === strtolower($content[0])) {
$prefix = $content[0];
$content = substr($content, 1);
}
if (
'"' === $content[0]
&& (true === $this->configuration['strings_containing_single_quote_chars'] || !str_contains($content, "'"))
// regex: odd number of backslashes, not followed by double quote or dollar
&& !Preg::match('/(?<!\\\\)(?:\\\\{2})*\\\\(?!["$\\\\])/', $content)
) {
$content = substr($content, 1, -1);
$content = str_replace(['\\"', '\\$', '\''], ['"', '$', '\\\''], $content);
$tokens[$index] = new Token([T_CONSTANT_ENCAPSED_STRING, $prefix.'\''.$content.'\'']);
}
}
}
/**
* {@inheritdoc}
*/
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
{
return new FixerConfigurationResolver([
(new FixerOptionBuilder('strings_containing_single_quote_chars', 'Whether to fix double-quoted strings that contains single-quotes.'))
->setAllowedTypes(['bool'])
->setDefault(false)
->getOption(),
]);
}
}
@@ -0,0 +1,329 @@
<?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\StringNotation;
use PhpCsFixer\AbstractFunctionReferenceFixer;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
final class StringLengthToEmptyFixer extends AbstractFunctionReferenceFixer
{
/**
* {@inheritdoc}
*/
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'String tests for empty must be done against `\'\'`, not with `strlen`.',
[new CodeSample("<?php \$a = 0 === strlen(\$b) || \\STRLEN(\$c) < 1;\n")],
null,
'Risky when `strlen` is overridden, when called using a `stringable` object, also no longer triggers warning when called using non-string(able).'
);
}
/**
* {@inheritdoc}
*
* Must run before NoExtraBlankLinesFixer, NoTrailingWhitespaceFixer.
* Must run after NoSpacesInsideParenthesisFixer.
*/
public function getPriority(): int
{
return 1;
}
/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
$argumentsAnalyzer = new ArgumentsAnalyzer();
foreach ($this->findStrLengthCalls($tokens) as $candidate) {
[$functionNameIndex, $openParenthesisIndex, $closeParenthesisIndex] = $candidate;
$arguments = $argumentsAnalyzer->getArguments($tokens, $openParenthesisIndex, $closeParenthesisIndex);
if (1 !== \count($arguments)) {
continue; // must be one argument
}
// test for leading `\` before `strlen` call
$nextIndex = $tokens->getNextMeaningfulToken($closeParenthesisIndex);
$previousIndex = $tokens->getPrevMeaningfulToken($functionNameIndex);
if ($tokens[$previousIndex]->isGivenKind(T_NS_SEPARATOR)) {
$namespaceSeparatorIndex = $previousIndex;
$previousIndex = $tokens->getPrevMeaningfulToken($previousIndex);
} else {
$namespaceSeparatorIndex = null;
}
// test for yoda vs non-yoda fix case
if ($this->isOperatorOfInterest($tokens[$previousIndex])) { // test if valid yoda case to fix
$operatorIndex = $previousIndex;
$operandIndex = $tokens->getPrevMeaningfulToken($previousIndex);
if (!$this->isOperandOfInterest($tokens[$operandIndex])) { // test if operand is `0` or `1`
continue;
}
$replacement = $this->getReplacementYoda($tokens[$operatorIndex], $tokens[$operandIndex]);
if (null === $replacement) {
continue;
}
if ($this->isOfHigherPrecedence($tokens[$nextIndex])) { // is of higher precedence right; continue
continue;
}
if ($this->isOfHigherPrecedence($tokens[$tokens->getPrevMeaningfulToken($operandIndex)])) { // is of higher precedence left; continue
continue;
}
} elseif ($this->isOperatorOfInterest($tokens[$nextIndex])) { // test if valid !yoda case to fix
$operatorIndex = $nextIndex;
$operandIndex = $tokens->getNextMeaningfulToken($nextIndex);
if (!$this->isOperandOfInterest($tokens[$operandIndex])) { // test if operand is `0` or `1`
continue;
}
$replacement = $this->getReplacementNotYoda($tokens[$operatorIndex], $tokens[$operandIndex]);
if (null === $replacement) {
continue;
}
if ($this->isOfHigherPrecedence($tokens[$tokens->getNextMeaningfulToken($operandIndex)])) { // is of higher precedence right; continue
continue;
}
if ($this->isOfHigherPrecedence($tokens[$previousIndex])) { // is of higher precedence left; continue
continue;
}
} else {
continue;
}
// prepare for fixing
$keepParentheses = $this->keepParentheses($tokens, $openParenthesisIndex, $closeParenthesisIndex);
if (T_IS_IDENTICAL === $replacement) {
$operandContent = '===';
} else { // T_IS_NOT_IDENTICAL === $replacement
$operandContent = '!==';
}
// apply fixing
$tokens[$operandIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, "''"]);
$tokens[$operatorIndex] = new Token([$replacement, $operandContent]);
if (!$keepParentheses) {
$tokens->clearTokenAndMergeSurroundingWhitespace($closeParenthesisIndex);
$tokens->clearTokenAndMergeSurroundingWhitespace($openParenthesisIndex);
}
$tokens->clearTokenAndMergeSurroundingWhitespace($functionNameIndex);
if (null !== $namespaceSeparatorIndex) {
$tokens->clearTokenAndMergeSurroundingWhitespace($namespaceSeparatorIndex);
}
}
}
private function getReplacementYoda(Token $operator, Token $operand): ?int
{
/* Yoda 0
0 === strlen($b) | '' === $b
0 !== strlen($b) | '' !== $b
0 <= strlen($b) | X makes no sense, assume overridden
0 >= strlen($b) | '' === $b
0 < strlen($b) | '' !== $b
0 > strlen($b) | X makes no sense, assume overridden
*/
if ('0' === $operand->getContent()) {
if ($operator->isGivenKind([T_IS_IDENTICAL, T_IS_GREATER_OR_EQUAL])) {
return T_IS_IDENTICAL;
}
if ($operator->isGivenKind(T_IS_NOT_IDENTICAL) || $operator->equals('<')) {
return T_IS_NOT_IDENTICAL;
}
return null;
}
/* Yoda 1
1 === strlen($b) | X cannot simplify
1 !== strlen($b) | X cannot simplify
1 <= strlen($b) | '' !== $b
1 >= strlen($b) | cannot simplify
1 < strlen($b) | cannot simplify
1 > strlen($b) | '' === $b
*/
if ($operator->isGivenKind(T_IS_SMALLER_OR_EQUAL)) {
return T_IS_NOT_IDENTICAL;
}
if ($operator->equals('>')) {
return T_IS_IDENTICAL;
}
return null;
}
private function getReplacementNotYoda(Token $operator, Token $operand): ?int
{
/* Not Yoda 0
strlen($b) === 0 | $b === ''
strlen($b) !== 0 | $b !== ''
strlen($b) <= 0 | $b === ''
strlen($b) >= 0 | X makes no sense, assume overridden
strlen($b) < 0 | X makes no sense, assume overridden
strlen($b) > 0 | $b !== ''
*/
if ('0' === $operand->getContent()) {
if ($operator->isGivenKind([T_IS_IDENTICAL, T_IS_SMALLER_OR_EQUAL])) {
return T_IS_IDENTICAL;
}
if ($operator->isGivenKind(T_IS_NOT_IDENTICAL) || $operator->equals('>')) {
return T_IS_NOT_IDENTICAL;
}
return null;
}
/* Not Yoda 1
strlen($b) === 1 | X cannot simplify
strlen($b) !== 1 | X cannot simplify
strlen($b) <= 1 | X cannot simplify
strlen($b) >= 1 | $b !== ''
strlen($b) < 1 | $b === ''
strlen($b) > 1 | X cannot simplify
*/
if ($operator->isGivenKind(T_IS_GREATER_OR_EQUAL)) {
return T_IS_NOT_IDENTICAL;
}
if ($operator->equals('<')) {
return T_IS_IDENTICAL;
}
return null;
}
private function isOperandOfInterest(Token $token): bool
{
if (!$token->isGivenKind(T_LNUMBER)) {
return false;
}
$content = $token->getContent();
return '0' === $content || '1' === $content;
}
private function isOperatorOfInterest(Token $token): bool
{
return
$token->isGivenKind([T_IS_IDENTICAL, T_IS_NOT_IDENTICAL, T_IS_SMALLER_OR_EQUAL, T_IS_GREATER_OR_EQUAL])
|| $token->equals('<') || $token->equals('>')
;
}
private function isOfHigherPrecedence(Token $token): bool
{
static $operatorsPerContent = [
'!',
'%',
'*',
'+',
'-',
'.',
'/',
'~',
'?',
];
return $token->isGivenKind([T_INSTANCEOF, T_POW, T_SL, T_SR]) || $token->equalsAny($operatorsPerContent);
}
private function keepParentheses(Tokens $tokens, int $openParenthesisIndex, int $closeParenthesisIndex): bool
{
$i = $tokens->getNextMeaningfulToken($openParenthesisIndex);
if ($tokens[$i]->isCast()) {
$i = $tokens->getNextMeaningfulToken($i);
}
for (; $i < $closeParenthesisIndex; ++$i) {
$token = $tokens[$i];
if ($token->isGivenKind([T_VARIABLE, T_STRING]) || $token->isObjectOperator() || $token->isWhitespace() || $token->isComment()) {
continue;
}
$blockType = Tokens::detectBlockType($token);
if (null !== $blockType && $blockType['isStart']) {
$i = $tokens->findBlockEnd($blockType['type'], $i);
continue;
}
return true;
}
return false;
}
private function findStrLengthCalls(Tokens $tokens): \Generator
{
$candidates = [];
$count = \count($tokens);
for ($i = 0; $i < $count; ++$i) {
$candidate = $this->find('strlen', $tokens, $i, $count);
if (null === $candidate) {
break;
}
$i = $candidate[1]; // proceed to openParenthesisIndex
$candidates[] = $candidate;
}
foreach (array_reverse($candidates) as $candidate) {
yield $candidate;
}
}
}
@@ -0,0 +1,88 @@
<?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\StringNotation;
use PhpCsFixer\AbstractFixer;
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* Fixes the line endings in multi-line strings.
*
* @author Ilija Tovilo <ilija.tovilo@me.com>
*/
final class StringLineEndingFixer extends AbstractFixer implements WhitespacesAwareFixerInterface
{
/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isAnyTokenKindsFound([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE, T_INLINE_HTML]);
}
/**
* {@inheritdoc}
*/
public function isRisky(): bool
{
return true;
}
/**
* {@inheritdoc}
*/
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'All multi-line strings must use correct line ending.',
[
new CodeSample(
"<?php \$a = 'my\r\nmulti\nline\r\nstring';\r\n"
),
],
null,
'Changing the line endings of multi-line strings might affect string comparisons and outputs.'
);
}
/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
$ending = $this->whitespacesConfig->getLineEnding();
foreach ($tokens as $tokenIndex => $token) {
if (!$token->isGivenKind([T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE, T_INLINE_HTML])) {
continue;
}
$tokens[$tokenIndex] = new Token([
$token->getId(),
Preg::replace(
'#\R#u',
$ending,
$token->getContent()
),
]);
}
}
}