From 19dde1363e8bd83e98bc2320ecc42b61e379255d Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 13 Jun 2017 19:51:22 +0200 Subject: [PATCH] Treat special names correctly in getShortName() Also change the API to accept a string rather than a FullyQualified name instance, as this is not appropriate for symbols like "self". --- lib/PhpParser/NameContext.php | 44 ++++++++++++++++++++---------- test/PhpParser/NameContextTest.php | 13 +++++++-- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/lib/PhpParser/NameContext.php b/lib/PhpParser/NameContext.php index ce9a9a4..5064607 100644 --- a/lib/PhpParser/NameContext.php +++ b/lib/PhpParser/NameContext.php @@ -148,21 +148,27 @@ class NameContext { } /** - * Get possible ways of writing a fully qualified name (e.g., by making use of aliases) + * Get possible ways of writing a fully qualified name (e.g., by making use of aliases). * - * @param FullyQualified $name Fully-qualified name - * @param int $type One of Stmt\Use_::TYPE_* + * @param string $name Fully-qualified name (without leading namespace separator) + * @param int $type One of Stmt\Use_::TYPE_* * * @return Name[] Possible representations of the name */ - public function getPossibleNames(FullyQualified $name, int $type) : array { - $nameStr = (string) $name; + public function getPossibleNames(string $name, int $type) : array { $lcName = strtolower($name); - // Collect possible ways to write this name, starting with the fully-qualified name - $possibleNames = [$name]; + if ($type === Stmt\Use_::TYPE_NORMAL) { + // self, parent and static must always be unqualified + if ($lcName === "self" || $lcName === "parent" || $lcName === "static") { + return [new Name($name)]; + } + } - if (null !== $nsRelativeName = $this->getNamespaceRelativeName($name, $nameStr, $lcName)) { + // Collect possible ways to write this name, starting with the fully-qualified name + $possibleNames = [new FullyQualified($name)]; + + if (null !== $nsRelativeName = $this->getNamespaceRelativeName($name, $lcName, $type)) { // Make sure there is no alias that makes the normally namespace-relative name // into something else if (null === $this->resolveAlias($nsRelativeName, $type)) { @@ -182,7 +188,7 @@ class NameContext { foreach ($this->origAliases[$type] as $alias => $orig) { if ($type === Stmt\Use_::TYPE_CONSTANT) { // Constants are are complicated-sensitive - if ($this->normalizeConstName($orig) === $this->normalizeConstName($nameStr)) { + if ($this->normalizeConstName($orig) === $this->normalizeConstName($name)) { $possibleNames[] = new Name($alias); } } else { @@ -199,12 +205,12 @@ class NameContext { /** * Get shortest representation of this fully-qualified name. * - * @param FullyQualified $name Fully-qualified name to shorten - * @param int $type One of Stmt\Use_::TYPE_* + * @param string $name Fully-qualified name (without leading namespace separator) + * @param int $type One of Stmt\Use_::TYPE_* * * @return Name Shortest representation */ - public function getShortName(Name\FullyQualified $name, int $type) : Name { + public function getShortName(string $name, int $type) : Name { $possibleNames = $this->getPossibleNames($name, $type); // Find shortest name @@ -244,20 +250,28 @@ class NameContext { return null; } - private function getNamespaceRelativeName(Name\FullyQualified $name, $nameStr, $lcName) { + private function getNamespaceRelativeName(string $name, string $lcName, int $type) { if (null === $this->namespace) { return new Name($name); } + if ($type === Stmt\Use_::TYPE_CONSTANT) { + // The constants true/false/null always resolve to the global symbols, even inside a + // namespace, so they may be used without qualification + if ($lcName === "true" || $lcName === "false" || $lcName === "null") { + return new Name($name); + } + } + $namespacePrefix = strtolower($this->namespace . '\\'); if (0 === strpos($lcName, $namespacePrefix)) { - return new Name(substr($nameStr, strlen($namespacePrefix))); + return new Name(substr($name, strlen($namespacePrefix))); } return null; } - private function normalizeConstName($name) { + private function normalizeConstName(string $name) { $nsSep = strrpos($name, '\\'); if (false === $nsSep) { return $name; diff --git a/test/PhpParser/NameContextTest.php b/test/PhpParser/NameContextTest.php index d38e6f3..f275946 100644 --- a/test/PhpParser/NameContextTest.php +++ b/test/PhpParser/NameContextTest.php @@ -18,8 +18,7 @@ class NameContextTest extends TestCase { $nameContext->addAlias(new Name('Foo\fn'), 'fn', Use_::TYPE_FUNCTION); $nameContext->addAlias(new Name('Foo\CN'), 'CN', Use_::TYPE_CONSTANT); - $fqName = new Name\FullyQualified($name); - $possibleNames = $nameContext->getPossibleNames($fqName, $type); + $possibleNames = $nameContext->getPossibleNames($name, $type); $possibleNames = array_map(function (Name $name) { return $name->toCodeString(); }, $possibleNames); @@ -30,7 +29,7 @@ class NameContextTest extends TestCase { $expectedShortName = $expectedPossibleNames[count($expectedPossibleNames) - 1]; $this->assertSame( $expectedShortName, - $nameContext->getShortName($fqName, $type)->toCodeString() + $nameContext->getShortName($name, $type)->toCodeString() ); } @@ -53,6 +52,14 @@ class NameContextTest extends TestCase { [Use_::TYPE_CONSTANT, 'Foo\CN', ['\Foo\CN', 'Foo\CN', 'CN']], [Use_::TYPE_CONSTANT, 'foo\CN', ['\foo\CN', 'Foo\CN', 'CN']], [Use_::TYPE_CONSTANT, 'foo\cn', ['\foo\cn', 'Foo\cn']], + // self/parent/static must not be fully qualified + [Use_::TYPE_NORMAL, 'self', ['self']], + [Use_::TYPE_NORMAL, 'parent', ['parent']], + [Use_::TYPE_NORMAL, 'static', ['static']], + // true/false/null do not need to be fully qualified, even in namespaces + [Use_::TYPE_CONSTANT, 'true', ['\true', 'true']], + [Use_::TYPE_CONSTANT, 'false', ['\false', 'false']], + [Use_::TYPE_CONSTANT, 'null', ['\null', 'null']], ]; } } \ No newline at end of file