Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeAlternationTransformer - add support for PHP8 #5405

Merged
merged 1 commit into from
Dec 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
75 changes: 58 additions & 17 deletions src/Tokenizer/Transformer/TypeAlternationTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@
*/
final class TypeAlternationTransformer extends AbstractTransformer
{
/**
* {@inheritdoc}
*/
public function getPriority()
{
// needs to run after TypeColonTransformer
return -15;
}

/**
* {@inheritdoc}
*/
Expand All @@ -44,36 +53,63 @@ public function process(Tokens $tokens, Token $token, $index)
}

$prevIndex = $tokens->getPrevMeaningfulToken($index);
$prevToken = $tokens[$prevIndex];

if (!$prevToken->isGivenKind(T_STRING)) {
if (!$tokens[$prevIndex]->isGivenKind(T_STRING)) {
return;
}

do {
$prevIndex = $tokens->getPrevMeaningfulToken($prevIndex);

if (null === $prevIndex) {
return;
}

if (!$tokens[$prevIndex]->isGivenKind([T_NS_SEPARATOR, T_STRING])) {
break;
}
} while (true);

$prevToken = $tokens[$prevIndex];
/** @var Token $prevToken */
$prevToken = $tokens[$prevIndex];

if ($prevToken->isGivenKind([T_NS_SEPARATOR, T_STRING])) {
continue;
}
if ($prevToken->isGivenKind([
CT::T_TYPE_COLON, // `|` is part of a function return type union `foo(): A|B`
CT::T_TYPE_ALTERNATION, // `|` is part of a union (chain) `| X | Y`
T_VAR, T_PUBLIC, T_PROTECTED, T_PRIVATE, // `|` is part of class property `var X|Y $a;`
])) {
$this->replaceToken($tokens, $index);

if (
$prevToken->isGivenKind(CT::T_TYPE_ALTERNATION)
|| (
$prevToken->equals('(')
&& $tokens[$tokens->getPrevMeaningfulToken($prevIndex)]->isGivenKind(T_CATCH)
)
) {
$tokens[$index] = new Token([CT::T_TYPE_ALTERNATION, '|']);
}
return;
}

break;
} while (true);
if (!$prevToken->equals('(')) {
return;
}

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

/** @var Token $prePrevToken */
$prePrevToken = $tokens[$prevPrevTokenIndex];

if ($prePrevToken->isGivenKind([
T_CATCH, // `|` is part of catch `catch(X |`
T_FUNCTION, // `|` is part of an anonymous function variable `static function (X|Y`
])) {
$this->replaceToken($tokens, $index);

return;
}

if (
$prePrevToken->isGivenKind(T_STRING)
&& $tokens[$tokens->getPrevMeaningfulToken($prevPrevTokenIndex)]->isGivenKind(T_FUNCTION)
) {
// `|` is part of function variable `function Foo (X|Y`
$this->replaceToken($tokens, $index);

return;
}
}

/**
Expand All @@ -83,4 +119,9 @@ protected function getDeprecatedCustomTokens()
{
return [CT::T_TYPE_ALTERNATION];
}

private function replaceToken(Tokens $tokens, $index)
{
$tokens[$index] = new Token([CT::T_TYPE_ALTERNATION, '|']);
}
}
1 change: 1 addition & 0 deletions src/Tokenizer/Transformer/TypeColonTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ final class TypeColonTransformer extends AbstractTransformer
public function getPriority()
{
// needs to run after ReturnRefTransformer and UseTransformer
// and before TypeAlternationTransformer
return -10;
}

Expand Down
1 change: 1 addition & 0 deletions tests/AutoReview/TransformerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public function provideTransformerPriorityCases()
[$transformers['square_brace'], $transformers['brace_class_instantiation']],
[$transformers['type_colon'], $transformers['named_argument']],
[$transformers['type_colon'], $transformers['nullable_type']],
[$transformers['type_colon'], $transformers['type_alternation']],
[$transformers['use'], $transformers['type_colon']],
];
}
Expand Down
6 changes: 4 additions & 2 deletions tests/Test/AbstractIntegrationTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,11 @@ public function testIntegration(IntegrationCase $case)
*/
public function provideIntegrationCases()
{
$fixturesDir = realpath(static::getFixturesDir());
$dir = static::getFixturesDir();
$fixturesDir = realpath($dir);

if (!is_dir($fixturesDir)) {
throw new \UnexpectedValueException(sprintf('Given fixture dir "%s" is not a directory.', $fixturesDir));
throw new \UnexpectedValueException(sprintf('Given fixture dir "%s" is not a directory.', \is_string($fixturesDir) ? $fixturesDir : $dir));
}

$factory = static::createIntegrationCaseFactory();
Expand Down
108 changes: 74 additions & 34 deletions tests/Tokenizer/Analyzer/ClassyAnalyzerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,14 @@
final class ClassyAnalyzerTest extends TestCase
{
/**
* @param string $source
* @param string $source
* @param array<int, bool> $expected
*
* @dataProvider provideIsClassyInvocationCases
*/
public function testIsClassyInvocation($source, array $expected)
{
$tokens = Tokens::fromCode($source);
$analyzer = new ClassyAnalyzer();

foreach ($expected as $index => $isClassy) {
static::assertSame($isClassy, $analyzer->isClassyInvocation($tokens, $index), 'Token at index '.$index.' should match the expected value.');
}
self::assertClassyInvocation($source, $expected);
}

public function provideIsClassyInvocationCases()
Expand Down Expand Up @@ -109,53 +105,52 @@ public function provideIsClassyInvocationCases()
}

/**
* @param string $source
* @param string $source
* @param array<int, bool> $expected
*
* @dataProvider provideIsClassyInvocation70Cases
* @requires PHP 7.0
*/
public function testIsClassyInvocation70($source, array $expected)
{
$tokens = Tokens::fromCode($source);
$analyzer = new ClassyAnalyzer();

foreach ($expected as $index => $isClassy) {
static::assertSame($isClassy, $analyzer->isClassyInvocation($tokens, $index), 'Token at index '.$index.' should match the expected value.');
}
self::assertClassyInvocation($source, $expected);
}

public function provideIsClassyInvocation70Cases()
{
return [
[
'<?php function foo(int $foo, string &$bar): self {}',
[3 => false, 5 => false, 10 => false, 17 => false],
],
[
'<?php function foo(): Foo {}',
[3 => false, 8 => true],
],
[
'<?php function foo(): \Foo {}',
[3 => false, 9 => true],
],
yield [
'<?php function foo(int $foo, string &$bar): self {}',
[3 => false, 5 => false, 10 => false, 17 => false],
];

yield [
'<?php function foo(): Foo {}',
[3 => false, 8 => true],
];

yield [
'<?php function foo(): \Foo {}',
[3 => false, 9 => true],
];

foreach (['bool', 'float', 'int', 'parent', 'self', 'string', 'void'] as $returnType) {
yield [
sprintf('<?php function foo(): %s {}', $returnType),
[3 => false, 8 => false],
];
}
}

/**
* @param string $source
* @param string $source
* @param array<int, bool> $expected
*
* @dataProvider provideIsClassyInvocation71Cases
* @requires PHP 7.1
*/
public function testIsClassyInvocation71($source, array $expected)
{
$tokens = Tokens::fromCode($source);
$analyzer = new ClassyAnalyzer();

foreach ($expected as $index => $isClassy) {
static::assertSame($isClassy, $analyzer->isClassyInvocation($tokens, $index), 'Token at index '.$index.' should match the expected value.');
}
self::assertClassyInvocation($source, $expected);
}

public function provideIsClassyInvocation71Cases()
Expand All @@ -179,4 +174,49 @@ public function provideIsClassyInvocation71Cases()
],
];
}

/**
* @param string $source
* @param array<int, bool> $expected
*
* @dataProvider provideIsClassyInvocation80Cases
* @requires PHP 8.0
*/
public function testIsClassyInvocation80($source, array $expected)
{
self::assertClassyInvocation($source, $expected);
}

public function provideIsClassyInvocation80Cases()
{
yield [
'<?php function foo(): \Foo|int {}',
[3 => false, 9 => true, 11 => false],
];

yield [
'<?php function foo(): \Foo|A|int {}',
[3 => false, 9 => true, 11 => true, 13 => false],
];

yield [
'<?php function foo(): int|A|NULL {}',
[3 => false, 8 => false, 10 => true, 12 => false],
];

yield [
'<?php function foo(): int|A|false {}',
[3 => false, 8 => false, 10 => true, 12 => false],
];
}

private static function assertClassyInvocation($source, array $expected)
{
$tokens = Tokens::fromCode($source);
$analyzer = new ClassyAnalyzer();

foreach ($expected as $index => $isClassy) {
static::assertSame($isClassy, $analyzer->isClassyInvocation($tokens, $index), sprintf('Token at index %d should match the expected value "%s".', $index, true === $isClassy ? 'true' : 'false'));
}
}
}
Loading