diff --git a/src/Standards/Generic/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php b/src/Standards/Generic/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php index f62cfe94bd..259fa2963d 100644 --- a/src/Standards/Generic/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php +++ b/src/Standards/Generic/Sniffs/NamingConventions/UpperCaseConstantNameSniff.php @@ -87,37 +87,39 @@ public function process(File $phpcsFile, $stackPtr) return; } - // Make sure this is not a method call. - $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); + // Make sure this is not a method call or class instantiation. + $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); if ($tokens[$prev]['code'] === T_OBJECT_OPERATOR || $tokens[$prev]['code'] === T_DOUBLE_COLON || $tokens[$prev]['code'] === T_NULLSAFE_OBJECT_OPERATOR + || $tokens[$prev]['code'] === T_NEW ) { return; } + // Make sure this is not an attribute. + if (empty($tokens[$stackPtr]['nested_attributes']) === false) { + return; + } + // If the next non-whitespace token after this token // is not an opening parenthesis then it is not a function call. $openBracket = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); - if ($openBracket === false) { + if ($openBracket === false || $tokens[$openBracket]['code'] !== T_OPEN_PARENTHESIS) { return; } - // The next non-whitespace token must be the constant name. - $constPtr = $phpcsFile->findNext(T_WHITESPACE, ($openBracket + 1), null, true); - if ($tokens[$constPtr]['code'] !== T_CONSTANT_ENCAPSED_STRING) { + // Bow out if next non-empty token after the opening parenthesis is not a string (the + // constant name). This could happen when live coding, if the constant is a variable or an + // expression, or if handling a first-class callable or a function definition outside the + // global scope. + $constPtr = $phpcsFile->findNext(Tokens::$emptyTokens, ($openBracket + 1), null, true); + if ($constPtr === false || $tokens[$constPtr]['code'] !== T_CONSTANT_ENCAPSED_STRING) { return; } $constName = $tokens[$constPtr]['content']; - - // Check for constants like self::CONSTANT. - $prefix = ''; - $splitPos = strpos($constName, '::'); - if ($splitPos !== false) { - $prefix = substr($constName, 0, ($splitPos + 2)); - $constName = substr($constName, ($splitPos + 2)); - } + $prefix = ''; // Strip namespace from constant like /foo/bar/CONSTANT. $splitPos = strrpos($constName, '\\'); @@ -128,9 +130,9 @@ public function process(File $phpcsFile, $stackPtr) if (strtoupper($constName) !== $constName) { if (strtolower($constName) === $constName) { - $phpcsFile->recordMetric($stackPtr, 'Constant name case', 'lower'); + $phpcsFile->recordMetric($constPtr, 'Constant name case', 'lower'); } else { - $phpcsFile->recordMetric($stackPtr, 'Constant name case', 'mixed'); + $phpcsFile->recordMetric($constPtr, 'Constant name case', 'mixed'); } $error = 'Constants must be uppercase; expected %s but found %s'; @@ -138,9 +140,9 @@ public function process(File $phpcsFile, $stackPtr) $prefix.strtoupper($constName), $prefix.$constName, ]; - $phpcsFile->addError($error, $stackPtr, 'ConstantNotUpperCase', $data); + $phpcsFile->addError($error, $constPtr, 'ConstantNotUpperCase', $data); } else { - $phpcsFile->recordMetric($stackPtr, 'Constant name case', 'upper'); + $phpcsFile->recordMetric($constPtr, 'Constant name case', 'upper'); } }//end process() diff --git a/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.1.inc b/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.1.inc new file mode 100644 index 0000000000..c5af2349cb --- /dev/null +++ b/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.1.inc @@ -0,0 +1,91 @@ +define('bar'); +$foo->getBar()->define('foo'); +Foo::define('bar'); + +class ClassConstBowOutTest { + const /* comment */ abc = 1; + const // phpcs:ignore Standard.Category.Sniff + some_constant = 2; +} + +$foo->getBar()?->define('foo'); + +// PHP 8.3 introduces typed constants. +class TypedConstants { + + const MyClass MYCONST = new MyClass; + const int VALID_NAME = 0; + final public const INT invalid_name = 0; + const FALSE false = false; // Yes, false can be used as a constant name, don't ask. + final protected const array ARRAY = array(); // Same goes for array. +} + +define /* comment */ ( /* comment */ 'CommentsInUnconventionalPlaces', 'value' ); + +define +// comment +( + // phpcs:ignore Stnd.Cat.SniffName -- for reasons. + 'CommentsInUnconventionalPlaces', + 'value' +); + +$foo-> /* comment */ define('bar'); +$foo?-> +// phpcs:ignore Stnd.Cat.SniffName -- for reasons. +define('bar'); + +const DEFINE = 'value'; + +#[Define('some param')] +class MyClass {} + +#[ + AttributeA, + define('some param') +] +class MyClass {} + +const MixedCase = 1; + +define('lower_case_name', 'value'); +define($var, 'sniff should bow out'); +define(constantName(), 'sniff should bow out'); +define($obj->constantName(), 'sniff should bow out'); +define(MyClass::constantName(), 'sniff should bow out'); +define(condition() ? 'name1' : 'name2', 'sniff should bow out'); + +$callable = define(...); + +// Valid if outside the global namespace. Sniff should bow out. +function define($param) {} + +class MyClass { + public function define($param) {} +} + +$a = ($cond) ? DEFINE : SOMETHING_ELSE; + +$object = new Define('value'); diff --git a/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.2.inc b/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.2.inc new file mode 100644 index 0000000000..4136912dc9 --- /dev/null +++ b/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.2.inc @@ -0,0 +1,7 @@ +define('bar'); -$foo->getBar()->define('foo'); -Foo::define('bar'); - -class ClassConstBowOutTest { - const /* comment */ abc = 1; - const // phpcs:ignore Standard.Category.Sniff - some_constant = 2; -} - -$foo->getBar()?->define('foo'); - -// PHP 8.3 introduces typed constants. -class TypedConstants { - const MISSING_VALUE; // Parse error. - const MyClass MYCONST = new MyClass; - const int VALID_NAME = 0; - const INT invalid_name = 0; - const FALSE false = false; // Yes, false can be used as a constant name, don't ask. - const array ARRAY = array(); // Same goes for array. -} diff --git a/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.php b/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.php index afe3625589..34a535abcc 100644 --- a/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.php +++ b/src/Standards/Generic/Tests/NamingConventions/UpperCaseConstantNameUnitTest.php @@ -26,21 +26,32 @@ final class UpperCaseConstantNameUnitTest extends AbstractSniffUnitTest * The key of the array should represent the line number and the value * should represent the number of errors that should occur on that line. * + * @param string $testFile The name of the test file to process. + * * @return array */ - public function getErrorList() + public function getErrorList($testFile='') { - return [ - 8 => 1, - 10 => 1, - 12 => 1, - 14 => 1, - 19 => 1, - 28 => 1, - 30 => 1, - 40 => 1, - 41 => 1, - ]; + switch ($testFile) { + case 'UpperCaseConstantNameUnitTest.1.inc': + return [ + 8 => 1, + 10 => 1, + 12 => 1, + 14 => 1, + 19 => 1, + 28 => 1, + 30 => 1, + 40 => 1, + 41 => 1, + 45 => 1, + 51 => 1, + 71 => 1, + 73 => 1, + ]; + default: + return []; + } }//end getErrorList()