1<?php
2/**
3 * PEAR_Sniffs_WhiteSpace_ObjectOperatorIndentSniff.
4 *
5 * PHP version 5
6 *
7 * @category  PHP
8 * @package   PHP_CodeSniffer
9 * @author    Greg Sherwood <gsherwood@squiz.net>
10 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
11 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
12 * @link      http://pear.php.net/package/PHP_CodeSniffer
13 */
14
15/**
16 * PEAR_Sniffs_WhiteSpace_ObjectOperatorIndentSniff.
17 *
18 * Checks that object operators are indented 4 spaces if they are the first
19 * thing on a line.
20 *
21 * @category  PHP
22 * @package   PHP_CodeSniffer
23 * @author    Greg Sherwood <gsherwood@squiz.net>
24 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
25 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
26 * @version   Release: @package_version@
27 * @link      http://pear.php.net/package/PHP_CodeSniffer
28 */
29class PEAR_Sniffs_WhiteSpace_ObjectOperatorIndentSniff implements PHP_CodeSniffer_Sniff
30{
31
32    /**
33     * The number of spaces code should be indented.
34     *
35     * @var int
36     */
37    public $indent = 4;
38
39
40    /**
41     * Returns an array of tokens this test wants to listen for.
42     *
43     * @return array
44     */
45    public function register()
46    {
47        return array(T_OBJECT_OPERATOR);
48
49    }//end register()
50
51
52    /**
53     * Processes this test, when one of its tokens is encountered.
54     *
55     * @param PHP_CodeSniffer_File $phpcsFile All the tokens found in the document.
56     * @param int                  $stackPtr  The position of the current token
57     *                                        in the stack passed in $tokens.
58     *
59     * @return void
60     */
61    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
62    {
63        $tokens = $phpcsFile->getTokens();
64
65        // Make sure this is the first object operator in a chain of them.
66        $varToken = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
67        if ($varToken === false || $tokens[$varToken]['code'] !== T_VARIABLE) {
68            return;
69        }
70
71        // Make sure this is a chained call.
72        $next = $phpcsFile->findNext(
73            T_OBJECT_OPERATOR,
74            ($stackPtr + 1),
75            null,
76            false,
77            null,
78            true
79        );
80
81        if ($next === false) {
82            // Not a chained call.
83            return;
84        }
85
86        // Determine correct indent.
87        for ($i = ($varToken - 1); $i >= 0; $i--) {
88            if ($tokens[$i]['line'] !== $tokens[$varToken]['line']) {
89                $i++;
90                break;
91            }
92        }
93
94        $requiredIndent = 0;
95        if ($i >= 0 && $tokens[$i]['code'] === T_WHITESPACE) {
96            $requiredIndent = strlen($tokens[$i]['content']);
97        }
98
99        $requiredIndent += $this->indent;
100
101        // Determine the scope of the original object operator.
102        $origBrackets = null;
103        if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) {
104            $origBrackets = $tokens[$stackPtr]['nested_parenthesis'];
105        }
106
107        $origConditions = null;
108        if (isset($tokens[$stackPtr]['conditions']) === true) {
109            $origConditions = $tokens[$stackPtr]['conditions'];
110        }
111
112        // Check indentation of each object operator in the chain.
113        // If the first object operator is on a different line than
114        // the variable, make sure we check its indentation too.
115        if ($tokens[$stackPtr]['line'] > $tokens[$varToken]['line']) {
116            $next = $stackPtr;
117        }
118
119        while ($next !== false) {
120            // Make sure it is in the same scope, otherwise don't check indent.
121            $brackets = null;
122            if (isset($tokens[$next]['nested_parenthesis']) === true) {
123                $brackets = $tokens[$next]['nested_parenthesis'];
124            }
125
126            $conditions = null;
127            if (isset($tokens[$next]['conditions']) === true) {
128                $conditions = $tokens[$next]['conditions'];
129            }
130
131            if ($origBrackets === $brackets && $origConditions === $conditions) {
132                // Make sure it starts a line, otherwise dont check indent.
133                $prev   = $phpcsFile->findPrevious(T_WHITESPACE, ($next - 1), $stackPtr, true);
134                $indent = $tokens[($next - 1)];
135                if ($tokens[$prev]['line'] !== $tokens[$next]['line']
136                    && $indent['code'] === T_WHITESPACE
137                ) {
138                    if ($indent['line'] === $tokens[$next]['line']) {
139                        $foundIndent = strlen($indent['content']);
140                    } else {
141                        $foundIndent = 0;
142                    }
143
144                    if ($foundIndent !== $requiredIndent) {
145                        $error = 'Object operator not indented correctly; expected %s spaces but found %s';
146                        $data  = array(
147                                  $requiredIndent,
148                                  $foundIndent,
149                                 );
150
151                        $fix = $phpcsFile->addFixableError($error, $next, 'Incorrect', $data);
152                        if ($fix === true) {
153                            $spaces = str_repeat(' ', $requiredIndent);
154                            if ($foundIndent === 0) {
155                                $phpcsFile->fixer->addContentBefore($next, $spaces);
156                            } else {
157                                $phpcsFile->fixer->replaceToken(($next - 1), $spaces);
158                            }
159                        }
160                    }
161                }//end if
162
163                // It cant be the last thing on the line either.
164                $content = $phpcsFile->findNext(T_WHITESPACE, ($next + 1), null, true);
165                if ($tokens[$content]['line'] !== $tokens[$next]['line']) {
166                    $error = 'Object operator must be at the start of the line, not the end';
167                    $fix   = $phpcsFile->addFixableError($error, $next, 'StartOfLine');
168                    if ($fix === true) {
169                        $phpcsFile->fixer->beginChangeset();
170                        for ($x = ($next + 1); $x < $content; $x++) {
171                            $phpcsFile->fixer->replaceToken($x, '');
172                        }
173
174                        $phpcsFile->fixer->addNewlineBefore($next);
175                        $phpcsFile->fixer->endChangeset();
176                    }
177                }
178            }//end if
179
180            $next = $phpcsFile->findNext(
181                T_OBJECT_OPERATOR,
182                ($next + 1),
183                null,
184                false,
185                null,
186                true
187            );
188        }//end while
189
190    }//end process()
191
192
193}//end class
194