1<?php
2/**
3 * Squiz_Sniffs_Operators_IncrementDecrementUsageSniff.
4 *
5 * PHP version 5
6 *
7 * @category  PHP
8 * @package   PHP_CodeSniffer
9 * @author    Greg Sherwood <gsherwood@squiz.net>
10 * @author    Marc McIntyre <mmcintyre@squiz.net>
11 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
12 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
13 * @link      http://pear.php.net/package/PHP_CodeSniffer
14 */
15
16/**
17 * Squiz_Sniffs_Operators_IncrementDecrementUsageSniff.
18 *
19 * Tests that the ++ operators are used when possible and not
20 * used when it makes the code confusing.
21 *
22 * @category  PHP
23 * @package   PHP_CodeSniffer
24 * @author    Greg Sherwood <gsherwood@squiz.net>
25 * @author    Marc McIntyre <mmcintyre@squiz.net>
26 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
27 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
28 * @version   Release: @package_version@
29 * @link      http://pear.php.net/package/PHP_CodeSniffer
30 */
31class Squiz_Sniffs_Operators_IncrementDecrementUsageSniff implements PHP_CodeSniffer_Sniff
32{
33
34
35    /**
36     * Returns an array of tokens this test wants to listen for.
37     *
38     * @return array
39     */
40    public function register()
41    {
42        return array(
43                T_EQUAL,
44                T_PLUS_EQUAL,
45                T_MINUS_EQUAL,
46                T_INC,
47                T_DEC,
48               );
49
50    }//end register()
51
52
53    /**
54     * Processes this test, when one of its tokens is encountered.
55     *
56     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
57     * @param int                  $stackPtr  The position of the current token
58     *                                        in the stack passed in $tokens.
59     *
60     * @return void
61     */
62    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
63    {
64        $tokens = $phpcsFile->getTokens();
65
66        if ($tokens[$stackPtr]['code'] === T_INC || $tokens[$stackPtr]['code'] === T_DEC) {
67            $this->processIncDec($phpcsFile, $stackPtr);
68        } else {
69            $this->processAssignment($phpcsFile, $stackPtr);
70        }
71
72    }//end process()
73
74
75    /**
76     * Checks to ensure increment and decrement operators are not confusing.
77     *
78     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
79     * @param int                  $stackPtr  The position of the current token
80     *                                        in the stack passed in $tokens.
81     *
82     * @return void
83     */
84    protected function processIncDec(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
85    {
86        $tokens = $phpcsFile->getTokens();
87
88        // Work out where the variable is so we know where to
89        // start looking for other operators.
90        if ($tokens[($stackPtr - 1)]['code'] === T_VARIABLE
91            || ($tokens[($stackPtr - 1)]['code'] === T_STRING
92            && $tokens[($stackPtr - 2)]['code'] === T_OBJECT_OPERATOR)
93        ) {
94            $start = ($stackPtr + 1);
95        } else {
96            $start = ($stackPtr + 2);
97        }
98
99        $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $start, null, true);
100        if ($next === false) {
101            return;
102        }
103
104        if (isset(PHP_CodeSniffer_Tokens::$arithmeticTokens[$tokens[$next]['code']]) === true) {
105            $error = 'Increment and decrement operators cannot be used in an arithmetic operation';
106            $phpcsFile->addError($error, $stackPtr, 'NotAllowed');
107            return;
108        }
109
110        $prev = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($start - 3), null, true);
111        if ($prev === false) {
112            return;
113        }
114
115        // Check if this is in a string concat.
116        if ($tokens[$next]['code'] === T_STRING_CONCAT || $tokens[$prev]['code'] === T_STRING_CONCAT) {
117            $error = 'Increment and decrement operators must be bracketed when used in string concatenation';
118            $phpcsFile->addError($error, $stackPtr, 'NoBrackets');
119        }
120
121    }//end processIncDec()
122
123
124    /**
125     * Checks to ensure increment and decrement operators are used.
126     *
127     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
128     * @param int                  $stackPtr  The position of the current token
129     *                                        in the stack passed in $tokens.
130     *
131     * @return void
132     */
133    protected function processAssignment(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
134    {
135        $tokens = $phpcsFile->getTokens();
136
137        $assignedVar = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
138        // Not an assignment, return.
139        if ($tokens[$assignedVar]['code'] !== T_VARIABLE) {
140            return;
141        }
142
143        $statementEnd = $phpcsFile->findNext(array(T_SEMICOLON, T_CLOSE_PARENTHESIS, T_CLOSE_SQUARE_BRACKET, T_CLOSE_CURLY_BRACKET), $stackPtr);
144
145        // If there is anything other than variables, numbers, spaces or operators we need to return.
146        $noiseTokens = $phpcsFile->findNext(array(T_LNUMBER, T_VARIABLE, T_WHITESPACE, T_PLUS, T_MINUS, T_OPEN_PARENTHESIS), ($stackPtr + 1), $statementEnd, true);
147
148        if ($noiseTokens !== false) {
149            return;
150        }
151
152        // If we are already using += or -=, we need to ignore
153        // the statement if a variable is being used.
154        if ($tokens[$stackPtr]['code'] !== T_EQUAL) {
155            $nextVar = $phpcsFile->findNext(T_VARIABLE, ($stackPtr + 1), $statementEnd);
156            if ($nextVar !== false) {
157                return;
158            }
159        }
160
161        if ($tokens[$stackPtr]['code'] === T_EQUAL) {
162            $nextVar          = ($stackPtr + 1);
163            $previousVariable = ($stackPtr + 1);
164            $variableCount    = 0;
165            while (($nextVar = $phpcsFile->findNext(T_VARIABLE, ($nextVar + 1), $statementEnd)) !== false) {
166                $previousVariable = $nextVar;
167                $variableCount++;
168            }
169
170            if ($variableCount !== 1) {
171                return;
172            }
173
174            $nextVar = $previousVariable;
175            if ($tokens[$nextVar]['content'] !== $tokens[$assignedVar]['content']) {
176                return;
177            }
178        }
179
180        // We have only one variable, and it's the same as what is being assigned,
181        // so we need to check what is being added or subtracted.
182        $nextNumber     = ($stackPtr + 1);
183        $previousNumber = ($stackPtr + 1);
184        $numberCount    = 0;
185        while (($nextNumber = $phpcsFile->findNext(array(T_LNUMBER), ($nextNumber + 1), $statementEnd, false)) !== false) {
186            $previousNumber = $nextNumber;
187            $numberCount++;
188        }
189
190        if ($numberCount !== 1) {
191            return;
192        }
193
194        $nextNumber = $previousNumber;
195        if ($tokens[$nextNumber]['content'] === '1') {
196            if ($tokens[$stackPtr]['code'] === T_EQUAL) {
197                $opToken = $phpcsFile->findNext(array(T_PLUS, T_MINUS), ($nextVar + 1), $statementEnd);
198                if ($opToken === false) {
199                    // Operator was before the variable, like:
200                    // $var = 1 + $var;
201                    // So we ignore it.
202                    return;
203                }
204
205                $operator = $tokens[$opToken]['content'];
206            } else {
207                $operator = substr($tokens[$stackPtr]['content'], 0, 1);
208            }
209
210            // If we are adding or subtracting negative value, the operator
211            // needs to be reversed.
212            if ($tokens[$stackPtr]['code'] !== T_EQUAL) {
213                $negative = $phpcsFile->findPrevious(T_MINUS, ($nextNumber - 1), $stackPtr);
214                if ($negative !== false) {
215                    if ($operator === '+') {
216                        $operator = '-';
217                    } else {
218                        $operator = '+';
219                    }
220                }
221            }
222
223            $expected = $tokens[$assignedVar]['content'].$operator.$operator;
224            $found    = $phpcsFile->getTokensAsString($assignedVar, ($statementEnd - $assignedVar + 1));
225
226            if ($operator === '+') {
227                $error = 'Increment';
228            } else {
229                $error = 'Decrement';
230            }
231
232            $error .= " operators should be used where possible; found \"$found\" but expected \"$expected\"";
233            $phpcsFile->addError($error, $stackPtr, 'Found');
234        }//end if
235
236    }//end processAssignment()
237
238
239}//end class
240