From 51761af97432e694cf1c40910bde4aa62c6cd603 Mon Sep 17 00:00:00 2001 From: RJ Garcia Date: Wed, 22 Aug 2018 02:49:35 -0700 Subject: [PATCH] Parse Group Use Statements - Updated namespace parser to handle grouped namespaces Signed-off-by: RJ Garcia --- src/Types/ContextFactory.php | 118 +++++++++++++++++++----- tests/unit/Types/ContextFactoryTest.php | 13 ++- 2 files changed, 106 insertions(+), 25 deletions(-) diff --git a/src/Types/ContextFactory.php b/src/Types/ContextFactory.php index 92621f6..54f3b73 100644 --- a/src/Types/ContextFactory.php +++ b/src/Types/ContextFactory.php @@ -193,8 +193,7 @@ private function parseUseStatement(\ArrayIterator $tokens) while ($continue) { $this->skipToNextStringOrNamespaceSeparator($tokens); - list($alias, $fqnn) = $this->extractUseStatement($tokens); - $uses[$alias] = $fqnn; + $uses = array_merge($uses, $this->extractUseStatements($tokens)); if ($tokens->current()[0] === self::T_LITERAL_END_OF_USE) { $continue = false; } @@ -215,38 +214,113 @@ private function skipToNextStringOrNamespaceSeparator(\ArrayIterator $tokens) /** * Deduce the namespace name and alias of an import when we are at the T_USE token or have not reached the end of - * a USE statement yet. + * a USE statement yet. This will return a key/value array of the alias => namespace. * * @return array */ - private function extractUseStatement(\ArrayIterator $tokens) + private function extractUseStatements(\ArrayIterator $tokens) { - $result = ['']; - while ($tokens->valid() - && ($tokens->current()[0] !== self::T_LITERAL_USE_SEPARATOR) - && ($tokens->current()[0] !== self::T_LITERAL_END_OF_USE) - ) { - if ($tokens->current()[0] === T_AS) { - $result[] = ''; + $extractedUseStatements = []; + $groupedNs = ''; + $currentNs = ''; + $currentAlias = null; + $state = "start"; + + $i = 0; + while ($tokens->valid()) { + $i += 1; + $currentToken = $tokens->current(); + $tokenId = is_string($currentToken) ? $currentToken : $currentToken[0]; + $tokenValue = is_string($currentToken) ? null : $currentToken[1]; + switch ($state) { + case "start": + switch ($tokenId) { + case T_STRING: + case T_NS_SEPARATOR: + $currentNs .= $tokenValue; + break; + case T_CURLY_OPEN: + case '{': + $state = 'grouped'; + $groupedNs = $currentNs; + break; + case T_AS: + $state = 'start-alias'; + break; + case self::T_LITERAL_USE_SEPARATOR: + case self::T_LITERAL_END_OF_USE: + $state = 'end'; + break; + default: + break; + } + break; + case "start-alias": + switch ($tokenId) { + case T_STRING: + $currentAlias .= $tokenValue; + break; + case self::T_LITERAL_USE_SEPARATOR: + case self::T_LITERAL_END_OF_USE: + $state = 'end'; + break; + default: + break; + } + break; + case "grouped": + switch ($tokenId) { + case T_STRING: + case T_NS_SEPARATOR: + $currentNs .= $tokenValue; + break; + case T_AS: + $state = 'grouped-alias'; + break; + case self::T_LITERAL_USE_SEPARATOR: + $state = 'grouped'; + $extractedUseStatements[$currentAlias ?: $currentNs] = $currentNs; + $currentNs = $groupedNs; + $currentAlias = null; + break; + case self::T_LITERAL_END_OF_USE: + $state = 'end'; + break; + default: + break; + } + break; + case "grouped-alias": + switch ($tokenId) { + case T_STRING: + $currentAlias .= $tokenValue; + break; + case self::T_LITERAL_USE_SEPARATOR: + $state = 'grouped'; + $extractedUseStatements[$currentAlias ?: $currentNs] = $currentNs; + $currentNs = $groupedNs; + $currentAlias = null; + break; + case self::T_LITERAL_END_OF_USE: + $state = 'end'; + break; + default: + break; + } } - if ($tokens->current()[0] === T_STRING || $tokens->current()[0] === T_NS_SEPARATOR) { - $result[count($result) - 1] .= $tokens->current()[1]; + if ($state == "end") { + break; } $tokens->next(); } - if (count($result) === 1) { - $backslashPos = strrpos($result[0], '\\'); - - if (false !== $backslashPos) { - $result[] = substr($result[0], $backslashPos + 1); - } else { - $result[] = $result[0]; - } + if ($groupedNs != $currentNs) { + $extractedUseStatements[$currentAlias ?: $currentNs] = $currentNs; } - return array_reverse($result); + + return $extractedUseStatements; } } diff --git a/tests/unit/Types/ContextFactoryTest.php b/tests/unit/Types/ContextFactoryTest.php index 55f0d52..33eb3f8 100644 --- a/tests/unit/Types/ContextFactoryTest.php +++ b/tests/unit/Types/ContextFactoryTest.php @@ -14,11 +14,14 @@ // Added imports on purpose as mock for the unit tests, please do not remove. use \ReflectionClass; - use Mockery as m; - use phpDocumentor; + use Mockery as m, phpDocumentor; use phpDocumentor\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlock\Tag; use PHPUnit\Framework\TestCase; // yes, the slash is part of the test + use PHPUnit\Framework\{ + Assert, + Exception as e + }; /** * @coversDefaultClass \phpDocumentor\Reflection\Types\ContextFactory @@ -53,6 +56,8 @@ public function testReadsAliasesFromClassReflection() 'Tag' => Tag::class, 'phpDocumentor' => 'phpDocumentor', 'TestCase' => TestCase::class, + 'Assert' => Assert::class, + 'e' => e::class, ReflectionClass::class => ReflectionClass::class, ]; $context = $fixture->createFromReflector(new ReflectionClass($this)); @@ -60,7 +65,9 @@ public function testReadsAliasesFromClassReflection() $actual = $context->getNamespaceAliases(); // sort so that order differences don't break it - $this->assertSame(sort($expected), sort($actual)); + sort($expected); + sort($actual); + $this->assertSame($expected, $actual); } /**