From bdf70c8c2afa599d3bd3d6bbae8a9ae15b784e94 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Mon, 24 Oct 2022 09:45:57 +0200 Subject: [PATCH] :sparkles: New `Context::inAttribute()` method ... to determine whether an arbitrary token is within an attribute. Includes unit tests. --- PHPCSUtils/Utils/Context.php | 27 ++++ Tests/Utils/Context/InAttributeTest.inc | 18 +++ Tests/Utils/Context/InAttributeTest.php | 167 ++++++++++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 Tests/Utils/Context/InAttributeTest.inc create mode 100644 Tests/Utils/Context/InAttributeTest.php diff --git a/PHPCSUtils/Utils/Context.php b/PHPCSUtils/Utils/Context.php index 80a51464..e079144d 100644 --- a/PHPCSUtils/Utils/Context.php +++ b/PHPCSUtils/Utils/Context.php @@ -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". diff --git a/Tests/Utils/Context/InAttributeTest.inc b/Tests/Utils/Context/InAttributeTest.inc new file mode 100644 index 00000000..75298e9e --- /dev/null +++ b/Tests/Utils/Context/InAttributeTest.inc @@ -0,0 +1,18 @@ + $something; + +/* testMultiLineAttributeWithVars */ +// Illegal syntax, vars not allowed in attribute, but not our concern. +#[ + MyAttribute([$a, $b]) +] +function foo() {} + +/* testParseError */ +#[ + UnClosedAttribute() diff --git a/Tests/Utils/Context/InAttributeTest.php b/Tests/Utils/Context/InAttributeTest.php new file mode 100644 index 00000000..95333014 --- /dev/null +++ b/Tests/Utils/Context/InAttributeTest.php @@ -0,0 +1,167 @@ +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, + ], + ]; + } +}