Skip to content

Commit ef4c333

Browse files
committed
Support for use const and use function (fixes #43 and #47)
1 parent d2989ad commit ef4c333

16 files changed

+289
-63
lines changed

SlevomatCodingStandard/Helpers/ReferencedName.php

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,31 @@
55
class ReferencedName
66
{
77

8+
const TYPE_DEFAULT = 'default';
9+
const TYPE_FUNCTION = 'function';
10+
const TYPE_CONSTANT = 'constant';
11+
812
/** @var string */
913
private $nameAsReferencedInFile;
1014

1115
/** @var integer */
1216
private $pointer;
1317

18+
/** @var string */
19+
private $type;
20+
1421
/**
1522
* @param string $nameAsReferencedInFile
1623
* @param integer $pointer
24+
* @param string $type
1725
*/
18-
public function __construct($nameAsReferencedInFile, $pointer)
26+
public function __construct($nameAsReferencedInFile, $pointer, $type)
1927
{
2028
$this->nameAsReferencedInFile = $nameAsReferencedInFile;
2129
$this->pointer = $pointer;
30+
$this->type = $type;
2231
}
32+
2333
/**
2434
* @return string
2535
*/
@@ -36,4 +46,30 @@ public function getPointer()
3646
return $this->pointer;
3747
}
3848

49+
/**
50+
* @return boolean
51+
*/
52+
public function isConstant()
53+
{
54+
return $this->type === self::TYPE_CONSTANT;
55+
}
56+
57+
/**
58+
* @return boolean
59+
*/
60+
public function isFunction()
61+
{
62+
return $this->type === self::TYPE_FUNCTION;
63+
}
64+
65+
/**
66+
* @param \SlevomatCodingStandard\Helpers\UseStatement $useStatement
67+
* @return boolean
68+
*/
69+
public function hasSameUseStatementType(UseStatement $useStatement)
70+
{
71+
return $this->isConstant() === $useStatement->isConstant()
72+
&& $this->isFunction() === $useStatement->isFunction();
73+
}
74+
3975
}

SlevomatCodingStandard/Helpers/ReferencedNameHelper.php

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ private static function createAllReferencedNames(PHP_CodeSniffer_File $phpcsFile
8484
], true);
8585
});
8686
foreach ($referencedNames as $name) {
87-
$types[] = new ReferencedName($name, $nameStartPointer);
87+
$types[] = new ReferencedName($name, $nameStartPointer, ReferencedName::TYPE_DEFAULT);
8888
}
8989
}
9090
};
@@ -124,7 +124,34 @@ private static function createAllReferencedNames(PHP_CodeSniffer_File $phpcsFile
124124
);
125125
continue;
126126
}
127-
$types[] = new ReferencedName(TokenHelper::getContent($phpcsFile, $nameStartPointer, $nameEndPointer), $nameStartPointer);
127+
128+
$nextTokenAfterEndPointer = TokenHelper::findNextNonWhitespace($phpcsFile, $nameEndPointer);
129+
$previousTokenBeforeStartPointer = TokenHelper::findPreviousNonWhitespace($phpcsFile, $nameStartPointer - 1);
130+
$type = ReferencedName::TYPE_DEFAULT;
131+
if ($nextTokenAfterEndPointer !== null && $previousTokenBeforeStartPointer !== null) {
132+
if ($tokens[$nextTokenAfterEndPointer]['code'] === T_OPEN_PARENTHESIS) {
133+
if ($tokens[$previousTokenBeforeStartPointer]['code'] !== T_NEW) {
134+
$type = ReferencedName::TYPE_FUNCTION;
135+
}
136+
} elseif ($tokens[$nextTokenAfterEndPointer]['code'] !== T_VARIABLE) {
137+
if (
138+
!in_array($tokens[$previousTokenBeforeStartPointer]['code'], [
139+
T_EXTENDS,
140+
T_IMPLEMENTS,
141+
T_INSTANCEOF,
142+
T_USE, // trait
143+
T_NEW,
144+
T_COLON, // return typehint
145+
], true)
146+
&& !in_array($tokens[$nextTokenAfterEndPointer]['code'], [
147+
T_DOUBLE_COLON,
148+
], true)
149+
) {
150+
$type = ReferencedName::TYPE_CONSTANT;
151+
}
152+
}
153+
}
154+
$types[] = new ReferencedName(TokenHelper::getContent($phpcsFile, $nameStartPointer, $nameEndPointer), $nameStartPointer, $type);
128155
$beginSearchAtPointer = $nameEndPointer + 1;
129156
}
130157
return $types;

SlevomatCodingStandard/Helpers/UseStatement.php

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
class UseStatement
66
{
77

8+
const TYPE_DEFAULT = ReferencedName::TYPE_DEFAULT;
9+
const TYPE_FUNCTION = ReferencedName::TYPE_FUNCTION;
10+
const TYPE_CONSTANT = ReferencedName::TYPE_CONSTANT;
11+
812
/** @var string */
913
private $nameAsReferencedInFile;
1014

@@ -17,21 +21,27 @@ class UseStatement
1721
/** @var integer */
1822
private $usePointer;
1923

24+
/** @var string */
25+
private $type;
26+
2027
/**
2128
* @param string $nameAsReferencedInFile
2229
* @param string $fullyQualifiedClassName
2330
* @param integer $usePointer T_USE pointer
31+
* @param string $type
2432
*/
2533
public function __construct(
2634
$nameAsReferencedInFile,
2735
$fullyQualifiedClassName,
28-
$usePointer
36+
$usePointer,
37+
$type
2938
)
3039
{
3140
$this->nameAsReferencedInFile = $nameAsReferencedInFile;
3241
$this->normalizedNameAsReferencedInFile = self::normalizedNameAsReferencedInFile($nameAsReferencedInFile);
3342
$this->fullyQualifiedTypeName = $fullyQualifiedClassName;
3443
$this->usePointer = $usePointer;
44+
$this->type = $type;
3545
}
3646

3747
/**
@@ -75,4 +85,44 @@ public function getPointer()
7585
return $this->usePointer;
7686
}
7787

88+
/**
89+
* @return boolean
90+
*/
91+
public function isConstant()
92+
{
93+
return $this->type === self::TYPE_CONSTANT;
94+
}
95+
96+
/**
97+
* @return boolean
98+
*/
99+
public function isFunction()
100+
{
101+
return $this->type === self::TYPE_FUNCTION;
102+
}
103+
104+
/**
105+
* @param self $that
106+
* @return boolean
107+
*/
108+
public function hasSameType(self $that)
109+
{
110+
return $this->type === $that->type;
111+
}
112+
113+
/**
114+
* @param self $that
115+
* @return integer
116+
*/
117+
public function compareByType(self $that)
118+
{
119+
$order = [
120+
self::TYPE_DEFAULT => 1,
121+
self::TYPE_CONSTANT => 2,
122+
self::TYPE_FUNCTION => 3,
123+
];
124+
125+
return $order[$this->type] - $order[$that->type];
126+
}
127+
78128
}

SlevomatCodingStandard/Helpers/UseStatementHelper.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ public static function getFullyQualifiedTypeNameFromUse(PHP_CodeSniffer_File $ph
7272
$nameEndPointer = TokenHelper::findPreviousExcluding($phpcsFile, [T_WHITESPACE], $nameEndPointer - 1) + 1;
7373
}
7474
$nameStartPointer = $phpcsFile->findNext(TokenHelper::$nameTokenCodes, $usePointer + 1, $nameEndPointer);
75+
if (in_array($tokens[$nameStartPointer]['content'], ['const', 'function'], true)) {
76+
$nameStartPointer = $phpcsFile->findNext(TokenHelper::$nameTokenCodes, $nameStartPointer + 1, $nameEndPointer);
77+
}
7578
$name = TokenHelper::getContent($phpcsFile, $nameStartPointer, $nameEndPointer);
7679

7780
return NamespaceHelper::normalizeToCanonicalName($name);
@@ -85,12 +88,23 @@ public static function getFullyQualifiedTypeNameFromUse(PHP_CodeSniffer_File $ph
8588
public static function getUseStatements(PHP_CodeSniffer_File $phpcsFile, $openTagPointer)
8689
{
8790
$names = [];
91+
$tokens = $phpcsFile->getTokens();
8892
foreach (self::getUseStatementPointers($phpcsFile, $openTagPointer) as $usePointer) {
93+
$nextTokenFromUsePointer = TokenHelper::findNextNonWhitespace($phpcsFile, $usePointer + 1);
94+
$type = UseStatement::TYPE_DEFAULT;
95+
if ($tokens[$nextTokenFromUsePointer]['code'] === T_STRING) {
96+
if ($tokens[$nextTokenFromUsePointer]['content'] === 'const') {
97+
$type = UseStatement::TYPE_CONSTANT;
98+
} elseif ($tokens[$nextTokenFromUsePointer]['content'] === 'function') {
99+
$type = UseStatement::TYPE_FUNCTION;
100+
}
101+
}
89102
$name = self::getNameAsReferencedInClassFromUse($phpcsFile, $usePointer);
90103
$useStatement = new UseStatement(
91104
$name,
92105
self::getFullyQualifiedTypeNameFromUse($phpcsFile, $usePointer),
93-
$usePointer
106+
$usePointer,
107+
$type
94108
);
95109
$names[$useStatement->getCanonicalNameAsReferencedInFile()] = $useStatement;
96110
}

SlevomatCodingStandard/Sniffs/Namespaces/AlphabeticallySortedUsesSniff.php

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ class AlphabeticallySortedUsesSniff implements \PHP_CodeSniffer_Sniff
1212

1313
const CODE_INCORRECT_ORDER = 'IncorrectlyOrderedUses';
1414

15-
/** @var string */
16-
private $lastUseTypeName;
15+
/** @var \SlevomatCodingStandard\Helpers\UseStatement */
16+
private $lastUse;
1717

1818
/**
1919
* @return integer[]
@@ -31,20 +31,19 @@ public function register()
3131
*/
3232
public function process(PHP_CodeSniffer_File $phpcsFile, $openTagPointer)
3333
{
34-
$this->lastUseTypeName = null;
34+
$this->lastUse = null;
3535
$useStatements = UseStatementHelper::getUseStatements(
3636
$phpcsFile,
3737
$openTagPointer
3838
);
3939
foreach ($useStatements as $useStatement) {
40-
$typeName = $useStatement->getFullyQualifiedTypeName();
41-
if ($this->lastUseTypeName === null) {
42-
$this->lastUseTypeName = $typeName;
40+
if ($this->lastUse === null) {
41+
$this->lastUse = $useStatement;
4342
} else {
44-
$order = $this->compareStrings($typeName, $this->lastUseTypeName);
43+
$order = $this->compareUseStatements($useStatement, $this->lastUse);
4544
if ($order < 0) {
4645
$fix = $phpcsFile->addFixableError(
47-
sprintf('Use statements are incorrectly ordered. The first wrong one is %s', $typeName),
46+
sprintf('Use statements are incorrectly ordered. The first wrong one is %s', $useStatement->getFullyQualifiedTypeName()),
4847
$useStatement->getPointer(),
4948
self::CODE_INCORRECT_ORDER
5049
);
@@ -54,7 +53,7 @@ public function process(PHP_CodeSniffer_File $phpcsFile, $openTagPointer)
5453

5554
return;
5655
} else {
57-
$this->lastUseTypeName = $typeName;
56+
$this->lastUse = $useStatement;
5857
}
5958
}
6059
}
@@ -78,7 +77,7 @@ private function fixAlphabeticalOrder(
7877
}
7978

8079
uasort($useStatements, function (UseStatement $a, UseStatement $b) {
81-
return $this->compareStrings($a->getFullyQualifiedTypeName(), $b->getFullyQualifiedTypeName());
80+
return $this->compareUseStatements($a, $b);
8281
});
8382

8483
$phpcsFile->fixer->addContent($firstUseStatement->getPointer(), implode(PHP_EOL, array_map(function (UseStatement $useStatement) {
@@ -93,34 +92,40 @@ private function fixAlphabeticalOrder(
9392
}
9493

9594
/**
96-
* @param string $a
97-
* @param string $b
95+
* @param \SlevomatCodingStandard\Helpers\UseStatement $a
96+
* @param \SlevomatCodingStandard\Helpers\UseStatement $b
9897
* @return integer
9998
*/
100-
private function compareStrings($a, $b)
99+
private function compareUseStatements(UseStatement $a, UseStatement $b)
101100
{
101+
if (!$a->hasSameType($b)) {
102+
return $a->compareByType($b);
103+
}
104+
$aName = $a->getFullyQualifiedTypeName();
105+
$bName = $b->getFullyQualifiedTypeName();
106+
102107
$i = 0;
103-
for (; $i < min(strlen($a), strlen($b)); $i++) {
104-
if ($this->isSpecialCharacter($a[$i]) && !$this->isSpecialCharacter($b[$i])) {
108+
for (; $i < min(strlen($aName), strlen($bName)); $i++) {
109+
if ($this->isSpecialCharacter($aName[$i]) && !$this->isSpecialCharacter($bName[$i])) {
105110
return -1;
106-
} elseif (!$this->isSpecialCharacter($a[$i]) && $this->isSpecialCharacter($b[$i])) {
111+
} elseif (!$this->isSpecialCharacter($aName[$i]) && $this->isSpecialCharacter($bName[$i])) {
107112
return 1;
108113
}
109114

110-
if (is_numeric($a[$i]) && is_numeric($b[$i])) {
115+
if (is_numeric($aName[$i]) && is_numeric($bName[$i])) {
111116
break;
112117
}
113118

114-
$cmp = strcasecmp($a[$i], $b[$i]);
119+
$cmp = strcasecmp($aName[$i], $bName[$i]);
115120
if (
116121
$cmp !== 0
117-
|| ($a[$i] !== $b[$i] && strtolower($a[$i]) === strtolower($b[$i]))
122+
|| ($aName[$i] !== $bName[$i] && strtolower($aName[$i]) === strtolower($bName[$i]))
118123
) {
119124
return $cmp;
120125
}
121126
}
122127

123-
return strnatcasecmp(substr($a, $i), substr($b, $i));
128+
return strnatcasecmp(substr($aName, $i), substr($bName, $i));
124129
}
125130

126131
/**

SlevomatCodingStandard/Sniffs/Namespaces/FullyQualifiedExceptionsSniff.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public function process(PHP_CodeSniffer_File $phpcsFile, $openTagPointer)
5555
$pointer = $referencedName->getPointer();
5656
$name = $referencedName->getNameAsReferencedInFile();
5757
$normalizedName = UseStatement::normalizedNameAsReferencedInFile($name);
58-
if (isset($useStatements[$normalizedName])) {
58+
if (isset($useStatements[$normalizedName]) && $referencedName->hasSameUseStatementType($useStatements[$normalizedName])) {
5959
$useStatement = $useStatements[$normalizedName];
6060
if (
6161
!StringHelper::endsWith($useStatement->getFullyQualifiedTypeName(), 'Exception')

SlevomatCodingStandard/Sniffs/Namespaces/UnusedUsesSniff.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ public function process(PHP_CodeSniffer_File $phpcsFile, $openTagPointer)
4646
!NamespaceHelper::isFullyQualifiedName($name)
4747
&& isset($unusedNames[$normalizedNameAsReferencedInFile])
4848
) {
49+
if (!$referencedName->hasSameUseStatementType($unusedNames[$normalizedNameAsReferencedInFile])) {
50+
continue;
51+
}
4952
if ($unusedNames[$normalizedNameAsReferencedInFile]->getNameAsReferencedInFile() !== $nameAsReferencedInFile) {
5053
$phpcsFile->addError(sprintf(
5154
'Case of reference name %s and use statement %s do not match',

build.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
>
3131
<arg value="--exclude"/>
3232
<arg path="tests/Helpers/data/php7"/>
33+
<arg value="--exclude"/>
34+
<arg path="tests/Sniffs/Namespaces/data/php7"/>
3335
<arg path="SlevomatCodingStandard" />
3436
<arg path="tests" />
3537
</exec>

0 commit comments

Comments
 (0)