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

MultipleStatementAlignmentSniff::checkAlignment()   F

Complexity

Conditions 42
Paths > 20000

Size

Total Lines 212

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 212
rs 0
cc 42
nc 63190
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
 * Checks alignment of assignments.
4
 *
5
 * If there are multiple adjacent assignments, it will check that the equals signs of
6
 * each assignment are aligned. It will display a warning to advise that the signs should be aligned.
7
 *
8
 * @author    Greg Sherwood <[email protected]>
9
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
10
 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
11
 */
12
13
namespace PHP_CodeSniffer\Standards\Generic\Sniffs\Formatting;
14
15
use PHP_CodeSniffer\Sniffs\Sniff;
16
use PHP_CodeSniffer\Files\File;
17
use PHP_CodeSniffer\Util\Tokens;
18
19
class MultipleStatementAlignmentSniff implements Sniff
20
{
21
22
    /**
23
     * A list of tokenizers this sniff supports.
24
     *
25
     * @var array
26
     */
27
    public $supportedTokenizers = [
28
        'PHP',
29
        'JS',
30
    ];
31
32
    /**
33
     * If true, an error will be thrown; otherwise a warning.
34
     *
35
     * @var boolean
36
     */
37
    public $error = false;
38
39
    /**
40
     * The maximum amount of padding before the alignment is ignored.
41
     *
42
     * If the amount of padding required to align this assignment with the
43
     * surrounding assignments exceeds this number, the assignment will be
44
     * ignored and no errors or warnings will be thrown.
45
     *
46
     * @var integer
47
     */
48
    public $maxPadding = 1000;
49
50
51
    /**
52
     * Returns an array of tokens this test wants to listen for.
53
     *
54
     * @return array
55
     */
56
    public function register()
57
    {
58
        $tokens = Tokens::$assignmentTokens;
59
        unset($tokens[T_DOUBLE_ARROW]);
60
        return $tokens;
61
62
    }//end register()
63
64
65
    /**
66
     * Processes this test, when one of its tokens is encountered.
67
     *
68
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
69
     * @param int                         $stackPtr  The position of the current token
70
     *                                               in the stack passed in $tokens.
71
     *
72
     * @return int
73
     */
74
    public function process(File $phpcsFile, $stackPtr)
75
    {
76
        $tokens = $phpcsFile->getTokens();
77
78
        // Ignore assignments used in a condition, like an IF or FOR.
79
        if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) {
80
            foreach ($tokens[$stackPtr]['nested_parenthesis'] as $start => $end) {
81
                if (isset($tokens[$start]['parenthesis_owner']) === true) {
82
                    return;
83
                }
84
            }
85
        }
86
87
        $lastAssign = $this->checkAlignment($phpcsFile, $stackPtr);
88
        return ($lastAssign + 1);
89
90
    }//end process()
91
92
93
    /**
94
     * Processes this test, when one of its tokens is encountered.
95
     *
96
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
97
     * @param int                         $stackPtr  The position of the current token
98
     *                                               in the stack passed in $tokens.
99
     *
100
     * @return int
101
     */
102
    public function checkAlignment($phpcsFile, $stackPtr)
103
    {
104
        $tokens = $phpcsFile->getTokens();
105
106
        $assignments = [];
107
        $prevAssign  = null;
108
        $lastLine    = $tokens[$stackPtr]['line'];
109
        $maxPadding  = null;
110
        $stopped     = null;
111
        $lastCode    = $stackPtr;
112
        $lastSemi    = null;
113
114
        $find = Tokens::$assignmentTokens;
115
        unset($find[T_DOUBLE_ARROW]);
116
117
        for ($assign = $stackPtr; $assign < $phpcsFile->numTokens; $assign++) {
118
            if (isset($find[$tokens[$assign]['code']]) === false) {
119
                // A blank line indicates that the assignment block has ended.
120
                if (isset(Tokens::$emptyTokens[$tokens[$assign]['code']]) === false) {
121
                    if (($tokens[$assign]['line'] - $tokens[$lastCode]['line']) > 1) {
122
                        break;
123
                    }
124
125
                    $lastCode = $assign;
126
127
                    if ($tokens[$assign]['code'] === T_SEMICOLON) {
128
                        if ($tokens[$assign]['conditions'] === $tokens[$stackPtr]['conditions']) {
129
                            if ($lastSemi !== null && $prevAssign !== null && $lastSemi > $prevAssign) {
130
                                // This statement did not have an assignment operator in it.
131
                                break;
132
                            } else {
133
                                $lastSemi = $assign;
134
                            }
135
                        } else {
136
                            // Statement is in a different context, so the block is over.
137
                            break;
138
                        }
139
                    }
140
                }//end if
141
142
                continue;
143
            } else if ($assign !== $stackPtr && $tokens[$assign]['line'] === $lastLine) {
144
                // Skip multiple assignments on the same line. We only need to
145
                // try and align the first assignment.
146
                continue;
147
            }//end if
148
149
            if ($assign !== $stackPtr) {
150
                // Has to be nested inside the same conditions as the first assignment.
151
                if ($tokens[$assign]['conditions'] !== $tokens[$stackPtr]['conditions']) {
152
                    break;
153
                }
154
155
                // Make sure it is not assigned inside a condition (eg. IF, FOR).
156
                if (isset($tokens[$assign]['nested_parenthesis']) === true) {
157
                    foreach ($tokens[$assign]['nested_parenthesis'] as $start => $end) {
158
                        if (isset($tokens[$start]['parenthesis_owner']) === true) {
159
                            break(2);
160
                        }
161
                    }
162
                }
163
            }//end if
164
165
            $var = $phpcsFile->findPrevious(
166
                Tokens::$emptyTokens,
167
                ($assign - 1),
168
                null,
169
                true
170
            );
171
172
            // Make sure we wouldn't break our max padding length if we
173
            // aligned with this statement, or they wouldn't break the max
174
            // padding length if they aligned with us.
175
            $varEnd    = $tokens[($var + 1)]['column'];
176
            $assignLen = $tokens[$assign]['length'];
177
            if ($assign !== $stackPtr) {
178
                if (($varEnd + 1) > $assignments[$prevAssign]['assign_col']) {
179
                    $padding      = 1;
180
                    $assignColumn = ($varEnd + 1);
181
                } else {
182
                    $padding = ($assignments[$prevAssign]['assign_col'] - $varEnd + $assignments[$prevAssign]['assign_len'] - $assignLen);
183
                    if ($padding <= 0) {
184
                        $padding = 1;
185
                    }
186
187
                    if ($padding > $this->maxPadding) {
188
                        $stopped = $assign;
189
                        break;
190
                    }
191
192
                    $assignColumn = ($varEnd + $padding);
193
                }//end if
194
195
                if (($assignColumn + $assignLen) > ($assignments[$maxPadding]['assign_col'] + $assignments[$maxPadding]['assign_len'])) {
196
                    $newPadding = ($varEnd - $assignments[$maxPadding]['var_end'] + $assignLen - $assignments[$maxPadding]['assign_len'] + 1);
197
                    if ($newPadding > $this->maxPadding) {
198
                        $stopped = $assign;
199
                        break;
200
                    } else {
201
                        // New alignment settings for previous assignments.
202
                        foreach ($assignments as $i => $data) {
203
                            if ($i === $assign) {
204
                                break;
205
                            }
206
207
                            $newPadding = ($varEnd - $data['var_end'] + $assignLen - $data['assign_len'] + 1);
208
                            $assignments[$i]['expected']   = $newPadding;
209
                            $assignments[$i]['assign_col'] = ($data['var_end'] + $newPadding);
210
                        }
211
212
                        $padding      = 1;
213
                        $assignColumn = ($varEnd + 1);
214
                    }
215
                } else if ($padding > $assignments[$maxPadding]['expected']) {
216
                    $maxPadding = $assign;
217
                }//end if
218
            } else {
219
                $padding      = 1;
220
                $assignColumn = ($varEnd + 1);
221
                $maxPadding   = $assign;
222
            }//end if
223
224
            $found = 0;
225
            if ($tokens[($var + 1)]['code'] === T_WHITESPACE) {
226
                $found = $tokens[($var + 1)]['length'];
227
                if ($found === 0) {
228
                    // This means a newline was found.
229
                    $found = 1;
230
                }
231
            }
232
233
            $assignments[$assign] = [
234
                'var_end'    => $varEnd,
235
                'assign_len' => $assignLen,
236
                'assign_col' => $assignColumn,
237
                'expected'   => $padding,
238
                'found'      => $found,
239
            ];
240
241
            $lastLine   = $tokens[$assign]['line'];
242
            $prevAssign = $assign;
243
        }//end for
244
245
        if (empty($assignments) === true) {
246
            return $stackPtr;
247
        }
248
249
        $numAssignments = count($assignments);
250
251
        $errorGenerated = false;
252
        foreach ($assignments as $assignment => $data) {
253
            if ($data['found'] === $data['expected']) {
254
                continue;
255
            }
256
257
            $expectedText = $data['expected'].' space';
258
            if ($data['expected'] !== 1) {
259
                $expectedText .= 's';
260
            }
261
262
            if ($data['found'] === null) {
263
                $foundText = 'a new line';
264
            } else {
265
                $foundText = $data['found'].' space';
266
                if ($data['found'] !== 1) {
267
                    $foundText .= 's';
268
                }
269
            }
270
271
            if ($numAssignments === 1) {
272
                $type  = 'Incorrect';
273
                $error = 'Equals sign not aligned correctly; expected %s but found %s';
274
            } else {
275
                $type  = 'NotSame';
276
                $error = 'Equals sign not aligned with surrounding assignments; expected %s but found %s';
277
            }
278
279
            $errorData = [
280
                $expectedText,
281
                $foundText,
282
            ];
283
284
            if ($this->error === true) {
285
                $fix = $phpcsFile->addFixableError($error, $assignment, $type, $errorData);
286
            } else {
287
                $fix = $phpcsFile->addFixableWarning($error, $assignment, $type.'Warning', $errorData);
288
            }
289
290
            $errorGenerated = true;
291
292
            if ($fix === true && $data['found'] !== null) {
293
                $newContent = str_repeat(' ', $data['expected']);
294
                if ($data['found'] === 0) {
295
                    $phpcsFile->fixer->addContentBefore($assignment, $newContent);
296
                } else {
297
                    $phpcsFile->fixer->replaceToken(($assignment - 1), $newContent);
298
                }
299
            }
300
        }//end foreach
301
302
        if ($numAssignments > 1) {
303
            if ($errorGenerated === true) {
304
                $phpcsFile->recordMetric($stackPtr, 'Adjacent assignments aligned', 'no');
305
            } else {
306
                $phpcsFile->recordMetric($stackPtr, 'Adjacent assignments aligned', 'yes');
307
            }
308
        }
309
310
        if ($stopped !== null) {
311
            return $this->checkAlignment($phpcsFile, $stopped);
312
        } else {
313
            return $assignment;
314
        }
315
316
    }//end checkAlignment()
317
318
319
}//end class
320