Missing dependancies
This commit is contained in:
Vendored
+236
@@ -0,0 +1,236 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Gregor Harlan
|
||||
*/
|
||||
final class CombineNestedDirnameFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Replace multiple nested calls of `dirname` by only one call with second `$level` parameter. Requires PHP >= 7.0.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\ndirname(dirname(dirname(\$path)));\n"
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when the function `dirname` is overridden.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before MethodArgumentSpaceFixer, NoSpacesInsideParenthesisFixer.
|
||||
* Must run after DirConstantFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 35;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = $tokens->count() - 1; 0 <= $index; --$index) {
|
||||
$dirnameInfo = $this->getDirnameInfo($tokens, $index);
|
||||
|
||||
if (!$dirnameInfo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indexes'][0]);
|
||||
|
||||
if (!$tokens[$prev]->equals('(')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prev = $tokens->getPrevMeaningfulToken($prev);
|
||||
$firstArgumentEnd = $dirnameInfo['end'];
|
||||
$dirnameInfoArray = [$dirnameInfo];
|
||||
|
||||
while ($dirnameInfo = $this->getDirnameInfo($tokens, $prev, $firstArgumentEnd)) {
|
||||
$dirnameInfoArray[] = $dirnameInfo;
|
||||
$prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indexes'][0]);
|
||||
|
||||
if (!$tokens[$prev]->equals('(')) {
|
||||
break;
|
||||
}
|
||||
|
||||
$prev = $tokens->getPrevMeaningfulToken($prev);
|
||||
$firstArgumentEnd = $dirnameInfo['end'];
|
||||
}
|
||||
|
||||
if (\count($dirnameInfoArray) > 1) {
|
||||
$this->combineDirnames($tokens, $dirnameInfoArray);
|
||||
}
|
||||
|
||||
$index = $prev;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $index Index of `dirname`
|
||||
* @param null|int $firstArgumentEndIndex Index of last token of first argument of `dirname` call
|
||||
*
|
||||
* @return array{indexes: list<int>, secondArgument?: int, levels: int, end: int}|bool `false` when it is not a (supported) `dirname` call, an array with info about the dirname call otherwise
|
||||
*/
|
||||
private function getDirnameInfo(Tokens $tokens, int $index, ?int $firstArgumentEndIndex = null)
|
||||
{
|
||||
if (!$tokens[$index]->equals([T_STRING, 'dirname'], false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(new FunctionsAnalyzer())->isGlobalFunctionCall($tokens, $index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$info = ['indexes' => []];
|
||||
$prev = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$prev]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
$info['indexes'][] = $prev;
|
||||
}
|
||||
|
||||
$info['indexes'][] = $index;
|
||||
|
||||
// opening parenthesis "("
|
||||
$next = $tokens->getNextMeaningfulToken($index);
|
||||
$info['indexes'][] = $next;
|
||||
|
||||
if (null !== $firstArgumentEndIndex) {
|
||||
$next = $tokens->getNextMeaningfulToken($firstArgumentEndIndex);
|
||||
} else {
|
||||
$next = $tokens->getNextMeaningfulToken($next);
|
||||
|
||||
if ($tokens[$next]->equals(')')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (!$tokens[$next]->equalsAny([',', ')'])) {
|
||||
$blockType = Tokens::detectBlockType($tokens[$next]);
|
||||
|
||||
if (null !== $blockType) {
|
||||
$next = $tokens->findBlockEnd($blockType['type'], $next);
|
||||
}
|
||||
|
||||
$next = $tokens->getNextMeaningfulToken($next);
|
||||
}
|
||||
}
|
||||
|
||||
$info['indexes'][] = $next;
|
||||
|
||||
if ($tokens[$next]->equals(',')) {
|
||||
$next = $tokens->getNextMeaningfulToken($next);
|
||||
$info['indexes'][] = $next;
|
||||
}
|
||||
|
||||
if ($tokens[$next]->equals(')')) {
|
||||
$info['levels'] = 1;
|
||||
$info['end'] = $next;
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
if (!$tokens[$next]->isGivenKind(T_LNUMBER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['secondArgument'] = $next;
|
||||
$info['levels'] = (int) $tokens[$next]->getContent();
|
||||
|
||||
$next = $tokens->getNextMeaningfulToken($next);
|
||||
|
||||
if ($tokens[$next]->equals(',')) {
|
||||
$info['indexes'][] = $next;
|
||||
$next = $tokens->getNextMeaningfulToken($next);
|
||||
}
|
||||
|
||||
if (!$tokens[$next]->equals(')')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['indexes'][] = $next;
|
||||
$info['end'] = $next;
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array{indexes: list<int>, secondArgument?: int, levels: int, end: int}> $dirnameInfoArray
|
||||
*/
|
||||
private function combineDirnames(Tokens $tokens, array $dirnameInfoArray): void
|
||||
{
|
||||
$outerDirnameInfo = array_pop($dirnameInfoArray);
|
||||
$levels = $outerDirnameInfo['levels'];
|
||||
|
||||
foreach ($dirnameInfoArray as $dirnameInfo) {
|
||||
$levels += $dirnameInfo['levels'];
|
||||
|
||||
foreach ($dirnameInfo['indexes'] as $index) {
|
||||
$tokens->removeLeadingWhitespace($index);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($index);
|
||||
}
|
||||
}
|
||||
|
||||
$levelsToken = new Token([T_LNUMBER, (string) $levels]);
|
||||
|
||||
if (isset($outerDirnameInfo['secondArgument'])) {
|
||||
$tokens[$outerDirnameInfo['secondArgument']] = $levelsToken;
|
||||
} else {
|
||||
$prev = $tokens->getPrevMeaningfulToken($outerDirnameInfo['end']);
|
||||
$items = [];
|
||||
|
||||
if (!$tokens[$prev]->equals(',')) {
|
||||
$items = [new Token(','), new Token([T_WHITESPACE, ' '])];
|
||||
}
|
||||
|
||||
$items[] = $levelsToken;
|
||||
$tokens->insertAt($outerDirnameInfo['end'], $items);
|
||||
}
|
||||
}
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class DateTimeCreateFromFormatCallFixer extends AbstractFixer
|
||||
{
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'The first argument of `DateTime::createFromFormat` method must start with `!`.',
|
||||
[
|
||||
new CodeSample("<?php \\DateTime::createFromFormat('Y-m-d', '2022-02-11');\n"),
|
||||
],
|
||||
"Consider this code:
|
||||
`DateTime::createFromFormat('Y-m-d', '2022-02-11')`.
|
||||
What value will be returned? '2022-02-11 00:00:00.0'? No, actual return value has 'H:i:s' section like '2022-02-11 16:55:37.0'.
|
||||
Change 'Y-m-d' to '!Y-m-d', return value will be '2022-02-11 00:00:00.0'.
|
||||
So, adding `!` to format string will make return value more intuitive.",
|
||||
'Risky when depending on the actual timings being used even when not explicit set in format.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after NoUselessConcatOperatorFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOUBLE_COLON);
|
||||
}
|
||||
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$argumentsAnalyzer = new ArgumentsAnalyzer();
|
||||
$namespacesAnalyzer = new NamespacesAnalyzer();
|
||||
$namespaceUsesAnalyzer = new NamespaceUsesAnalyzer();
|
||||
|
||||
foreach ($namespacesAnalyzer->getDeclarations($tokens) as $namespace) {
|
||||
$scopeStartIndex = $namespace->getScopeStartIndex();
|
||||
$useDeclarations = $namespaceUsesAnalyzer->getDeclarationsInNamespace($tokens, $namespace);
|
||||
|
||||
for ($index = $namespace->getScopeEndIndex(); $index > $scopeStartIndex; --$index) {
|
||||
if (!$tokens[$index]->isGivenKind(T_DOUBLE_COLON)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$functionNameIndex = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if (!$tokens[$functionNameIndex]->equals([T_STRING, 'createFromFormat'], false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$tokens->getNextMeaningfulToken($functionNameIndex)]->equals('(')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$classNameIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
if (!$tokens[$classNameIndex]->equalsAny([[T_STRING, 'DateTime'], [T_STRING, 'DateTimeImmutable']], false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$preClassNameIndex = $tokens->getPrevMeaningfulToken($classNameIndex);
|
||||
|
||||
if ($tokens[$preClassNameIndex]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
if ($tokens[$tokens->getPrevMeaningfulToken($preClassNameIndex)]->isGivenKind(T_STRING)) {
|
||||
continue;
|
||||
}
|
||||
} elseif (!$namespace->isGlobalNamespace()) {
|
||||
continue;
|
||||
} else {
|
||||
foreach ($useDeclarations as $useDeclaration) {
|
||||
foreach (['datetime', 'datetimeimmutable'] as $name) {
|
||||
if ($name === strtolower($useDeclaration->getShortName()) && $name !== strtolower($useDeclaration->getFullName())) {
|
||||
continue 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$openIndex = $tokens->getNextTokenOfKind($functionNameIndex, ['(']);
|
||||
$closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex);
|
||||
|
||||
$argumentIndex = $this->getFirstArgumentTokenIndex($tokens, $argumentsAnalyzer->getArguments($tokens, $openIndex, $closeIndex));
|
||||
|
||||
if (null === $argumentIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$format = $tokens[$argumentIndex]->getContent();
|
||||
|
||||
if (\strlen($format) < 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$offset = 'b' === $format[0] || 'B' === $format[0] ? 2 : 1;
|
||||
|
||||
if ('!' === $format[$offset]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens->clearAt($argumentIndex);
|
||||
$tokens->insertAt($argumentIndex, new Token([T_CONSTANT_ENCAPSED_STRING, substr_replace($format, '!', $offset, 0)]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, int> $arguments
|
||||
*/
|
||||
private function getFirstArgumentTokenIndex(Tokens $tokens, array $arguments): ?int
|
||||
{
|
||||
if (2 !== \count($arguments)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$argumentStartIndex = array_key_first($arguments);
|
||||
$argumentEndIndex = $arguments[$argumentStartIndex];
|
||||
$argumentStartIndex = $tokens->getNextMeaningfulToken($argumentStartIndex - 1);
|
||||
|
||||
if (
|
||||
$argumentStartIndex !== $argumentEndIndex
|
||||
&& $tokens->getNextMeaningfulToken($argumentStartIndex) <= $argumentEndIndex
|
||||
) {
|
||||
return null; // argument is not a simple single string
|
||||
}
|
||||
|
||||
return !$tokens[$argumentStartIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)
|
||||
? null // first argument is not a string
|
||||
: $argumentStartIndex;
|
||||
}
|
||||
}
|
||||
Vendored
+126
@@ -0,0 +1,126 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFopenFlagFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class FopenFlagOrderFixer extends AbstractFopenFlagFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Order the flags in `fopen` calls, `b` and `t` must be last.',
|
||||
[new CodeSample("<?php\n\$a = fopen(\$foo, 'br+');\n")],
|
||||
null,
|
||||
'Risky when the function `fopen` is overridden.'
|
||||
);
|
||||
}
|
||||
|
||||
protected function fixFopenFlagToken(Tokens $tokens, int $argumentStartIndex, int $argumentEndIndex): void
|
||||
{
|
||||
$argumentFlagIndex = null;
|
||||
|
||||
for ($i = $argumentStartIndex; $i <= $argumentEndIndex; ++$i) {
|
||||
if ($tokens[$i]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null !== $argumentFlagIndex) {
|
||||
return; // multiple meaningful tokens found, no candidate for fixing
|
||||
}
|
||||
|
||||
$argumentFlagIndex = $i;
|
||||
}
|
||||
|
||||
// check if second argument is candidate
|
||||
if (null === $argumentFlagIndex || !$tokens[$argumentFlagIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$content = $tokens[$argumentFlagIndex]->getContent();
|
||||
$contentQuote = $content[0]; // `'`, `"`, `b` or `B`
|
||||
|
||||
if ('b' === $contentQuote || 'B' === $contentQuote) {
|
||||
$binPrefix = $contentQuote;
|
||||
$contentQuote = $content[1]; // `'` or `"`
|
||||
$mode = substr($content, 2, -1);
|
||||
} else {
|
||||
$binPrefix = '';
|
||||
$mode = substr($content, 1, -1);
|
||||
}
|
||||
|
||||
$modeLength = \strlen($mode);
|
||||
if ($modeLength < 2) {
|
||||
return; // nothing to sort
|
||||
}
|
||||
|
||||
if (false === $this->isValidModeString($mode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$split = $this->sortFlags(Preg::split('#([^\+]\+?)#', $mode, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE));
|
||||
$newContent = $binPrefix.$contentQuote.implode('', $split).$contentQuote;
|
||||
|
||||
if ($content !== $newContent) {
|
||||
$tokens[$argumentFlagIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, $newContent]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $flags
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function sortFlags(array $flags): array
|
||||
{
|
||||
usort(
|
||||
$flags,
|
||||
static function (string $flag1, string $flag2): int {
|
||||
if ($flag1 === $flag2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ('b' === $flag1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ('b' === $flag2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ('t' === $flag1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ('t' === $flag2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return $flag1 < $flag2 ? -1 : 1;
|
||||
}
|
||||
);
|
||||
|
||||
return $flags;
|
||||
}
|
||||
}
|
||||
+112
@@ -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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFopenFlagFixer;
|
||||
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\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class FopenFlagsFixer extends AbstractFopenFlagFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'The flags in `fopen` calls must omit `t`, and `b` must be omitted or included consistently.',
|
||||
[
|
||||
new CodeSample("<?php\n\$a = fopen(\$foo, 'rwt');\n"),
|
||||
new CodeSample("<?php\n\$a = fopen(\$foo, 'rwt');\n", ['b_mode' => false]),
|
||||
],
|
||||
null,
|
||||
'Risky when the function `fopen` is overridden.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('b_mode', 'The `b` flag must be used (`true`) or omitted (`false`).'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function fixFopenFlagToken(Tokens $tokens, int $argumentStartIndex, int $argumentEndIndex): void
|
||||
{
|
||||
$argumentFlagIndex = null;
|
||||
|
||||
for ($i = $argumentStartIndex; $i <= $argumentEndIndex; ++$i) {
|
||||
if ($tokens[$i]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null !== $argumentFlagIndex) {
|
||||
return; // multiple meaningful tokens found, no candidate for fixing
|
||||
}
|
||||
|
||||
$argumentFlagIndex = $i;
|
||||
}
|
||||
|
||||
// check if second argument is candidate
|
||||
if (null === $argumentFlagIndex || !$tokens[$argumentFlagIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$content = $tokens[$argumentFlagIndex]->getContent();
|
||||
$contentQuote = $content[0]; // `'`, `"`, `b` or `B`
|
||||
|
||||
if ('b' === $contentQuote || 'B' === $contentQuote) {
|
||||
$binPrefix = $contentQuote;
|
||||
$contentQuote = $content[1]; // `'` or `"`
|
||||
$mode = substr($content, 2, -1);
|
||||
} else {
|
||||
$binPrefix = '';
|
||||
$mode = substr($content, 1, -1);
|
||||
}
|
||||
|
||||
if (false === $this->isValidModeString($mode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$mode = str_replace('t', '', $mode);
|
||||
|
||||
if (true === $this->configuration['b_mode']) {
|
||||
if (!str_contains($mode, 'b')) {
|
||||
$mode .= 'b';
|
||||
}
|
||||
} else {
|
||||
$mode = str_replace('b', '', $mode);
|
||||
}
|
||||
|
||||
$newContent = $binPrefix.$contentQuote.$mode.$contentQuote;
|
||||
|
||||
if ($content !== $newContent) {
|
||||
$tokens[$argumentFlagIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, $newContent]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+261
@@ -0,0 +1,261 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
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\FixerDefinition\VersionSpecification;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
/**
|
||||
* Fixer for rules defined in PSR2 generally (¶1 and ¶6).
|
||||
*
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class FunctionDeclarationFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const SPACING_NONE = 'none';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const SPACING_ONE = 'one';
|
||||
|
||||
private const SUPPORTED_SPACINGS = [self::SPACING_NONE, self::SPACING_ONE];
|
||||
|
||||
private string $singleLineWhitespaceOptions = " \t";
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Spaces should be properly placed in a function declaration.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
class Foo
|
||||
{
|
||||
public static function bar ( $baz , $foo )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function foo ($bar, $baz)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$f = function () {};
|
||||
',
|
||||
['closure_function_spacing' => self::SPACING_NONE]
|
||||
),
|
||||
new VersionSpecificCodeSample(
|
||||
'<?php
|
||||
$f = fn () => null;
|
||||
',
|
||||
new VersionSpecification(70400),
|
||||
['closure_fn_spacing' => self::SPACING_NONE]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before MethodArgumentSpaceFixer.
|
||||
* Must run after SingleSpaceAfterConstructFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 31;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
for ($index = $tokens->count() - 1; $index >= 0; --$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if (!$token->isGivenKind([T_FUNCTION, T_FN])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$startParenthesisIndex = $tokens->getNextTokenOfKind($index, ['(', ';', [T_CLOSE_TAG]]);
|
||||
|
||||
if (!$tokens[$startParenthesisIndex]->equals('(')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startParenthesisIndex);
|
||||
|
||||
if (false === $this->configuration['trailing_comma_single_line']
|
||||
&& !$tokens->isPartialCodeMultiline($index, $endParenthesisIndex)
|
||||
) {
|
||||
$commaIndex = $tokens->getPrevMeaningfulToken($endParenthesisIndex);
|
||||
|
||||
if ($tokens[$commaIndex]->equals(',')) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($commaIndex);
|
||||
}
|
||||
}
|
||||
|
||||
$startBraceIndex = $tokens->getNextTokenOfKind($endParenthesisIndex, [';', '{', [T_DOUBLE_ARROW]]);
|
||||
|
||||
// fix single-line whitespace before { or =>
|
||||
// eg: `function foo(){}` => `function foo() {}`
|
||||
// eg: `function foo() {}` => `function foo() {}`
|
||||
// eg: `fn() =>` => `fn() =>`
|
||||
if (
|
||||
$tokens[$startBraceIndex]->equalsAny(['{', [T_DOUBLE_ARROW]])
|
||||
&& (
|
||||
!$tokens[$startBraceIndex - 1]->isWhitespace()
|
||||
|| $tokens[$startBraceIndex - 1]->isWhitespace($this->singleLineWhitespaceOptions)
|
||||
)
|
||||
) {
|
||||
$tokens->ensureWhitespaceAtIndex($startBraceIndex - 1, 1, ' ');
|
||||
}
|
||||
|
||||
$afterParenthesisIndex = $tokens->getNextNonWhitespace($endParenthesisIndex);
|
||||
$afterParenthesisToken = $tokens[$afterParenthesisIndex];
|
||||
|
||||
if ($afterParenthesisToken->isGivenKind(CT::T_USE_LAMBDA)) {
|
||||
// fix whitespace after CT:T_USE_LAMBDA (we might add a token, so do this before determining start and end parenthesis)
|
||||
$tokens->ensureWhitespaceAtIndex($afterParenthesisIndex + 1, 0, ' ');
|
||||
|
||||
$useStartParenthesisIndex = $tokens->getNextTokenOfKind($afterParenthesisIndex, ['(']);
|
||||
$useEndParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $useStartParenthesisIndex);
|
||||
|
||||
if (false === $this->configuration['trailing_comma_single_line']
|
||||
&& !$tokens->isPartialCodeMultiline($index, $useEndParenthesisIndex)
|
||||
) {
|
||||
$commaIndex = $tokens->getPrevMeaningfulToken($useEndParenthesisIndex);
|
||||
|
||||
if ($tokens[$commaIndex]->equals(',')) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($commaIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// remove single-line edge whitespaces inside use parentheses
|
||||
$this->fixParenthesisInnerEdge($tokens, $useStartParenthesisIndex, $useEndParenthesisIndex);
|
||||
|
||||
// fix whitespace before CT::T_USE_LAMBDA
|
||||
$tokens->ensureWhitespaceAtIndex($afterParenthesisIndex - 1, 1, ' ');
|
||||
}
|
||||
|
||||
// remove single-line edge whitespaces inside parameters list parentheses
|
||||
$this->fixParenthesisInnerEdge($tokens, $startParenthesisIndex, $endParenthesisIndex);
|
||||
$isLambda = $tokensAnalyzer->isLambda($index);
|
||||
|
||||
// remove whitespace before (
|
||||
// eg: `function foo () {}` => `function foo() {}`
|
||||
if (!$isLambda && $tokens[$startParenthesisIndex - 1]->isWhitespace() && !$tokens[$tokens->getPrevNonWhitespace($startParenthesisIndex - 1)]->isComment()) {
|
||||
$tokens->clearAt($startParenthesisIndex - 1);
|
||||
}
|
||||
|
||||
$option = $token->isGivenKind(T_FN) ? 'closure_fn_spacing' : 'closure_function_spacing';
|
||||
|
||||
if ($isLambda && self::SPACING_NONE === $this->configuration[$option]) {
|
||||
// optionally remove whitespace after T_FUNCTION of a closure
|
||||
// eg: `function () {}` => `function() {}`
|
||||
if ($tokens[$index + 1]->isWhitespace()) {
|
||||
$tokens->clearAt($index + 1);
|
||||
}
|
||||
} else {
|
||||
// otherwise, enforce whitespace after T_FUNCTION
|
||||
// eg: `function foo() {}` => `function foo() {}`
|
||||
$tokens->ensureWhitespaceAtIndex($index + 1, 0, ' ');
|
||||
}
|
||||
|
||||
if ($isLambda) {
|
||||
$prev = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$prev]->isGivenKind(T_STATIC)) {
|
||||
// fix whitespace after T_STATIC
|
||||
// eg: `$a = static function(){};` => `$a = static function(){};`
|
||||
$tokens->ensureWhitespaceAtIndex($prev + 1, 0, ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('closure_function_spacing', 'Spacing to use before open parenthesis for closures.'))
|
||||
->setDefault(self::SPACING_ONE)
|
||||
->setAllowedValues(self::SUPPORTED_SPACINGS)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('closure_fn_spacing', 'Spacing to use before open parenthesis for short arrow functions.'))
|
||||
->setDefault(self::SPACING_ONE) // @TODO change to SPACING_NONE on next major 4.0
|
||||
->setAllowedValues(self::SUPPORTED_SPACINGS)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('trailing_comma_single_line', 'Whether trailing commas are allowed in single line signatures.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function fixParenthesisInnerEdge(Tokens $tokens, int $start, int $end): void
|
||||
{
|
||||
do {
|
||||
--$end;
|
||||
} while ($tokens->isEmptyAt($end));
|
||||
|
||||
// remove single-line whitespace before `)`
|
||||
if ($tokens[$end]->isWhitespace($this->singleLineWhitespaceOptions)) {
|
||||
$tokens->clearAt($end);
|
||||
}
|
||||
|
||||
// remove single-line whitespace after `(`
|
||||
if ($tokens[$start + 1]->isWhitespace($this->singleLineWhitespaceOptions)) {
|
||||
$tokens->clearAt($start + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\Analysis\TypeAnalysis;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class FunctionTypehintSpaceFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Ensure single space between function\'s argument and its typehint.',
|
||||
[
|
||||
new CodeSample("<?php\nfunction sample(array\$a)\n{}\n"),
|
||||
new CodeSample("<?php\nfunction sample(array \$a)\n{}\n"),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$functionsAnalyzer = new FunctionsAnalyzer();
|
||||
|
||||
for ($index = $tokens->count() - 1; $index >= 0; --$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if (!$token->isGivenKind([T_FUNCTION, T_FN])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$arguments = $functionsAnalyzer->getFunctionArguments($tokens, $index);
|
||||
|
||||
foreach (array_reverse($arguments) as $argument) {
|
||||
$type = $argument->getTypeAnalysis();
|
||||
|
||||
if (!$type instanceof TypeAnalysis) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens->ensureWhitespaceAtIndex($type->getEndIndex() + 1, 0, ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Kuba Werłos <werlos@gmail.com>
|
||||
*/
|
||||
final class ImplodeCallFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Function `implode` must be called with 2 arguments in the documented order.',
|
||||
[
|
||||
new CodeSample("<?php\nimplode(\$pieces, '');\n"),
|
||||
new CodeSample("<?php\nimplode(\$pieces);\n"),
|
||||
],
|
||||
null,
|
||||
'Risky when the function `implode` is overridden.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before MethodArgumentSpaceFixer.
|
||||
* Must run after NoAliasFunctionsFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 37;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$functionsAnalyzer = new FunctionsAnalyzer();
|
||||
|
||||
for ($index = \count($tokens) - 1; $index > 0; --$index) {
|
||||
if (!$tokens[$index]->equals([T_STRING, 'implode'], false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$argumentsIndices = $this->getArgumentIndices($tokens, $index);
|
||||
|
||||
if (1 === \count($argumentsIndices)) {
|
||||
$firstArgumentIndex = key($argumentsIndices);
|
||||
$tokens->insertAt($firstArgumentIndex, [
|
||||
new Token([T_CONSTANT_ENCAPSED_STRING, "''"]),
|
||||
new Token(','),
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (2 === \count($argumentsIndices)) {
|
||||
[$firstArgumentIndex, $secondArgumentIndex] = array_keys($argumentsIndices);
|
||||
|
||||
// If the first argument is string we have nothing to do
|
||||
if ($tokens[$firstArgumentIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) {
|
||||
continue;
|
||||
}
|
||||
// If the second argument is not string we cannot make a swap
|
||||
if (!$tokens[$secondArgumentIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// collect tokens from first argument
|
||||
$firstArgumentEndIndex = $argumentsIndices[key($argumentsIndices)];
|
||||
$newSecondArgumentTokens = [];
|
||||
for ($i = key($argumentsIndices); $i <= $firstArgumentEndIndex; ++$i) {
|
||||
$newSecondArgumentTokens[] = clone $tokens[$i];
|
||||
$tokens->clearAt($i);
|
||||
}
|
||||
|
||||
$tokens->insertAt($firstArgumentIndex, clone $tokens[$secondArgumentIndex]);
|
||||
|
||||
// insert above increased the second argument index
|
||||
++$secondArgumentIndex;
|
||||
$tokens->clearAt($secondArgumentIndex);
|
||||
$tokens->insertAt($secondArgumentIndex, $newSecondArgumentTokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, int> In the format: startIndex => endIndex
|
||||
*/
|
||||
private function getArgumentIndices(Tokens $tokens, int $functionNameIndex): array
|
||||
{
|
||||
$argumentsAnalyzer = new ArgumentsAnalyzer();
|
||||
|
||||
$openParenthesis = $tokens->getNextTokenOfKind($functionNameIndex, ['(']);
|
||||
$closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis);
|
||||
|
||||
$indices = [];
|
||||
|
||||
foreach ($argumentsAnalyzer->getArguments($tokens, $openParenthesis, $closeParenthesis) as $startIndexCandidate => $endIndex) {
|
||||
$indices[$tokens->getNextMeaningfulToken($startIndexCandidate - 1)] = $tokens->getPrevMeaningfulToken($endIndex + 1);
|
||||
}
|
||||
|
||||
return $indices;
|
||||
}
|
||||
}
|
||||
Vendored
+352
@@ -0,0 +1,352 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
final class LambdaNotUsedImportFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* @var ArgumentsAnalyzer
|
||||
*/
|
||||
private $argumentsAnalyzer;
|
||||
|
||||
/**
|
||||
* @var FunctionsAnalyzer
|
||||
*/
|
||||
private $functionAnalyzer;
|
||||
|
||||
/**
|
||||
* @var TokensAnalyzer
|
||||
*/
|
||||
private $tokensAnalyzer;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Lambda must not import variables it doesn\'t use.',
|
||||
[new CodeSample("<?php\n\$foo = function() use (\$bar) {};\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before MethodArgumentSpaceFixer, NoSpacesInsideParenthesisFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 31;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAllTokenKindsFound([T_FUNCTION, CT::T_USE_LAMBDA]);
|
||||
}
|
||||
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$this->argumentsAnalyzer = new ArgumentsAnalyzer();
|
||||
$this->functionAnalyzer = new FunctionsAnalyzer();
|
||||
$this->tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
for ($index = $tokens->count() - 4; $index > 0; --$index) {
|
||||
$lambdaUseIndex = $this->getLambdaUseIndex($tokens, $index);
|
||||
|
||||
if (false !== $lambdaUseIndex) {
|
||||
$this->fixLambda($tokens, $lambdaUseIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixLambda(Tokens $tokens, int $lambdaUseIndex): void
|
||||
{
|
||||
$lambdaUseOpenBraceIndex = $tokens->getNextTokenOfKind($lambdaUseIndex, ['(']);
|
||||
$lambdaUseCloseBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $lambdaUseOpenBraceIndex);
|
||||
$arguments = $this->argumentsAnalyzer->getArguments($tokens, $lambdaUseOpenBraceIndex, $lambdaUseCloseBraceIndex);
|
||||
|
||||
$imports = $this->filterArguments($tokens, $arguments);
|
||||
|
||||
if (0 === \count($imports)) {
|
||||
return; // no imports to remove
|
||||
}
|
||||
|
||||
$notUsedImports = $this->findNotUsedLambdaImports($tokens, $imports, $lambdaUseCloseBraceIndex);
|
||||
$notUsedImportsCount = \count($notUsedImports);
|
||||
|
||||
if (0 === $notUsedImportsCount) {
|
||||
return; // no not used imports found
|
||||
}
|
||||
|
||||
if ($notUsedImportsCount === \count($arguments)) {
|
||||
$this->clearImportsAndUse($tokens, $lambdaUseIndex, $lambdaUseCloseBraceIndex); // all imports are not used
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->clearImports($tokens, array_reverse($notUsedImports));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, int>
|
||||
*/
|
||||
private function findNotUsedLambdaImports(Tokens $tokens, array $imports, int $lambdaUseCloseBraceIndex): array
|
||||
{
|
||||
static $riskyKinds = [
|
||||
CT::T_DYNAMIC_VAR_BRACE_OPEN,
|
||||
T_EVAL,
|
||||
T_INCLUDE,
|
||||
T_INCLUDE_ONCE,
|
||||
T_REQUIRE,
|
||||
T_REQUIRE_ONCE,
|
||||
];
|
||||
|
||||
// figure out where the lambda starts ...
|
||||
$lambdaOpenIndex = $tokens->getNextTokenOfKind($lambdaUseCloseBraceIndex, ['{']);
|
||||
$curlyBracesLevel = 0;
|
||||
|
||||
for ($index = $lambdaOpenIndex;; ++$index) { // go through the body of the lambda and keep count of the (possible) usages of the imported variables
|
||||
$token = $tokens[$index];
|
||||
|
||||
if ($token->equals('{')) {
|
||||
++$curlyBracesLevel;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->equals('}')) {
|
||||
--$curlyBracesLevel;
|
||||
|
||||
if (0 === $curlyBracesLevel) {
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_STRING) && 'compact' === strtolower($token->getContent()) && $this->functionAnalyzer->isGlobalFunctionCall($tokens, $index)) {
|
||||
return []; // wouldn't touch it with a ten-foot pole
|
||||
}
|
||||
|
||||
if ($token->isGivenKind($riskyKinds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($token->equals('$')) {
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$nextIndex]->isGivenKind(T_VARIABLE)) {
|
||||
return []; // "$$a" case
|
||||
}
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_VARIABLE)) {
|
||||
$content = $token->getContent();
|
||||
|
||||
if (isset($imports[$content])) {
|
||||
unset($imports[$content]);
|
||||
|
||||
if (0 === \count($imports)) {
|
||||
return $imports;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_STRING_VARNAME)) {
|
||||
$content = '$'.$token->getContent();
|
||||
|
||||
if (isset($imports[$content])) {
|
||||
unset($imports[$content]);
|
||||
|
||||
if (0 === \count($imports)) {
|
||||
return $imports;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($token->isClassy()) { // is anonymous class
|
||||
// check if used as argument in the constructor of the anonymous class
|
||||
$index = $tokens->getNextTokenOfKind($index, ['(', '{']);
|
||||
|
||||
if ($tokens[$index]->equals('(')) {
|
||||
$closeBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
|
||||
$arguments = $this->argumentsAnalyzer->getArguments($tokens, $index, $closeBraceIndex);
|
||||
|
||||
$imports = $this->countImportsUsedAsArgument($tokens, $imports, $arguments);
|
||||
|
||||
$index = $tokens->getNextTokenOfKind($closeBraceIndex, ['{']);
|
||||
}
|
||||
|
||||
// skip body
|
||||
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_FUNCTION)) {
|
||||
// check if used as argument
|
||||
$lambdaUseOpenBraceIndex = $tokens->getNextTokenOfKind($index, ['(']);
|
||||
$lambdaUseCloseBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $lambdaUseOpenBraceIndex);
|
||||
$arguments = $this->argumentsAnalyzer->getArguments($tokens, $lambdaUseOpenBraceIndex, $lambdaUseCloseBraceIndex);
|
||||
|
||||
$imports = $this->countImportsUsedAsArgument($tokens, $imports, $arguments);
|
||||
|
||||
// check if used as import
|
||||
$index = $tokens->getNextTokenOfKind($index, [[CT::T_USE_LAMBDA], '{']);
|
||||
|
||||
if ($tokens[$index]->isGivenKind(CT::T_USE_LAMBDA)) {
|
||||
$lambdaUseOpenBraceIndex = $tokens->getNextTokenOfKind($index, ['(']);
|
||||
$lambdaUseCloseBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $lambdaUseOpenBraceIndex);
|
||||
$arguments = $this->argumentsAnalyzer->getArguments($tokens, $lambdaUseOpenBraceIndex, $lambdaUseCloseBraceIndex);
|
||||
|
||||
$imports = $this->countImportsUsedAsArgument($tokens, $imports, $arguments);
|
||||
|
||||
$index = $tokens->getNextTokenOfKind($lambdaUseCloseBraceIndex, ['{']);
|
||||
}
|
||||
|
||||
// skip body
|
||||
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $imports;
|
||||
}
|
||||
|
||||
private function countImportsUsedAsArgument(Tokens $tokens, array $imports, array $arguments): array
|
||||
{
|
||||
foreach ($arguments as $start => $end) {
|
||||
$info = $this->argumentsAnalyzer->getArgumentInfo($tokens, $start, $end);
|
||||
$content = $info->getName();
|
||||
|
||||
if (isset($imports[$content])) {
|
||||
unset($imports[$content]);
|
||||
|
||||
if (0 === \count($imports)) {
|
||||
return $imports;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $imports;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false|int
|
||||
*/
|
||||
private function getLambdaUseIndex(Tokens $tokens, int $index)
|
||||
{
|
||||
if (!$tokens[$index]->isGivenKind(T_FUNCTION) || !$this->tokensAnalyzer->isLambda($index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$lambdaUseIndex = $tokens->getNextMeaningfulToken($index); // we are @ '(' or '&' after this
|
||||
|
||||
if ($tokens[$lambdaUseIndex]->isGivenKind(CT::T_RETURN_REF)) {
|
||||
$lambdaUseIndex = $tokens->getNextMeaningfulToken($lambdaUseIndex);
|
||||
}
|
||||
|
||||
$lambdaUseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $lambdaUseIndex); // we are @ ')' after this
|
||||
$lambdaUseIndex = $tokens->getNextMeaningfulToken($lambdaUseIndex);
|
||||
|
||||
if (!$tokens[$lambdaUseIndex]->isGivenKind(CT::T_USE_LAMBDA)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $lambdaUseIndex;
|
||||
}
|
||||
|
||||
private function filterArguments(Tokens $tokens, array $arguments): array
|
||||
{
|
||||
$imports = [];
|
||||
|
||||
foreach ($arguments as $start => $end) {
|
||||
$info = $this->argumentsAnalyzer->getArgumentInfo($tokens, $start, $end);
|
||||
$argument = $info->getNameIndex();
|
||||
|
||||
if ($tokens[$tokens->getPrevMeaningfulToken($argument)]->equals('&')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$argumentCandidate = $tokens[$argument];
|
||||
|
||||
if ('$this' === $argumentCandidate->getContent()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->tokensAnalyzer->isSuperGlobal($argument)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$imports[$argumentCandidate->getContent()] = $argument;
|
||||
}
|
||||
|
||||
return $imports;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, int> $imports
|
||||
*/
|
||||
private function clearImports(Tokens $tokens, array $imports): void
|
||||
{
|
||||
foreach ($imports as $removeIndex) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($removeIndex);
|
||||
$previousRemoveIndex = $tokens->getPrevMeaningfulToken($removeIndex);
|
||||
|
||||
if ($tokens[$previousRemoveIndex]->equals(',')) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($previousRemoveIndex);
|
||||
} elseif ($tokens[$previousRemoveIndex]->equals('(')) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($tokens->getNextMeaningfulToken($removeIndex)); // next is always ',' here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove `use` and all imported variables.
|
||||
*/
|
||||
private function clearImportsAndUse(Tokens $tokens, int $lambdaUseIndex, int $lambdaUseCloseBraceIndex): void
|
||||
{
|
||||
for ($i = $lambdaUseCloseBraceIndex; $i >= $lambdaUseIndex; --$i) {
|
||||
if ($tokens[$i]->isComment()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$i]->isWhitespace()) {
|
||||
$previousIndex = $tokens->getPrevNonWhitespace($i);
|
||||
|
||||
if ($tokens[$previousIndex]->isComment()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($i);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+468
@@ -0,0 +1,468 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
|
||||
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\FixerDefinition\VersionSpecification;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* Fixer for rules defined in PSR2 ¶4.4, ¶4.6.
|
||||
*
|
||||
* @author Kuanhung Chen <ericj.tw@gmail.com>
|
||||
*/
|
||||
final class MethodArgumentSpaceFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'In method arguments and method call, there MUST NOT be a space before each comma and there MUST be one space after each comma. Argument lists MAY be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list MUST be on the next line, and there MUST be only one argument per line.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\nfunction sample(\$a=10,\$b=20,\$c=30) {}\nsample(1, 2);\n",
|
||||
null
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\nfunction sample(\$a=10,\$b=20,\$c=30) {}\nsample(1, 2);\n",
|
||||
['keep_multiple_spaces_after_comma' => false]
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\nfunction sample(\$a=10,\$b=20,\$c=30) {}\nsample(1, 2);\n",
|
||||
['keep_multiple_spaces_after_comma' => true]
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\nfunction sample(\$a=10,\n \$b=20,\$c=30) {}\nsample(1,\n 2);\n",
|
||||
['on_multiline' => 'ensure_fully_multiline']
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\nfunction sample(\n \$a=10,\n \$b=20,\n \$c=30\n) {}\nsample(\n 1,\n 2\n);\n",
|
||||
['on_multiline' => 'ensure_single_line']
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\nfunction sample(\$a=10,\n \$b=20,\$c=30) {}\nsample(1, \n 2);\nsample('foo', 'foobarbaz', 'baz');\nsample('foobar', 'bar', 'baz');\n",
|
||||
[
|
||||
'on_multiline' => 'ensure_fully_multiline',
|
||||
'keep_multiple_spaces_after_comma' => true,
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\nfunction sample(\$a=10,\n \$b=20,\$c=30) {}\nsample(1, \n 2);\nsample('foo', 'foobarbaz', 'baz');\nsample('foobar', 'bar', 'baz');\n",
|
||||
[
|
||||
'on_multiline' => 'ensure_fully_multiline',
|
||||
'keep_multiple_spaces_after_comma' => false,
|
||||
]
|
||||
),
|
||||
new VersionSpecificCodeSample(
|
||||
<<<'SAMPLE'
|
||||
<?php
|
||||
sample(
|
||||
<<<EOD
|
||||
foo
|
||||
EOD
|
||||
,
|
||||
'bar'
|
||||
);
|
||||
|
||||
SAMPLE
|
||||
,
|
||||
new VersionSpecification(70300),
|
||||
['after_heredoc' => true]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound('(');
|
||||
}
|
||||
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
if (isset($configuration['ensure_fully_multiline'])) {
|
||||
$this->configuration['on_multiline'] = $this->configuration['ensure_fully_multiline']
|
||||
? 'ensure_fully_multiline'
|
||||
: 'ignore';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before ArrayIndentationFixer.
|
||||
* Must run after CombineNestedDirnameFixer, FunctionDeclarationFixer, ImplodeCallFixer, LambdaNotUsedImportFixer, NoMultilineWhitespaceAroundDoubleArrowFixer, NoUselessSprintfFixer, PowToExponentiationFixer, StrictParamFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 30;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$expectedTokens = [T_LIST, T_FUNCTION, CT::T_USE_LAMBDA, T_FN, T_CLASS];
|
||||
|
||||
for ($index = $tokens->count() - 1; $index > 0; --$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if (!$token->equals('(')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$meaningfulTokenBeforeParenthesis = $tokens[$tokens->getPrevMeaningfulToken($index)];
|
||||
|
||||
if (
|
||||
$meaningfulTokenBeforeParenthesis->isKeyword()
|
||||
&& !$meaningfulTokenBeforeParenthesis->isGivenKind($expectedTokens)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isMultiline = $this->fixFunction($tokens, $index);
|
||||
|
||||
if (
|
||||
$isMultiline
|
||||
&& 'ensure_fully_multiline' === $this->configuration['on_multiline']
|
||||
&& !$meaningfulTokenBeforeParenthesis->isGivenKind(T_LIST)
|
||||
) {
|
||||
$this->ensureFunctionFullyMultiline($tokens, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('keep_multiple_spaces_after_comma', 'Whether keep multiple spaces after comma.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder(
|
||||
'on_multiline',
|
||||
'Defines how to handle function arguments lists that contain newlines.'
|
||||
))
|
||||
->setAllowedValues(['ignore', 'ensure_single_line', 'ensure_fully_multiline'])
|
||||
->setDefault('ensure_fully_multiline')
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('after_heredoc', 'Whether the whitespace between heredoc end and comma should be removed.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(false)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix arguments spacing for given function.
|
||||
*
|
||||
* @param Tokens $tokens Tokens to handle
|
||||
* @param int $startFunctionIndex Start parenthesis position
|
||||
*
|
||||
* @return bool whether the function is multiline
|
||||
*/
|
||||
private function fixFunction(Tokens $tokens, int $startFunctionIndex): bool
|
||||
{
|
||||
$isMultiline = false;
|
||||
|
||||
$endFunctionIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startFunctionIndex);
|
||||
$firstWhitespaceIndex = $this->findWhitespaceIndexAfterParenthesis($tokens, $startFunctionIndex, $endFunctionIndex);
|
||||
$lastWhitespaceIndex = $this->findWhitespaceIndexAfterParenthesis($tokens, $endFunctionIndex, $startFunctionIndex);
|
||||
|
||||
foreach ([$firstWhitespaceIndex, $lastWhitespaceIndex] as $index) {
|
||||
if (null === $index || !Preg::match('/\R/', $tokens[$index]->getContent())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('ensure_single_line' !== $this->configuration['on_multiline']) {
|
||||
$isMultiline = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$newLinesRemoved = $this->ensureSingleLine($tokens, $index);
|
||||
|
||||
if (!$newLinesRemoved) {
|
||||
$isMultiline = true;
|
||||
}
|
||||
}
|
||||
|
||||
for ($index = $endFunctionIndex - 1; $index > $startFunctionIndex; --$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if ($token->equals(')')) {
|
||||
$index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_CLOSE)) {
|
||||
$index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->equals('}')) {
|
||||
$index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->equals(',')) {
|
||||
$this->fixSpace($tokens, $index);
|
||||
if (!$isMultiline && $this->isNewline($tokens[$index + 1])) {
|
||||
$isMultiline = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $isMultiline;
|
||||
}
|
||||
|
||||
private function findWhitespaceIndexAfterParenthesis(Tokens $tokens, int $startParenthesisIndex, int $endParenthesisIndex): ?int
|
||||
{
|
||||
$direction = $endParenthesisIndex > $startParenthesisIndex ? 1 : -1;
|
||||
$startIndex = $startParenthesisIndex + $direction;
|
||||
$endIndex = $endParenthesisIndex - $direction;
|
||||
|
||||
for ($index = $startIndex; $index !== $endIndex; $index += $direction) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if ($token->isWhitespace()) {
|
||||
return $index;
|
||||
}
|
||||
|
||||
if (!$token->isComment()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool Whether newlines were removed from the whitespace token
|
||||
*/
|
||||
private function ensureSingleLine(Tokens $tokens, int $index): bool
|
||||
{
|
||||
$previousToken = $tokens[$index - 1];
|
||||
|
||||
if ($previousToken->isComment() && !str_starts_with($previousToken->getContent(), '/*')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$content = Preg::replace('/\R\h*/', '', $tokens[$index]->getContent());
|
||||
|
||||
$tokens->ensureWhitespaceAtIndex($index, 0, $content);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function ensureFunctionFullyMultiline(Tokens $tokens, int $startFunctionIndex): void
|
||||
{
|
||||
// find out what the indentation is
|
||||
$searchIndex = $startFunctionIndex;
|
||||
do {
|
||||
$prevWhitespaceTokenIndex = $tokens->getPrevTokenOfKind(
|
||||
$searchIndex,
|
||||
[[T_WHITESPACE]]
|
||||
);
|
||||
|
||||
$searchIndex = $prevWhitespaceTokenIndex;
|
||||
} while (null !== $prevWhitespaceTokenIndex
|
||||
&& !str_contains($tokens[$prevWhitespaceTokenIndex]->getContent(), "\n")
|
||||
);
|
||||
|
||||
if (null === $prevWhitespaceTokenIndex) {
|
||||
$existingIndentation = '';
|
||||
} else {
|
||||
$existingIndentation = $tokens[$prevWhitespaceTokenIndex]->getContent();
|
||||
$lastLineIndex = strrpos($existingIndentation, "\n");
|
||||
$existingIndentation = false === $lastLineIndex
|
||||
? $existingIndentation
|
||||
: substr($existingIndentation, $lastLineIndex + 1)
|
||||
;
|
||||
}
|
||||
|
||||
$indentation = $existingIndentation.$this->whitespacesConfig->getIndent();
|
||||
$endFunctionIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startFunctionIndex);
|
||||
|
||||
$wasWhitespaceBeforeEndFunctionAddedAsNewToken = $tokens->ensureWhitespaceAtIndex(
|
||||
$tokens[$endFunctionIndex - 1]->isWhitespace() ? $endFunctionIndex - 1 : $endFunctionIndex,
|
||||
0,
|
||||
$this->whitespacesConfig->getLineEnding().$existingIndentation
|
||||
);
|
||||
|
||||
if ($wasWhitespaceBeforeEndFunctionAddedAsNewToken) {
|
||||
++$endFunctionIndex;
|
||||
}
|
||||
|
||||
for ($index = $endFunctionIndex - 1; $index > $startFunctionIndex; --$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
// skip nested method calls and arrays
|
||||
if ($token->equals(')')) {
|
||||
$index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip nested arrays
|
||||
if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_CLOSE)) {
|
||||
$index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->equals('}')) {
|
||||
$index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->equals(',') && !$tokens[$tokens->getNextMeaningfulToken($index)]->equals(')')) {
|
||||
$this->fixNewline($tokens, $index, $indentation);
|
||||
}
|
||||
}
|
||||
|
||||
$this->fixNewline($tokens, $startFunctionIndex, $indentation, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to insert newline after comma or opening parenthesis.
|
||||
*
|
||||
* @param int $index index of a comma
|
||||
* @param string $indentation the indentation that should be used
|
||||
* @param bool $override whether to override the existing character or not
|
||||
*/
|
||||
private function fixNewline(Tokens $tokens, int $index, string $indentation, bool $override = true): void
|
||||
{
|
||||
if ($tokens[$index + 1]->isComment()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($tokens[$index + 2]->isComment()) {
|
||||
$nextMeaningfulTokenIndex = $tokens->getNextMeaningfulToken($index + 2);
|
||||
if (!$this->isNewline($tokens[$nextMeaningfulTokenIndex - 1])) {
|
||||
$tokens->ensureWhitespaceAtIndex($nextMeaningfulTokenIndex, 0, $this->whitespacesConfig->getLineEnding().$indentation);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$nextMeaningfulTokenIndex = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$nextMeaningfulTokenIndex]->equals(')')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tokens->ensureWhitespaceAtIndex($index + 1, 0, $this->whitespacesConfig->getLineEnding().$indentation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to insert space after comma and remove space before comma.
|
||||
*/
|
||||
private function fixSpace(Tokens $tokens, int $index): void
|
||||
{
|
||||
// remove space before comma if exist
|
||||
if ($tokens[$index - 1]->isWhitespace()) {
|
||||
$prevIndex = $tokens->getPrevNonWhitespace($index - 1);
|
||||
|
||||
if (
|
||||
!$tokens[$prevIndex]->equals(',') && !$tokens[$prevIndex]->isComment()
|
||||
&& (true === $this->configuration['after_heredoc'] || !$tokens[$prevIndex]->isGivenKind(T_END_HEREDOC))
|
||||
) {
|
||||
$tokens->clearAt($index - 1);
|
||||
}
|
||||
}
|
||||
|
||||
$nextIndex = $index + 1;
|
||||
$nextToken = $tokens[$nextIndex];
|
||||
|
||||
// Two cases for fix space after comma (exclude multiline comments)
|
||||
// 1) multiple spaces after comma
|
||||
// 2) no space after comma
|
||||
if ($nextToken->isWhitespace()) {
|
||||
$newContent = $nextToken->getContent();
|
||||
|
||||
if ('ensure_single_line' === $this->configuration['on_multiline']) {
|
||||
$newContent = Preg::replace('/\R/', '', $newContent);
|
||||
}
|
||||
|
||||
if (
|
||||
(false === $this->configuration['keep_multiple_spaces_after_comma'] || Preg::match('/\R/', $newContent))
|
||||
&& !$this->isCommentLastLineToken($tokens, $index + 2)
|
||||
) {
|
||||
$newContent = ltrim($newContent, " \t");
|
||||
}
|
||||
|
||||
$tokens[$nextIndex] = new Token([T_WHITESPACE, '' === $newContent ? ' ' : $newContent]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->isCommentLastLineToken($tokens, $index + 1)) {
|
||||
$tokens->insertAt($index + 1, new Token([T_WHITESPACE, ' ']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if last item of current line is a comment.
|
||||
*
|
||||
* @param Tokens $tokens tokens to handle
|
||||
* @param int $index index of token
|
||||
*/
|
||||
private function isCommentLastLineToken(Tokens $tokens, int $index): bool
|
||||
{
|
||||
if (!$tokens[$index]->isComment() || !$tokens[$index + 1]->isWhitespace()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$content = $tokens[$index + 1]->getContent();
|
||||
|
||||
return $content !== ltrim($content, "\r\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if token is new line.
|
||||
*/
|
||||
private function isNewline(Token $token): bool
|
||||
{
|
||||
return $token->isWhitespace() && str_contains($token->getContent(), "\n");
|
||||
}
|
||||
}
|
||||
+424
@@ -0,0 +1,424 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
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\Tokenizer\Analyzer\Analysis\NamespaceAnalysis;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\NamespacesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
|
||||
|
||||
/**
|
||||
* @author Andreas Möller <am@localheinz.com>
|
||||
*/
|
||||
final class NativeFunctionInvocationFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const SET_ALL = '@all';
|
||||
|
||||
/**
|
||||
* Subset of SET_INTERNAL.
|
||||
*
|
||||
* Change function call to functions known to be optimized by the Zend engine.
|
||||
* For details:
|
||||
* - @see https://github.com/php/php-src/blob/php-7.2.6/Zend/zend_compile.c "zend_try_compile_special_func"
|
||||
* - @see https://github.com/php/php-src/blob/php-7.2.6/ext/opcache/Optimizer/pass1_5.c
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public const SET_COMPILER_OPTIMIZED = '@compiler_optimized';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const SET_INTERNAL = '@internal';
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
private $functionFilter;
|
||||
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$this->functionFilter = $this->getFunctionFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Add leading `\` before function invocation to speed up resolving.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
function baz($options)
|
||||
{
|
||||
if (!array_key_exists("foo", $options)) {
|
||||
throw new \InvalidArgumentException();
|
||||
}
|
||||
|
||||
return json_encode($options);
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
function baz($options)
|
||||
{
|
||||
if (!array_key_exists("foo", $options)) {
|
||||
throw new \InvalidArgumentException();
|
||||
}
|
||||
|
||||
return json_encode($options);
|
||||
}
|
||||
',
|
||||
[
|
||||
'exclude' => [
|
||||
'json_encode',
|
||||
],
|
||||
]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
namespace space1 {
|
||||
echo count([1]);
|
||||
}
|
||||
namespace {
|
||||
echo count([1]);
|
||||
}
|
||||
',
|
||||
['scope' => 'all']
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
namespace space1 {
|
||||
echo count([1]);
|
||||
}
|
||||
namespace {
|
||||
echo count([1]);
|
||||
}
|
||||
',
|
||||
['scope' => 'namespaced']
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
myGlobalFunction();
|
||||
count();
|
||||
',
|
||||
['include' => ['myGlobalFunction']]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
myGlobalFunction();
|
||||
count();
|
||||
',
|
||||
['include' => ['@all']]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
myGlobalFunction();
|
||||
count();
|
||||
',
|
||||
['include' => ['@internal']]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
$a .= str_repeat($a, 4);
|
||||
$c = get_class($d);
|
||||
',
|
||||
['include' => ['@compiler_optimized']]
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when any of the functions are overridden.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before GlobalNamespaceImportFixer.
|
||||
* Must run after BacktickToShellExecFixer, RegularCallableCallFixer, StrictParamFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
if ('all' === $this->configuration['scope']) {
|
||||
$this->fixFunctionCalls($tokens, $this->functionFilter, 0, \count($tokens) - 1, false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$namespaces = (new NamespacesAnalyzer())->getDeclarations($tokens);
|
||||
|
||||
// 'scope' is 'namespaced' here
|
||||
/** @var NamespaceAnalysis $namespace */
|
||||
foreach (array_reverse($namespaces) as $namespace) {
|
||||
$this->fixFunctionCalls($tokens, $this->functionFilter, $namespace->getScopeStartIndex(), $namespace->getScopeEndIndex(), $namespace->isGlobalNamespace());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('exclude', 'List of functions to ignore.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([static function (array $value): bool {
|
||||
foreach ($value as $functionName) {
|
||||
if (!\is_string($functionName) || '' === trim($functionName) || trim($functionName) !== $functionName) {
|
||||
throw new InvalidOptionsException(sprintf(
|
||||
'Each element must be a non-empty, trimmed string, got "%s" instead.',
|
||||
get_debug_type($functionName)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}])
|
||||
->setDefault([])
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('include', 'List of function names or sets to fix. Defined sets are `@internal` (all native functions), `@all` (all global functions) and `@compiler_optimized` (functions that are specially optimized by Zend).'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([static function (array $value): bool {
|
||||
foreach ($value as $functionName) {
|
||||
if (!\is_string($functionName) || '' === trim($functionName) || trim($functionName) !== $functionName) {
|
||||
throw new InvalidOptionsException(sprintf(
|
||||
'Each element must be a non-empty, trimmed string, got "%s" instead.',
|
||||
get_debug_type($functionName)
|
||||
));
|
||||
}
|
||||
|
||||
$sets = [
|
||||
self::SET_ALL,
|
||||
self::SET_INTERNAL,
|
||||
self::SET_COMPILER_OPTIMIZED,
|
||||
];
|
||||
|
||||
if (str_starts_with($functionName, '@') && !\in_array($functionName, $sets, true)) {
|
||||
throw new InvalidOptionsException(sprintf('Unknown set "%s", known sets are "%s".', $functionName, implode('", "', $sets)));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}])
|
||||
->setDefault([self::SET_COMPILER_OPTIMIZED])
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('scope', 'Only fix function calls that are made within a namespace or fix all.'))
|
||||
->setAllowedValues(['all', 'namespaced'])
|
||||
->setDefault('all')
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('strict', 'Whether leading `\` of function call not meant to have it should be removed.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function fixFunctionCalls(Tokens $tokens, callable $functionFilter, int $start, int $end, bool $tryToRemove): void
|
||||
{
|
||||
$functionsAnalyzer = new FunctionsAnalyzer();
|
||||
|
||||
$tokensToInsert = [];
|
||||
for ($index = $start; $index < $end; ++$index) {
|
||||
if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
if (!$functionFilter($tokens[$index]->getContent()) || $tryToRemove) {
|
||||
if (false === $this->configuration['strict']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
continue; // do not bother if previous token is already namespace separator
|
||||
}
|
||||
|
||||
$tokensToInsert[$index] = new Token([T_NS_SEPARATOR, '\\']);
|
||||
}
|
||||
|
||||
$tokens->insertSlices($tokensToInsert);
|
||||
}
|
||||
|
||||
private function getFunctionFilter(): callable
|
||||
{
|
||||
$exclude = $this->normalizeFunctionNames($this->configuration['exclude']);
|
||||
|
||||
if (\in_array(self::SET_ALL, $this->configuration['include'], true)) {
|
||||
if (\count($exclude) > 0) {
|
||||
return static function (string $functionName) use ($exclude): bool {
|
||||
return !isset($exclude[strtolower($functionName)]);
|
||||
};
|
||||
}
|
||||
|
||||
return static function (): bool {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
$include = [];
|
||||
|
||||
if (\in_array(self::SET_INTERNAL, $this->configuration['include'], true)) {
|
||||
$include = $this->getAllInternalFunctionsNormalized();
|
||||
} elseif (\in_array(self::SET_COMPILER_OPTIMIZED, $this->configuration['include'], true)) {
|
||||
$include = $this->getAllCompilerOptimizedFunctionsNormalized(); // if `@internal` is set all compiler optimized function are already loaded
|
||||
}
|
||||
|
||||
foreach ($this->configuration['include'] as $additional) {
|
||||
if (!str_starts_with($additional, '@')) {
|
||||
$include[strtolower($additional)] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (\count($exclude) > 0) {
|
||||
return static function (string $functionName) use ($include, $exclude): bool {
|
||||
return isset($include[strtolower($functionName)]) && !isset($exclude[strtolower($functionName)]);
|
||||
};
|
||||
}
|
||||
|
||||
return static function (string $functionName) use ($include): bool {
|
||||
return isset($include[strtolower($functionName)]);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, true> normalized function names of which the PHP compiler optimizes
|
||||
*/
|
||||
private function getAllCompilerOptimizedFunctionsNormalized(): array
|
||||
{
|
||||
return $this->normalizeFunctionNames([
|
||||
// @see https://github.com/php/php-src/blob/PHP-7.4/Zend/zend_compile.c "zend_try_compile_special_func"
|
||||
'array_key_exists',
|
||||
'array_slice',
|
||||
'assert',
|
||||
'boolval',
|
||||
'call_user_func',
|
||||
'call_user_func_array',
|
||||
'chr',
|
||||
'count',
|
||||
'defined',
|
||||
'doubleval',
|
||||
'floatval',
|
||||
'func_get_args',
|
||||
'func_num_args',
|
||||
'get_called_class',
|
||||
'get_class',
|
||||
'gettype',
|
||||
'in_array',
|
||||
'intval',
|
||||
'is_array',
|
||||
'is_bool',
|
||||
'is_double',
|
||||
'is_float',
|
||||
'is_int',
|
||||
'is_integer',
|
||||
'is_long',
|
||||
'is_null',
|
||||
'is_object',
|
||||
'is_real',
|
||||
'is_resource',
|
||||
'is_scalar',
|
||||
'is_string',
|
||||
'ord',
|
||||
'sizeof',
|
||||
'strlen',
|
||||
'strval',
|
||||
// @see https://github.com/php/php-src/blob/php-7.2.6/ext/opcache/Optimizer/pass1_5.c
|
||||
// @see https://github.com/php/php-src/blob/PHP-8.1.2/Zend/Optimizer/block_pass.c
|
||||
// @see https://github.com/php/php-src/blob/php-8.1.3/Zend/Optimizer/zend_optimizer.c
|
||||
'constant',
|
||||
'define',
|
||||
'dirname',
|
||||
'extension_loaded',
|
||||
'function_exists',
|
||||
'is_callable',
|
||||
'ini_get',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, true> normalized function names of all internal defined functions
|
||||
*/
|
||||
private function getAllInternalFunctionsNormalized(): array
|
||||
{
|
||||
return $this->normalizeFunctionNames(get_defined_functions()['internal']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $functionNames
|
||||
*
|
||||
* @return array<string, true> all function names lower cased
|
||||
*/
|
||||
private function normalizeFunctionNames(array $functionNames): array
|
||||
{
|
||||
foreach ($functionNames as $index => $functionName) {
|
||||
$functionNames[strtolower($functionName)] = true;
|
||||
unset($functionNames[$index]);
|
||||
}
|
||||
|
||||
return $functionNames;
|
||||
}
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* Fixer for rules defined in PSR2 ¶4.6.
|
||||
*
|
||||
* @author Varga Bence <vbence@czentral.org>
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class NoSpacesAfterFunctionNameFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'When making a method or function call, there MUST NOT be a space between the method or function name and the opening parenthesis.',
|
||||
[new CodeSample("<?php\nrequire ('sample.php');\necho (test (3));\nexit (1);\n\$func ();\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before FunctionToConstantFixer, GetClassToClassKeywordFixer.
|
||||
* Must run after PowToExponentiationFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound(array_merge($this->getFunctionyTokenKinds(), [T_STRING]));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$functionyTokens = $this->getFunctionyTokenKinds();
|
||||
$languageConstructionTokens = $this->getLanguageConstructionTokenKinds();
|
||||
$braceTypes = $this->getBraceAfterVariableKinds();
|
||||
|
||||
foreach ($tokens as $index => $token) {
|
||||
// looking for start brace
|
||||
if (!$token->equals('(')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// last non-whitespace token, can never be `null` always at least PHP open tag before it
|
||||
$lastTokenIndex = $tokens->getPrevNonWhitespace($index);
|
||||
|
||||
// check for ternary operator
|
||||
$endParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
|
||||
$nextNonWhiteSpace = $tokens->getNextMeaningfulToken($endParenthesisIndex);
|
||||
if (
|
||||
null !== $nextNonWhiteSpace
|
||||
&& $tokens[$nextNonWhiteSpace]->equals('?')
|
||||
&& $tokens[$lastTokenIndex]->isGivenKind($languageConstructionTokens)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if it is a function call
|
||||
if ($tokens[$lastTokenIndex]->isGivenKind($functionyTokens)) {
|
||||
$this->fixFunctionCall($tokens, $index);
|
||||
} elseif ($tokens[$lastTokenIndex]->isGivenKind(T_STRING)) { // for real function calls or definitions
|
||||
$possibleDefinitionIndex = $tokens->getPrevMeaningfulToken($lastTokenIndex);
|
||||
if (!$tokens[$possibleDefinitionIndex]->isGivenKind(T_FUNCTION)) {
|
||||
$this->fixFunctionCall($tokens, $index);
|
||||
}
|
||||
} elseif ($tokens[$lastTokenIndex]->equalsAny($braceTypes)) {
|
||||
$block = Tokens::detectBlockType($tokens[$lastTokenIndex]);
|
||||
if (
|
||||
Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE === $block['type']
|
||||
|| Tokens::BLOCK_TYPE_DYNAMIC_VAR_BRACE === $block['type']
|
||||
|| Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE === $block['type']
|
||||
|| Tokens::BLOCK_TYPE_PARENTHESIS_BRACE === $block['type']
|
||||
) {
|
||||
$this->fixFunctionCall($tokens, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes whitespaces around braces of a function(y) call.
|
||||
*
|
||||
* @param Tokens $tokens tokens to handle
|
||||
* @param int $index index of token
|
||||
*/
|
||||
private function fixFunctionCall(Tokens $tokens, int $index): void
|
||||
{
|
||||
// remove space before opening brace
|
||||
if ($tokens[$index - 1]->isWhitespace()) {
|
||||
$tokens->clearAt($index - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<list<int>|string>
|
||||
*/
|
||||
private function getBraceAfterVariableKinds(): array
|
||||
{
|
||||
static $tokens = [
|
||||
')',
|
||||
']',
|
||||
[CT::T_DYNAMIC_VAR_BRACE_CLOSE],
|
||||
[CT::T_ARRAY_INDEX_CURLY_BRACE_CLOSE],
|
||||
];
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token kinds which can work as function calls.
|
||||
*
|
||||
* @return int[] Token names
|
||||
*/
|
||||
private function getFunctionyTokenKinds(): array
|
||||
{
|
||||
static $tokens = [
|
||||
T_ARRAY,
|
||||
T_ECHO,
|
||||
T_EMPTY,
|
||||
T_EVAL,
|
||||
T_EXIT,
|
||||
T_INCLUDE,
|
||||
T_INCLUDE_ONCE,
|
||||
T_ISSET,
|
||||
T_LIST,
|
||||
T_PRINT,
|
||||
T_REQUIRE,
|
||||
T_REQUIRE_ONCE,
|
||||
T_UNSET,
|
||||
T_VARIABLE,
|
||||
];
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token kinds of actually language construction.
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
private function getLanguageConstructionTokenKinds(): array
|
||||
{
|
||||
static $languageConstructionTokens = [
|
||||
T_ECHO,
|
||||
T_PRINT,
|
||||
T_INCLUDE,
|
||||
T_INCLUDE_ONCE,
|
||||
T_REQUIRE,
|
||||
T_REQUIRE_ONCE,
|
||||
];
|
||||
|
||||
return $languageConstructionTokens;
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractProxyFixer;
|
||||
use PhpCsFixer\Fixer\Basic\NoTrailingCommaInSinglelineFixer;
|
||||
use PhpCsFixer\Fixer\DeprecatedFixerInterface;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
final class NoTrailingCommaInSinglelineFunctionCallFixer extends AbstractProxyFixer implements DeprecatedFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'When making a method or function call on a single line there MUST NOT be a trailing comma after the last argument.',
|
||||
[new CodeSample("<?php\nfoo(\$a,);\n")]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoSpacesInsideParenthesisFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSuccessorsNames(): array
|
||||
{
|
||||
return array_keys($this->proxyFixers);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createProxyFixers(): array
|
||||
{
|
||||
$fixer = new NoTrailingCommaInSinglelineFixer();
|
||||
$fixer->configure(['elements' => ['arguments', 'array_destructuring']]);
|
||||
|
||||
return [$fixer];
|
||||
}
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Mark Scherer
|
||||
* @author Lucas Manzke <lmanzke@outlook.com>
|
||||
* @author Gregor Harlan <gharlan@web.de>
|
||||
*/
|
||||
final class NoUnreachableDefaultArgumentValueFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'In function arguments there must not be arguments with default values before non-default ones.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
function example($foo = "two words", $bar) {}
|
||||
'
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Modifies the signature of functions; therefore risky when using systems (such as some Symfony components) that rely on those (for example through reflection).'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after NullableTypeDeclarationForDefaultNullValueFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$functionKinds = [T_FUNCTION, T_FN];
|
||||
|
||||
for ($i = 0, $l = $tokens->count(); $i < $l; ++$i) {
|
||||
if (!$tokens[$i]->isGivenKind($functionKinds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$startIndex = $tokens->getNextTokenOfKind($i, ['(']);
|
||||
$i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex);
|
||||
|
||||
$this->fixFunctionDefinition($tokens, $startIndex, $i);
|
||||
}
|
||||
}
|
||||
|
||||
private function fixFunctionDefinition(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
$lastArgumentIndex = $this->getLastNonDefaultArgumentIndex($tokens, $startIndex, $endIndex);
|
||||
|
||||
if (null === $lastArgumentIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
for ($i = $lastArgumentIndex; $i > $startIndex; --$i) {
|
||||
$token = $tokens[$i];
|
||||
|
||||
if ($token->isGivenKind(T_VARIABLE)) {
|
||||
$lastArgumentIndex = $i;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$token->equals('=') || $this->isNonNullableTypehintedNullableVariable($tokens, $i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->removeDefaultValue($tokens, $i, $this->getDefaultValueEndIndex($tokens, $lastArgumentIndex));
|
||||
}
|
||||
}
|
||||
|
||||
private function getLastNonDefaultArgumentIndex(Tokens $tokens, int $startIndex, int $endIndex): ?int
|
||||
{
|
||||
for ($i = $endIndex - 1; $i > $startIndex; --$i) {
|
||||
$token = $tokens[$i];
|
||||
|
||||
if ($token->equals('=')) {
|
||||
$i = $tokens->getPrevMeaningfulToken($i);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->isGivenKind(T_VARIABLE) && !$this->isEllipsis($tokens, $i)) {
|
||||
return $i;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function isEllipsis(Tokens $tokens, int $variableIndex): bool
|
||||
{
|
||||
return $tokens[$tokens->getPrevMeaningfulToken($variableIndex)]->isGivenKind(T_ELLIPSIS);
|
||||
}
|
||||
|
||||
private function getDefaultValueEndIndex(Tokens $tokens, int $index): int
|
||||
{
|
||||
do {
|
||||
$index = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) {
|
||||
$index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ATTRIBUTE, $index);
|
||||
}
|
||||
} while (!$tokens[$index]->equals(','));
|
||||
|
||||
return $tokens->getPrevMeaningfulToken($index);
|
||||
}
|
||||
|
||||
private function removeDefaultValue(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
for ($i = $startIndex; $i <= $endIndex;) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($i);
|
||||
$this->clearWhitespacesBeforeIndex($tokens, $i);
|
||||
$i = $tokens->getNextMeaningfulToken($i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $index Index of "="
|
||||
*/
|
||||
private function isNonNullableTypehintedNullableVariable(Tokens $tokens, int $index): bool
|
||||
{
|
||||
$nextToken = $tokens[$tokens->getNextMeaningfulToken($index)];
|
||||
|
||||
if (!$nextToken->equals([T_STRING, 'null'], false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$variableIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
$searchTokens = [',', '(', [T_STRING], [CT::T_ARRAY_TYPEHINT], [T_CALLABLE]];
|
||||
$typehintKinds = [T_STRING, CT::T_ARRAY_TYPEHINT, T_CALLABLE];
|
||||
|
||||
$prevIndex = $tokens->getPrevTokenOfKind($variableIndex, $searchTokens);
|
||||
|
||||
if (!$tokens[$prevIndex]->isGivenKind($typehintKinds)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !$tokens[$tokens->getPrevMeaningfulToken($prevIndex)]->isGivenKind(CT::T_NULLABLE_TYPE);
|
||||
}
|
||||
|
||||
private function clearWhitespacesBeforeIndex(Tokens $tokens, int $index): void
|
||||
{
|
||||
$prevIndex = $tokens->getNonEmptySibling($index, -1);
|
||||
if (!$tokens[$prevIndex]->isWhitespace()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$prevNonWhiteIndex = $tokens->getPrevNonWhitespace($prevIndex);
|
||||
if (null === $prevNonWhiteIndex || !$tokens[$prevNonWhiteIndex]->isComment()) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+121
@@ -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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class NoUselessSprintfFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'There must be no `sprintf` calls with only the first argument.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\n\$foo = sprintf('bar');\n"
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when if the `sprintf` function is overridden.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before MethodArgumentSpaceFixer, NativeFunctionCasingFixer, NoEmptyStatementFixer, NoExtraBlankLinesFixer, NoSpacesInsideParenthesisFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 42;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$functionAnalyzer = new FunctionsAnalyzer();
|
||||
$argumentsAnalyzer = new ArgumentsAnalyzer();
|
||||
|
||||
for ($index = \count($tokens) - 1; $index > 0; --$index) {
|
||||
if (!$tokens[$index]->isGivenKind(T_STRING)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('sprintf' !== strtolower($tokens[$index]->getContent())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$functionAnalyzer->isGlobalFunctionCall($tokens, $index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$openParenthesisIndex = $tokens->getNextTokenOfKind($index, ['(']);
|
||||
|
||||
if ($tokens[$tokens->getNextMeaningfulToken($openParenthesisIndex)]->isGivenKind(T_ELLIPSIS)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$closeParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesisIndex);
|
||||
|
||||
if (1 !== $argumentsAnalyzer->countArguments($tokens, $openParenthesisIndex, $closeParenthesisIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($closeParenthesisIndex);
|
||||
|
||||
$prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($closeParenthesisIndex);
|
||||
|
||||
if ($tokens[$prevMeaningfulTokenIndex]->equals(',')) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($prevMeaningfulTokenIndex);
|
||||
}
|
||||
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($openParenthesisIndex);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($index);
|
||||
|
||||
$prevMeaningfulTokenIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$prevMeaningfulTokenIndex]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($prevMeaningfulTokenIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
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\Tokenizer\Analyzer\Analysis\ArgumentAnalysis;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author HypeMC
|
||||
*/
|
||||
final class NullableTypeDeclarationForDefaultNullValueFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Adds or removes `?` before type declarations for parameters with a default `null` value.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\nfunction sample(string \$str = null)\n{}\n"
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\nfunction sample(?string \$str = null)\n{}\n",
|
||||
['use_nullable_type_declaration' => false]
|
||||
),
|
||||
],
|
||||
'Rule is applied only in a PHP 7.1+ environment.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_VARIABLE) && $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoUnreachableDefaultArgumentValueFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('use_nullable_type_declaration', 'Whether to add or remove `?` before type declarations for parameters with a default `null` value.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$functionsAnalyzer = new FunctionsAnalyzer();
|
||||
$tokenKinds = [T_FUNCTION, T_FN];
|
||||
|
||||
for ($index = $tokens->count() - 1; $index >= 0; --$index) {
|
||||
$token = $tokens[$index];
|
||||
|
||||
if (!$token->isGivenKind($tokenKinds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$arguments = $functionsAnalyzer->getFunctionArguments($tokens, $index);
|
||||
$this->fixFunctionParameters($tokens, $arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ArgumentAnalysis[] $arguments
|
||||
*/
|
||||
private function fixFunctionParameters(Tokens $tokens, array $arguments): void
|
||||
{
|
||||
$constructorPropertyModifiers = [
|
||||
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC,
|
||||
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED,
|
||||
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE,
|
||||
];
|
||||
|
||||
if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required
|
||||
$constructorPropertyModifiers[] = T_READONLY;
|
||||
}
|
||||
|
||||
foreach (array_reverse($arguments) as $argumentInfo) {
|
||||
if (
|
||||
// Skip, if the parameter
|
||||
// - doesn't have a type declaration
|
||||
!$argumentInfo->hasTypeAnalysis()
|
||||
// type is a union
|
||||
|| str_contains($argumentInfo->getTypeAnalysis()->getName(), '|')
|
||||
// - a default value is not null we can continue
|
||||
|| !$argumentInfo->hasDefault() || 'null' !== strtolower($argumentInfo->getDefault())
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$argumentTypeInfo = $argumentInfo->getTypeAnalysis();
|
||||
|
||||
if (\PHP_VERSION_ID >= 80000 && false === $this->configuration['use_nullable_type_declaration']) {
|
||||
$visibility = $tokens[$tokens->getPrevMeaningfulToken($argumentTypeInfo->getStartIndex())];
|
||||
|
||||
if ($visibility->isGivenKind($constructorPropertyModifiers)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (true === $this->configuration['use_nullable_type_declaration']) {
|
||||
if (!$argumentTypeInfo->isNullable() && 'mixed' !== $argumentTypeInfo->getName()) {
|
||||
$tokens->insertAt($argumentTypeInfo->getStartIndex(), new Token([CT::T_NULLABLE_TYPE, '?']));
|
||||
}
|
||||
} else {
|
||||
if ($argumentTypeInfo->isNullable()) {
|
||||
$tokens->removeTrailingWhitespace($argumentTypeInfo->getStartIndex());
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($argumentTypeInfo->getStartIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+196
@@ -0,0 +1,196 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractPhpdocToTypeDeclarationFixer;
|
||||
use PhpCsFixer\DocBlock\Annotation;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Jan Gantzert <jan@familie-gantzert.de>
|
||||
*/
|
||||
final class PhpdocToParamTypeFixer extends AbstractPhpdocToTypeDeclarationFixer
|
||||
{
|
||||
/**
|
||||
* @var array{int, string}[]
|
||||
*/
|
||||
private const EXCLUDE_FUNC_NAMES = [
|
||||
[T_STRING, '__clone'],
|
||||
[T_STRING, '__destruct'],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, true>
|
||||
*/
|
||||
private const SKIPPED_TYPES = [
|
||||
'mixed' => true,
|
||||
'resource' => true,
|
||||
'static' => true,
|
||||
'void' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'EXPERIMENTAL: Takes `@param` annotations of non-mixed types and adjusts accordingly the function signature. Requires PHP >= 7.0.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
/**
|
||||
* @param string $foo
|
||||
* @param string|null $bar
|
||||
*/
|
||||
function f($foo, $bar)
|
||||
{}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
/** @param Foo $foo */
|
||||
function foo($foo) {}
|
||||
/** @param string $foo */
|
||||
function bar($foo) {}
|
||||
',
|
||||
['scalar_types' => false]
|
||||
),
|
||||
],
|
||||
null,
|
||||
'This rule is EXPERIMENTAL and [1] is not covered with backward compatibility promise. [2] `@param` annotation is mandatory for the fixer to make changes, signatures of methods without it (no docblock, inheritdocs) will not be fixed. [3] Manual actions are required if inherited signatures are not properly documented.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_FUNCTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
protected function isSkippedType(string $type): bool
|
||||
{
|
||||
return isset(self::SKIPPED_TYPES[$type]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = $tokens->count() - 1; 0 < $index; --$index) {
|
||||
if (!$tokens[$index]->isGivenKind(T_FUNCTION)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$funcName = $tokens->getNextMeaningfulToken($index);
|
||||
if ($tokens[$funcName]->equalsAny(self::EXCLUDE_FUNC_NAMES, false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$docCommentIndex = $this->findFunctionDocComment($tokens, $index);
|
||||
|
||||
if (null === $docCommentIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->getAnnotationsFromDocComment('param', $tokens, $docCommentIndex) as $paramTypeAnnotation) {
|
||||
$typeInfo = $this->getCommonTypeFromAnnotation($paramTypeAnnotation, false);
|
||||
|
||||
if (null === $typeInfo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$paramType, $isNullable] = $typeInfo;
|
||||
|
||||
$startIndex = $tokens->getNextTokenOfKind($index, ['(']);
|
||||
$variableIndex = $this->findCorrectVariable($tokens, $startIndex, $paramTypeAnnotation);
|
||||
|
||||
if (null === $variableIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$byRefIndex = $tokens->getPrevMeaningfulToken($variableIndex);
|
||||
|
||||
if ($tokens[$byRefIndex]->equals('&')) {
|
||||
$variableIndex = $byRefIndex;
|
||||
}
|
||||
|
||||
if ($this->hasParamTypeHint($tokens, $variableIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->isValidSyntax(sprintf('<?php function f(%s $x) {}', $paramType))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens->insertAt($variableIndex, array_merge(
|
||||
$this->createTypeDeclarationTokens($paramType, $isNullable),
|
||||
[new Token([T_WHITESPACE, ' '])]
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function findCorrectVariable(Tokens $tokens, int $startIndex, Annotation $paramTypeAnnotation): ?int
|
||||
{
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startIndex);
|
||||
|
||||
for ($index = $startIndex + 1; $index < $endIndex; ++$index) {
|
||||
if (!$tokens[$index]->isGivenKind(T_VARIABLE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$variableName = $tokens[$index]->getContent();
|
||||
|
||||
if ($paramTypeAnnotation->getVariableName() === $variableName) {
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the function already has a param type hint.
|
||||
*
|
||||
* @param int $index The index of the end of the function definition line, EG at { or ;
|
||||
*/
|
||||
private function hasParamTypeHint(Tokens $tokens, int $index): bool
|
||||
{
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
return !$tokens[$prevIndex]->equalsAny([',', '(']);
|
||||
}
|
||||
}
|
||||
Vendored
+244
@@ -0,0 +1,244 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractPhpdocToTypeDeclarationFixer;
|
||||
use PhpCsFixer\DocBlock\Annotation;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecification;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
final class PhpdocToPropertyTypeFixer extends AbstractPhpdocToTypeDeclarationFixer
|
||||
{
|
||||
/**
|
||||
* @var array<string, true>
|
||||
*/
|
||||
private array $skippedTypes = [
|
||||
'mixed' => true,
|
||||
'resource' => true,
|
||||
'null' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'EXPERIMENTAL: Takes `@var` annotation of non-mixed types and adjusts accordingly the property signature. Requires PHP >= 7.4.',
|
||||
[
|
||||
new VersionSpecificCodeSample(
|
||||
'<?php
|
||||
class Foo {
|
||||
/** @var int */
|
||||
private $foo;
|
||||
/** @var \Traversable */
|
||||
private $bar;
|
||||
}
|
||||
',
|
||||
new VersionSpecification(70400)
|
||||
),
|
||||
new VersionSpecificCodeSample(
|
||||
'<?php
|
||||
class Foo {
|
||||
/** @var int */
|
||||
private $foo;
|
||||
/** @var \Traversable */
|
||||
private $bar;
|
||||
}
|
||||
',
|
||||
new VersionSpecification(70400),
|
||||
['scalar_types' => false]
|
||||
),
|
||||
],
|
||||
null,
|
||||
'This rule is EXPERIMENTAL and [1] is not covered with backward compatibility promise. [2] `@var` annotation is mandatory for the fixer to make changes, signatures of properties without it (no docblock) will not be fixed. [3] Manual actions might be required for newly typed properties that are read before initialization.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_DOC_COMMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 7;
|
||||
}
|
||||
|
||||
protected function isSkippedType(string $type): bool
|
||||
{
|
||||
return isset($this->skippedTypes[$type]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = $tokens->count() - 1; 0 < $index; --$index) {
|
||||
if ($tokens[$index]->isGivenKind([T_CLASS, T_TRAIT])) {
|
||||
$this->fixClass($tokens, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixClass(Tokens $tokens, int $index): void
|
||||
{
|
||||
$index = $tokens->getNextTokenOfKind($index, ['{']);
|
||||
$classEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
|
||||
|
||||
for (; $index < $classEndIndex; ++$index) {
|
||||
if ($tokens[$index]->isGivenKind(T_FUNCTION)) {
|
||||
$index = $tokens->getNextTokenOfKind($index, ['{', ';']);
|
||||
|
||||
if ($tokens[$index]->equals('{')) {
|
||||
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$index]->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$docCommentIndex = $index;
|
||||
$propertyIndices = $this->findNextUntypedPropertiesDeclaration($tokens, $docCommentIndex);
|
||||
|
||||
if ([] === $propertyIndices) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$typeInfo = $this->resolveApplicableType(
|
||||
$propertyIndices,
|
||||
$this->getAnnotationsFromDocComment('var', $tokens, $docCommentIndex)
|
||||
);
|
||||
|
||||
if (null === $typeInfo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$propertyType, $isNullable] = $typeInfo;
|
||||
|
||||
if (\in_array($propertyType, ['callable', 'never', 'void'], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newTokens = array_merge(
|
||||
$this->createTypeDeclarationTokens($propertyType, $isNullable),
|
||||
[new Token([T_WHITESPACE, ' '])]
|
||||
);
|
||||
|
||||
$tokens->insertAt(current($propertyIndices), $newTokens);
|
||||
|
||||
$index = max($propertyIndices) + \count($newTokens) + 1;
|
||||
$classEndIndex += \count($newTokens);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, int>
|
||||
*/
|
||||
private function findNextUntypedPropertiesDeclaration(Tokens $tokens, int $index): array
|
||||
{
|
||||
do {
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
} while ($tokens[$index]->isGivenKind([
|
||||
T_PRIVATE,
|
||||
T_PROTECTED,
|
||||
T_PUBLIC,
|
||||
T_STATIC,
|
||||
T_VAR,
|
||||
]));
|
||||
|
||||
if (!$tokens[$index]->isGivenKind(T_VARIABLE)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$properties = [];
|
||||
|
||||
while (!$tokens[$index]->equals(';')) {
|
||||
if ($tokens[$index]->isGivenKind(T_VARIABLE)) {
|
||||
$properties[$tokens[$index]->getContent()] = $index;
|
||||
}
|
||||
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, int> $propertyIndices
|
||||
* @param Annotation[] $annotations
|
||||
*/
|
||||
private function resolveApplicableType(array $propertyIndices, array $annotations): ?array
|
||||
{
|
||||
$propertyTypes = [];
|
||||
|
||||
foreach ($annotations as $annotation) {
|
||||
$propertyName = $annotation->getVariableName();
|
||||
|
||||
if (null === $propertyName) {
|
||||
if (1 !== \count($propertyIndices)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$propertyName = key($propertyIndices);
|
||||
}
|
||||
|
||||
if (!isset($propertyIndices[$propertyName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$typeInfo = $this->getCommonTypeFromAnnotation($annotation, false);
|
||||
|
||||
if (!isset($propertyTypes[$propertyName])) {
|
||||
$propertyTypes[$propertyName] = [];
|
||||
} elseif ($typeInfo !== $propertyTypes[$propertyName]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$propertyTypes[$propertyName] = $typeInfo;
|
||||
}
|
||||
|
||||
if (\count($propertyTypes) !== \count($propertyIndices)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = array_shift($propertyTypes);
|
||||
|
||||
foreach ($propertyTypes as $propertyType) {
|
||||
if ($propertyType !== $type) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
Vendored
+207
@@ -0,0 +1,207 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractPhpdocToTypeDeclarationFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecification;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Filippo Tessarotto <zoeslam@gmail.com>
|
||||
*/
|
||||
final class PhpdocToReturnTypeFixer extends AbstractPhpdocToTypeDeclarationFixer
|
||||
{
|
||||
/**
|
||||
* @var array<int, array<int, int|string>>
|
||||
*/
|
||||
private array $excludeFuncNames = [
|
||||
[T_STRING, '__construct'],
|
||||
[T_STRING, '__destruct'],
|
||||
[T_STRING, '__clone'],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, true>
|
||||
*/
|
||||
private array $skippedTypes = [
|
||||
'mixed' => true,
|
||||
'resource' => true,
|
||||
'null' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'EXPERIMENTAL: Takes `@return` annotation of non-mixed types and adjusts accordingly the function signature. Requires PHP >= 7.0.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
/** @return \My\Bar */
|
||||
function f1()
|
||||
{}
|
||||
|
||||
/** @return void */
|
||||
function f2()
|
||||
{}
|
||||
'
|
||||
),
|
||||
new VersionSpecificCodeSample(
|
||||
'<?php
|
||||
|
||||
/** @return object */
|
||||
function my_foo()
|
||||
{}
|
||||
',
|
||||
new VersionSpecification(70200)
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
|
||||
/** @return Foo */
|
||||
function foo() {}
|
||||
/** @return string */
|
||||
function bar() {}
|
||||
',
|
||||
['scalar_types' => false]
|
||||
),
|
||||
new VersionSpecificCodeSample(
|
||||
'<?php
|
||||
final class Foo {
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function create($prototype) {
|
||||
return new static($prototype);
|
||||
}
|
||||
}
|
||||
',
|
||||
new VersionSpecification(80000)
|
||||
),
|
||||
],
|
||||
null,
|
||||
'This rule is EXPERIMENTAL and [1] is not covered with backward compatibility promise. [2] `@return` annotation is mandatory for the fixer to make changes, signatures of methods without it (no docblock, inheritdocs) will not be fixed. [3] Manual actions are required if inherited signatures are not properly documented.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before FullyQualifiedStrictTypesFixer, NoSuperfluousPhpdocTagsFixer, PhpdocAlignFixer, ReturnTypeDeclarationFixer.
|
||||
* Must run after AlignMultilineCommentFixer, CommentToPhpdocFixer, PhpdocIndentFixer, PhpdocScalarFixer, PhpdocToCommentFixer, PhpdocTypesFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 13;
|
||||
}
|
||||
|
||||
protected function isSkippedType(string $type): bool
|
||||
{
|
||||
return isset($this->skippedTypes[$type]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
unset($this->skippedTypes['mixed']);
|
||||
}
|
||||
|
||||
for ($index = $tokens->count() - 1; 0 < $index; --$index) {
|
||||
if (!$tokens[$index]->isGivenKind([T_FUNCTION, T_FN])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$funcName = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$funcName]->equalsAny($this->excludeFuncNames, false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$docCommentIndex = $this->findFunctionDocComment($tokens, $index);
|
||||
if (null === $docCommentIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$returnTypeAnnotation = $this->getAnnotationsFromDocComment('return', $tokens, $docCommentIndex);
|
||||
if (1 !== \count($returnTypeAnnotation)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$typeInfo = $this->getCommonTypeFromAnnotation(current($returnTypeAnnotation), true);
|
||||
|
||||
if (null === $typeInfo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$returnType, $isNullable] = $typeInfo;
|
||||
|
||||
$startIndex = $tokens->getNextTokenOfKind($index, ['{', ';']);
|
||||
|
||||
if ($this->hasReturnTypeHint($tokens, $startIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->isValidSyntax(sprintf('<?php function f():%s {}', $returnType))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$endFuncIndex = $tokens->getPrevTokenOfKind($startIndex, [')']);
|
||||
|
||||
$tokens->insertAt(
|
||||
$endFuncIndex + 1,
|
||||
array_merge(
|
||||
[
|
||||
new Token([CT::T_TYPE_COLON, ':']),
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
],
|
||||
$this->createTypeDeclarationTokens($returnType, $isNullable)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the function already has a return type hint.
|
||||
*
|
||||
* @param int $index The index of the end of the function definition line, EG at { or ;
|
||||
*/
|
||||
private function hasReturnTypeHint(Tokens $tokens, int $index): bool
|
||||
{
|
||||
$endFuncIndex = $tokens->getPrevTokenOfKind($index, [')']);
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($endFuncIndex);
|
||||
|
||||
return $tokens[$nextIndex]->isGivenKind(CT::T_TYPE_COLON);
|
||||
}
|
||||
}
|
||||
Vendored
+265
@@ -0,0 +1,265 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class RegularCallableCallFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Callables must be called without using `call_user_func*` when possible.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
call_user_func("var_dump", 1, 2);
|
||||
|
||||
call_user_func("Bar\Baz::d", 1, 2);
|
||||
|
||||
call_user_func_array($callback, [1, 2]);
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
call_user_func(function ($a, $b) { var_dump($a, $b); }, 1, 2);
|
||||
|
||||
call_user_func(static function ($a, $b) { var_dump($a, $b); }, 1, 2);
|
||||
'
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when the `call_user_func` or `call_user_func_array` function is overridden or when are used in constructions that should be avoided, like `call_user_func_array(\'foo\', [\'bar\' => \'baz\'])` or `call_user_func($foo, $foo = \'bar\')`.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NativeFunctionInvocationFixer.
|
||||
* Must run after NoBinaryStringFixer, NoUselessConcatOperatorFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_STRING);
|
||||
}
|
||||
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$functionsAnalyzer = new FunctionsAnalyzer();
|
||||
$argumentsAnalyzer = new ArgumentsAnalyzer();
|
||||
|
||||
for ($index = $tokens->count() - 1; $index > 0; --$index) {
|
||||
if (!$tokens[$index]->equalsAny([[T_STRING, 'call_user_func'], [T_STRING, 'call_user_func_array']], false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) {
|
||||
continue; // redeclare/override
|
||||
}
|
||||
|
||||
$openParenthesis = $tokens->getNextMeaningfulToken($index);
|
||||
$closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis);
|
||||
$arguments = $argumentsAnalyzer->getArguments($tokens, $openParenthesis, $closeParenthesis);
|
||||
|
||||
if (1 > \count($arguments)) {
|
||||
return; // no arguments!
|
||||
}
|
||||
|
||||
$this->processCall($tokens, $index, $arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, int> $arguments
|
||||
*/
|
||||
private function processCall(Tokens $tokens, int $index, array $arguments): void
|
||||
{
|
||||
$firstArgIndex = $tokens->getNextMeaningfulToken(
|
||||
$tokens->getNextMeaningfulToken($index)
|
||||
);
|
||||
|
||||
/** @var Token $firstArgToken */
|
||||
$firstArgToken = $tokens[$firstArgIndex];
|
||||
|
||||
if ($firstArgToken->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) {
|
||||
$afterFirstArgIndex = $tokens->getNextMeaningfulToken($firstArgIndex);
|
||||
|
||||
if (!$tokens[$afterFirstArgIndex]->equalsAny([',', ')'])) {
|
||||
return; // first argument is an expression like `call_user_func("foo"."bar", ...)`, not supported!
|
||||
}
|
||||
|
||||
$firstArgTokenContent = $firstArgToken->getContent();
|
||||
|
||||
if (!$this->isValidFunctionInvoke($firstArgTokenContent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$newCallTokens = Tokens::fromCode('<?php '.substr(str_replace('\\\\', '\\', $firstArgToken->getContent()), 1, -1).'();');
|
||||
$newCallTokensSize = $newCallTokens->count();
|
||||
$newCallTokens->clearAt(0);
|
||||
$newCallTokens->clearRange($newCallTokensSize - 3, $newCallTokensSize - 1);
|
||||
$newCallTokens->clearEmptyTokens();
|
||||
|
||||
$this->replaceCallUserFuncWithCallback($tokens, $index, $newCallTokens, $firstArgIndex, $firstArgIndex);
|
||||
} elseif (
|
||||
$firstArgToken->isGivenKind(T_FUNCTION)
|
||||
|| (
|
||||
$firstArgToken->isGivenKind(T_STATIC)
|
||||
&& $tokens[$tokens->getNextMeaningfulToken($firstArgIndex)]->isGivenKind(T_FUNCTION)
|
||||
)
|
||||
) {
|
||||
$firstArgEndIndex = $tokens->findBlockEnd(
|
||||
Tokens::BLOCK_TYPE_CURLY_BRACE,
|
||||
$tokens->getNextTokenOfKind($firstArgIndex, ['{'])
|
||||
);
|
||||
|
||||
$newCallTokens = $this->getTokensSubcollection($tokens, $firstArgIndex, $firstArgEndIndex);
|
||||
$newCallTokens->insertAt($newCallTokens->count(), new Token(')'));
|
||||
$newCallTokens->insertAt(0, new Token('('));
|
||||
$this->replaceCallUserFuncWithCallback($tokens, $index, $newCallTokens, $firstArgIndex, $firstArgEndIndex);
|
||||
} elseif ($firstArgToken->isGivenKind(T_VARIABLE)) {
|
||||
$firstArgEndIndex = reset($arguments);
|
||||
|
||||
// check if the same variable is used multiple times and if so do not fix
|
||||
|
||||
foreach ($arguments as $argumentStart => $argumentEnd) {
|
||||
if ($firstArgEndIndex === $argumentEnd) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ($i = $argumentStart; $i <= $argumentEnd; ++$i) {
|
||||
if ($tokens[$i]->equals($firstArgToken)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if complex statement and if so wrap the call in () if on PHP 7 or up, else do not fix
|
||||
|
||||
$newCallTokens = $this->getTokensSubcollection($tokens, $firstArgIndex, $firstArgEndIndex);
|
||||
$complex = false;
|
||||
|
||||
for ($newCallIndex = \count($newCallTokens) - 1; $newCallIndex >= 0; --$newCallIndex) {
|
||||
if ($newCallTokens[$newCallIndex]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT, T_VARIABLE])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$blockType = Tokens::detectBlockType($newCallTokens[$newCallIndex]);
|
||||
|
||||
if (null !== $blockType && (Tokens::BLOCK_TYPE_ARRAY_INDEX_CURLY_BRACE === $blockType['type'] || Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE === $blockType['type'])) {
|
||||
$newCallIndex = $newCallTokens->findBlockStart($blockType['type'], $newCallIndex);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$complex = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ($complex) {
|
||||
$newCallTokens->insertAt($newCallTokens->count(), new Token(')'));
|
||||
$newCallTokens->insertAt(0, new Token('('));
|
||||
}
|
||||
$this->replaceCallUserFuncWithCallback($tokens, $index, $newCallTokens, $firstArgIndex, $firstArgEndIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private function replaceCallUserFuncWithCallback(Tokens $tokens, int $callIndex, Tokens $newCallTokens, int $firstArgStartIndex, int $firstArgEndIndex): void
|
||||
{
|
||||
$tokens->clearRange($firstArgStartIndex, $firstArgEndIndex);
|
||||
|
||||
$afterFirstArgIndex = $tokens->getNextMeaningfulToken($firstArgEndIndex);
|
||||
$afterFirstArgToken = $tokens[$afterFirstArgIndex];
|
||||
|
||||
if ($afterFirstArgToken->equals(',')) {
|
||||
$useEllipsis = $tokens[$callIndex]->equals([T_STRING, 'call_user_func_array'], false);
|
||||
|
||||
if ($useEllipsis) {
|
||||
$secondArgIndex = $tokens->getNextMeaningfulToken($afterFirstArgIndex);
|
||||
$tokens->insertAt($secondArgIndex, new Token([T_ELLIPSIS, '...']));
|
||||
}
|
||||
|
||||
$tokens->clearAt($afterFirstArgIndex);
|
||||
$tokens->removeTrailingWhitespace($afterFirstArgIndex);
|
||||
}
|
||||
|
||||
$tokens->overrideRange($callIndex, $callIndex, $newCallTokens);
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($callIndex);
|
||||
|
||||
if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($prevIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private function getTokensSubcollection(Tokens $tokens, int $indexStart, int $indexEnd): Tokens
|
||||
{
|
||||
$size = $indexEnd - $indexStart + 1;
|
||||
$subCollection = new Tokens($size);
|
||||
|
||||
for ($i = 0; $i < $size; ++$i) {
|
||||
/** @var Token $toClone */
|
||||
$toClone = $tokens[$i + $indexStart];
|
||||
$subCollection[$i] = clone $toClone;
|
||||
}
|
||||
|
||||
return $subCollection;
|
||||
}
|
||||
|
||||
private function isValidFunctionInvoke(string $name): bool
|
||||
{
|
||||
if (\strlen($name) < 3 || 'b' === $name[0] || 'B' === $name[0]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$name = substr($name, 1, -1);
|
||||
|
||||
if ($name !== trim($name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Vendored
+131
@@ -0,0 +1,131 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
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\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class ReturnTypeDeclarationFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Adjust spacing around colon in return type declarations and backed enum types.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\nfunction foo(int \$a):string {};\n"
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\nfunction foo(int \$a):string {};\n",
|
||||
['space_before' => 'none']
|
||||
),
|
||||
new CodeSample(
|
||||
"<?php\nfunction foo(int \$a):string {};\n",
|
||||
['space_before' => 'one']
|
||||
),
|
||||
],
|
||||
'Rule is applied only in a PHP 7+ environment.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after PhpdocToReturnTypeFixer, VoidReturnFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -17;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(CT::T_TYPE_COLON);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$oneSpaceBefore = 'one' === $this->configuration['space_before'];
|
||||
|
||||
for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) {
|
||||
if (!$tokens[$index]->isGivenKind(CT::T_TYPE_COLON)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$previousIndex = $index - 1;
|
||||
$previousToken = $tokens[$previousIndex];
|
||||
|
||||
if ($previousToken->isWhitespace()) {
|
||||
if (!$tokens[$tokens->getPrevNonWhitespace($index - 1)]->isComment()) {
|
||||
if ($oneSpaceBefore) {
|
||||
$tokens[$previousIndex] = new Token([T_WHITESPACE, ' ']);
|
||||
} else {
|
||||
$tokens->clearAt($previousIndex);
|
||||
}
|
||||
}
|
||||
} elseif ($oneSpaceBefore) {
|
||||
$tokenWasAdded = $tokens->ensureWhitespaceAtIndex($index, 0, ' ');
|
||||
|
||||
if ($tokenWasAdded) {
|
||||
++$limit;
|
||||
}
|
||||
|
||||
++$index;
|
||||
}
|
||||
|
||||
++$index;
|
||||
|
||||
$tokenWasAdded = $tokens->ensureWhitespaceAtIndex($index, 0, ' ');
|
||||
|
||||
if ($tokenWasAdded) {
|
||||
++$limit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('space_before', 'Spacing to apply before colon.'))
|
||||
->setAllowedValues(['one', 'none'])
|
||||
->setDefault('none')
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Vendored
+168
@@ -0,0 +1,168 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
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 Kuba Werłos <werlos@gmail.com>
|
||||
*/
|
||||
final class SingleLineThrowFixer extends AbstractFixer
|
||||
{
|
||||
private const REMOVE_WHITESPACE_AFTER_TOKENS = ['['];
|
||||
private const REMOVE_WHITESPACE_AROUND_TOKENS = ['(', [T_DOUBLE_COLON]];
|
||||
private const REMOVE_WHITESPACE_BEFORE_TOKENS = [')', ']', ',', ';'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Throwing exception must be done in single line.',
|
||||
[
|
||||
new CodeSample("<?php\nthrow new Exception(\n 'Error.',\n 500\n);\n"),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_THROW);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before BracesFixer, ConcatSpaceFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 36;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) {
|
||||
if (!$tokens[$index]->isGivenKind(T_THROW)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$endCandidateIndex = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
while (!$tokens[$endCandidateIndex]->equalsAny([')', ']', ',', ';'])) {
|
||||
$blockType = Tokens::detectBlockType($tokens[$endCandidateIndex]);
|
||||
|
||||
if (null !== $blockType) {
|
||||
if (Tokens::BLOCK_TYPE_CURLY_BRACE === $blockType['type'] || !$blockType['isStart']) {
|
||||
break;
|
||||
}
|
||||
|
||||
$endCandidateIndex = $tokens->findBlockEnd($blockType['type'], $endCandidateIndex);
|
||||
}
|
||||
|
||||
$endCandidateIndex = $tokens->getNextMeaningfulToken($endCandidateIndex);
|
||||
}
|
||||
|
||||
$this->trimNewLines($tokens, $index, $tokens->getPrevMeaningfulToken($endCandidateIndex));
|
||||
}
|
||||
}
|
||||
|
||||
private function trimNewLines(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
for ($index = $startIndex; $index < $endIndex; ++$index) {
|
||||
$content = $tokens[$index]->getContent();
|
||||
|
||||
if ($tokens[$index]->isGivenKind(T_COMMENT)) {
|
||||
if (str_starts_with($content, '//')) {
|
||||
$content = '/*'.substr($content, 2).' */';
|
||||
$tokens->clearAt($index + 1);
|
||||
} elseif (str_starts_with($content, '#')) {
|
||||
$content = '/*'.substr($content, 1).' */';
|
||||
$tokens->clearAt($index + 1);
|
||||
} elseif (0 !== Preg::match('/\R/', $content)) {
|
||||
$content = Preg::replace('/\R/', ' ', $content);
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_COMMENT, $content]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$index]->isGivenKind(T_WHITESPACE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (0 === Preg::match('/\R/', $content)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prevIndex = $tokens->getNonEmptySibling($index, -1);
|
||||
|
||||
if ($this->isPreviousTokenToClear($tokens[$prevIndex])) {
|
||||
$tokens->clearAt($index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$nextIndex = $tokens->getNonEmptySibling($index, 1);
|
||||
|
||||
if (
|
||||
$this->isNextTokenToClear($tokens[$nextIndex])
|
||||
&& !$tokens[$prevIndex]->isGivenKind(T_FUNCTION)
|
||||
) {
|
||||
$tokens->clearAt($index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_WHITESPACE, ' ']);
|
||||
}
|
||||
}
|
||||
|
||||
private function isPreviousTokenToClear(Token $token): bool
|
||||
{
|
||||
static $tokens = null;
|
||||
|
||||
if (null === $tokens) {
|
||||
$tokens = array_merge(self::REMOVE_WHITESPACE_AFTER_TOKENS, self::REMOVE_WHITESPACE_AROUND_TOKENS);
|
||||
}
|
||||
|
||||
return $token->equalsAny($tokens) || $token->isObjectOperator();
|
||||
}
|
||||
|
||||
private function isNextTokenToClear(Token $token): bool
|
||||
{
|
||||
static $tokens = null;
|
||||
|
||||
if (null === $tokens) {
|
||||
$tokens = array_merge(self::REMOVE_WHITESPACE_AROUND_TOKENS, self::REMOVE_WHITESPACE_BEFORE_TOKENS);
|
||||
}
|
||||
|
||||
return $token->equalsAny($tokens) || $token->isObjectOperator();
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
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;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
final class StaticLambdaFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Lambdas not (indirect) referencing `$this` must be declared `static`.',
|
||||
[new CodeSample("<?php\n\$a = function () use (\$b)\n{ echo \$b;\n};\n")],
|
||||
null,
|
||||
'Risky when using `->bindTo` on lambdas without referencing to `$this`.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$analyzer = new TokensAnalyzer($tokens);
|
||||
$expectedFunctionKinds = [T_FUNCTION, T_FN];
|
||||
|
||||
for ($index = $tokens->count() - 4; $index > 0; --$index) {
|
||||
if (!$tokens[$index]->isGivenKind($expectedFunctionKinds) || !$analyzer->isLambda($index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prev = $tokens->getPrevMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$prev]->isGivenKind(T_STATIC)) {
|
||||
continue; // lambda is already 'static'
|
||||
}
|
||||
|
||||
$argumentsStartIndex = $tokens->getNextTokenOfKind($index, ['(']);
|
||||
$argumentsEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $argumentsStartIndex);
|
||||
|
||||
// figure out where the lambda starts and ends
|
||||
|
||||
if ($tokens[$index]->isGivenKind(T_FUNCTION)) {
|
||||
$lambdaOpenIndex = $tokens->getNextTokenOfKind($argumentsEndIndex, ['{']);
|
||||
$lambdaEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $lambdaOpenIndex);
|
||||
} else { // T_FN
|
||||
$lambdaOpenIndex = $tokens->getNextTokenOfKind($argumentsEndIndex, [[T_DOUBLE_ARROW]]);
|
||||
$lambdaEndIndex = $this->findExpressionEnd($tokens, $lambdaOpenIndex);
|
||||
}
|
||||
|
||||
if ($this->hasPossibleReferenceToThis($tokens, $lambdaOpenIndex, $lambdaEndIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// make the lambda static
|
||||
$tokens->insertAt(
|
||||
$index,
|
||||
[
|
||||
new Token([T_STATIC, 'static']),
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
]
|
||||
);
|
||||
|
||||
$index -= 4; // fixed after a lambda, closes candidate is at least 4 tokens before that
|
||||
}
|
||||
}
|
||||
|
||||
private function findExpressionEnd(Tokens $tokens, int $index): int
|
||||
{
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
while (null !== $nextIndex) {
|
||||
/** @var Token $nextToken */
|
||||
$nextToken = $tokens[$nextIndex];
|
||||
|
||||
if ($nextToken->equalsAny([',', ';', [T_CLOSE_TAG]])) {
|
||||
break;
|
||||
}
|
||||
|
||||
$blockType = Tokens::detectBlockType($nextToken);
|
||||
|
||||
if (null !== $blockType && $blockType['isStart']) {
|
||||
$nextIndex = $tokens->findBlockEnd($blockType['type'], $nextIndex);
|
||||
}
|
||||
|
||||
$index = $nextIndex;
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($index);
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 'true' if there is a possible reference to '$this' within the given tokens index range.
|
||||
*/
|
||||
private function hasPossibleReferenceToThis(Tokens $tokens, int $startIndex, int $endIndex): bool
|
||||
{
|
||||
for ($i = $startIndex; $i <= $endIndex; ++$i) {
|
||||
if ($tokens[$i]->isGivenKind(T_VARIABLE) && '$this' === strtolower($tokens[$i]->getContent())) {
|
||||
return true; // directly accessing '$this'
|
||||
}
|
||||
|
||||
if ($tokens[$i]->isGivenKind([
|
||||
T_INCLUDE, // loading additional symbols we cannot analyze here
|
||||
T_INCLUDE_ONCE, // "
|
||||
T_REQUIRE, // "
|
||||
T_REQUIRE_ONCE, // "
|
||||
CT::T_DYNAMIC_VAR_BRACE_OPEN, // "$h = ${$g};" case
|
||||
T_EVAL, // "$c = eval('return $this;');" case
|
||||
])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($tokens[$i]->equals('$')) {
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($i);
|
||||
|
||||
if ($tokens[$nextIndex]->isGivenKind(T_VARIABLE)) {
|
||||
return true; // "$$a" case
|
||||
}
|
||||
}
|
||||
|
||||
if ($tokens[$i]->equals([T_STRING, 'parent'], false)) {
|
||||
return true; // parent:: case
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Vendored
+207
@@ -0,0 +1,207 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecification;
|
||||
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
/**
|
||||
* @author Gregor Harlan
|
||||
*/
|
||||
final class UseArrowFunctionsFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Anonymous functions with one-liner return statement must use arrow functions.',
|
||||
[
|
||||
new VersionSpecificCodeSample(
|
||||
<<<'SAMPLE'
|
||||
<?php
|
||||
foo(function ($a) use ($b) {
|
||||
return $a + $b;
|
||||
});
|
||||
|
||||
SAMPLE
|
||||
,
|
||||
new VersionSpecification(70400)
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when using `isset()` on outside variables that are not imported with `use ()`.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isAllTokenKindsFound([T_FUNCTION, T_RETURN]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$analyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
for ($index = $tokens->count() - 1; $index > 0; --$index) {
|
||||
if (!$tokens[$index]->isGivenKind(T_FUNCTION) || !$analyzer->isLambda($index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find parameters end
|
||||
// Abort if they are multilined
|
||||
|
||||
$parametersStart = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$parametersStart]->isGivenKind(CT::T_RETURN_REF)) {
|
||||
$parametersStart = $tokens->getNextMeaningfulToken($parametersStart);
|
||||
}
|
||||
|
||||
$parametersEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $parametersStart);
|
||||
|
||||
if ($this->isMultilined($tokens, $parametersStart, $parametersEnd)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find `use ()` start and end
|
||||
// Abort if it contains reference variables
|
||||
|
||||
$next = $tokens->getNextMeaningfulToken($parametersEnd);
|
||||
|
||||
$useStart = null;
|
||||
$useEnd = null;
|
||||
|
||||
if ($tokens[$next]->isGivenKind(CT::T_USE_LAMBDA)) {
|
||||
$useStart = $next;
|
||||
|
||||
if ($tokens[$useStart - 1]->isGivenKind(T_WHITESPACE)) {
|
||||
--$useStart;
|
||||
}
|
||||
|
||||
$next = $tokens->getNextMeaningfulToken($next);
|
||||
|
||||
while (!$tokens[$next]->equals(')')) {
|
||||
if ($tokens[$next]->equals('&')) {
|
||||
// variables used by reference are not supported by arrow functions
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$next = $tokens->getNextMeaningfulToken($next);
|
||||
}
|
||||
|
||||
$useEnd = $next;
|
||||
$next = $tokens->getNextMeaningfulToken($next);
|
||||
}
|
||||
|
||||
// Find opening brace and following `return`
|
||||
// Abort if there is more than whitespace between them (like comments)
|
||||
|
||||
$braceOpen = $tokens[$next]->equals('{') ? $next : $tokens->getNextTokenOfKind($next, ['{']);
|
||||
$return = $braceOpen + 1;
|
||||
|
||||
if ($tokens[$return]->isGivenKind(T_WHITESPACE)) {
|
||||
++$return;
|
||||
}
|
||||
|
||||
if (!$tokens[$return]->isGivenKind(T_RETURN)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find semicolon of `return` statement
|
||||
|
||||
$semicolon = $tokens->getNextTokenOfKind($return, ['{', ';']);
|
||||
|
||||
if (!$tokens[$semicolon]->equals(';')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find closing brace
|
||||
// Abort if there is more than whitespace between semicolon and closing brace
|
||||
|
||||
$braceClose = $semicolon + 1;
|
||||
|
||||
if ($tokens[$braceClose]->isGivenKind(T_WHITESPACE)) {
|
||||
++$braceClose;
|
||||
}
|
||||
|
||||
if (!$tokens[$braceClose]->equals('}')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Abort if the `return` statement is multilined
|
||||
|
||||
if ($this->isMultilined($tokens, $return, $semicolon)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Transform the function to an arrow function
|
||||
|
||||
$this->transform($tokens, $index, $useStart, $useEnd, $braceOpen, $return, $semicolon, $braceClose);
|
||||
}
|
||||
}
|
||||
|
||||
private function isMultilined(Tokens $tokens, int $start, int $end): bool
|
||||
{
|
||||
for ($i = $start; $i < $end; ++$i) {
|
||||
if (str_contains($tokens[$i]->getContent(), "\n")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function transform(Tokens $tokens, int $index, ?int $useStart, ?int $useEnd, int $braceOpen, int $return, int $semicolon, int $braceClose): void
|
||||
{
|
||||
$tokensToInsert = [new Token([T_DOUBLE_ARROW, '=>'])];
|
||||
|
||||
if ($tokens->getNextMeaningfulToken($return) === $semicolon) {
|
||||
$tokensToInsert[] = new Token([T_WHITESPACE, ' ']);
|
||||
$tokensToInsert[] = new Token([T_STRING, 'null']);
|
||||
}
|
||||
|
||||
$tokens->clearRange($semicolon, $braceClose);
|
||||
$tokens->clearRange($braceOpen + 1, $return);
|
||||
$tokens->overrideRange($braceOpen, $braceOpen, $tokensToInsert);
|
||||
|
||||
if (null !== $useStart) {
|
||||
$tokens->clearRange($useStart, $useEnd);
|
||||
}
|
||||
|
||||
$tokens[$index] = new Token([T_FN, 'fn']);
|
||||
}
|
||||
}
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
<?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\FunctionNotation;
|
||||
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\DocBlock\Annotation;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
/**
|
||||
* @author Mark Nielsen
|
||||
*/
|
||||
final class VoidReturnFixer extends AbstractFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Add `void` return type to functions with missing or empty return statements, but priority is given to `@return` annotations. Requires PHP >= 7.1.',
|
||||
[
|
||||
new CodeSample(
|
||||
"<?php\nfunction foo(\$a) {};\n"
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Modifies the signature of functions.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpdocNoEmptyReturnFixer, ReturnTypeDeclarationFixer.
|
||||
* Must run after NoSuperfluousPhpdocTagsFixer, SimplifiedNullReturnFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_FUNCTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
// These cause syntax errors.
|
||||
static $excludedFunctions = [
|
||||
[T_STRING, '__clone'],
|
||||
[T_STRING, '__construct'],
|
||||
[T_STRING, '__debugInfo'],
|
||||
[T_STRING, '__destruct'],
|
||||
[T_STRING, '__isset'],
|
||||
[T_STRING, '__serialize'],
|
||||
[T_STRING, '__set_state'],
|
||||
[T_STRING, '__sleep'],
|
||||
[T_STRING, '__toString'],
|
||||
];
|
||||
|
||||
for ($index = $tokens->count() - 1; 0 <= $index; --$index) {
|
||||
if (!$tokens[$index]->isGivenKind(T_FUNCTION)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$functionName = $tokens->getNextMeaningfulToken($index);
|
||||
if ($tokens[$functionName]->equalsAny($excludedFunctions, false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$startIndex = $tokens->getNextTokenOfKind($index, ['{', ';']);
|
||||
|
||||
if ($this->hasReturnTypeHint($tokens, $startIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$startIndex]->equals(';')) {
|
||||
// No function body defined, fallback to PHPDoc.
|
||||
if ($this->hasVoidReturnAnnotation($tokens, $index)) {
|
||||
$this->fixFunctionDefinition($tokens, $startIndex);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->hasReturnAnnotation($tokens, $index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startIndex);
|
||||
|
||||
if ($this->hasVoidReturn($tokens, $startIndex, $endIndex)) {
|
||||
$this->fixFunctionDefinition($tokens, $startIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether there is a non-void return annotation in the function's PHPDoc comment.
|
||||
*
|
||||
* @param int $index The index of the function token
|
||||
*/
|
||||
private function hasReturnAnnotation(Tokens $tokens, int $index): bool
|
||||
{
|
||||
foreach ($this->findReturnAnnotations($tokens, $index) as $return) {
|
||||
if (['void'] !== $return->getTypes()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether there is a void return annotation in the function's PHPDoc comment.
|
||||
*
|
||||
* @param int $index The index of the function token
|
||||
*/
|
||||
private function hasVoidReturnAnnotation(Tokens $tokens, int $index): bool
|
||||
{
|
||||
foreach ($this->findReturnAnnotations($tokens, $index) as $return) {
|
||||
if (['void'] === $return->getTypes()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the function already has a return type hint.
|
||||
*
|
||||
* @param int $index The index of the end of the function definition line, EG at { or ;
|
||||
*/
|
||||
private function hasReturnTypeHint(Tokens $tokens, int $index): bool
|
||||
{
|
||||
$endFuncIndex = $tokens->getPrevTokenOfKind($index, [')']);
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($endFuncIndex);
|
||||
|
||||
return $tokens[$nextIndex]->isGivenKind(CT::T_TYPE_COLON);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the function has a void return.
|
||||
*
|
||||
* @param int $startIndex Start of function body
|
||||
* @param int $endIndex End of function body
|
||||
*/
|
||||
private function hasVoidReturn(Tokens $tokens, int $startIndex, int $endIndex): bool
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
for ($i = $startIndex; $i < $endIndex; ++$i) {
|
||||
if (
|
||||
// skip anonymous classes
|
||||
($tokens[$i]->isGivenKind(T_CLASS) && $tokensAnalyzer->isAnonymousClass($i))
|
||||
// skip lambda functions
|
||||
|| ($tokens[$i]->isGivenKind(T_FUNCTION) && $tokensAnalyzer->isLambda($i))
|
||||
) {
|
||||
$i = $tokens->getNextTokenOfKind($i, ['{']);
|
||||
$i = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $i);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$i]->isGivenKind([T_YIELD, T_YIELD_FROM])) {
|
||||
return false; // Generators cannot return void.
|
||||
}
|
||||
|
||||
if (!$tokens[$i]->isGivenKind(T_RETURN)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$i = $tokens->getNextMeaningfulToken($i);
|
||||
if (!$tokens[$i]->equals(';')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $index The index of the end of the function definition line, EG at { or ;
|
||||
*/
|
||||
private function fixFunctionDefinition(Tokens $tokens, int $index): void
|
||||
{
|
||||
$endFuncIndex = $tokens->getPrevTokenOfKind($index, [')']);
|
||||
$tokens->insertAt($endFuncIndex + 1, [
|
||||
new Token([CT::T_TYPE_COLON, ':']),
|
||||
new Token([T_WHITESPACE, ' ']),
|
||||
new Token([T_STRING, 'void']),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all the return annotations in the function's PHPDoc comment.
|
||||
*
|
||||
* @param int $index The index of the function token
|
||||
*
|
||||
* @return Annotation[]
|
||||
*/
|
||||
private function findReturnAnnotations(Tokens $tokens, int $index): array
|
||||
{
|
||||
do {
|
||||
$index = $tokens->getPrevNonWhitespace($index);
|
||||
} while ($tokens[$index]->isGivenKind([
|
||||
T_ABSTRACT,
|
||||
T_FINAL,
|
||||
T_PRIVATE,
|
||||
T_PROTECTED,
|
||||
T_PUBLIC,
|
||||
T_STATIC,
|
||||
]));
|
||||
|
||||
if (!$tokens[$index]->isGivenKind(T_DOC_COMMENT)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$doc = new DocBlock($tokens[$index]->getContent());
|
||||
|
||||
return $doc->getAnnotationsOfType('return');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user