Skip to content

Commit 53fa0b6

Browse files
committed
✨ New Universal.PHP.NoFQNTrueFalseNull sniff
... to forbid using `true`, `false` and `null` as fully qualified constants. Includes fixer. Includes unit tests. Includes documentation. Note: yes, there is a related tokenizer issue in PHPCS where these tokens are not tokenized the same PHP cross-version, however, the tokenization will change in PHPCS 4.0 as well, so there isn't much point addressing that now as it would just make things even more complicated for sniffs come PHPCS 4.0. And yes, this does mean that this sniff will need adjusting for PHPCS 4.0.
1 parent cf28c84 commit 53fa0b6

5 files changed

+227
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0"?>
2+
<documentation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="https://phpcsstandards.github.io/PHPCSDevTools/phpcsdocs.xsd"
4+
title="No FQN True False or Null"
5+
>
6+
<standard>
7+
<![CDATA[
8+
Forbids using `true`, `false` and `null` as fully qualified constants.
9+
]]>
10+
</standard>
11+
<code_comparison>
12+
<code title="Valid: Using true/false/null without namespace separator prefix.">
13+
<![CDATA[
14+
$a = <em>null</em>;
15+
16+
if ($something === <em>false</em>) {}
17+
]]>
18+
</code>
19+
<code title="Invalid: Using true/false/null as fully qualified constants.">
20+
<![CDATA[
21+
$a = <em>\</em>null;
22+
23+
if ($something === <em>\</em>false) {}
24+
]]>
25+
</code>
26+
</code_comparison>
27+
</documentation>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
/**
3+
* PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer.
4+
*
5+
* @package PHPCSExtra
6+
* @copyright 2020 PHPCSExtra Contributors
7+
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
8+
* @link https://github.com/PHPCSStandards/PHPCSExtra
9+
*/
10+
11+
namespace PHPCSExtra\Universal\Sniffs\PHP;
12+
13+
use PHP_CodeSniffer\Files\File;
14+
use PHP_CodeSniffer\Sniffs\Sniff;
15+
use PHP_CodeSniffer\Util\Tokens;
16+
17+
/**
18+
* Forbids the use of `true`/`false`/`null` as fully qualified constants.
19+
*
20+
* @since 1.3.0
21+
*/
22+
final class NoFQNTrueFalseNullSniff implements Sniff
23+
{
24+
25+
/**
26+
* Registers the tokens that this sniff wants to listen for.
27+
*
28+
* @since 1.3.0
29+
*
30+
* @return array<int|string>
31+
*/
32+
public function register()
33+
{
34+
return [
35+
// PHP < 8.0.
36+
\T_TRUE,
37+
\T_FALSE,
38+
\T_NULL,
39+
40+
// PHP >= 8.0.
41+
\T_STRING,
42+
];
43+
}
44+
45+
/**
46+
* Processes this test, when one of its tokens is encountered.
47+
*
48+
* @since 1.3.0
49+
*
50+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
51+
* @param int $stackPtr The position of the current token
52+
* in the stack passed in $tokens.
53+
*
54+
* @return void
55+
*/
56+
public function process(File $phpcsFile, $stackPtr)
57+
{
58+
$tokens = $phpcsFile->getTokens();
59+
$content = $tokens[$stackPtr]['content'];
60+
$contentLC = \strtolower($content);
61+
62+
if ($contentLC !== 'true' && $contentLC !== 'false' && $contentLC !== 'null') {
63+
return;
64+
}
65+
66+
$prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
67+
if ($tokens[$prev]['code'] !== \T_NS_SEPARATOR) {
68+
return;
69+
}
70+
71+
$prevPrev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prev - 1), null, true);
72+
if ($tokens[$prevPrev]['code'] === \T_STRING || $tokens[$prevPrev]['code'] === \T_NAMESPACE) {
73+
return;
74+
}
75+
76+
$next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
77+
if ($tokens[$next]['code'] === \T_NS_SEPARATOR) {
78+
return;
79+
}
80+
81+
$fix = $phpcsFile->addFixableError(
82+
'The special PHP constant "%s" should not be fully qualified.',
83+
$prev,
84+
'Found',
85+
[$contentLC]
86+
);
87+
88+
if ($fix === true) {
89+
$phpcsFile->fixer->replaceToken($prev, '');
90+
}
91+
}
92+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
$thisIsFine = true;
4+
$thisIsFine = False;
5+
$thisIsFine = NULL;
6+
7+
// Namespaced names should be left alone.
8+
$fullyQualified = \Fully\True\Qualified;
9+
$nsRelative = namespace\Null;
10+
$partiallyQualified = False\Something\Something;
11+
12+
/* These should all be flagged */
13+
$notOkay = \true;
14+
$notOkay = \false;
15+
$notOkay = \ null;
16+
17+
$notOkay = \TRUE;
18+
$notOkay = \/*comment*/False;
19+
$notOkay = \nULl;
20+
21+
class UseInTypes {
22+
const MyClass|\TRUE CONSTANT_NAME = new MyClass;
23+
24+
public readonly \False|float|\null $property;
25+
26+
public function foo(string|\false $name) : \NULL|int {}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
$thisIsFine = true;
4+
$thisIsFine = False;
5+
$thisIsFine = NULL;
6+
7+
// Namespaced names should be left alone.
8+
$fullyQualified = \Fully\True\Qualified;
9+
$nsRelative = namespace\Null;
10+
$partiallyQualified = False\Something\Something;
11+
12+
/* These should all be flagged */
13+
$notOkay = true;
14+
$notOkay = false;
15+
$notOkay = null;
16+
17+
$notOkay = TRUE;
18+
$notOkay = /*comment*/False;
19+
$notOkay = nULl;
20+
21+
class UseInTypes {
22+
const MyClass|TRUE CONSTANT_NAME = new MyClass;
23+
24+
public readonly False|float|null $property;
25+
26+
public function foo(string|false $name) : NULL|int {}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
/**
3+
* PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer.
4+
*
5+
* @package PHPCSExtra
6+
* @copyright 2020 PHPCSExtra Contributors
7+
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
8+
* @link https://github.com/PHPCSStandards/PHPCSExtra
9+
*/
10+
11+
namespace PHPCSExtra\Universal\Tests\PHP;
12+
13+
use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;
14+
15+
/**
16+
* Unit test class for the NoFQNTrueFalseNull sniff.
17+
*
18+
* @covers PHPCSExtra\Universal\Sniffs\PHP\NoFQNTrueFalseNullSniff
19+
*
20+
* @since 1.3.0
21+
*/
22+
final class NoFQNTrueFalseNullUnitTest extends AbstractSniffUnitTest
23+
{
24+
25+
/**
26+
* Returns the lines where errors should occur.
27+
*
28+
* @return array<int, int> Key is the line number, value is the number of expected errors.
29+
*/
30+
public function getErrorList()
31+
{
32+
return [
33+
13 => 1,
34+
14 => 1,
35+
15 => 1,
36+
17 => 1,
37+
18 => 1,
38+
19 => 1,
39+
22 => 1,
40+
24 => 2,
41+
26 => 2,
42+
];
43+
}
44+
45+
/**
46+
* Returns the lines where warnings should occur.
47+
*
48+
* @return array<int, int> Key is the line number, value is the number of expected warnings.
49+
*/
50+
public function getWarningList()
51+
{
52+
return [];
53+
}
54+
}

0 commit comments

Comments
 (0)