Failed Conditions
Push — master ( a46fbb...c35fd9 )
by Alexander
01:13
created

TypeCommentSniff::findLastOnLine()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 2
dl 0
loc 14
ccs 6
cts 7
cp 0.8571
crap 3.0261
rs 9.7998
c 0
b 0
f 0
1
<?php
2
/**
3
 * Ensures type comments follow basic formatting.
4
 *
5
 * PHP version 5
6
 *
7
 * @category PHP
8
 * @package  PHP_CodeSniffer
9
 * @author   Alexander Obuhovich <[email protected]>
10
 * @license  https://github.com/aik099/CodingStandard/blob/master/LICENSE BSD 3-Clause
11
 * @link     https://github.com/aik099/CodingStandard
12
 */
13
14
namespace CodingStandard\Sniffs\Commenting;
15
16
use PHP_CodeSniffer\Files\File;
17
use PHP_CodeSniffer\Sniffs\Sniff;
18
19
/**
20
 * Ensures type comments follow basic formatting.
21
 *
22
 * @category PHP
23
 * @package  PHP_CodeSniffer
24
 * @author   Alexander Obuhovich <[email protected]>
25
 * @license  https://github.com/aik099/CodingStandard/blob/master/LICENSE BSD 3-Clause
26
 * @link     https://github.com/aik099/CodingStandard
27
 */
28
29
class TypeCommentSniff implements Sniff
30
{
31
32
    const TYPE_TAG = '@var';
33
34
    /**
35
     * A list of tokenizers this sniff supports.
36
     *
37
     * @var array
38
     */
39
    public $supportedTokenizers = array('PHP');
40
41
42
    /**
43
     * Returns an array of tokens this test wants to listen for.
44
     *
45
     * @return array
46
     */
47 1
    public function register()
48
    {
49
        return array(
50 1
                T_COMMENT,
51 1
                T_DOC_COMMENT_OPEN_TAG,
52 1
               );
53
    }//end register()
54
55
56
    /**
57
     * Processes this test, when one of its tokens is encountered.
58
     *
59
     * @param File $phpcsFile The file being scanned.
60
     * @param int  $stackPtr  The position of the current token in the
61
     *                        stack passed in $tokens.
62
     *
63
     * @return void
64
     */
65 1
    public function process(File $phpcsFile, $stackPtr)
66
    {
67 1
        $tokens = $phpcsFile->getTokens();
68
69 1
        if ($tokens[$stackPtr]['code'] === T_COMMENT) {
70 1
            $this->processComment($phpcsFile, $stackPtr);
71 1
        } elseif ($tokens[$stackPtr]['code'] === T_DOC_COMMENT_OPEN_TAG) {
72 1
            $this->processDocBlock($phpcsFile, $stackPtr);
73 1
        }
74 1
    }//end process()
75
76
77
    /**
78
     * Processes comment.
79
     *
80
     * @param File $phpcsFile The file being scanned.
81
     * @param int  $stackPtr  The position of the current token
82
     *                        in the stack passed in $tokens.
83
     *
84
     * @return void
85
     */
86 1
    public function processComment(File $phpcsFile, $stackPtr)
87
    {
88 1
        $tokens      = $phpcsFile->getTokens();
89 1
        $commentText = $tokens[$stackPtr]['content'];
90
91
        // Multi-line block comment.
92 1
        if (substr($commentText, 0, 2) !== '/*' || substr($commentText, -2) !== '*/') {
93 1
            return;
94
        }
95
96
        // Not a type comment.
97 1
        if ($this->isTypeComment($commentText) === false) {
98 1
            return;
99
        }
100
101
        // The "/**@var ...*/" comment isn't parsed as DocBlock and is fixed here.
102 1
        $error = 'Type comment must be in "/** %s ClassName $variable_name */" format';
103 1
        $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'WrongStyle', array(self::TYPE_TAG));
104 1
        if ($fix === true) {
105 1
            $phpcsFile->fixer->replaceToken($stackPtr, '/** '.trim($commentText, ' /*').' */');
106 1
        }
107 1
    }//end processComment()
108
109
110
    /**
111
     * Processes DocBlock.
112
     *
113
     * @param File $phpcsFile The file being scanned.
114
     * @param int  $stackPtr  The position of the current token
115
     *                        in the stack passed in $tokens.
116
     *
117
     * @return void
118
     */
119 1
    public function processDocBlock(File $phpcsFile, $stackPtr)
120
    {
121 1
        $tokens       = $phpcsFile->getTokens();
122 1
        $commentStart = $stackPtr;
123 1
        $commentEnd   = $tokens[$stackPtr]['comment_closer'];
124
125
        // Multi-line DocBlock.
126 1
        if ($tokens[$commentEnd]['line'] !== $tokens[$commentStart]['line']) {
127 1
            return;
128
        }
129
130 1
        $commentTags = $tokens[$stackPtr]['comment_tags'];
131
132
        // Single-line DocBlock without tags inside.
133 1
        if (empty($commentTags) === true) {
134 1
            return;
135
        }
136
137
        // First tag will always exist.
138 1
        $firstTagPtr = $commentTags[0];
139
140
        // Not a type comment.
141 1
        if ($this->isTypeComment($tokens[$firstTagPtr]['content']) === false) {
142 1
            return;
143
        }
144
145 1
        $structure = new TypeCommentStructure($phpcsFile, $stackPtr);
146
147 1
        if ($structure->className === null) {
148 1
            $error              = 'Type comment must be in "/** %s ClassName $variable_name */" format';
149 1
            $leadingWhitespace  = $tokens[$stackPtr + 1]['code'] === T_DOC_COMMENT_WHITESPACE;
150 1
            $trailingWhitespace = $tokens[$commentEnd - 1]['code'] === T_DOC_COMMENT_WHITESPACE;
151
152 1
            if ($leadingWhitespace === false || $trailingWhitespace === false) {
153 1
                $fix = $phpcsFile->addFixableError($error, $stackPtr, 'WrongStyle', array(self::TYPE_TAG));
154 1
                if ($fix === true) {
155 1
                    if ($leadingWhitespace === false) {
156 1
                        $phpcsFile->fixer->addContentBefore($stackPtr + 1, ' ');
157 1
                    }
158
159 1
                    if ($trailingWhitespace === false) {
160 1
                        $phpcsFile->fixer->addContent($commentEnd - 1, ' ');
161 1
                    }
162 1
                }
163 1
            } else {
164 1
                $phpcsFile->addError($error, $firstTagPtr, 'WrongStyle', array(self::TYPE_TAG));
165
            }
166
167 1
            return;
168
        }//end if
169
170 1
        $this->processDocBlockContent($phpcsFile, $stackPtr, $structure);
171 1
        $this->processVariableAssociation($phpcsFile, $stackPtr, $structure);
172 1
    }//end processDocBlock()
173
174
175
    /**
176
     * Processes DocBlock content.
177
     *
178
     * @param File                 $phpcsFile The file being scanned.
179
     * @param int                  $stackPtr  The position of the current token
180
     *                                        in the stack passed in $tokens.
181
     * @param TypeCommentStructure $structure Type comment structure.
182
     *
183
     * @return void
184
     */
185 1
    public function processDocBlockContent(
186
        File $phpcsFile,
187
        $stackPtr,
188
        TypeCommentStructure $structure
189
    ) {
190 1
        $tokens      = $phpcsFile->getTokens();
191 1
        $commentTags = $tokens[$stackPtr]['comment_tags'];
192 1
        $firstTagPtr = $commentTags[0];
193
194
        // Check correct tag usage.
195 1
        if ($tokens[$firstTagPtr]['content'] !== self::TYPE_TAG) {
196 1
            $fix = $phpcsFile->addFixableError(
197 1
                'Type comment must use "%s" tag; "%s" used',
198 1
                $firstTagPtr,
199 1
                'WrongTag',
200
                array(
201 1
                 self::TYPE_TAG,
202 1
                 $tokens[$firstTagPtr]['content'],
203
                )
204 1
            );
205 1
            if ($fix === true) {
206 1
                $phpcsFile->fixer->replaceToken($firstTagPtr, self::TYPE_TAG);
207 1
            }
208 1
        }
209
210
        // Check spacing around the tag.
211 1
        $spaceBeforeTagPtr = ($stackPtr + 1);
212 1
        if ($tokens[$spaceBeforeTagPtr]['length'] !== 1) {
213 1
            $fix = $phpcsFile->addFixableError(
214 1
                'There must be 1 space before "%s" tag; %d found',
215 1
                $firstTagPtr,
216 1
                'SpaceBeforeTag',
217
                array(
218 1
                 $tokens[$firstTagPtr]['content'],
219 1
                 $tokens[$spaceBeforeTagPtr]['length'],
220
                )
221 1
            );
222 1
            if ($fix === true) {
223 1
                $phpcsFile->fixer->replaceToken($spaceBeforeTagPtr, ' ');
224 1
            }
225 1
        }
226
227 1
        $spaceAfterTagPtr = ($stackPtr + 3);
228 1
        if ($tokens[$spaceAfterTagPtr]['length'] !== 1) {
229 1
            $fix = $phpcsFile->addFixableError(
230 1
                'There must be 1 space between "%s" tag and class name; %d found',
231 1
                $firstTagPtr,
232 1
                'SpaceAfterTag',
233
                array(
234 1
                 $tokens[$firstTagPtr]['content'],
235 1
                 $tokens[$spaceAfterTagPtr]['length'],
236
                )
237 1
            );
238 1
            if ($fix === true) {
239 1
                $phpcsFile->fixer->replaceToken($spaceAfterTagPtr, ' ');
240 1
            }
241 1
        }
242
243
        // Check presence of both type & variable.
244 1
        if ($structure->variableName === null) {
245 1
            if ($structure->isVariable($structure->className) === false) {
246 1
                $phpcsFile->addError(
247 1
                    'Type comment missing variable: /** %s %s ______ */',
248 1
                    $structure->tagContentPtr,
249 1
                    'VariableMissing',
250
                    array(
251 1
                     self::TYPE_TAG,
252 1
                     $structure->className,
253
                    )
254 1
                );
255 1
            } else {
256 1
                $phpcsFile->addError(
257 1
                    'Type comment missing type: /** %s ______ %s */',
258 1
                    $structure->tagContentPtr,
259 1
                    'TypeMissing',
260
                    array(
261 1
                     self::TYPE_TAG,
262 1
                     $structure->className,
263
                    )
264 1
                );
265
            }//end if
266
267 1
            return;
268
        }//end if
269
270 1
        $classPositionCorrect    = $structure->isVariable($structure->className) === false;
271 1
        $variablePositionCorrect = $structure->isVariable($structure->variableName);
272
273
        // Malformed type definition.
274 1
        if (($classPositionCorrect === false && $variablePositionCorrect === true)
275 1
            || ($classPositionCorrect === true && $variablePositionCorrect === false)
276 1
        ) {
277 1
            $error = 'Type comment must be in "/** %s ClassName $variable_name */" format';
278 1
            $phpcsFile->addError($error, $firstTagPtr, 'WrongStyle', array(self::TYPE_TAG));
279
280 1
            return;
281
        }
282
283 1
        if ($classPositionCorrect === true || $variablePositionCorrect === true) {
284 1
            $expectedTagContent = $structure->className.' '.$structure->variableName.' '.$structure->description;
285 1
        } else {
286 1
            $expectedTagContent = $structure->variableName.' '.$structure->className.' '.$structure->description;
287
        }
288
289 1
        if ($structure->tagContent !== $expectedTagContent) {
290 1
            $fix = $phpcsFile->addFixableError(
291 1
                'Wrong type and variable spacing/order. Expected: "%s"',
292 1
                $structure->tagContentPtr,
293 1
                'WrongOrder',
294 1
                array($expectedTagContent)
295 1
            );
296 1
            if ($fix === true) {
297 1
                $phpcsFile->fixer->replaceToken($structure->tagContentPtr, $expectedTagContent);
298 1
            }
299 1
        }
300 1
    }//end processDocBlockContent()
301
302
303
    /**
304
     * Processes variable around DocBlock.
305
     *
306
     * @param File                 $phpcsFile The file being scanned.
307
     * @param int                  $stackPtr  The position of the current token
308
     *                                        in the stack passed in $tokens.
309
     * @param TypeCommentStructure $structure Type comment structure.
310
     *
311
     * @return void
312
     */
313 1
    public function processVariableAssociation(
314
        File $phpcsFile,
315
        $stackPtr,
316
        TypeCommentStructure $structure
317
    ) {
318
        // Variable association can't be determined.
319 1
        if ($structure->variableName === null || $structure->isVariable($structure->variableName) === false) {
320 1
            return;
321
        }
322
323 1
        $this->processVariableBeforeDocBlock($phpcsFile, $stackPtr, $structure);
324 1
        $this->processVariableAfterDocBlock($phpcsFile, $stackPtr, $structure);
325 1
    }//end processVariableAssociation()
326
327
328
    /**
329
     * Processes variable before DocBlock.
330
     *
331
     * @param File                 $phpcsFile The file being scanned.
332
     * @param int                  $stackPtr  The position of the current token
333
     *                                        in the stack passed in $tokens.
334
     * @param TypeCommentStructure $structure Type comment structure.
335
     *
336
     * @return void
337
     */
338 1
    public function processVariableBeforeDocBlock(
339
        File $phpcsFile,
340
        $stackPtr,
341
        TypeCommentStructure $structure
342
    ) {
343 1
        $tokens = $phpcsFile->getTokens();
344
345 1
        $prevStatementEnd = $phpcsFile->findPrevious(
346 1
            T_WHITESPACE,
347 1
            ($stackPtr - 1),
348 1
            null,
349
            true
350 1
        );
351
352
        if ($prevStatementEnd === false
353 1
            || $tokens[$prevStatementEnd]['code'] !== T_SEMICOLON
354 1
        ) {
355 1
            return;
356
        }
357
358 1
        $assignmentTokenPtr = $phpcsFile->findPrevious(
359 1
            T_EQUAL,
360 1
            ($prevStatementEnd - 1),
361 1
            null,
362 1
            false,
363 1
            null,
364
            true
365 1
        );
366
367
        // Not an assignment.
368 1
        if ($assignmentTokenPtr === false) {
369
            return;
370
        }
371
372 1
        $variableTokenPtr = $phpcsFile->findPrevious(
373 1
            T_WHITESPACE,
374 1
            ($assignmentTokenPtr - 1),
375 1
            null,
376
            true
377 1
        );
378
379
        // Assignment not to a variable, mentioned in type comment.
380
        if ($variableTokenPtr === false
381 1
            || $tokens[$variableTokenPtr]['code'] !== T_VARIABLE
382 1
            || $tokens[$variableTokenPtr]['content'] !== $structure->variableName
383 1
        ) {
384 1
            return;
385
        }
386
387 1
        $fix = $phpcsFile->addFixableError(
388 1
            'Type comment must be placed before variable declaration',
389 1
            $stackPtr,
390
            'AfterVariable'
391 1
        );
392 1
        if ($fix === true) {
393 1
            $move_content = '';
394 1
            $copyStartPtr = $this->findFirstOnLine($phpcsFile, $stackPtr);
395 1
            $copyEndPtr   = $this->findLastOnLine($phpcsFile, $stackPtr);
396
397 1
            $phpcsFile->fixer->beginChangeset();
398
399 1
            for ($i = $copyStartPtr; $i <= $copyEndPtr; $i++) {
400 1
                $move_content .= $phpcsFile->fixer->getTokenContent($i);
0 ignored issues
show
Security Bug introduced by
It seems like $i defined by $i++ on line 399 can also be of type false; however, PHP_CodeSniffer\Fixer::getTokenContent() does only seem to accept integer, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
401 1
                $phpcsFile->fixer->replaceToken($i, '');
0 ignored issues
show
Security Bug introduced by
It seems like $i defined by $i++ on line 399 can also be of type false; however, PHP_CodeSniffer\Fixer::replaceToken() does only seem to accept integer, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
402 1
            }
403
404 1
            $phpcsFile->fixer->addContentBefore(
405 1
                $this->findFirstOnLine($phpcsFile, $variableTokenPtr),
0 ignored issues
show
Bug introduced by
It seems like $variableTokenPtr defined by $phpcsFile->findPrevious...kenPtr - 1, null, true) on line 372 can also be of type boolean; however, CodingStandard\Sniffs\Co...niff::findFirstOnLine() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Security Bug introduced by
It seems like $this->findFirstOnLine($...ile, $variableTokenPtr) targeting CodingStandard\Sniffs\Co...niff::findFirstOnLine() can also be of type false; however, PHP_CodeSniffer\Fixer::addContentBefore() does only seem to accept integer, did you maybe forget to handle an error condition?
Loading history...
406
                $move_content
407 1
            );
408 1
            $phpcsFile->fixer->endChangeset();
409 1
        }//end if
410 1
    }//end processVariableBeforeDocBlock()
411
412
413
    /**
414
     * Processes variable before DocBlock.
415
     *
416
     * @param File                 $phpcsFile The file being scanned.
417
     * @param int                  $stackPtr  The position of the current token
418
     *                                        in the stack passed in $tokens.
419
     * @param TypeCommentStructure $structure Type comment structure.
420
     *
421
     * @return void
422
     */
423 1
    public function processVariableAfterDocBlock(
424
        File $phpcsFile,
425
        $stackPtr,
426
        TypeCommentStructure $structure
427
    ) {
428 1
        $tokens           = $phpcsFile->getTokens();
429 1
        $variableTokenPtr = $phpcsFile->findNext(
430 1
            T_WHITESPACE,
431 1
            ($tokens[$stackPtr]['comment_closer'] + 1),
432 1
            null,
433
            true
434 1
        );
435
436
        // No variable placed on next line after type comment.
437
        if ($variableTokenPtr === false
438 1
            || $tokens[$variableTokenPtr]['code'] !== T_VARIABLE
439 1
            || $tokens[$variableTokenPtr]['line'] !== ($tokens[$stackPtr]['line'] + 1)
440 1
        ) {
441 1
            return;
442
        }
443
444 1
        if ($tokens[$variableTokenPtr]['content'] !== $structure->variableName) {
445 1
            $phpcsFile->addError(
446 1
                'Type comment variable mismatch, expected "%s"; found: "%s"',
447 1
                $structure->tagContentPtr,
448 1
                'VariableMismatch',
449
                array(
450 1
                 $tokens[$variableTokenPtr]['content'],
451 1
                 $structure->variableName,
452
                )
453 1
            );
454
455
            // Don't apply more checks, unless type comment is in sync with variable.
456 1
            return;
457
        }
458
459 1
        $prevStatementEnd = $phpcsFile->findPrevious(
460 1
            T_WHITESPACE,
461 1
            ($stackPtr - 1),
462 1
            null,
463
            true
464 1
        );
465
466
        // Previous statement is absent or placed correctly.
467
        if ($prevStatementEnd === false
468 1
            || $tokens[$prevStatementEnd]['code'] !== T_SEMICOLON
469 1
            || $tokens[$prevStatementEnd]['line'] < ($tokens[$stackPtr]['line'] - 1)
470 1
        ) {
471 1
            return;
472
        }
473
474 1
        $fix = $phpcsFile->addFixableError(
475 1
            'There must be at least 1 empty line above type comment',
476 1
            $stackPtr,
477
            'EmptyLineAbove'
478 1
        );
479 1
        if ($fix === true) {
480 1
            $phpcsFile->fixer->addNewline($prevStatementEnd);
0 ignored issues
show
Bug introduced by
It seems like $prevStatementEnd defined by $phpcsFile->findPrevious...ackPtr - 1, null, true) on line 459 can also be of type boolean; however, PHP_CodeSniffer\Fixer::addNewline() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
481 1
        }
482 1
    }//end processVariableAfterDocBlock()
483
484
485
    /**
486
     * Checks if comment is type comment.
487
     *
488
     * @param string $commentText Comment text.
489
     *
490
     * @return bool
491
     */
492 1
    protected function isTypeComment($commentText)
493
    {
494 1
        return strpos($commentText, self::TYPE_TAG) !== false || strpos($commentText, '@type') !== false;
495
    }//end isTypeComment()
496
497
498
    /**
499
     * Finds first token on a line.
500
     *
501
     * @param File $phpcsFile All the tokens found in the document.
502
     * @param int  $start     Start from token.
503
     *
504
     * @return int | bool
505
     */
506 1
    public function findFirstOnLine(File $phpcsFile, $start)
507
    {
508 1
        $tokens = $phpcsFile->getTokens();
509
510 1
        for ($i = $start; $i >= 0; $i--) {
511 1
            if ($tokens[$i]['line'] === $tokens[$start]['line']) {
512 1
                continue;
513
            }
514
515 1
            return ($i + 1);
516
        }
517
518
        return false;
519
    }//end findFirstOnLine()
520
521
522
    /**
523
     * Finds last token on a line.
524
     *
525
     * @param File $phpcsFile All the tokens found in the document.
526
     * @param int  $start     Start from token.
527
     *
528
     * @return int | bool
529
     */
530 1
    public function findLastOnLine(File $phpcsFile, $start)
531
    {
532 1
        $tokens = $phpcsFile->getTokens();
533
534 1
        for ($i = $start; $i <= $phpcsFile->numTokens; $i++) {
535 1
            if ($tokens[$i]['line'] === $tokens[$start]['line']) {
536 1
                continue;
537
            }
538
539 1
            return ($i - 1);
540
        }
541
542
        return false;
543
    }//end findLastOnLine()
544
}//end class
545
546
547
/**
548
 * Type comment structure
549
 */
550
class TypeCommentStructure
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
551
{
552
553
    /**
554
     * Tag content pointer.
555
     *
556
     * @var int
557
     */
558
    public $tagContentPtr;
559
560
    /**
561
     * Tag content.
562
     *
563
     * @var string
564
     */
565
    public $tagContent;
566
567
    /**
568
     * Class name.
569
     *
570
     * @var string
571
     */
572
    public $className;
573
574
    /**
575
     * Variable name.
576
     *
577
     * @var string
578
     */
579
    public $variableName;
580
581
    /**
582
     * Description.
583
     *
584
     * @var string
585
     */
586
    public $description;
587
588
    /**
589
     * Token sequence.
590
     *
591
     * @var array
592
     */
593
    protected $tokenSequence = array(
594
                                1 => T_DOC_COMMENT_WHITESPACE,
595
                                2 => T_DOC_COMMENT_TAG,
596
                                3 => T_DOC_COMMENT_WHITESPACE,
597
                                4 => T_DOC_COMMENT_STRING,
598
                               );
599
600
601
    /**
602
     * Creates from tokens.
603
     *
604
     * @param File $phpcsFile The file being scanned.
605
     * @param int  $stackPtr  The position of the current token
606
     *                        in the stack passed in $tokens.
607
     */
608 1
    public function __construct(File $phpcsFile, $stackPtr)
609
    {
610 1
        $tokens = $phpcsFile->getTokens();
611
612
        // Ensure, that DocBlock is built from correct tokens.
613 1
        for ($i = 1; $i <= 4; $i++) {
614 1
            if ($tokens[($stackPtr + $i)]['code'] !== $this->tokenSequence[$i]) {
615 1
                return;
616
            }
617 1
        }
618
619 1
        $this->tagContentPtr = ($stackPtr + 4);
620 1
        $this->tagContent    = $tokens[$this->tagContentPtr]['content'];
621 1
        $tagContentParts     = array_values(array_filter(explode(' ', $this->tagContent)));
622
623 1
        if (isset($tagContentParts[0]) === true) {
624 1
            $this->className = $tagContentParts[0];
625 1
        }
626
627 1
        if (isset($tagContentParts[1]) === true) {
628 1
            $this->variableName = $tagContentParts[1];
629 1
        }
630
631 1
        if (count($tagContentParts) > 2) {
632 1
            $this->description = implode(' ', array_slice($tagContentParts, 2)).' ';
633 1
        } else {
634 1
            $this->description = '';
635
        }
636 1
    }//end __construct()
637
638
639
    /**
640
     * Detects if given text is a variable.
641
     *
642
     * @param string $text Text.
643
     *
644
     * @return bool
645
     */
646 1
    public function isVariable($text)
647
    {
648 1
        return substr($text, 0, 1) === '$';
649
    }//end isVariable()
650
}//end class
651