Skip to content

Commit

Permalink
bug #5741 Fix constant invocation detection cases (kubawerlos)
Browse files Browse the repository at this point in the history
This PR was merged into the 2.19 branch.

Discussion
----------

Fix constant invocation detection cases

Replaces #5685.

/cc `@kubawerlos` I kept your authorship if you don't mind.

Commits
-------

d52875d Fix constant invocation detection cases
  • Loading branch information
keradus committed Jun 4, 2021
2 parents 15ddc20 + d52875d commit 7c4728c
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 55 deletions.
19 changes: 19 additions & 0 deletions src/Tokenizer/TokensAnalyzer.php
Expand Up @@ -383,6 +383,14 @@ public function isConstantInvocation($index)
}
}

// check for attribute: `#[Foo]`
if (
\defined('T_ATTRIBUTE') // @TODO: drop condition when PHP 8.0+ is required
&& $this->tokens[$prevIndex]->isGivenKind(T_ATTRIBUTE)
) {
return false;
}

// check for goto label
if ($this->tokens[$nextIndex]->equals(':')) {
if (null === $this->gotoLabelAnalyzer) {
Expand All @@ -394,6 +402,17 @@ public function isConstantInvocation($index)
}
}

// check for non-capturing catches
while ($this->tokens[$prevIndex]->isGivenKind([CT::T_TYPE_ALTERNATION, T_STRING])) {
$prevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex);
}
if ($this->tokens[$prevIndex]->equals('(')) {
$prevPrevIndex = $this->tokens->getPrevMeaningfulToken($prevIndex);
if ($this->tokens[$prevPrevIndex]->isGivenKind(T_CATCH)) {
return false;
}
}

return true;
}

Expand Down
14 changes: 14 additions & 0 deletions tests/Fixer/ConstantNotation/NativeConstantInvocationFixerTest.php
Expand Up @@ -553,4 +553,18 @@ public function testFixPrePHP80()
'
);
}

/**
* @requires PHP 8.0
*/
public function testFixPhp80()
{
$this->fixer->configure(['strict' => true]);
$this->doTest(
'<?php
try {
} catch (\Exception) {
}'
);
}
}
149 changes: 94 additions & 55 deletions tests/Tokenizer/TokensAnalyzerTest.php
Expand Up @@ -769,11 +769,7 @@ public function testIsLambdaInvalid()
*/
public function testIsConstantInvocation($source, array $expected)
{
$tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));

foreach ($expected as $index => $expectedValue) {
static::assertSame($expectedValue, $tokensAnalyzer->isConstantInvocation($index), 'Token at index '.$index.' should match the expected value.');
}
$this->doIsConstantInvocationTest($source, $expected);
}

public function provideIsConstantInvocationCases()
Expand Down Expand Up @@ -813,19 +809,19 @@ public function provideIsConstantInvocationCases()
],
[
'<?php func(FOO, Bar\BAZ);',
[3 => true, 8 => true],
[1 => false, 3 => true, 6 => false, 8 => true],
],
[
'<?php if (FOO && BAR) {}',
[4 => true, 8 => true],
],
[
'<?php return FOO * X\Y\BAR;',
[3 => true, 11 => true],
[3 => true, 7 => false, 9 => false, 11 => true],
],
[
'<?php function x() { yield FOO; yield FOO => BAR; }',
[11 => true, 16 => true, 20 => true],
[3 => false, 11 => true, 16 => true, 20 => true],
],
[
'<?php switch ($a) { case FOO: break; }',
Expand All @@ -845,7 +841,7 @@ public function provideIsConstantInvocationCases()
],
[
'<?php namespace X; const FOO = 1;',
[8 => false],
[3 => false, 8 => false],
],
[
'<?php class FOO {}',
Expand All @@ -861,43 +857,43 @@ public function provideIsConstantInvocationCases()
],
[
'<?php class x extends FOO {}',
[7 => false],
[3 => false, 7 => false],
],
[
'<?php class x implements FOO {}',
[7 => false],
[3 => false, 7 => false],
],
[
'<?php class x implements FOO, BAR, BAZ {}',
[7 => false, 10 => false, 13 => false],
[3 => false, 7 => false, 10 => false, 13 => false],
],
[
'<?php class x { const FOO = 1; }',
[9 => false],
[3 => false, 9 => false],
],
[
'<?php class x { use FOO; }',
[9 => false],
[3 => false, 9 => false],
],
[
'<?php class x { use FOO, BAR { FOO::BAZ insteadof BAR; } }',
[9 => false, 12 => false, 16 => false, 18 => false, 22 => false],
[3 => false, 9 => false, 12 => false, 16 => false, 18 => false, 22 => false],
],
[
'<?php function x (FOO $foo, BAR &$bar, BAZ ...$baz) {}',
[6 => false, 11 => false, 17 => false],
[3 => false, 6 => false, 11 => false, 17 => false],
],
[
'<?php FOO();',
[1 => false],
],
[
'<?php FOO::x();',
[1 => false],
[1 => false, 3 => false],
],
[
'<?php x::FOO();',
[3 => false],
[1 => false, 3 => false],
],
[
'<?php $foo instanceof FOO;',
Expand All @@ -921,11 +917,11 @@ public function provideIsConstantInvocationCases()
],
[
'<?php foo(E_USER_DEPRECATED | E_DEPRECATED);',
[3 => true, 7 => true],
[1 => false, 3 => true, 7 => true],
],
[
'<?php interface Foo extends Bar, Baz, Qux {}',
[7 => false, 10 => false, 13 => false],
[3 => false, 7 => false, 10 => false, 13 => false],
],
[
'<?php use Foo\Bar, Foo\Baz, Foo\Qux;',
Expand All @@ -942,23 +938,19 @@ public function provideIsConstantInvocationCases()
*/
public function testIsConstantInvocation70($source, array $expected)
{
$tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));

foreach ($expected as $index => $expectedValue) {
static::assertSame($expectedValue, $tokensAnalyzer->isConstantInvocation($index), 'Token at index '.$index.' should match the expected value.');
}
$this->doIsConstantInvocationTest($source, $expected);
}

public function provideIsConstantInvocation70Cases()
{
return [
[
'<?php function x(): FOO {}',
[8 => false],
[3 => false, 8 => false],
],
[
'<?php use X\Y\{FOO, BAR as BAR2, BAZ};',
[8 => false, 11 => false, 15 => false, 18 => false],
[3 => false, 5 => false, 8 => false, 11 => false, 15 => false, 18 => false],
],
];
}
Expand All @@ -971,47 +963,87 @@ public function provideIsConstantInvocation70Cases()
*/
public function testIsConstantInvocation71($source, array $expected)
{
$tokensAnalyzer = new TokensAnalyzer(Tokens::fromCode($source));

foreach ($expected as $index => $expectedValue) {
static::assertSame($expectedValue, $tokensAnalyzer->isConstantInvocation($index), 'Token at index '.$index.' should match the expected value.');
}
$this->doIsConstantInvocationTest($source, $expected);
}

public function provideIsConstantInvocation71Cases()
{
return [
[
'<?php function x(?FOO $foo) {}',
[6 => false],
[3 => false, 6 => false],
],
[
'<?php function x(): ?FOO {}',
[9 => false],
[3 => false, 9 => false],
],
[
'<?php try {} catch (FOO|BAR|BAZ $e) {}',
[9 => false, 11 => false, 13 => false],
],
[
'<?php interface Foo { public function bar(): Baz; }',
[16 => false],
[3 => false, 11 => false, 16 => false],
],
[
'<?php interface Foo { public function bar(): \Baz; }',
[17 => false],
[3 => false, 11 => false, 17 => false],
],
[
'<?php interface Foo { public function bar(): ?Baz; }',
[17 => false],
[3 => false, 11 => false, 17 => false],
],
[
'<?php interface Foo { public function bar(): ?\Baz; }',
[18 => false],
[3 => false, 11 => false, 18 => false],
],
];
}

/**
* @param string $source
*
* @dataProvider provideIsConstantInvocationPhp80Cases
* @requires PHP 8.0
*/
public function testIsConstantInvocationPhp80($source, array $expected)
{
$this->doIsConstantInvocationTest($source, $expected);
}

public function provideIsConstantInvocationPhp80Cases()
{
yield [
'<?php $a?->b?->c;',
[3 => false, 5 => false],
];

yield [
'<?php try {} catch (Exception) {}',
[9 => false],
];

yield [
'<?php try {} catch (\Exception) {}',
[10 => false],
];

yield [
'<?php try {} catch (Foo | Bar) {}',
[9 => false, 13 => false],
];

yield [
'<?php #[Foo] function foo() {}',
[2 => false, 7 => false],
];

yield [
'<?php #[Foo()] function foo() {}',
[2 => false, 9 => false],
];
}

public function testIsConstantInvocationInvalid()
{
$this->expectException(\LogicException::class);
Expand All @@ -1021,23 +1053,6 @@ public function testIsConstantInvocationInvalid()
$tokensAnalyzer->isConstantInvocation(0);
}

/**
* @requires PHP 8.0
*/
public function testIsConstantInvocationForNullSafeObjectOperator()
{
$tokens = Tokens::fromCode('<?php $a?->b?->c;');

$tokensAnalyzer = new TokensAnalyzer($tokens);

foreach ($tokens as $index => $token) {
if (!$token->isGivenKind(T_STRING)) {
continue;
}
static::assertFalse($tokensAnalyzer->isConstantInvocation($index));
}
}

/**
* @param string $source
*
Expand Down Expand Up @@ -2071,4 +2086,28 @@ public function provideIsSuperGlobalCases()

return $cases;
}

/**
* @param string $source
*/
private function doIsConstantInvocationTest($source, array $expected)
{
$tokens = Tokens::fromCode($source);

static::assertCount(
$tokens->countTokenKind(T_STRING),
$expected,
'All T_STRING tokens must be tested'
);

$tokensAnalyzer = new TokensAnalyzer($tokens);

foreach ($expected as $index => $expectedValue) {
static::assertSame(
$expectedValue,
$tokensAnalyzer->isConstantInvocation($index),
sprintf('Token at index '.$index.' should match the expected value (%s).', $expectedValue ? 'true' : 'false')
);
}
}
}

0 comments on commit 7c4728c

Please sign in to comment.