Skip to content

Commit 50f391a

Browse files
authored
Merge pull request #101 from Big-Shark/patch-1
Add support "&" separator
2 parents d6f6df1 + 3b13265 commit 50f391a

File tree

4 files changed

+150
-41
lines changed

4 files changed

+150
-41
lines changed

src/TypeResolver.php

Lines changed: 22 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use ArrayIterator;
1717
use InvalidArgumentException;
1818
use phpDocumentor\Reflection\Types\Array_;
19+
use phpDocumentor\Reflection\Types\Expression_;
1920
use phpDocumentor\Reflection\Types\ClassString;
2021
use phpDocumentor\Reflection\Types\Collection;
2122
use phpDocumentor\Reflection\Types\Compound;
@@ -134,9 +135,9 @@ public function resolve(string $type, ?Context $context = null) : Type
134135
$context = new Context('');
135136
}
136137

137-
// split the type string into tokens `|`, `?`, `<`, `>`, `,`, `(`, `)[]`, '<', '>' and type names
138+
// split the type string into tokens `|`, `?`, `<`, `>`, `,`, `(`, `)`, `[]`, '<', '>' and type names
138139
$tokens = preg_split(
139-
'/(\\||\\?|<|>|, ?|\\(|\\)(?:\\[\\])+)/',
140+
'/(\\||\\?|<|>|&|, ?|\\(|\\)|\\[\\]+)/',
140141
$type,
141142
-1,
142143
PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE
@@ -163,6 +164,7 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
163164
{
164165
$types = [];
165166
$token = '';
167+
$compoundToken = '|';
166168
while ($tokens->valid()) {
167169
$token = $tokens->current();
168170
if ($token === null) {
@@ -171,7 +173,7 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
171173
);
172174
}
173175

174-
if ($token === '|') {
176+
if ($token === '|' || $token === '&') {
175177
if (count($types) === 0) {
176178
throw new RuntimeException(
177179
'A type is missing before a type separator'
@@ -189,6 +191,7 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
189191
);
190192
}
191193

194+
$compoundToken = $token;
192195
$tokens->next();
193196
} elseif ($token === '?') {
194197
if (!in_array($parserContext, [
@@ -209,22 +212,16 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
209212
$tokens->next();
210213
$type = $this->parseTypes($tokens, $context, self::PARSER_IN_ARRAY_EXPRESSION);
211214

212-
$resolvedType = new Array_($type);
213-
214215
$token = $tokens->current();
215-
// Someone did not properly close their array expression ..
216-
if ($token === null) {
216+
if ($token === null) { // Someone did not properly close their array expression ..
217217
break;
218218
}
219219

220-
// we generate arrays corresponding to the number of '[]' after the ')'
221-
$numberOfArrays = (strlen($token) - 1) / 2;
222-
for ($i = 0; $i < $numberOfArrays - 1; ++$i) {
223-
$resolvedType = new Array_($resolvedType);
224-
}
220+
$tokens->next();
221+
222+
$resolvedType = new Expression_($type);
225223

226224
$types[] = $resolvedType;
227-
$tokens->next();
228225
} elseif ($parserContext === self::PARSER_IN_ARRAY_EXPRESSION && $token[0] === ')') {
229226
break;
230227
} elseif ($token === '<') {
@@ -248,6 +245,16 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
248245
&& ($token === '>' || trim($token) === ',')
249246
) {
250247
break;
248+
} elseif ($token === self::OPERATOR_ARRAY) {
249+
end($types);
250+
$last = key($types);
251+
$lastItem = $types[$last];
252+
if ($lastItem instanceof Expression_) {
253+
$lastItem = $lastItem->getValueType();
254+
}
255+
$types[$last] = new Array_($lastItem);
256+
257+
$tokens->next();
251258
} else {
252259
$type = $this->resolveSingleType($token, $context);
253260
$tokens->next();
@@ -259,7 +266,7 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
259266
}
260267
}
261268

262-
if ($token === '|') {
269+
if ($token === '|' || $token === '&') {
263270
throw new RuntimeException(
264271
'A type is missing after a type separator'
265272
);
@@ -287,7 +294,7 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
287294
return $types[0];
288295
}
289296

290-
return new Compound($types);
297+
return new Compound($types, $compoundToken);
291298
}
292299

293300
/**
@@ -304,8 +311,6 @@ private function resolveSingleType(string $type, Context $context) : object
304311
switch (true) {
305312
case $this->isKeyword($type):
306313
return $this->resolveKeyword($type);
307-
case $this->isTypedArray($type):
308-
return $this->resolveTypedArray($type, $context);
309314
case $this->isFqsen($type):
310315
return $this->resolveTypedObject($type);
311316
case $this->isPartialStructuralElementName($type):
@@ -345,18 +350,6 @@ public function addKeyword(string $keyword, string $typeClassName) : void
345350
$this->keywords[$keyword] = $typeClassName;
346351
}
347352

348-
/**
349-
* Detects whether the given type represents an array.
350-
*
351-
* @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
352-
*
353-
* @psalm-pure
354-
*/
355-
private function isTypedArray(string $type) : bool
356-
{
357-
return substr($type, -2) === self::OPERATOR_ARRAY;
358-
}
359-
360353
/**
361354
* Detects whether the given type represents a PHPDoc keyword.
362355
*
@@ -391,16 +384,6 @@ private function isFqsen(string $type) : bool
391384
return strpos($type, self::OPERATOR_NAMESPACE) === 0;
392385
}
393386

394-
/**
395-
* Resolves the given typed array string (i.e. `string[]`) into an Array object with the right types set.
396-
*
397-
* @psalm-pure
398-
*/
399-
private function resolveTypedArray(string $type, Context $context) : Array_
400-
{
401-
return new Array_($this->resolveSingleType(substr($type, 0, -2), $context));
402-
}
403-
404387
/**
405388
* Resolves the given keyword (such as `string`) into a Type object representing that keyword.
406389
*

src/Types/Compound.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,23 @@ final class Compound implements Type, IteratorAggregate
3737
*/
3838
private $types = [];
3939

40+
/** @var string */
41+
private $token;
42+
4043
/**
4144
* Initializes a compound type (i.e. `string|int`) and tests if the provided types all implement the Type interface.
4245
*
4346
* @param Type[] $types
4447
*
4548
* @phpstan-param list<Type> $types
4649
*/
47-
public function __construct(array $types)
50+
public function __construct(array $types, string $token = '|')
4851
{
4952
foreach ($types as $type) {
5053
$this->add($type);
5154
}
55+
56+
$this->token = $token;
5257
}
5358

5459
/**
@@ -91,7 +96,7 @@ public function contains(Type $type) : bool
9196
*/
9297
public function __toString() : string
9398
{
94-
return implode('|', $this->types);
99+
return implode($this->token, $this->types);
95100
}
96101

97102
/**

src/Types/Expression_.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of phpDocumentor.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* @link http://phpdoc.org
12+
*/
13+
namespace phpDocumentor\Reflection\Types;
14+
15+
use phpDocumentor\Reflection\Type;
16+
17+
/**
18+
* Represents an expression type as described in the PSR-5, the PHPDoc Standard.
19+
*/
20+
final class Expression_ implements Type
21+
{
22+
/** @var Type */
23+
protected $valueType;
24+
25+
/**
26+
* Initializes this representation of an array with the given Type.
27+
*/
28+
public function __construct(Type $valueType)
29+
{
30+
$this->valueType = $valueType;
31+
}
32+
33+
/**
34+
* Returns the value for the keys of this array.
35+
*/
36+
public function getValueType() : Type
37+
{
38+
return $this->valueType;
39+
}
40+
41+
/**
42+
* Returns a rendered output of the Type as it would be used in a DocBlock.
43+
*/
44+
public function __toString() : string
45+
{
46+
return '(' . $this->valueType . ')';
47+
}
48+
}

tests/unit/TypeResolverTest.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515

1616
use Mockery as m;
1717
use phpDocumentor\Reflection\Types\Array_;
18+
use phpDocumentor\Reflection\Types\Expression_;
1819
use phpDocumentor\Reflection\Types\Boolean;
1920
use phpDocumentor\Reflection\Types\ClassString;
2021
use phpDocumentor\Reflection\Types\Compound;
2122
use phpDocumentor\Reflection\Types\Context;
2223
use phpDocumentor\Reflection\Types\Iterable_;
24+
use phpDocumentor\Reflection\Types\Null_;
2325
use phpDocumentor\Reflection\Types\Nullable;
2426
use phpDocumentor\Reflection\Types\Object_;
2527
use phpDocumentor\Reflection\Types\String_;
@@ -251,6 +253,77 @@ public function testResolvingCompoundTypes() : void
251253
$this->assertInstanceOf(Fqsen::class, $secondType->getFqsen());
252254
}
253255

256+
/**
257+
* @uses \phpDocumentor\Reflection\Types\Context
258+
* @uses \phpDocumentor\Reflection\Types\Compound
259+
* @uses \phpDocumentor\Reflection\Types\String_
260+
* @uses \phpDocumentor\Reflection\Types\Object_
261+
* @uses \phpDocumentor\Reflection\Fqsen
262+
* @uses \phpDocumentor\Reflection\FqsenResolver
263+
*
264+
* @covers ::__construct
265+
* @covers ::resolve
266+
* @covers ::<private>
267+
*/
268+
public function testResolvingAmpersandCompoundTypes() : void
269+
{
270+
$fixture = new TypeResolver();
271+
272+
$resolvedType = $fixture->resolve('Reflection\DocBlock&\PHPUnit\Framework\MockObject\MockObject ', new Context('phpDocumentor'));
273+
274+
$this->assertInstanceOf(Compound::class, $resolvedType);
275+
$this->assertSame('\phpDocumentor\Reflection\DocBlock&\PHPUnit\Framework\MockObject\MockObject', (string) $resolvedType);
276+
277+
$firstType = $resolvedType->get(0);
278+
279+
$secondType = $resolvedType->get(1);
280+
281+
$this->assertInstanceOf(Object_::class, $firstType);
282+
$this->assertInstanceOf(Fqsen::class, $firstType->getFqsen());
283+
$this->assertInstanceOf(Object_::class, $secondType);
284+
$this->assertInstanceOf(Fqsen::class, $secondType->getFqsen());
285+
}
286+
287+
/**
288+
* @uses \phpDocumentor\Reflection\Types\Context
289+
* @uses \phpDocumentor\Reflection\Types\Compound
290+
* @uses \phpDocumentor\Reflection\Types\String_
291+
* @uses \phpDocumentor\Reflection\Types\Object_
292+
* @uses \phpDocumentor\Reflection\Fqsen
293+
* @uses \phpDocumentor\Reflection\FqsenResolver
294+
*
295+
* @covers ::__construct
296+
* @covers ::resolve
297+
* @covers ::<private>
298+
*/
299+
public function testResolvingMixedCompoundTypes() : void
300+
{
301+
$fixture = new TypeResolver();
302+
303+
$resolvedType = $fixture->resolve('(Reflection\DocBlock&\PHPUnit\Framework\MockObject\MockObject)|null', new Context('phpDocumentor'));
304+
305+
$this->assertInstanceOf(Compound::class, $resolvedType);
306+
$this->assertSame('(\phpDocumentor\Reflection\DocBlock&\PHPUnit\Framework\MockObject\MockObject)|null', (string) $resolvedType);
307+
308+
$firstType = $resolvedType->get(0);
309+
310+
$secondType = $resolvedType->get(1);
311+
312+
$this->assertInstanceOf(Expression_::class, $firstType);
313+
$this->assertSame('(\phpDocumentor\Reflection\DocBlock&\PHPUnit\Framework\MockObject\MockObject)', (string) $firstType);
314+
$this->assertInstanceOf(Null_::class, $secondType);
315+
316+
$resolvedType = $firstType->getValueType();
317+
318+
$firstSubType = $resolvedType->get(0);
319+
$secondSubType = $resolvedType->get(1);
320+
321+
$this->assertInstanceOf(Object_::class, $firstSubType);
322+
$this->assertInstanceOf(Fqsen::class, $secondSubType->getFqsen());
323+
$this->assertInstanceOf(Object_::class, $secondSubType);
324+
$this->assertInstanceOf(Fqsen::class, $secondSubType->getFqsen());
325+
}
326+
254327
/**
255328
* @uses \phpDocumentor\Reflection\Types\Context
256329
* @uses \phpDocumentor\Reflection\Types\Compound

0 commit comments

Comments
 (0)