Skip to content

Commit

Permalink
TypeAlternationTransformer - add support for PHP8
Browse files Browse the repository at this point in the history
  • Loading branch information
SpacePossum committed Dec 31, 2020
1 parent 98dd819 commit fcacc62
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 59 deletions.
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

0 comments on commit fcacc62

Please sign in to comment.