Passed
Push — develop ( 6ae3da...c93a2f )
by Paul
02:03
created

TokenConsumer::hasOpenedString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace PhpUnitGen\Annotation;
4
5
use PhpUnitGen\Annotation\AnnotationInterface\AnnotationInterface;
6
use PhpUnitGen\Exception\AnnotationParseException;
7
8
/**
9
 * Class TokenConsumer.
10
 *
11
 * @author     Paul Thébaud <[email protected]>.
12
 * @copyright  2017-2018 Paul Thébaud <[email protected]>.
13
 * @license    https://opensource.org/licenses/MIT The MIT license.
14
 * @link       https://github.com/paul-thebaud/phpunit-generator
15
 * @since      Class available since Release 2.0.0.
16
 */
17
class TokenConsumer
18
{
19
    /**
20
     * @var AnnotationFactory $annotationFactory The annotation factory to use.
21
     */
22
    private $annotationFactory;
23
24
    /**
25
     * @var AnnotationInterface[] $parsedAnnotations The parsed annotation list.
26
     */
27
    private $parsedAnnotations;
28
29
    /**
30
     * @var int $currentLine The current line number.
31
     */
32
    private $currentLine;
33
34
    /**
35
     * @var AbstractAnnotation $currentAnnotation The current parsed annotation, null if none.
36
     */
37
    private $currentAnnotation;
38
39
    /**
40
     * @var string|null $currentAnnotationContent The current parsed annotation content, null if none.
41
     */
42
    private $currentAnnotationContent;
43
44
    /**
45
     * @var int|null $openedStringToken The opened string opening token identifier, null if no string opened.
46
     */
47
    private $openedStringToken;
48
49
    /**
50
     * @var bool $currentlyEscaping Tells if last parsed token was an escape token.
51
     */
52
    private $currentlyEscaping;
53
54
    /**
55
     * @var int $openedParenthesis The opened parenthesis number for an annotation content.
56
     */
57
    private $openedParenthesis;
58
59
    /**
60
     * TokenConsumer constructor.
61
     *
62
     * @param AnnotationFactory $annotationFactory The annotation factory to use.
63
     */
64
    public function __construct(AnnotationFactory $annotationFactory)
65
    {
66
        $this->annotationFactory = $annotationFactory;
67
        $this->parsedAnnotations = [];
68
    }
69
70
    /**
71
     * Initialize the documentation parsing process.
72
     */
73
    public function initialize(): void
74
    {
75
        $this->parsedAnnotations = [];
76
        $this->currentLine       = 0;
77
        $this->reset();
78
    }
79
80
    /**
81
     * Reset an annotation parsing.
82
     */
83
    public function reset(): void
84
    {
85
        $this->currentAnnotation        = null;
86
        $this->currentAnnotationContent = null;
87
        $this->openedStringToken        = null;
88
        $this->currentlyEscaping        = false;
89
90
        $this->openedParenthesis = 0;
91
    }
92
93
    /**
94
     * Finalize the documentation parsing process.
95
     *
96
     * @throws AnnotationParseException If an annotation content is not closed.
97
     */
98
    public function finalize(): void
99
    {
100
        if ($this->currentAnnotation !== null) {
101
            if ($this->currentAnnotationContent === null) {
102
                $this->parsedAnnotations[] = $this->currentAnnotation;
103
            } else {
104
                throw new AnnotationParseException(
105
                    'An annotation content is not closed (you probably forget to close a parenthesis or a quote)'
106
                );
107
            }
108
        }
109
    }
110
111
    /**
112
     * Add a token content to the current annotation content.
113
     *
114
     * @param string $value The token value to add.
115
     */
116
    public function addTokenToContent(string $value)
117
    {
118
        if ($this->currentAnnotationContent !== null) {
119
            $this->currentAnnotationContent .= $value;
120
        }
121
    }
122
123
    /**
124
     * Consume an annotation token ("@PhpUnitGen" for example).
125
     *
126
     * @param string $value The annotation token value.
127
     *
128
     * @throws AnnotationParseException If the annotation is unknown.
129
     */
130
    public function consumeAnnotationToken(string $value): void
131
    {
132
        if ($this->currentAnnotation === null) {
133
            // We are not in an annotation, build a new one.
134
            $this->currentAnnotation = $this->annotationFactory
135
                ->invoke($value, $this->currentLine);
136
        } else {
137
            if ($this->currentAnnotationContent === null) {
138
                // It is an annotation without content, save it and create the new one.
139
                $this->parsedAnnotations[] = $this->currentAnnotation;
140
                $this->reset();
141
                $this->currentAnnotation = $this->annotationFactory
142
                    ->invoke($value, $this->currentLine);
143
            } else {
144
                // An annotation content parsing is not finished.
145
                throw new AnnotationParseException(
146
                    'An annotation content is not closed (you probably forget to close a parenthesis or a quote)'
147
                );
148
            }
149
        }
150
    }
151
152
    /**
153
     * Consume an opening parenthesis token.
154
     */
155
    public function consumeOpeningParenthesisToken(): void
156
    {
157
        if ($this->currentAnnotation !== null && $this->openedStringToken === null) {
158
            // We are in an annotation but not in a string, lets do something.
159
            if ($this->currentAnnotationContent === null) {
160
                if ($this->currentAnnotation->getLine() === $this->currentLine) {
161
                    // Begin content parsing only if it is on the same line.
162
                    $this->currentAnnotationContent = '';
163
                    $this->openedParenthesis++;
164
                }
165
            } else {
166
                $this->openedParenthesis++;
167
            }
168
        }
169
    }
170
171
    /**
172
     * Consume an closing parenthesis token.
173
     */
174
    public function consumeClosingParenthesisToken(): void
175
    {
176
        if ($this->currentAnnotationContent !== null && $this->openedStringToken === null) {
177
            // We are in an annotation content and not in a string.
178
            if ($this->openedParenthesis > 0) {
179
                $this->openedParenthesis--;
180
                if ($this->openedParenthesis === 0) {
181
                    // Annotation content is finished.
182
                    $this->currentAnnotation->setStringContent(substr(
183
                        $this->currentAnnotationContent,
184
                        1,
185
                        strlen($this->currentAnnotationContent) - 2
186
                    ));
187
                    $this->parsedAnnotations[] = $this->currentAnnotation;
188
                    $this->reset();
189
                }
190
            }
191
        }
192
    }
193
194
    /**
195
     * Consume a quote token (" or ').
196
     *
197
     * @param int $type The type of the quote token.
198
     */
199
    public function consumeQuoteToken(int $type): void
200
    {
201
        if ($this->currentAnnotationContent !== null) {
202
            if ($this->openedStringToken === null) {
203
                // It is a string opening.
204
                $this->openedStringToken = $type;
205
            } else {
206
                if (! $this->currentlyEscaping && $type === $this->openedStringToken) {
207
                    // We are in a string, the token is not escaped and is the same as the string opening token.
208
                    // Close the string.
209
                    $this->openedStringToken = null;
210
                }
211
            }
212
        }
213
    }
214
215
    /**
216
     * A method that is executed after consuming a token.
217
     *
218
     * @param int $type The token type (an integer from the Lexer class constant).
219
     */
220
    public function afterConsume(int $type): void
221
    {
222
        // Put in escaping mode if it were not escaping and there is a backslash token.
223
        if (! $this->currentlyEscaping && $type === AnnotationLexer::T_BACKSLASH) {
224
            $this->currentlyEscaping = true;
225
        } else {
226
            $this->currentlyEscaping = false;
227
        }
228
    }
229
230
    /**
231
     * @return bool True if a string is currently opened.
232
     */
233
    public function hasOpenedString(): bool
234
    {
235
        return $this->openedStringToken !== null;
236
    }
237
238
    /**
239
     * Increase the line number.
240
     */
241
    public function increaseLine(): void
242
    {
243
        $this->currentLine++;
244
    }
245
246
    /**
247
     * @return AnnotationInterface[] The parsed annotations.
248
     */
249
    public function getParsedAnnotations(): array
250
    {
251
        return $this->parsedAnnotations;
252
    }
253
}
254