First implementation

This commit is contained in:
ErickSkrauch
2018-04-17 21:28:51 +03:00
parent c601056af1
commit 0bb3e80827
18 changed files with 4772 additions and 2 deletions

17
src/Config.php Normal file
View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Ely\CS;
use PhpCsFixer\Config as PhpCsFixerConfig;
class Config {
public static function create(array $overwrittenRules = []): PhpCsFixerConfig {
return PhpCsFixerConfig::create()
->setRiskyAllowed(true)
->registerCustomFixers(new Fixers())
->setRules(Rules::create($overwrittenRules));
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Ely\CS\Fixer;
abstract class AbstractFixer extends \PhpCsFixer\AbstractFixer {
/**
* {@inheritdoc}
*/
public function getName() {
return sprintf('Ely/%s', parent::getName());
}
}

View File

@ -0,0 +1,179 @@
<?php
declare(strict_types=1);
namespace Ely\CS\Fixer\Operator;
use Ely\CS\Fixer\AbstractFixer;
use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
/**
* This is the extended version of the original new_with_braces fixer.
* It allows you to remove braces around an anonymous class declaration in a case
* when said class constructor doesn't contain any arguments.
*
* @url https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/5c5de791ab/src/Fixer/Operator/NewWithBracesFixer.php
*
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*/
final class NewWithBracesFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface {
/**
* {@inheritdoc}
*/
public function getDefinition() {
return new FixerDefinition(
'All instances created with new keyword must be followed by braces.',
[
new CodeSample("<?php \$x = new X;\n"),
]
);
}
/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens) {
return $tokens->isTokenKindFound(T_NEW);
}
/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens) {
static $nextTokenKinds = null;
if ($nextTokenKinds === null) {
$nextTokenKinds = [
'?',
';',
',',
'(',
')',
'[',
']',
':',
'<',
'>',
'+',
'-',
'*',
'/',
'%',
'&',
'^',
'|',
[T_CLASS],
[T_IS_SMALLER_OR_EQUAL],
[T_IS_GREATER_OR_EQUAL],
[T_IS_EQUAL],
[T_IS_NOT_EQUAL],
[T_IS_IDENTICAL],
[T_IS_NOT_IDENTICAL],
[T_CLOSE_TAG],
[T_LOGICAL_AND],
[T_LOGICAL_OR],
[T_LOGICAL_XOR],
[T_BOOLEAN_AND],
[T_BOOLEAN_OR],
[T_SL],
[T_SR],
[T_INSTANCEOF],
[T_AS],
[T_DOUBLE_ARROW],
[T_POW],
[CT::T_ARRAY_SQUARE_BRACE_OPEN],
[CT::T_ARRAY_SQUARE_BRACE_CLOSE],
[CT::T_BRACE_CLASS_INSTANTIATION_OPEN],
[CT::T_BRACE_CLASS_INSTANTIATION_CLOSE],
];
if (defined('T_SPACESHIP')) {
$nextTokenKinds[] = [T_SPACESHIP];
}
}
for ($index = $tokens->count() - 3; $index > 0; --$index) {
$token = $tokens[$index];
if (!$token->isGivenKind(T_NEW)) {
continue;
}
$nextIndex = $tokens->getNextTokenOfKind($index, $nextTokenKinds);
$nextToken = $tokens[$nextIndex];
// new anonymous class definition
if ($nextToken->isGivenKind(T_CLASS)) {
if ($this->configuration['remove_for_anonymous_classes']) {
$nextTokenIndex = $tokens->getNextMeaningfulToken($nextIndex);
$nextNextTokenIndex = $tokens->getNextMeaningfulToken($nextTokenIndex);
if ($tokens[$nextTokenIndex]->equals('(') && $tokens[$nextNextTokenIndex]->equals(')')) {
$this->removeBracesAfter($tokens, $nextIndex);
}
} else {
if (!$tokens[$tokens->getNextMeaningfulToken($nextIndex)]->equals('(')) {
$this->insertBracesAfter($tokens, $nextIndex);
}
}
continue;
}
// entrance into array index syntax - need to look for exit
while ($nextToken->equals('[')) {
$nextIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_INDEX_SQUARE_BRACE, $nextIndex) + 1;
$nextToken = $tokens[$nextIndex];
}
// new statement has a gap in it - advance to the next token
if ($nextToken->isWhitespace()) {
$nextIndex = $tokens->getNextNonWhitespace($nextIndex);
$nextToken = $tokens[$nextIndex];
}
// new statement with () - nothing to do
if ($nextToken->equals('(')) {
continue;
}
$this->insertBracesAfter($tokens, $tokens->getPrevMeaningfulToken($nextIndex));
}
}
/**
* {@inheritdoc}
*/
protected function createConfigurationDefinition() {
return new FixerConfigurationResolver([
(new FixerOptionBuilder('remove_for_anonymous_classes', 'when enabled will remove braces around an anonymous class declaration in a case when constructor doesn\'t contain any arguments'))
->setAllowedTypes(['bool'])
->setDefault(false)
->getOption(),
]);
}
/**
* @param Tokens $tokens
* @param int $index
*/
private function insertBracesAfter(Tokens $tokens, $index) {
$tokens->insertAt(++$index, [new Token('('), new Token(')')]);
}
/**
* @param Tokens $tokens
* @param int $index
*/
private function removeBracesAfter(Tokens $tokens, int $index) {
$tokens->clearRange(
$tokens->getNextTokenOfKind($index, ['(']),
$tokens->getNextTokenOfKind($index, [')'])
);
}
}

View File

@ -0,0 +1,150 @@
<?php
declare(strict_types=1);
namespace Ely\CS\Fixer\Whitespace;
use Ely\CS\Fixer\AbstractFixer;
use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface;
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\Tokenizer\TokensAnalyzer;
use PhpCsFixer\Utils;
/**
* This is copy of the PR https://github.com/FriendsOfPHP/PHP-CS-Fixer/pull/3688
*
* @author ErickSkrauch <erickskrauch@ely.by>
*/
final class BlankLineAroundClassBodyFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface {
/**
* {@inheritdoc}
*/
public function getDefinition() {
return new FixerDefinition(
'Ensure that class body contains one blank line after class definition and before its end.',
[
new CodeSample(
'<?php
class Sample
{
protected function foo()
{
}
}
'
),
new CodeSample(
'<?php
new class extends Foo {
protected function foo()
{
}
};
',
['apply_to_anonymous_classes' => false]
),
new CodeSample(
'<?php
new class extends Foo {
protected function foo()
{
}
};
',
['apply_to_anonymous_classes' => true]
),
]
);
}
/**
* {@inheritdoc}
*/
public function getPriority() {
// should be run after the BracesFixer
return -26;
}
/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens) {
return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds());
}
/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens) {
$analyzer = new TokensAnalyzer($tokens);
foreach ($tokens as $index => $token) {
if (!$token->isClassy()) {
continue;
}
$countLines = $this->configuration['blank_lines_count'];
if (!$this->configuration['apply_to_anonymous_classes'] && $analyzer->isAnonymousClass($index)) {
$countLines = 0;
}
$startBraceIndex = $tokens->getNextTokenOfKind($index, ['{']);
if ($tokens[$startBraceIndex + 1]->isWhitespace()) {
$nextStatementIndex = $tokens->getNextMeaningfulToken($startBraceIndex);
// Traits should be placed right after a class opening brace,
if ($tokens[$nextStatementIndex]->getContent() !== 'use') {
$this->fixBlankLines($tokens, $startBraceIndex + 1, $countLines);
}
}
$endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startBraceIndex);
if ($tokens[$endBraceIndex - 1]->isWhitespace()) {
$this->fixBlankLines($tokens, $endBraceIndex - 1, $countLines);
}
}
}
/**
* {@inheritdoc}
*/
protected function createConfigurationDefinition() {
return new FixerConfigurationResolver([
(new FixerOptionBuilder('blank_lines_count', 'adjusts an amount of the blank lines.'))
->setAllowedTypes(['int'])
->setDefault(1)
->getOption(),
(new FixerOptionBuilder('apply_to_anonymous_classes', 'whether this fixer should be applied to anonymous classes.'))
->setAllowedTypes(['bool'])
->setDefault(true)
->getOption(),
]);
}
/**
* Cleanup a whitespace token.
*
* @param Tokens $tokens
* @param int $index
* @param int $countLines
*/
private function fixBlankLines(Tokens $tokens, $index, $countLines) {
$content = $tokens[$index]->getContent();
// Apply fix only in the case when the count lines do not equals to expected
if (substr_count($content, "\n") === $countLines + 1) {
return;
}
// The final bit of the whitespace must be the next statement's indentation
$lines = Utils::splitLines($content);
$eol = $this->whitespacesConfig->getLineEnding();
$tokens[$index] = new Token([T_WHITESPACE, str_repeat($eol, $countLines + 1) . end($lines)]);
}
}

View File

@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
namespace Ely\CS\Fixer\Whitespace;
use Ely\CS\Fixer\AbstractFixer;
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use SplFileInfo;
/**
* This is extended version of the original `blank_line_before_statement` fixer.
* It applies only to `return` statements and only in cases, when on the current nesting level more than one statements.
*
* @url https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/5c5de791ab/src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php
*
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
* @author Andreas Möller <am@localheinz.com>
* @author SpacePossum
*/
final class BlankLineBeforeReturnFixer extends AbstractFixer implements WhitespacesAwareFixerInterface {
/**
* {@inheritdoc}
*/
public function getDefinition() {
return new FixerDefinition(
'An empty line feed should precede a return statement.',
[new CodeSample("<?php\nfunction A()\n{\n echo 1;\n return 1;\n}\n")]
);
}
/**
* @inheritdoc
*/
public function isCandidate(Tokens $tokens) {
return $tokens->isTokenKindFound(T_RETURN);
}
/**
* {@inheritdoc}
*/
public function getPriority() {
// should be run after NoUselessReturnFixer, ClassDefinitionFixer and BracesFixer
return -26;
}
/**
* @inheritdoc
*/
protected function applyFix(SplFileInfo $file, Tokens $tokens) {
for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) {
$token = $tokens[$index];
if (!$token->isGivenKind(T_RETURN)) {
continue;
}
$eol = $this->whitespacesConfig->getLineEnding();
$prevNonWhitespaceToken = $tokens[$tokens->getPrevNonWhitespace($index)];
if (!$prevNonWhitespaceToken->equalsAny([';', '}'])) {
continue;
}
$prevIndex = $index - 1;
$prevToken = $tokens[$prevIndex];
if ($prevToken->isWhitespace()) {
$countParts = substr_count($prevToken->getContent(), "\n");
if ($countParts === 0) {
$tokens[$prevIndex] = new Token([T_WHITESPACE, rtrim($prevToken->getContent(), " \t") . $eol . $eol]);
} elseif ($countParts === 1) {
$backwardIndex = $prevIndex;
do {
if (--$backwardIndex < 0) {
break;
}
$backwardToken = $tokens[$backwardIndex];
if ($backwardToken->getContent() === '{') {
break;
}
if ($backwardToken->isWhitespace()) {
$countParts += substr_count($backwardToken->getContent(), "\n");
}
} while ($countParts < 3);
if ($countParts !== 2) {
$tokens[$prevIndex] = new Token([T_WHITESPACE, $eol . $prevToken->getContent()]);
}
}
} else {
$tokens->insertAt($index, new Token([T_WHITESPACE, $eol . $eol]));
++$index;
++$limit;
}
}
}
}

View File

@ -0,0 +1,163 @@
<?php
declare(strict_types=1);
namespace Ely\CS\Fixer\Whitespace;
use Ely\CS\Fixer\AbstractFixer;
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\Utils;
use SplFileInfo;
/**
* This is rewritten version of the original fixer created by @PedroTroller with improved cases validation and
* targeted to the PHP-CS-Fixer 2.11 version.
*
* @url https://github.com/PedroTroller/PhpCSFixer-Custom-Fixers/blob/affdf99f51/src/PedroTroller/CS/Fixer/CodingStyle/LineBreakBetweenStatementsFixer.php
*
* @author ErickSkrauch <erickskrauch@ely.by>
*/
final class LineBreakAfterStatementsFixer extends AbstractFixer implements WhitespacesAwareFixerInterface {
/**
* There is no 'do', 'cause the processing of the 'while' also includes do {} while (); construction
*/
const STATEMENTS = [
T_IF,
T_SWITCH,
T_FOR,
T_FOREACH,
T_WHILE,
];
/**
* @inheritdoc
*/
public function getDefinition() {
return new FixerDefinition(
'Ensures that there is one blank line above the control statements',
[
new CodeSample(
'<?php
class Foo
{
/**
* @return null
*/
public function foo() {
do {
// ...
} while (true);
foreach (["foo", "bar"] as $str) {
// ...
}
if (true === false) {
// ...
}
foreach (["foo", "bar"] as $str) {
if ($str === "foo") {
// smth
}
}
while (true) {
// ...
}
switch("123") {
case "123":
break;
}
$a = "next statement";
}
}
'
),
]
);
}
/**
* @inheritdoc
*/
public function isCandidate(Tokens $tokens) {
return $tokens->isAnyTokenKindsFound(self::STATEMENTS);
}
protected function applyFix(SplFileInfo $file, Tokens $tokens) {
foreach ($tokens as $index => $token) {
if (!$token->isGivenKind(self::STATEMENTS)) {
continue;
}
$endStatementIndex = $this->findStatementEnd($tokens, $index);
$nextStatementIndex = $tokens->getNextNonWhitespace($endStatementIndex);
if ($nextStatementIndex === null) {
continue;
}
if ($tokens[$nextStatementIndex]->equals('}')) {
$this->fixBlankLines($tokens, $endStatementIndex + 1, 0);
continue;
}
$this->fixBlankLines($tokens, $endStatementIndex + 1, 1);
}
}
private function fixBlankLines(Tokens $tokens, $index, $countLines) {
$content = $tokens[$index]->getContent();
// Apply fix only in the case when the count lines do not equals to expected
if (substr_count($content, "\n") === $countLines + 1) {
return;
}
// The final bit of the whitespace must be the next statement's indentation
$lines = Utils::splitLines($content);
$eol = $this->whitespacesConfig->getLineEnding();
$tokens[$index] = new Token([T_WHITESPACE, str_repeat($eol, $countLines + 1) . end($lines)]);
}
private function findStatementEnd(Tokens $tokens, int $index): int {
$nextIndex = $tokens->getNextMeaningfulToken($index);
$nextToken = $tokens[$nextIndex];
if ($nextToken->equals('(')) {
$parenthesisEndIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $nextIndex);
$possibleStartBraceIndex = $tokens->getNextNonWhitespace($parenthesisEndIndex);
} else {
$possibleStartBraceIndex = $nextIndex;
}
// `do {} while ();`
if ($tokens[$index]->isGivenKind(T_WHILE) && $tokens[$possibleStartBraceIndex]->equals(';')) {
return $possibleStartBraceIndex;
}
$blockEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $possibleStartBraceIndex);
$nextStatementIndex = $tokens->getNextMeaningfulToken($blockEnd);
if ($nextStatementIndex === null) {
return $blockEnd;
}
// `if () {} elseif {}`
if ($tokens[$nextStatementIndex]->isGivenKind(T_ELSEIF)) {
return $this->findStatementEnd($tokens, $nextStatementIndex);
}
// `if () {} else if {}` or simple `if () {} else {}`
if ($tokens[$nextStatementIndex]->isGivenKind(T_ELSE)) {
$nextNextStatementIndex = $tokens->getNextMeaningfulToken($nextStatementIndex);
if ($tokens[$nextNextStatementIndex]->isGivenKind(T_IF)) {
return $this->findStatementEnd($tokens, $nextNextStatementIndex);
}
return $this->findStatementEnd($tokens, $nextStatementIndex);
}
return $blockEnd;
}
}

39
src/Fixers.php Normal file
View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Ely\CS;
use ArrayIterator;
use IteratorAggregate;
use PhpCsFixer\Finder;
use PhpCsFixer\Fixer\FixerInterface;
use ReflectionClass;
use Traversable;
class Fixers implements IteratorAggregate {
public function getIterator(): Traversable {
$finder = new Finder();
$finder->in(__DIR__ . '/Fixer')->name('*.php');
$classes = [];
foreach ($finder as $file) {
$class = '\\Ely\\CS' . str_replace('/', '\\', mb_substr($file->getPathname(), mb_strlen(__DIR__), -4));
if (!class_exists($class)) {
continue;
}
/** @noinspection PhpUnhandledExceptionInspection */
$rfl = new ReflectionClass($class);
if (!$rfl->implementsInterface(FixerInterface::class) || $rfl->isAbstract()) {
continue;
}
$classes[] = $class;
}
return new ArrayIterator(array_map(function($class) {
return new $class();
}, $classes));
}
}

74
src/Rules.php Normal file
View File

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Ely\CS;
class Rules {
private static $rules = [
'@PSR2' => true,
'binary_operator_spaces' => true,
'braces' => [
'position_after_functions_and_oop_constructs' => 'same',
],
'cast_spaces' => [
'space' => 'none',
],
'class_attributes_separation' => [
'elements' => ['method', 'property'],
],
'compact_nullable_typehint' => true,
'concat_space' => [
'spacing' => 'one',
],
'declare_equal_normalize' => true,
'function_declaration' => [
'closure_function_spacing' => 'none',
],
'function_to_constant' => true,
'include' => true,
'linebreak_after_opening_tag' => true,
'method_chaining_indentation' => true,
'modernize_types_casting' => true,
'no_short_bool_cast' => true,
'no_trailing_comma_in_singleline_array' => true,
'no_unneeded_final_method' => true,
'no_unused_imports' => true,
'no_useless_else' => true,
'no_whitespace_before_comma_in_array' => true,
'no_whitespace_in_blank_line' => true,
'non_printable_character' => [
'use_escape_sequences_in_strings' => true,
],
'object_operator_without_whitespace' => true,
'ordered_class_elements' => true,
'ordered_imports' => true,
'random_api_migration' => true,
'return_type_declaration' => [
'space_before' => 'none',
],
'single_quote' => true,
'strict_comparison' => true,
'ternary_operator_spaces' => true,
'ternary_to_null_coalescing' => true,
'trailing_comma_in_multiline_array' => true,
'visibility_required' => [
'elements' => ['property', 'method', 'const'],
],
'whitespace_after_comma_in_array' => true,
// Our custom or extended fixers
'Ely/blank_line_around_class_body' => [
'apply_to_anonymous_classes' => false,
],
'Ely/blank_line_before_return' => true,
'Ely/line_break_after_statements' => true,
'Ely/new_with_braces' => [
'remove_for_anonymous_classes' => true,
],
];
public static function create(array $overwrittenRules = []): array {
return array_merge(self::$rules, $overwrittenRules);
}
}