Skip to content

Commit 886f662

Browse files
committed
Experimental: Parse Doctrine annotations
1 parent ffc6510 commit 886f662

16 files changed

+1068
-29
lines changed

composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"php": "^7.2 || ^8.0"
77
},
88
"require-dev": {
9+
"doctrine/annotations": "^2.0",
910
"nikic/php-parser": "^4.15",
1011
"php-parallel-lint/php-parallel-lint": "^1.2",
1112
"phpstan/extension-installer": "^1.0",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
4+
5+
use PHPStan\PhpDocParser\Ast\Node;
6+
use PHPStan\PhpDocParser\Ast\NodeAttributes;
7+
use function implode;
8+
9+
class DoctrineAnnotation implements Node
10+
{
11+
12+
use NodeAttributes;
13+
14+
/** @var string */
15+
public $name;
16+
17+
/** @var list<DoctrineArgument> */
18+
public $arguments;
19+
20+
/**
21+
* @param list<DoctrineArgument> $arguments
22+
*/
23+
public function __construct(string $name, array $arguments)
24+
{
25+
$this->name = $name;
26+
$this->arguments = $arguments;
27+
}
28+
29+
public function __toString(): string
30+
{
31+
$arguments = implode(', ', $this->arguments);
32+
return $this->name . '(' . $arguments . ')';
33+
}
34+
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
4+
5+
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNode;
6+
use PHPStan\PhpDocParser\Ast\Node;
7+
use PHPStan\PhpDocParser\Ast\NodeAttributes;
8+
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
9+
10+
/**
11+
* @phpstan-type ValueType = DoctrineAnnotation|IdentifierTypeNode|DoctrineArray|ConstExprNode
12+
*/
13+
class DoctrineArgument implements Node
14+
{
15+
16+
use NodeAttributes;
17+
18+
/** @var IdentifierTypeNode|null */
19+
public $key;
20+
21+
/** @var ValueType */
22+
public $value;
23+
24+
/**
25+
* @param ValueType $value
26+
*/
27+
public function __construct(?IdentifierTypeNode $key, $value)
28+
{
29+
$this->key = $key;
30+
$this->value = $value;
31+
}
32+
33+
34+
public function __toString(): string
35+
{
36+
if ($this->key === null) {
37+
return (string) $this->value;
38+
}
39+
40+
return $this->key . '=' . $this->value;
41+
}
42+
43+
}
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
4+
5+
use PHPStan\PhpDocParser\Ast\Node;
6+
use PHPStan\PhpDocParser\Ast\NodeAttributes;
7+
use function implode;
8+
9+
class DoctrineArray implements Node
10+
{
11+
12+
use NodeAttributes;
13+
14+
/** @var list<DoctrineArrayItem> */
15+
public $items;
16+
17+
/**
18+
* @param list<DoctrineArrayItem> $items
19+
*/
20+
public function __construct(array $items)
21+
{
22+
$this->items = $items;
23+
}
24+
25+
public function __toString(): string
26+
{
27+
$items = implode(', ', $this->items);
28+
29+
return '{' . $items . '}';
30+
}
31+
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
4+
5+
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
6+
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
7+
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
8+
use PHPStan\PhpDocParser\Ast\Node;
9+
use PHPStan\PhpDocParser\Ast\NodeAttributes;
10+
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
11+
12+
/**
13+
* @phpstan-import-type ValueType from DoctrineArgument
14+
* @phpstan-type KeyType = ConstExprIntegerNode|ConstExprStringNode|IdentifierTypeNode|ConstFetchNode|null
15+
*/
16+
class DoctrineArrayItem implements Node
17+
{
18+
19+
use NodeAttributes;
20+
21+
/** @var KeyType */
22+
public $key;
23+
24+
/** @var ValueType */
25+
public $value;
26+
27+
/**
28+
* @param KeyType $key
29+
* @param ValueType $value
30+
*/
31+
public function __construct($key, $value)
32+
{
33+
$this->key = $key;
34+
$this->value = $value;
35+
}
36+
37+
38+
public function __toString(): string
39+
{
40+
if ($this->key === null) {
41+
return (string) $this->value;
42+
}
43+
44+
return $this->key . '=' . $this->value;
45+
}
46+
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine;
4+
5+
use PHPStan\PhpDocParser\Ast\NodeAttributes;
6+
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
7+
use function trim;
8+
9+
class DoctrineTagValueNode implements PhpDocTagValueNode
10+
{
11+
12+
use NodeAttributes;
13+
14+
/** @var DoctrineAnnotation */
15+
public $annotation;
16+
17+
/** @var string (may be empty) */
18+
public $description;
19+
20+
21+
public function __construct(
22+
DoctrineAnnotation $annotation,
23+
string $description
24+
)
25+
{
26+
$this->annotation = $annotation;
27+
$this->description = $description;
28+
}
29+
30+
31+
public function __toString(): string
32+
{
33+
return trim("{$this->annotation} {$this->description}");
34+
}
35+
36+
}

src/Ast/PhpDoc/PhpDocTagNode.php

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
44

55
use PHPStan\PhpDocParser\Ast\NodeAttributes;
6+
use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineTagValueNode;
67
use function trim;
78

89
class PhpDocTagNode implements PhpDocChildNode
@@ -25,6 +26,10 @@ public function __construct(string $name, PhpDocTagValueNode $value)
2526

2627
public function __toString(): string
2728
{
29+
if ($this->value instanceof DoctrineTagValueNode) {
30+
return (string) $this->value;
31+
}
32+
2833
return trim("{$this->name} {$this->value}");
2934
}
3035

src/Lexer/Lexer.php

+34-20
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,24 @@ class Lexer
3030
public const TOKEN_OPEN_PHPDOC = 15;
3131
public const TOKEN_CLOSE_PHPDOC = 16;
3232
public const TOKEN_PHPDOC_TAG = 17;
33-
public const TOKEN_FLOAT = 18;
34-
public const TOKEN_INTEGER = 19;
35-
public const TOKEN_SINGLE_QUOTED_STRING = 20;
36-
public const TOKEN_DOUBLE_QUOTED_STRING = 21;
37-
public const TOKEN_IDENTIFIER = 22;
38-
public const TOKEN_THIS_VARIABLE = 23;
39-
public const TOKEN_VARIABLE = 24;
40-
public const TOKEN_HORIZONTAL_WS = 25;
41-
public const TOKEN_PHPDOC_EOL = 26;
42-
public const TOKEN_OTHER = 27;
43-
public const TOKEN_END = 28;
44-
public const TOKEN_COLON = 29;
45-
public const TOKEN_WILDCARD = 30;
46-
public const TOKEN_OPEN_CURLY_BRACKET = 31;
47-
public const TOKEN_CLOSE_CURLY_BRACKET = 32;
48-
public const TOKEN_NEGATED = 33;
49-
public const TOKEN_ARROW = 34;
33+
public const TOKEN_DOCTRINE_TAG = 18;
34+
public const TOKEN_FLOAT = 19;
35+
public const TOKEN_INTEGER = 20;
36+
public const TOKEN_SINGLE_QUOTED_STRING = 21;
37+
public const TOKEN_DOUBLE_QUOTED_STRING = 22;
38+
public const TOKEN_IDENTIFIER = 23;
39+
public const TOKEN_THIS_VARIABLE = 24;
40+
public const TOKEN_VARIABLE = 25;
41+
public const TOKEN_HORIZONTAL_WS = 26;
42+
public const TOKEN_PHPDOC_EOL = 27;
43+
public const TOKEN_OTHER = 28;
44+
public const TOKEN_END = 29;
45+
public const TOKEN_COLON = 30;
46+
public const TOKEN_WILDCARD = 31;
47+
public const TOKEN_OPEN_CURLY_BRACKET = 32;
48+
public const TOKEN_CLOSE_CURLY_BRACKET = 33;
49+
public const TOKEN_NEGATED = 34;
50+
public const TOKEN_ARROW = 35;
5051

5152
public const TOKEN_LABELS = [
5253
self::TOKEN_REFERENCE => '\'&\'',
@@ -72,6 +73,7 @@ class Lexer
7273
self::TOKEN_OPEN_PHPDOC => '\'/**\'',
7374
self::TOKEN_CLOSE_PHPDOC => '\'*/\'',
7475
self::TOKEN_PHPDOC_TAG => 'TOKEN_PHPDOC_TAG',
76+
self::TOKEN_DOCTRINE_TAG => 'TOKEN_DOCTRINE_TAG',
7577
self::TOKEN_PHPDOC_EOL => 'TOKEN_PHPDOC_EOL',
7678
self::TOKEN_FLOAT => 'TOKEN_FLOAT',
7779
self::TOKEN_INTEGER => 'TOKEN_INTEGER',
@@ -90,9 +92,17 @@ class Lexer
9092
public const TYPE_OFFSET = 1;
9193
public const LINE_OFFSET = 2;
9294

95+
/** @var bool */
96+
private $parseDoctrineAnnotations;
97+
9398
/** @var string|null */
9499
private $regexp;
95100

101+
public function __construct(bool $parseDoctrineAnnotations = false)
102+
{
103+
$this->parseDoctrineAnnotations = $parseDoctrineAnnotations;
104+
}
105+
96106
/**
97107
* @return list<array{string, int, int}>
98108
*/
@@ -166,11 +176,15 @@ private function generateRegexp(): string
166176
self::TOKEN_DOUBLE_QUOTED_STRING => '"(?:\\\\[^\\r\\n]|[^"\\r\\n\\\\])*+"',
167177

168178
self::TOKEN_WILDCARD => '\\*',
169-
170-
// anything but TOKEN_CLOSE_PHPDOC or TOKEN_HORIZONTAL_WS or TOKEN_EOL
171-
self::TOKEN_OTHER => '(?:(?!\\*/)[^\\s])++',
172179
];
173180

181+
if ($this->parseDoctrineAnnotations) {
182+
$patterns[self::TOKEN_DOCTRINE_TAG] = '@[a-z_\\\\][a-z0-9_\:\\\\]*[a-z_][a-z0-9_]*';
183+
}
184+
185+
// anything but TOKEN_CLOSE_PHPDOC or TOKEN_HORIZONTAL_WS or TOKEN_EOL
186+
$patterns[self::TOKEN_OTHER] = '(?:(?!\\*/)[^\\s])++';
187+
174188
foreach ($patterns as $type => &$pattern) {
175189
$pattern = '(?:' . $pattern . ')(*MARK:' . $type . ')';
176190
}

0 commit comments

Comments
 (0)