Skip to content

Commit

Permalink
Merge pull request #261 from PHPCSStandards/passedparameters/allow-se…
Browse files Browse the repository at this point in the history
…tting-limit

PassedParameters::getParameters(): add new, optional $limit parameter
  • Loading branch information
jrfnl committed May 18, 2021
2 parents e6f27a9 + 205e9de commit 000626c
Show file tree
Hide file tree
Showing 4 changed files with 279 additions and 10 deletions.
18 changes: 15 additions & 3 deletions PHPCSUtils/Utils/PassedParameters.php
Expand Up @@ -152,6 +152,8 @@ public static function hasParameters(File $phpcsFile, $stackPtr)
* @param int $stackPtr The position of the `T_STRING`, PHP 8.0 identifier
* name token, `T_VARIABLE`, `T_ARRAY`, `T_OPEN_SHORT_ARRAY`,
* `T_ISSET`, or `T_UNSET` token.
* @param int $limit Optional. Limit the parameter retrieval to the first #
* parameters/array entries.
*
* @return array A multi-dimentional array information on each parameter/array item.
* The information gathered about each parameter/array item is in the following format:
Expand Down Expand Up @@ -186,7 +188,7 @@ public static function hasParameters(File $phpcsFile, $stackPtr)
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the token passed is not one of the
* accepted types or doesn't exist.
*/
public static function getParameters(File $phpcsFile, $stackPtr)
public static function getParameters(File $phpcsFile, $stackPtr, $limit = 0)
{
if (self::hasParameters($phpcsFile, $stackPtr) === false) {
return [];
Expand Down Expand Up @@ -314,6 +316,11 @@ public static function getParameters(File $phpcsFile, $stackPtr)
break;
}

// Stop if there is a valid limit and the limit has been reached.
if (\is_int($limit) && $limit > 0 && $cnt === $limit) {
break;
}

// Prepare for the next parameter.
$paramStart = ($nextComma + 1);
$cnt++;
Expand Down Expand Up @@ -363,8 +370,13 @@ public static function getParameters(File $phpcsFile, $stackPtr)
*/
public static function getParameter(File $phpcsFile, $stackPtr, $paramOffset, $paramNames = [])
{
$tokens = $phpcsFile->getTokens();
$parameters = self::getParameters($phpcsFile, $stackPtr);
$tokens = $phpcsFile->getTokens();

if (empty($paramNames) === true) {
$parameters = self::getParameters($phpcsFile, $stackPtr, $paramOffset);
} else {
$parameters = self::getParameters($phpcsFile, $stackPtr);
}

/*
* Non-function calls.
Expand Down
79 changes: 72 additions & 7 deletions Tests/Utils/PassedParameters/GetParameterFromStackTest.php
Expand Up @@ -44,16 +44,16 @@ public function testGetParameterNoParams()

/**
* 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.
* to make sure that no exception is thrown for the missing parameter name.
*
* @dataProvider dataGetParameterNonFunctionCallMissingParamName
* @dataProvider dataGetParameterNonFunctionCallNoParamName
*
* @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)
public function testGetParameterNonFunctionCallNoParamName($testMarker, $targetType)
{
$stackPtr = $this->getTargetToken($testMarker, $targetType);
$expected = [
Expand All @@ -70,11 +70,11 @@ public function testGetParameterNonFunctionCallMissingParamName($testMarker, $ta
/**
* Data provider.
*
* @see testGetParameterNonFunctionCallMissingParamName() For the array format.
* @see testGetParameterNonFunctionCallNoParamName() For the array format.
*
* @return array
*/
public function dataGetParameterNonFunctionCallMissingParamName()
public function dataGetParameterNonFunctionCallNoParamName()
{
return [
'isset' => [
Expand All @@ -90,11 +90,11 @@ public function dataGetParameterNonFunctionCallMissingParamName()

/**
* Test retrieving the parameter details from a function call with only positional parameters
* without passing a valid name.
* without passing a valid name to make sure no exception is thrown.
*
* @return void
*/
public function testGetParameterFunctionCallPositionalMissingParamName()
public function testGetParameterFunctionCallPositionalNoParamName()
{
$stackPtr = $this->getTargetToken('/* testAllParamsPositional */', \T_STRING);
$expected = [
Expand Down Expand Up @@ -143,6 +143,71 @@ public function testGetParameterFunctionCallPositionalMissingParamNameNonExisten
PassedParameters::getParameter(self::$phpcsFile, $stackPtr, 10);
}

/**
* Verify that the $limit parameter used with `PassedParameters::getParameters()` from within the
* `PassedParameters::getParameter()` function call does not interfer with the handling of named parameters.
*
* @dataProvider dataGetParameterFunctionCallWithParamName
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param array $expected The expected function output.
*
* @return void
*/
public function testGetParameterFunctionCallWithParamName($testMarker, $expected)
{
$stackPtr = $this->getTargetToken($testMarker, \T_STRING);

$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::getParameter(self::$phpcsFile, $stackPtr, 2, 'value');
$this->assertSame($expected, $result);
}

/**
* Data provider.
*
* @see testGetParameterFunctionCallWithParamName() For the array format.
*
* @return array
*/
public function dataGetParameterFunctionCallWithParamName()
{
/*
* 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 = \version_compare(Helper::getVersion(), '3.6.0', '>=');

return [
'all-named-non-standard-order' => [
'/* testAllParamsNamedNonStandardOrder */',
[
'name_start' => ($namedParamsInPhpcs === true) ? 46 : 42,
'name_end' => ($namedParamsInPhpcs === true) ? 49 : 44,
'name' => 'value',
'start' => ($namedParamsInPhpcs === true) ? 50 : 45,
'end' => ($namedParamsInPhpcs === true) ? 51 : 46,
'raw' => "'value'",
],
],
'mixed-positional-and-named-target-non-named' => [
'/* testMixedPositionalAndNamedParams */',
[
'start' => 6,
'end' => 8,
'raw' => "'value'",
],
],
];
}

/**
* Test retrieving the details for a specific parameter from a function call or construct.
*
Expand Down
27 changes: 27 additions & 0 deletions Tests/Utils/PassedParameters/GetParametersWithLimitTest.inc
@@ -0,0 +1,27 @@
<?php

/* testNoParams */
$array = array();

/* testFunctionCall */
myfunction( 1, 2, 3, 4, 5, 6, true );

/* testSimpleLongArray */
$foo = array( 1, 2, 3, 4, 5, 6, true );

/* testSimpleShortArray */
$foo = [ 1, 2, 3, 4, 5, 6, true ];

/* testLongArrayWithKeys */
$foo = array( 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => true );

/* testShortArrayWithKeys */
$foo = [
'a' => 1,
'b' => 2,
'c' => 3,
'd' => 4,
'e' => 5,
'f' => 6,
'g' => true,
];
165 changes: 165 additions & 0 deletions Tests/Utils/PassedParameters/GetParametersWithLimitTest.php
@@ -0,0 +1,165 @@
<?php
/**
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
*
* @package PHPCSUtils
* @copyright 2019-2020 PHPCSUtils Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCSStandards/PHPCSUtils
*/

namespace PHPCSUtils\Tests\Utils\PassedParameters;

use PHPCSUtils\TestUtils\UtilityMethodTestCase;
use PHPCSUtils\Utils\PassedParameters;

/**
* Tests for the \PHPCSUtils\Utils\PassedParameters::getParameters() methods for
* when the $limit parameter has been passed.
*
* @covers \PHPCSUtils\Utils\PassedParameters::getParameters
*
* @group passedparameters
*
* @since 1.0.0
*/
class GetParametersWithLimitTest extends UtilityMethodTestCase
{

/**
* Test retrieving the parameter details with a limit from an array without parameters.
*
* @return void
*/
public function testGetParametersWithLimitNoParams()
{
$stackPtr = $this->getTargetToken('/* testNoParams */', \T_ARRAY);

$result = PassedParameters::getParameters(self::$phpcsFile, $stackPtr, 3);
$this->assertSame([], $result);
$this->assertCount(0, $result);
}

/**
* Test passing an invalid limit.
*
* @dataProvider dataGetParametersWithIneffectiveLimit
*
* @param mixed $limit Parameter value for the $limit parameter.
*
* @return void
*/
public function testGetParametersWithIneffectiveLimit($limit)
{
$stackPtr = $this->getTargetToken('/* testFunctionCall */', \T_STRING);

$result = PassedParameters::getParameters(self::$phpcsFile, $stackPtr, $limit);
$this->assertNotEmpty($result);
$this->assertCount(7, $result);
}

/**
* Data provider.
*
* @see testGetParametersWithIneffectiveLimit() For the array format.
*
* @return array
*/
public function dataGetParametersWithIneffectiveLimit()
{
return [
'invalid-limit-wrong-type-null' => [null],
'invalid-limit-wrong-type-bool' => [true],
'invalid-limit-wrong-type-string' => ['10'],
'invalid-limit-negative-int' => [-10],
'valid-limit-set-to-0 = no-limit' => [0],
'valid-limit-higher-than-param-count' => [10],
];
}

/**
* Test retrieving the parameter details from a function call or construct.
*
* @dataProvider dataGetParametersWithLimit
*
* @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.
* @param array $limit The number of parameters to limit this call to.
* Should match the expected count.
* @param array $expected Optional. The expected return value. Only tested when not empty.
*
* @return void
*/
public function testGetParametersWithLimit($testMarker, $targetType, $limit, $expected = [])
{
$stackPtr = $this->getTargetToken($testMarker, [$targetType]);

$result = PassedParameters::getParameters(self::$phpcsFile, $stackPtr, $limit);
$this->assertNotEmpty($result);
$this->assertCount($limit, $result);

if (empty($expected) === true) {
return;
}

// 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']);
}

foreach ($result as $key => $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 testGetParametersWithLimit() For the array format.
*
* @return array
*/
public function dataGetParametersWithLimit()
{
return [
'function-call' => [
'/* testFunctionCall */',
\T_STRING,
2,
],
'long-array-no-keys' => [
'/* testSimpleLongArray */',
\T_ARRAY,
1,
[
1 => [
'start' => 2,
'end' => 3,
'raw' => '1',
],
],
],
'short-array-no-keys' => [
'/* testSimpleShortArray */',
\T_OPEN_SHORT_ARRAY,
5,
],
'long-array-with-keys' => [
'/* testLongArrayWithKeys */',
\T_ARRAY,
7,
],
'short-array-with-keys' => [
'/* testShortArrayWithKeys */',
\T_OPEN_SHORT_ARRAY,
4,
],
];
}
}

0 comments on commit 000626c

Please sign in to comment.