Skip to content

Commit

Permalink
✨ New Context::inAttribute() method
Browse files Browse the repository at this point in the history
... to determine whether an arbitrary token is within an attribute.

Includes unit tests.
  • Loading branch information
jrfnl committed Oct 24, 2022
1 parent e13d929 commit bdf70c8
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 0 deletions.
27 changes: 27 additions & 0 deletions PHPCSUtils/Utils/Context.php
Expand Up @@ -84,6 +84,33 @@ public static function inUnset(File $phpcsFile, $stackPtr)
return (Parentheses::getLastOwner($phpcsFile, $stackPtr, \T_UNSET) !== false);
}

/**
* Check whether an arbitrary token is within an attribute.
*
* @since 1.0.0-alpha4
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the token we are checking.
*
* @return bool
*/
public static function inAttribute(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();

// Check for the existence of the token.
if (isset($tokens[$stackPtr]) === false) {
return false;
}

if (isset($tokens[$stackPtr]['attribute_opener'], $tokens[$stackPtr]['attribute_closer']) === false) {
return false;
}

return ($stackPtr !== $tokens[$stackPtr]['attribute_opener']
&& $stackPtr !== $tokens[$stackPtr]['attribute_closer']);
}

/**
* Check whether an arbitrary token is in a foreach condition and if so, in which part:
* before or after the "as".
Expand Down
18 changes: 18 additions & 0 deletions Tests/Utils/Context/InAttributeTest.inc
@@ -0,0 +1,18 @@
<?php

/* testNotAttribute */
$var = [1, self::MYCONSTANT];

/* testAttribute */
$fn = #[MyAttribute(1, self::MYCONSTANT)] fn($a) => $something;

/* testMultiLineAttributeWithVars */
// Illegal syntax, vars not allowed in attribute, but not our concern.
#[
MyAttribute([$a, $b])
]
function foo() {}

/* testParseError */
#[
UnClosedAttribute()
167 changes: 167 additions & 0 deletions Tests/Utils/Context/InAttributeTest.php
@@ -0,0 +1,167 @@
<?php
/**
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
*
* @package PHPCSUtils
* @copyright 2019 PHPCSUtils Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCSStandards/PHPCSUtils
*/

namespace PHPCSUtils\Tests\Utils\Context;

use PHPCSUtils\TestUtils\UtilityMethodTestCase;
use PHPCSUtils\Utils\Context;

/**
* Tests for the \PHPCSUtils\Utils\Context::inAttribute() method.
*
* @covers \PHPCSUtils\Utils\Context::inAttribute
*
* @group context
*
* @since 1.0.0
*/
final class InAttributeTest extends UtilityMethodTestCase
{

/**
* Test passing a non-existent token pointer.
*
* @return void
*/
public function testNonExistentToken()
{
$this->assertFalse(Context::inAttribute(self::$phpcsFile, 10000));
}

/**
* Test correctly identifying that an arbitrary token is NOT within an attribute.
*
* @dataProvider dataNotInAttribute
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int|string|array $targetType The token type(s) to look for.
*
* @return void
*/
public function testNotInAttribute($testMarker, $targetType)
{
$target = $this->getTargetToken($testMarker, $targetType);
$this->assertFalse(Context::inAttribute(self::$phpcsFile, $target));
}

/**
* Data provider.
*
* @see testInAttribute()
*
* @return array
*/
public function dataNotInAttribute()
{
return [
'code nowhere near an attribute [1]' => [
'testMarker' => '/* testNotAttribute */',
'targetType' => \T_OPEN_SHORT_ARRAY,
],
'code nowhere near an attribute [2]' => [
'testMarker' => '/* testNotAttribute */',
'targetType' => \T_SELF,
],

'code directly before an attribute (same line)' => [
'testMarker' => '/* testAttribute */',
'targetType' => \T_VARIABLE,
],
'attribute opener' => [
'testMarker' => '/* testAttribute */',
'targetType' => \T_ATTRIBUTE,
],
'attribute closer' => [
'testMarker' => '/* testAttribute */',
'targetType' => \T_ATTRIBUTE_END,
],
'code directly after an attribute (same line)' => [
'testMarker' => '/* testAttribute */',
'targetType' => \T_FN,
],

'code directly after an attribute (different line)' => [
'testMarker' => '/* testMultiLineAttributeWithVars */',
'targetType' => \T_FUNCTION,
],

'code in an unclosed attribute (parse error)' => [
'testMarker' => '/* testParseError */',
'targetType' => \T_STRING,
],
];
}

/**
* Test correctly identifying that an arbitrary token IS within an attribute.
*
* @dataProvider dataInAttribute
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int|string|array $targetType The token type(s) to look for.
*
* @return void
*/
public function testInAttribute($testMarker, $targetType)
{
$target = $this->getTargetToken($testMarker, $targetType);
$this->assertTrue(Context::inAttribute(self::$phpcsFile, $target));
}

/**
* Data provider.
*
* @see testInAttribute()
*
* @return array
*/
public function dataInAttribute()
{
return [
'single line attribute - attribute name' => [
'testMarker' => '/* testAttribute */',
'targetType' => \T_STRING,
],
'single line attribute - attribute param [1]' => [
'testMarker' => '/* testAttribute */',
'targetType' => \T_LNUMBER,
],
'single line attribute - comma in attribute param sequence' => [
'testMarker' => '/* testAttribute */',
'targetType' => \T_COMMA,
],
'single line attribute - attribute param [2]' => [
'testMarker' => '/* testAttribute */',
'targetType' => \T_SELF,
],
'single line attribute - close parenthesis' => [
'testMarker' => '/* testAttribute */',
'targetType' => \T_CLOSE_PARENTHESIS,
],

'multi line attribute - attribute name' => [
'testMarker' => '/* testMultiLineAttributeWithVars */',
'targetType' => \T_STRING,
],
'multi line attribute - attribute param [1]' => [
'testMarker' => '/* testMultiLineAttributeWithVars */',
'targetType' => \T_OPEN_SHORT_ARRAY,
],
'multi line attribute - attribute param [2]' => [
'testMarker' => '/* testMultiLineAttributeWithVars */',
'targetType' => \T_VARIABLE,
],
'multi line attribute - close parenthesis' => [
'testMarker' => '/* testMultiLineAttributeWithVars */',
'targetType' => \T_CLOSE_PARENTHESIS,
],
];
}
}

0 comments on commit bdf70c8

Please sign in to comment.