Passed
Push — master ( 8fc7b2...8c329d )
by Grégoire
52s queued 11s
created

TokenParser   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 175
Duplicated Lines 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
eloc 68
c 6
b 0
f 0
dl 0
loc 175
rs 9.6
wmc 35

6 Methods

Rating   Name   Duplication   Size   Complexity  
A next() 0 15 6
A __construct() 0 14 1
A parseUseStatements() 0 19 5
B parseNamespace() 0 11 7
A parseClass() 0 6 1
C parseUseStatement() 0 44 15
1
<?php
2
3
namespace Doctrine\Common\Annotations;
4
5
/**
6
 * Parses a file for namespaces/use/class declarations.
7
 *
8
 * @author Fabien Potencier <[email protected]>
9
 * @author Christian Kaps <[email protected]>
10
 */
11
class TokenParser
12
{
13
    /**
14
     * The token list.
15
     *
16
     * @var array
17
     */
18
    private $tokens;
19
20
    /**
21
     * The number of tokens.
22
     *
23
     * @var int
24
     */
25
    private $numTokens;
26
27
    /**
28
     * The current array pointer.
29
     *
30
     * @var int
31
     */
32
    private $pointer = 0;
33
34
    /**
35
     * @param string $contents
36
     */
37
    public function __construct($contents)
38
    {
39
        $this->tokens = token_get_all($contents);
40
41
        // The PHP parser sets internal compiler globals for certain things. Annoyingly, the last docblock comment it
42
        // saw gets stored in doc_comment. When it comes to compile the next thing to be include()d this stored
43
        // doc_comment becomes owned by the first thing the compiler sees in the file that it considers might have a
44
        // docblock. If the first thing in the file is a class without a doc block this would cause calls to
45
        // getDocBlock() on said class to return our long lost doc_comment. Argh.
46
        // To workaround, cause the parser to parse an empty docblock. Sure getDocBlock() will return this, but at least
47
        // it's harmless to us.
48
        token_get_all("<?php\n/**\n *\n */");
49
50
        $this->numTokens = count($this->tokens);
51
    }
52
53
    /**
54
     * Gets the next non whitespace and non comment token.
55
     *
56
     * @param boolean $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped.
57
     *                                     If FALSE then only whitespace and normal comments are skipped.
58
     *
59
     * @return array|null The token if exists, null otherwise.
60
     */
61
    public function next($docCommentIsComment = TRUE)
62
    {
63
        for ($i = $this->pointer; $i < $this->numTokens; $i++) {
64
            $this->pointer++;
65
            if ($this->tokens[$i][0] === T_WHITESPACE ||
66
                $this->tokens[$i][0] === T_COMMENT ||
67
                ($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT)) {
68
69
                continue;
70
            }
71
72
            return $this->tokens[$i];
73
        }
74
75
        return null;
76
    }
77
78
    /**
79
     * Parses a single use statement.
80
     *
81
     * @return array A list with all found class names for a use statement.
82
     */
83
    public function parseUseStatement()
84
    {
85
86
        $groupRoot = '';
87
        $class = '';
88
        $alias = '';
89
        $statements = [];
90
        $explicitAlias = false;
91
        while (($token = $this->next())) {
92
            if (!$explicitAlias && $token[0] === T_STRING) {
93
                $class .= $token[1];
94
                $alias = $token[1];
95
            } else if ($explicitAlias && $token[0] === T_STRING) {
96
                $alias = $token[1];
97
            } else if (\PHP_VERSION_ID >= 80000 && ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED)) {
98
                $class .= $token[1];
99
100
                $classSplit = explode('\\', $token[1]);
101
                $alias = $classSplit[count($classSplit) - 1];
102
            } else if ($token[0] === T_NS_SEPARATOR) {
103
                $class .= '\\';
104
                $alias = '';
105
            } else if ($token[0] === T_AS) {
106
                $explicitAlias = true;
107
                $alias = '';
108
            } else if ($token === ',') {
109
                $statements[strtolower($alias)] = $groupRoot . $class;
110
                $class = '';
111
                $alias = '';
112
                $explicitAlias = false;
113
            } else if ($token === ';') {
114
                $statements[strtolower($alias)] = $groupRoot . $class;
115
                break;
116
            } else if ($token === '{' ) {
117
                $groupRoot = $class;
118
                $class = '';
119
            } else if ($token === '}' ) {
120
                continue;
121
            } else {
122
                break;
123
            }
124
        }
125
126
        return $statements;
127
    }
128
129
    /**
130
     * Gets all use statements.
131
     *
132
     * @param string $namespaceName The namespace name of the reflected class.
133
     *
134
     * @return array A list with all found use statements.
135
     */
136
    public function parseUseStatements($namespaceName)
137
    {
138
        $statements = [];
139
        while (($token = $this->next())) {
140
            if ($token[0] === T_USE) {
141
                $statements = array_merge($statements, $this->parseUseStatement());
142
                continue;
143
            }
144
            if ($token[0] !== T_NAMESPACE || $this->parseNamespace() != $namespaceName) {
145
                continue;
146
            }
147
148
            // Get fresh array for new namespace. This is to prevent the parser to collect the use statements
149
            // for a previous namespace with the same name. This is the case if a namespace is defined twice
150
            // or if a namespace with the same name is commented out.
151
            $statements = [];
152
        }
153
154
        return $statements;
155
    }
156
157
    /**
158
     * Gets the namespace.
159
     *
160
     * @return string The found namespace.
161
     */
162
    public function parseNamespace()
163
    {
164
        $name = '';
165
        while (($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR || (
166
            \PHP_VERSION_ID >= 80000 &&
167
            ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED)
168
        ))) {
169
            $name .= $token[1];
170
        }
171
172
        return $name;
173
    }
174
175
    /**
176
     * Gets the class name.
177
     *
178
     * @return string The found class name.
179
     */
180
    public function parseClass()
181
    {
182
        // Namespaces and class names are tokenized the same: T_STRINGs
183
        // separated by T_NS_SEPARATOR so we can use one function to provide
184
        // both.
185
        return $this->parseNamespace();
186
    }
187
}
188