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 2eefe4a

Browse files
committed
Fixed parsing description started with HTML tag
1 parent 2b0e830 commit 2eefe4a

File tree

3 files changed

+108
-1
lines changed

3 files changed

+108
-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: 36 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,10 +169,38 @@ 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+
if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) {
183+
return false;
184+
}
185+
186+
while (!$tokens->isCurrentTokenType(Lexer::TOKEN_END)) {
187+
if (
188+
$tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)
189+
&& strpos($tokens->currentTokenValue(), '/' . $htmlTagName . '>') !== false
190+
) {
191+
return true;
192+
}
193+
194+
$tokens->next();
195+
}
196+
197+
return false;
198+
}
164199

165200
public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $baseType): Ast\Type\GenericTypeNode
166201
{
167202
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
203+
168204
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
169205
$genericTypes = [$this->parse($tokens)];
170206

‎tests/PHPStan/Parser/PhpDocParserTest.php‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3130,6 +3130,78 @@ public function provideRealWorldExampleData(): \Iterator
31303130
];
31313131
}
31323132

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

0 commit comments

Comments
(0)

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