mirror of
https://github.com/elyby/php-code-style.git
synced 2024-11-29 10:12:07 +05:30
Fixes #12. Implemented Ely\align_multiline_parameters
This commit is contained in:
parent
4a4f556d7b
commit
6956e0271e
@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- Enh #12: Implemented `Ely\align_multiline_parameters` fixer.
|
||||
- Enabled `Ely\align_multiline_parameters` for Ely.by codestyle in `['types' => false, 'defaults' => false]` mode.
|
||||
|
||||
### Fixed
|
||||
- Bug #10: `Ely/blank_line_before_return` don't treat interpolation curly bracket as beginning of the scope.
|
||||
- Bug #9: `Ely/line_break_after_statements` add space before next meaningful line of code and skip comments.
|
||||
|
52
README.md
52
README.md
@ -43,12 +43,14 @@ vendor/bin/php-cs-fixer fix
|
||||
### Configuration
|
||||
|
||||
You can pass a custom set of rules to the `\Ely\CS\Config::create()` call. For example, it can be used to validate a
|
||||
project with PHP 7.0 compatibility:
|
||||
project with PHP 7.4 compatibility:
|
||||
|
||||
```php
|
||||
<?php
|
||||
return \Ely\CS\Config::create([
|
||||
'visibility_required' => ['property', 'method'],
|
||||
'trailing_comma_in_multiline' => [
|
||||
'elements' => ['arrays', 'arguments'],
|
||||
],
|
||||
])->setFinder($finder);
|
||||
```
|
||||
|
||||
@ -76,12 +78,15 @@ class Foo extends Bar implements FooInterface {
|
||||
private const SAMPLE_1 = 123;
|
||||
private const SAMPLE_2 = 321;
|
||||
|
||||
public $field1;
|
||||
public Typed $field1;
|
||||
|
||||
public $field2;
|
||||
|
||||
public function sampleFunction(int $a, int $b = null): array {
|
||||
if ($a === $b) {
|
||||
public function sampleFunction(
|
||||
int $a,
|
||||
private readonly int $b = null,
|
||||
): array {
|
||||
if ($a === $this->b) {
|
||||
$result = bar();
|
||||
} else {
|
||||
$result = BazClass::bar($this->field1, $this->field2);
|
||||
@ -89,7 +94,7 @@ class Foo extends Bar implements FooInterface {
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
public function setToNull(): self {
|
||||
$this->field1 = null;
|
||||
return $this;
|
||||
@ -154,6 +159,16 @@ class Foo extends Bar implements FooInterface {
|
||||
echo 'the next statement is here';
|
||||
```
|
||||
|
||||
* There MUST be no alignment around multiline function parameters.
|
||||
|
||||
```php
|
||||
<?php
|
||||
function foo(
|
||||
string $input,
|
||||
int $key = 0,
|
||||
): void {}
|
||||
```
|
||||
|
||||
## Using our fixers
|
||||
|
||||
First of all, you must install Ely.by PHP-CS-Fixer package as described in the [installation chapter](#installation).
|
||||
@ -169,6 +184,31 @@ return \PhpCsFixer\Config::create()
|
||||
|
||||
And then you'll be able to use our custom rules.
|
||||
|
||||
### `Ely/align_multiline_parameters`
|
||||
|
||||
Forces aligned or not aligned multiline function parameters:
|
||||
|
||||
```diff
|
||||
--- Original
|
||||
+++ New
|
||||
@@ @@
|
||||
function foo(
|
||||
string $string,
|
||||
- int $index = 0,
|
||||
- $arg = 'no type',
|
||||
+ int $index = 0,
|
||||
+ $arg = 'no type',
|
||||
): void {}
|
||||
```
|
||||
|
||||
**Configuration:**
|
||||
|
||||
* `variables` - when set to `true`, forces variables alignment. On `false` forces strictly no alignment.
|
||||
You can set it to `null` to disable touching of variables. **Default**: `true`.
|
||||
|
||||
* `defaults` - when set to `true`, forces defaults alignment. On `false` forces strictly no alignment.
|
||||
You can set it to `null` to disable touching of defaults. **Default**: `false`.
|
||||
|
||||
### `Ely/blank_line_around_class_body`
|
||||
|
||||
Ensure that a class body contains one blank line after its definition and before its end:
|
||||
|
165
src/Fixer/FunctionNotation/AlignMultilineParametersFixer.php
Normal file
165
src/Fixer/FunctionNotation/AlignMultilineParametersFixer.php
Normal file
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Ely\CS\Fixer\FunctionNotation;
|
||||
|
||||
use Ely\CS\Fixer\AbstractFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
use SplFileInfo;
|
||||
|
||||
final class AlignMultilineParametersFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface {
|
||||
|
||||
private const C_VARIABLES = 'variables';
|
||||
private const C_DEFAULTS = 'defaults';
|
||||
|
||||
public function getDefinition(): FixerDefinitionInterface {
|
||||
return new FixerDefinition(
|
||||
'Aligns parameters in multiline function declaration.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
function test(
|
||||
string $a,
|
||||
int $b = 0
|
||||
): void {};
|
||||
',
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
function test(
|
||||
string $string,
|
||||
int $int = 0
|
||||
): void {};
|
||||
',
|
||||
[self::C_VARIABLES => false, self::C_DEFAULTS => false],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function isCandidate(Tokens $tokens): bool {
|
||||
return $tokens->isAnyTokenKindsFound([T_FUNCTION, T_FN]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Must run after StatementIndentationFixer, MethodArgumentSpaceFixer
|
||||
*/
|
||||
public function getPriority(): int {
|
||||
return -10;
|
||||
}
|
||||
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface {
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder(self::C_VARIABLES, 'on null no value alignment, on bool forces alignment'))
|
||||
->setAllowedTypes(['bool', 'null'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder(self::C_DEFAULTS, 'on null no value alignment, on bool forces alignment'))
|
||||
->setAllowedTypes(['bool', 'null'])
|
||||
->setDefault(null)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function applyFix(SplFileInfo $file, Tokens $tokens): void {
|
||||
// There is nothing to do
|
||||
if ($this->configuration[self::C_VARIABLES] === null && $this->configuration[self::C_DEFAULTS] === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
$functionsAnalyzer = new FunctionsAnalyzer();
|
||||
/** @var \PhpCsFixer\Tokenizer\Token $functionToken */
|
||||
foreach ($tokens as $i => $functionToken) {
|
||||
if (!$functionToken->isGivenKind([T_FUNCTION, T_FN])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$openBraceIndex = $tokens->getNextTokenOfKind($i, ['(']);
|
||||
$isMultiline = $tokensAnalyzer->isBlockMultiline($tokens, $openBraceIndex);
|
||||
if (!$isMultiline) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var \PhpCsFixer\Tokenizer\Analyzer\Analysis\ArgumentAnalysis[] $arguments */
|
||||
$arguments = $functionsAnalyzer->getFunctionArguments($tokens, $i);
|
||||
if (empty($arguments)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$longestType = 0;
|
||||
$longestVariableName = 0;
|
||||
$hasAtLeastOneTypedArgument = false;
|
||||
foreach ($arguments as $argument) {
|
||||
$typeAnalysis = $argument->getTypeAnalysis();
|
||||
if ($typeAnalysis) {
|
||||
$hasAtLeastOneTypedArgument = true;
|
||||
$typeLength = strlen($typeAnalysis->getName());
|
||||
if ($typeLength > $longestType) {
|
||||
$longestType = $typeLength;
|
||||
}
|
||||
}
|
||||
|
||||
$variableNameLength = strlen($argument->getName());
|
||||
if ($variableNameLength > $longestVariableName) {
|
||||
$longestVariableName = $variableNameLength;
|
||||
}
|
||||
}
|
||||
|
||||
$argsIndent = WhitespacesAnalyzer::detectIndent($tokens, $i) . $this->whitespacesConfig->getIndent();
|
||||
foreach ($arguments as $argument) {
|
||||
if ($this->configuration[self::C_VARIABLES] !== null) {
|
||||
$whitespaceIndex = $argument->getNameIndex() - 1;
|
||||
if ($this->configuration[self::C_VARIABLES] === true) {
|
||||
$typeLen = 0;
|
||||
if ($argument->getTypeAnalysis() !== null) {
|
||||
$typeLen = strlen($argument->getTypeAnalysis()->getName());
|
||||
}
|
||||
|
||||
$appendix = str_repeat(' ', $longestType - $typeLen + (int)$hasAtLeastOneTypedArgument);
|
||||
if ($argument->hasTypeAnalysis()) {
|
||||
$whitespace = $appendix;
|
||||
} else {
|
||||
$whitespace = $this->whitespacesConfig->getLineEnding() . $argsIndent . $appendix;
|
||||
}
|
||||
} else {
|
||||
if ($argument->hasTypeAnalysis()) {
|
||||
$whitespace = ' ';
|
||||
} else {
|
||||
$whitespace = $this->whitespacesConfig->getLineEnding() . $argsIndent;
|
||||
}
|
||||
}
|
||||
|
||||
$tokens->ensureWhitespaceAtIndex($whitespaceIndex, 0, $whitespace);
|
||||
}
|
||||
|
||||
if ($this->configuration[self::C_DEFAULTS] !== null) {
|
||||
// Can't use $argument->hasDefault() because it's null when it's default for a type (e.g. 0 for int)
|
||||
/** @var \PhpCsFixer\Tokenizer\Token $equalToken */
|
||||
$equalToken = $tokens[$tokens->getNextMeaningfulToken($argument->getNameIndex())];
|
||||
if ($equalToken->getContent() === '=') {
|
||||
$nameLen = strlen($argument->getName());
|
||||
$whitespaceIndex = $argument->getNameIndex() + 1;
|
||||
if ($this->configuration[self::C_DEFAULTS] === true) {
|
||||
$tokens->ensureWhitespaceAtIndex($whitespaceIndex, 0, str_repeat(' ', $longestVariableName - $nameLen + 1));
|
||||
} else {
|
||||
$tokens->ensureWhitespaceAtIndex($whitespaceIndex, 0, ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -214,6 +214,10 @@ class Rules {
|
||||
],
|
||||
|
||||
// Our custom or extended fixers
|
||||
'Ely/align_multiline_parameters' => [
|
||||
'variables' => false,
|
||||
'defaults' => false,
|
||||
],
|
||||
'Ely/blank_line_around_class_body' => [
|
||||
'apply_to_anonymous_classes' => false,
|
||||
],
|
||||
|
@ -0,0 +1,219 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Ely\CS\Test\Fixer\FunctionNotation;
|
||||
|
||||
use Ely\CS\Fixer\FunctionNotation\AlignMultilineParametersFixer;
|
||||
use PhpCsFixer\AbstractFixer;
|
||||
use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
|
||||
|
||||
/**
|
||||
* @covers \Ely\CS\Fixer\FunctionNotation\AlignMultilineParametersFixer
|
||||
*/
|
||||
final class AlignMultilineParametersFixerTest extends AbstractFixerTestCase {
|
||||
|
||||
/**
|
||||
* @dataProvider provideTrueCases
|
||||
*/
|
||||
public function testBothTrue(string $expected, ?string $input = null): void {
|
||||
$this->fixer->configure([
|
||||
'variables' => true,
|
||||
'defaults' => true,
|
||||
]);
|
||||
$this->doTest($expected, $input);
|
||||
}
|
||||
|
||||
public function provideTrueCases(): iterable {
|
||||
yield 'empty function' => [
|
||||
'<?php
|
||||
function test(): void {}
|
||||
',
|
||||
];
|
||||
|
||||
yield 'empty multiline function' => [
|
||||
'<?php
|
||||
function test(
|
||||
): void {}
|
||||
',
|
||||
];
|
||||
|
||||
yield 'single line function' => [
|
||||
'<?php
|
||||
function test(string $a, int $b): void {}
|
||||
',
|
||||
];
|
||||
|
||||
yield 'single line fn' => [
|
||||
'<?php
|
||||
fn(string $a, int $b) => $b;
|
||||
',
|
||||
];
|
||||
|
||||
yield 'function, no defaults' => [
|
||||
'<?php
|
||||
function test(
|
||||
string $a,
|
||||
int $b
|
||||
): void {}
|
||||
',
|
||||
'<?php
|
||||
function test(
|
||||
string $a,
|
||||
int $b
|
||||
): void {}
|
||||
',
|
||||
];
|
||||
|
||||
yield 'function, one has default' => [
|
||||
'<?php
|
||||
function test(
|
||||
string $a,
|
||||
int $b = 0
|
||||
): void {}
|
||||
',
|
||||
'<?php
|
||||
function test(
|
||||
string $a,
|
||||
int $b = 0
|
||||
): void {}
|
||||
',
|
||||
];
|
||||
|
||||
yield 'function, one has no type' => [
|
||||
'<?php
|
||||
function test(
|
||||
string $a,
|
||||
$b
|
||||
): void {}
|
||||
',
|
||||
'<?php
|
||||
function test(
|
||||
string $a,
|
||||
$b
|
||||
): void {}
|
||||
',
|
||||
];
|
||||
|
||||
yield 'function, one has no type, but has default' => [
|
||||
'<?php
|
||||
function test(
|
||||
string $a,
|
||||
$b = 0
|
||||
): void {}
|
||||
',
|
||||
'<?php
|
||||
function test(
|
||||
string $a,
|
||||
$b = 0
|
||||
): void {}
|
||||
',
|
||||
];
|
||||
|
||||
yield 'function, no types at all' => [
|
||||
'<?php
|
||||
function test(
|
||||
$string = "string",
|
||||
$int = 0
|
||||
): void {}
|
||||
',
|
||||
'<?php
|
||||
function test(
|
||||
$string = "string",
|
||||
$int = 0
|
||||
): void {}
|
||||
',
|
||||
];
|
||||
|
||||
yield 'function, defaults' => [
|
||||
'<?php
|
||||
function test(
|
||||
string $string = "string",
|
||||
int $int = 0
|
||||
): void {}
|
||||
',
|
||||
'<?php
|
||||
function test(
|
||||
string $string = "string",
|
||||
int $int = 0
|
||||
): void {}
|
||||
',
|
||||
];
|
||||
|
||||
yield 'class method, defaults' => [
|
||||
'<?php
|
||||
class Test {
|
||||
public function foo(
|
||||
string $string = "string",
|
||||
int $int = 0
|
||||
): void {}
|
||||
}
|
||||
',
|
||||
'<?php
|
||||
class Test {
|
||||
public function foo(
|
||||
string $string = "string",
|
||||
int $int = 0
|
||||
): void {}
|
||||
}
|
||||
',
|
||||
];
|
||||
|
||||
yield 'fn, defaults' => [
|
||||
'<?php
|
||||
fn(
|
||||
string $string = "string",
|
||||
int $int = 0
|
||||
) => $int;
|
||||
',
|
||||
'<?php
|
||||
fn(
|
||||
string $string = "string",
|
||||
int $int = 0
|
||||
) => $int;
|
||||
',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideFalseCases
|
||||
*/
|
||||
public function testBothFalse(string $expected, ?string $input = null): void {
|
||||
$this->fixer->configure([
|
||||
'variables' => false,
|
||||
'defaults' => false,
|
||||
]);
|
||||
$this->doTest($expected, $input);
|
||||
}
|
||||
|
||||
public function provideFalseCases(): iterable {
|
||||
foreach ($this->provideTrueCases() as $key => $case) {
|
||||
if (isset($case[1])) {
|
||||
yield $key => [$case[1], $case[0]];
|
||||
} else {
|
||||
yield $key => $case;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideNullCases
|
||||
*/
|
||||
public function testBothNull(string $expected, ?string $input = null): void {
|
||||
$this->fixer->configure([
|
||||
'variables' => null,
|
||||
'defaults' => null,
|
||||
]);
|
||||
$this->doTest($expected, $input);
|
||||
}
|
||||
|
||||
public function provideNullCases(): iterable {
|
||||
foreach ($this->provideFalseCases() as $key => $case) {
|
||||
yield $key => [$case[0]];
|
||||
}
|
||||
}
|
||||
|
||||
protected function createFixer(): AbstractFixer {
|
||||
return new AlignMultilineParametersFixer();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user