-
Notifications
You must be signed in to change notification settings - Fork 10
add configurable order function for Alphabetical Use Statement Sniff, fixes #5 #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,26 +16,55 @@ | |
/** | ||
* Alphabetical Use Statements sniff. | ||
* | ||
* Use statements must be in alphabetical order, grouped by empty lines | ||
* Use statements must be in alphabetical order, grouped by empty lines. | ||
* | ||
* @category PHP | ||
* @package PHP_CodeSniffer-MO4 | ||
* @author Xaver Loppenstedt <xaver@loppenstedt.de> | ||
* @author Steffen Ritter <steffenritter1@gmail.com> | ||
* @author Christian Albrecht <christian.albrecht@mayflower.de> | ||
* @copyright 2013-2014 Xaver Loppenstedt, some rights reserved. | ||
* @copyright 2013-2017 Xaver Loppenstedt, some rights reserved. | ||
* @license http://spdx.org/licenses/MIT MIT License | ||
* @link https://github.com/Mayflower/mo4-coding-standard | ||
*/ | ||
|
||
namespace MO4\Sniffs\Formatting; | ||
|
||
use PHP_CodeSniffer\Exceptions\RuntimeException; | ||
use PHP_CodeSniffer\Files\File; | ||
use PHP_CodeSniffer\Standards\PSR2\Sniffs\Namespaces\UseDeclarationSniff; | ||
use PHP_CodeSniffer\Util\Common; | ||
use PHP_CodeSniffer\Util\Tokens as PHP_CodeSniffer_Tokens; | ||
|
||
class AlphabeticalUseStatementsSniff extends UseDeclarationSniff | ||
{ | ||
|
||
const NAMESPACE_SEPRATOR_STRING = '\\'; | ||
|
||
/** | ||
* Sorting order, can be one of: | ||
* 'dictionary', 'string', 'string-locale' or 'string-case-insensitive' | ||
* | ||
* Unknown types will be mapped to 'string'. | ||
* | ||
* @var string | ||
*/ | ||
public $order = 'dictionary'; | ||
|
||
|
||
/** | ||
* Supported ordering methods | ||
* | ||
* @var array | ||
*/ | ||
private $supportedOrderingMethods = [ | ||
'dictionary', | ||
'string', | ||
'string', | ||
'string-locale', | ||
'string-case-insensitive', | ||
]; | ||
|
||
/** | ||
* Last import seen in group | ||
* | ||
|
@@ -58,6 +87,30 @@ class AlphabeticalUseStatementsSniff extends UseDeclarationSniff | |
private $currentFile = null; | ||
|
||
|
||
/** | ||
* Returns an array of tokens this test wants to listen for. | ||
* | ||
* @return array | ||
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException | ||
*/ | ||
public function register() | ||
{ | ||
if (in_array($this->order, $this->supportedOrderingMethods) === false) { | ||
$error = sprintf( | ||
"'%s' is not a valid order function for %s! Pick one of: %s", | ||
$this->order, | ||
Common::getSniffCode(__CLASS__), | ||
implode(', ', $this->supportedOrderingMethods) | ||
); | ||
|
||
throw new RuntimeException($error); | ||
} | ||
|
||
return parent::register(); | ||
|
||
}//end register() | ||
|
||
|
||
/** | ||
* Processes this test, when one of its tokens is encountered. | ||
* | ||
|
@@ -99,11 +152,11 @@ public function process(File $phpcsFile, $stackPtr) | |
|
||
$fixable = false; | ||
if ($this->lastImport !== '' | ||
&& strcmp($this->lastImport, $currentImport) > 0 | ||
&& $this->compareString($this->lastImport, $currentImport) > 0 | ||
) { | ||
$msg = 'USE statements must be sorted alphabetically'; | ||
$msg = 'USE statements must be sorted alphabetically, order %s'; | ||
$code = 'MustBeSortedAlphabetically'; | ||
$fixable = $phpcsFile->addFixableError($msg, $currentPtr, $code); | ||
$fixable = $phpcsFile->addFixableError($msg, $currentPtr, $code, [$this->order]); | ||
} | ||
|
||
if (true === $fixable) { | ||
|
@@ -280,12 +333,82 @@ private function findNewDestination( | |
$prevLine = $tokens[$prevPtr]['line']; | ||
$prevImportArr = $this->getUseImport($phpcsFile, $prevPtr); | ||
} while ($prevLine === ($line - 1) | ||
&& (strcmp($prevImportArr['content'], $import) > 0) | ||
&& ($this->compareString($prevImportArr['content'], $import) > 0) | ||
); | ||
|
||
return $ptr; | ||
|
||
}//end findNewDestination() | ||
|
||
|
||
/** | ||
* Compare namespace strings according defined order function. | ||
* | ||
* @param string $a first namespace string | ||
* @param string $b second namespace string | ||
* | ||
* @return int | ||
*/ | ||
private function compareString($a, $b) | ||
{ | ||
if ('dictionary' === $this->order) { | ||
return $this->dictionaryCompare($a, $b); | ||
} else if ('string' === $this->order) { | ||
return strcmp($a, $b); | ||
} else if ('string-locale' === $this->order) { | ||
return strcoll($a, $b); | ||
} else if ('string-case-insensitive' === $this->order) { | ||
return strcasecmp($a, $b); | ||
} else { | ||
return $this->dictionaryCompare($a, $b); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method falls back to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added the check to |
||
} | ||
|
||
}//end compareString() | ||
|
||
|
||
/** | ||
* Lexicographical namespace string compare. | ||
* | ||
* Example: | ||
* | ||
* use Doctrine\ORM\Query; | ||
* use Doctrine\ORM\Query\Expr; | ||
* use Doctrine\ORM\QueryBuilder; | ||
* | ||
* @param string $a first namespace string | ||
* @param string $b second namespace string | ||
* | ||
* @return int | ||
*/ | ||
private function dictionaryCompare($a, $b) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @aoldemeier could you check the correctness of this function? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After having realized I was confused about the signature of substr I am now convinced that the function is correct (making common sense assumptions about how dictionaryCompare should work)... |
||
{ | ||
$min = min(strlen($a), strlen($b)); | ||
|
||
for ($i = 0; $i < $min; $i++) { | ||
if ($a[$i] === $b[$i]) { | ||
continue; | ||
} | ||
|
||
if ($a[$i] === self::NAMESPACE_SEPRATOR_STRING) { | ||
return -1; | ||
} | ||
|
||
if ($b[$i] === self::NAMESPACE_SEPRATOR_STRING) { | ||
return 1; | ||
} | ||
|
||
if ($a[$i] < $b[$i]) { | ||
return -1; | ||
} | ||
|
||
if ($a[$i] > $b[$i]) { | ||
return 1; | ||
} | ||
}//end for | ||
|
||
return strcmp(substr($a, $min), substr($b, $min)); | ||
|
||
}//end dictionaryCompare() | ||
|
||
|
||
}//end class |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
|
||
use A1\B\C; | ||
use A1\B; | ||
use A1\BD; | ||
|
||
use A2\BD; | ||
use A2\B; | ||
use A2\B\C; | ||
|
||
use A3\B; | ||
use A3\BD; | ||
use A3\B\C; | ||
|
||
use A4\B\C; | ||
use A4\BD; | ||
use A4\B; | ||
|
||
use A5\BD; | ||
use A5\B\C; | ||
use A5\B; | ||
|
||
class Foo | ||
{ | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
|
||
use A1\B; | ||
use A1\B\C; | ||
use A1\BD; | ||
|
||
use A2\B; | ||
use A2\B\C; | ||
use A2\BD; | ||
|
||
use A3\B; | ||
use A3\B\C; | ||
use A3\BD; | ||
|
||
use A4\B; | ||
use A4\B\C; | ||
use A4\BD; | ||
|
||
use A5\B; | ||
use A5\B\C; | ||
use A5\BD; | ||
|
||
class Foo | ||
{ | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: "SEPRATOR" ->"SEPARATOR"