Skip to content

Commit

Permalink
fix: Handle FQCN properly with `leading_backslash_in_global_namespace…
Browse files Browse the repository at this point in the history
…` option enabled (#7654)

Co-authored-by: Greg Korba <greg@codito.dev>
  • Loading branch information
mvorisek and Wirone authored Jan 3, 2024
1 parent 70110ce commit 0061223
Show file tree
Hide file tree
Showing 7 changed files with 45 additions and 29 deletions.
2 changes: 1 addition & 1 deletion doc/rules/import/fully_qualified_strict_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ With configuration: ``['leading_backslash_in_global_namespace' => true]``.
try {
foo();
- } catch (\Exception|\Foo\A $e) {
+ } catch (Exception|A $e) {
+ } catch (\Exception|A $e) {
}
}
namespace Foo\Bar {
Expand Down
43 changes: 26 additions & 17 deletions src/Fixer/Import/FullyQualifiedStrictTypesFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,9 @@ private function fixExtendsImplements(Tokens $tokens, int $index, array &$uses,

while (true) {
if ($tokens[$index]->equalsAny([',', '{', [T_IMPLEMENTS]])) {
$this->shortenClassIfPossible($tokens, $extend, $uses, $namespaceName);
if ([] !== $extend['tokens']) {
$this->shortenClassIfPossible($tokens, $extend, $uses, $namespaceName);
}

if ($tokens[$index]->equalsAny($isExtends ? [[T_IMPLEMENTS], '{'] : ['{'])) {
break;
Expand Down Expand Up @@ -372,7 +374,7 @@ private function fixCatch(Tokens $tokens, int $index, array &$uses, string $name

while (true) {
if ($tokens[$index]->equalsAny([')', [T_VARIABLE], [CT::T_TYPE_ALTERNATION]])) {
if (0 === \count($caughtExceptionClass['tokens'])) {
if ([] === $caughtExceptionClass['tokens']) {
break;
}

Expand Down Expand Up @@ -407,7 +409,9 @@ private function fixPrevName(Tokens $tokens, int $index, array &$uses, string $n
$classConstantRef['content'] = $tokens[$index]->getContent().$classConstantRef['content'];
} else {
$classConstantRef['tokens'] = array_reverse($classConstantRef['tokens']);
$this->shortenClassIfPossible($tokens, $classConstantRef, $uses, $namespaceName);
if ([] !== $classConstantRef['tokens']) {
$this->shortenClassIfPossible($tokens, $classConstantRef, $uses, $namespaceName);
}

break;
}
Expand All @@ -428,7 +432,9 @@ private function fixNextName(Tokens $tokens, int $index, array &$uses, string $n
$classConstantRef['tokens'][] = $index;
$classConstantRef['content'] .= $tokens[$index]->getContent();
} else {
$this->shortenClassIfPossible($tokens, $classConstantRef, $uses, $namespaceName);
if ([] !== $classConstantRef['tokens']) {
$this->shortenClassIfPossible($tokens, $classConstantRef, $uses, $namespaceName);
}

break;
}
Expand All @@ -447,27 +453,30 @@ private function shortenClassIfPossible(Tokens $tokens, array $class, array &$us
$this->registerSymbolForImport('class', $longTypeContent, $uses, $namespaceName);
}

if (str_starts_with($longTypeContent, '\\')) {
$typeName = substr($longTypeContent, 1);
if (str_starts_with($longTypeContent, '\\') || '' === $namespaceName) {
$typeName = ltrim($longTypeContent, '\\');
$typeNameLower = strtolower($typeName);

if (isset($uses[$typeNameLower])) {
// if the type without leading "\" equals any of the full "uses" long names, it can be replaced with the short one
$this->replaceClassWithShort($tokens, $class, $uses[$typeNameLower]);
} elseif ('' === $namespaceName) {
if (true === $this->configuration['leading_backslash_in_global_namespace']) {
// if we are in the global namespace and the type is not imported the leading '\' can be removed
$inUses = false;

foreach ($uses as $useShortName) {
if (strtolower($useShortName) === $typeNameLower) {
$inUses = true;
$inUses = false;
foreach ($uses as $useShortName) {
if (strtolower($useShortName) === $typeNameLower) {
$inUses = true;

break;
}
break;
}
}

if (!$inUses) {
if (!$inUses) {
if (true === $this->configuration['leading_backslash_in_global_namespace']) {
if ($typeName === $longTypeContent) {
$tokens->insertAt($class['tokens'][0], new Token([T_NS_SEPARATOR, '\\']));
}
} else {
// if we are in the global namespace and the type is not imported the leading '\' can be removed
$this->replaceClassWithShort($tokens, $class, $typeName);
}
}
Expand All @@ -480,7 +489,7 @@ private function shortenClassIfPossible(Tokens $tokens, array $class, array &$us
$typeNameShort = substr($typeName, \strlen($namespaceName) + 1);
$this->replaceClassWithShort($tokens, $class, $typeNameShort);
}
} // else: no shorter type possible
}
}

/**
Expand Down
19 changes: 13 additions & 6 deletions tests/Fixer/Import/FullyQualifiedStrictTypesFixerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,13 @@ interface NakanoInterface extends \Foo\Bar\IzumiInterface, \Foo\Bar\A, \D\E, \C,
];

yield 'interface in global namespace with global extend' => [
'<?php interface Foo1 extends ArrayAccess2{}',
'<?php interface Foo1 extends \ArrayAccess2{}',
'<?php interface Foo1 extends ArrayAccess2{}',
['leading_backslash_in_global_namespace' => true],
];

yield 'interface in global namespace with multiple extend' => [
'<?php use B\Exception; interface Foo extends ArrayAccess, \Exception, Exception {}',
'<?php use B\Exception; interface Foo extends \ArrayAccess, \Exception, Exception {}',
'<?php use B\Exception; interface Foo extends \ArrayAccess, \Exception, \B\Exception {}',
['leading_backslash_in_global_namespace' => true],
];
Expand Down Expand Up @@ -277,9 +277,12 @@ class SomeClass extends \Foo\Bar\A implements \Foo\Bar\Izumi, A, \A\B, \Foo\Bar\
yield 'catch in multiple namespaces' => [
'<?php
namespace {
try{ foo(); } catch (Exception $z) {}
try{ foo(); } catch (A\X $z) {}
try{ foo(); } catch (B\Z $z) {}
try{ foo(); } catch (\Exception $z) {}
try{ foo(); } catch (\Exception $z) {}
try{ foo(); } catch (\A\X $z) {}
try{ foo(); } catch (\A\X $z) {}
try{ foo(); } catch (\B\Z $z) {}
try{ foo(); } catch (\B\Z $z) {}
}
namespace A {
try{ foo(); } catch (\Exception $z) {}
Expand All @@ -294,8 +297,11 @@ class SomeClass extends \Foo\Bar\A implements \Foo\Bar\Izumi, A, \A\B, \Foo\Bar\
',
'<?php
namespace {
try{ foo(); } catch (Exception $z) {}
try{ foo(); } catch (\Exception $z) {}
try{ foo(); } catch (A\X $z) {}
try{ foo(); } catch (\A\X $z) {}
try{ foo(); } catch (B\Z $z) {}
try{ foo(); } catch (\B\Z $z) {}
}
namespace A {
Expand Down Expand Up @@ -323,6 +329,7 @@ class SomeClass extends \Foo\Bar\A implements \Foo\Bar\Izumi, A, \A\B, \Foo\Bar\
];

yield 'new class not imported' => [
'<?php new A\B(); new A\B();',
'<?php new \A\B(); new A\B();',
];

Expand All @@ -342,7 +349,7 @@ class SomeClass extends \Foo\Bar\A implements \Foo\Bar\Izumi, A, \A\B, \Foo\Bar\
];

yield 'use trait complex' => [
'<?php use A\B; class Foo { use \A\C; use \D; use B { B::bar as baz; } };',
'<?php use A\B; class Foo { use A\C; use D; use B { B::bar as baz; } };',
'<?php use A\B; class Foo { use \A\C; use \D; use \A\B { \A\B::bar as baz; } };',
];

Expand Down
2 changes: 1 addition & 1 deletion tests/Fixtures/Integration/misc/PHP7_1.test
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function iterable_foo(iterable $foo): void
echo $str[-2];
} catch (ExceptionType1|ExceptionType2 $e) {
// Code to handle the exception
} catch (\Exception $e) {
} catch (Exception $e) {
// ...
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/Fixtures/Integration/misc/PHP7_3.test
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ is_countable($foo);
[$a, &$b] = $array; // `list_syntax` rule

// https://github.com/php/php-src/pull/2978 instanceof now allows literals as the first operand (the result is always false).
null instanceof \stdClass;
null instanceof stdClass;

// https://wiki.php.net/rfc/trailing-comma-function-calls Trailing commas in function and method calls are now allowed.
foo(
Expand All @@ -63,7 +63,7 @@ mb_strlen($str); // `native_function_casing` rule
$c = \get_class($d); // `native_function_invocation` rule
$a = rtrim($b); // `no_alias_functions` rule
$foo->bar($arg1, $arg2); // `no_spaces_inside_parenthesis` rule
final class MyClass extends \PHPUnit_Framework_TestCase
final class MyClass extends PHPUnit_Framework_TestCase
{
public function testFoo(): void
{
Expand Down
2 changes: 1 addition & 1 deletion tests/Fixtures/Integration/misc/PHP8_0.test
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class T_Mixed

// https://wiki.php.net/rfc/throw_expression
if (1) {
foo() ?? throw new \Exception();
foo() ?? throw new Exception();
$a = $condition && throw new Exception();
$callable = static fn () => throw new Exception();
$value = $falsableValue ?: throw new InvalidArgumentException();
Expand Down
2 changes: 1 addition & 1 deletion tests/Fixtures/Integration/misc/PHP8_2.test
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ trait WithConstants
// https://wiki.php.net/rfc/dnf_types
function generateSlug(null|(HasId&HasTitle) $post)
{
throw new \Exception('not implemented');
throw new Exception('not implemented');
}

--INPUT--
Expand Down

0 comments on commit 0061223

Please sign in to comment.