1<?php
2/**
3 * A class to find T_VARIABLE tokens.
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
16if (class_exists('PHP_CodeSniffer_Standards_AbstractScopeSniff', true) === false) {
17    $error = 'Class PHP_CodeSniffer_Standards_AbstractScopeSniff not found';
18    throw new PHP_CodeSniffer_Exception($error);
19}
20
21/**
22 * A class to find T_VARIABLE tokens.
23 *
24 * This class can distinguish between normal T_VARIABLE tokens, and those tokens
25 * that represent class members. If a class member is encountered, then the
26 * processMemberVar method is called so the extending class can process it. If
27 * the token is found to be a normal T_VARIABLE token, then processVariable is
28 * called.
29 *
30 * @category  PHP
31 * @package   PHP_CodeSniffer
32 * @author    Greg Sherwood <gsherwood@squiz.net>
33 * @author    Marc McIntyre <mmcintyre@squiz.net>
34 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600)
35 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
36 * @version   Release: @package_version@
37 * @link      http://pear.php.net/package/PHP_CodeSniffer
38 */
39abstract class PHP_CodeSniffer_Standards_AbstractVariableSniff extends PHP_CodeSniffer_Standards_AbstractScopeSniff
40{
41
42    /**
43     * The end token of the current function that we are in.
44     *
45     * @var int
46     */
47    private $_endFunction = -1;
48
49    /**
50     * TRUE if a function is currently open.
51     *
52     * @var boolean
53     */
54    private $_functionOpen = false;
55
56    /**
57     * The current PHP_CodeSniffer file that we are processing.
58     *
59     * @var PHP_CodeSniffer_File
60     */
61    protected $currentFile = null;
62
63
64    /**
65     * Constructs an AbstractVariableTest.
66     */
67    public function __construct()
68    {
69        $scopes = array(
70                   T_CLASS,
71                   T_ANON_CLASS,
72                   T_TRAIT,
73                   T_INTERFACE,
74                  );
75
76        $listen = array(
77                   T_FUNCTION,
78                   T_VARIABLE,
79                   T_DOUBLE_QUOTED_STRING,
80                   T_HEREDOC,
81                  );
82
83        parent::__construct($scopes, $listen, true);
84
85    }//end __construct()
86
87
88    /**
89     * Processes the token in the specified PHP_CodeSniffer_File.
90     *
91     * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this
92     *                                        token was found.
93     * @param int                  $stackPtr  The position where the token was found.
94     * @param array                $currScope The current scope opener token.
95     *
96     * @return void
97     */
98    protected final function processTokenWithinScope(
99        PHP_CodeSniffer_File $phpcsFile,
100        $stackPtr,
101        $currScope
102    ) {
103        if ($this->currentFile !== $phpcsFile) {
104            $this->currentFile   = $phpcsFile;
105            $this->_functionOpen = false;
106            $this->_endFunction  = -1;
107        }
108
109        $tokens = $phpcsFile->getTokens();
110
111        if ($stackPtr > $this->_endFunction) {
112            $this->_functionOpen = false;
113        }
114
115        if ($tokens[$stackPtr]['code'] === T_FUNCTION
116            && $this->_functionOpen === false
117        ) {
118            $this->_functionOpen = true;
119
120            $methodProps = $phpcsFile->getMethodProperties($stackPtr);
121
122            // If the function is abstract, or is in an interface,
123            // then set the end of the function to it's closing semicolon.
124            if ($methodProps['is_abstract'] === true
125                || $tokens[$currScope]['code'] === T_INTERFACE
126            ) {
127                $this->_endFunction
128                    = $phpcsFile->findNext(array(T_SEMICOLON), $stackPtr);
129            } else {
130                if (isset($tokens[$stackPtr]['scope_closer']) === false) {
131                    $error = 'Possible parse error: non-abstract method defined as abstract';
132                    $phpcsFile->addWarning($error, $stackPtr);
133                    return;
134                }
135
136                $this->_endFunction = $tokens[$stackPtr]['scope_closer'];
137            }
138        }//end if
139
140        if ($tokens[$stackPtr]['code'] === T_DOUBLE_QUOTED_STRING
141            || $tokens[$stackPtr]['code'] === T_HEREDOC
142        ) {
143            // Check to see if this string has a variable in it.
144            $pattern = '|(?<!\\\\)(?:\\\\{2})*\${?[a-zA-Z0-9_]+}?|';
145            if (preg_match($pattern, $tokens[$stackPtr]['content']) !== 0) {
146                $this->processVariableInString($phpcsFile, $stackPtr);
147            }
148
149            return;
150        }
151
152        if ($this->_functionOpen === true) {
153            if ($tokens[$stackPtr]['code'] === T_VARIABLE) {
154                $this->processVariable($phpcsFile, $stackPtr);
155            }
156        } else {
157            // What if we assign a member variable to another?
158            // ie. private $_count = $this->_otherCount + 1;.
159            $this->processMemberVar($phpcsFile, $stackPtr);
160        }
161
162    }//end processTokenWithinScope()
163
164
165    /**
166     * Processes the token outside the scope in the file.
167     *
168     * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this
169     *                                        token was found.
170     * @param int                  $stackPtr  The position where the token was found.
171     *
172     * @return void
173     */
174    protected final function processTokenOutsideScope(
175        PHP_CodeSniffer_File $phpcsFile,
176        $stackPtr
177    ) {
178        $tokens = $phpcsFile->getTokens();
179        // These variables are not member vars.
180        if ($tokens[$stackPtr]['code'] === T_VARIABLE) {
181            $this->processVariable($phpcsFile, $stackPtr);
182        } else if ($tokens[$stackPtr]['code'] === T_DOUBLE_QUOTED_STRING
183            || $tokens[$stackPtr]['code'] === T_HEREDOC
184        ) {
185            // Check to see if this string has a variable in it.
186            $pattern = '|(?<!\\\\)(?:\\\\{2})*\${?[a-zA-Z0-9_]+}?|';
187            if (preg_match($pattern, $tokens[$stackPtr]['content']) !== 0) {
188                $this->processVariableInString($phpcsFile, $stackPtr);
189            }
190        }
191
192    }//end processTokenOutsideScope()
193
194
195    /**
196     * Called to process class member vars.
197     *
198     * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this
199     *                                        token was found.
200     * @param int                  $stackPtr  The position where the token was found.
201     *
202     * @return void
203     */
204    abstract protected function processMemberVar(
205        PHP_CodeSniffer_File $phpcsFile,
206        $stackPtr
207    );
208
209
210    /**
211     * Called to process normal member vars.
212     *
213     * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this
214     *                                        token was found.
215     * @param int                  $stackPtr  The position where the token was found.
216     *
217     * @return void
218     */
219    abstract protected function processVariable(
220        PHP_CodeSniffer_File $phpcsFile,
221        $stackPtr
222    );
223
224
225    /**
226     * Called to process variables found in double quoted strings or heredocs.
227     *
228     * Note that there may be more than one variable in the string, which will
229     * result only in one call for the string or one call per line for heredocs.
230     *
231     * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this
232     *                                        token was found.
233     * @param int                  $stackPtr  The position where the double quoted
234     *                                        string was found.
235     *
236     * @return void
237     */
238    abstract protected function processVariableInString(
239        PHP_CodeSniffer_File
240        $phpcsFile,
241        $stackPtr
242    );
243
244
245}//end class
246