'foo',
diff --git a/README.md b/README.md
index a82fa893..df37064d 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@ PHPCSExtra
-[][phpcsextra-packagist]
+[][phpcsextra-packagist]
[](https://github.com/PHPCSStandards/PHPCSExtra/releases)
:construction:
[](https://packagist.org/packages/phpcsstandards/phpcsextra#dev-develop)
@@ -13,10 +13,10 @@ PHPCSExtra
[][gha-test-results]
[](https://coveralls.io/github/PHPCSStandards/PHPCSExtra)
-[][phpcsextra-packagist]
-[][gha-test-results]
+[][phpcsextra-packagist]
+[][gha-test-results]
-[](https://github.com/PHPCSStandards/PHPCSExtra/blob/stable/LICENSE)
+[](https://github.com/PHPCSStandards/PHPCSExtra/blob/stable/LICENSE)

@@ -46,7 +46,7 @@ Minimum Requirements
-------------------------------------------
* PHP 5.4 or higher.
-* [PHP_CodeSniffer][phpcs-gh] version **3.8.0** or higher.
+* [PHP_CodeSniffer][phpcs-gh] version **3.12.1** or higher.
* [PHPCSUtils][phpcsutils-gh] version **1.0.9** or higher.
@@ -55,7 +55,7 @@ Installation
Installing via Composer is highly recommended.
-[Composer](http://getcomposer.org/) will automatically install the project dependencies and register the rulesets from PHPCSExtra and other external standards with PHP_CodeSniffer using the [Composer PHPCS plugin][composer-installer-gh].
+[Composer](https://getcomposer.org/) will automatically install the project dependencies and register the rulesets from PHPCSExtra and other external standards with PHP_CodeSniffer using the [Composer PHPCS plugin][composer-installer-gh].
### Composer Project-based Installation
@@ -403,14 +403,22 @@ This is considered a **_risky_ fixer**.
#### `Universal.Operators.TypeSeparatorSpacing` :wrench: :bar_chart: :books:
-Enforce no spaces around the union type and intersection type operators.
+Enforce spacing rules around the union, intersection and DNF type operators.
+* No space on either side of a union or intersection type operator.
+* No space on the inside of DNF type parenthesis or before/after if the previous/next "thing" is part of the type.
+* One space before a DNF open parenthesis when it is at the start of a type.
+* One space after a DNF close parenthesis when it is at the end of a type.
-The available error codes are: `UnionTypeSpacesBefore`, `UnionTypeSpacesAfter`, `IntersectionTypeSpacesBefore`, `IntersectionTypeSpacesAfter`.
+The available error codes are: `UnionTypeSpacesBefore`, `UnionTypeSpacesAfter`, `IntersectionTypeSpacesBefore`, `IntersectionTypeSpacesAfter`, `DNFOpenSpacesBefore`, `DNFOpenSpacesAfter`, `DNFCloseTypeSpacesBefore`, `DNFCloseTypeSpacesAfter`.
#### `Universal.PHP.LowercasePHPTag` :wrench: :bar_chart: :books:
Enforces that the "PHP" in a PHP open tag is lowercase.
+#### `Universal.PHP.NoFQNTrueFalseNull` :wrench: :books:
+
+Forbids using `true`, `false` and `null` as fully qualified constants.
+
#### `Universal.PHP.OneStatementInShortEchoTag` :wrench: :books:
Disallow short open echo tags `=` containing more than one PHP statement.
diff --git a/Universal/Docs/Arrays/DuplicateArrayKeyStandard.xml b/Universal/Docs/Arrays/DuplicateArrayKeyStandard.xml
index 00f9b7bb..02c0974e 100644
--- a/Universal/Docs/Arrays/DuplicateArrayKeyStandard.xml
+++ b/Universal/Docs/Arrays/DuplicateArrayKeyStandard.xml
@@ -5,8 +5,8 @@
>
diff --git a/Universal/Docs/Arrays/MixedArrayKeyTypesStandard.xml b/Universal/Docs/Arrays/MixedArrayKeyTypesStandard.xml
index f7efdda1..f66b235a 100644
--- a/Universal/Docs/Arrays/MixedArrayKeyTypesStandard.xml
+++ b/Universal/Docs/Arrays/MixedArrayKeyTypesStandard.xml
@@ -5,7 +5,7 @@
>
diff --git a/Universal/Docs/Arrays/MixedKeyedUnkeyedArrayStandard.xml b/Universal/Docs/Arrays/MixedKeyedUnkeyedArrayStandard.xml
index 79897c19..5f2dbda5 100644
--- a/Universal/Docs/Arrays/MixedKeyedUnkeyedArrayStandard.xml
+++ b/Universal/Docs/Arrays/MixedKeyedUnkeyedArrayStandard.xml
@@ -5,7 +5,7 @@
>
diff --git a/Universal/Docs/CodeAnalysis/ConstructorDestructorReturnStandard.xml b/Universal/Docs/CodeAnalysis/ConstructorDestructorReturnStandard.xml
index b44b4777..35b76785 100644
--- a/Universal/Docs/CodeAnalysis/ConstructorDestructorReturnStandard.xml
+++ b/Universal/Docs/CodeAnalysis/ConstructorDestructorReturnStandard.xml
@@ -9,14 +9,14 @@
]]>
-
+
-
+
: int {}
@@ -31,7 +31,7 @@ class Foo {
]]>
-
+
-
+
-
+
$v ) {}
]]>
-
+
$k ) {}
]]>
diff --git a/Universal/Docs/CodeAnalysis/NoDoubleNegativeStandard.xml b/Universal/Docs/CodeAnalysis/NoDoubleNegativeStandard.xml
index 953149b7..972a304b 100644
--- a/Universal/Docs/CodeAnalysis/NoDoubleNegativeStandard.xml
+++ b/Universal/Docs/CodeAnalysis/NoDoubleNegativeStandard.xml
@@ -9,14 +9,14 @@
]]>
-
+
! $b;
if((bool) callMe($a)) {}
]]>
-
+
! ! $b;
diff --git a/Universal/Docs/CodeAnalysis/NoEchoSprintfStandard.xml b/Universal/Docs/CodeAnalysis/NoEchoSprintfStandard.xml
index 914eb65d..08812140 100644
--- a/Universal/Docs/CodeAnalysis/NoEchoSprintfStandard.xml
+++ b/Universal/Docs/CodeAnalysis/NoEchoSprintfStandard.xml
@@ -9,13 +9,13 @@
]]>
-
+
printf('text %s text', $var);
echo callMe('text %s text', $var);
]]>
-
+
echo sprintf('text %s text', $var);
echo vsprintf('text %s text', [$var]);
diff --git a/Universal/Docs/Constants/LowercaseClassResolutionKeywordStandard.xml b/Universal/Docs/Constants/LowercaseClassResolutionKeywordStandard.xml
index 7b61c8fa..6d34f128 100644
--- a/Universal/Docs/Constants/LowercaseClassResolutionKeywordStandard.xml
+++ b/Universal/Docs/Constants/LowercaseClassResolutionKeywordStandard.xml
@@ -9,12 +9,12 @@
]]>
-
+
-
+
MyClass::CLASS;
]]>
diff --git a/Universal/Docs/Constants/UppercaseMagicConstantsStandard.xml b/Universal/Docs/Constants/UppercaseMagicConstantsStandard.xml
index 2a9583fa..e89dceff 100644
--- a/Universal/Docs/Constants/UppercaseMagicConstantsStandard.xml
+++ b/Universal/Docs/Constants/UppercaseMagicConstantsStandard.xml
@@ -9,13 +9,13 @@
]]>
-
+
__LINE__;
include __DIR__ . '/file.php';
]]>
-
+
__NameSpace__;
include dirname(__file__) . '/file.php';
diff --git a/Universal/Docs/ControlStructures/DisallowAlternativeSyntaxStandard.xml b/Universal/Docs/ControlStructures/DisallowAlternativeSyntaxStandard.xml
index 4be7e6df..816e812f 100644
--- a/Universal/Docs/ControlStructures/DisallowAlternativeSyntaxStandard.xml
+++ b/Universal/Docs/ControlStructures/DisallowAlternativeSyntaxStandard.xml
@@ -9,7 +9,7 @@
]]>
-
+
{
$var = 1;
@@ -20,7 +20,7 @@ while (++$i < 10) {
}
]]>
-
+
:
$var = 1;
diff --git a/Universal/Docs/ControlStructures/DisallowLonelyIfStandard.xml b/Universal/Docs/ControlStructures/DisallowLonelyIfStandard.xml
index 88d09228..8acb7bfa 100644
--- a/Universal/Docs/ControlStructures/DisallowLonelyIfStandard.xml
+++ b/Universal/Docs/ControlStructures/DisallowLonelyIfStandard.xml
@@ -11,7 +11,7 @@
]]>
-
+
-
+
-
+
-
+
diff --git a/Universal/Docs/Operators/ConcatPositionStandard.xml b/Universal/Docs/Operators/ConcatPositionStandard.xml
index 4d2761af..4936f04a 100644
--- a/Universal/Docs/Operators/ConcatPositionStandard.xml
+++ b/Universal/Docs/Operators/ConcatPositionStandard.xml
@@ -13,14 +13,14 @@
]]>
-
+
. $b . 'text'
. $c;
]]>
-
+
.
$b . 'text'
diff --git a/Universal/Docs/Operators/TypeSeparatorSpacingStandard.xml b/Universal/Docs/Operators/TypeSeparatorSpacingStandard.xml
index 3d3c328e..58a3b561 100644
--- a/Universal/Docs/Operators/TypeSeparatorSpacingStandard.xml
+++ b/Universal/Docs/Operators/TypeSeparatorSpacingStandard.xml
@@ -5,25 +5,31 @@
>
-
+
|string $paramA,
- TypeA&TypeB $paramB
+ TypeA&TypeB $paramB,
+ (TypeA&TypeB)|null $paramC
): int|false {}
]]>
-
+
| string $paramA,
- TypeA & TypeB $paramB
+ TypeA & TypeB $paramB,
+ ( TypeA&TypeB ) |null $paramC
): int
|
false {}
diff --git a/Universal/Docs/PHP/NoFQNTrueFalseNullStandard.xml b/Universal/Docs/PHP/NoFQNTrueFalseNullStandard.xml
new file mode 100644
index 00000000..205b4661
--- /dev/null
+++ b/Universal/Docs/PHP/NoFQNTrueFalseNullStandard.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+ null;
+
+if ($something === false) {}
+ ]]>
+
+
+ \null;
+
+if ($something === \false) {}
+ ]]>
+
+
+
diff --git a/Universal/Docs/UseStatements/DisallowUseConstStandard.xml b/Universal/Docs/UseStatements/DisallowUseConstStandard.xml
index 0dbec70b..b4340954 100644
--- a/Universal/Docs/UseStatements/DisallowUseConstStandard.xml
+++ b/Universal/Docs/UseStatements/DisallowUseConstStandard.xml
@@ -15,7 +15,7 @@ use Vendor\Sub\ClassName;
use function Vendor\Sub\functionName;
]]>
-
+
-
+
-
+
[space][space][space][space]$foo = 'bar';
@@ -17,7 +17,7 @@
[tab]$foo = 'bar';
]]>
-
+
[space][space]$foo = 'bar';
diff --git a/Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php b/Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php
index 7097fad7..1fe6841a 100644
--- a/Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php
+++ b/Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php
@@ -80,9 +80,6 @@ final class DuplicateArrayKeySniff extends AbstractArrayDeclarationSniff
/**
* Process every part of the array declaration.
*
- * This contains the default logic for the sniff, but can be overloaded in a concrete child class
- * if needed.
- *
* @since 1.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
diff --git a/Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php b/Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php
index 6f78cb66..147d46e7 100644
--- a/Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php
+++ b/Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php
@@ -166,7 +166,7 @@ public function process(File $phpcsFile, $stackPtr)
$current = $tokens[$stackPtr]['scope_opener'];
$end = $tokens[$stackPtr]['scope_closer'];
- // Not searching for arrow functions as those have an implicit return, so no
+ // Not searching for arrow functions as those have an implicit return, so won't use the `return` keyword.
$search = Collections::functionDeclarationTokens();
$search[\T_RETURN] = \T_RETURN;
diff --git a/Universal/Sniffs/ControlStructures/DisallowLonelyIfSniff.php b/Universal/Sniffs/ControlStructures/DisallowLonelyIfSniff.php
index 58c81ae1..f29a1fea 100644
--- a/Universal/Sniffs/ControlStructures/DisallowLonelyIfSniff.php
+++ b/Universal/Sniffs/ControlStructures/DisallowLonelyIfSniff.php
@@ -121,7 +121,7 @@ public function process(File $phpcsFile, $stackPtr)
if ($tokens[$nextAfter]['code'] === \T_SEMICOLON) {
$innerScopeCloser = $nextAfter;
} else {
- // Missing semi-colon. Report, but don't auto-fix.
+ // Missing semicolon. Report, but don't auto-fix.
$autoFixable = false;
}
} else {
diff --git a/Universal/Sniffs/Files/SeparateFunctionsFromOOSniff.php b/Universal/Sniffs/Files/SeparateFunctionsFromOOSniff.php
index c274e751..9cf191e0 100644
--- a/Universal/Sniffs/Files/SeparateFunctionsFromOOSniff.php
+++ b/Universal/Sniffs/Files/SeparateFunctionsFromOOSniff.php
@@ -81,8 +81,7 @@ public function register()
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
- * @return int|void Integer stack pointer to skip forward or void to continue
- * normal file processing.
+ * @return int Integer stack pointer to skip forward.
*/
public function process(File $phpcsFile, $stackPtr)
{
@@ -185,6 +184,6 @@ public function process(File $phpcsFile, $stackPtr)
}
// Ignore the rest of the file.
- return ($phpcsFile->numTokens + 1);
+ return $phpcsFile->numTokens;
}
}
diff --git a/Universal/Sniffs/Operators/ConcatPositionSniff.php b/Universal/Sniffs/Operators/ConcatPositionSniff.php
index 093785ad..1a035766 100644
--- a/Universal/Sniffs/Operators/ConcatPositionSniff.php
+++ b/Universal/Sniffs/Operators/ConcatPositionSniff.php
@@ -96,8 +96,7 @@ public function register()
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
- * @return int|void Integer stack pointer to skip forward or void to continue
- * normal file processing.
+ * @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
diff --git a/Universal/Sniffs/Operators/TypeSeparatorSpacingSniff.php b/Universal/Sniffs/Operators/TypeSeparatorSpacingSniff.php
index 440ecc86..34d9c476 100644
--- a/Universal/Sniffs/Operators/TypeSeparatorSpacingSniff.php
+++ b/Universal/Sniffs/Operators/TypeSeparatorSpacingSniff.php
@@ -14,15 +14,31 @@
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\Fixers\SpacesFixer;
+use PHPCSUtils\Tokens\Collections;
/**
- * Enforce no space around union type and intersection type separators.
+ * Enforce spacing rules around union, intersection and DNF type separators.
*
* @since 1.0.0
+ * @since 1.3.0 Support for DNF types.
*/
final class TypeSeparatorSpacingSniff implements Sniff
{
+ /**
+ * Tokens this sniff targets.
+ *
+ * @since 1.3.0
+ *
+ * @var array
+ */
+ private $targetTokens = [
+ \T_TYPE_UNION => \T_TYPE_UNION,
+ \T_TYPE_INTERSECTION => \T_TYPE_INTERSECTION,
+ \T_TYPE_OPEN_PARENTHESIS => \T_TYPE_OPEN_PARENTHESIS,
+ \T_TYPE_CLOSE_PARENTHESIS => \T_TYPE_CLOSE_PARENTHESIS,
+ ];
+
/**
* Returns an array of tokens this test wants to listen for.
*
@@ -32,10 +48,7 @@ final class TypeSeparatorSpacingSniff implements Sniff
*/
public function register()
{
- return [
- \T_TYPE_UNION,
- \T_TYPE_INTERSECTION,
- ];
+ return $this->targetTokens;
}
/**
@@ -53,28 +66,78 @@ public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
- $type = ($tokens[$stackPtr]['code'] === \T_TYPE_UNION) ? 'union' : 'intersection';
- $code = \ucfirst($type) . 'Type';
+ $type = 'union';
+ $code = 'UnionType';
+ if ($tokens[$stackPtr]['code'] === \T_TYPE_INTERSECTION) {
+ $type = 'intersection';
+ $code = 'IntersectionType';
+ } elseif ($tokens[$stackPtr]['code'] === \T_TYPE_OPEN_PARENTHESIS) {
+ $type = 'DNF parenthesis open';
+ $code = 'DNFOpen';
+ } elseif ($tokens[$stackPtr]['code'] === \T_TYPE_CLOSE_PARENTHESIS) {
+ $type = 'DNF parenthesis close';
+ $code = 'DNFClose';
+ }
- $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
- SpacesFixer::checkAndFix(
- $phpcsFile,
- $stackPtr,
- $prevNonEmpty,
- 0, // Expected spaces.
- 'Expected %s before the ' . $type . ' type separator. Found: %s',
- $code . 'SpacesBefore',
- 'error',
- 0, // Severity.
- 'Space before ' . $type . ' type separator'
- );
+ $expectedSpaces = 0;
+ $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
+ if ($tokens[$stackPtr]['code'] === \T_TYPE_OPEN_PARENTHESIS) {
+ if ($tokens[$prevNonEmpty]['code'] === \T_COLON
+ || $tokens[$prevNonEmpty]['code'] === \T_CONST
+ || isset(Collections::propertyModifierKeywords()[$tokens[$prevNonEmpty]['code']]) === true
+ ) {
+ // Start of return type or property/const type. Always demand 1 space.
+ $expectedSpaces = 1;
+ }
+
+ if ($tokens[$prevNonEmpty]['code'] === \T_OPEN_PARENTHESIS
+ || $tokens[$prevNonEmpty]['code'] === \T_COMMA
+ ) {
+ // Start of parameter type. Allow new line/indent before.
+ if ($tokens[$prevNonEmpty]['line'] === $tokens[$stackPtr]['line']) {
+ $expectedSpaces = 1;
+ } else {
+ $expectedSpaces = 'skip';
+ }
+ }
+ }
+
+ if (isset($this->targetTokens[$tokens[$prevNonEmpty]['code']]) === true) {
+ // Prevent duplicate errors when there are two adjacent operators.
+ $expectedSpaces = 'skip';
+ }
+
+ if ($expectedSpaces !== 'skip') {
+ SpacesFixer::checkAndFix(
+ $phpcsFile,
+ $stackPtr,
+ $prevNonEmpty,
+ $expectedSpaces,
+ 'Expected %s before the ' . $type . ' type separator. Found: %s',
+ $code . 'SpacesBefore',
+ 'error',
+ 0, // Severity.
+ 'Space before ' . $type . ' type separator'
+ );
+ }
+
+ $expectedSpaces = 0;
+ $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
+ if ($tokens[$stackPtr]['code'] === \T_TYPE_CLOSE_PARENTHESIS) {
+ if ($tokens[$nextNonEmpty]['code'] === \T_OPEN_CURLY_BRACKET
+ || $tokens[$nextNonEmpty]['code'] === \T_VARIABLE
+ || $tokens[$nextNonEmpty]['code'] === \T_STRING
+ ) {
+ // End of return type, parameter or property/const type. Always demand 1 space.
+ $expectedSpaces = 1;
+ }
+ }
- $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
SpacesFixer::checkAndFix(
$phpcsFile,
$stackPtr,
$nextNonEmpty,
- 0, // Expected spaces.
+ $expectedSpaces,
'Expected %s after the ' . $type . ' type separator. Found: %s',
$code . 'SpacesAfter',
'error',
diff --git a/Universal/Sniffs/PHP/NoFQNTrueFalseNullSniff.php b/Universal/Sniffs/PHP/NoFQNTrueFalseNullSniff.php
new file mode 100644
index 00000000..6aa3b188
--- /dev/null
+++ b/Universal/Sniffs/PHP/NoFQNTrueFalseNullSniff.php
@@ -0,0 +1,92 @@
+
+ */
+ public function register()
+ {
+ return [
+ // PHP < 8.0.
+ \T_TRUE,
+ \T_FALSE,
+ \T_NULL,
+
+ // PHP >= 8.0.
+ \T_STRING,
+ ];
+ }
+
+ /**
+ * Processes this test, when one of its tokens is encountered.
+ *
+ * @since 1.3.0
+ *
+ * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
+ * @param int $stackPtr The position of the current token
+ * in the stack passed in $tokens.
+ *
+ * @return void
+ */
+ public function process(File $phpcsFile, $stackPtr)
+ {
+ $tokens = $phpcsFile->getTokens();
+ $content = $tokens[$stackPtr]['content'];
+ $contentLC = \strtolower($content);
+
+ if ($contentLC !== 'true' && $contentLC !== 'false' && $contentLC !== 'null') {
+ return;
+ }
+
+ $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
+ if ($tokens[$prev]['code'] !== \T_NS_SEPARATOR) {
+ return;
+ }
+
+ $prevPrev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prev - 1), null, true);
+ if ($tokens[$prevPrev]['code'] === \T_STRING || $tokens[$prevPrev]['code'] === \T_NAMESPACE) {
+ return;
+ }
+
+ $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
+ if ($tokens[$next]['code'] === \T_NS_SEPARATOR) {
+ return;
+ }
+
+ $fix = $phpcsFile->addFixableError(
+ 'The special PHP constant "%s" should not be fully qualified.',
+ $prev,
+ 'Found',
+ [$contentLC]
+ );
+
+ if ($fix === true) {
+ $phpcsFile->fixer->replaceToken($prev, '');
+ }
+ }
+}
diff --git a/Universal/Sniffs/PHP/OneStatementInShortEchoTagSniff.php b/Universal/Sniffs/PHP/OneStatementInShortEchoTagSniff.php
index cbccb4c6..d68c38ee 100644
--- a/Universal/Sniffs/PHP/OneStatementInShortEchoTagSniff.php
+++ b/Universal/Sniffs/PHP/OneStatementInShortEchoTagSniff.php
@@ -75,7 +75,7 @@ public function process(File $phpcsFile, $stackPtr)
return;
}
- // Semi-colon, so check for any code between it and the close tag.
+ // Semicolon, so check for any code between it and the close tag.
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($endOfStatement + 1), null, true);
if ($nextNonEmpty === false
|| $tokens[$nextNonEmpty]['code'] === \T_CLOSE_TAG
diff --git a/Universal/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php b/Universal/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php
index 817a44e1..c94934f1 100644
--- a/Universal/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php
+++ b/Universal/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php
@@ -63,6 +63,9 @@ final class DisallowInlineTabsSniff implements Sniff
\T_DOC_COMMENT_WHITESPACE => true,
\T_DOC_COMMENT_STRING => true,
\T_COMMENT => true,
+ \T_START_HEREDOC => true,
+ \T_START_NOWDOC => true,
+ \T_YIELD_FROM => true,
];
/**
@@ -102,7 +105,7 @@ public function process(File $phpcsFile, $stackPtr)
$dummy = new DummyTokenizer('', $phpcsFile->config);
for ($i = 0; $i < $phpcsFile->numTokens; $i++) {
- // Skip all non-whitespace tokens and skip whitespace at the start of a new line.
+ // Skip all non-target tokens and skip whitespace at the start of a new line.
if (isset($this->find[$tokens[$i]['code']]) === false
|| (($tokens[$i]['code'] === \T_WHITESPACE
|| $tokens[$i]['code'] === \T_DOC_COMMENT_WHITESPACE)
diff --git a/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.inc b/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.inc
index 530a14a5..ae6d1916 100644
--- a/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.inc
+++ b/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.inc
@@ -36,6 +36,38 @@ $closure = function (TypeA | TypeB $p, TypeA &namespace\TypeB $q): \
$arrow = fn (TypeA | TypeB $p, TypeA & namespace\TypeB $q): string | int => $p * $q;
+/*
+ * PHP 8.2 DNF types.
+ */
+class DNFTypes {
+ private const (A&B)|false DNF_CORRECT_START = false;
+ protected const false|(A&B) DNF_CORRECT_END = false;
+ public const ( A&B ) |false DNF_INCORRECT_START = false;
+ final const false| ( A&B ) DNF_INCORRECT_END = false;
+
+ private (A&B)|false $dnfCorrectStart = false;
+ readonly false|(A&B) $dnfCorrectEnd = false;
+ static ( A&B ) |false $dnfIncorrectStart = false;
+ final false| ( A&B ) $dnfIncorrectEnd = false;
+
+ public function DNFCorrect(
+ (A&B)|false $paramA, (A&B)|false $paramB,
+ null|(\FQN&\Partially\Qualified) $paramC,
+ ): (A&B)|(C&D) {
+ }
+
+ public function DNFIncorrect(
+ ( A&B ) | false $paramA, (A&B) |false $paramB,
+ null| ( \FQN&\Partially\Qualified )
+ $paramC,
+ ): ( A &B )
+ |
+ ( C&D ) {
+ }
+
+ public function DNFIncorrectReturnTypeNoSpace():(A&B)|(C&D){}
+}
+
// Live coding.
// This test should be the last test in the file.
function foo(int|
diff --git a/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.inc.fixed b/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.inc.fixed
index a1ec0ebb..a86fcc82 100644
--- a/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.inc.fixed
+++ b/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.inc.fixed
@@ -32,6 +32,35 @@ $closure = function (TypeA|TypeB $p, TypeA&namespace\TypeB $q): \Fully\Qualified
$arrow = fn (TypeA|TypeB $p, TypeA&namespace\TypeB $q): string|int => $p * $q;
+/*
+ * PHP 8.2 DNF types.
+ */
+class DNFTypes {
+ private const (A&B)|false DNF_CORRECT_START = false;
+ protected const false|(A&B) DNF_CORRECT_END = false;
+ public const (A&B)|false DNF_INCORRECT_START = false;
+ final const false|(A&B) DNF_INCORRECT_END = false;
+
+ private (A&B)|false $dnfCorrectStart = false;
+ readonly false|(A&B) $dnfCorrectEnd = false;
+ static (A&B)|false $dnfIncorrectStart = false;
+ final false|(A&B) $dnfIncorrectEnd = false;
+
+ public function DNFCorrect(
+ (A&B)|false $paramA, (A&B)|false $paramB,
+ null|(\FQN&\Partially\Qualified) $paramC,
+ ): (A&B)|(C&D) {
+ }
+
+ public function DNFIncorrect(
+ (A&B)|false $paramA, (A&B)|false $paramB,
+ null|(\FQN&\Partially\Qualified) $paramC,
+ ): (A&B)|(C&D) {
+ }
+
+ public function DNFIncorrectReturnTypeNoSpace(): (A&B)|(C&D) {}
+}
+
// Live coding.
// This test should be the last test in the file.
function foo(int|
diff --git a/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.php b/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.php
index 76c2ad7d..e42fb6aa 100644
--- a/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.php
+++ b/Universal/Tests/Operators/TypeSeparatorSpacingUnitTest.php
@@ -38,6 +38,16 @@ public function getErrorList()
33 => 1,
35 => 5,
37 => 6,
+ 45 => 4,
+ 46 => 4,
+ 50 => 4,
+ 51 => 4,
+ 60 => 6,
+ 61 => 4,
+ 63 => 5,
+ 64 => 1,
+ 65 => 3,
+ 68 => 2,
];
}
diff --git a/Universal/Tests/PHP/NoFQNTrueFalseNullUnitTest.inc b/Universal/Tests/PHP/NoFQNTrueFalseNullUnitTest.inc
new file mode 100644
index 00000000..a537a293
--- /dev/null
+++ b/Universal/Tests/PHP/NoFQNTrueFalseNullUnitTest.inc
@@ -0,0 +1,27 @@
+ Key is the line number, value is the number of expected errors.
+ */
+ public function getErrorList()
+ {
+ return [
+ 13 => 1,
+ 14 => 1,
+ 15 => 1,
+ 17 => 1,
+ 18 => 1,
+ 19 => 1,
+ 22 => 1,
+ 24 => 2,
+ 26 => 2,
+ ];
+ }
+
+ /**
+ * Returns the lines where warnings should occur.
+ *
+ * @return array Key is the line number, value is the number of expected warnings.
+ */
+ public function getWarningList()
+ {
+ return [];
+ }
+}
diff --git a/Universal/Tests/UseStatements/KeywordSpacingUnitTest.2.inc b/Universal/Tests/UseStatements/KeywordSpacingUnitTest.2.inc
index 162e98b3..a12f3abb 100644
--- a/Universal/Tests/UseStatements/KeywordSpacingUnitTest.2.inc
+++ b/Universal/Tests/UseStatements/KeywordSpacingUnitTest.2.inc
@@ -5,7 +5,5 @@
* For PHP < 8.0, it will correctly flag this as "no space" after the `use` keyword.
* For PHP 8.0+, this will no longer be flagged as keywords are allowed in namespaced names,
* so the `use` is tokenized as `T_STRING` instead of `T_USE` and won't be flagged.
- *
- * For this reason, there is no "fixed" file included as the test would not be able to pass on PHP 8.0+.
*/
use\Util\MyOtherClass;
diff --git a/Universal/Tests/UseStatements/KeywordSpacingUnitTest.2.inc.fixed b/Universal/Tests/UseStatements/KeywordSpacingUnitTest.2.inc.fixed
new file mode 100644
index 00000000..e15065e9
--- /dev/null
+++ b/Universal/Tests/UseStatements/KeywordSpacingUnitTest.2.inc.fixed
@@ -0,0 +1,9 @@
+
+ */
+ protected function getTestFiles($testFileBase)
+ {
+ $testFiles = parent::getTestFiles($testFileBase);
+
+ if (\PHP_VERSION_ID < 80000) {
+ return $testFiles;
+ }
+
+ // The issue being tested in the "2" test case file cannot be flagged/fixed on PHP 8.0+.
+ $target = 'KeywordSpacingUnitTest.2.inc';
+ $length = \strlen($target);
+ foreach ($testFiles as $i => $fileName) {
+ if (\substr($fileName, -$length) === $target) {
+ unset($testFiles[$i]);
+ break;
+ }
+ }
+
+ return $testFiles;
+ }
+
/**
* Returns the lines where errors should occur.
*
@@ -55,12 +83,8 @@ public function getErrorList($testFile = '')
];
case 'KeywordSpacingUnitTest.2.inc':
- if (\PHP_VERSION_ID >= 80000) {
- return [];
- }
-
return [
- 11 => 1,
+ 9 => 1,
];
default:
diff --git a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.1.inc b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.1.inc
index 0d065c08..f50b2102 100644
--- a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.1.inc
+++ b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.1.inc
@@ -59,3 +59,24 @@ $aaaaaaaa = true;
// Tab indented, no tabs.
// phpcs:ignore Stnd.Cat.SniffName -- testing mixed comment + annotations don't trigger on indentation.
// Tab indented, no tabs.
+
+$a = <<< TAB_BETWEEN
+text
+TAB_BETWEEN;
+
+$a = <<< 'TABS_BETWEEN'
+text
+TABS_BETWEEN;
+
+function myGenerator() {
+ yield from tabsBetweenShouldBeFixed();
+
+yield /*comment*/ from tabsBetweenShouldBeFixedEvenWhenKeywordStartsOnColumn1();
+
+ yield
+ from tabIndentationShouldBeIgnored();
+
+ yield
+ /*comment*/
+ from tabIndentationShouldBeIgnored();
+}
diff --git a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.1.inc.fixed b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.1.inc.fixed
index 63726813..37c19527 100644
--- a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.1.inc.fixed
+++ b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.1.inc.fixed
@@ -59,3 +59,24 @@ $aaaaaaaa = true;
// Tab indented, no tabs.
// phpcs:ignore Stnd.Cat.SniffName -- testing mixed comment + annotations don't trigger on indentation.
// Tab indented, no tabs.
+
+$a = <<< TAB_BETWEEN
+text
+TAB_BETWEEN;
+
+$a = <<< 'TABS_BETWEEN'
+text
+TABS_BETWEEN;
+
+function myGenerator() {
+ yield from tabsBetweenShouldBeFixed();
+
+yield /*comment*/ from tabsBetweenShouldBeFixedEvenWhenKeywordStartsOnColumn1();
+
+ yield
+ from tabIndentationShouldBeIgnored();
+
+ yield
+ /*comment*/
+ from tabIndentationShouldBeIgnored();
+}
diff --git a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.4.inc b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.4.inc
index 7227c96c..d79d8d3f 100644
--- a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.4.inc
+++ b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.4.inc
@@ -33,3 +33,17 @@ $aaaaaaaa = true;
// Tab indented and inline tabs.
// Tab indented and inline tabs.
+
+$a = <<< TAB_BETWEEN
+text
+TAB_BETWEEN;
+
+$a = <<< 'TABS_BETWEEN'
+text
+TABS_BETWEEN;
+
+function myGenerator() {
+ yield from tabsBetweenShouldBeFixed();
+
+yield /*comment*/ from tabsBetweenShouldBeFixedEvenWhenKeywordStartsOnColumn1();
+}
diff --git a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.4.inc.fixed b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.4.inc.fixed
index 982e48ea..715f13e0 100644
--- a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.4.inc.fixed
+++ b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.4.inc.fixed
@@ -33,3 +33,17 @@ $aaaaaaaa = true;
// Tab indented and inline tabs.
// Tab indented and inline tabs.
+
+$a = <<< TAB_BETWEEN
+text
+TAB_BETWEEN;
+
+$a = <<< 'TABS_BETWEEN'
+text
+TABS_BETWEEN;
+
+function myGenerator() {
+ yield from tabsBetweenShouldBeFixed();
+
+yield /*comment*/ from tabsBetweenShouldBeFixedEvenWhenKeywordStartsOnColumn1();
+}
diff --git a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.5.inc b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.5.inc
index 139b27fd..1904a508 100644
--- a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.5.inc
+++ b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.5.inc
@@ -33,3 +33,17 @@ $aaaaaaaa = true;
// Tab indented and inline tabs.
// Tab indented and inline tabs.
+
+$a = <<< TAB_BETWEEN
+text
+TAB_BETWEEN;
+
+$a = <<< 'TABS_BETWEEN'
+text
+TABS_BETWEEN;
+
+function myGenerator() {
+ yield from tabsBetweenShouldBeFixed();
+
+yield /*comment*/ from tabsBetweenShouldBeFixedEvenWhenKeywordStartsOnColumn1();
+}
diff --git a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.5.inc.fixed b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.5.inc.fixed
index ce001601..e2bd84e0 100644
--- a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.5.inc.fixed
+++ b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.5.inc.fixed
@@ -33,3 +33,17 @@ $aaaaaaaa = true;
// Tab indented and inline tabs.
// Tab indented and inline tabs.
+
+$a = <<< TAB_BETWEEN
+text
+TAB_BETWEEN;
+
+$a = <<< 'TABS_BETWEEN'
+text
+TABS_BETWEEN;
+
+function myGenerator() {
+ yield from tabsBetweenShouldBeFixed();
+
+yield /*comment*/ from tabsBetweenShouldBeFixedEvenWhenKeywordStartsOnColumn1();
+}
diff --git a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.6.inc b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.6.inc
index 05b55208..9dcecd1b 100644
--- a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.6.inc
+++ b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.6.inc
@@ -41,3 +41,24 @@ $aaaaaaaa = true;
* @param int $var Description.
* @param string $string Another description.
*/
+
+$a = <<< TAB_BETWEEN
+text
+TAB_BETWEEN;
+
+$a = <<< 'TABS_BETWEEN'
+text
+TABS_BETWEEN;
+
+function myGenerator() {
+ yield from tabsBetweenShouldBeFixed();
+
+ yield /*comment*/ from tabsBetweenShouldBeFixed();
+
+ yield
+ from tabIndentationShouldBeIgnored();
+
+ yield
+ /*comment*/
+ from tabIndentationShouldBeIgnored();
+}
diff --git a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.php b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.php
index 147e3361..4de61162 100644
--- a/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.php
+++ b/Universal/Tests/WhiteSpace/DisallowInlineTabsUnitTest.php
@@ -10,6 +10,7 @@
namespace PHPCSExtra\Universal\Tests\WhiteSpace;
+use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;
/**
@@ -73,6 +74,12 @@ public function setCliValues($testFile, $config)
*/
public function getErrorList($testFile = '')
{
+ // As of PHP 8.3, comments may be tokenized within a "yield from" token, but only for PHPCS < 3.11.0.
+ $commentsInYieldFrom = false;
+ if (\PHP_VERSION_ID >= 80300 && \version_compare(Config::VERSION, '3.11.0', '<')) {
+ $commentsInYieldFrom = true;
+ }
+
switch ($testFile) {
case 'DisallowInlineTabsUnitTest.1.inc':
return [
@@ -93,6 +100,10 @@ public function getErrorList($testFile = '')
49 => 1,
52 => 1,
53 => 1,
+ 63 => 1,
+ 67 => 1,
+ 72 => 1,
+ 74 => ($commentsInYieldFrom === true ? 1 : 2),
];
case 'DisallowInlineTabsUnitTest.2.inc':
@@ -126,6 +137,10 @@ public function getErrorList($testFile = '')
31 => 1,
34 => 1,
35 => 1,
+ 37 => 1,
+ 41 => 1,
+ 46 => 1,
+ 48 => ($commentsInYieldFrom === true ? 1 : 2),
];
case 'DisallowInlineTabsUnitTest.5.inc':
@@ -145,6 +160,10 @@ public function getErrorList($testFile = '')
31 => 1,
34 => 1,
35 => 1,
+ 37 => 1,
+ 41 => 1,
+ 46 => 1,
+ 48 => ($commentsInYieldFrom === true ? 1 : 2),
];
default:
diff --git a/composer.json b/composer.json
index 92122571..b565df6a 100644
--- a/composer.json
+++ b/composer.json
@@ -22,7 +22,7 @@
},
"require" : {
"php" : ">=5.4",
- "squizlabs/php_codesniffer" : "^3.8.0",
+ "squizlabs/php_codesniffer" : "^3.12.1",
"phpcsstandards/phpcsutils" : "^1.0.9"
},
"require-dev" : {
@@ -64,6 +64,7 @@
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
- }
+ },
+ "lock": false
}
}
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 060dda93..95991de0 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -16,36 +16,41 @@ parameters:
# Keep to stay in line with parent class.
-
message: '`^Constructor of class PHPCSExtra\\Universal\\Helpers\\DummyTokenizer has an unused parameter \$content\.$`'
- path: Universal\Helpers\DummyTokenizer.php
+ path: Universal/Helpers/DummyTokenizer.php
count: 1
# Level 4
# PHPStan doesn't seem to like uninitialized properties...
+ # Ref: https://github.com/phpstan/phpstan/issues/10305
-
message: '`^Property \S+Sniff::\$(phpVersion|tabWidth) \(int\) in isset\(\) is not nullable\.$`'
paths:
- - Modernize\Sniffs\FunctionCalls\DirnameSniff.php
- - Universal\Sniffs\Arrays\DuplicateArrayKeySniff.php
- - Universal\Sniffs\CodeAnalysis\ConstructorDestructorReturnSniff.php
- - Universal\Sniffs\WhiteSpace\CommaSpacingSniff.php
- - Universal\Sniffs\WhiteSpace\DisallowInlineTabsSniff.php
- - Universal\Sniffs\WhiteSpace\PrecisionAlignmentSniff.php
+ - Modernize/Sniffs/FunctionCalls/DirnameSniff.php
+ - Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php
+ - Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php
+ - Universal/Sniffs/WhiteSpace/CommaSpacingSniff.php
+ - Universal/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php
+ - Universal/Sniffs/WhiteSpace/PrecisionAlignmentSniff.php
-
message: '`^Strict comparison using === between true and false will always evaluate to false\.$`'
paths:
- - Modernize\Sniffs\FunctionCalls\DirnameSniff.php
- - Universal\Sniffs\Arrays\DuplicateArrayKeySniff.php
- - Universal\Sniffs\CodeAnalysis\ConstructorDestructorReturnSniff.php
- - Universal\Sniffs\WhiteSpace\CommaSpacingSniff.php
- - Universal\Sniffs\WhiteSpace\DisallowInlineTabsSniff.php
- - Universal\Sniffs\WhiteSpace\PrecisionAlignmentSniff.php
+ - Modernize/Sniffs/FunctionCalls/DirnameSniff.php
+ - Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php
+ - Universal/Sniffs/CodeAnalysis/ConstructorDestructorReturnSniff.php
+ - Universal/Sniffs/WhiteSpace/CommaSpacingSniff.php
+ - Universal/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php
+ - Universal/Sniffs/WhiteSpace/PrecisionAlignmentSniff.php
-
message: '`^Property PHPCSExtra\\Universal\\Sniffs\\Arrays\\DuplicateArrayKeySniff\:\:\$currentMaxIntKey[GL]t8 \(int\) in isset\(\) is not nullable\.$`'
- path: Universal\Sniffs\Arrays\DuplicateArrayKeySniff.php
+ path: Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php
count: 5
+ -
+ message: '`^Strict comparison using === between true and true will always evaluate to true\.$`'
+ path: Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php
+ count: 1
-
message: '`^Result of && is always false\.$`'
- path: Universal\Sniffs\Arrays\DuplicateArrayKeySniff.php
+ path: Universal/Sniffs/Arrays/DuplicateArrayKeySniff.php
count: 1
# Level 5