mirror of
https://github.com/elyby/php-code-style.git
synced 2024-12-01 19:21:48 +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).
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [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
|
### Fixed
|
||||||
- Bug #10: `Ely/blank_line_before_return` don't treat interpolation curly bracket as beginning of the scope.
|
- 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.
|
- Bug #9: `Ely/line_break_after_statements` add space before next meaningful line of code and skip comments.
|
||||||
|
50
README.md
50
README.md
@ -43,12 +43,14 @@ vendor/bin/php-cs-fixer fix
|
|||||||
### Configuration
|
### 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
|
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
|
||||||
<?php
|
<?php
|
||||||
return \Ely\CS\Config::create([
|
return \Ely\CS\Config::create([
|
||||||
'visibility_required' => ['property', 'method'],
|
'trailing_comma_in_multiline' => [
|
||||||
|
'elements' => ['arrays', 'arguments'],
|
||||||
|
],
|
||||||
])->setFinder($finder);
|
])->setFinder($finder);
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -76,12 +78,15 @@ class Foo extends Bar implements FooInterface {
|
|||||||
private const SAMPLE_1 = 123;
|
private const SAMPLE_1 = 123;
|
||||||
private const SAMPLE_2 = 321;
|
private const SAMPLE_2 = 321;
|
||||||
|
|
||||||
public $field1;
|
public Typed $field1;
|
||||||
|
|
||||||
public $field2;
|
public $field2;
|
||||||
|
|
||||||
public function sampleFunction(int $a, int $b = null): array {
|
public function sampleFunction(
|
||||||
if ($a === $b) {
|
int $a,
|
||||||
|
private readonly int $b = null,
|
||||||
|
): array {
|
||||||
|
if ($a === $this->b) {
|
||||||
$result = bar();
|
$result = bar();
|
||||||
} else {
|
} else {
|
||||||
$result = BazClass::bar($this->field1, $this->field2);
|
$result = BazClass::bar($this->field1, $this->field2);
|
||||||
@ -154,6 +159,16 @@ class Foo extends Bar implements FooInterface {
|
|||||||
echo 'the next statement is here';
|
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
|
## Using our fixers
|
||||||
|
|
||||||
First of all, you must install Ely.by PHP-CS-Fixer package as described in the [installation chapter](#installation).
|
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.
|
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`
|
### `Ely/blank_line_around_class_body`
|
||||||
|
|
||||||
Ensure that a class body contains one blank line after its definition and before its end:
|
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
|
// Our custom or extended fixers
|
||||||
|
'Ely/align_multiline_parameters' => [
|
||||||
|
'variables' => false,
|
||||||
|
'defaults' => false,
|
||||||
|
],
|
||||||
'Ely/blank_line_around_class_body' => [
|
'Ely/blank_line_around_class_body' => [
|
||||||
'apply_to_anonymous_classes' => false,
|
'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