Skip to content

Commit 5703f61

Browse files
committed
Parse Group Use Statements
- Updated namespace parser to handle grouped namespaces Signed-off-by: RJ Garcia <[email protected]>
1 parent f989f45 commit 5703f61

File tree

2 files changed

+107
-25
lines changed

2 files changed

+107
-25
lines changed

src/Types/ContextFactory.php

Lines changed: 97 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,7 @@ private function parseUseStatement(\ArrayIterator $tokens)
193193
while ($continue) {
194194
$this->skipToNextStringOrNamespaceSeparator($tokens);
195195

196-
list($alias, $fqnn) = $this->extractUseStatement($tokens);
197-
$uses[$alias] = $fqnn;
196+
$uses = array_merge($uses, $this->extractUseStatements($tokens));
198197
if ($tokens->current()[0] === self::T_LITERAL_END_OF_USE) {
199198
$continue = false;
200199
}
@@ -215,38 +214,114 @@ private function skipToNextStringOrNamespaceSeparator(\ArrayIterator $tokens)
215214

216215
/**
217216
* Deduce the namespace name and alias of an import when we are at the T_USE token or have not reached the end of
218-
* a USE statement yet.
217+
* a USE statement yet. This will return an array of tuples where the element is the full namespace and the second is an
218+
* optional alias.
219219
*
220220
* @return array
221221
*/
222-
private function extractUseStatement(\ArrayIterator $tokens)
222+
private function extractUseStatements(\ArrayIterator $tokens)
223223
{
224-
$result = [''];
225-
while ($tokens->valid()
226-
&& ($tokens->current()[0] !== self::T_LITERAL_USE_SEPARATOR)
227-
&& ($tokens->current()[0] !== self::T_LITERAL_END_OF_USE)
228-
) {
229-
if ($tokens->current()[0] === T_AS) {
230-
$result[] = '';
224+
$extractedUseStatements = [];
225+
$groupedNs = '';
226+
$currentNs = '';
227+
$currentAlias = null;
228+
$state = "start";
229+
230+
$i = 0;
231+
while ($tokens->valid()) {
232+
$i += 1;
233+
$currentToken = $tokens->current();
234+
$tokenId = is_string($currentToken) ? $currentToken : $currentToken[0];
235+
$tokenValue = is_string($currentToken) ? null : $currentToken[1];
236+
switch ($state) {
237+
case "start":
238+
switch ($tokenId) {
239+
case T_STRING:
240+
case T_NS_SEPARATOR:
241+
$currentNs .= $tokenValue;
242+
break;
243+
case T_CURLY_OPEN:
244+
case '{':
245+
$state = 'grouped';
246+
$groupedNs = $currentNs;
247+
break;
248+
case T_AS:
249+
$state = 'start-alias';
250+
break;
251+
case self::T_LITERAL_USE_SEPARATOR:
252+
case self::T_LITERAL_END_OF_USE:
253+
$state = 'end';
254+
break;
255+
default:
256+
break;
257+
}
258+
break;
259+
case "start-alias":
260+
switch ($tokenId) {
261+
case T_STRING:
262+
$currentAlias .= $tokenValue;
263+
break;
264+
case self::T_LITERAL_USE_SEPARATOR:
265+
case self::T_LITERAL_END_OF_USE:
266+
$state = 'end';
267+
break;
268+
default:
269+
break;
270+
}
271+
break;
272+
case "grouped":
273+
switch ($tokenId) {
274+
case T_STRING:
275+
case T_NS_SEPARATOR:
276+
$currentNs .= $tokenValue;
277+
break;
278+
case T_AS:
279+
$state = 'grouped-alias';
280+
break;
281+
case self::T_LITERAL_USE_SEPARATOR:
282+
$state = 'grouped';
283+
$extractedUseStatements[$currentAlias ?: $currentNs] = $currentNs;
284+
$currentNs = $groupedNs;
285+
$currentAlias = null;
286+
break;
287+
case self::T_LITERAL_END_OF_USE:
288+
$state = 'end';
289+
break;
290+
default:
291+
break;
292+
}
293+
break;
294+
case "grouped-alias":
295+
switch ($tokenId) {
296+
case T_STRING:
297+
$currentAlias .= $tokenValue;
298+
break;
299+
case self::T_LITERAL_USE_SEPARATOR:
300+
$state = 'grouped';
301+
$extractedUseStatements[$currentAlias ?: $currentNs] = $currentNs;
302+
$currentNs = $groupedNs;
303+
$currentAlias = null;
304+
break;
305+
case self::T_LITERAL_END_OF_USE:
306+
$state = 'end';
307+
break;
308+
default:
309+
break;
310+
}
231311
}
232312

233-
if ($tokens->current()[0] === T_STRING || $tokens->current()[0] === T_NS_SEPARATOR) {
234-
$result[count($result) - 1] .= $tokens->current()[1];
313+
if ($state == "end") {
314+
break;
235315
}
236316

237317
$tokens->next();
238318
}
239319

240-
if (count($result) === 1) {
241-
$backslashPos = strrpos($result[0], '\\');
242-
243-
if (false !== $backslashPos) {
244-
$result[] = substr($result[0], $backslashPos + 1);
245-
} else {
246-
$result[] = $result[0];
247-
}
320+
if ($groupedNs != $currentNs) {
321+
$extractedUseStatements[$currentAlias ?: $currentNs] = $currentNs;
248322
}
249323

250-
return array_reverse($result);
324+
325+
return $extractedUseStatements;
251326
}
252327
}

tests/unit/Types/ContextFactoryTest.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@
1414

1515
// Added imports on purpose as mock for the unit tests, please do not remove.
1616
use \ReflectionClass;
17-
use Mockery as m;
18-
use phpDocumentor;
17+
use Mockery as m, phpDocumentor;
1918
use phpDocumentor\Reflection\DocBlock;
2019
use phpDocumentor\Reflection\DocBlock\Tag;
2120
use PHPUnit\Framework\TestCase; // yes, the slash is part of the test
21+
use PHPUnit\Framework\{
22+
Assert,
23+
Exception as e
24+
};
2225

2326
/**
2427
* @coversDefaultClass \phpDocumentor\Reflection\Types\ContextFactory
@@ -53,14 +56,18 @@ public function testReadsAliasesFromClassReflection()
5356
'Tag' => Tag::class,
5457
'phpDocumentor' => 'phpDocumentor',
5558
'TestCase' => TestCase::class,
59+
'Assert' => Assert::class,
60+
'e' => e::class,
5661
ReflectionClass::class => ReflectionClass::class,
5762
];
5863
$context = $fixture->createFromReflector(new ReflectionClass($this));
5964

6065
$actual = $context->getNamespaceAliases();
6166

6267
// sort so that order differences don't break it
63-
$this->assertSame(sort($expected), sort($actual));
68+
sort($expected);
69+
sort($actual);
70+
$this->assertSame($expected, $actual);
6471
}
6572

6673
/**

0 commit comments

Comments
 (0)