Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 1948aa8

Browse files
kukulichondrejmirtes
authored andcommitted
Fixed parsing description started with HTML tag
1 parent 2b0e830 commit 1948aa8

File tree

3 files changed

+110
-1
lines changed

3 files changed

+110
-1
lines changed

‎src/Parser/PhpDocParser.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ class PhpDocParser
1212
private const DISALLOWED_DESCRIPTION_START_TOKENS = [
1313
Lexer::TOKEN_UNION,
1414
Lexer::TOKEN_INTERSECTION,
15-
Lexer::TOKEN_OPEN_ANGLE_BRACKET,
1615
];
1716

1817
/** @var TypeParser */

‎src/Parser/TypeParser.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode
6565
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) {
6666
$tokens->dropSavePoint(); // because of ConstFetchNode
6767
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
68+
$tokens->pushSavePoint();
69+
70+
$isHtml = $this->isHtml($tokens);
71+
$tokens->rollback();
72+
if ($isHtml) {
73+
return $type;
74+
}
75+
6876
$type = $this->parseGeneric($tokens, $type);
6977

7078
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
@@ -161,6 +169,35 @@ private function parseNullable(TokenIterator $tokens): Ast\Type\TypeNode
161169
return new Ast\Type\NullableTypeNode($type);
162170
}
163171

172+
public function isHtml(TokenIterator $tokens): bool
173+
{
174+
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
175+
176+
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) {
177+
return false;
178+
}
179+
180+
$htmlTagName = $tokens->currentTokenValue();
181+
182+
$tokens->next();
183+
184+
if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) {
185+
return false;
186+
}
187+
188+
while (!$tokens->isCurrentTokenType(Lexer::TOKEN_END)) {
189+
if (
190+
$tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)
191+
&& strpos($tokens->currentTokenValue(), '/' . $htmlTagName . '>') !== false
192+
) {
193+
return true;
194+
}
195+
196+
$tokens->next();
197+
}
198+
199+
return false;
200+
}
164201

165202
public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $baseType): Ast\Type\GenericTypeNode
166203
{

‎tests/PHPStan/Parser/PhpDocParserTest.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ protected function setUp(): void
6666
* @dataProvider provideTemplateTagsData
6767
* @dataProvider provideExtendsTagsData
6868
* @dataProvider provideRealWorldExampleData
69+
* @dataProvider provideDescriptionWithOrWithoutHtml
6970
* @param string $label
7071
* @param string $input
7172
* @param PhpDocNode $expectedPhpDocNode
@@ -3130,6 +3131,78 @@ public function provideRealWorldExampleData(): \Iterator
31303131
];
31313132
}
31323133

3134+
public function provideDescriptionWithOrWithoutHtml(): \Iterator
3135+
{
3136+
yield [
3137+
'Description with HTML tags in @return tag (close tags together)',
3138+
'/**' . PHP_EOL .
3139+
' * @return Foo <strong>Important <i>description</i></strong>' . PHP_EOL .
3140+
' */',
3141+
new PhpDocNode([
3142+
new PhpDocTagNode(
3143+
'@return',
3144+
new ReturnTagValueNode(
3145+
new IdentifierTypeNode('Foo'),
3146+
'<strong>Important <i>description</i></strong>'
3147+
)
3148+
),
3149+
]),
3150+
];
3151+
3152+
yield [
3153+
'Description with HTML tags in @throws tag (closed tags with text between)',
3154+
'/**' . PHP_EOL .
3155+
' * @throws FooException <strong>Important <em>description</em> etc</strong>' . PHP_EOL .
3156+
' */',
3157+
new PhpDocNode([
3158+
new PhpDocTagNode(
3159+
'@throws',
3160+
new ThrowsTagValueNode(
3161+
new IdentifierTypeNode('FooException'),
3162+
'<strong>Important <em>description</em> etc</strong>'
3163+
)
3164+
),
3165+
]),
3166+
];
3167+
3168+
yield [
3169+
'Description with HTML tags in @mixin tag',
3170+
'/**' . PHP_EOL .
3171+
' * @mixin Mixin <strong>Important description</strong>' . PHP_EOL .
3172+
' */',
3173+
new PhpDocNode([
3174+
new PhpDocTagNode(
3175+
'@mixin',
3176+
new MixinTagValueNode(
3177+
new IdentifierTypeNode('Mixin'),
3178+
'<strong>Important description</strong>'
3179+
)
3180+
),
3181+
]),
3182+
];
3183+
3184+
yield [
3185+
'Description with unclosed HTML tags in @return tag - unclosed HTML tag is parsed as generics',
3186+
'/**' . PHP_EOL .
3187+
' * @return Foo <strong>Important description' . PHP_EOL .
3188+
' */',
3189+
new PhpDocNode([
3190+
new PhpDocTagNode(
3191+
'@return',
3192+
new ReturnTagValueNode(
3193+
new GenericTypeNode(
3194+
new IdentifierTypeNode('Foo'),
3195+
[
3196+
new IdentifierTypeNode('strong'),
3197+
]
3198+
),
3199+
'Important description'
3200+
)
3201+
),
3202+
]),
3203+
];
3204+
}
3205+
31333206
public function dataParseTagValue(): array
31343207
{
31353208
return [

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /