Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPCSStandards / PHPCSUtils / 178

22 Jan 2020 - 22:47 coverage: 97.187%. First build
178

Pull #48

travis-ci-com

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
:sparkles: New Utils\PassedParameters class

This introduces four new methods to PHPCSUtils.

These methods are intended for use with:
* `T_STRING` and `T_VARIABLE` tokens for function calls;
* `T_SELF` and `T_STATIC` token for function calls in the form of `new self()`;
* `T_ARRAY` and `T_OPEN_SHORT_ARRAY` tokens for array declarations;
* `T_ISSET` and `T_UNSET` tokens for calls to these language constructs.

Methods:
* `hasParameters()` - to check if parameters have been passed. Returns true/false.
* `getParameters()` - to retrieve details of the passed parameters. Returns an array with `start` (token position), `end` (token position), `raw` (string content) and `clean` (string content without comments) information for each parameter.
* `getParameter()` - to retrieve the details of a specific parameter. Index for the parameters is 1-based. Returns an array with the details for the specified parameter or `false` if the parameter does not exist.
* `getParameterCount()` - to get the a count of the number of parameters. Returns integer.

These methods have been battle-tested in the past year or two in the PHPCompatibility and the WordPressCS standards.

Includes dedicated unit tests.

N.B.: In the class as pulled to PHPCS originally, these methods also handled (short/long) list constructs, however, as lists can have empty entries - `list(,,)` - prior to PHP 7 and can skip items - `list($a,, $b)` - in cross-version PHP, parsing lists with the functions in this class will break on those type of list definitions.
So, I've decided that this is better served with a dedicated utility function focussing just on list constructs.
Pull Request #48: New Utils\PassedParameters class

76 of 77 new or added lines in 1 file covered. (98.7%)

1520 of 1564 relevant lines covered (97.19%)

69.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.7
/PHPCSUtils/Utils/PassedParameters.php
1
<?php
2
/**
3
 * PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
4
 *
5
 * @package   PHPCSUtils
6
 * @copyright 2019 PHPCSUtils Contributors
7
 * @license   https://opensource.org/licenses/LGPL-3.0 LGPL3
8
 * @link      https://github.com/PHPCSStandards/PHPCSUtils
9
 */
10

11
namespace PHPCSUtils\Utils;
12

13
use PHP_CodeSniffer\Exceptions\RuntimeException;
14
use PHP_CodeSniffer\Files\File;
15
use PHP_CodeSniffer\Util\Tokens;
16
use PHPCSUtils\Utils\GetTokensAsString;
17

18
/**
19
 * Utility functions to retrieve information about parameters passed to function calls,
20
 * array declarations, isset and unset constructs.
21
 *
22
 * @since 1.0.0
23
 */
24
class PassedParameters
25
{
26

27
    /**
28
     * The token types these methods can handle.
29
     *
30
     * @since 1.0.0
31
     *
32
     * @var array <int|string> => <irrelevant>
33
     */
34
    private static $allowedConstructs = [
35
        \T_STRING           => true,
36
        \T_VARIABLE         => true,
37
        \T_SELF             => true,
38
        \T_STATIC           => true,
39
        \T_ARRAY            => true,
40
        \T_OPEN_SHORT_ARRAY => true,
41
        \T_ISSET            => true,
42
        \T_UNSET            => true,
43
    ];
44

45
    /**
46
     * Tokens which are considered stop point, either because they are the end
47
     * of the parameter (comma) or because we need to skip over them.
48
     *
49
     * @since 1.0.0
50
     *
51
     * @var array <int|string> => <int|string>
52
     */
53
    private static $callParsingStopPoints = [
54
        \T_COMMA                => \T_COMMA,
55
        \T_OPEN_SHORT_ARRAY     => \T_OPEN_SHORT_ARRAY,
56
        \T_OPEN_SQUARE_BRACKET  => \T_OPEN_SQUARE_BRACKET,
57
        \T_OPEN_PARENTHESIS     => \T_OPEN_PARENTHESIS,
58
        \T_DOC_COMMENT_OPEN_TAG => \T_DOC_COMMENT_OPEN_TAG,
59
    ];
60

61
    /**
62
     * Checks if any parameters have been passed.
63
     *
64
     * - If passed a T_STRING or T_VARIABLE stack pointer, it will treat it as a function call.
65
     *   If a T_STRING or T_VARIABLE which is *not* a function call is passed, the behaviour is
66
     *   unreliable.
67
     * - If passed a T_SELF or T_STATIC stack pointer, it will accept it as a
68
     *   function call when used like `new self()`.
69
     * - If passed a T_ARRAY or T_OPEN_SHORT_ARRAY stack pointer, it will detect
70
     *   whether the array has values or is empty.
71
     * - If passed a T_ISSET or T_UNSET stack pointer, it will detect whether those
72
     *   language constructs have "parameters".
73
     *
74
     * @since 1.0.0
75
     *
76
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
77
     * @param int                         $stackPtr  The position of the T_STRING, T_VARIABLE, T_ARRAY,
78
     *                                               T_OPEN_SHORT_ARRAY, T_ISSET, or T_UNSET token.
79
     *
80
     * @return bool
81
     *
82
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the token passed is not one of the
83
     *                                                      accepted types or doesn't exist.
84
     */
85
    public static function hasParameters(File $phpcsFile, $stackPtr)
86
    {
87
        $tokens = $phpcsFile->getTokens();
496×
88

89
        if (isset($tokens[$stackPtr], self::$allowedConstructs[$tokens[$stackPtr]['code']]) === false) {
496×
90
            throw new RuntimeException(
8×
91
                'The hasParameters() method expects a function call, array, isset or unset token to be passed.'
92
            );
93
        }
94

95
        if ($tokens[$stackPtr]['code'] === \T_SELF || $tokens[$stackPtr]['code'] === \T_STATIC) {
488×
96
            $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
7×
97
            if ($tokens[$prev]['code'] !== \T_NEW) {
7×
98
                throw new RuntimeException(
4×
99
                    'The hasParameters() method expects a function call, array, isset or unset token to be passed.'
100
                );
101
            }
102
        }
103

104
        $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
484×
105
        if ($next === false) {
484×
106
            return false;
4×
107
        }
108

109
        // Deal with short array syntax.
110
        if ($tokens[$stackPtr]['code'] === \T_OPEN_SHORT_ARRAY) {
480×
111
            if ($next === $tokens[$stackPtr]['bracket_closer']) {
88×
112
                // No parameters.
113
                return false;
16×
114
            }
115

116
            return true;
72×
117
        }
118

119
        // Deal with function calls, long arrays, isset and unset.
120
        // Next non-empty token should be the open parenthesis.
121
        if ($tokens[$next]['code'] !== \T_OPEN_PARENTHESIS) {
392×
122
            return false;
4×
123
        }
124

125
        if (isset($tokens[$next]['parenthesis_closer']) === false) {
388×
126
            return false;
4×
127
        }
128

129
        $closeParenthesis = $tokens[$next]['parenthesis_closer'];
384×
130
        $nextNextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($next + 1), ($closeParenthesis + 1), true);
384×
131

132
        if ($nextNextNonEmpty === $closeParenthesis) {
384×
133
            // No parameters.
134
            return false;
48×
135
        }
136

137
        return true;
336×
138
    }
139

140
    /**
141
     * Get information on all parameters passed.
142
     *
143
     * See {@see PHPCSUtils\Utils\PassedParameters::hasParameters()} for information on the supported
144
     * constructs.
145
     *
146
     * @since 1.0.0
147
     *
148
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
149
     * @param int                         $stackPtr  The position of the T_STRING, T_VARIABLE, T_ARRAY,
150
     *                                               T_OPEN_SHORT_ARRAY, T_ISSET, or T_UNSET token.
151
     *
152
     * @return array Returns a multi-dimentional array with the "start" token pointer, "end" token
153
     *               pointer, "raw" parameter value and "clean" (only code, no comments) parameter
154
     *               value for all parameters. The array starts at index 1.
155
     *               If no parameters are found, will return an empty array.
156
     *
157
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the token passed is not one of the
158
     *                                                      accepted types or doesn't exist.
159
     */
160
    public static function getParameters(File $phpcsFile, $stackPtr)
161
    {
162
        if (self::hasParameters($phpcsFile, $stackPtr) === false) {
368×
163
            return [];
4×
164
        }
165

166
        // Ok, we know we have a valid token with parameters and valid open & close brackets/parenthesis.
167
        $tokens = $phpcsFile->getTokens();
364×
168

169
        // Mark the beginning and end tokens.
170
        if ($tokens[$stackPtr]['code'] === \T_OPEN_SHORT_ARRAY) {
364×
171
            $opener = $stackPtr;
60×
172
            $closer = $tokens[$stackPtr]['bracket_closer'];
60×
173
        } else {
174
            $opener = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
304×
175
            $closer = $tokens[$opener]['parenthesis_closer'];
304×
176
        }
177

178
        $parameters   = [];
364×
179
        $nextComma    = $opener;
364×
180
        $paramStart   = ($opener + 1);
364×
181
        $cnt          = 1;
364×
182
        $stopPoints   = self::$callParsingStopPoints + Tokens::$scopeOpeners;
364×
183
        $stopPoints[] = $tokens[$closer]['code'];
364×
184

185
        while (($nextComma = $phpcsFile->findNext($stopPoints, ($nextComma + 1), ($closer + 1))) !== false) {
364×
186
            // Ignore anything within square brackets.
187
            if (isset($tokens[$nextComma]['bracket_opener'], $tokens[$nextComma]['bracket_closer'])
364×
188
                && $nextComma === $tokens[$nextComma]['bracket_opener']
364×
189
            ) {
190
                $nextComma = $tokens[$nextComma]['bracket_closer'];
140×
191
                continue;
140×
192
            }
193

194
            // Skip past nested arrays, function calls and arbitrary groupings.
195
            if ($tokens[$nextComma]['code'] === \T_OPEN_PARENTHESIS
364×
196
                && isset($tokens[$nextComma]['parenthesis_closer'])
364×
197
            ) {
198
                $nextComma = $tokens[$nextComma]['parenthesis_closer'];
176×
199
                continue;
176×
200
            }
201

202
            // Skip past closures, anonymous classes and anything else scope related.
203
            if (isset($tokens[$nextComma]['scope_condition'], $tokens[$nextComma]['scope_closer'])
364×
204
                && $tokens[$nextComma]['scope_condition'] === $nextComma
364×
205
            ) {
206
                $nextComma = $tokens[$nextComma]['scope_closer'];
8×
207
                continue;
8×
208
            }
209

210
            // Skip over potentially large docblocks.
211
            if ($tokens[$nextComma]['code'] === \T_DOC_COMMENT_OPEN_TAG
364×
212
                && isset($tokens[$nextComma]['comment_closer'])
364×
213
            ) {
214
                $nextComma = $tokens[$nextComma]['comment_closer'];
4×
215
                continue;
4×
216
            }
217

218
            if ($tokens[$nextComma]['code'] !== \T_COMMA
364×
219
                && $tokens[$nextComma]['code'] !== $tokens[$closer]['code']
364×
220
            ) {
221
                // Just in case.
NEW
222
                continue;
!
223
            }
224

225
            // Ok, we've reached the end of the parameter.
226
            $paramEnd                  = ($nextComma - 1);
364×
227
            $parameters[$cnt]['start'] = $paramStart;
364×
228
            $parameters[$cnt]['end']   = $paramEnd;
364×
229
            $parameters[$cnt]['raw']   = \trim(GetTokensAsString::normal($phpcsFile, $paramStart, $paramEnd));
364×
230
            $parameters[$cnt]['clean'] = \trim(GetTokensAsString::noComments($phpcsFile, $paramStart, $paramEnd));
364×
231

232
            // Check if there are more tokens before the closing parenthesis.
233
            // Prevents function calls with trailing comma's from setting an extra parameter:
234
            // `functionCall( $param1, $param2, );`.
235
            $hasNextParam = $phpcsFile->findNext(
364×
236
                Tokens::$emptyTokens,
364×
237
                ($nextComma + 1),
364×
238
                $closer,
239
                true
240
            );
241
            if ($hasNextParam === false) {
364×
242
                break;
364×
243
            }
244

245
            // Prepare for the next parameter.
246
            $paramStart = ($nextComma + 1);
212×
247
            $cnt++;
212×
248
        }
249

250
        return $parameters;
364×
251
    }
252

253
    /**
254
     * Get information on a specific parameter passed.
255
     *
256
     * See {@see PHPCSUtils\Utils\PassedParameters::hasParameters()} for information on the supported
257
     * constructs.
258
     *
259
     * @since 1.0.0
260
     *
261
     * @param \PHP_CodeSniffer\Files\File $phpcsFile   The file where this token was found.
262
     * @param int                         $stackPtr    The position of the T_STRING, T_VARIABLE, T_ARRAY,
263
     *                                                 T_OPEN_SHORT_ARRAY, T_ISSET or T_UNSET token.
264
     * @param int                         $paramOffset The 1-based index position of the parameter to retrieve.
265
     *
266
     * @return array|false Returns an array with the "start" token pointer, "end" token pointer,
267
     *                     "raw" parameter value and "clean" (only code, no comments) parameter
268
     *                     value for the parameter at the specified offset.
269
     *                     Or FALSE if the specified parameter is not found.
270
     *
271
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the token passed is not one of the
272
     *                                                      accepted types or doesn't exist.
273
     */
274
    public static function getParameter(File $phpcsFile, $stackPtr, $paramOffset)
275
    {
276
        $parameters = self::getParameters($phpcsFile, $stackPtr);
52×
277

278
        if (isset($parameters[$paramOffset]) === false) {
52×
279
            return false;
4×
280
        }
281

282
        return $parameters[$paramOffset];
48×
283
    }
284

285
    /**
286
     * Count the number of parameters which have been passed.
287
     *
288
     * See {@see PHPCSUtils\Utils\PassedParameters::hasParameters()} for information on the supported
289
     * constructs.
290
     *
291
     * @since 1.0.0
292
     *
293
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
294
     * @param int                         $stackPtr  The position of the T_STRING, T_VARIABLE, T_ARRAY,
295
     *                                               T_OPEN_SHORT_ARRAY, T_ISSET or T_UNSET token.
296
     *
297
     * @return int
298
     *
299
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the token passed is not one of the
300
     *                                                      accepted types or doesn't exist.
301
     */
302
    public static function getParameterCount(File $phpcsFile, $stackPtr)
303
    {
304
        if (self::hasParameters($phpcsFile, $stackPtr) === false) {
256×
305
            return 0;
4×
306
        }
307

308
        return \count(self::getParameters($phpcsFile, $stackPtr));
252×
309
    }
310
}
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2023 Coveralls, Inc