Failed Conditions
Push — master ( c35fd9...2f93af )
by Alexander
02:57
created

ValidVariableNameSniff::processVariableInString()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
nc 6
nop 2
dl 0
loc 38
ccs 23
cts 23
cp 1
crap 7
rs 8.3786
c 0
b 0
f 0
1
<?php
2
/**
3
 * CodingStandard_Sniffs_NamingConventions_ValidVariableNameSniff.
4
 *
5
 * PHP version 5
6
 *
7
 * @category PHP
8
 * @package  PHP_CodeSniffer
9
 * @author   Greg Sherwood <[email protected]>
10
 * @author   Marc McIntyre <[email protected]>
11
 * @author   Alexander Obuhovich <[email protected]>
12
 * @license  https://github.com/aik099/CodingStandard/blob/master/LICENSE BSD 3-Clause
13
 * @link     https://github.com/aik099/CodingStandard
14
 */
15
16
namespace CodingStandard\Sniffs\NamingConventions;
17
18
use PHP_CodeSniffer\Files\File;
19
use PHP_CodeSniffer\Sniffs\AbstractVariableSniff;
20
use PHP_CodeSniffer\Util\Common;
21
22
/**
23
 * CodingStandard_Sniffs_NamingConventions_ValidVariableNameSniff.
24
 *
25
 * Checks the naming of variables and member variables.
26
 *
27
 * @category PHP
28
 * @package  PHP_CodeSniffer
29
 * @author   Greg Sherwood <[email protected]>
30
 * @author   Marc McIntyre <[email protected]>
31
 * @author   Alexander Obuhovich <[email protected]>
32
 * @license  https://github.com/aik099/CodingStandard/blob/master/LICENSE BSD 3-Clause
33
 * @link     https://github.com/aik099/CodingStandard
34
 */
35
class ValidVariableNameSniff extends AbstractVariableSniff
36
{
37
38
    /**
39
     * Variable names, that are reserved in PHP.
40
     *
41
     * @var array
42
     */
43
    protected $phpReservedVars = array(
44
                                  '_SERVER',
45
                                  '_GET',
46
                                  '_POST',
47
                                  '_REQUEST',
48
                                  '_SESSION',
49
                                  '_ENV',
50
                                  '_COOKIE',
51
                                  '_FILES',
52
                                  'GLOBALS',
53
                                  'http_response_header',
54
                                  'HTTP_RAW_POST_DATA',
55
                                  'php_errormsg',
56
                                 );
57
58
    /**
59
     * Member variable names that break the rules, but are allowed.
60
     *
61
     * @var array
62
     */
63
    protected $memberExceptions = array(
64
                                   // From "kBase".
65
                                   'Application',
66
                                   'Conn',
67
68
                                   // From "kEvent".
69
                                   'Name',
70
                                   'MasterEvent',
71
                                   'Prefix',
72
                                   'Special',
73
74
                                   // From "kDBItem".
75
                                   'IDField',
76
                                   'TableName',
77
                                   'IgnoreValidation',
78
                                  );
79
80
81
    /**
82
     * Processes this test, when one of its tokens is encountered.
83
     *
84
     * @param File $phpcsFile The file being scanned.
85
     * @param int  $stackPtr  The position of the current token in the
86
     *                        stack passed in $tokens.
87
     *
88
     * @return void
89
     */
90 1
    protected function processVariable(File $phpcsFile, $stackPtr)
91
    {
92 1
        $tokens  = $phpcsFile->getTokens();
93 1
        $varName = ltrim($tokens[$stackPtr]['content'], '$');
94
95
        // If it's a php reserved var, then its ok.
96 1
        if (in_array($varName, $this->phpReservedVars) === true) {
97 1
            return;
98
        }
99
100 1
        $objOperator = $phpcsFile->findPrevious(array(T_WHITESPACE), ($stackPtr - 1), null, true);
101 1
        if ($tokens[$objOperator]['code'] === T_DOUBLE_COLON
102 1
            || $tokens[$objOperator]['code'] === T_OBJECT_OPERATOR
103
        ) {
104
            // Don't validate class/object property usage,
105
            // because their declaration is already validated.
106 1
            return;
107
        }
108
109 1
        if ($this->isSnakeCaps($varName) === false) {
110 1
            $error = 'Variable "%s" is not in valid snake caps format';
111 1
            $data  = array($varName);
112 1
            $phpcsFile->addError($error, $stackPtr, 'NotSnakeCaps', $data);
113
        }
114 1
    }//end processVariable()
115
116
117
    /**
118
     * Processes class member variables.
119
     *
120
     * @param File $phpcsFile The file being scanned.
121
     * @param int  $stackPtr  The position of the current token in the
122
     *                        stack passed in $tokens.
123
     *
124
     * @return void
125
     */
126 1
    protected function processMemberVar(File $phpcsFile, $stackPtr)
127
    {
128 1
        $tokens = $phpcsFile->getTokens();
129
130 1
        $varName     = ltrim($tokens[$stackPtr]['content'], '$');
131 1
        $memberProps = $phpcsFile->getMemberProperties($stackPtr);
132
133
        // @codeCoverageIgnoreStart
134
        if (empty($memberProps) === true) {
135
            // Couldn't get any info about this variable, which
136
            // generally means it is invalid or possibly has a parse
137
            // error. Any errors will be reported by the core, so
138
            // we can ignore it.
139
            return;
140
        }
141
        // @codeCoverageIgnoreEnd
142
143 1
        $classToken = $phpcsFile->findPrevious(
144 1
            array(T_CLASS, T_INTERFACE, T_TRAIT),
145 1
            $stackPtr
146
        );
147 1
        $className  = $phpcsFile->getDeclarationName($classToken);
0 ignored issues
show
Bug introduced by
It seems like $classToken defined by $phpcsFile->findPrevious...E, T_TRAIT), $stackPtr) on line 143 can also be of type boolean; however, PHP_CodeSniffer\Files\File::getDeclarationName() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
148
149 1
        $public    = ($memberProps['scope'] !== 'private');
150 1
        $errorData = array($className.'::'.$varName);
151
152 1
        if ($public === true) {
153 1
            if (substr($varName, 0, 1) === '_') {
154 1
                $error = '%s member variable "%s" must not contain a leading underscore';
155
                $data  = array(
156 1
                          ucfirst($memberProps['scope']),
157 1
                          $errorData[0],
158
                         );
159 1
                $phpcsFile->addError($error, $stackPtr, 'PublicHasUnderscore', $data);
160 1
                return;
161
            }
162
        } else {
163 1
            if (substr($varName, 0, 1) !== '_') {
164 1
                $error = 'Private member variable "%s" must contain a leading underscore';
165 1
                $phpcsFile->addError($error, $stackPtr, 'PrivateNoUnderscore', $errorData);
166 1
                return;
167
            }
168
        }
169
170 1
        if ($this->isCamelCaps($varName, $public) === false) {
171 1
            $error = '%s member variable "%s" is not in valid camel caps format';
172
            $data  = array(
173 1
                      ucfirst($memberProps['scope']),
174 1
                      $errorData[0],
175
                     );
176 1
            $phpcsFile->addError($error, $stackPtr, 'MemberNotCamelCaps', $data);
177
        }
178 1
    }//end processMemberVar()
179
180
181
    /**
182
     * Processes the variable found within a double quoted string.
183
     *
184
     * @param File $phpcsFile The file being scanned.
185
     * @param int  $stackPtr  The position of the double quoted
186
     *                        string.
187
     *
188
     * @return void
189
     */
190 1
    protected function processVariableInString(File $phpcsFile, $stackPtr)
191
    {
192 1
        $tokens         = $phpcsFile->getTokens();
193 1
        $content        = $tokens[$stackPtr]['content'];
194 1
        $variablesFound = preg_match_all(
195 1
            '|[^\\\]\${?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)|',
196 1
            $content,
197 1
            $matches,
198 1
            PREG_SET_ORDER + PREG_OFFSET_CAPTURE
199
        );
200
201 1
        if ($variablesFound === 0) {
202 1
            return;
203
        }
204
205 1
        foreach ($matches as $match) {
0 ignored issues
show
Bug introduced by
The expression $matches of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
206 1
            $varName = $match[1][0];
207 1
            $offset  = $match[1][1];
208
209
            // If it's a php reserved var, then its ok.
210 1
            if (in_array($varName, $this->phpReservedVars) === true) {
211 1
                continue;
212
            }
213
214
            // Don't validate class/object property usage in strings,
215
            // because their declaration is already validated.
216 1
            $variablePrefix = substr($content, $offset - 3, 2);
217 1
            if ($variablePrefix === '::' || $variablePrefix === '->') {
218 1
                continue;
219
            }
220
221 1
            if ($this->isSnakeCaps($varName) === false) {
222 1
                $error = 'Variable in string "%s" is not in valid snake caps format';
223 1
                $data  = array($varName);
224 1
                $phpcsFile->addError($error, $stackPtr, 'StringNotSnakeCaps', $data);
225
            }
226
        }//end foreach
227 1
    }//end processVariableInString()
228
229
230
    /**
231
     * Determines if a variable is in camel caps case.
232
     *
233
     * @param string $string String.
234
     * @param bool   $public If true, the first character in the string
235
     *                       must be an a-z character. If false, the
236
     *                       character must be an underscore. This
237
     *                       argument is only applicable if $classFormat
238
     *                       is false.
239
     *
240
     * @return bool
241
     */
242 1
    protected function isCamelCaps($string, $public = true)
243
    {
244 1
        if (in_array($string, $this->memberExceptions) === true) {
245
            return true;
246
        }
247
248 1
        return Common::isCamelCaps($string, false, $public, false);
249
    }//end isCamelCaps()
250
251
252
    /**
253
     * Determines if a variable is in snake caps case.
254
     *
255
     * @param string $string String.
256
     *
257
     * @return bool
258
     */
259 1
    protected function isSnakeCaps($string)
260
    {
261 1
        return strtolower($string) === $string;
262
    }//end isSnakeCaps()
263
}//end class
264