├── CHANGELOG.md ├── LICENSE ├── Modernize ├── Docs │ └── FunctionCalls │ │ └── DirnameStandard.xml ├── Sniffs │ └── FunctionCalls │ │ └── DirnameSniff.php └── ruleset.xml ├── NormalizedArrays ├── Docs │ └── Arrays │ │ ├── ArrayBraceSpacingStandard.xml │ │ └── CommaAfterLastStandard.xml ├── Sniffs │ └── Arrays │ │ ├── ArrayBraceSpacingSniff.php │ │ └── CommaAfterLastSniff.php └── ruleset.xml ├── README.md ├── Universal ├── Docs │ ├── Arrays │ │ ├── DisallowShortArraySyntaxStandard.xml │ │ ├── DuplicateArrayKeyStandard.xml │ │ ├── MixedArrayKeyTypesStandard.xml │ │ └── MixedKeyedUnkeyedArrayStandard.xml │ ├── Classes │ │ ├── DisallowAnonClassParenthesesStandard.xml │ │ ├── DisallowFinalClassStandard.xml │ │ ├── ModifierKeywordOrderStandard.xml │ │ ├── RequireAnonClassParenthesesStandard.xml │ │ └── RequireFinalClassStandard.xml │ ├── CodeAnalysis │ │ ├── ConstructorDestructorReturnStandard.xml │ │ ├── ForeachUniqueAssignmentStandard.xml │ │ ├── NoDoubleNegativeStandard.xml │ │ ├── NoEchoSprintfStandard.xml │ │ └── StaticInFinalClassStandard.xml │ ├── Constants │ │ ├── LowercaseClassResolutionKeywordStandard.xml │ │ ├── ModifierKeywordOrderStandard.xml │ │ └── UppercaseMagicConstantsStandard.xml │ ├── ControlStructures │ │ ├── DisallowAlternativeSyntaxStandard.xml │ │ ├── DisallowLonelyIfStandard.xml │ │ └── IfElseDeclarationStandard.xml │ ├── Files │ │ └── SeparateFunctionsFromOOStandard.xml │ ├── FunctionDeclarations │ │ ├── NoLongClosuresStandard.xml │ │ └── RequireFinalMethodsInTraitsStandard.xml │ ├── Lists │ │ ├── DisallowLongListSyntaxStandard.xml │ │ └── DisallowShortListSyntaxStandard.xml │ ├── Namespaces │ │ ├── DisallowCurlyBraceSyntaxStandard.xml │ │ ├── DisallowDeclarationWithoutNameStandard.xml │ │ ├── EnforceCurlyBraceSyntaxStandard.xml │ │ └── OneDeclarationPerFileStandard.xml │ ├── NamingConventions │ │ └── NoReservedKeywordParameterNamesStandard.xml │ ├── OOStructures │ │ └── AlphabeticExtendsImplementsStandard.xml │ ├── Operators │ │ ├── ConcatPositionStandard.xml │ │ ├── DisallowLogicalAndOrStandard.xml │ │ ├── DisallowShortTernaryStandard.xml │ │ ├── DisallowStandalonePostIncrementDecrementStandard.xml │ │ ├── StrictComparisonsStandard.xml │ │ └── TypeSeparatorSpacingStandard.xml │ ├── PHP │ │ ├── LowercasePHPTagStandard.xml │ │ ├── NoFQNTrueFalseNullStandard.xml │ │ └── OneStatementInShortEchoTagStandard.xml │ ├── UseStatements │ │ ├── DisallowMixedGroupUseStandard.xml │ │ ├── DisallowUseClassStandard.xml │ │ ├── DisallowUseConstStandard.xml │ │ ├── DisallowUseFunctionStandard.xml │ │ ├── KeywordSpacingStandard.xml │ │ ├── LowercaseFunctionConstStandard.xml │ │ ├── NoLeadingBackslashStandard.xml │ │ └── NoUselessAliasesStandard.xml │ └── WhiteSpace │ │ ├── AnonClassKeywordSpacingStandard.xml │ │ ├── CommaSpacingStandard.xml │ │ ├── DisallowInlineTabsStandard.xml │ │ └── PrecisionAlignmentStandard.xml ├── Helpers │ └── DummyTokenizer.php ├── Sniffs │ ├── Arrays │ │ ├── DisallowShortArraySyntaxSniff.php │ │ ├── DuplicateArrayKeySniff.php │ │ ├── MixedArrayKeyTypesSniff.php │ │ └── MixedKeyedUnkeyedArraySniff.php │ ├── Classes │ │ ├── DisallowAnonClassParenthesesSniff.php │ │ ├── DisallowFinalClassSniff.php │ │ ├── ModifierKeywordOrderSniff.php │ │ ├── RequireAnonClassParenthesesSniff.php │ │ └── RequireFinalClassSniff.php │ ├── CodeAnalysis │ │ ├── ConstructorDestructorReturnSniff.php │ │ ├── ForeachUniqueAssignmentSniff.php │ │ ├── NoDoubleNegativeSniff.php │ │ ├── NoEchoSprintfSniff.php │ │ └── StaticInFinalClassSniff.php │ ├── Constants │ │ ├── LowercaseClassResolutionKeywordSniff.php │ │ ├── ModifierKeywordOrderSniff.php │ │ └── UppercaseMagicConstantsSniff.php │ ├── ControlStructures │ │ ├── DisallowAlternativeSyntaxSniff.php │ │ ├── DisallowLonelyIfSniff.php │ │ └── IfElseDeclarationSniff.php │ ├── Files │ │ └── SeparateFunctionsFromOOSniff.php │ ├── FunctionDeclarations │ │ ├── NoLongClosuresSniff.php │ │ └── RequireFinalMethodsInTraitsSniff.php │ ├── Lists │ │ ├── DisallowLongListSyntaxSniff.php │ │ └── DisallowShortListSyntaxSniff.php │ ├── Namespaces │ │ ├── DisallowCurlyBraceSyntaxSniff.php │ │ ├── DisallowDeclarationWithoutNameSniff.php │ │ ├── EnforceCurlyBraceSyntaxSniff.php │ │ └── OneDeclarationPerFileSniff.php │ ├── NamingConventions │ │ └── NoReservedKeywordParameterNamesSniff.php │ ├── OOStructures │ │ └── AlphabeticExtendsImplementsSniff.php │ ├── Operators │ │ ├── ConcatPositionSniff.php │ │ ├── DisallowLogicalAndOrSniff.php │ │ ├── DisallowShortTernarySniff.php │ │ ├── DisallowStandalonePostIncrementDecrementSniff.php │ │ ├── StrictComparisonsSniff.php │ │ └── TypeSeparatorSpacingSniff.php │ ├── PHP │ │ ├── LowercasePHPTagSniff.php │ │ ├── NoFQNTrueFalseNullSniff.php │ │ └── OneStatementInShortEchoTagSniff.php │ ├── UseStatements │ │ ├── DisallowMixedGroupUseSniff.php │ │ ├── DisallowUseClassSniff.php │ │ ├── DisallowUseConstSniff.php │ │ ├── DisallowUseFunctionSniff.php │ │ ├── KeywordSpacingSniff.php │ │ ├── LowercaseFunctionConstSniff.php │ │ ├── NoLeadingBackslashSniff.php │ │ └── NoUselessAliasesSniff.php │ └── WhiteSpace │ │ ├── AnonClassKeywordSpacingSniff.php │ │ ├── CommaSpacingSniff.php │ │ ├── DisallowInlineTabsSniff.php │ │ └── PrecisionAlignmentSniff.php └── ruleset.xml └── composer.json /Modernize/Docs/FunctionCalls/DirnameStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | = 5.3: Usage of dirname(__FILE__) can be replaced with __DIR__. 9 | ]]> 10 | 11 | 12 | 13 | __DIR__; 15 | ]]> 16 | 17 | 18 | dirname(__FILE__); 20 | ]]> 21 | 22 | 23 | 24 | = 7.0: Nested calls to dirname() can be replaced by using dirname() with the $levels parameter. 26 | ]]> 27 | 28 | 29 | 30 | dirname($file, 3); 32 | ]]> 33 | 34 | 35 | dirname(dirname(dirname($file))); 37 | ]]> 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Modernize/ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | A collection of sniffs to detect code modernization opportunities. 5 | 6 | -------------------------------------------------------------------------------- /NormalizedArrays/Docs/Arrays/ArrayBraceSpacingStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | (1, 2); 20 | ]]> 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | 35 | 36 | 37 | ); 39 | 40 | $args = [ ]; 41 | ]]> 42 | 43 | 44 | 45 | 48 | 49 | 50 | 51 | 56 | 57 | 58 | 1, 2 ); 60 | 61 | $args = [ 1, 2 ]; 62 | ]]> 63 | 64 | 65 | 66 | 69 | 70 | 71 | 72 | 74 | 1, 75 | 2 76 | ); 77 | 78 | $args = [ 79 | 1, 80 | 2 81 | ]; 82 | ]]> 83 | 84 | 85 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /NormalizedArrays/Docs/Arrays/CommaAfterLastStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | no comma after the last array item. 9 | 10 | However, for multi-line arrays, there should be a comma after the last array item. 11 | ]]> 12 | 13 | 14 | 15 | 18 | 19 | 20 | , ); 22 | ]]> 23 | 24 | 25 | 26 | 27 | 'foo', 30 | 2 => 'bar', 31 | ]; 32 | ]]> 33 | 34 | 35 | 'foo', 38 | 2 => 'bar' 39 | ]; 40 | ]]> 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /NormalizedArrays/ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | A ruleset for PHP_CodeSniffer to check arrays for normalized format. 5 | 6 | -------------------------------------------------------------------------------- /Universal/Docs/Arrays/DisallowShortArraySyntaxStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | array( 15 | 'foo' => 'bar', 16 | ); 17 | ]]> 18 | 19 | 20 | [ 22 | 'foo' => 'bar', 23 | ]; 24 | ]]> 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Universal/Docs/Arrays/DuplicateArrayKeyStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 13 | 14 | 'foo' => 22, 17 | 'bar' => 25, 18 | 'baz' => 28, 19 | ); 20 | 21 | $args = array( 22 | 22, 23 | 25, 24 | 2 => 28, 25 | ); 26 | ]]> 27 | 28 | 29 | 'foo' => 22, 32 | 'bar' => 25, 33 | 'bar' => 28, 34 | ); 35 | 36 | $args = array( 37 | 22, 38 | 25, 39 | 1 => 28, 40 | ); 41 | ]]> 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Universal/Docs/Arrays/MixedArrayKeyTypesStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 'foo' => 22, 16 | 'bar' => 25, 17 | ); 18 | 19 | $args = array( 20 | 0 => 22, 21 | 1 => 25, 22 | ); 23 | ]]> 24 | 25 | 26 | 22, 29 | 25, 30 | ); 31 | 32 | $args = array( 33 | 'foo' => 22, 34 | 12 => 25, 35 | ); 36 | 37 | ]]> 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Universal/Docs/Arrays/MixedKeyedUnkeyedArrayStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 'foo' => 22, 16 | 'bar' => 25, 17 | ); 18 | 19 | $args = array(22, 25); 20 | ]]> 21 | 22 | 23 | 22, 26 | 25, 27 | ); 28 | ]]> 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Universal/Docs/Classes/DisallowAnonClassParenthesesStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | {}; 15 | $anon = new class($param) {}; 16 | ]]> 17 | 18 | 19 | () {}; 21 | ]]> 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Universal/Docs/Classes/DisallowFinalClassStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | class Foo {} 15 | abstract class Bar implements MyInterface {} 16 | ]]> 17 | 18 | 19 | final class Foo {} 21 | final class Bar extends MyAbstract {} 22 | ]]> 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Universal/Docs/Classes/ModifierKeywordOrderStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | 15 | final readonly class Foo {} 17 | abstract readonly class Bar {} 18 | ]]> 19 | 20 | 21 | readonly final class Foo {} 23 | readonly abstract class Bar {} 24 | ]]> 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Universal/Docs/Classes/RequireAnonClassParenthesesStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | () {}; 15 | ]]> 16 | 17 | 18 | {}; 20 | ]]> 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Universal/Docs/Classes/RequireFinalClassStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | final class Foo {} 15 | final class Bar extends MyAbstract {} 16 | ]]> 17 | 18 | 19 | class Foo {} 21 | abstract class Bar implements MyInterface {} 22 | ]]> 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Universal/Docs/CodeAnalysis/ConstructorDestructorReturnStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | : int {} 23 | } 24 | ]]> 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 47 | 48 | 49 | $this; 54 | } 55 | 56 | public function __destruct() { 57 | // Do something. 58 | return false; 59 | } 60 | } 61 | ]]> 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Universal/Docs/CodeAnalysis/ForeachUniqueAssignmentStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 15 | 16 | $v ) {} 18 | ]]> 19 | 20 | 21 | $k ) {} 23 | ]]> 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Universal/Docs/CodeAnalysis/NoDoubleNegativeStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | ! $b; 15 | 16 | if((bool) callMe($a)) {} 17 | ]]> 18 | 19 | 20 | ! ! $b; 22 | 23 | if(! ! ! callMe($a)) {} 24 | ]]> 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Universal/Docs/CodeAnalysis/NoEchoSprintfStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | printf('text %s text', $var); 15 | echo callMe('text %s text', $var); 16 | ]]> 17 | 18 | 19 | echo sprintf('text %s text', $var); 21 | echo vsprintf('text %s text', [$var]); 22 | ]]> 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Universal/Docs/CodeAnalysis/StaticInFinalClassStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 17 | 18 | self 22 | { 23 | $var = self::functionCall(); 24 | $var = $obj instanceof self; 25 | $var = new self; 26 | } 27 | } 28 | ]]> 29 | 30 | 31 | static|false { 35 | $var = static::$prop; 36 | $var = $obj instanceof static; 37 | $var = new static(); 38 | } 39 | }; 40 | ]]> 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Universal/Docs/Constants/LowercaseClassResolutionKeywordStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | MyClass::CLASS; 20 | ]]> 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Universal/Docs/Constants/ModifierKeywordOrderStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | 15 | final public const FOO = 'foo'; 18 | } 19 | ]]> 20 | 21 | 22 | protected final const BAR = 'foo'; 26 | } 27 | ]]> 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Universal/Docs/Constants/UppercaseMagicConstantsStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | __LINE__; 15 | include __DIR__ . '/file.php'; 16 | ]]> 17 | 18 | 19 | __NameSpace__; 21 | include dirname(__file__) . '/file.php'; 22 | ]]> 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Universal/Docs/ControlStructures/DisallowAlternativeSyntaxStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | { 15 | $var = 1; 16 | } 17 | 18 | while (++$i < 10) { 19 | echo $i; 20 | } 21 | ]]> 22 | 23 | 24 | : 26 | $var = 1; 27 | endif; 28 | 29 | while (++$i < 10): 30 | echo $i; 31 | endwhile; 32 | ]]> 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Universal/Docs/ControlStructures/DisallowLonelyIfStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | 15 | elseif ($bar) { 19 | // ... 20 | } 21 | 22 | if ($foo) { 23 | // ... 24 | } else { 25 | if ($bar) { 26 | // ... 27 | } 28 | 29 | doSomethingElse(); 30 | 31 | } 32 | 33 | ]]> 34 | 35 | 36 | if ($bar) { 41 | // ... 42 | } else { 43 | // ... 44 | } 45 | } 46 | ]]> 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Universal/Docs/ControlStructures/IfElseDeclarationStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 17 | elseif ($bar) { 18 | $var = 2; 19 | } 20 | else { 21 | $var = 3; 22 | } 23 | ]]> 24 | 25 | 26 | elseif ($bar) { 30 | $var = 2; 31 | } else { 32 | $var = 3; 33 | } 34 | ]]> 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Universal/Docs/Files/SeparateFunctionsFromOOStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 15 | 16 | 29 | 30 | 31 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Universal/Docs/FunctionDeclarations/NoLongClosuresStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 16 | 17 | 24 | 25 | 26 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Universal/Docs/FunctionDeclarations/RequireFinalMethodsInTraitsStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | final public function bar() {} 16 | final public static function baz() {} 17 | 18 | // Also valid (out of scope): 19 | protected abstract function overload() {} 20 | private function okay() {} 21 | } 22 | ]]> 23 | 24 | 25 | public function bar() {} 28 | protected static function baz() {} 29 | } 30 | ]]> 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Universal/Docs/Lists/DisallowLongListSyntaxStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | [$a, $b] = $array; 15 | ]]> 16 | 17 | 18 | list($a, $b) = $array; 20 | ]]> 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Universal/Docs/Lists/DisallowShortListSyntaxStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | list($a, $b) = $array; 15 | ]]> 16 | 17 | 18 | [$a, $b] = $array; 20 | ]]> 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Universal/Docs/Namespaces/DisallowCurlyBraceSyntaxStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | ; 15 | 16 | // Code 17 | ]]> 18 | 19 | 20 | { 22 | // Code. 23 | } 24 | ]]> 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Universal/Docs/Namespaces/DisallowDeclarationWithoutNameStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | Vendor\Name { 15 | } 16 | ]]> 17 | 18 | 19 | { 21 | } 22 | ]]> 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Universal/Docs/Namespaces/EnforceCurlyBraceSyntaxStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | { 15 | // Code. 16 | } 17 | ]]> 18 | 19 | 20 | ; 22 | 23 | // Code 24 | ]]> 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Universal/Docs/Namespaces/OneDeclarationPerFileStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | namespace Vendor\Project\Sub\B { 23 | } 24 | ]]> 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Universal/Docs/NamingConventions/NoReservedKeywordParameterNamesStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Universal/Docs/OOStructures/AlphabeticExtendsImplementsStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | Bar, Foo 15 | { 16 | } 17 | ]]> 18 | 19 | 20 | Foo, Bar 22 | { 23 | } 24 | ]]> 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Universal/Docs/Operators/ConcatPositionStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 16 | 17 | . $b . 'text' 20 | . $c; 21 | ]]> 22 | 23 | 24 | . 26 | $b . 'text' 27 | . $c; 28 | ]]> 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Universal/Docs/Operators/DisallowLogicalAndOrStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 15 | 16 | && $var > 10) {} 18 | 19 | if (empty($var) || $var < 0) {} 20 | ]]> 21 | 22 | 23 | and $var > 10) {} 25 | 26 | if (empty($var) or $var < 0) {} 27 | ]]> 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Universal/Docs/Operators/DisallowShortTernaryStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | 15 | ? $a : 'default'; 17 | ]]> 18 | 19 | 20 | ?: 'default'; 22 | echo $a ? : 'default'; 23 | ]]> 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Universal/Docs/Operators/DisallowStandalonePostIncrementDecrementStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | 15 | ++$i; 17 | --$j; 18 | ]]> 19 | 20 | 21 | ++; 23 | $j--; 24 | ]]> 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | ++$i; 36 | ]]> 37 | 38 | 39 | --$i++++; 41 | ]]> 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Universal/Docs/Operators/StrictComparisonsStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | 15 | === 'text') {} 17 | 18 | if ($var !== true) {} 19 | ]]> 20 | 21 | 22 | == 'text') {} 24 | 25 | if ($var != true) {} 26 | ]]> 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Universal/Docs/Operators/TypeSeparatorSpacingStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 18 | 19 | |string $paramA, 22 | TypeA&TypeB $paramB, 23 | (TypeA&TypeB)|null $paramC 24 | ): int|false {} 25 | ]]> 26 | 27 | 28 | | string $paramA, 31 | TypeA & TypeB $paramB, 32 | ( TypeA&TypeB ) |null $paramC 33 | ): int 34 | | 35 | false {} 36 | ]]> 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Universal/Docs/PHP/LowercasePHPTagStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Universal/Docs/PHP/NoFQNTrueFalseNullStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | null; 15 | 16 | if ($something === false) {} 17 | ]]> 18 | 19 | 20 | \null; 22 | 23 | if ($something === \false) {} 24 | ]]> 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Universal/Docs/PHP/OneStatementInShortEchoTagStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 13 | 14 | 16 | ]]> 17 | 18 | 19 | $text; echo $moreText; ?> 21 | ]]> 22 | 23 | 24 | 25 | 26 | 28 | echo $text; 29 | echo $moreText; 30 | ?> 31 | ]]> 32 | 33 | 34 | $text; 36 | echo $moreText; 37 | ?> 38 | ]]> 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Universal/Docs/UseStatements/DisallowMixedGroupUseStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 26 | 27 | 28 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Universal/Docs/UseStatements/DisallowUseClassStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Universal/Docs/UseStatements/DisallowUseConstStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Universal/Docs/UseStatements/DisallowUseFunctionStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Universal/Docs/UseStatements/KeywordSpacingStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | function strpos; 15 | use const PHP_EOL as MY_EOL; 16 | ]]> 17 | 18 | 19 | function strpos; 21 | use 22 | const 23 | PHP_EOL 24 | as 25 | MY_EOL; 26 | ]]> 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Universal/Docs/UseStatements/LowercaseFunctionConstStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | function strpos; 15 | use const PHP_EOL; 16 | ]]> 17 | 18 | 19 | Function strpos; 21 | use CONST PHP_EOL; 22 | ]]> 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Universal/Docs/UseStatements/NoLeadingBackslashStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | use Package\ClassName; 15 | ]]> 16 | 17 | 18 | \Package\ClassName; 20 | ]]> 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Universal/Docs/UseStatements/NoUselessAliasesStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 15 | 16 | 21 | 22 | 23 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Universal/Docs/WhiteSpace/AnonClassKeywordSpacingStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | 15 | class($param) 17 | { 18 | public function __construct($p) {} 19 | }; 20 | ]]> 21 | 22 | 23 | class ($param) 25 | { 26 | public function __construct($p) {} 27 | }; 28 | ]]> 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Universal/Docs/WhiteSpace/CommaSpacingStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 19 | 20 | 21 | 22 | , $param2, $param3); 24 | 25 | function_call( 26 | $param1, 27 | $param2, 28 | $param3 29 | ); 30 | 31 | $array = array($item1, $item2, $item3); 32 | $array = [ 33 | $item1, 34 | $item2, 35 | ]; 36 | 37 | list(, $a, $b,,) = $array; 38 | list( 39 | , 40 | $a, 41 | $b, 42 | ) = $array; 43 | ]]> 44 | 45 | 46 | , $param2,$param3); 48 | 49 | function_call( 50 | $a 51 | ,$b 52 | ,$c 53 | ); 54 | 55 | $array = array($item1,$item2 , $item3); 56 | $array = [ 57 | $item1, 58 | $item2 , 59 | ]; 60 | 61 | list( ,$a, $b ,,) = $array; 62 | list( 63 | , 64 | $a, 65 | $b , 66 | ) = $array; 67 | ]]> 68 | 69 | 70 | 71 | 74 | 75 | 76 | 77 | , // Comment. 80 | $param2, /* Comment. */ 81 | ); 82 | ]]> 83 | 84 | 85 | , 89 | $param2 /* Comment. */, 90 | ); 91 | ]]> 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /Universal/Docs/WhiteSpace/DisallowInlineTabsStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | [space]= 'text'; 15 | $text[space][space]= 'more text'; 16 | ]]> 17 | 18 | 19 | [tab]= 'text'; 21 | $text[tab]= 'more text'; 22 | ]]> 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Universal/Docs/WhiteSpace/PrecisionAlignmentStandard.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | [space][space][space][space]$foo = 'bar'; 16 | 17 | [tab]$foo = 'bar'; 18 | ]]> 19 | 20 | 21 | [space][space]$foo = 'bar'; 24 | 25 | [tab][space]$foo = 'bar'; 26 | ]]> 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Universal/Helpers/DummyTokenizer.php: -------------------------------------------------------------------------------- 1 | eolChar = $eolChar; 37 | $this->config = $config; 38 | } 39 | 40 | /** 41 | * Creates an array of tokens when given some content. 42 | * 43 | * @param string $string The string to tokenize. 44 | * 45 | * @return array> 46 | */ 47 | protected function tokenize($string) 48 | { 49 | return []; 50 | } 51 | 52 | /** 53 | * Performs additional processing after main tokenizing. 54 | * 55 | * @return void 56 | */ 57 | protected function processAdditional() 58 | { 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Universal/Sniffs/Arrays/DisallowShortArraySyntaxSniff.php: -------------------------------------------------------------------------------- 1 | 46 | */ 47 | public function register() 48 | { 49 | return Collections::arrayOpenTokensBC(); 50 | } 51 | 52 | /** 53 | * Processes this test, when one of its tokens is encountered. 54 | * 55 | * @since 1.0.0 56 | * 57 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 58 | * @param int $stackPtr The position of the current token 59 | * in the stack passed in $tokens. 60 | * 61 | * @return void 62 | */ 63 | public function process(File $phpcsFile, $stackPtr) 64 | { 65 | $tokens = $phpcsFile->getTokens(); 66 | 67 | if ($tokens[$stackPtr]['code'] === \T_ARRAY) { 68 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'no'); 69 | return; 70 | } 71 | 72 | if (Arrays::isShortArray($phpcsFile, $stackPtr) === false) { 73 | // Square brackets, but not a short array. Probably short list or real square brackets. 74 | return; 75 | } 76 | 77 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes'); 78 | 79 | $error = 'Short array syntax is not allowed'; 80 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Found'); 81 | 82 | if ($fix === true) { 83 | $phpcsFile->fixer->beginChangeset(); 84 | $phpcsFile->fixer->replaceToken($tokens[$stackPtr]['bracket_opener'], 'array('); 85 | $phpcsFile->fixer->replaceToken($tokens[$stackPtr]['bracket_closer'], ')'); 86 | $phpcsFile->fixer->endChangeset(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Universal/Sniffs/Arrays/MixedArrayKeyTypesSniff.php: -------------------------------------------------------------------------------- 1 | seenStringKey = false; 53 | $this->seenNumericKey = false; 54 | 55 | parent::processArray($phpcsFile); 56 | } 57 | 58 | /** 59 | * Process the tokens in an array key. 60 | * 61 | * @since 1.0.0 62 | * 63 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the 64 | * token was found. 65 | * @param int $startPtr The stack pointer to the first token in the "key" part of 66 | * an array item. 67 | * @param int $endPtr The stack pointer to the last token in the "key" part of 68 | * an array item. 69 | * @param int $itemNr Which item in the array is being handled. 70 | * 71 | * @return true|void Returning `TRUE` will short-circuit the array item loop and stop processing. 72 | * In effect, this means that the sniff will not examine the double arrow, the array 73 | * value or comma for this array item and will not process any array items after this one. 74 | */ 75 | public function processKey(File $phpcsFile, $startPtr, $endPtr, $itemNr) 76 | { 77 | $key = $this->getActualArrayKey($phpcsFile, $startPtr, $endPtr); 78 | if (isset($key) === false) { 79 | // Key could not be determined. 80 | return; 81 | } 82 | 83 | $integerKey = \is_int($key); 84 | 85 | // Handle integer key. 86 | if ($integerKey === true) { 87 | if ($this->seenStringKey === false) { 88 | if ($this->seenNumericKey !== false) { 89 | // Already seen a numeric key before. 90 | return; 91 | } 92 | 93 | $this->seenNumericKey = true; 94 | return; 95 | } 96 | 97 | // Ok, so we've seen a string key before and now see an explicit numeric key. 98 | $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true); 99 | $phpcsFile->addError( 100 | 'Arrays should have either numeric keys or string keys. Explicit numeric key detected,' 101 | . ' while all previous keys in this array were string keys.', 102 | $firstNonEmpty, 103 | 'ExplicitNumericKey' 104 | ); 105 | 106 | // Stop the loop. 107 | return true; 108 | } 109 | 110 | // Handle string key. 111 | if ($this->seenNumericKey === false) { 112 | if ($this->seenStringKey !== false) { 113 | // Already seen a string key before. 114 | return; 115 | } 116 | 117 | $this->seenStringKey = true; 118 | return; 119 | } 120 | 121 | // Ok, so we've seen a numeric key before and now see a string key. 122 | $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true); 123 | $phpcsFile->addError( 124 | 'Arrays should have either numeric keys or string keys. String key detected,' 125 | . ' while all previous keys in this array were integer based keys.', 126 | $firstNonEmpty, 127 | 'StringKey' 128 | ); 129 | 130 | // Stop the loop. 131 | return true; 132 | } 133 | 134 | /** 135 | * Process an array item without an array key. 136 | * 137 | * @since 1.0.0 138 | * 139 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the 140 | * token was found. 141 | * @param int $startPtr The stack pointer to the first token in the array item, 142 | * which in this case will be the first token of the array 143 | * value part of the array item. 144 | * @param int $itemNr Which item in the array is being handled. 145 | * 146 | * @return true|void Returning `TRUE` will short-circuit the array item loop and stop processing. 147 | * In effect, this means that the sniff will not examine the array value or 148 | * comma for this array item and will not process any array items after this one. 149 | */ 150 | public function processNoKey(File $phpcsFile, $startPtr, $itemNr) 151 | { 152 | if ($this->seenStringKey === false) { 153 | if ($this->seenNumericKey !== false) { 154 | // Already seen a numeric key before. 155 | return; 156 | } 157 | 158 | $this->seenNumericKey = true; 159 | return; 160 | } 161 | 162 | // Ok, so we've seen a string key before and now see an implicit numeric key. 163 | $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true); 164 | $phpcsFile->addError( 165 | 'Arrays should have either numeric keys or string keys. Implicit numeric key detected,' 166 | . ' while all previous keys in this array were string keys.', 167 | $firstNonEmpty, 168 | 'ImplicitNumericKey' 169 | ); 170 | 171 | // Stop the loop. 172 | return true; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /Universal/Sniffs/Arrays/MixedKeyedUnkeyedArraySniff.php: -------------------------------------------------------------------------------- 1 | Key is the item number; value the stack point to the first non empty token in the item. 40 | */ 41 | private $itemsWithoutKey = []; 42 | 43 | /** 44 | * Process the array declaration. 45 | * 46 | * @since 1.0.0 47 | * 48 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the 49 | * token was found. 50 | * 51 | * @return void 52 | */ 53 | public function processArray(File $phpcsFile) 54 | { 55 | // Reset properties before processing this array. 56 | $this->hasKeys = false; 57 | $this->itemsWithoutKey = []; 58 | 59 | parent::processArray($phpcsFile); 60 | } 61 | 62 | /** 63 | * Process the tokens in an array key. 64 | * 65 | * @since 1.0.0 66 | * 67 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the 68 | * token was found. 69 | * @param int $startPtr The stack pointer to the first token in the "key" part of 70 | * an array item. 71 | * @param int $endPtr The stack pointer to the last token in the "key" part of 72 | * an array item. 73 | * @param int $itemNr Which item in the array is being handled. 74 | * 75 | * @return void 76 | */ 77 | public function processKey(File $phpcsFile, $startPtr, $endPtr, $itemNr) 78 | { 79 | $this->hasKeys = true; 80 | 81 | // Process any previously encountered items without keys. 82 | if (empty($this->itemsWithoutKey) === false) { 83 | foreach ($this->itemsWithoutKey as $itemNr => $stackPtr) { 84 | $phpcsFile->addError( 85 | 'Inconsistent array detected. A mix of keyed and unkeyed array items is not allowed.' 86 | . ' The array item in position %d does not have an array key.', 87 | $stackPtr, 88 | 'Found', 89 | [$itemNr] 90 | ); 91 | } 92 | 93 | // No need to do this again. 94 | $this->itemsWithoutKey = []; 95 | } 96 | } 97 | 98 | /** 99 | * Process an array item without an array key. 100 | * 101 | * @since 1.0.0 102 | * 103 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the 104 | * token was found. 105 | * @param int $startPtr The stack pointer to the first token in the array item, 106 | * which in this case will be the first token of the array 107 | * value part of the array item. 108 | * @param int $itemNr Which item in the array is being handled. 109 | * 110 | * @return void 111 | */ 112 | public function processNoKey(File $phpcsFile, $startPtr, $itemNr) 113 | { 114 | $firstNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, $startPtr, null, true); 115 | if ($firstNonEmpty === false || $this->tokens[$firstNonEmpty]['code'] === \T_COMMA) { 116 | // Shouldn't really be possible, but this must be a parse error (empty array item). 117 | return; 118 | } 119 | 120 | // If we already know there are keys in the array, throw an error message straight away. 121 | if ($this->hasKeys === true) { 122 | $phpcsFile->addError( 123 | 'Inconsistent array detected. A mix of keyed and unkeyed array items is not allowed.' 124 | . ' The array item in position %d does not have an array key.', 125 | $firstNonEmpty, 126 | 'Found', 127 | [$itemNr] 128 | ); 129 | } else { 130 | // Save the array item info for later in case we do encounter an array key later on in the array. 131 | $this->itemsWithoutKey[$itemNr] = $firstNonEmpty; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Universal/Sniffs/Classes/DisallowAnonClassParenthesesSniff.php: -------------------------------------------------------------------------------- 1 | 41 | */ 42 | public function register() 43 | { 44 | return [\T_ANON_CLASS]; 45 | } 46 | 47 | /** 48 | * Processes this test, when one of its tokens is encountered. 49 | * 50 | * @since 1.0.0 51 | * 52 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 53 | * @param int $stackPtr The position of the current token 54 | * in the stack passed in $tokens. 55 | * 56 | * @return void 57 | */ 58 | public function process(File $phpcsFile, $stackPtr) 59 | { 60 | $tokens = $phpcsFile->getTokens(); 61 | $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); 62 | 63 | // Note: no need to check for `false` as PHPCS won't retokenize `class` to `T_ANON_CLASS` in that case. 64 | if ($tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS) { 65 | // No parentheses found. 66 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'no'); 67 | return; 68 | } 69 | 70 | if (isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false) { 71 | /* 72 | * Incomplete set of parentheses. Ignore. 73 | * Shouldn't be possible as PHPCS won't retokenize `class` to `T_ANON_CLASS` in that case. 74 | */ 75 | // @codeCoverageIgnoreStart 76 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes'); 77 | return; 78 | // @codeCoverageIgnoreEnd 79 | } 80 | 81 | $opener = $nextNonEmpty; 82 | $closer = $tokens[$opener]['parenthesis_closer']; 83 | $hasParams = $phpcsFile->findNext(Tokens::$emptyTokens, ($opener + 1), $closer, true); 84 | if ($hasParams !== false) { 85 | // There is something between the parentheses. Ignore. 86 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes, with parameter(s)'); 87 | return; 88 | } 89 | 90 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes'); 91 | 92 | $fix = $phpcsFile->addFixableError( 93 | 'Parenthesis not allowed when creating a new anonymous class without passing parameters', 94 | $stackPtr, 95 | 'Found' 96 | ); 97 | 98 | if ($fix === true) { 99 | $phpcsFile->fixer->beginChangeset(); 100 | 101 | for ($i = $opener; $i <= $closer; $i++) { 102 | if (isset(Tokens::$commentTokens[$tokens[$i]['code']]) === true) { 103 | continue; 104 | } 105 | 106 | $phpcsFile->fixer->replaceToken($i, ''); 107 | } 108 | 109 | $phpcsFile->fixer->endChangeset(); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Universal/Sniffs/Classes/DisallowFinalClassSniff.php: -------------------------------------------------------------------------------- 1 | 42 | */ 43 | public function register() 44 | { 45 | return [\T_CLASS]; 46 | } 47 | 48 | /** 49 | * Processes this test, when one of its tokens is encountered. 50 | * 51 | * @since 1.0.0 52 | * 53 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 54 | * @param int $stackPtr The position of the current token 55 | * in the stack passed in $tokens. 56 | * 57 | * @return void 58 | */ 59 | public function process(File $phpcsFile, $stackPtr) 60 | { 61 | $classProp = ObjectDeclarations::getClassProperties($phpcsFile, $stackPtr); 62 | if ($classProp['is_final'] === false) { 63 | if ($classProp['is_abstract'] === true) { 64 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'abstract'); 65 | return; 66 | } 67 | 68 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'not abstract, not final'); 69 | return; 70 | } 71 | 72 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'final'); 73 | 74 | $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); 75 | if ($nextNonEmpty === false) { 76 | // Live coding or parse error. 77 | return; 78 | } 79 | 80 | // No extra safeguards needed, we know the keyword will exist based on the check above. 81 | $finalKeyword = $phpcsFile->findPrevious(\T_FINAL, ($stackPtr - 1)); 82 | $snippetEnd = $nextNonEmpty; 83 | $classCloser = ''; 84 | 85 | $tokens = $phpcsFile->getTokens(); 86 | if (isset($tokens[$stackPtr]['scope_opener']) === true) { 87 | $snippetEnd = $tokens[$stackPtr]['scope_opener']; 88 | $classCloser = '}'; 89 | } 90 | 91 | $snippet = GetTokensAsString::compact($phpcsFile, $finalKeyword, $snippetEnd, true); 92 | $fix = $phpcsFile->addFixableError( 93 | 'Declaring a class as final is not allowed. Found: %s%s', 94 | $finalKeyword, 95 | 'FinalClassFound', 96 | [$snippet, $classCloser] 97 | ); 98 | 99 | if ($fix === true) { 100 | $phpcsFile->fixer->beginChangeset(); 101 | $phpcsFile->fixer->replaceToken($finalKeyword, ''); 102 | 103 | // Remove redundant whitespace. 104 | for ($i = ($finalKeyword + 1); $i < $stackPtr; $i++) { 105 | if ($tokens[$i]['code'] === \T_WHITESPACE) { 106 | $phpcsFile->fixer->replaceToken($i, ''); 107 | continue; 108 | } 109 | 110 | break; 111 | } 112 | 113 | $phpcsFile->fixer->endChangeset(); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Universal/Sniffs/Classes/ModifierKeywordOrderSniff.php: -------------------------------------------------------------------------------- 1 | 73 | */ 74 | public function register() 75 | { 76 | return [\T_CLASS]; 77 | } 78 | 79 | /** 80 | * Processes this test, when one of its tokens is encountered. 81 | * 82 | * @since 1.0.0 83 | * 84 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 85 | * @param int $stackPtr The position of the current token 86 | * in the stack passed in $tokens. 87 | * 88 | * @return void 89 | */ 90 | public function process(File $phpcsFile, $stackPtr) 91 | { 92 | $classProp = ObjectDeclarations::getClassProperties($phpcsFile, $stackPtr); 93 | 94 | if ($classProp['readonly_token'] === false 95 | || ($classProp['final_token'] === false && $classProp['abstract_token'] === false) 96 | ) { 97 | /* 98 | * Either no modifier keywords found at all; or only one type of modifier 99 | * keyword (abstract/final or readonly) declared, but not both. No ordering needed. 100 | */ 101 | return; 102 | } 103 | 104 | if ($classProp['final_token'] !== false && $classProp['abstract_token'] !== false) { 105 | // Parse error. Ignore. 106 | return; 107 | } 108 | 109 | $readonly = $classProp['readonly_token']; 110 | 111 | if ($classProp['final_token'] !== false) { 112 | $extendability = $classProp['final_token']; 113 | } else { 114 | $extendability = $classProp['abstract_token']; 115 | } 116 | 117 | if ($readonly < $extendability) { 118 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, self::READONLY_EXTEND); 119 | } else { 120 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, self::EXTEND_READONLY); 121 | } 122 | 123 | $message = 'Class modifier keywords are not in the correct order. Expected: "%s", found: "%s"'; 124 | 125 | switch ($this->order) { 126 | case self::READONLY_EXTEND: 127 | if ($readonly < $extendability) { 128 | // Order is correct. Nothing to do. 129 | return; 130 | } 131 | 132 | $this->handleError($phpcsFile, $extendability, $readonly); 133 | break; 134 | 135 | case self::EXTEND_READONLY: 136 | default: 137 | if ($extendability < $readonly) { 138 | // Order is correct. Nothing to do. 139 | return; 140 | } 141 | 142 | $this->handleError($phpcsFile, $readonly, $extendability); 143 | break; 144 | } 145 | } 146 | 147 | /** 148 | * Throw the error and potentially fix it. 149 | * 150 | * @since 1.0.0 151 | * 152 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 153 | * @param int $firstKeyword The position of the first keyword found. 154 | * @param int $secondKeyword The position of the second keyword token. 155 | * 156 | * @return void 157 | */ 158 | private function handleError(File $phpcsFile, $firstKeyword, $secondKeyword) 159 | { 160 | $tokens = $phpcsFile->getTokens(); 161 | 162 | $message = 'Class modifier keywords are not in the correct order. Expected: "%s", found: "%s"'; 163 | $data = [ 164 | $tokens[$secondKeyword]['content'] . ' ' . $tokens[$firstKeyword]['content'], 165 | $tokens[$firstKeyword]['content'] . ' ' . $tokens[$secondKeyword]['content'], 166 | ]; 167 | 168 | $fix = $phpcsFile->addFixableError($message, $firstKeyword, 'Incorrect', $data); 169 | 170 | if ($fix === true) { 171 | $phpcsFile->fixer->beginChangeset(); 172 | 173 | $phpcsFile->fixer->replaceToken($secondKeyword, ''); 174 | 175 | // Prevent leaving behind trailing whitespace. 176 | $i = ($secondKeyword + 1); 177 | while ($tokens[$i]['code'] === \T_WHITESPACE) { 178 | $phpcsFile->fixer->replaceToken($i, ''); 179 | ++$i; 180 | } 181 | 182 | // Use the original token content as the case used for keywords is not the concern of this sniff. 183 | $phpcsFile->fixer->addContentBefore($firstKeyword, $tokens[$secondKeyword]['content'] . ' '); 184 | 185 | $phpcsFile->fixer->endChangeset(); 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /Universal/Sniffs/Classes/RequireAnonClassParenthesesSniff.php: -------------------------------------------------------------------------------- 1 | 40 | */ 41 | public function register() 42 | { 43 | return [\T_ANON_CLASS]; 44 | } 45 | 46 | /** 47 | * Processes this test, when one of its tokens is encountered. 48 | * 49 | * @since 1.0.0 50 | * 51 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 52 | * @param int $stackPtr The position of the current token 53 | * in the stack passed in $tokens. 54 | * 55 | * @return void 56 | */ 57 | public function process(File $phpcsFile, $stackPtr) 58 | { 59 | $tokens = $phpcsFile->getTokens(); 60 | $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); 61 | 62 | // Note: no need to check for `false` as PHPCS won't retokenize `class` to `T_ANON_CLASS` in that case. 63 | if ($tokens[$nextNonEmpty]['code'] === \T_OPEN_PARENTHESIS) { 64 | // Parentheses found. 65 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes'); 66 | return; 67 | } 68 | 69 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'no'); 70 | 71 | $fix = $phpcsFile->addFixableError( 72 | 'Parenthesis required when creating a new anonymous class.', 73 | $stackPtr, 74 | 'Missing' 75 | ); 76 | 77 | if ($fix === true) { 78 | $phpcsFile->fixer->addContent($stackPtr, '()'); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Universal/Sniffs/Classes/RequireFinalClassSniff.php: -------------------------------------------------------------------------------- 1 | 42 | */ 43 | public function register() 44 | { 45 | return [\T_CLASS]; 46 | } 47 | 48 | /** 49 | * Processes this test, when one of its tokens is encountered. 50 | * 51 | * @since 1.0.0 52 | * 53 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 54 | * @param int $stackPtr The position of the current token 55 | * in the stack passed in $tokens. 56 | * 57 | * @return void 58 | */ 59 | public function process(File $phpcsFile, $stackPtr) 60 | { 61 | $classProp = ObjectDeclarations::getClassProperties($phpcsFile, $stackPtr); 62 | if ($classProp['is_final'] === true) { 63 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'final'); 64 | return; 65 | } 66 | 67 | if ($classProp['is_abstract'] === true) { 68 | // Abstract classes can't be final. 69 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'abstract'); 70 | return; 71 | } 72 | 73 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'not abstract, not final'); 74 | 75 | $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); 76 | if ($nextNonEmpty === false) { 77 | // Live coding or parse error. 78 | return; 79 | } 80 | 81 | $snippetEnd = $nextNonEmpty; 82 | $classCloser = ''; 83 | 84 | $tokens = $phpcsFile->getTokens(); 85 | if (isset($tokens[$stackPtr]['scope_opener']) === true) { 86 | $snippetEnd = $tokens[$stackPtr]['scope_opener']; 87 | $classCloser = '}'; 88 | } 89 | 90 | $snippet = GetTokensAsString::compact($phpcsFile, $stackPtr, $snippetEnd, true); 91 | $fix = $phpcsFile->addFixableError( 92 | 'A non-abstract class should be declared as final. Found: %s%s', 93 | $stackPtr, 94 | 'NonFinalClassFound', 95 | [$snippet, $classCloser] 96 | ); 97 | 98 | if ($fix === true) { 99 | $phpcsFile->fixer->addContentBefore($stackPtr, 'final '); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Universal/Sniffs/CodeAnalysis/ForeachUniqueAssignmentSniff.php: -------------------------------------------------------------------------------- 1 | 37 | */ 38 | public function register() 39 | { 40 | return [\T_FOREACH]; 41 | } 42 | 43 | /** 44 | * Processes this test, when one of its tokens is encountered. 45 | * 46 | * @since 1.0.0 47 | * 48 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 49 | * @param int $stackPtr The position of the current token 50 | * in the stack passed in $tokens. 51 | * 52 | * @return void 53 | */ 54 | public function process(File $phpcsFile, $stackPtr) 55 | { 56 | $tokens = $phpcsFile->getTokens(); 57 | 58 | if (isset($tokens[$stackPtr]['parenthesis_opener'], $tokens[$stackPtr]['parenthesis_closer']) === false) { 59 | // Parse error or live coding, not our concern. 60 | return; 61 | } 62 | 63 | $opener = $tokens[$stackPtr]['parenthesis_opener']; 64 | $closer = $tokens[$stackPtr]['parenthesis_closer']; 65 | 66 | $asPtr = $phpcsFile->findNext(\T_AS, ($opener + 1), $closer); 67 | if ($asPtr === false) { 68 | // Parse error or live coding, not our concern. 69 | return; 70 | } 71 | 72 | // Real target. 73 | $find = [\T_DOUBLE_ARROW]; 74 | // Prevent matching on double arrows within a list assignment. 75 | $find += Collections::listTokens(); 76 | 77 | $doubleArrowPtr = $phpcsFile->findNext($find, ($asPtr + 1), $closer); 78 | if ($doubleArrowPtr === false 79 | || $tokens[$doubleArrowPtr]['code'] !== \T_DOUBLE_ARROW 80 | ) { 81 | // No key assignment. 82 | return; 83 | } 84 | 85 | $isListAssignment = $phpcsFile->findNext(Tokens::$emptyTokens, ($doubleArrowPtr + 1), $closer, true); 86 | if ($isListAssignment === false) { 87 | // Parse error or live coding, not our concern. 88 | } 89 | 90 | $keyAsString = \ltrim(GetTokensAsString::noEmpties($phpcsFile, ($asPtr + 1), ($doubleArrowPtr - 1)), '&'); 91 | $valueAssignments = []; 92 | if (isset(Collections::listTokens()[$tokens[$isListAssignment]['code']]) === false) { 93 | // Single value assignment. 94 | $valueAssignments[] = GetTokensAsString::noEmpties($phpcsFile, ($doubleArrowPtr + 1), ($closer - 1)); 95 | } else { 96 | // List assignment. 97 | $assignments = Lists::getAssignments($phpcsFile, $isListAssignment); 98 | foreach ($assignments as $listItem) { 99 | if ($listItem['assignment'] === '') { 100 | // Ignore empty list assignments. 101 | continue; 102 | } 103 | 104 | // Note: this doesn't take nested lists into account (yet). 105 | $valueAssignments[] = $listItem['assignment']; 106 | } 107 | } 108 | 109 | if (empty($valueAssignments)) { 110 | // No assignments found. 111 | return; 112 | } 113 | 114 | foreach ($valueAssignments as $valueAsString) { 115 | $valueAsString = \ltrim($valueAsString, '&'); 116 | 117 | if ($keyAsString !== $valueAsString) { 118 | // Key and value not the same. 119 | continue; 120 | } 121 | 122 | $error = 'The variables used for the key and the value in a foreach assignment should be unique.'; 123 | $error .= 'Both the key and the value will currently be assigned to: "%s"'; 124 | 125 | $fix = $phpcsFile->addFixableError($error, $doubleArrowPtr, 'NotUnique', [$valueAsString]); 126 | if ($fix === true) { 127 | $phpcsFile->fixer->beginChangeset(); 128 | 129 | // Remove the key. 130 | for ($i = ($asPtr + 1); $i < ($doubleArrowPtr + 1); $i++) { 131 | if ($tokens[$i]['code'] === \T_WHITESPACE 132 | && isset(Tokens::$commentTokens[$tokens[($i + 1)]['code']]) 133 | ) { 134 | // Don't remove whitespace when followed directly by a comment. 135 | continue; 136 | } 137 | 138 | if (isset(Tokens::$commentTokens[$tokens[$i]['code']])) { 139 | // Don't remove comments. 140 | continue; 141 | } 142 | 143 | // Remove everything else. 144 | $phpcsFile->fixer->replaceToken($i, ''); 145 | } 146 | 147 | $phpcsFile->fixer->endChangeset(); 148 | } 149 | 150 | break; 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /Universal/Sniffs/CodeAnalysis/NoEchoSprintfSniff.php: -------------------------------------------------------------------------------- 1 | 36 | */ 37 | private $targetFunctions = [ 38 | 'sprintf' => 'printf', 39 | 'vsprintf' => 'vprintf', 40 | ]; 41 | 42 | /** 43 | * Returns an array of tokens this test wants to listen for. 44 | * 45 | * @since 1.1.0 46 | * 47 | * @return array 48 | */ 49 | public function register() 50 | { 51 | return [\T_ECHO]; 52 | } 53 | 54 | /** 55 | * Processes this test, when one of its tokens is encountered. 56 | * 57 | * @since 1.1.0 58 | * 59 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 60 | * @param int $stackPtr The position of the current token 61 | * in the stack passed in $tokens. 62 | * 63 | * @return void 64 | */ 65 | public function process(File $phpcsFile, $stackPtr) 66 | { 67 | $tokens = $phpcsFile->getTokens(); 68 | 69 | $skip = Tokens::$emptyTokens; 70 | $skip[] = \T_NS_SEPARATOR; 71 | 72 | $next = $phpcsFile->findNext($skip, ($stackPtr + 1), null, true); 73 | if ($next === false 74 | || $tokens[$next]['code'] !== \T_STRING 75 | || isset($this->targetFunctions[\strtolower($tokens[$next]['content'])]) === false 76 | ) { 77 | // Not our target. 78 | return; 79 | } 80 | 81 | $detectedFunction = \strtolower($tokens[$next]['content']); 82 | 83 | $openParens = $phpcsFile->findNext(Tokens::$emptyTokens, ($next + 1), null, true); 84 | if ($openParens === false 85 | || $tokens[$openParens]['code'] !== \T_OPEN_PARENTHESIS 86 | || isset($tokens[$openParens]['parenthesis_closer']) === false 87 | ) { 88 | // Live coding/parse error. 89 | return; 90 | } 91 | 92 | $closeParens = $tokens[$openParens]['parenthesis_closer']; 93 | $afterFunctionCall = $phpcsFile->findNext(Tokens::$emptyTokens, ($closeParens + 1), null, true); 94 | if ($afterFunctionCall === false 95 | || ($tokens[$afterFunctionCall]['code'] !== \T_SEMICOLON 96 | && $tokens[$afterFunctionCall]['code'] !== \T_CLOSE_TAG) 97 | ) { 98 | // Live coding/parse error or compound echo statement. 99 | return; 100 | } 101 | 102 | $fix = $phpcsFile->addFixableError( 103 | 'Unnecessary "echo %s(...)" found. Use "%s(...)" instead.', 104 | $next, 105 | 'Found', 106 | [ 107 | $tokens[$next]['content'], 108 | $this->targetFunctions[$detectedFunction], 109 | ] 110 | ); 111 | 112 | if ($fix === true) { 113 | $phpcsFile->fixer->beginChangeset(); 114 | 115 | // Remove echo and whitespace. 116 | $phpcsFile->fixer->replaceToken($stackPtr, ''); 117 | 118 | for ($i = ($stackPtr + 1); $i < $next; $i++) { 119 | if ($tokens[$i]['code'] !== \T_WHITESPACE) { 120 | break; 121 | } 122 | 123 | $phpcsFile->fixer->replaceToken($i, ''); 124 | } 125 | 126 | $phpcsFile->fixer->replaceToken($next, $this->targetFunctions[$detectedFunction]); 127 | 128 | $phpcsFile->fixer->endChangeset(); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Universal/Sniffs/Constants/LowercaseClassResolutionKeywordSniff.php: -------------------------------------------------------------------------------- 1 | 43 | */ 44 | public function register() 45 | { 46 | return [\T_STRING]; 47 | } 48 | 49 | /** 50 | * Processes this test, when one of its tokens is encountered. 51 | * 52 | * @since 1.0.0 53 | * 54 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 55 | * @param int $stackPtr The position of the current token 56 | * in the stack passed in $tokens. 57 | * 58 | * @return void 59 | */ 60 | public function process(File $phpcsFile, $stackPtr) 61 | { 62 | $tokens = $phpcsFile->getTokens(); 63 | $content = $tokens[$stackPtr]['content']; 64 | $contentLC = \strtolower($content); 65 | 66 | if ($contentLC !== 'class') { 67 | return; 68 | } 69 | 70 | $nextToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); 71 | if ($nextToken !== false && $tokens[$nextToken]['code'] === \T_OPEN_PARENTHESIS) { 72 | // Function call or declaration for a function called "class". 73 | return; 74 | } 75 | 76 | $prevToken = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); 77 | if ($prevToken === false || $tokens[$prevToken]['code'] !== \T_DOUBLE_COLON) { 78 | return; 79 | } 80 | 81 | if ($contentLC === $content) { 82 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'lowercase'); 83 | return; 84 | } 85 | 86 | $error = "The ::class keyword for class name resolution must be in lowercase. Expected: '::%s'; found: '::%s'"; 87 | $data = [ 88 | $contentLC, 89 | $content, 90 | ]; 91 | 92 | $errorCode = ''; 93 | if (\strtoupper($content) === $content) { 94 | $errorCode = 'Uppercase'; 95 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'uppercase'); 96 | } else { 97 | $errorCode = 'Mixedcase'; 98 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'mixed case'); 99 | } 100 | 101 | $fix = $phpcsFile->addFixableError($error, $stackPtr, $errorCode, $data); 102 | if ($fix === true) { 103 | $phpcsFile->fixer->replaceToken($stackPtr, $contentLC); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Universal/Sniffs/Constants/ModifierKeywordOrderSniff.php: -------------------------------------------------------------------------------- 1 | 75 | */ 76 | public function register() 77 | { 78 | return [\T_CONST]; 79 | } 80 | 81 | /** 82 | * Processes this test, when one of its tokens is encountered. 83 | * 84 | * @since 1.0.0 85 | * 86 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 87 | * @param int $stackPtr The position of the current token 88 | * in the stack passed in $tokens. 89 | * 90 | * @return void 91 | */ 92 | public function process(File $phpcsFile, $stackPtr) 93 | { 94 | if (Scopes::isOOConstant($phpcsFile, $stackPtr) === false) { 95 | return; 96 | } 97 | 98 | $tokens = $phpcsFile->getTokens(); 99 | $valid = Collections::constantModifierKeywords() + Tokens::$emptyTokens; 100 | 101 | $finalPtr = false; 102 | $visibilityPtr = false; 103 | 104 | for ($i = ($stackPtr - 1); $i > 0; $i--) { 105 | if (isset($valid[$tokens[$i]['code']]) === false) { 106 | break; 107 | } 108 | 109 | if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) { 110 | continue; 111 | } 112 | 113 | if ($tokens[$i]['code'] === \T_FINAL) { 114 | $finalPtr = $i; 115 | } else { 116 | $visibilityPtr = $i; 117 | } 118 | } 119 | 120 | if ($finalPtr === false || $visibilityPtr === false) { 121 | /* 122 | * Either no modifier keywords found at all; or only one type of modifier 123 | * keyword (final or visibility) declared, but not both. No ordering needed. 124 | */ 125 | return; 126 | } 127 | 128 | if ($visibilityPtr < $finalPtr) { 129 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, self::VISIBILITY_FINAL); 130 | } else { 131 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, self::FINAL_VISIBILITY); 132 | } 133 | 134 | $message = 'OO constant modifier keywords are not in the correct order. Expected: "%s", found: "%s"'; 135 | 136 | switch ($this->order) { 137 | case self::VISIBILITY_FINAL: 138 | if ($visibilityPtr < $finalPtr) { 139 | // Order is correct. Nothing to do. 140 | return; 141 | } 142 | 143 | $this->handleError($phpcsFile, $finalPtr, $visibilityPtr); 144 | break; 145 | 146 | case self::FINAL_VISIBILITY: 147 | default: 148 | if ($finalPtr < $visibilityPtr) { 149 | // Order is correct. Nothing to do. 150 | return; 151 | } 152 | 153 | $this->handleError($phpcsFile, $visibilityPtr, $finalPtr); 154 | break; 155 | } 156 | } 157 | 158 | /** 159 | * Throw the error and potentially fix it. 160 | * 161 | * @since 1.0.0 162 | * 163 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 164 | * @param int $firstKeyword The position of the first keyword found. 165 | * @param int $secondKeyword The position of the second keyword token. 166 | * 167 | * @return void 168 | */ 169 | private function handleError(File $phpcsFile, $firstKeyword, $secondKeyword) 170 | { 171 | $tokens = $phpcsFile->getTokens(); 172 | 173 | $message = 'Constant modifier keywords are not in the correct order. Expected: "%s", found: "%s"'; 174 | $data = [ 175 | $tokens[$secondKeyword]['content'] . ' ' . $tokens[$firstKeyword]['content'], 176 | $tokens[$firstKeyword]['content'] . ' ' . $tokens[$secondKeyword]['content'], 177 | ]; 178 | 179 | $fix = $phpcsFile->addFixableError($message, $firstKeyword, 'Incorrect', $data); 180 | 181 | if ($fix === true) { 182 | $phpcsFile->fixer->beginChangeset(); 183 | 184 | $phpcsFile->fixer->replaceToken($secondKeyword, ''); 185 | 186 | // Prevent leaving behind trailing whitespace. 187 | $i = ($secondKeyword + 1); 188 | while ($tokens[$i]['code'] === \T_WHITESPACE) { 189 | $phpcsFile->fixer->replaceToken($i, ''); 190 | ++$i; 191 | } 192 | 193 | // Use the original token content as the case used for keywords is not the concern of this sniff. 194 | $phpcsFile->fixer->addContentBefore($firstKeyword, $tokens[$secondKeyword]['content'] . ' '); 195 | 196 | $phpcsFile->fixer->endChangeset(); 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /Universal/Sniffs/Constants/UppercaseMagicConstantsSniff.php: -------------------------------------------------------------------------------- 1 | 42 | */ 43 | public function register() 44 | { 45 | return Tokens::$magicConstants; 46 | } 47 | 48 | /** 49 | * Processes this test, when one of its tokens is encountered. 50 | * 51 | * @since 1.0.0 52 | * 53 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 54 | * @param int $stackPtr The position of the current token 55 | * in the stack passed in $tokens. 56 | * 57 | * @return void 58 | */ 59 | public function process(File $phpcsFile, $stackPtr) 60 | { 61 | $tokens = $phpcsFile->getTokens(); 62 | $content = $tokens[$stackPtr]['content']; 63 | $contentUC = \strtoupper($content); 64 | if ($contentUC === $content) { 65 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'uppercase'); 66 | return; 67 | } 68 | 69 | $error = 'Magic constants should be in uppercase. Expected: %s; found: %s'; 70 | $errorCode = ''; 71 | $data = [ 72 | $contentUC, 73 | $content, 74 | ]; 75 | 76 | if (\strtolower($content) === $content) { 77 | $errorCode = 'Lowercase'; 78 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'lowercase'); 79 | } else { 80 | $errorCode = 'Mixedcase'; 81 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'mixed case'); 82 | } 83 | 84 | $fix = $phpcsFile->addFixableError($error, $stackPtr, $errorCode, $data); 85 | if ($fix === true) { 86 | $phpcsFile->fixer->replaceToken($stackPtr, $contentUC); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Universal/Sniffs/ControlStructures/IfElseDeclarationSniff.php: -------------------------------------------------------------------------------- 1 | 49 | */ 50 | public function register() 51 | { 52 | return [ 53 | \T_ELSE, 54 | \T_ELSEIF, 55 | ]; 56 | } 57 | 58 | /** 59 | * Processes this test, when one of its tokens is encountered. 60 | * 61 | * @since 1.0.0 62 | * 63 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 64 | * @param int $stackPtr The position of the current token 65 | * in the stack passed in $tokens. 66 | * 67 | * @return void 68 | */ 69 | public function process(File $phpcsFile, $stackPtr) 70 | { 71 | $tokens = $phpcsFile->getTokens(); 72 | 73 | /* 74 | * Check for control structures without braces and alternative syntax. 75 | */ 76 | $scopePtr = $stackPtr; 77 | if (isset($tokens[$stackPtr]['scope_opener']) === false) { 78 | // Deal with "else if". 79 | $next = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); 80 | if ($tokens[$next]['code'] === \T_IF) { 81 | $scopePtr = $next; 82 | } 83 | } 84 | 85 | if (isset($tokens[$scopePtr]['scope_opener']) === false 86 | || $tokens[$tokens[$scopePtr]['scope_opener']]['code'] === \T_COLON 87 | ) { 88 | // No scope opener found or alternative syntax (not our concern). 89 | return; 90 | } 91 | 92 | /* 93 | * Check whether the else(if) is on a new line. 94 | */ 95 | $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); 96 | if ($prevNonEmpty === false || $tokens[$prevNonEmpty]['code'] !== \T_CLOSE_CURLY_BRACKET) { 97 | // Parse error or mixing braced and non-braced. Not our concern. 98 | return; 99 | } 100 | 101 | if ($tokens[$prevNonEmpty]['line'] !== $tokens[$stackPtr]['line']) { 102 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes'); 103 | return; 104 | } 105 | 106 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'no'); 107 | 108 | $errorBase = \strtoupper($tokens[$stackPtr]['content']); 109 | $error = $errorBase . ' statement must be on a new line.'; 110 | 111 | $prevNonWhitespace = $phpcsFile->findPrevious(\T_WHITESPACE, ($stackPtr - 1), null, true); 112 | 113 | if ($prevNonWhitespace !== $prevNonEmpty) { 114 | // Comment found between previous scope closer and the keyword. 115 | $fix = $phpcsFile->addError($error, $stackPtr, 'NoNewLine'); 116 | return; 117 | } 118 | 119 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoNewLine'); 120 | if ($fix === false) { 121 | return; 122 | } 123 | 124 | /* 125 | * Fix it. 126 | */ 127 | 128 | // Figure out the indentation for the else(if). 129 | $indentBase = $prevNonEmpty; 130 | if (isset($tokens[$prevNonEmpty]['scope_condition']) === true 131 | && ($tokens[$tokens[$prevNonEmpty]['scope_condition']]['column'] === 1 132 | || ($tokens[($tokens[$prevNonEmpty]['scope_condition'] - 1)]['code'] === \T_WHITESPACE 133 | && $tokens[($tokens[$prevNonEmpty]['scope_condition'] - 1)]['column'] === 1)) 134 | ) { 135 | // Base the indentation off the previous if/elseif if on a line by itself. 136 | $indentBase = $tokens[$prevNonEmpty]['scope_condition']; 137 | } 138 | 139 | $indent = ''; 140 | $firstOnIndentLine = $indentBase; 141 | if ($tokens[$firstOnIndentLine]['column'] !== 1) { 142 | while (isset($tokens[($firstOnIndentLine - 1)]) && $tokens[--$firstOnIndentLine]['column'] !== 1); 143 | 144 | if ($tokens[$firstOnIndentLine]['code'] === \T_WHITESPACE) { 145 | $indent = $tokens[$firstOnIndentLine]['content']; 146 | 147 | // If tabs were replaced, use the original content. 148 | if (isset($tokens[$firstOnIndentLine]['orig_content']) === true) { 149 | $indent = $tokens[$firstOnIndentLine]['orig_content']; 150 | } 151 | } 152 | } 153 | 154 | $phpcsFile->fixer->beginChangeset(); 155 | 156 | // Remove any whitespace between the previous scope closer and the else(if). 157 | for ($i = ($prevNonEmpty + 1); $i < $stackPtr; $i++) { 158 | $phpcsFile->fixer->replaceToken($i, ''); 159 | } 160 | 161 | $phpcsFile->fixer->addContent($prevNonEmpty, $phpcsFile->eolChar . $indent); 162 | $phpcsFile->fixer->endChangeset(); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /Universal/Sniffs/Files/SeparateFunctionsFromOOSniff.php: -------------------------------------------------------------------------------- 1 | 53 | */ 54 | private $search = [ 55 | // Some tokens to help skip over structures we're not interested in. 56 | \T_START_HEREDOC => \T_START_HEREDOC, 57 | \T_START_NOWDOC => \T_START_NOWDOC, 58 | ]; 59 | 60 | /** 61 | * Returns an array of tokens this test wants to listen for. 62 | * 63 | * @since 1.0.0 64 | * 65 | * @return array 66 | */ 67 | public function register() 68 | { 69 | $this->search += Tokens::$ooScopeTokens; 70 | $this->search += Collections::functionDeclarationTokens(); 71 | 72 | return Collections::phpOpenTags(); 73 | } 74 | 75 | /** 76 | * Processes this test, when one of its tokens is encountered. 77 | * 78 | * @since 1.0.0 79 | * 80 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 81 | * @param int $stackPtr The position of the current token 82 | * in the stack passed in $tokens. 83 | * 84 | * @return int Integer stack pointer to skip forward. 85 | */ 86 | public function process(File $phpcsFile, $stackPtr) 87 | { 88 | $tokens = $phpcsFile->getTokens(); 89 | 90 | $firstOO = null; 91 | $firstFunction = null; 92 | $functionCount = 0; 93 | $OOCount = 0; 94 | 95 | for ($i = 0; $i < $phpcsFile->numTokens; $i++) { 96 | // Ignore anything within square brackets. 97 | if ($tokens[$i]['code'] !== \T_OPEN_CURLY_BRACKET 98 | && isset($tokens[$i]['bracket_opener'], $tokens[$i]['bracket_closer']) 99 | && $i === $tokens[$i]['bracket_opener'] 100 | ) { 101 | $i = $tokens[$i]['bracket_closer']; 102 | continue; 103 | } 104 | 105 | // Skip past nested arrays, function calls and arbitrary groupings. 106 | if ($tokens[$i]['code'] === \T_OPEN_PARENTHESIS 107 | && isset($tokens[$i]['parenthesis_closer']) 108 | ) { 109 | $i = $tokens[$i]['parenthesis_closer']; 110 | continue; 111 | } 112 | 113 | // Skip over potentially large docblocks. 114 | if ($tokens[$i]['code'] === \T_DOC_COMMENT_OPEN_TAG 115 | && isset($tokens[$i]['comment_closer']) 116 | ) { 117 | $i = $tokens[$i]['comment_closer']; 118 | continue; 119 | } 120 | 121 | // Ignore everything else we're not interested in. 122 | if (isset($this->search[$tokens[$i]['code']]) === false) { 123 | continue; 124 | } 125 | 126 | // Skip over structures which won't contain anything we're interested in. 127 | if (($tokens[$i]['code'] === \T_START_HEREDOC 128 | || $tokens[$i]['code'] === \T_START_NOWDOC 129 | || $tokens[$i]['code'] === \T_ANON_CLASS 130 | || $tokens[$i]['code'] === \T_CLOSURE 131 | || $tokens[$i]['code'] === \T_FN) 132 | && isset($tokens[$i]['scope_condition'], $tokens[$i]['scope_closer']) 133 | && $tokens[$i]['scope_condition'] === $i 134 | ) { 135 | $i = $tokens[$i]['scope_closer']; 136 | continue; 137 | } 138 | 139 | // This will be either a function declaration or an OO declaration token. 140 | if ($tokens[$i]['code'] === \T_FUNCTION) { 141 | if (isset($firstFunction) === false) { 142 | $firstFunction = $i; 143 | } 144 | 145 | ++$functionCount; 146 | } else { 147 | if (isset($firstOO) === false) { 148 | $firstOO = $i; 149 | } 150 | 151 | ++$OOCount; 152 | } 153 | 154 | if (isset($tokens[$i]['scope_closer']) === true) { 155 | $i = $tokens[$i]['scope_closer']; 156 | } 157 | } 158 | 159 | if ($functionCount > 0 && $OOCount > 0) { 160 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'Both function and OO declarations'); 161 | 162 | $reportToken = \max($firstFunction, $firstOO); 163 | 164 | $phpcsFile->addError( 165 | 'A file should either contain function declarations or OO structure declarations, but not both.' 166 | . ' Found %d function declaration(s) and %d OO structure declaration(s).' 167 | . ' The first function declaration was found on line %d;' 168 | . ' the first OO declaration was found on line %d', 169 | $reportToken, 170 | 'Mixed', 171 | [ 172 | $functionCount, 173 | $OOCount, 174 | $tokens[$firstFunction]['line'], 175 | $tokens[$firstOO]['line'], 176 | ] 177 | ); 178 | } elseif ($functionCount > 0) { 179 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'Only function(s)'); 180 | } elseif ($OOCount > 0) { 181 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'Only OO structure(s)'); 182 | } else { 183 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'Neither'); 184 | } 185 | 186 | // Ignore the rest of the file. 187 | return $phpcsFile->numTokens; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /Universal/Sniffs/FunctionDeclarations/RequireFinalMethodsInTraitsSniff.php: -------------------------------------------------------------------------------- 1 | 42 | */ 43 | public function register() 44 | { 45 | return [\T_FUNCTION]; 46 | } 47 | 48 | /** 49 | * Processes this test, when one of its tokens is encountered. 50 | * 51 | * @since 1.1.0 52 | * 53 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 54 | * @param int $stackPtr The position of the current token 55 | * in the stack passed in $tokens. 56 | * 57 | * @return void 58 | */ 59 | public function process(File $phpcsFile, $stackPtr) 60 | { 61 | $tokens = $phpcsFile->getTokens(); 62 | if (isset($tokens[$stackPtr]['parenthesis_opener']) === false) { 63 | // Parse error/live coding. 64 | return; 65 | } 66 | 67 | $scopePtr = Scopes::validDirectScope($phpcsFile, $stackPtr, \T_TRAIT); 68 | if ($scopePtr === false) { 69 | // Not a trait method. 70 | return; 71 | } 72 | 73 | $methodProps = FunctionDeclarations::getProperties($phpcsFile, $stackPtr); 74 | if ($methodProps['scope'] === 'private') { 75 | // Private methods can't be final. 76 | return; 77 | } 78 | 79 | if ($methodProps['is_final'] === true) { 80 | // Already final, nothing to do. 81 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'final'); 82 | return; 83 | } 84 | 85 | if ($methodProps['is_abstract'] === true) { 86 | // Abstract classes can't be final. 87 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'abstract'); 88 | return; 89 | } 90 | 91 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'not abstract, not final'); 92 | 93 | $methodName = FunctionDeclarations::getName($phpcsFile, $stackPtr); 94 | $magic = ''; 95 | $code = 'NonFinalMethodFound'; 96 | if (FunctionDeclarations::isMagicMethodName($methodName) === true) { 97 | // Use separate error code for magic methods. 98 | $magic = 'magic '; 99 | $code = 'NonFinalMagicMethodFound'; 100 | } 101 | 102 | $data = [ 103 | $methodProps['scope'], 104 | $magic, 105 | $methodName, 106 | ObjectDeclarations::getName($phpcsFile, $scopePtr), 107 | ]; 108 | 109 | $fix = $phpcsFile->addFixableError( 110 | 'The non-abstract, %s %smethod "%s()" in trait %s should be declared as final.', 111 | $stackPtr, 112 | $code, 113 | $data 114 | ); 115 | 116 | if ($fix === true) { 117 | $phpcsFile->fixer->addContentBefore($stackPtr, 'final '); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Universal/Sniffs/Lists/DisallowLongListSyntaxSniff.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | public function register() 33 | { 34 | return [\T_LIST]; 35 | } 36 | 37 | /** 38 | * Processes this test, when one of its tokens is encountered. 39 | * 40 | * @since 1.0.0 41 | * 42 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 43 | * @param int $stackPtr The position of the current token 44 | * in the stack passed in $tokens. 45 | * 46 | * @return void 47 | */ 48 | public function process(File $phpcsFile, $stackPtr) 49 | { 50 | $openClose = Lists::getOpenClose($phpcsFile, $stackPtr); 51 | if ($openClose === false) { 52 | // Live coding or parse error. 53 | return; 54 | } 55 | 56 | $fix = $phpcsFile->addFixableError('Long list syntax is not allowed', $stackPtr, 'Found'); 57 | 58 | if ($fix === true) { 59 | $opener = $openClose['opener']; 60 | $closer = $openClose['closer']; 61 | 62 | $phpcsFile->fixer->beginChangeset(); 63 | 64 | $phpcsFile->fixer->replaceToken($stackPtr, ''); 65 | $phpcsFile->fixer->replaceToken($opener, '['); 66 | $phpcsFile->fixer->replaceToken($closer, ']'); 67 | 68 | $phpcsFile->fixer->endChangeset(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Universal/Sniffs/Lists/DisallowShortListSyntaxSniff.php: -------------------------------------------------------------------------------- 1 | 39 | */ 40 | public function register() 41 | { 42 | return Collections::listOpenTokensBC(); 43 | } 44 | 45 | /** 46 | * Processes this test, when one of its tokens is encountered. 47 | * 48 | * @since 1.0.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 | 60 | if ($tokens[$stackPtr]['code'] === \T_LIST) { 61 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'no'); 62 | return; 63 | } 64 | 65 | $openClose = Lists::getOpenClose($phpcsFile, $stackPtr); 66 | 67 | if ($openClose === false) { 68 | // Not a short list, live coding or parse error. 69 | return; 70 | } 71 | 72 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes'); 73 | 74 | $fix = $phpcsFile->addFixableError('Short list syntax is not allowed', $stackPtr, 'Found'); 75 | 76 | if ($fix === true) { 77 | $opener = $openClose['opener']; 78 | $closer = $openClose['closer']; 79 | 80 | $phpcsFile->fixer->beginChangeset(); 81 | $phpcsFile->fixer->replaceToken($opener, 'list('); 82 | $phpcsFile->fixer->replaceToken($closer, ')'); 83 | $phpcsFile->fixer->endChangeset(); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Universal/Sniffs/Namespaces/DisallowCurlyBraceSyntaxSniff.php: -------------------------------------------------------------------------------- 1 | 40 | */ 41 | public function register() 42 | { 43 | return [\T_NAMESPACE]; 44 | } 45 | 46 | /** 47 | * Processes this test, when one of its tokens is encountered. 48 | * 49 | * @since 1.0.0 50 | * 51 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 52 | * @param int $stackPtr The position of the current token 53 | * in the stack passed in $tokens. 54 | * 55 | * @return void 56 | */ 57 | public function process(File $phpcsFile, $stackPtr) 58 | { 59 | if (Namespaces::isDeclaration($phpcsFile, $stackPtr) === false) { 60 | // Namespace operator, not a declaration; or live coding/parse error. 61 | return; 62 | } 63 | 64 | $tokens = $phpcsFile->getTokens(); 65 | 66 | if (isset($tokens[$stackPtr]['scope_condition']) === false 67 | || $tokens[$stackPtr]['scope_condition'] !== $stackPtr 68 | ) { 69 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'no'); 70 | return; 71 | } 72 | 73 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes'); 74 | 75 | $phpcsFile->addError( 76 | 'Namespace declarations using the curly brace syntax are not allowed.', 77 | $stackPtr, 78 | 'Forbidden' 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Universal/Sniffs/Namespaces/DisallowDeclarationWithoutNameSniff.php: -------------------------------------------------------------------------------- 1 | 40 | */ 41 | public function register() 42 | { 43 | return [\T_NAMESPACE]; 44 | } 45 | 46 | /** 47 | * Processes this test, when one of its tokens is encountered. 48 | * 49 | * @since 1.0.0 50 | * 51 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 52 | * @param int $stackPtr The position of the current token 53 | * in the stack passed in $tokens. 54 | * 55 | * @return void 56 | */ 57 | public function process(File $phpcsFile, $stackPtr) 58 | { 59 | $name = Namespaces::getDeclaredName($phpcsFile, $stackPtr); 60 | if ($name === false) { 61 | // Use of the namespace keyword as an operator or live coding/parse error. 62 | return; 63 | } 64 | 65 | if ($name !== '') { 66 | // Named namespace. 67 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes'); 68 | return; 69 | } 70 | 71 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'no'); 72 | 73 | // Namespace declaration without namespace name (= global namespace). 74 | $phpcsFile->addError( 75 | 'Namespace declarations without a namespace name are not allowed.', 76 | $stackPtr, 77 | 'Forbidden' 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Universal/Sniffs/Namespaces/EnforceCurlyBraceSyntaxSniff.php: -------------------------------------------------------------------------------- 1 | 40 | */ 41 | public function register() 42 | { 43 | return [\T_NAMESPACE]; 44 | } 45 | 46 | /** 47 | * Processes this test, when one of its tokens is encountered. 48 | * 49 | * @since 1.0.0 50 | * 51 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 52 | * @param int $stackPtr The position of the current token 53 | * in the stack passed in $tokens. 54 | * 55 | * @return void 56 | */ 57 | public function process(File $phpcsFile, $stackPtr) 58 | { 59 | if (Namespaces::isDeclaration($phpcsFile, $stackPtr) === false) { 60 | // Namespace operator, not a declaration; or live coding/parse error. 61 | return; 62 | } 63 | 64 | $tokens = $phpcsFile->getTokens(); 65 | 66 | if (isset($tokens[$stackPtr]['scope_condition']) === true 67 | && $tokens[$stackPtr]['scope_condition'] === $stackPtr 68 | ) { 69 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes'); 70 | return; 71 | } 72 | 73 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'no'); 74 | 75 | $phpcsFile->addError( 76 | 'Namespace declarations without curly braces are not allowed.', 77 | $stackPtr, 78 | 'Forbidden' 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Universal/Sniffs/Namespaces/OneDeclarationPerFileSniff.php: -------------------------------------------------------------------------------- 1 | 49 | */ 50 | public function register() 51 | { 52 | return [\T_NAMESPACE]; 53 | } 54 | 55 | /** 56 | * Processes this test, when one of its tokens is encountered. 57 | * 58 | * @since 1.0.0 59 | * 60 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 61 | * @param int $stackPtr The position of the current token 62 | * in the stack passed in $tokens. 63 | * 64 | * @return void 65 | */ 66 | public function process(File $phpcsFile, $stackPtr) 67 | { 68 | $fileName = $phpcsFile->getFilename(); 69 | if ($this->currentFile !== $fileName) { 70 | // Reset the properties for each new file. 71 | $this->currentFile = $fileName; 72 | $this->declarationSeen = false; 73 | } 74 | 75 | if (Namespaces::isDeclaration($phpcsFile, $stackPtr) === false) { 76 | // Namespace operator, not a declaration; or live coding/parse error. 77 | return; 78 | } 79 | 80 | if ($this->declarationSeen === false) { 81 | // This is the first namespace declaration in the file. 82 | $this->declarationSeen = $stackPtr; 83 | return; 84 | } 85 | 86 | $tokens = $phpcsFile->getTokens(); 87 | 88 | // OK, so this is a file with multiple namespace declarations. 89 | $phpcsFile->addError( 90 | 'There should be only one namespace declaration per file. The first declaration was found on line %d', 91 | $stackPtr, 92 | 'MultipleFound', 93 | [$tokens[$this->declarationSeen]['line']] 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Universal/Sniffs/Operators/DisallowLogicalAndOrSniff.php: -------------------------------------------------------------------------------- 1 | 43 | */ 44 | private $metricType = [ 45 | \T_LOGICAL_AND => 'logical (and/or)', 46 | \T_LOGICAL_OR => 'logical (and/or)', 47 | \T_BOOLEAN_AND => 'boolean (&&/||)', 48 | \T_BOOLEAN_OR => 'boolean (&&/||)', 49 | ]; 50 | 51 | /** 52 | * The tokens this sniff targets with error code and replacements. 53 | * 54 | * @since 1.0.0 55 | * 56 | * @var array> 57 | */ 58 | private $targetTokenInfo = [ 59 | \T_LOGICAL_AND => [ 60 | 'error_code' => 'LogicalAnd', 61 | 'replacement' => '&&', 62 | ], 63 | \T_LOGICAL_OR => [ 64 | 'error_code' => 'LogicalOr', 65 | 'replacement' => '||', 66 | ], 67 | ]; 68 | 69 | /** 70 | * Returns an array of tokens this test wants to listen for. 71 | * 72 | * @since 1.0.0 73 | * 74 | * @return array 75 | */ 76 | public function register() 77 | { 78 | return \array_keys($this->metricType); 79 | } 80 | 81 | /** 82 | * Processes this test, when one of its tokens is encountered. 83 | * 84 | * @since 1.0.0 85 | * 86 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 87 | * @param int $stackPtr The position of the current token 88 | * in the stack passed in $tokens. 89 | * 90 | * @return void 91 | */ 92 | public function process(File $phpcsFile, $stackPtr) 93 | { 94 | $tokens = $phpcsFile->getTokens(); 95 | $tokenCode = $tokens[$stackPtr]['code']; 96 | 97 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, $this->metricType[$tokenCode]); 98 | 99 | if (isset($this->targetTokenInfo[$tokenCode]) === false) { 100 | // Already using boolean operator. 101 | return; 102 | } 103 | 104 | $error = 'Using logical operators is not allowed. Expected: "%s"; Found: "%s"'; 105 | $data = [ 106 | $this->targetTokenInfo[$tokenCode]['replacement'], 107 | $tokens[ $stackPtr ]['content'], 108 | ]; 109 | 110 | $phpcsFile->addError($error, $stackPtr, $this->targetTokenInfo[$tokenCode]['error_code'], $data); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Universal/Sniffs/Operators/DisallowShortTernarySniff.php: -------------------------------------------------------------------------------- 1 | 44 | */ 45 | public function register() 46 | { 47 | return [\T_INLINE_THEN]; 48 | } 49 | 50 | /** 51 | * Processes this test, when one of its tokens is encountered. 52 | * 53 | * @since 1.0.0 54 | * 55 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 56 | * @param int $stackPtr The position of the current token 57 | * in the stack passed in $tokens. 58 | * 59 | * @return void 60 | */ 61 | public function process(File $phpcsFile, $stackPtr) 62 | { 63 | if (Operators::isShortTernary($phpcsFile, $stackPtr) === false) { 64 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'long'); 65 | return; 66 | } 67 | 68 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'short'); 69 | 70 | $phpcsFile->addError( 71 | 'Using short ternaries is not allowed as they are rarely used correctly', 72 | $stackPtr, 73 | 'Found' 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Universal/Sniffs/Operators/StrictComparisonsSniff.php: -------------------------------------------------------------------------------- 1 | 42 | */ 43 | private $metricType = [ 44 | \T_IS_EQUAL => 'loose', 45 | \T_IS_NOT_EQUAL => 'loose', 46 | \T_IS_IDENTICAL => 'strict', 47 | \T_IS_NOT_IDENTICAL => 'strict', 48 | ]; 49 | 50 | /** 51 | * The tokens this sniff targets with error code and replacements. 52 | * 53 | * @since 1.0.0 54 | * 55 | * @var array> 56 | */ 57 | private $targetTokenInfo = [ 58 | \T_IS_EQUAL => [ 59 | 'error_code' => 'LooseEqual', 60 | 'replacement' => '===', 61 | ], 62 | \T_IS_NOT_EQUAL => [ 63 | 'error_code' => 'LooseNotEqual', 64 | 'replacement' => '!==', 65 | ], 66 | ]; 67 | 68 | /** 69 | * Returns an array of tokens this test wants to listen for. 70 | * 71 | * @since 1.0.0 72 | * 73 | * @return array 74 | */ 75 | public function register() 76 | { 77 | return \array_keys($this->metricType); 78 | } 79 | 80 | /** 81 | * Processes this test, when one of its tokens is encountered. 82 | * 83 | * @since 1.0.0 84 | * 85 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 86 | * @param int $stackPtr The position of the current token 87 | * in the stack passed in $tokens. 88 | * 89 | * @return void 90 | */ 91 | public function process(File $phpcsFile, $stackPtr) 92 | { 93 | $tokens = $phpcsFile->getTokens(); 94 | $tokenCode = $tokens[$stackPtr]['code']; 95 | 96 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, $this->metricType[$tokenCode]); 97 | 98 | if (isset($this->targetTokenInfo[$tokenCode]) === false) { 99 | // Already using strict comparison operator. 100 | return; 101 | } 102 | 103 | $error = 'Loose comparisons are not allowed. Expected: "%s"; Found: "%s"'; 104 | $data = [ 105 | $this->targetTokenInfo[$tokenCode]['replacement'], 106 | $tokens[ $stackPtr ]['content'], 107 | ]; 108 | 109 | $fix = $phpcsFile->addFixableError($error, $stackPtr, $this->targetTokenInfo[$tokenCode]['error_code'], $data); 110 | if ($fix === false) { 111 | return; 112 | } 113 | 114 | $phpcsFile->fixer->replaceToken($stackPtr, $this->targetTokenInfo[$tokenCode]['replacement']); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Universal/Sniffs/Operators/TypeSeparatorSpacingSniff.php: -------------------------------------------------------------------------------- 1 | 34 | */ 35 | private $targetTokens = [ 36 | \T_TYPE_UNION => \T_TYPE_UNION, 37 | \T_TYPE_INTERSECTION => \T_TYPE_INTERSECTION, 38 | \T_TYPE_OPEN_PARENTHESIS => \T_TYPE_OPEN_PARENTHESIS, 39 | \T_TYPE_CLOSE_PARENTHESIS => \T_TYPE_CLOSE_PARENTHESIS, 40 | ]; 41 | 42 | /** 43 | * Returns an array of tokens this test wants to listen for. 44 | * 45 | * @since 1.0.0 46 | * 47 | * @return array 48 | */ 49 | public function register() 50 | { 51 | return $this->targetTokens; 52 | } 53 | 54 | /** 55 | * Processes this test, when one of its tokens is encountered. 56 | * 57 | * @since 1.0.0 58 | * 59 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 60 | * @param int $stackPtr The position of the current token 61 | * in the stack passed in $tokens. 62 | * 63 | * @return void 64 | */ 65 | public function process(File $phpcsFile, $stackPtr) 66 | { 67 | $tokens = $phpcsFile->getTokens(); 68 | 69 | $type = 'union'; 70 | $code = 'UnionType'; 71 | if ($tokens[$stackPtr]['code'] === \T_TYPE_INTERSECTION) { 72 | $type = 'intersection'; 73 | $code = 'IntersectionType'; 74 | } elseif ($tokens[$stackPtr]['code'] === \T_TYPE_OPEN_PARENTHESIS) { 75 | $type = 'DNF parenthesis open'; 76 | $code = 'DNFOpen'; 77 | } elseif ($tokens[$stackPtr]['code'] === \T_TYPE_CLOSE_PARENTHESIS) { 78 | $type = 'DNF parenthesis close'; 79 | $code = 'DNFClose'; 80 | } 81 | 82 | $expectedSpaces = 0; 83 | $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); 84 | if ($tokens[$stackPtr]['code'] === \T_TYPE_OPEN_PARENTHESIS) { 85 | if ($tokens[$prevNonEmpty]['code'] === \T_COLON 86 | || $tokens[$prevNonEmpty]['code'] === \T_CONST 87 | || isset(Collections::propertyModifierKeywords()[$tokens[$prevNonEmpty]['code']]) === true 88 | ) { 89 | // Start of return type or property/const type. Always demand 1 space. 90 | $expectedSpaces = 1; 91 | } 92 | 93 | if ($tokens[$prevNonEmpty]['code'] === \T_OPEN_PARENTHESIS 94 | || $tokens[$prevNonEmpty]['code'] === \T_COMMA 95 | ) { 96 | // Start of parameter type. Allow new line/indent before. 97 | if ($tokens[$prevNonEmpty]['line'] === $tokens[$stackPtr]['line']) { 98 | $expectedSpaces = 1; 99 | } else { 100 | $expectedSpaces = 'skip'; 101 | } 102 | } 103 | } 104 | 105 | if (isset($this->targetTokens[$tokens[$prevNonEmpty]['code']]) === true) { 106 | // Prevent duplicate errors when there are two adjacent operators. 107 | $expectedSpaces = 'skip'; 108 | } 109 | 110 | if ($expectedSpaces !== 'skip') { 111 | SpacesFixer::checkAndFix( 112 | $phpcsFile, 113 | $stackPtr, 114 | $prevNonEmpty, 115 | $expectedSpaces, 116 | 'Expected %s before the ' . $type . ' type separator. Found: %s', 117 | $code . 'SpacesBefore', 118 | 'error', 119 | 0, // Severity. 120 | 'Space before ' . $type . ' type separator' 121 | ); 122 | } 123 | 124 | $expectedSpaces = 0; 125 | $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); 126 | if ($tokens[$stackPtr]['code'] === \T_TYPE_CLOSE_PARENTHESIS) { 127 | if ($tokens[$nextNonEmpty]['code'] === \T_OPEN_CURLY_BRACKET 128 | || $tokens[$nextNonEmpty]['code'] === \T_VARIABLE 129 | || $tokens[$nextNonEmpty]['code'] === \T_STRING 130 | ) { 131 | // End of return type, parameter or property/const type. Always demand 1 space. 132 | $expectedSpaces = 1; 133 | } 134 | } 135 | 136 | SpacesFixer::checkAndFix( 137 | $phpcsFile, 138 | $stackPtr, 139 | $nextNonEmpty, 140 | $expectedSpaces, 141 | 'Expected %s after the ' . $type . ' type separator. Found: %s', 142 | $code . 'SpacesAfter', 143 | 'error', 144 | 0, // Severity. 145 | 'Space after ' . $type . ' type separator' 146 | ); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Universal/Sniffs/PHP/LowercasePHPTagSniff.php: -------------------------------------------------------------------------------- 1 | 39 | */ 40 | public function register() 41 | { 42 | return [\T_OPEN_TAG]; 43 | } 44 | 45 | /** 46 | * Processes this test, when one of its tokens is encountered. 47 | * 48 | * @since 1.2.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 === $content) { 63 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'lowercase'); 64 | return; 65 | } 66 | 67 | $errorCode = ''; 68 | if (\strtoupper($content) === $content) { 69 | $errorCode = 'Uppercase'; 70 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'uppercase'); 71 | } else { 72 | $errorCode = 'Mixedcase'; 73 | $phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'mixed case'); 74 | } 75 | 76 | $fix = $phpcsFile->addFixableError( 77 | 'The php open tag should be in lowercase. Found: %s', 78 | $stackPtr, 79 | $errorCode, 80 | [\trim($content)] 81 | ); 82 | 83 | if ($fix === true) { 84 | $phpcsFile->fixer->replaceToken($stackPtr, $contentLC); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Universal/Sniffs/PHP/NoFQNTrueFalseNullSniff.php: -------------------------------------------------------------------------------- 1 | 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 | } 93 | -------------------------------------------------------------------------------- /Universal/Sniffs/PHP/OneStatementInShortEchoTagSniff.php: -------------------------------------------------------------------------------- 1 | 36 | */ 37 | public function register() 38 | { 39 | return [\T_OPEN_TAG_WITH_ECHO]; 40 | } 41 | 42 | /** 43 | * Processes this test, when one of its tokens is encountered. 44 | * 45 | * @since 1.0.0 46 | * 47 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 48 | * @param int $stackPtr The position of the current token 49 | * in the stack passed in $tokens. 50 | * 51 | * @return void 52 | */ 53 | public function process(File $phpcsFile, $stackPtr) 54 | { 55 | $tokens = $phpcsFile->getTokens(); 56 | 57 | for ($endOfStatement = ($stackPtr + 1); $endOfStatement < $phpcsFile->numTokens; $endOfStatement++) { 58 | if ($tokens[$endOfStatement]['code'] === \T_CLOSE_TAG 59 | || $tokens[$endOfStatement]['code'] === \T_SEMICOLON 60 | ) { 61 | break; 62 | } 63 | 64 | // Skip over anything within parenthesis. 65 | if ($tokens[$endOfStatement]['code'] === \T_OPEN_PARENTHESIS 66 | && isset($tokens[$endOfStatement]['parenthesis_closer']) 67 | ) { 68 | $endOfStatement = $tokens[$endOfStatement]['parenthesis_closer']; 69 | } 70 | } 71 | 72 | if ($endOfStatement === $phpcsFile->numTokens 73 | || $tokens[$endOfStatement]['code'] === \T_CLOSE_TAG 74 | ) { 75 | return; 76 | } 77 | 78 | // Semicolon, so check for any code between it and the close tag. 79 | $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($endOfStatement + 1), null, true); 80 | if ($nextNonEmpty === false 81 | || $tokens[$nextNonEmpty]['code'] === \T_CLOSE_TAG 82 | ) { 83 | return; 84 | } 85 | 86 | $fix = $phpcsFile->addFixableError( 87 | 'Only one statement is allowed when using short open echo PHP tags.' 88 | . ' Use the "fixer->replaceToken($stackPtr, 'fixer->replaceToken($stackPtr, ' 44 | */ 45 | protected $keywords = [ 46 | 'const' => true, 47 | 'function' => true, 48 | ]; 49 | 50 | /** 51 | * Returns an array of tokens this test wants to listen for. 52 | * 53 | * @since 1.0.0 54 | * 55 | * @return array 56 | */ 57 | public function register() 58 | { 59 | return [\T_USE]; 60 | } 61 | 62 | /** 63 | * Processes this test, when one of its tokens is encountered. 64 | * 65 | * @since 1.0.0 66 | * 67 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 68 | * @param int $stackPtr The position of the current token in the 69 | * stack passed in $tokens. 70 | * 71 | * @return void 72 | */ 73 | public function process(File $phpcsFile, $stackPtr) 74 | { 75 | if (UseStatements::isImportUse($phpcsFile, $stackPtr) === false) { 76 | // Trait or closure use statement. 77 | return; 78 | } 79 | 80 | $tokens = $phpcsFile->getTokens(); 81 | $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); 82 | if ($nextNonEmpty === false) { 83 | // Live coding or parse error. 84 | return; 85 | } 86 | 87 | if (isset($this->keywords[\strtolower($tokens[$nextNonEmpty]['content'])]) === true) { 88 | // Keyword found at start of statement, applies to whole statement. 89 | $this->processKeyword($phpcsFile, $nextNonEmpty, $tokens[$nextNonEmpty]['content']); 90 | return; 91 | } 92 | 93 | // This may still be a group use statement with function/const substatements. 94 | $openGroup = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG, \T_OPEN_USE_GROUP], ($stackPtr + 1)); 95 | if ($openGroup === false || $tokens[$openGroup]['code'] !== \T_OPEN_USE_GROUP) { 96 | // Not a group use statement. 97 | return; 98 | } 99 | 100 | $closeGroup = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG, \T_CLOSE_USE_GROUP], ($openGroup + 1)); 101 | if ($closeGroup === false || $tokens[$closeGroup]['code'] !== \T_CLOSE_USE_GROUP) { 102 | // Live coding or parse error. 103 | return; 104 | } 105 | 106 | $current = $openGroup; 107 | do { 108 | $current = $phpcsFile->findNext(Tokens::$emptyTokens, ($current + 1), $closeGroup, true); 109 | if ($current === false) { 110 | return; 111 | } 112 | 113 | if (isset($this->keywords[\strtolower($tokens[$current]['content'])]) === true) { 114 | $this->processKeyword($phpcsFile, $current, $tokens[$current]['content']); 115 | } 116 | 117 | // We're within the use group, so find the next comma. 118 | $current = $phpcsFile->findNext(\T_COMMA, ($current + 1), $closeGroup); 119 | } while ($current !== false); 120 | } 121 | 122 | /** 123 | * Processes a found keyword. 124 | * 125 | * @since 1.0.0 126 | * 127 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 128 | * @param int $stackPtr The position of the keyword in the token stack. 129 | * @param string $content The keyword as found. 130 | * 131 | * @return void 132 | */ 133 | public function processKeyword(File $phpcsFile, $stackPtr, $content) 134 | { 135 | $contentLC = \strtolower($content); 136 | $metricName = \sprintf(self::METRIC_NAME, $contentLC); 137 | if ($contentLC === $content) { 138 | // Already lowercase. Bow out. 139 | $phpcsFile->recordMetric($stackPtr, $metricName, 'lowercase'); 140 | return; 141 | } 142 | 143 | if (\strtoupper($content) === $content) { 144 | $phpcsFile->recordMetric($stackPtr, $metricName, 'uppercase'); 145 | } else { 146 | $phpcsFile->recordMetric($stackPtr, $metricName, 'mixed case'); 147 | } 148 | 149 | $error = 'The "%s" keyword when used in an import use statements must be lowercase.'; 150 | $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NotLowercase', [$contentLC]); 151 | 152 | if ($fix === true) { 153 | $phpcsFile->fixer->replaceToken($stackPtr, $contentLC); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /Universal/Sniffs/UseStatements/NoLeadingBackslashSniff.php: -------------------------------------------------------------------------------- 1 | 45 | */ 46 | public function register() 47 | { 48 | return [\T_USE]; 49 | } 50 | 51 | /** 52 | * Processes this test, when one of its tokens is encountered. 53 | * 54 | * @since 1.0.0 55 | * 56 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 57 | * @param int $stackPtr The position of the current token in the 58 | * stack passed in $tokens. 59 | * 60 | * @return void 61 | */ 62 | public function process(File $phpcsFile, $stackPtr) 63 | { 64 | if (UseStatements::isImportUse($phpcsFile, $stackPtr) === false) { 65 | // Trait or closure use statement. 66 | return; 67 | } 68 | 69 | $endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG, \T_OPEN_USE_GROUP], ($stackPtr + 1)); 70 | if ($endOfStatement === false) { 71 | // Live coding or parse error. 72 | return; 73 | } 74 | 75 | $tokens = $phpcsFile->getTokens(); 76 | $current = $stackPtr; 77 | 78 | do { 79 | $continue = $this->processImport($phpcsFile, $current, $endOfStatement); 80 | if ($continue === false) { 81 | break; 82 | } 83 | 84 | // Move the stackPtr forward to the next part of the use statement, if any. 85 | $current = $phpcsFile->findNext(\T_COMMA, ($current + 1), $endOfStatement); 86 | } while ($current !== false); 87 | 88 | if ($tokens[$endOfStatement]['code'] !== \T_OPEN_USE_GROUP) { 89 | // Finished the statement. 90 | return; 91 | } 92 | 93 | $current = $endOfStatement; // Group open brace. 94 | $endOfStatement = $phpcsFile->findNext([\T_CLOSE_USE_GROUP], ($endOfStatement + 1)); 95 | if ($endOfStatement === false) { 96 | // Live coding or parse error. 97 | return; 98 | } 99 | 100 | do { 101 | $continue = $this->processImport($phpcsFile, $current, $endOfStatement, true); 102 | if ($continue === false) { 103 | break; 104 | } 105 | 106 | // Move the stackPtr forward to the next part of the use statement, if any. 107 | $current = $phpcsFile->findNext(\T_COMMA, ($current + 1), $endOfStatement); 108 | } while ($current !== false); 109 | } 110 | 111 | /** 112 | * Examine an individual import statement. 113 | * 114 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 115 | * @param int $stackPtr The position of the current token. 116 | * @param int $endOfStatement End token for the current import statement. 117 | * @param bool $groupUse Whether the current statement is a partial one 118 | * within a group use statement. 119 | * 120 | * @return bool Whether or not to continue examining this import use statement. 121 | */ 122 | private function processImport(File $phpcsFile, $stackPtr, $endOfStatement, $groupUse = false) 123 | { 124 | $tokens = $phpcsFile->getTokens(); 125 | 126 | $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), $endOfStatement, true); 127 | if ($nextNonEmpty === false) { 128 | // Reached the end of the statement. 129 | return false; 130 | } 131 | 132 | // Skip past 'function'/'const' keyword. 133 | $contentLC = \strtolower($tokens[$nextNonEmpty]['content']); 134 | if ($tokens[$nextNonEmpty]['code'] === \T_STRING 135 | && ($contentLC === 'function' || $contentLC === 'const') 136 | ) { 137 | $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextNonEmpty + 1), $endOfStatement, true); 138 | if ($nextNonEmpty === false) { 139 | // Reached the end of the statement. 140 | return false; 141 | } 142 | } 143 | 144 | if ($tokens[$nextNonEmpty]['code'] === \T_NS_SEPARATOR) { 145 | $phpcsFile->recordMetric($nextNonEmpty, self::METRIC_NAME, 'yes'); 146 | 147 | $error = 'An import use statement should never start with a leading backslash'; 148 | $code = 'LeadingBackslashFound'; 149 | 150 | if ($groupUse === true) { 151 | $error = 'Parse error: partial import use statement in a use group starting with a leading backslash'; 152 | $code = 'LeadingBackslashFoundInGroup'; 153 | } 154 | 155 | $fix = $phpcsFile->addFixableError($error, $nextNonEmpty, $code); 156 | 157 | if ($fix === true) { 158 | if ($tokens[$nextNonEmpty - 1]['code'] !== \T_WHITESPACE) { 159 | $phpcsFile->fixer->replaceToken($nextNonEmpty, ' '); 160 | } else { 161 | $phpcsFile->fixer->replaceToken($nextNonEmpty, ''); 162 | } 163 | } 164 | } else { 165 | $phpcsFile->recordMetric($nextNonEmpty, self::METRIC_NAME, 'no'); 166 | } 167 | 168 | return true; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /Universal/Sniffs/UseStatements/NoUselessAliasesSniff.php: -------------------------------------------------------------------------------- 1 | 37 | */ 38 | public function register() 39 | { 40 | return [\T_USE]; 41 | } 42 | 43 | /** 44 | * Processes this test, when one of its tokens is encountered. 45 | * 46 | * @since 1.1.0 47 | * 48 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 49 | * @param int $stackPtr The position of the current token 50 | * in the stack passed in $tokens. 51 | * 52 | * @return void 53 | */ 54 | public function process(File $phpcsFile, $stackPtr) 55 | { 56 | if (UseStatements::isImportUse($phpcsFile, $stackPtr) === false) { 57 | // Closure or trait use statement. Bow out. 58 | return; 59 | } 60 | 61 | $endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG], ($stackPtr + 1)); 62 | if ($endOfStatement === false) { 63 | // Parse error or live coding. 64 | return; 65 | } 66 | 67 | $hasAliases = $phpcsFile->findNext(\T_AS, ($stackPtr + 1), $endOfStatement); 68 | if ($hasAliases === false) { 69 | // This use import statement does not alias anything, bow out. 70 | return; 71 | } 72 | 73 | $useStatements = UseStatements::splitImportUseStatement($phpcsFile, $stackPtr); 74 | if (\count($useStatements, \COUNT_RECURSIVE) <= 3) { 75 | // No statements found. Shouldn't be possible, but still. Bow out. 76 | return; 77 | } 78 | 79 | $tokens = $phpcsFile->getTokens(); 80 | 81 | // Collect all places where aliases are used in this use statement. 82 | $aliasPtrs = []; 83 | $currentAs = $hasAliases; 84 | do { 85 | $aliasPtr = $phpcsFile->findNext(Tokens::$emptyTokens, ($currentAs + 1), null, true); 86 | if ($aliasPtr !== false && $tokens[$aliasPtr]['code'] === \T_STRING) { 87 | $aliasPtrs[$currentAs] = $aliasPtr; 88 | } 89 | 90 | $currentAs = $phpcsFile->findNext(\T_AS, ($currentAs + 1), $endOfStatement); 91 | } while ($currentAs !== false); 92 | 93 | // Now check the names in each use statement for useless aliases. 94 | foreach ($useStatements as $type => $statements) { 95 | foreach ($statements as $alias => $fqName) { 96 | $unqualifiedName = \ltrim(\substr($fqName, \strrpos($fqName, '\\')), '\\'); 97 | 98 | $uselessAlias = false; 99 | if ($type === 'const') { 100 | // Do a case-sensitive comparison for constants. 101 | if ($unqualifiedName === $alias) { 102 | $uselessAlias = true; 103 | } 104 | } elseif (NamingConventions::isEqual($unqualifiedName, $alias)) { 105 | $uselessAlias = true; 106 | } 107 | 108 | if ($uselessAlias === false) { 109 | continue; 110 | } 111 | 112 | // Now check if this is actually used as an alias or just the actual name. 113 | foreach ($aliasPtrs as $asPtr => $aliasPtr) { 114 | if ($tokens[$aliasPtr]['content'] !== $alias) { 115 | continue; 116 | } 117 | 118 | // Make sure this is really the right one. 119 | $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($asPtr - 1), null, true); 120 | if ($tokens[$prev]['code'] !== \T_STRING 121 | || $tokens[$prev]['content'] !== $unqualifiedName 122 | ) { 123 | continue; 124 | } 125 | 126 | $error = 'Useless alias "%s" found for import of "%s"'; 127 | $code = 'Found'; 128 | $data = [$alias, $fqName]; 129 | 130 | // Okay, so this is the one which should be flagged. 131 | $hasComments = $phpcsFile->findNext(Tokens::$commentTokens, ($prev + 1), $aliasPtr); 132 | if ($hasComments !== false) { 133 | // Don't auto-fix if there are comments. 134 | $phpcsFile->addError($error, $aliasPtr, $code, $data); 135 | break; 136 | } 137 | 138 | $fix = $phpcsFile->addFixableError($error, $aliasPtr, $code, $data); 139 | 140 | if ($fix === true) { 141 | $phpcsFile->fixer->beginChangeset(); 142 | 143 | for ($i = ($prev + 1); $i <= $aliasPtr; $i++) { 144 | $phpcsFile->fixer->replaceToken($i, ''); 145 | } 146 | 147 | $phpcsFile->fixer->endChangeset(); 148 | } 149 | 150 | break; 151 | } 152 | } 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Universal/Sniffs/WhiteSpace/AnonClassKeywordSpacingSniff.php: -------------------------------------------------------------------------------- 1 | 41 | */ 42 | public function register() 43 | { 44 | return [\T_ANON_CLASS]; 45 | } 46 | 47 | /** 48 | * Processes this test, when one of its tokens is encountered. 49 | * 50 | * @since 1.0.0 51 | * 52 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 53 | * @param int $stackPtr The position of the current token 54 | * in the stack passed in $tokens. 55 | * 56 | * @return void 57 | */ 58 | public function process(File $phpcsFile, $stackPtr) 59 | { 60 | $tokens = $phpcsFile->getTokens(); 61 | $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true); 62 | if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS) { 63 | // No parentheses, nothing to do. 64 | return; 65 | } 66 | 67 | SpacesFixer::checkAndFix( 68 | $phpcsFile, 69 | $stackPtr, 70 | $nextNonEmpty, 71 | (int) $this->spacing, 72 | 'There must be %1$s between the class keyword and the open parenthesis for an anonymous class. Found: %2$s', 73 | 'Incorrect', 74 | 'error', 75 | 0, 76 | 'Anon class: space between keyword and open parenthesis' 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Universal/Sniffs/WhiteSpace/DisallowInlineTabsSniff.php: -------------------------------------------------------------------------------- 1 | 32 | * 33 | * 34 | * 35 | * The PHPCS native `Generic.Whitespace.DisallowTabIndent` sniff oversteps its reach and silently 36 | * does mid-line tab to space replacements as well. 37 | * However, the sister-sniff `Generic.Whitespace.DisallowSpaceIndent` leaves mid-line tabs/spaces alone. 38 | * This sniff fills that gap. 39 | * 40 | * @since 1.0.0 41 | */ 42 | final class DisallowInlineTabsSniff implements Sniff 43 | { 44 | 45 | /** 46 | * The --tab-width CLI value that is being used. 47 | * 48 | * @since 1.0.0 49 | * 50 | * @var int 51 | */ 52 | private $tabWidth; 53 | 54 | /** 55 | * Tokens to check for mid-line tabs. 56 | * 57 | * @since 1.0.0 58 | * 59 | * @var array 60 | */ 61 | private $find = [ 62 | \T_WHITESPACE => true, 63 | \T_DOC_COMMENT_WHITESPACE => true, 64 | \T_DOC_COMMENT_STRING => true, 65 | \T_COMMENT => true, 66 | \T_START_HEREDOC => true, 67 | \T_START_NOWDOC => true, 68 | \T_YIELD_FROM => true, 69 | ]; 70 | 71 | /** 72 | * Registers the tokens that this sniff wants to listen for. 73 | * 74 | * @since 1.0.0 75 | * 76 | * @return array 77 | */ 78 | public function register() 79 | { 80 | return Collections::phpOpenTags(); 81 | } 82 | 83 | /** 84 | * Processes this test, when one of its tokens is encountered. 85 | * 86 | * @since 1.0.0 87 | * 88 | * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. 89 | * @param int $stackPtr The position of the current token 90 | * in the stack passed in $tokens. 91 | * 92 | * @return int Integer stack pointer to skip the rest of the file. 93 | */ 94 | public function process(File $phpcsFile, $stackPtr) 95 | { 96 | if (isset($this->tabWidth) === false) { 97 | $this->tabWidth = (int) Helper::getTabWidth($phpcsFile); 98 | } 99 | 100 | if (\defined('PHP_CODESNIFFER_IN_TESTS')) { 101 | $this->tabWidth = (int) Helper::getCommandLineData($phpcsFile, 'tabWidth'); 102 | } 103 | 104 | $tokens = $phpcsFile->getTokens(); 105 | $dummy = new DummyTokenizer('', $phpcsFile->config); 106 | 107 | for ($i = 0; $i < $phpcsFile->numTokens; $i++) { 108 | // Skip all non-target tokens and skip whitespace at the start of a new line. 109 | if (isset($this->find[$tokens[$i]['code']]) === false 110 | || (($tokens[$i]['code'] === \T_WHITESPACE 111 | || $tokens[$i]['code'] === \T_DOC_COMMENT_WHITESPACE) 112 | && $tokens[$i]['column'] === 1) 113 | ) { 114 | continue; 115 | } 116 | 117 | // If tabs haven't been converted to spaces by the tokenizer, do so now. 118 | $token = $tokens[$i]; 119 | if (isset($token['orig_content']) === false) { 120 | if ($token['content'] === '' || \strpos($token['content'], "\t") === false) { 121 | // If there are no tabs, we can continue, no matter what. 122 | continue; 123 | } 124 | 125 | $dummy->replaceTabsInToken($token); 126 | } 127 | 128 | /* 129 | * Tokens only have the 'orig_content' key if they contain tabs, 130 | * so from here on out, we **know** there will be tabs in the content. 131 | */ 132 | $origContent = $token['orig_content']; 133 | $commentOnly = ''; 134 | 135 | $multiLineComment = false; 136 | if (($tokens[$i]['code'] === \T_COMMENT 137 | || isset(Tokens::$phpcsCommentTokens[$tokens[$i]['code']])) 138 | && $tokens[$i]['column'] === 1 139 | && ($tokens[($i - 1)]['code'] === \T_COMMENT 140 | || isset(Tokens::$phpcsCommentTokens[$tokens[($i - 1)]['code']])) 141 | ) { 142 | $multiLineComment = true; 143 | } 144 | 145 | if ($multiLineComment === true) { 146 | // This is the subsequent line of a multi-line comment. Account for indentation. 147 | $commentOnly = \ltrim($origContent); 148 | if ($commentOnly === '' || \strpos($commentOnly, "\t") === false) { 149 | continue; 150 | } 151 | } 152 | 153 | $fix = $phpcsFile->addFixableError( 154 | 'Spaces must be used for mid-line alignment; tabs are not allowed', 155 | $i, 156 | 'NonIndentTabsUsed' 157 | ); 158 | 159 | if ($fix === false) { 160 | continue; 161 | } 162 | 163 | $indent = ''; 164 | if ($multiLineComment === true) { 165 | // Take the original indent (tabs/spaces) and combine with the tab-replaced comment content. 166 | $indent = \str_replace($commentOnly, '', $origContent); 167 | $token['content'] = \ltrim($token['content']); 168 | } 169 | 170 | $phpcsFile->fixer->replaceToken($i, $indent . $token['content']); 171 | } 172 | 173 | // Scanned the whole file in one go. Don't scan this file again. 174 | return $phpcsFile->numTokens; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /Universal/ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | A collection of universal sniffs. This standard is not designed to be used to check code. Include individual sniffs from this standard in a custom ruleset instead. 5 | 6 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "phpcsstandards/phpcsextra", 3 | "description" : "A collection of sniffs and standards for use with PHP_CodeSniffer.", 4 | "type" : "phpcodesniffer-standard", 5 | "keywords" : [ "phpcs", "phpcbf", "standards", "static analysis", "php_codesniffer", "phpcodesniffer-standard" ], 6 | "license" : "LGPL-3.0-or-later", 7 | "authors" : [ 8 | { 9 | "name" : "Juliette Reinders Folmer", 10 | "role" : "lead", 11 | "homepage" : "https://github.com/jrfnl" 12 | }, 13 | { 14 | "name" : "Contributors", 15 | "homepage" : "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" 16 | } 17 | ], 18 | "support" : { 19 | "issues" : "https://github.com/PHPCSStandards/PHPCSExtra/issues", 20 | "source" : "https://github.com/PHPCSStandards/PHPCSExtra", 21 | "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy" 22 | }, 23 | "require" : { 24 | "php" : ">=5.4", 25 | "squizlabs/php_codesniffer" : "^3.12.1", 26 | "phpcsstandards/phpcsutils" : "^1.0.9" 27 | }, 28 | "require-dev" : { 29 | "php-parallel-lint/php-parallel-lint": "^1.4.0", 30 | "php-parallel-lint/php-console-highlighter": "^1.0", 31 | "phpcsstandards/phpcsdevcs": "^1.1.6", 32 | "phpcsstandards/phpcsdevtools": "^1.2.1", 33 | "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" 34 | }, 35 | "extra": { 36 | "branch-alias": { 37 | "dev-stable": "1.x-dev", 38 | "dev-develop": "1.x-dev" 39 | } 40 | }, 41 | "scripts" : { 42 | "lint": [ 43 | "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . --show-deprecated -e php --exclude vendor --exclude .git" 44 | ], 45 | "checkcs": [ 46 | "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs" 47 | ], 48 | "fixcs": [ 49 | "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf" 50 | ], 51 | "check-complete": [ 52 | "@php ./vendor/phpcsstandards/phpcsdevtools/bin/phpcs-check-feature-completeness ./Modernize ./NormalizedArrays ./Universal" 53 | ], 54 | "test": [ 55 | "@php ./vendor/phpunit/phpunit/phpunit --filter PHPCSExtra --no-coverage ./vendor/squizlabs/php_codesniffer/tests/AllTests.php" 56 | ], 57 | "coverage": [ 58 | "@php ./vendor/phpunit/phpunit/phpunit --filter PHPCSExtra ./vendor/squizlabs/php_codesniffer/tests/AllTests.php" 59 | ], 60 | "coverage-local": [ 61 | "@php ./vendor/phpunit/phpunit/phpunit --filter PHPCSExtra ./vendor/squizlabs/php_codesniffer/tests/AllTests.php --coverage-html ./build/coverage-html" 62 | ] 63 | }, 64 | "config": { 65 | "allow-plugins": { 66 | "dealerdirect/phpcodesniffer-composer-installer": true 67 | }, 68 | "lock": false 69 | } 70 | } 71 | --------------------------------------------------------------------------------