diff --git a/PHPCSUtils/Utils/PassedParameters.php b/PHPCSUtils/Utils/PassedParameters.php index 157f6d24..94a2cf10 100644 --- a/PHPCSUtils/Utils/PassedParameters.php +++ b/PHPCSUtils/Utils/PassedParameters.php @@ -16,6 +16,7 @@ use PHPCSUtils\Tokens\Collections; use PHPCSUtils\Utils\Arrays; use PHPCSUtils\Utils\GetTokensAsString; +use PHPCSUtils\Utils\NamingConventions; /** * Utility functions to retrieve information about parameters passed to function calls, @@ -144,6 +145,8 @@ public static function hasParameters(File $phpcsFile, $stackPtr) * See {@see PassedParameters::hasParameters()} for information on the supported constructs. * * @since 1.0.0 + * @since 1.0.0-alpha4 Added support for PHP 8.0 function calls with named arguments by + * introducing the new `'name_start'`, `'name_end'` and `'name'` index keys. * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. * @param int $stackPtr The position of the `T_STRING`, PHP 8.0 identifier @@ -160,6 +163,23 @@ public static function hasParameters(File $phpcsFile, $stackPtr) * 'clean' => string, // Same as `raw`, but all comment tokens have been stripped out. * ) * ``` + * For function calls passing named arguments, the format is as follows: + * ```php + * 1 => array( + * 'name_start' => int, // The stack pointer to the first token in the parameter name. + * 'name_end' => int, // The stack pointer to the last token in the parameter name. + * // This will normally be the colon, but may be different in + * // PHPCS versions prior to the version adding support for + * // named parameters (PHPCS x.x.x). + * 'name' => string, // The parameter name as a string (without the colon). + * 'start' => int, // The stack pointer to the first token in the parameter value. + * 'end' => int, // The stack pointer to the last token in the parameter value. + * 'raw' => string, // A string with the contents of all tokens between `start` and `end`. + * 'clean' => string, // Same as `raw`, but all comment tokens have been stripped out. + * ) + * ``` + * The `'start'`, `'end'`, `'raw'` and `'clean'` indexes will always contain just and only + * information on the parameter value. * _Note: The array starts at index 1._ * If no parameters/array items are found, an empty array will be returned. * @@ -186,6 +206,8 @@ public static function getParameters(File $phpcsFile, $stackPtr) $closer = $tokens[$opener]['parenthesis_closer']; } + $mayHaveNames = (isset(Collections::functionCallTokens()[$tokens[$stackPtr]['code']]) === true); + $parameters = []; $nextComma = $opener; $paramStart = ($opener + 1); @@ -234,7 +256,46 @@ public static function getParameters(File $phpcsFile, $stackPtr) } // Ok, we've reached the end of the parameter. - $paramEnd = ($nextComma - 1); + $paramEnd = ($nextComma - 1); + + if ($mayHaveNames === true) { + $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $paramStart, ($paramEnd + 1), true); + if ($firstNonEmpty !== $paramEnd) { + /* + * BC: Prior to support for named parameters being added to PHPCS in PHPCS 3.6.0 (?), the + * parameter name + the colon would in most cases be tokenized as one token: T_GOTO_LABEL. + */ + if ($tokens[$firstNonEmpty]['code'] === \T_GOTO_LABEL) { + $parameters[$cnt]['name_start'] = $paramStart; + $parameters[$cnt]['name_end'] = $firstNonEmpty; + $parameters[$cnt]['name'] = \substr($tokens[$firstNonEmpty]['content'], 0, -1); + $paramStart = ($firstNonEmpty + 1); + } else { + // PHPCS 3.6.0 (?) and select situations in PHPCS < 3.6.0 (?). + $secondNonEmpty = $phpcsFile->findNext( + Tokens::$emptyTokens, + ($firstNonEmpty + 1), + ($paramEnd + 1), + true + ); + + /* + * BC: Checking the content of the colon token instead of the token type as in PHPCS < 3.6.0 (?) + * the colon _may_ be tokenized as `T_STRING` or even `T_INLINE_ELSE`. + */ + if ($tokens[$secondNonEmpty]['content'] === ':' + && ($tokens[$firstNonEmpty]['type'] === 'T_PARAM_NAME' + || NamingConventions::isValidIdentifierName($tokens[$firstNonEmpty]['content']) === true) + ) { + $parameters[$cnt]['name_start'] = $paramStart; + $parameters[$cnt]['name_end'] = $secondNonEmpty; + $parameters[$cnt]['name'] = $tokens[$firstNonEmpty]['content']; + $paramStart = ($secondNonEmpty + 1); + } + } + } + } + $parameters[$cnt]['start'] = $paramStart; $parameters[$cnt]['end'] = $paramEnd; $parameters[$cnt]['raw'] = \trim(GetTokensAsString::normal($phpcsFile, $paramStart, $paramEnd)); @@ -266,6 +327,9 @@ public static function getParameters(File $phpcsFile, $stackPtr) * * See {@see PassedParameters::hasParameters()} for information on the supported constructs. * + * @see PassedParameters::getParameterFromStack() For when the parameter stack of a function call is + * already retrieved. + * * @since 1.0.0 * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. @@ -273,31 +337,50 @@ public static function getParameters(File $phpcsFile, $stackPtr) * name token, `T_VARIABLE`, `T_ARRAY`, `T_OPEN_SHORT_ARRAY`, * `T_ISSET`, or `T_UNSET` token. * @param int $paramOffset The 1-based index position of the parameter to retrieve. + * @param string|string[] $paramNames Optional. Either the name of the target parameter + * to retrieve as a string or an array of names for the + * same target parameter. + * Only relevant for function calls. + * An arrays of names is supported to allow for functions + * for which the parameter names have undergone name + * changes over time. + * When specified, the name will take precedence over the + * offset. + * For PHP 8 support, it is STRONGLY recommended to + * always pass both the offset as well as the parameter + * name when examining function calls. * - * @return array|false Array with information on the parameter/array item at the specified offset. + * @return array|false Array with information on the parameter/array item at the specified offset, + * or with the specified name. * Or `FALSE` if the specified parameter/array item is not found. - * The format of the return value is: - * ```php - * array( - * 'start' => int, // The stack pointer to the first token in the parameter/array item. - * 'end' => int, // The stack pointer to the last token in the parameter/array item. - * 'raw' => string, // A string with the contents of all tokens between `start` and `end`. - * 'clean' => string, // Same as `raw`, but all comment tokens have been stripped out. - * ) - * ``` + * See {@see PassedParameters::getParameters()} for the format of the returned + * (single-dimensional) array. * * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the token passed is not one of the * accepted types or doesn't exist. + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If a function call parameter is requested and + * the `$paramName` parameter is not passed. */ - public static function getParameter(File $phpcsFile, $stackPtr, $paramOffset) + public static function getParameter(File $phpcsFile, $stackPtr, $paramOffset, $paramNames = []) { + $tokens = $phpcsFile->getTokens(); $parameters = self::getParameters($phpcsFile, $stackPtr); - if (isset($parameters[$paramOffset]) === false) { + /* + * Non-function calls. + */ + if (isset(Collections::functionCallTokens()[$tokens[$stackPtr]['code']]) === false) { + if (isset($parameters[$paramOffset]) === true) { + return $parameters[$paramOffset]; + } + return false; } - return $parameters[$paramOffset]; + /* + * Function calls. + */ + return self::getParameterFromStack($parameters, $paramOffset, $paramNames); } /** @@ -325,4 +408,68 @@ public static function getParameterCount(File $phpcsFile, $stackPtr) return \count(self::getParameters($phpcsFile, $stackPtr)); } + + /** + * Get information on a specific function call parameter passed. + * + * This is an efficiency method to correcty handle positional versus named parameters + * for function calls when multiple parameters need to be examined. + * + * See {@see PassedParameters::hasParameters()} for information on the supported constructs. + * + * @since 1.0.0 + * + * @param array $parameters The output of a previous call to {@see PassedParameters::getParameters()}. + * @param int $paramOffset The 1-based index position of the parameter to retrieve. + * @param string|string[] $paramNames Either the name of the target parameter to retrieve + * as a string or an array of names for the same target parameter. + * An arrays of names is supported to allow for functions + * for which the parameter names have undergone name + * changes over time. + * The name will take precedence over the offset. + * + * @return array|false Array with information on the parameter at the specified offset, + * or with the specified name. + * Or `FALSE` if the specified parameter is not found. + * See {@see PassedParameters::getParameters()} for the format of the returned + * (single-dimensional) array. + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the `$paramNames` parameter is not passed + * and the requested parameter was not passed + * as a positional parameter in the function call + * being examined. + */ + public static function getParameterFromStack(array $parameters, $paramOffset, $paramNames) + { + if (empty($parameters) === true) { + return false; + } + + // First check for positional parameters. + if (isset($parameters[$paramOffset]) === true + && isset($parameters[$paramOffset]['name']) === false + ) { + return $parameters[$paramOffset]; + } + + $paramNames = \array_flip((array) $paramNames); + if (empty($paramNames) === true) { + throw new RuntimeException( + 'To allow for support for PHP 8 named parameters, the $paramNames parameter must be passed.' + ); + } + + // Next check if a named parameter was passed with the specified name. + foreach ($parameters as $paramDetails) { + if (isset($paramDetails['name']) === false) { + continue; + } + + if (isset($paramNames[$paramDetails['name']]) === true) { + return $paramDetails; + } + } + + return false; + } } diff --git a/Tests/Utils/PassedParameters/GetParameterFromStackTest.inc b/Tests/Utils/PassedParameters/GetParameterFromStackTest.inc new file mode 100644 index 00000000..b18b415a --- /dev/null +++ b/Tests/Utils/PassedParameters/GetParameterFromStackTest.inc @@ -0,0 +1,59 @@ +getTargetToken('/* testNoParams */', \T_STRING); + + $result = PassedParameters::getParameter(self::$phpcsFile, $stackPtr, 2, 'value'); + $this->assertFalse($result); + } + + /** + * Test retrieving the parameter details from a non-function call without passing a valid name + * to make sure that no error notice is thrown for the missing parameter name. + * + * @dataProvider dataGetParameterNonFunctionCallMissingParamName + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param int|string $targetType The type of token to look for. + * + * @return void + */ + public function testGetParameterNonFunctionCallMissingParamName($testMarker, $targetType) + { + $stackPtr = $this->getTargetToken($testMarker, $targetType); + $expected = [ + 'start' => ($stackPtr + 5), + 'end' => ($stackPtr + 6), + 'raw' => '$var2', + 'clean' => '$var2', + ]; + + $result = PassedParameters::getParameter(self::$phpcsFile, $stackPtr, 2); + $this->assertSame($expected, $result); + } + + /** + * Data provider. + * + * @see testGetParameterNonFunctionCallMissingParamName() For the array format. + * + * @return array + */ + public function dataGetParameterNonFunctionCallMissingParamName() + { + return [ + 'isset' => [ + '/* testIsset */', + \T_ISSET, + ], + 'array' => [ + '/* testArray */', + \T_ARRAY, + ], + ]; + } + + /** + * Test retrieving the parameter details from a function call with only positional parameters + * without passing a valid name. + * + * @return void + */ + public function testGetParameterFunctionCallPositionalMissingParamName() + { + $stackPtr = $this->getTargetToken('/* testAllParamsPositional */', \T_STRING); + $expected = [ + 'start' => ($stackPtr + 5), + 'end' => ($stackPtr + 6), + 'raw' => "'value'", + 'clean' => "'value'", + ]; + + $result = PassedParameters::getParameter(self::$phpcsFile, $stackPtr, 2); + $this->assertSame($expected, $result); + } + + /** + * Test retrieving the parameter details from a function call with only named parameters + * without passing a valid name. + * + * @return void + */ + public function testGetParameterFunctionCallMissingParamName() + { + $this->expectPhpcsException( + 'To allow for support for PHP 8 named parameters, the $paramNames parameter must be passed.' + ); + + $stackPtr = $this->getTargetToken('/* testAllParamsNamedStandardOrder */', \T_STRING); + + PassedParameters::getParameter(self::$phpcsFile, $stackPtr, 2); + } + + /** + * Test retrieving the details for a specific parameter from a function call or construct. + * + * @dataProvider dataGetParameterFromStack + * + * @param string $testMarker The comment which prefaces the target token in the test file. + * @param array $expectedName The expected result array for the $name parameter. + * @param array|false $expectedExpires The expected result array for the $expires_or_options parameter. + * @param array|false $expectedHttpOnly The expected result array for the $httponly parameter. + * + * @return void + */ + public function testGetParameterFromStack($testMarker, $expectedName, $expectedExpires, $expectedHttpOnly) + { + $stackPtr = $this->getTargetToken($testMarker, \T_STRING); + $parameters = PassedParameters::getParameters(self::$phpcsFile, $stackPtr); + + /* + * Test $name parameter. Param name passed as string. + */ + $expected = false; + if ($expectedName !== false) { + // Start/end token position values in the expected array are set as offsets + // in relation to the target token. + // Change these to exact positions based on the retrieved stackPtr. + $expected = $expectedName; + $expected['start'] += $stackPtr; + $expected['end'] += $stackPtr; + if (isset($expected['name_start'], $expected['name_end']) === true) { + $expected['name_start'] += $stackPtr; + $expected['name_end'] += $stackPtr; + } + $expected['clean'] = $expected['raw']; + } + + $result = PassedParameters::getParameterFromStack($parameters, 1, 'name'); + $this->assertSame($expected, $result, 'Expected output for parameter 1 ("name") did not match'); + + /* + * Test $expires_or_options parameter. Param name passed as array with alternative names. + */ + $expected = false; + if ($expectedExpires !== false) { + // Start/end token position values in the expected array are set as offsets + // in relation to the target token. + // Change these to exact positions based on the retrieved stackPtr. + $expected = $expectedExpires; + $expected['start'] += $stackPtr; + $expected['end'] += $stackPtr; + if (isset($expected['name_start'], $expected['name_end']) === true) { + $expected['name_start'] += $stackPtr; + $expected['name_end'] += $stackPtr; + } + $expected['clean'] = $expected['raw']; + } + + $result = PassedParameters::getParameterFromStack($parameters, 3, ['expires_or_options', 'expires', 'options']); + $this->assertSame($expected, $result, 'Expected output for parameter 3 ("expires_or_options") did not match'); + + /* + * Test $httponly parameter. Param name passed as array. + */ + $expected = false; + if ($expectedHttpOnly !== false) { + // Start/end token position values in the expected array are set as offsets + // in relation to the target token. + // Change these to exact positions based on the retrieved stackPtr. + $expected = $expectedHttpOnly; + $expected['start'] += $stackPtr; + $expected['end'] += $stackPtr; + if (isset($expected['name_start'], $expected['name_end']) === true) { + $expected['name_start'] += $stackPtr; + $expected['name_end'] += $stackPtr; + } + $expected['clean'] = $expected['raw']; + } + + $result = PassedParameters::getParameterFromStack($parameters, 7, ['httponly']); + $this->assertSame($expected, $result, 'Expected output for parameter 7 ("httponly") did not match'); + } + + /** + * Data provider. + * + * @see testGetParameterFromStack() For the array format. + * + * @return array + */ + public function dataGetParameterFromStack() + { + /* + * Work around to account for the different token positions due to the old tokenization + * to T_GOTO_LABEL which joins two tokens into one (incorrectly). + */ + $namedParamsInPhpcs = false; + + return [ + 'all-params-all-positional' => [ + 'marker' => '/* testAllParamsPositional */', + 'name' => [ + 'start' => 2, + 'end' => 3, + 'raw' => "'name'", + ], + 'expires_or_options' => [ + 'start' => 8, + 'end' => 25, + 'raw' => 'time() + (60 * 60 * 24)', + ], + 'httponly' => [ + 'start' => 36, + 'end' => 38, + 'raw' => 'false', + ], + ], + 'all-params-all-named-standard-order' => [ + 'marker' => '/* testAllParamsNamedStandardOrder */', + 'name' => [ + 'name_start' => 2, + 'name_end' => ($namedParamsInPhpcs === true) ? 5 : 4, + 'name' => 'name', + 'start' => ($namedParamsInPhpcs === true) ? 6 : 5, + 'end' => ($namedParamsInPhpcs === true) ? 7 : 6, + 'raw' => "'name'", + ], + 'expires_or_options' => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 16 : 14, + 'name_end' => ($namedParamsInPhpcs === true) ? 19 : 16, + 'name' => 'expires_or_options', + 'start' => ($namedParamsInPhpcs === true) ? 20 : 17, + 'end' => ($namedParamsInPhpcs === true) ? 37 : 34, + 'raw' => 'time() + (60 * 60 * 24)', + ], + 'httponly' => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 60 : 54, + 'name_end' => ($namedParamsInPhpcs === true) ? 63 : 56, + 'name' => 'httponly', + 'start' => ($namedParamsInPhpcs === true) ? 64 : 57, + 'end' => ($namedParamsInPhpcs === true) ? 66 : 59, + 'raw' => 'false', + ], + ], + 'all-params-all-named-random-order' => [ + 'marker' => '/* testAllParamsNamedNonStandardOrder */', + 'name' => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 32 : 30, + 'name_end' => ($namedParamsInPhpcs === true) ? 35 : 32, + 'name' => 'name', + 'start' => ($namedParamsInPhpcs === true) ? 36 : 33, + 'end' => ($namedParamsInPhpcs === true) ? 37 : 34, + 'raw' => "'name'", + ], + 'expires_or_options' => [ + 'name_start' => 2, + 'name_end' => ($namedParamsInPhpcs === true) ? 5 : 4, + 'name' => 'expires_or_options', + 'start' => ($namedParamsInPhpcs === true) ? 6 : 5, + 'end' => ($namedParamsInPhpcs === true) ? 23 : 22, + 'raw' => 'time() + (60 * 60 * 24)', + ], + 'httponly' => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 53 : 48, + 'name_end' => ($namedParamsInPhpcs === true) ? 56 : 50, + 'name' => 'httponly', + 'start' => ($namedParamsInPhpcs === true) ? 57 : 51, + 'end' => ($namedParamsInPhpcs === true) ? 58 : 52, + 'raw' => 'false', + ], + ], + 'all-params-mixed-positional-and-named' => [ + 'marker' => '/* testMixedPositionalAndNamedParams */', + 'name' => [ + 'start' => 2, + 'end' => 4, + 'raw' => "'name'", + ], + 'expires_or_options' => [ + 'start' => 10, + 'end' => 28, + 'raw' => 'time() + (60 * 60 * 24)', + ], + 'httponly' => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 44 : 42, + 'name_end' => ($namedParamsInPhpcs === true) ? 47 : 44, + 'name' => 'httponly', + 'start' => ($namedParamsInPhpcs === true) ? 48 : 45, + 'end' => ($namedParamsInPhpcs === true) ? 49 : 46, + 'raw' => 'false', + ], + ], + 'select-params-mixed-positional-and-named' => [ + 'marker' => '/* testMixedPositionalAndNamedParamsNotAllOptionalSet */', + 'name' => [ + 'start' => 2, + 'end' => 4, + 'raw' => "'name'", + ], + 'expires_or_options' => [ + 'name_start' => 6, + 'name_end' => ($namedParamsInPhpcs === true) ? 9 : 8, + 'name' => 'expires_or_options', + 'start' => ($namedParamsInPhpcs === true) ? 10 : 9, + 'end' => ($namedParamsInPhpcs === true) ? 27 : 26, + 'raw' => 'time() + (60 * 60 * 24)', + ], + 'httponly' => false, + ], + 'select-params-mixed-positional-and-named-old-name' => [ + 'marker' => '/* testMixedPositionalAndNamedParamsOldName */', + 'name' => [ + 'start' => 2, + 'end' => 4, + 'raw' => "'name'", + ], + 'expires_or_options' => [ + 'name_start' => 6, + 'name_end' => ($namedParamsInPhpcs === true) ? 9 : 8, + 'name' => 'expires', + 'start' => ($namedParamsInPhpcs === true) ? 10 : 9, + 'end' => ($namedParamsInPhpcs === true) ? 27 : 26, + 'raw' => 'time() + (60 * 60 * 24)', + ], + 'httponly' => false, + ], + ]; + } +} diff --git a/Tests/Utils/PassedParameters/GetParametersNamedTest.inc b/Tests/Utils/PassedParameters/GetParametersNamedTest.inc new file mode 100644 index 00000000..3cd9cfcf --- /dev/null +++ b/Tests/Utils/PassedParameters/GetParametersNamedTest.inc @@ -0,0 +1,92 @@ +getPos(skip: false), + count: /* testNestedFunctionCallInner2 */ count(array_or_countable: $array), + value: 50 +); + +/* testNamespacedFQNFunction */ +\Fully\Qualified\function_name(label: $string, more:false); + +/* testVariableFunction */ +$fn(label: $string, more:false); + +/* testClassInstantiationStatic */ +$obj = new static(label: $string, more:false); + +/* testAnonClass */ +$anon = new class(label: $string, more: false) { + public function __construct($label, $more) {} +}; + +function myfoo( $💩💩💩, $Пасха, $_valid) {} +/* testNonAsciiNames */ +foo(💩💩💩: [], Пасха: 'text', _valid: 123); + +/* testMixedPositionalAndNamedArgsWithTernary */ +foo( $cond ? true : false, name: $value2 ); + +/* testNamedArgWithTernary */ +foo( label: $cond ? true : false, more: $cond ? CONSTANT_A : CONSTANT_B ); + +/* testTernaryWithFunctionCallsInThenElse */ +echo $cond ? foo( label: $something ) : /* testTernaryWithFunctionCallsInElse */ bar( more: $something_else ); + +/* testCompileErrorNamedBeforePositional */ +// Not the concern of PHPCSUtils. Should still be handled. +test(param: $bar, $foo); + +/* testDuplicateName */ +// Error Exception, but not the concern of PHPCSUtils. Should still be handled. +test(param: 1, param: 2); + +/* testIncorrectOrderWithVariadic */ +// Error Exception, but not the concern of PHPCSUtils. Should still be handled. +array_fill(start_index: 0, ...[100, 50]); + +/* testCompileErrorIncorrectOrderWithVariadic */ +// Not the concern of PHPCSUtils. Should still be handled. +test(...$values, param: $value); + +/* testParseErrorNoValue */ +// Not the concern of PHPCSUtils. Should still be handled. +//test(param1:, param2:); + +/* testParseErrorDynamicName */ +// Parse error. Ignore. +function_name($variableStoringParamName: $value); + +/* testReservedKeywordAsName */ +// Note: do not remove any of these - some are testing very specific cross-version tokenizer issues. +foobar( + abstract: $value, + class: $value, + const: $value, + function: $value, + iterable: $value, + match: $value, + protected: $value, + object: $value, + parent: $value, +); diff --git a/Tests/Utils/PassedParameters/GetParametersNamedTest.php b/Tests/Utils/PassedParameters/GetParametersNamedTest.php new file mode 100644 index 00000000..3b28667b --- /dev/null +++ b/Tests/Utils/PassedParameters/GetParametersNamedTest.php @@ -0,0 +1,642 @@ +getTargetToken($testMarker, [$targetType], $targetContent); + + // Start/end token position values in the expected array are set as offsets + // in relation to the target token. + // Change these to exact positions based on the retrieved stackPtr. + foreach ($expected as $key => $value) { + $expected[$key]['start'] = ($stackPtr + $value['start']); + $expected[$key]['end'] = ($stackPtr + $value['end']); + + if (isset($value['name_start'], $value['name_end']) === true) { + $expected[$key]['name_start'] = ($stackPtr + $value['name_start']); + $expected[$key]['name_end'] = ($stackPtr + $value['name_end']); + } + } + + $result = PassedParameters::getParameters(self::$phpcsFile, $stackPtr); + + foreach ($result as $key => $value) { + $this->assertArrayHasKey('clean', $value); + + // The GetTokensAsString functions have their own tests, no need to duplicate it here. + unset($result[$key]['clean']); + } + + $this->assertSame($expected, $result); + } + + /** + * Data provider. + * + * @see testGetParameters() For the array format. + * + * @return array + */ + public function dataGetParameters() + { + /* + * Work arounds to account for: + * 1. The different tokenization of namespaces names in PHP 8 and different PHPCS versions. + * 2. The different token positions due to the old tokenization to T_GOTO_LABEL + * which joins two tokens into one (incorrectly). + * 3. The new `match` keyword being recognized on PHP 8, but not before, while + * the `match` control structure is not supported in PHPCS yet. + */ + $php8Names = parent::usesPhp8NameTokens(); + $namedParamsInPhpcs = false; + $matchIsKeyword = \version_compare(\PHP_VERSION_ID, '80000', '>='); + + return [ + 'only-positional-args' => [ + '/* testPositionalArgs */', + \T_STRING, + [ + 1 => [ + 'start' => 2, + 'end' => 2, + 'raw' => 'START_INDEX', + ], + 2 => [ + 'start' => 4, + 'end' => ($php8Names === true) ? 5 : 6, + 'raw' => '\COUNT', + ], + 3 => [ + 'start' => ($php8Names === true) ? 7 : 8, + 'end' => ($php8Names === true) ? 7 : 11, + 'raw' => 'MyNS\VALUE', + ], + ], + ], + 'named-args' => [ + '/* testNamedArgs */', + \T_STRING, + [ + 1 => [ + 'name_start' => 2, + 'name_end' => ($namedParamsInPhpcs === true) ? 3 : 2, + 'name' => 'start_index', + 'start' => ($namedParamsInPhpcs === true) ? 4 : 3, + 'end' => ($namedParamsInPhpcs === true) ? 5 : 4, + 'raw' => '0', + ], + 2 => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 7 : 6, + 'name_end' => ($namedParamsInPhpcs === true) ? 9 : 7, + 'name' => 'count', + 'start' => ($namedParamsInPhpcs === true) ? 10 : 8, + 'end' => ($namedParamsInPhpcs === true) ? 11 : 9, + 'raw' => '100', + ], + 3 => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 13 : 11, + 'name_end' => ($namedParamsInPhpcs === true) ? 15 : 12, + 'name' => 'value', + 'start' => ($namedParamsInPhpcs === true) ? 16 : 13, + 'end' => ($namedParamsInPhpcs === true) ? 17 : 14, + 'raw' => '50', + ], + ], + ], + 'named-args-multiline' => [ + '/* testNamedArgsMultiline */', + \T_STRING, + [ + 1 => [ + 'name_start' => 2, + 'name_end' => 6, + 'name' => 'start_index', + 'start' => 7, + 'end' => 8, + 'raw' => '0', + ], + 2 => [ + 'name_start' => 10, + 'name_end' => 14, + 'name' => 'count', + 'start' => 15, + 'end' => 16, + 'raw' => '100', + ], + 3 => [ + 'name_start' => 18, + 'name_end' => 22, + 'name' => 'value', + 'start' => 23, + 'end' => 24, + 'raw' => '50', + ], + ], + ], + 'named-args-whitespace-comments' => [ + '/* testNamedArgsWithWhitespaceAndComments */', + \T_STRING, + [ + 1 => [ + 'name_start' => 3, + 'name_end' => 6, + 'name' => 'start_index', + 'start' => 7, + 'end' => 8, + 'raw' => '0', + ], + 2 => [ + 'name_start' => 10, + 'name_end' => 17, + 'name' => 'count', + 'start' => 18, + 'end' => 19, + 'raw' => '100', + ], + 3 => [ + 'name_start' => 21, + 'name_end' => ($namedParamsInPhpcs === true) ? 23 : 22, + 'name' => 'value', + 'start' => ($namedParamsInPhpcs === true) ? 24 : 23, + 'end' => ($namedParamsInPhpcs === true) ? 25 : 24, + 'raw' => '50', + ], + ], + ], + 'mixed-positional-and-named-args' => [ + '/* testMixedPositionalAndNamedArgs */', + \T_STRING, + [ + 1 => [ + 'start' => 2, + 'end' => 2, + 'raw' => '$string', + ], + 2 => [ + 'name_start' => 4, + 'name_end' => ($namedParamsInPhpcs === true) ? 6 : 5, + 'name' => 'double_encode', + 'start' => ($namedParamsInPhpcs === true) ? 7 : 6, + 'end' => ($namedParamsInPhpcs === true) ? 8 : 7, + 'raw' => 'false', + ], + ], + ], + 'named-args-nested-function-call-outer' => [ + '/* testNestedFunctionCallOuter */', + \T_STRING, + [ + 1 => [ + 'name_start' => 2, + 'name_end' => ($namedParamsInPhpcs === true) ? 5 : 4, + 'name' => 'start_index', + 'start' => ($namedParamsInPhpcs === true) ? 6 : 5, + 'end' => ($namedParamsInPhpcs === true) ? 17 : 15, + 'raw' => '/* testNestedFunctionCallInner1 */ $obj->getPos(skip: false)', + ], + 2 => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 19 : 17, + 'name_end' => ($namedParamsInPhpcs === true) ? 22 : 19, + 'name' => 'count', + 'start' => ($namedParamsInPhpcs === true) ? 23 : 20, + 'end' => ($namedParamsInPhpcs === true) ? 32 : 28, + 'raw' => '/* testNestedFunctionCallInner2 */ count(array_or_countable: $array)', + ], + 3 => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 34 : 30, + 'name_end' => ($namedParamsInPhpcs === true) ? 37 : 32, + 'name' => 'value', + 'start' => ($namedParamsInPhpcs === true) ? 38 : 33, + 'end' => ($namedParamsInPhpcs === true) ? 40 : 35, + 'raw' => '50', + ], + ], + ], + 'named-args-nested-function-call-inner-1' => [ + '/* testNestedFunctionCallInner1 */', + \T_STRING, + [ + 1 => [ + 'name_start' => 2, + 'name_end' => ($namedParamsInPhpcs === true) ? 3 : 2, + 'name' => 'skip', + 'start' => ($namedParamsInPhpcs === true) ? 4 : 3, + 'end' => ($namedParamsInPhpcs === true) ? 5 : 4, + 'raw' => 'false', + ], + ], + ], + 'named-args-nested-function-call-inner-2' => [ + '/* testNestedFunctionCallInner2 */', + \T_STRING, + [ + 1 => [ + 'name_start' => 2, + 'name_end' => ($namedParamsInPhpcs === true) ? 3 : 2, + 'name' => 'array_or_countable', + 'start' => ($namedParamsInPhpcs === true) ? 4 : 3, + 'end' => ($namedParamsInPhpcs === true) ? 5 : 4, + 'raw' => '$array', + ], + ], + ], + 'named-args-in-fqn-function-call' => [ + '/* testNamespacedFQNFunction */', + ($php8Names === true) ? \T_NAME_FULLY_QUALIFIED : \T_STRING, + [ + 1 => [ + 'name_start' => 2, + 'name_end' => ($namedParamsInPhpcs === true) ? 3 : 2, + 'name' => 'label', + 'start' => ($namedParamsInPhpcs === true) ? 4 : 3, + 'end' => ($namedParamsInPhpcs === true) ? 5 : 4, + 'raw' => '$string', + ], + 2 => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 7 : 6, + 'name_end' => ($namedParamsInPhpcs === true) ? 9 : 7, + 'name' => 'more', + 'start' => ($namedParamsInPhpcs === true) ? 10 : 8, + 'end' => ($namedParamsInPhpcs === true) ? 10 : 8, + 'raw' => 'false', + ], + ], + ($php8Names === true) ? null : 'function_name', + ], + 'named-args-in-variable-function-call' => [ + '/* testVariableFunction */', + \T_VARIABLE, + [ + 1 => [ + 'name_start' => 2, + 'name_end' => ($namedParamsInPhpcs === true) ? 3 : 2, + 'name' => 'label', + 'start' => ($namedParamsInPhpcs === true) ? 4 : 3, + 'end' => ($namedParamsInPhpcs === true) ? 5 : 4, + 'raw' => '$string', + ], + 2 => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 7 : 6, + 'name_end' => ($namedParamsInPhpcs === true) ? 9 : 7, + 'name' => 'more', + 'start' => ($namedParamsInPhpcs === true) ? 10 : 8, + 'end' => ($namedParamsInPhpcs === true) ? 10 : 8, + 'raw' => 'false', + ], + ], + ], + 'named-args-in-class-instantiation-with-static' => [ + '/* testClassInstantiationStatic */', + \T_STATIC, + [ + 1 => [ + 'name_start' => 2, + 'name_end' => ($namedParamsInPhpcs === true) ? 3 : 2, + 'name' => 'label', + 'start' => ($namedParamsInPhpcs === true) ? 4 : 3, + 'end' => ($namedParamsInPhpcs === true) ? 5 : 4, + 'raw' => '$string', + ], + 2 => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 7 : 6, + 'name_end' => ($namedParamsInPhpcs === true) ? 9 : 7, + 'name' => 'more', + 'start' => ($namedParamsInPhpcs === true) ? 10 : 8, + 'end' => ($namedParamsInPhpcs === true) ? 10 : 8, + 'raw' => 'false', + ], + ], + ], + 'named-args-in-anon-class-instantiation' => [ + '/* testAnonClass */', + \T_ANON_CLASS, + [ + 1 => [ + 'name_start' => 2, + 'name_end' => ($namedParamsInPhpcs === true) ? 3 : 2, + 'name' => 'label', + 'start' => ($namedParamsInPhpcs === true) ? 4 : 3, + 'end' => ($namedParamsInPhpcs === true) ? 5 : 4, + 'raw' => '$string', + ], + 2 => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 7 : 6, + 'name_end' => ($namedParamsInPhpcs === true) ? 9 : 7, + 'name' => 'more', + 'start' => ($namedParamsInPhpcs === true) ? 10 : 8, + 'end' => ($namedParamsInPhpcs === true) ? 11 : 9, + 'raw' => 'false', + ], + ], + ], + 'named-args-non-ascii-names' => [ + '/* testNonAsciiNames */', + \T_STRING, + [ + 1 => [ + 'name_start' => 2, + 'name_end' => ($namedParamsInPhpcs === true) ? 3 : 2, + 'name' => '💩💩💩', + 'start' => ($namedParamsInPhpcs === true) ? 4 : 3, + 'end' => ($namedParamsInPhpcs === true) ? 6 : 5, + 'raw' => '[]', + ], + 2 => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 8 : 7, + 'name_end' => ($namedParamsInPhpcs === true) ? 10 : 8, + 'name' => 'Пасха', + 'start' => ($namedParamsInPhpcs === true) ? 11 : 9, + 'end' => ($namedParamsInPhpcs === true) ? 12 : 10, + 'raw' => "'text'", + ], + 3 => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 14 : 12, + 'name_end' => ($namedParamsInPhpcs === true) ? 16 : 13, + 'name' => '_valid', + 'start' => ($namedParamsInPhpcs === true) ? 17 : 14, + 'end' => ($namedParamsInPhpcs === true) ? 18 : 15, + 'raw' => '123', + ], + ], + ], + 'mixed-positional-and-named-args-with-ternary' => [ + '/* testMixedPositionalAndNamedArgsWithTernary */', + \T_STRING, + [ + 1 => [ + 'start' => 2, + 'end' => 11, + 'raw' => '$cond ? true : false', + ], + 2 => [ + 'name_start' => 13, + 'name_end' => 15, + 'name' => 'name', + 'start' => 16, + 'end' => 18, + 'raw' => '$value2', + ], + ], + ], + 'named-args-with-ternary' => [ + '/* testNamedArgWithTernary */', + \T_STRING, + [ + 1 => [ + 'name_start' => 2, + 'name_end' => ($namedParamsInPhpcs === true) ? 4 : 3, + 'name' => 'label', + 'start' => ($namedParamsInPhpcs === true) ? 5 : 4, + 'end' => ($namedParamsInPhpcs === true) ? 14 : 13, + 'raw' => '$cond ? true : false', + ], + 2 => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 16 : 15, + 'name_end' => ($namedParamsInPhpcs === true) ? 18 : 17, + 'name' => 'more', + 'start' => ($namedParamsInPhpcs === true) ? 19 : 18, + 'end' => ($namedParamsInPhpcs === true) ? 29 : 28, + 'raw' => '$cond ? CONSTANT_A : CONSTANT_B', + ], + ], + ], + 'ternary-with-function-call-in-then' => [ + '/* testTernaryWithFunctionCallsInThenElse */', + \T_STRING, + [ + 1 => [ + 'name_start' => 2, + 'name_end' => 4, + 'name' => 'label', + 'start' => 5, + 'end' => 7, + 'raw' => '$something', + ], + ], + ], + 'ternary-with-function-call-in-else' => [ + '/* testTernaryWithFunctionCallsInElse */', + \T_STRING, + [ + 1 => [ + 'name_start' => 2, + 'name_end' => 4, + 'name' => 'more', + 'start' => 5, + 'end' => 7, + 'raw' => '$something_else', + ], + ], + ], + 'named-args-compile-error-named-before-positional' => [ + '/* testCompileErrorNamedBeforePositional */', + \T_STRING, + [ + 1 => [ + 'name_start' => 2, + 'name_end' => ($namedParamsInPhpcs === true) ? 3 : 2, + 'name' => 'param', + 'start' => ($namedParamsInPhpcs === true) ? 4 : 3, + 'end' => ($namedParamsInPhpcs === true) ? 5 : 4, + 'raw' => '$bar', + ], + 2 => [ + 'start' => ($namedParamsInPhpcs === true) ? 7 : 6, + 'end' => ($namedParamsInPhpcs === true) ? 8 : 7, + 'raw' => '$foo', + ], + ], + ], + 'named-args-error-exception-duplicate-name' => [ + '/* testDuplicateName */', + \T_STRING, + [ + 1 => [ + 'name_start' => 2, + 'name_end' => ($namedParamsInPhpcs === true) ? 3 : 2, + 'name' => 'param', + 'start' => ($namedParamsInPhpcs === true) ? 4 : 3, + 'end' => ($namedParamsInPhpcs === true) ? 5 : 4, + 'raw' => '1', + ], + 2 => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 7 : 6, + 'name_end' => ($namedParamsInPhpcs === true) ? 9 : 7, + 'name' => 'param', + 'start' => ($namedParamsInPhpcs === true) ? 10 : 8, + 'end' => ($namedParamsInPhpcs === true) ? 11 : 9, + 'raw' => '2', + ], + ], + ], + 'named-args-error-exception-incorrect-order-variadic' => [ + '/* testIncorrectOrderWithVariadic */', + \T_STRING, + [ + 1 => [ + 'name_start' => 2, + 'name_end' => ($namedParamsInPhpcs === true) ? 3 : 2, + 'name' => 'start_index', + 'start' => ($namedParamsInPhpcs === true) ? 4 : 3, + 'end' => ($namedParamsInPhpcs === true) ? 5 : 4, + 'raw' => '0', + ], + 2 => [ + 'start' => ($namedParamsInPhpcs === true) ? 7 : 6, + 'end' => ($namedParamsInPhpcs === true) ? 14 : 13, + 'raw' => '...[100, 50]', + ], + ], + ], + 'named-args-compile-error-incorrect-order-variadic' => [ + '/* testCompileErrorIncorrectOrderWithVariadic */', + \T_STRING, + [ + 1 => [ + 'start' => 2, + 'end' => 3, + 'raw' => '...$values', + ], + 2 => [ + 'name_start' => 5, + 'name_end' => ($namedParamsInPhpcs === true) ? 7 : 6, + 'name' => 'param', + 'start' => ($namedParamsInPhpcs === true) ? 8 : 7, + 'end' => ($namedParamsInPhpcs === true) ? 9 : 8, + 'raw' => '$value', + ], + ], + ], + 'named-args-parse-error-dynamic-name' => [ + '/* testParseErrorDynamicName */', + \T_STRING, + [ + 1 => [ + 'start' => 2, + 'end' => 5, + 'raw' => '$variableStoringParamName: $value', + ], + ], + ], + 'named-args-using-reserved-keywords' => [ + '/* testReservedKeywordAsName */', + \T_STRING, + [ + 1 => [ + 'name_start' => 2, + 'name_end' => 5, + 'name' => 'abstract', + 'start' => 6, + 'end' => 7, + 'raw' => '$value', + ], + 2 => [ + 'name_start' => 9, + 'name_end' => 12, + 'name' => 'class', + 'start' => 13, + 'end' => 14, + 'raw' => '$value', + ], + 3 => [ + 'name_start' => 16, + 'name_end' => 19, + 'name' => 'const', + 'start' => 20, + 'end' => 21, + 'raw' => '$value', + ], + 4 => [ + 'name_start' => 23, + 'name_end' => 26, + 'name' => 'function', + 'start' => 27, + 'end' => 28, + 'raw' => '$value', + ], + 5 => [ + 'name_start' => 30, + 'name_end' => ($namedParamsInPhpcs === true) ? 33 : 32, + 'name' => 'iterable', + 'start' => ($namedParamsInPhpcs === true) ? 34 : 33, + 'end' => ($namedParamsInPhpcs === true) ? 35 : 34, + 'raw' => '$value', + ], + 6 => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 37 : 36, + 'name_end' => ($namedParamsInPhpcs === true) ? 40 : (($matchIsKeyword === true) ? 39 : 38), + 'name' => 'match', + 'start' => ($namedParamsInPhpcs === true) ? 41 : (($matchIsKeyword === true) ? 40 : 39), + 'end' => ($namedParamsInPhpcs === true) ? 42 : (($matchIsKeyword === true) ? 41 : 40), + 'raw' => '$value', + ], + 7 => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 44 : (($matchIsKeyword === true) ? 43 : 42), + 'name_end' => ($namedParamsInPhpcs === true) ? 47 : (($matchIsKeyword === true) ? 46 : 45), + 'name' => 'protected', + 'start' => ($namedParamsInPhpcs === true) ? 48 : (($matchIsKeyword === true) ? 47 : 46), + 'end' => ($namedParamsInPhpcs === true) ? 49 : (($matchIsKeyword === true) ? 48 : 47), + 'raw' => '$value', + ], + 8 => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 51 : (($matchIsKeyword === true) ? 50 : 49), + 'name_end' => ($namedParamsInPhpcs === true) ? 54 : (($matchIsKeyword === true) ? 52 : 51), + 'name' => 'object', + 'start' => ($namedParamsInPhpcs === true) ? 55 : (($matchIsKeyword === true) ? 53 : 52), + 'end' => ($namedParamsInPhpcs === true) ? 56 : (($matchIsKeyword === true) ? 54 : 53), + 'raw' => '$value', + ], + 9 => [ + 'name_start' => ($namedParamsInPhpcs === true) ? 58 : (($matchIsKeyword === true) ? 56 : 55), + 'name_end' => ($namedParamsInPhpcs === true) ? 61 : (($matchIsKeyword === true) ? 58 : 57), + 'name' => 'parent', + 'start' => ($namedParamsInPhpcs === true) ? 62 : (($matchIsKeyword === true) ? 59 : 58), + 'end' => ($namedParamsInPhpcs === true) ? 63 : (($matchIsKeyword === true) ? 60 : 59), + 'raw' => '$value', + ], + ], + ], + ]; + } +} diff --git a/Tests/Utils/PassedParameters/GetParametersTest.php b/Tests/Utils/PassedParameters/GetParametersTest.php index 0211416e..4628a950 100644 --- a/Tests/Utils/PassedParameters/GetParametersTest.php +++ b/Tests/Utils/PassedParameters/GetParametersTest.php @@ -20,6 +20,7 @@ * * @covers \PHPCSUtils\Utils\PassedParameters::getParameters * @covers \PHPCSUtils\Utils\PassedParameters::getParameter + * @covers \PHPCSUtils\Utils\PassedParameters::getParameterFromStack * @covers \PHPCSUtils\Utils\PassedParameters::hasParameters * * @group passedparameters