Completed
Branch development (b1b115)
by Johannes
10:28
created

FunctionDeclarationSniff::process()   F

Complexity

Conditions 30
Paths > 20000

Size

Total Lines 117

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 117
rs 0
cc 30
nc 37961
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Ensure single and multi-line function declarations are defined correctly.
4
 *
5
 * @author    Greg Sherwood <[email protected]>
6
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
7
 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
8
 */
9
10
namespace PHP_CodeSniffer\Standards\PEAR\Sniffs\Functions;
11
12
use PHP_CodeSniffer\Sniffs\Sniff;
13
use PHP_CodeSniffer\Files\File;
14
use PHP_CodeSniffer\Util\Tokens;
15
use PHP_CodeSniffer\Standards\Generic\Sniffs\Functions\OpeningFunctionBraceKernighanRitchieSniff;
16
use PHP_CodeSniffer\Standards\Generic\Sniffs\Functions\OpeningFunctionBraceBsdAllmanSniff;
17
18
class FunctionDeclarationSniff implements Sniff
19
{
20
21
    /**
22
     * A list of tokenizers this sniff supports.
23
     *
24
     * @var array
25
     */
26
    public $supportedTokenizers = [
27
        'PHP',
28
        'JS',
29
    ];
30
31
    /**
32
     * The number of spaces code should be indented.
33
     *
34
     * @var integer
35
     */
36
    public $indent = 4;
37
38
39
    /**
40
     * Returns an array of tokens this test wants to listen for.
41
     *
42
     * @return array
43
     */
44
    public function register()
45
    {
46
        return [
47
            T_FUNCTION,
48
            T_CLOSURE,
49
        ];
50
51
    }//end register()
52
53
54
    /**
55
     * Processes this test, when one of its tokens is encountered.
56
     *
57
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
58
     * @param int                         $stackPtr  The position of the current token
59
     *                                               in the stack passed in $tokens.
60
     *
61
     * @return void
62
     */
63
    public function process(File $phpcsFile, $stackPtr)
64
    {
65
        $tokens = $phpcsFile->getTokens();
66
67
        if (isset($tokens[$stackPtr]['parenthesis_opener']) === false
68
            || isset($tokens[$stackPtr]['parenthesis_closer']) === false
69
            || $tokens[$stackPtr]['parenthesis_opener'] === null
70
            || $tokens[$stackPtr]['parenthesis_closer'] === null
71
        ) {
72
            return;
73
        }
74
75
        $openBracket  = $tokens[$stackPtr]['parenthesis_opener'];
76
        $closeBracket = $tokens[$stackPtr]['parenthesis_closer'];
77
78
        if (strtolower($tokens[$stackPtr]['content']) === 'function') {
79
            // Must be one space after the FUNCTION keyword.
80
            if ($tokens[($stackPtr + 1)]['content'] === $phpcsFile->eolChar) {
81
                $spaces = 'newline';
82
            } else if ($tokens[($stackPtr + 1)]['code'] === T_WHITESPACE) {
83
                $spaces = strlen($tokens[($stackPtr + 1)]['content']);
84
            } else {
85
                $spaces = 0;
86
            }
87
88
            if ($spaces !== 1) {
89
                $error = 'Expected 1 space after FUNCTION keyword; %s found';
90
                $data  = [$spaces];
91
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterFunction', $data);
92
                if ($fix === true) {
93
                    if ($spaces === 0) {
94
                        $phpcsFile->fixer->addContent($stackPtr, ' ');
95
                    } else {
96
                        $phpcsFile->fixer->replaceToken(($stackPtr + 1), ' ');
97
                    }
98
                }
99
            }
100
        }//end if
101
102
        // Must be no space before the opening parenthesis. For closures, this is
103
        // enforced by the previous check because there is no content between the keywords
104
        // and the opening parenthesis.
105
        // Unfinished closures are tokenized as T_FUNCTION however, and can be excluded
106
        // by checking for the scope_opener.
107
        if ($tokens[$stackPtr]['code'] === T_FUNCTION
108
            && isset($tokens[$stackPtr]['scope_opener']) === true
109
        ) {
110
            if ($tokens[($openBracket - 1)]['content'] === $phpcsFile->eolChar) {
111
                $spaces = 'newline';
112
            } else if ($tokens[($openBracket - 1)]['code'] === T_WHITESPACE) {
113
                $spaces = strlen($tokens[($openBracket - 1)]['content']);
114
            } else {
115
                $spaces = 0;
116
            }
117
118
            if ($spaces !== 0) {
119
                $error = 'Expected 0 spaces before opening parenthesis; %s found';
120
                $data  = [$spaces];
121
                $fix   = $phpcsFile->addFixableError($error, $openBracket, 'SpaceBeforeOpenParen', $data);
122
                if ($fix === true) {
123
                    $phpcsFile->fixer->replaceToken(($openBracket - 1), '');
124
                }
125
            }
126
        }//end if
127
128
        // Must be one space before and after USE keyword for closures.
129
        if ($tokens[$stackPtr]['code'] === T_CLOSURE) {
130
            $use = $phpcsFile->findNext(T_USE, ($closeBracket + 1), $tokens[$stackPtr]['scope_opener']);
131
            if ($use !== false) {
132
                if ($tokens[($use + 1)]['code'] !== T_WHITESPACE) {
133
                    $length = 0;
134
                } else if ($tokens[($use + 1)]['content'] === "\t") {
135
                    $length = '\t';
136
                } else {
137
                    $length = strlen($tokens[($use + 1)]['content']);
138
                }
139
140
                if ($length !== 1) {
141
                    $error = 'Expected 1 space after USE keyword; found %s';
142
                    $data  = [$length];
143
                    $fix   = $phpcsFile->addFixableError($error, $use, 'SpaceAfterUse', $data);
144
                    if ($fix === true) {
145
                        if ($length === 0) {
146
                            $phpcsFile->fixer->addContent($use, ' ');
147
                        } else {
148
                            $phpcsFile->fixer->replaceToken(($use + 1), ' ');
149
                        }
150
                    }
151
                }
152
153
                if ($tokens[($use - 1)]['code'] !== T_WHITESPACE) {
154
                    $length = 0;
155
                } else if ($tokens[($use - 1)]['content'] === "\t") {
156
                    $length = '\t';
157
                } else {
158
                    $length = strlen($tokens[($use - 1)]['content']);
159
                }
160
161
                if ($length !== 1) {
162
                    $error = 'Expected 1 space before USE keyword; found %s';
163
                    $data  = [$length];
164
                    $fix   = $phpcsFile->addFixableError($error, $use, 'SpaceBeforeUse', $data);
165
                    if ($fix === true) {
166
                        if ($length === 0) {
167
                            $phpcsFile->fixer->addContentBefore($use, ' ');
168
                        } else {
169
                            $phpcsFile->fixer->replaceToken(($use - 1), ' ');
170
                        }
171
                    }
172
                }
173
            }//end if
174
        }//end if
175
176
        if ($this->isMultiLineDeclaration($phpcsFile, $stackPtr, $openBracket, $tokens) === true) {
177
            $this->processMultiLineDeclaration($phpcsFile, $stackPtr, $tokens);
178
        } else {
179
            $this->processSingleLineDeclaration($phpcsFile, $stackPtr, $tokens);
180
        }
181
182
    }//end process()
183
184
185
    /**
186
     * Determine if this is a multi-line function declaration.
187
     *
188
     * @param \PHP_CodeSniffer\Files\File $phpcsFile   The file being scanned.
189
     * @param int                         $stackPtr    The position of the current token
190
     *                                                 in the stack passed in $tokens.
191
     * @param int                         $openBracket The position of the opening bracket
192
     *                                                 in the stack passed in $tokens.
193
     * @param array                       $tokens      The stack of tokens that make up
194
     *                                                 the file.
195
     *
196
     * @return void
197
     */
198
    public function isMultiLineDeclaration($phpcsFile, $stackPtr, $openBracket, $tokens)
199
    {
200
        $closeBracket = $tokens[$openBracket]['parenthesis_closer'];
201
        if ($tokens[$openBracket]['line'] !== $tokens[$closeBracket]['line']) {
202
            return true;
203
        }
204
205
        // Closures may use the USE keyword and so be multi-line in this way.
206
        if ($tokens[$stackPtr]['code'] === T_CLOSURE) {
207
            $use = $phpcsFile->findNext(T_USE, ($closeBracket + 1), $tokens[$stackPtr]['scope_opener']);
208
            if ($use !== false) {
209
                // If the opening and closing parenthesis of the use statement
210
                // are also on the same line, this is a single line declaration.
211
                $open  = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($use + 1));
212
                $close = $tokens[$open]['parenthesis_closer'];
213
                if ($tokens[$open]['line'] !== $tokens[$close]['line']) {
214
                    return true;
215
                }
216
            }
217
        }
218
219
        return false;
220
221
    }//end isMultiLineDeclaration()
222
223
224
    /**
225
     * Processes single-line declarations.
226
     *
227
     * Just uses the Generic BSD-Allman brace sniff.
228
     *
229
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
230
     * @param int                         $stackPtr  The position of the current token
231
     *                                               in the stack passed in $tokens.
232
     * @param array                       $tokens    The stack of tokens that make up
233
     *                                               the file.
234
     *
235
     * @return void
236
     */
237
    public function processSingleLineDeclaration($phpcsFile, $stackPtr, $tokens)
238
    {
239
        if ($tokens[$stackPtr]['code'] === T_CLOSURE) {
240
            $sniff = new OpeningFunctionBraceKernighanRitchieSniff();
241
        } else {
242
            $sniff = new OpeningFunctionBraceBsdAllmanSniff();
243
        }
244
245
        $sniff->checkClosures = true;
246
        $sniff->process($phpcsFile, $stackPtr);
247
248
    }//end processSingleLineDeclaration()
249
250
251
    /**
252
     * Processes multi-line declarations.
253
     *
254
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
255
     * @param int                         $stackPtr  The position of the current token
256
     *                                               in the stack passed in $tokens.
257
     * @param array                       $tokens    The stack of tokens that make up
258
     *                                               the file.
259
     *
260
     * @return void
261
     */
262
    public function processMultiLineDeclaration($phpcsFile, $stackPtr, $tokens)
263
    {
264
        // We need to work out how far indented the function
265
        // declaration itself is, so we can work out how far to
266
        // indent parameters.
267
        $functionIndent = 0;
268
        for ($i = ($stackPtr - 1); $i >= 0; $i--) {
269
            if ($tokens[$i]['line'] !== $tokens[$stackPtr]['line']) {
270
                break;
271
            }
272
        }
273
274
        // Move $i back to the line the function is or to 0.
275
        $i++;
276
277
        if ($tokens[$i]['code'] === T_WHITESPACE) {
278
            $functionIndent = strlen($tokens[$i]['content']);
279
        }
280
281
        // The closing parenthesis must be on a new line, even
282
        // when checking abstract function definitions.
283
        $closeBracket = $tokens[$stackPtr]['parenthesis_closer'];
284
        $prev         = $phpcsFile->findPrevious(
285
            T_WHITESPACE,
286
            ($closeBracket - 1),
287
            null,
288
            true
289
        );
290
291
        if ($tokens[$closeBracket]['line'] !== $tokens[$tokens[$closeBracket]['parenthesis_opener']]['line']) {
292
            if ($tokens[$prev]['line'] === $tokens[$closeBracket]['line']) {
293
                $error = 'The closing parenthesis of a multi-line function declaration must be on a new line';
294
                $fix   = $phpcsFile->addFixableError($error, $closeBracket, 'CloseBracketLine');
295
                if ($fix === true) {
296
                    $phpcsFile->fixer->addNewlineBefore($closeBracket);
297
                }
298
            }
299
        }
300
301
        // If this is a closure and is using a USE statement, the closing
302
        // parenthesis we need to look at from now on is the closing parenthesis
303
        // of the USE statement.
304
        if ($tokens[$stackPtr]['code'] === T_CLOSURE) {
305
            $use = $phpcsFile->findNext(T_USE, ($closeBracket + 1), $tokens[$stackPtr]['scope_opener']);
306
            if ($use !== false) {
307
                $open         = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($use + 1));
308
                $closeBracket = $tokens[$open]['parenthesis_closer'];
309
310
                $prev = $phpcsFile->findPrevious(
311
                    T_WHITESPACE,
312
                    ($closeBracket - 1),
313
                    null,
314
                    true
315
                );
316
317
                if ($tokens[$closeBracket]['line'] !== $tokens[$tokens[$closeBracket]['parenthesis_opener']]['line']) {
318
                    if ($tokens[$prev]['line'] === $tokens[$closeBracket]['line']) {
319
                        $error = 'The closing parenthesis of a multi-line use declaration must be on a new line';
320
                        $fix   = $phpcsFile->addFixableError($error, $closeBracket, 'UseCloseBracketLine');
321
                        if ($fix === true) {
322
                            $phpcsFile->fixer->addNewlineBefore($closeBracket);
323
                        }
324
                    }
325
                }
326
            }//end if
327
        }//end if
328
329
        // Each line between the parenthesis should be indented 4 spaces.
330
        $openBracket = $tokens[$stackPtr]['parenthesis_opener'];
331
        $lastLine    = $tokens[$openBracket]['line'];
332
        for ($i = ($openBracket + 1); $i < $closeBracket; $i++) {
333
            if ($tokens[$i]['line'] !== $lastLine) {
334
                if ($i === $tokens[$stackPtr]['parenthesis_closer']
335
                    || ($tokens[$i]['code'] === T_WHITESPACE
336
                    && (($i + 1) === $closeBracket
337
                    || ($i + 1) === $tokens[$stackPtr]['parenthesis_closer']))
338
                ) {
339
                    // Closing braces need to be indented to the same level
340
                    // as the function.
341
                    $expectedIndent = $functionIndent;
342
                } else {
343
                    $expectedIndent = ($functionIndent + $this->indent);
344
                }
345
346
                // We changed lines, so this should be a whitespace indent token.
347
                if ($tokens[$i]['code'] !== T_WHITESPACE) {
348
                    $foundIndent = 0;
349
                } else if ($tokens[$i]['line'] !== $tokens[($i + 1)]['line']) {
350
                    // This is an empty line, so don't check the indent.
351
                    $foundIndent = $expectedIndent;
352
353
                    $error = 'Blank lines are not allowed in a multi-line function declaration';
354
                    $fix   = $phpcsFile->addFixableError($error, $i, 'EmptyLine');
355
                    if ($fix === true) {
356
                        $phpcsFile->fixer->replaceToken($i, '');
357
                    }
358
                } else {
359
                    $foundIndent = strlen($tokens[$i]['content']);
360
                }
361
362
                if ($expectedIndent !== $foundIndent) {
363
                    $error = 'Multi-line function declaration not indented correctly; expected %s spaces but found %s';
364
                    $data  = [
365
                        $expectedIndent,
366
                        $foundIndent,
367
                    ];
368
369
                    $fix = $phpcsFile->addFixableError($error, $i, 'Indent', $data);
370
                    if ($fix === true) {
371
                        $spaces = str_repeat(' ', $expectedIndent);
372
                        if ($foundIndent === 0) {
373
                            $phpcsFile->fixer->addContentBefore($i, $spaces);
374
                        } else {
375
                            $phpcsFile->fixer->replaceToken($i, $spaces);
376
                        }
377
                    }
378
                }
379
380
                $lastLine = $tokens[$i]['line'];
381
            }//end if
382
383
            if ($tokens[$i]['code'] === T_ARRAY || $tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) {
384
                // Skip arrays as they have their own indentation rules.
385
                if ($tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) {
386
                    $i = $tokens[$i]['bracket_closer'];
387
                } else {
388
                    $i = $tokens[$i]['parenthesis_closer'];
389
                }
390
391
                $lastLine = $tokens[$i]['line'];
392
                continue;
393
            }
394
        }//end for
395
396
        if (isset($tokens[$stackPtr]['scope_opener']) === false) {
397
            return;
398
        }
399
400
        // The opening brace needs to be one space away from the closing parenthesis.
401
        $opener = $tokens[$stackPtr]['scope_opener'];
402
        if ($tokens[$opener]['line'] !== $tokens[$closeBracket]['line']) {
403
            $error = 'The closing parenthesis and the opening brace of a multi-line function declaration must be on the same line';
404
            $fix   = $phpcsFile->addFixableError($error, $opener, 'NewlineBeforeOpenBrace');
405
            if ($fix === true) {
406
                $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($opener - 1), $closeBracket, true);
407
                $phpcsFile->fixer->beginChangeset();
408
                $phpcsFile->fixer->addContent($prev, ' {');
409
410
                // If the opener is on a line by itself, removing it will create
411
                // an empty line, so just remove the entire line instead.
412
                $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($opener - 1), $closeBracket, true);
413
                $next = $phpcsFile->findNext(T_WHITESPACE, ($opener + 1), null, true);
414
415
                if ($tokens[$prev]['line'] < $tokens[$opener]['line']
416
                    && $tokens[$next]['line'] > $tokens[$opener]['line']
417
                ) {
418
                    // Clear the whole line.
419
                    for ($i = ($prev + 1); $i < $next; $i++) {
420
                        if ($tokens[$i]['line'] === $tokens[$opener]['line']) {
421
                            $phpcsFile->fixer->replaceToken($i, '');
422
                        }
423
                    }
424
                } else {
425
                    // Just remove the opener.
426
                    $phpcsFile->fixer->replaceToken($opener, '');
427
                    if ($tokens[$next]['line'] === $tokens[$opener]['line']) {
428
                        $phpcsFile->fixer->replaceToken(($opener + 1), '');
429
                    }
430
                }
431
432
                $phpcsFile->fixer->endChangeset();
433
            }//end if
434
        } else {
435
            $prev = $tokens[($opener - 1)];
436
            if ($prev['code'] !== T_WHITESPACE) {
437
                $length = 0;
438
            } else {
439
                $length = strlen($prev['content']);
440
            }
441
442
            if ($length !== 1) {
443
                $error = 'There must be a single space between the closing parenthesis and the opening brace of a multi-line function declaration; found %s spaces';
444
                $fix   = $phpcsFile->addFixableError($error, ($opener - 1), 'SpaceBeforeOpenBrace', [$length]);
445
                if ($fix === true) {
446
                    if ($length === 0) {
447
                        $phpcsFile->fixer->addContentBefore($opener, ' ');
448
                    } else {
449
                        $phpcsFile->fixer->replaceToken(($opener - 1), ' ');
450
                    }
451
                }
452
453
                return;
454
            }//end if
455
        }//end if
456
457
    }//end processMultiLineDeclaration()
458
459
460
}//end class
461