Skip to content

Commit

Permalink
Merge branch '2.17'
Browse files Browse the repository at this point in the history
* 2.17:
  TypeAlternationTransformer - add support for PHP8
  Enhancement: Allow sorting of property, property-read, and property-write annotations by value Enhancement: Allow sorting of method annotations by value
  NoExtraBlankLinesFixer - PHP8 throw support
  minors
  NullableTypeTransformer - constructor property promotion support
  TokensAnalyzer::getClassyElements - return trait imports
  SingleSpaceAfterConstructFixer - Attributes, comments and PHPDoc support
  Fix: Description
  • Loading branch information
SpacePossum committed Dec 31, 2020
2 parents 180dad7 + 8b035db commit bd186ce
Show file tree
Hide file tree
Showing 29 changed files with 1,084 additions and 142 deletions.
4 changes: 2 additions & 2 deletions doc/rules/language_construct/single_space_after_construct.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ Configuration

List of constructs which must be followed by a single space.

Allowed values: a subset of ``['abstract', 'as', 'attribute', 'break', 'case', 'catch', 'class', 'clone', 'const', 'const_import', 'continue', 'do', 'echo', 'else', 'elseif', 'extends', 'final', 'finally', 'for', 'foreach', 'function', 'function_import', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'match', 'named_argument', 'new', 'open_tag_with_echo', 'php_open', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'return', 'static', 'throw', 'trait', 'try', 'use', 'use_lambda', 'use_trait', 'var', 'while', 'yield', 'yield_from']``
Allowed values: a subset of ``['abstract', 'as', 'attribute', 'break', 'case', 'catch', 'class', 'clone', 'comment', 'const', 'const_import', 'continue', 'do', 'echo', 'else', 'elseif', 'extends', 'final', 'finally', 'for', 'foreach', 'function', 'function_import', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'match', 'named_argument', 'new', 'open_tag_with_echo', 'php_doc', 'php_open', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'return', 'static', 'throw', 'trait', 'try', 'use', 'use_lambda', 'use_trait', 'var', 'while', 'yield', 'yield_from']``

Default value: ``['abstract', 'as', 'attribute', 'break', 'case', 'catch', 'class', 'clone', 'const', 'const_import', 'continue', 'do', 'echo', 'else', 'elseif', 'extends', 'final', 'finally', 'for', 'foreach', 'function', 'function_import', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'match', 'named_argument', 'new', 'open_tag_with_echo', 'php_open', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'return', 'static', 'throw', 'trait', 'try', 'use', 'use_lambda', 'use_trait', 'var', 'while', 'yield', 'yield_from']``
Default value: ``['abstract', 'as', 'attribute', 'break', 'case', 'catch', 'class', 'clone', 'comment', 'const', 'const_import', 'continue', 'do', 'echo', 'else', 'elseif', 'extends', 'final', 'finally', 'for', 'foreach', 'function', 'function_import', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'match', 'named_argument', 'new', 'open_tag_with_echo', 'php_doc', 'php_open', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'return', 'static', 'throw', 'trait', 'try', 'use', 'use_lambda', 'use_trait', 'var', 'while', 'yield', 'yield_from']``

Examples
--------
Expand Down
2 changes: 1 addition & 1 deletion doc/rules/phpdoc/phpdoc_order_by_value.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Configuration

List of annotations to order, e.g. ``["covers"]``.

Allowed values: a subset of ``['author', 'covers', 'coversNothing', 'dataProvider', 'depends', 'group', 'internal', 'requires', 'throws', 'uses']``
Allowed values: a subset of ``['author', 'covers', 'coversNothing', 'dataProvider', 'depends', 'group', 'internal', 'method', 'property', 'property-read', 'property-write', 'requires', 'throws', 'uses']``

Default value: ``['covers']``

Expand Down
2 changes: 1 addition & 1 deletion doc/rules/phpdoc/phpdoc_scalar.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Configuration
``types``
~~~~~~~~~

A map of types to fix.
A list of types to fix.

Allowed values: a subset of ``['boolean', 'callback', 'double', 'integer', 'real', 'str']``

Expand Down
3 changes: 2 additions & 1 deletion src/Fixer/Alias/SetTypeToCastFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens)
}

$prev = $tokens->getPrevMeaningfulToken($functionNameIndex);
if (!$tokens[$prev]->isGivenKind(T_OPEN_TAG) && !$tokens[$prev]->equalsAny([';', '{'])) {

if (!$tokens[$prev]->equalsAny([';', '{', '}', [T_OPEN_TAG]])) {
continue; // return value of the function is used
}

Expand Down
7 changes: 3 additions & 4 deletions src/Fixer/ClassNotation/VisibilityRequiredFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,9 @@ protected function createConfigurationDefinition()
protected function applyFix(\SplFileInfo $file, Tokens $tokens)
{
$tokensAnalyzer = new TokensAnalyzer($tokens);
$elements = $tokensAnalyzer->getClassyElements();

$propertyTypeDeclarationKinds = [T_STRING, T_NS_SEPARATOR, CT::T_NULLABLE_TYPE, CT::T_ARRAY_TYPEHINT];

foreach (array_reverse($elements, true) as $index => $element) {
foreach (array_reverse($tokensAnalyzer->getClassyElements(), true) as $index => $element) {
if (!\in_array($element['type'], $this->configuration['elements'], true)) {
continue;
}
Expand All @@ -115,8 +113,8 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens)
$staticIndex = null;
$typeIndex = null;
$prevIndex = $tokens->getPrevMeaningfulToken($index);

$expectedKinds = [T_ABSTRACT, T_FINAL, T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR];

if ('property' === $element['type']) {
$expectedKinds = array_merge($expectedKinds, $propertyTypeDeclarationKinds);
}
Expand All @@ -131,6 +129,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens)
} else {
$visibilityIndex = $prevIndex;
}

$prevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
}

Expand Down
14 changes: 11 additions & 3 deletions src/Fixer/LanguageConstruct/SingleSpaceAfterConstructFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ final class SingleSpaceAfterConstructFixer extends AbstractFixer implements Conf
'catch' => T_CATCH,
'class' => T_CLASS,
'clone' => T_CLONE,
'comment' => T_COMMENT,
'const' => T_CONST,
'const_import' => CT::T_CONST_IMPORT,
'continue' => T_CONTINUE,
Expand Down Expand Up @@ -70,6 +71,7 @@ final class SingleSpaceAfterConstructFixer extends AbstractFixer implements Conf
'named_argument' => CT::T_NAMED_ARGUMENT_COLON,
'new' => T_NEW,
'open_tag_with_echo' => T_OPEN_TAG_WITH_ECHO,
'php_doc' => T_DOC_COMMENT,
'php_open' => T_OPEN_TAG,
'print' => T_PRINT,
'private' => T_PRIVATE,
Expand Down Expand Up @@ -226,10 +228,16 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens)
continue;
}

if (!$tokens[$whitespaceTokenIndex]->equals([T_WHITESPACE])) {
$tokens->insertAt($whitespaceTokenIndex, new Token([T_WHITESPACE, ' ']));
} elseif (' ' !== $tokens[$whitespaceTokenIndex]->getContent()) {
if ($token->isComment() || $token->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) {
if ($tokens[$whitespaceTokenIndex]->equals([T_WHITESPACE]) && false !== strpos($tokens[$whitespaceTokenIndex]->getContent(), "\n")) {
continue;
}
}

if ($tokens[$whitespaceTokenIndex]->equals([T_WHITESPACE])) {
$tokens[$whitespaceTokenIndex] = new Token([T_WHITESPACE, ' ']);
} else {
$tokens->insertAt($whitespaceTokenIndex, new Token([T_WHITESPACE, ' ']));
}

if (
Expand Down
43 changes: 36 additions & 7 deletions src/Fixer/Phpdoc/PhpdocOrderByValueFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use Symfony\Component\OptionsResolver\Options;

/**
* @author Filippo Tessarotto <[email protected]>
Expand Down Expand Up @@ -96,7 +97,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens)
}

for ($index = $tokens->count() - 1; $index > 0; --$index) {
foreach ($this->configuration['annotations'] as $type) {
foreach ($this->configuration['annotations'] as $type => $typeLowerCase) {
$findPattern = sprintf(
'/@%s\s.+@%s\s/s',
$type,
Expand All @@ -113,20 +114,33 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens)
$docBlock = new DocBlock($tokens[$index]->getContent());

$annotations = $docBlock->getAnnotationsOfType($type);

$annotationMap = [];

$replacePattern = sprintf(
'/\*\s*@%s\s+(.+)/',
$type
);
if (\in_array($type, ['property', 'property-read', 'property-write'], true)) {
$replacePattern = sprintf(
'/(?s)\*\s*@%s\s+(?P<optionalTypes>.+\s+)?\$(?P<comparableContent>[^\s]+).*/',
$type
);

$replacement = '\2';
} elseif ('method' === $type) {
$replacePattern = '/(?s)\*\s*@method\s+(?P<optionalReturnTypes>.+\s+)?(?P<comparableContent>.+)\(.*/';
$replacement = '\2';
} else {
$replacePattern = sprintf(
'/\*\s*@%s\s+(?P<comparableContent>.+)/',
$typeLowerCase
);

$replacement = '\1';
}

foreach ($annotations as $annotation) {
$rawContent = $annotation->getContent();

$comparableContent = Preg::replace(
$replacePattern,
'\1',
$replacement,
strtolower(trim($rawContent))
);

Expand Down Expand Up @@ -167,6 +181,10 @@ protected function createConfigurationDefinition()
'depends',
'group',
'internal',
'method',
'property',
'property-read',
'property-write',
'requires',
'throws',
'uses',
Expand All @@ -180,6 +198,17 @@ protected function createConfigurationDefinition()
->setAllowedValues([
new AllowedValueSubset($allowedValues),
])
->setNormalizer(function (Options $options, $value) {
$normalized = [];

foreach ($value as $index => $annotation) {
// since we will be using strtolower on the input annotations when building the sorting
// map we must match the type in lower case as well
$normalized[$annotation] = strtolower($annotation);
}

return $normalized;
})
->setDefault([
'covers',
])
Expand Down
2 changes: 1 addition & 1 deletion src/Fixer/Phpdoc/PhpdocScalarFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public function getPriority()
protected function createConfigurationDefinition()
{
return new FixerConfigurationResolver([
(new FixerOptionBuilder('types', 'A map of types to fix.'))
(new FixerOptionBuilder('types', 'A list of types to fix.'))
->setAllowedValues([new AllowedValueSubset(array_keys(self::$types))])
->setDefault(['boolean', 'double', 'integer', 'real', 'str']) // TODO 3.0 add "callback"
->getOption(),
Expand Down
9 changes: 8 additions & 1 deletion src/Fixer/Whitespace/NoExtraBlankLinesFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public function configure(array $configuration = null)
T_DEFAULT => 'fixAfterToken',
T_RETURN => 'fixAfterToken',
T_SWITCH => 'fixAfterToken',
T_THROW => 'fixAfterToken',
T_THROW => 'fixAfterThrowToken',
T_USE => 'removeBetweenUse',
T_WHITESPACE => 'removeMultipleBlankLines',
CT::T_USE_TRAIT => 'removeBetweenUse',
Expand Down Expand Up @@ -408,6 +408,13 @@ private function fixAfterToken($index)
$this->removeEmptyLinesAfterLineWithTokenAt($index);
}

private function fixAfterThrowToken($index)
{
if ($this->tokens[$this->tokens->getPrevMeaningfulToken($index)]->equalsAny([';', '{', '}', ':', [T_OPEN_TAG]])) {
$this->fixAfterToken($index);
}
}

/**
* Remove white line(s) after the index of a block type,
* but only if the block is not on one line.
Expand Down
4 changes: 2 additions & 2 deletions src/Tokenizer/Analyzer/ClassyAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ public function isClassyInvocation(Tokens $tokens, $index)
$token = $tokens[$index];

if (!$token->isGivenKind(T_STRING)) {
throw new \LogicException(sprintf('No T_STRING at given index %d, got %s.', $index, $tokens[$index]->getName()));
throw new \LogicException(sprintf('No T_STRING at given index %d, got "%s".', $index, $tokens[$index]->getName()));
}

if (\in_array(strtolower($token->getContent()), ['bool', 'float', 'int', 'iterable', 'object', 'parent', 'self', 'string', 'void'], true)) {
if (\in_array(strtolower($token->getContent()), ['bool', 'float', 'int', 'iterable', 'object', 'parent', 'self', 'string', 'void', 'null', 'false'], true)) {
return false;
}

Expand Down
42 changes: 32 additions & 10 deletions src/Tokenizer/TokensAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

namespace PhpCsFixer\Tokenizer;

use PhpCsFixer\Tokenizer\Analyzer\GotoLabelAnalyzer;

/**
* Analyzer of Tokens collection.
*
Expand All @@ -32,6 +34,11 @@ final class TokensAnalyzer
*/
private $tokens;

/**
* @var ?GotoLabelAnalyzer
*/
private $gotoLabelAnalyzer;

public function __construct(Tokens $tokens)
{
$this->tokens = $tokens;
Expand All @@ -40,15 +47,17 @@ public function __construct(Tokens $tokens)
/**
* Get indexes of methods and properties in classy code (classes, interfaces and traits).
*
* @param bool $returnTraitsImports TODO on v3 remove flag and return the imports
*
* @return array[]
*/
public function getClassyElements()
public function getClassyElements($returnTraitsImports = false)
{
$elements = [];

for ($index = 1, $count = \count($this->tokens) - 2; $index < $count; ++$index) {
if ($this->tokens[$index]->isClassy()) {
list($index, $newElements) = $this->findClassyElements($index, $index);
list($index, $newElements) = $this->findClassyElements($index, $index, $returnTraitsImports);
$elements += $newElements;
}
}
Expand Down Expand Up @@ -362,8 +371,14 @@ public function isConstantInvocation($index)
}

// check for goto label
if ($this->tokens[$nextIndex]->equals(':') && $this->tokens[$prevIndex]->equalsAny([';', '}', [T_OPEN_TAG], [T_OPEN_TAG_WITH_ECHO]])) {
return false;
if ($this->tokens[$nextIndex]->equals(':')) {
if (null === $this->gotoLabelAnalyzer) {
$this->gotoLabelAnalyzer = new GotoLabelAnalyzer();
}

if ($this->gotoLabelAnalyzer->belongsToGoToLabel($this->tokens, $nextIndex)) {
return false;
}
}

return true;
Expand Down Expand Up @@ -638,12 +653,13 @@ public function isSuperGlobal($index)
* Searches in tokens from the classy (start) index till the end (index) of the classy.
* Returns an array; first value is the index until the method has analysed (int), second the found classy elements (array).
*
* @param int $classIndex classy index
* @param int $index
* @param int $classIndex classy index
* @param int $index
* @param bool $returnTraitsImports
*
* @return array
*/
private function findClassyElements($classIndex, $index)
private function findClassyElements($classIndex, $index, $returnTraitsImports = false)
{
$elements = [];
$curlyBracesLevel = 0;
Expand Down Expand Up @@ -681,7 +697,7 @@ private function findClassyElements($classIndex, $index)
--$nestedBracesLevel;

if (0 === $nestedBracesLevel) {
list($index, $newElements) = $this->findClassyElements($nestedClassIndex, $index);
list($index, $newElements) = $this->findClassyElements($nestedClassIndex, $index, $returnTraitsImports);
$elements += $newElements;

break;
Expand All @@ -691,12 +707,12 @@ private function findClassyElements($classIndex, $index)
}

if ($token->isClassy()) { // anonymous class in class
list($index, $newElements) = $this->findClassyElements($index, $index);
list($index, $newElements) = $this->findClassyElements($index, $index, $returnTraitsImports);
$elements += $newElements;
}
}
} else {
list($index, $newElements) = $this->findClassyElements($nestedClassIndex, $nestedClassIndex);
list($index, $newElements) = $this->findClassyElements($nestedClassIndex, $nestedClassIndex, $returnTraitsImports);
$elements += $newElements;
}

Expand Down Expand Up @@ -757,6 +773,12 @@ private function findClassyElements($classIndex, $index)
'type' => 'const',
'classIndex' => $classIndex,
];
} elseif ($returnTraitsImports && $token->isGivenKind(CT::T_USE_TRAIT)) {
$elements[$index] = [
'token' => $token,
'type' => 'trait_import',
'classIndex' => $classIndex,
];
}
}

Expand Down
14 changes: 13 additions & 1 deletion src/Tokenizer/Transformer/NullableTypeTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,19 @@ public function process(Tokens $tokens, Token $token, $index)
$prevIndex = $tokens->getPrevMeaningfulToken($index);
$prevToken = $tokens[$prevIndex];

if ($prevToken->equalsAny(['(', ',', [CT::T_TYPE_COLON], [T_PRIVATE], [T_PROTECTED], [T_PUBLIC], [T_VAR], [T_STATIC]])) {
if ($prevToken->equalsAny([
'(',
',',
[CT::T_TYPE_COLON],
[CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC],
[CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED],
[CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE],
[T_PRIVATE],
[T_PROTECTED],
[T_PUBLIC],
[T_VAR],
[T_STATIC],
])) {
$tokens[$index] = new Token([CT::T_NULLABLE_TYPE, '?']);
}
}
Expand Down
Loading

0 comments on commit bd186ce

Please sign in to comment.