PropertyCommentSniff::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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