PropertyCommentSniff   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 222
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 22
lcom 1
cbo 3
dl 0
loc 222
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
D processTokenWithinScope() 0 157 20
A processTokenOutsideScope() 0 4 1
1
<?php
2
3
/**
4
 * This file is part of the mo4-coding-standard (phpcs standard)
5
 *
6
 * PHP version 5
7
 *
8
 * @category PHP
9
 * @package  PHP_CodeSniffer-MO4
10
 * @author   Xaver Loppenstedt <[email protected]>
11
 * @license  http://spdx.org/licenses/MIT MIT License
12
 * @version  GIT: master
13
 * @link     https://github.com/Mayflower/mo4-coding-standard
14
 */
15
16
namespace MO4\Sniffs\Commenting;
17
18
use PHP_CodeSniffer\Files\File;
19
use PHP_CodeSniffer\Sniffs\AbstractScopeSniff;
20
21
/**
22
 * Property Comment Sniff sniff.
23
 *
24
 * Doc blocks of class properties must be multiline and have exactly one '@var'
25
 * annotation.
26
 *
27
 * @category  PHP
28
 * @package   PHP_CodeSniffer-MO4
29
 * @author    Xaver Loppenstedt <[email protected]>
30
 * @copyright 2013-2014 Xaver Loppenstedt, some rights reserved.
31
 * @license   http://spdx.org/licenses/MIT MIT License
32
 * @link      https://github.com/Mayflower/mo4-coding-standard
33
 */
34
class PropertyCommentSniff extends AbstractScopeSniff
35
{
36
    /**
37
     * A list of tokenizers this sniff supports.
38
     *
39
     * @var array
40
     */
41
    public $supportedTokenizers = array('PHP');
42
43
    /**
44
     * List of token types this sniff analyzes
45
     *
46
     * @var array
47
     */
48
    private $myTokenTypes = array(
49
                             T_VARIABLE,
50
                             T_CONST,
51
                            );
52
53
54
    /**
55
     * Construct PropertyCommentSniff
56
     *
57
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException
58
     */
59
    public function __construct()
60
    {
61
        $scopes = array(T_CLASS);
62
63
        parent::__construct($scopes, $this->myTokenTypes, true);
64
65
    }//end __construct()
66
67
68
    /**
69
     * Processes a token that is found within the scope that this test is
70
     * listening to.
71
     *
72
     * @param File $phpcsFile The file where this token was found.
73
     * @param int  $stackPtr  The position in the stack where this
74
     *                        token was found.
75
     * @param int  $currScope The position in the tokens array that
76
     *                        opened the scope that this test is
77
     *                        listening for.
78
     *
79
     * @return void
80
     */
81
    protected function processTokenWithinScope(
82
        File $phpcsFile,
83
        $stackPtr,
84
        $currScope
85
    ) {
86
        $find   = array(
87
                   T_COMMENT,
88
                   T_DOC_COMMENT_CLOSE_TAG,
89
                   T_CLASS,
90
                   T_CONST,
91
                   T_FUNCTION,
92
                   T_VARIABLE,
93
                   T_OPEN_TAG,
94
                  );
95
        $tokens = $phpcsFile->getTokens();
96
97
        // Before even checking the docblocks above the current var/const,
98
        // check if we have a single line comment after it on the same line,
99
        // and if that one is OK.
100
        $postComment = $phpcsFile->findNext(
101
            array(T_DOC_COMMENT_OPEN_TAG),
102
            $stackPtr
103
        );
104
        if ($postComment !== false
105
            && $tokens[$postComment]['line'] === $tokens[$stackPtr]['line']
106
        ) {
107
            if ($tokens[$postComment]['content'] === '/**') {
108
                // That's an error already.
109
                $phpcsFile->addError(
110
                    'no doc blocks are allowed after declaration',
111
                    $stackPtr,
112
                    'NoDocBlockAllowed'
113
                );
114
            } else {
115
                $postCommentEnd  = $tokens[$postComment]['comment_closer'];
116
                $postCommentLine = $tokens[$postCommentEnd]['line'];
117
                if ($tokens[$postComment]['line'] !== $postCommentLine) {
118
                    $phpcsFile->addError(
119
                        'no multiline comments after declarations allowed',
120
                        $stackPtr,
121
                        'MustBeOneLine'
122
                    );
123
                }
124
            }
125
        }
126
127
        // Don't do constants for now.
128
        if ($tokens[$stackPtr]['code'] === T_CONST) {
129
            return;
130
        }
131
132
        $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1));
133
        if ($commentEnd === false) {
134
            return;
135
        }
136
137
        $commentEnd = (int) $commentEnd;
138
139
        $conditions    = $tokens[$commentEnd]['conditions'];
140
        $lastCondition = array_pop($conditions);
141
        if ($lastCondition !== T_CLASS) {
142
            return;
143
        }
144
145
        $code = $tokens[$commentEnd]['code'];
146
147
        if ($code === T_DOC_COMMENT_CLOSE_TAG) {
148
            $commentStart = $tokens[$commentEnd]['comment_opener'];
149
150
            // Check if this comment is completely in one line,
151
            // above the current line,
152
            // and has a variable preceding it in the same line.
153
            // If yes, it doesn't count.
154
            $firstTokenOnLine = $phpcsFile->findFirstOnLine(
155
                $this->myTokenTypes,
156
                $commentEnd
157
            );
158
            if ($firstTokenOnLine !== false
159
                && $tokens[$commentStart]['line'] === $tokens[$commentEnd]['line']
160
                && $tokens[$stackPtr]['line'] > $tokens[$commentEnd]['line']
161
            ) {
162
                return;
163
            }
164
165
            $isCommentOneLiner
166
                = $tokens[$commentStart]['line'] === $tokens[$commentEnd]['line'];
167
168
            $length         = ($commentEnd - $commentStart + 1);
169
            $tokensAsString = $phpcsFile->getTokensAsString(
170
                $commentStart,
171
                $length
172
            );
173
174
            $varCount = (count(preg_split('/\s+@var\s+/', $tokensAsString)) - 1);
175
            if (($varCount === 0) || ($varCount > 1)) {
176
                $phpcsFile->addError(
177
                    'property doc comment must have exactly one @var annotation',
178
                    $commentStart,
179
                    'MustHaveOneVarAnnotationDefined'
180
                );
181
            }
182
183
            if ($varCount === 1) {
184
                if ($isCommentOneLiner === true) {
185
                    $fix = $phpcsFile->addFixableError(
186
                        'property doc comment must be multi line',
187
                        $commentEnd,
188
                        'NotMultiLineDocBlock'
189
                    );
190
191
                    if ($fix === true) {
192
                        $phpcsFile->fixer->beginChangeset();
193
                        $phpcsFile->fixer->addContent($commentStart, "\n     *");
194
                        $phpcsFile->fixer->replaceToken(
195
                            ($commentEnd - 1),
196
                            rtrim($tokens[($commentEnd - 1)]['content'])
197
                        );
198
                        $phpcsFile->fixer->addContentBefore($commentEnd, "\n     ");
199
                        $phpcsFile->fixer->endChangeset();
200
                    }
201
                }
202
            } else {
203
                if ($isCommentOneLiner === true) {
204
                    $phpcsFile->addError(
205
                        'property doc comment must be multi line',
206
                        $commentEnd,
207
                        'NotMultiLineDocBlock'
208
                    );
209
                }
210
            }//end if
211
        } else if ($code === T_COMMENT) {
212
            // It seems that when we are in here,
213
            // then we have a line comment at $commentEnd.
214
            // Now, check if the same comment has
215
            // a variable definition on the same line.
216
            // If yes, it doesn't count.
217
            $firstOnLine = $phpcsFile->findFirstOnLine(
218
                $this->myTokenTypes,
219
                $commentEnd
220
            );
221
222
            if ($firstOnLine === false) {
223
                $commentStart = $phpcsFile->findPrevious(
224
                    T_COMMENT,
225
                    $commentEnd,
226
                    null,
227
                    true
228
                );
229
                $phpcsFile->addError(
230
                    'property doc comment must begin with /**',
231
                    ($commentStart + 1),
232
                    'NotADocBlock'
233
                );
234
            }
235
        }//end if
236
237
    }//end processTokenWithinScope()
238
239
240
    /**
241
     * Process tokens outside scope.
242
     *
243
     * @param File $phpcsFile The file where this token was found.
244
     * @param int  $stackPtr  The position in the stack where this
245
     *                        token was found.
246
     *
247
     * @return void
248
     */
249
    protected function processTokenOutsideScope(File $phpcsFile, $stackPtr)
250
    {
251
252
    }//end processTokenOutsideScope()
253
254
255
}//end class
256