Skip to content

Commit

Permalink
Merge pull request #17 from KentarouTakeda/nested-generics-and-closur…
Browse files Browse the repository at this point in the history
…es-type-hint

Support nested generics and closures type hint
  • Loading branch information
barryvdh authored Oct 16, 2024
2 parents e6811e9 + 23fa082 commit bba116b
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 16 deletions.
43 changes: 30 additions & 13 deletions src/Barryvdh/Reflection/DocBlock/Tag/ParamTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,38 @@ public function getContent()
public function setContent($content)
{
Tag::setContent($content);
$parts = preg_split(
'/(\s+)/Su',
$this->description,
3,
PREG_SPLIT_DELIM_CAPTURE
);

// detect generic type
if (isset($parts[0]) && isset($parts[2]) && strpos($parts[0], '<') !== false && strpos($parts[2], '>') !== false) {
$parts[0] .= ' ' . $parts[2];
unset($parts[1]);
unset($parts[2]);
$parts = array_values($parts);

$parts = [];
$rest = $this->description;

// parsing generics and closures to detect types
for($pos = 0, $stacks = []; $pos < strlen($rest); $pos++) {
$char = $rest[$pos];

if($char === '<') {
array_unshift($stacks, $char);
}
if($char === '(') {
array_unshift($stacks, $char);
}
if($char === '>' && isset($stacks[0]) && $stacks[0] === '<') {
array_shift($stacks);
}
if($char === ')' && isset($stacks[0]) && $stacks[0] === '(') {
array_shift($stacks);
}

if(!$stacks && preg_match('/\A(\s+)(.*)/su', substr($rest, $pos), $matches)) {
$parts[0] = substr($rest, 0, $pos);
$parts[1] = $matches[1];
$rest = $matches[2];

break;
}
}

array_push($parts, ...preg_split('/(\s+)/u', $rest, 2, PREG_SPLIT_DELIM_CAPTURE));

// if the first item that is encountered is not a variable; it is a type
if (isset($parts[0])
&& (strlen($parts[0]) > 0)
Expand Down
8 changes: 6 additions & 2 deletions src/Barryvdh/Reflection/DocBlock/Type/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,9 @@ protected function explode($type)
$type_parts[] = $curr_type;
$curr_type = '';
} else {
if ($char === '<') {
if ($char === '<' || $char === '(') {
$nest_level++;
} else if ($char === '>') {
} else if ($char === '>' || $char === ')') {
$nest_level--;
}

Expand Down Expand Up @@ -189,6 +189,10 @@ protected function expand($type)
return $type;
}

if($type[0] === '(') {
return $type;
}

if ($this->isTypeAnArray($type)) {
return $this->expand(substr($type, 0, -2)) . self::OPERATOR_ARRAY;
}
Expand Down
70 changes: 69 additions & 1 deletion tests/Barryvdh/Reflection/DocBlock/Tag/ParamTagTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,75 @@ public function provideDataForConstructor()
array('int'),
'$bob',
"Type on a new line"
)
),

// generic array
array(
'param',
'array<int, string> $names',
'array<int, string>',
array('array<int, string>'),
'$names',
''
),

// nested generics
array(
'param',
'array<int, array<string, mixed>> $arrays',
'array<int, array<string, mixed>>',
array('array<int, array<string, mixed>>'),
'$arrays',
''
),

// closure
array(
'param',
'(\Closure(int, string): bool) $callback',
'(\Closure(int, string): bool)',
array('(\Closure(int, string): bool)'),
'$callback',
''
),

// generic array in closure
array(
'param',
'(\Closure(array<int, string>): bool) $callback',
'(\Closure(array<int, string>): bool)',
array('(\Closure(array<int, string>): bool)'),
'$callback',
''
),

// union types in closure
array(
'param',
'(\Closure(int|string): bool)|bool $callback',
'(\Closure(int|string): bool)|bool',
array('(\Closure(int|string): bool)', 'bool'),
'$callback',
''
),

// example from Laravel Framework - Eloquent Builder)
array(
'param',
'array<array-key, array|(\Closure(\Illuminate\Database\Eloquent\Relations\Relation<*,*,*>): mixed)|string>|string $relations',
'array<array-key, array|(\Closure(\Illuminate\Database\Eloquent\Relations\Relation<*,*,*>): mixed)|string>|string',
array('array<array-key, array|(\Closure(\Illuminate\Database\Eloquent\Relations\Relation<*,*,*>): mixed)|string>', 'string'),
'$relations',
''
),
array(
'param',
'(\Closure(\Illuminate\Database\Eloquent\Relations\Relation<*,*,*>): mixed)|string|null $callback',
'(\Closure(\Illuminate\Database\Eloquent\Relations\Relation<*,*,*>): mixed)|string|null',
array('(\Closure(\Illuminate\Database\Eloquent\Relations\Relation<*,*,*>): mixed)', 'string', 'null'),
'$callback',
''
),
);
}
}

0 comments on commit bba116b

Please sign in to comment.