Skip to content

Commit d735a22

Browse files
Merge pull request #22 from VincentLanglet/staging
✨ Add rules to check if use statements are alphabetically so…
2 parents 26cacb9 + ebcda36 commit d735a22

File tree

4 files changed

+222
-0
lines changed

4 files changed

+222
-0
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<?php
2+
3+
/**
4+
* Ensures USE blocks are alphabetically sorted.
5+
*/
6+
class Symfony3Custom_Sniffs_Namespaces_AlphabeticallySortedUseSniff implements PHP_CodeSniffer_Sniff
7+
{
8+
/**
9+
* @var bool
10+
*/
11+
private $caseSensitive = false;
12+
13+
/**
14+
* Returns an array of tokens this test wants to listen for.
15+
*
16+
* @return array
17+
*/
18+
public function register()
19+
{
20+
return array(T_USE);
21+
}
22+
23+
/**
24+
* Processes this test, when one of its tokens is encountered.
25+
*
26+
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
27+
* @param int $stackPtr The position of the current token in
28+
* the stack passed in $tokens.
29+
*
30+
* @return void
31+
*/
32+
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
33+
{
34+
if (true === $this->shouldIgnoreUse($phpcsFile, $stackPtr)) {
35+
return;
36+
}
37+
38+
$previousUse = $phpcsFile->findPrevious(T_USE, $stackPtr - 1);
39+
40+
if (false === $previousUse) {
41+
return;
42+
}
43+
44+
// Look for the real previous USE
45+
while (true === $this->shouldIgnoreUse($phpcsFile, $previousUse)) {
46+
$previousUse = $phpcsFile->findPrevious(T_USE, $previousUse - 1);
47+
48+
if (false === $previousUse) {
49+
return;
50+
}
51+
}
52+
53+
$namespace = $this->getNamespaceUsed($phpcsFile, $stackPtr);
54+
$previousNamespace = $this->getNamespaceUsed($phpcsFile, $previousUse);
55+
56+
if ($this->compareNamespaces($namespace, $previousNamespace) < 0) {
57+
$error = 'Namespaces used are not correctly sorted';
58+
$phpcsFile->addError($error, $stackPtr, 'AlphabeticallySortedUse');
59+
}
60+
}
61+
62+
/**
63+
* Check if this USE statement is part of the namespace block.
64+
*
65+
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
66+
* @param int $stackPtr The position of the current token in
67+
* the stack passed in $tokens.
68+
*
69+
* @return bool
70+
*/
71+
private function shouldIgnoreUse(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
72+
{
73+
$tokens = $phpcsFile->getTokens();
74+
75+
// Ignore USE keywords inside closures.
76+
$next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
77+
if (T_OPEN_PARENTHESIS === $tokens[$next]['code']) {
78+
return true;
79+
}
80+
81+
// Ignore USE keywords inside class and trait
82+
if ($phpcsFile->hasCondition($stackPtr, array(T_CLASS, T_TRAIT)) === true) {
83+
return true;
84+
}
85+
86+
return false;
87+
}
88+
89+
/**
90+
* Get full namespace imported
91+
*
92+
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
93+
* @param int $stackPtr The position of the current token in
94+
* the stack passed in $tokens.
95+
*
96+
* @return string
97+
*/
98+
private function getNamespaceUsed(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
99+
{
100+
$tokens = $phpcsFile->getTokens();
101+
$namespace = '';
102+
103+
$start = $phpcsFile->findNext([T_STRING, T_NS_SEPARATOR], $stackPtr);
104+
$end = $phpcsFile->findNext([T_STRING, T_NS_SEPARATOR], $start, null, true);
105+
106+
for ($i = $start; $i < $end; $i++) {
107+
$namespace .= $tokens[$i]['content'];
108+
}
109+
110+
return $namespace;
111+
}
112+
113+
/**
114+
* @param string $namespace1
115+
* @param string $namespace2
116+
*
117+
* @return int
118+
*/
119+
private function compareNamespaces($namespace1, $namespace2)
120+
{
121+
$array1 = explode('\\', $namespace1);
122+
$length1 = count($array1);
123+
$array2 = explode('\\', $namespace2);
124+
$length2 = count($array2);
125+
126+
for ($i = 0; $i < $length1; $i++) {
127+
if ($i >= $length2) {
128+
// $namespace2 is shorter than $namespace1
129+
// and they have the same beginning
130+
// so $namespace1 > $namespace2
131+
return 1;
132+
}
133+
134+
if (true === $this->caseSensitive && strcmp($array1[$i], $array2[$i]) !== 0) {
135+
return strcmp($array1[$i], $array2[$i]);
136+
} elseif (false === $this->caseSensitive && strcasecmp($array1[$i], $array2[$i]) !== 0) {
137+
return strcasecmp($array1[$i], $array2[$i]);
138+
}
139+
}
140+
141+
if ($length1 === $length2) {
142+
return 0;
143+
}
144+
145+
// $namespace1 is shorter than $namespace2
146+
// and they have the same beginning
147+
// so $namespace1 < $namespace2
148+
return -1;
149+
}
150+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace MyProject;
4+
5+
use BarClass as Bar;
6+
use My\Full\Classname as Another;
7+
use SomethingElse;
8+
use Something;
9+
10+
$var = new MyClass(
11+
function () use ($foo, $bar) {
12+
return true;
13+
}
14+
);
15+
16+
class Container extends Component implements IContainer
17+
{
18+
use TContainer;
19+
use AContainer;
20+
}
21+
22+
use LastThing;
23+
24+
trait HelloWorld
25+
{
26+
use Hello, World, The;
27+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
/**
4+
* Unit test class for the AlphabeticallySortedUse sniff.
5+
*
6+
* A sniff unit test checks a .inc file for expected violations of a single
7+
* coding standard. Expected errors and warnings are stored in this class.
8+
*/
9+
class Symfony3Custom_Tests_Namespaces_AlphabeticallySortedUseUnitTest extends AbstractSniffUnitTest
10+
{
11+
/**
12+
* Returns the lines where errors should occur.
13+
*
14+
* The key of the array should represent the line number and the value
15+
* should represent the number of errors that should occur on that line.
16+
*
17+
* @return array<int, int>
18+
*/
19+
public function getErrorList()
20+
{
21+
return array(
22+
8 => 1,
23+
22 => 1,
24+
);
25+
}
26+
27+
/**
28+
* Returns the lines where warnings should occur.
29+
*
30+
* The key of the array should represent the line number and the value
31+
* should represent the number of errors that should occur on that line.
32+
*
33+
* @return array(int => int)
34+
*/
35+
public function getWarningList()
36+
{
37+
return array();
38+
}
39+
}

docs/standards.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,9 @@ We mainly respect the [Symfony Standard](./standards/symfony.md) but
125125
```
126126
<rule ref="Symfony3Custom.Commenting.VariableComment"/>
127127
```
128+
129+
- USE keywords should be alphabetically sorted
130+
131+
```
132+
<rule ref="Symfony3Custom.Namespaces.AlphabeticallySortedUse"/>
133+
```

0 commit comments

Comments
 (0)