Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public function process(File $phpcsFile, $stackPtr)
for (; $next <= $end; ++$next) {
$code = $tokens[$next]['code'];

if (isset(Tokens::$emptyTokens[$code]) === true) {
if (isset(Tokens::$emptyTokens[$code]) === true || $code === T_NS_SEPARATOR) {
continue;
} else if ($code !== T_TRUE && $code !== T_FALSE) {
$goodCondition = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ public function isArrayStatic(File $phpcsFile, $arrayToken)
T_COMMA => T_COMMA,
T_TRUE => T_TRUE,
T_FALSE => T_FALSE,
T_NULL => T_NULL,
T_NS_SEPARATOR => T_NS_SEPARATOR,
];

for ($i = ($start + 1); $i < $end; $i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ if (true) {
if (file_exists(__FILE__) === true) {

}

// Check handling of case and FQN state.
if (\true) {
} else if (\FALSE) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ public function getWarningList($testFile='')
switch ($testFile) {
case 'UnconditionalIfStatementUnitTest.1.inc':
return [
3 => 1,
5 => 1,
7 => 1,
3 => 1,
5 => 1,
7 => 1,
16 => 1,
17 => 1,
];

default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ if ($value == true) {}
if (true === $value) {}
if (true == $value) {}

if($value === true){}
if($value == true){}
if($value === false){}
if($value == false){}
if(false === $value){}
if(!false == $value || true !== $value){}

Expand Down Expand Up @@ -139,8 +139,8 @@ if (is_array($val)
&& array('foo', 'bar') === array($foo, $bar)
&& ['foo', 'bar'] === [$foo, $bar]
&& array('foo' => true, 'bar' => false) === array(getContents())
&& ['foo' => true, 'bar' => false] === array(getContents())
&& array(getContents()) === ['foo' => true, 'bar' => false]
&& ['foo' => true, 'bar' => \false, 'baz' => null] === array(getContents())
&& array(getContents()) === ['foo' => \true, 'bar' => false, 'baz' => \null]
) {
}

Expand Down Expand Up @@ -185,3 +185,14 @@ echo match ($text) {
1 >= $value;
1 <= $value;
1 <=> $value;

// Handle FQN true/false/null the same as plain true/false/null.
if ($value === \true) {}
if (\true === $value) {}

if($value == \FALSE){}
if(\FALSE === $value){}
if(!\false == $value || true !== $value){}

if($value === \Null){}
if(\Null == $value){}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ public function getErrorList()
185 => 1,
186 => 1,
187 => 1,
191 => 1,
194 => 1,
195 => 2,
198 => 1,
];

}//end getErrorList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,7 @@ abstract class SkipOverPHP84AbstractProperties {
abstract MyType|TRUE $propA {get;}
protected abstract NULL|MyClass $propB {set;}
}

$a = \NULL;
$a = \falSe;
$a = \True;
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,7 @@ abstract class SkipOverPHP84AbstractProperties {
abstract MyType|TRUE $propA {get;}
protected abstract NULL|MyClass $propB {set;}
}

$a = \null;
$a = \false;
$a = \true;
3 changes: 3 additions & 0 deletions src/Standards/Generic/Tests/PHP/LowerCaseConstantUnitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ public function getErrorList($testFile='')
169 => 1,
171 => 1,
173 => 1,
181 => 1,
182 => 1,
183 => 1,
];

case 'LowerCaseConstantUnitTest.js':
Expand Down
4 changes: 4 additions & 0 deletions src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,7 @@ abstract class SkipOverPHP84AbstractProperties {
abstract MyType|true $propA {get;}
protected abstract null|MyClass $propB {set;}
}

$a = \null;
$a = \falSe;
$a = \True;
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,7 @@ abstract class SkipOverPHP84AbstractProperties {
abstract MyType|true $propA {get;}
protected abstract null|MyClass $propB {set;}
}

$a = \NULL;
$a = \FALSE;
$a = \TRUE;
3 changes: 3 additions & 0 deletions src/Standards/Generic/Tests/PHP/UpperCaseConstantUnitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ public function getErrorList()
112 => 1,
113 => 1,
114 => 1,
122 => 1,
123 => 1,
124 => 1,
];

}//end getErrorList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,14 @@ public function process(File $phpcsFile, $stackPtr)
}

if (array_key_exists('default', $param) === true) {
$defaultFound = true;
$defaultFound = true;
$defaultValueLc = strtolower($param['default']);
// Check if the arg is type hinted and using NULL for the default.
// This does not make the argument optional - it just allows NULL
// to be passed in.
if ($param['type_hint'] !== '' && strtolower($param['default']) === 'null') {
if ($param['type_hint'] !== ''
&& ($defaultValueLc === 'null' || $defaultValueLc === '\null')
) {
$defaultFound = false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,7 @@ class ConstructorPropertyPromotionMixedWithNormalParams {
mixed $requiredParam,
) {}
}

// Safeguard correct handling of FQN null as default value.
function foo(Foo $foo = \null, $bar) {}
function foo(Foo $foo = \null, Foz $foz = \NULL, $bar = true, $baz) {}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public function getErrorList($testFile='')
101 => 1,
106 => 1,
114 => 1,
120 => 1,
];

default:
Expand Down
5 changes: 5 additions & 0 deletions src/Standards/Squiz/Sniffs/PHP/NonExecutableCodeSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ public function process(File $phpcsFile, $stackPtr)

$prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);

// Allow for PHP 8.4+ fully qualified use of exit/die.
if ($tokens[$stackPtr]['code'] === \T_EXIT && $tokens[$prev]['code'] === \T_NS_SEPARATOR) {
$prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prev - 1), null, true);
}

// Tokens which can be used in inline expressions need special handling.
if (isset($this->expressionTokens[$tokens[$stackPtr]['code']]) === true) {
// If this token is preceded by a logical operator, it only relates to one line
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,9 @@ if (empty($argTags > 0)) {
}

myFunction($var1 === true ? "" : "foobar");

// Verify that FQN true/false are handled the same as unqualified.
if (true) {}
if (\true) {}
for ($var1 = 10; FALSE; $var1--) {}
for ($var1 = 10; \FALSE; $var1--) {}
18 changes: 16 additions & 2 deletions src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.1.inc
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ function parseError2() {
// All logical operators are allowed with inline expressions (but this was not correctly handled by the sniff).
function exitExpressionsWithLogicalOperators() {
$condition = false;
$condition || exit();
$condition || \exit();
$condition or die();

$condition = true;
Expand All @@ -327,7 +327,7 @@ function exitExpressionsWithLogicalOperators() {
function exitExpressionsInTernary() {
$value = $myValue ? $myValue : exit();
$value = $myValue ?: exit();
$value = $var == 'foo' ? 'bar' : die( 'world' );
$value = $var == 'foo' ? 'bar' : \die( 'world' );

$value = (!$myValue ) ? exit() : $myValue;
$value = $var != 'foo' ? die( 'world' ) : 'bar';
Expand Down Expand Up @@ -418,3 +418,17 @@ $closure = function ()
echo 'foo';
return; // This return should be flagged as not required.
};

function fqnExitWithCodeAfter() {
do_something();
exit();
do_something_else();
}

function fqnExitInControlStructureWithCodeAfter() {
if(do_something()) {
die();
do_something_else();
}
do_something_else();
}
2 changes: 2 additions & 0 deletions src/Standards/Squiz/Tests/PHP/NonExecutableCodeUnitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ public function getWarningList($testFile='')
406 => 1,
412 => 1,
419 => 1,
425 => 1,
431 => 1,
];

case 'NonExecutableCodeUnitTest.2.inc':
Expand Down
77 changes: 75 additions & 2 deletions src/Tokenizers/PHP.php
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,25 @@ protected function tokenize($string)
break;
}
}

// Fully Qualified `\exit`/`\die` should be preserved.
if ($token[0] === T_EXIT
&& $finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR
) {
for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
continue;
}

if ($finalTokens[$i]['code'] !== T_STRING
&& $finalTokens[$i]['code'] !== T_NAMESPACE
) {
$preserveKeyword = true;
}

break;
}
}
}//end if

// Types in typed constants should not be touched, but the constant name should be.
Expand Down Expand Up @@ -1355,6 +1374,38 @@ protected function tokenize($string)
$name = substr($name, 10);
}

// Special case keywords which can be used in fully qualified form.
if ($token[0] === T_NAME_FULLY_QUALIFIED) {
$specialCasedType = null;
$nameLc = strtolower($name);
if ($nameLc === 'exit' || $nameLc === 'die') {
$specialCasedType = 'T_EXIT';
} else if ($nameLc === 'true') {
$specialCasedType = 'T_TRUE';
} else if ($nameLc === 'false') {
$specialCasedType = 'T_FALSE';
} else if ($nameLc === 'null') {
$specialCasedType = 'T_NULL';
}

if ($specialCasedType !== null) {
$newToken = [];
$newToken['code'] = constant($specialCasedType);
$newToken['type'] = $specialCasedType;
$newToken['content'] = $name;
$finalTokens[$newStackPtr] = $newToken;
++$newStackPtr;

if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = Tokens::tokenName($token[0]);
$content = Common::prepareForOutput($token[1]);
echo "\t\t* token $stackPtr split into individual tokens T_NS_SEPARATOR + $specialCasedType".PHP_EOL;
}

continue;
}
}//end if

$parts = explode('\\', $name);
$partCount = count($parts);
$lastPart = ($partCount - 1);
Expand Down Expand Up @@ -2523,16 +2574,37 @@ function return types. We want to keep the parenthesis map clean,
} else if (isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true
&& $finalTokens[$lastNotEmptyToken]['code'] !== T_CONST
) {
$preserveTstring = true;
$preserveTstring = true;
$tokenContentLower = strtolower($token[1]);

// Special case for syntax like: return new self/new parent
// where self/parent should not be a string.
$tokenContentLower = strtolower($token[1]);
if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW
&& ($tokenContentLower === 'self' || $tokenContentLower === 'parent')
) {
$preserveTstring = false;
}

// Special case for fully qualified \true, \false and \null
// where true/false/null should not be a string.
// Note: if this is the _start_ of a longer namespaced name, this will undone again later.
if ($finalTokens[$lastNotEmptyToken]['code'] === T_NS_SEPARATOR
&& ($tokenContentLower === 'true' || $tokenContentLower === 'false' || $tokenContentLower === 'null')
) {
for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
if (isset(Tokens::$emptyTokens[$finalTokens[$i]['code']]) === true) {
continue;
}

if ($finalTokens[$i]['code'] !== T_STRING
&& $finalTokens[$i]['code'] !== T_NAMESPACE
) {
$preserveTstring = false;
}

break;
}
}
} else if ($finalTokens[$lastNotEmptyToken]['content'] === '&') {
// Function names for functions declared to return by reference.
for ($i = ($lastNotEmptyToken - 1); $i >= 0; $i--) {
Expand Down Expand Up @@ -3629,6 +3701,7 @@ protected function processAdditional()
} else if ($this->tokens[$i]['code'] === T_TRUE
|| $this->tokens[$i]['code'] === T_FALSE
|| $this->tokens[$i]['code'] === T_NULL
|| $this->tokens[$i]['code'] === T_EXIT
) {
for ($x = ($i + 1); $x < $numTokens; $x++) {
if (isset(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === false) {
Expand Down
4 changes: 4 additions & 0 deletions tests/Core/Files/File/FindStartOfStatementTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ switch ($foo) {
/* testInsideCaseGotoStatement */
goto myLabel;

case 7:
/* testInsideCaseFullyQualifiedDieStatement */
\die(1);

/* testDefaultStatement */
default:
/* testInsideDefaultContinueStatement */
Expand Down
6 changes: 6 additions & 0 deletions tests/Core/Files/File/FindStartOfStatementTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,12 @@ public static function dataFindStartInsideSwitchCaseDefaultStatements()
'targets' => T_SEMICOLON,
'expectedTarget' => T_GOTO,
],
'Namespace separator for "die" should be start for contents - close parenthesis' => [
// Note: not sure if this is actually correct - should this be the open parenthesis ?
'testMarker' => '/* testInsideCaseFullyQualifiedDieStatement */',
'targets' => T_CLOSE_PARENTHESIS,
'expectedTarget' => T_NS_SEPARATOR,
],
'Default keyword should be start of default statement - default itself' => [
'testMarker' => '/* testDefaultStatement */',
'targets' => T_DEFAULT,
Expand Down
5 changes: 5 additions & 0 deletions tests/Core/Tokenizers/PHP/ContextSensitiveKeywordsTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,11 @@ $a = isset($a);
/* testUnsetIsKeyword */
unset($a);

/* testFullyQualifiedDieIsKeyword */
\die;
/* testFullyQualifiedExitIsKeyword */
\exit($foo);

/* testIncludeIsKeyword */
include 'file.php';
/* testIncludeOnceIsKeyword */
Expand Down
Loading
Loading