Completed
Push — master ( 906394...0d82de )
by Alexander
02:07
created

InlineCommentSniff   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 303
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 3

Test Coverage

Coverage 95.7%
Metric Value
wmc 50
lcom 0
cbo 3
dl 0
loc 303
ccs 178
cts 186
cp 0.957
rs 8.6207

2 Methods

Rating   Name   Duplication   Size   Complexity  
A register() 0 8 1
F process() 0 262 49

How to fix   Complexity   

Complex Class

Complex classes like InlineCommentSniff often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use InlineCommentSniff, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * CodingStandard_Sniffs_Commenting_InlineCommentSniff.
4
 *
5
 * PHP version 5
6
 *
7
 * @category PHP
8
 * @package  PHP_CodeSniffer
9
 * @author   Greg Sherwood <[email protected]>
10
 * @author   Marc McIntyre <[email protected]>
11
 * @author   Alexander Obuhovich <[email protected]>
12
 * @license  https://github.com/aik099/CodingStandard/blob/master/LICENSE BSD 3-Clause
13
 * @link     https://github.com/aik099/CodingStandard
14
 */
15
16
/**
17
 * CodingStandard_Sniffs_Commenting_InlineCommentSniff.
18
 *
19
 * Checks that there is adequate spacing between comments.
20
 *
21
 * @category PHP
22
 * @package  PHP_CodeSniffer
23
 * @author   Greg Sherwood <[email protected]>
24
 * @author   Marc McIntyre <[email protected]>
25
 * @author   Alexander Obuhovich <[email protected]>
26
 * @license  https://github.com/aik099/CodingStandard/blob/master/LICENSE BSD 3-Clause
27
 * @link     https://github.com/aik099/CodingStandard
28
 */
29
class CodingStandard_Sniffs_Commenting_InlineCommentSniff implements PHP_CodeSniffer_Sniff
1 ignored issue
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
Coding Style introduced by
This class is not in CamelCase format.

Classes in PHP are usually named in CamelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. The whole name starts with a capital letter as well.

Thus the name database provider becomes DatabaseProvider.

Loading history...
30
{
31
32
    /**
33
     * A list of tokenizers this sniff supports.
34
     *
35
     * @var array
36
     */
37
    public $supportedTokenizers = array(
38
                                   'PHP',
39
                                   'JS',
40
                                  );
41
42
43
    /**
44
     * Returns an array of tokens this test wants to listen for.
45
     *
46
     * @return integer[]
47
     */
48 1
    public function register()
49
    {
50
        return array(
51 1
                T_COMMENT,
52 1
                T_DOC_COMMENT_OPEN_TAG,
53 1
               );
54
55
    }//end register()
56
57
58
    /**
59
     * Processes this test, when one of its tokens is encountered.
60
     *
61
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
62
     * @param int                  $stackPtr  The position of the current token in the
63
     *                                        stack passed in $tokens.
64
     *
65
     * @return void
66
     */
67 1
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
68
    {
69 1
        $tokens = $phpcsFile->getTokens();
70
71
        // If this is a function/class/interface doc block comment, skip it.
72
        // We are only interested in inline doc block comments, which are
73
        // not allowed.
74 1
        if ($tokens[$stackPtr]['code'] === T_DOC_COMMENT_OPEN_TAG) {
75 1
            $nextToken = $phpcsFile->findNext(
76 1
                PHP_CodeSniffer_Tokens::$emptyTokens,
77 1
                ($stackPtr + 1),
78 1
                null,
79
                true
80 1
            );
81
82
            $ignore = array(
83 1
                       T_CLASS,
84 1
                       T_INTERFACE,
85 1
                       T_TRAIT,
86 1
                       T_FUNCTION,
87 1
                       T_CLOSURE,
88 1
                       T_PUBLIC,
89 1
                       T_PRIVATE,
90 1
                       T_PROTECTED,
91 1
                       T_FINAL,
92 1
                       T_STATIC,
93 1
                       T_ABSTRACT,
94 1
                       T_CONST,
95 1
                       T_PROPERTY,
96 1
                      );
97
98 1
            if (in_array($tokens[$nextToken]['code'], $ignore) === true) {
99 1
                return;
100
            }
101
102 1
            if ($phpcsFile->tokenizerType === 'JS') {
103
                // We allow block comments if a function or object
104
                // is being assigned to a variable.
105 1
                $ignore    = PHP_CodeSniffer_Tokens::$emptyTokens;
106 1
                $ignore[]  = T_EQUAL;
107 1
                $ignore[]  = T_STRING;
108 1
                $ignore[]  = T_OBJECT_OPERATOR;
109 1
                $nextToken = $phpcsFile->findNext($ignore, ($nextToken + 1), null, true);
110 1
                if ($tokens[$nextToken]['code'] === T_FUNCTION
111 1
                    || $tokens[$nextToken]['code'] === T_CLOSURE
112 1
                    || $tokens[$nextToken]['code'] === T_OBJECT
113 1
                    || $tokens[$nextToken]['code'] === T_PROTOTYPE
114 1
                ) {
115 1
                    return;
116
                }
117 1
            }
118
119 1
            $prevToken = $phpcsFile->findPrevious(
120 1
                PHP_CodeSniffer_Tokens::$emptyTokens,
121 1
                ($stackPtr - 1),
122 1
                null,
123
                true
124 1
            );
125
126 1
            if ($tokens[$prevToken]['code'] === T_OPEN_TAG) {
127
                return;
128
            }
129
130
            // Only error once per comment.
131 1
            if ($tokens[$stackPtr]['content'] === '/**') {
132 1
                $commentEnd  = $phpcsFile->findNext(T_DOC_COMMENT_CLOSE_TAG, ($stackPtr + 1));
133 1
                $commentText = $phpcsFile->getTokensAsString($stackPtr, (($commentEnd - $stackPtr) + 1));
134
135 1
                if (strpos($commentText, '@var') === false && strpos($commentText, '@type') === false) {
136 1
                    $error = 'Inline doc block comments are not allowed; use "/* Comment */" or "// Comment" instead';
137 1
                    $phpcsFile->addError($error, $stackPtr, 'DocBlock');
138 1
                }
139 1
            }
140 1
        }//end if
141
142 1
        if ($tokens[$stackPtr]['content']{0} === '#') {
143 1
            $error = 'Perl-style comments are not allowed; use "// Comment" instead';
144 1
            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'WrongStyle');
145 1
            if ($fix === true) {
146 1
                $comment = ltrim($tokens[$stackPtr]['content'], "# \t");
147 1
                $phpcsFile->fixer->replaceToken($stackPtr, "// $comment");
148 1
            }
149 1
        }
150
151
        // We don't want:
152
        // - end of block comments, if the last comment is a closing curly brace
153
        // - closing comment of previous control structure (see "WhiteSpace.ControlStructureSpacing")
154 1
        $previousContent = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
155 1
        if ($tokens[$previousContent]['line'] === $tokens[$stackPtr]['line']
156 1
            || $tokens[$previousContent]['line'] === ($tokens[$stackPtr]['line'] - 1)
157 1
        ) {
158 1
            if ($tokens[$previousContent]['code'] === T_CLOSE_CURLY_BRACKET) {
159 1
                return;
160
            }
161
162
            // Special case for JS files.
163 1
            if ($tokens[$previousContent]['code'] === T_COMMA
164 1
                || $tokens[$previousContent]['code'] === T_SEMICOLON
165 1
            ) {
166 1
                $lastContent = $phpcsFile->findPrevious(T_WHITESPACE, ($previousContent - 1), null, true);
167 1
                if ($tokens[$lastContent]['code'] === T_CLOSE_CURLY_BRACKET) {
168 1
                    return;
169
                }
170 1
            }
171 1
        }
172
173 1
        $comment = rtrim($tokens[$stackPtr]['content']);
174
175
        // Only want inline comments.
176 1
        if (substr($comment, 0, 2) !== '//') {
177 1
            return;
178
        }
179
180 1
        if (trim(substr($comment, 2)) !== '') {
181 1
            $spaceCount = 0;
182 1
            $tabFound   = false;
183
184 1
            $commentLength = strlen($comment);
185 1
            for ($i = 2; $i < $commentLength; $i++) {
186 1
                if ($comment[$i] === "\t") {
187
                    $tabFound = true;
188
                    break;
189
                }
190
191 1
                if ($comment[$i] !== ' ') {
192 1
                    break;
193
                }
194
195 1
                $spaceCount++;
196 1
            }
197
198 1
            $fix = false;
199 1
            if ($tabFound === true) {
200
                $error = 'Tab found before comment text; expected "// %s" but found "%s"';
201
                $data  = array(
202
                          ltrim(substr($comment, 2)),
203
                          $comment,
204
                         );
205
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'TabBefore', $data);
206 1
            } else if ($spaceCount === 0) {
207 1
                $error = 'No space found before comment text; expected "// %s" but found "%s"';
208
                $data  = array(
209 1
                          substr($comment, 2),
210 1
                          $comment,
211 1
                         );
212 1
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'NoSpaceBefore', $data);
213 1
            } else if ($spaceCount > 1) {
214 1
                $error = 'Expected 1 space before comment text but found %s; use block comment if you need indentation';
215
                $data  = array(
216 1
                          $spaceCount,
217 1
                          substr($comment, (2 + $spaceCount)),
218 1
                          $comment,
219 1
                         );
220 1
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingBefore', $data);
221 1
            }//end if
222
223 1
            if ($fix === true) {
224 1
                $newComment = '// '.ltrim($tokens[$stackPtr]['content'], "/\t ");
225 1
                $phpcsFile->fixer->replaceToken($stackPtr, $newComment);
226 1
            }
227 1
        }//end if
228
229
        // The below section determines if a comment block is correctly capitalised,
230
        // and ends in a full-stop. It will find the last comment in a block, and
231
        // work its way up.
232 1
        $nextComment = $phpcsFile->findNext(array(T_COMMENT), ($stackPtr + 1), null, false);
233
234 1
        if (($nextComment !== false) && (($tokens[$nextComment]['line']) === ($tokens[$stackPtr]['line'] + 1))) {
235 1
            return;
236
        }
237
238 1
        $lastComment = $stackPtr;
239 1
        while (($topComment = $phpcsFile->findPrevious(array(T_COMMENT), ($lastComment - 1), null, false)) !== false) {
240 1
            if ($tokens[$topComment]['line'] !== ($tokens[$lastComment]['line'] - 1)) {
241 1
                break;
242
            }
243
244 1
            $lastComment = $topComment;
245 1
        }
246
247 1
        $topComment  = $lastComment;
248 1
        $commentText = '';
249
250 1
        for ($i = $topComment; $i <= $stackPtr; $i++) {
251 1
            if ($tokens[$i]['code'] === T_COMMENT) {
252 1
                $commentText .= trim(substr($tokens[$i]['content'], 2));
253 1
            }
254 1
        }
255
256 1
        if ($commentText === '') {
257 1
            $error = 'Blank comments are not allowed';
258 1
            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'Empty');
259 1
            if ($fix === true) {
260 1
                $phpcsFile->fixer->replaceToken($stackPtr, '');
261 1
            }
262
263 1
            return;
264
        }
265
266
        // If the first character is now uppercase and is not
267
        // a non-letter character, throw an error.
268
        // \p{Lu} : an uppercase letter that has a lowercase variant.
269
        // \P{L}  : a non-letter character.
270 1
        if (preg_match('/\p{Lu}|\P{L}/u', $commentText[0]) === 0) {
271 1
            $error = 'Inline comments must start with a capital letter';
272 1
            $phpcsFile->addError($error, $topComment, 'NotCapital');
273 1
        }
274
275
        // Only check the end of comment character if the start of the comment
276
        // is a letter, indicating that the comment is just standard text.
277 1
        if (preg_match('/\P{L}/u', $commentText[0]) === 0) {
278 1
            $commentCloser   = $commentText[(strlen($commentText) - 1)];
279
            $acceptedClosers = array(
280 1
                                'full-stops'        => '.',
281 1
                                'exclamation marks' => '!',
282 1
                                'or question marks' => '?',
283 1
                               );
284
285 1
            if (in_array($commentCloser, $acceptedClosers) === false) {
286 1
                $error = 'Inline comments must end in %s';
287 1
                $ender = '';
288 1
                foreach ($acceptedClosers as $closerName => $symbol) {
289 1
                    $ender .= ' '.$closerName.',';
290 1
                }
291
292 1
                $ender = trim($ender, ' ,');
293 1
                $data  = array($ender);
294 1
                $phpcsFile->addError($error, $stackPtr, 'InvalidEndChar', $data);
295 1
            }
296 1
        }
297
298
        // Finally, the line below the last comment cannot be empty if this inline
299
        // comment is on a line by itself.
300 1
        if ($tokens[$previousContent]['line'] < $tokens[$stackPtr]['line']) {
301 1
            for ($i = ($stackPtr + 1); $i < $phpcsFile->numTokens; $i++) {
302 1
                if ($tokens[$i]['line'] === ($tokens[$stackPtr]['line'] + 1)) {
303 1
                    if ($tokens[$i]['code'] !== T_WHITESPACE) {
304 1
                        return;
305
                    }
306 1
                } else if ($tokens[$i]['line'] > ($tokens[$stackPtr]['line'] + 1)) {
307 1
                    break;
308
                }
309 1
            }
310
311 1
            $error = 'There must be no blank line following an inline comment';
312 1
            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingAfter');
313 1
            if ($fix === true) {
314 1
                $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
315 1
                $phpcsFile->fixer->beginChangeset();
316 1
                for ($i = ($stackPtr + 1); $i < $next; $i++) {
317 1
                    if ($tokens[$i]['line'] === $tokens[$next]['line']) {
318 1
                        break;
319
                    }
320
321 1
                    $phpcsFile->fixer->replaceToken($i, '');
322 1
                }
323
324 1
                $phpcsFile->fixer->endChangeset();
325 1
            }
326 1
        }//end if
327
328 1
    }//end process()
329
330
331
}//end class
332