Skip to content

Commit 86c38ad

Browse files
authored
Add ConstructorAnalyzer (#619)
1 parent 3f3afc3 commit 86c38ad

File tree

5 files changed

+185
-7
lines changed

5 files changed

+185
-7
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![Latest stable version](https://img.shields.io/packagist/v/kubawerlos/php-cs-fixer-custom-fixers.svg?label=current%20version)](https://packagist.org/packages/kubawerlos/php-cs-fixer-custom-fixers)
44
[![PHP version](https://img.shields.io/packagist/php-v/kubawerlos/php-cs-fixer-custom-fixers.svg)](https://php.net)
55
[![License](https://img.shields.io/github/license/kubawerlos/php-cs-fixer-custom-fixers.svg)](LICENSE)
6-
![Tests](https://img.shields.io/badge/tests-2756-brightgreen.svg)
6+
![Tests](https://img.shields.io/badge/tests-2767-brightgreen.svg)
77
[![Downloads](https://img.shields.io/packagist/dt/kubawerlos/php-cs-fixer-custom-fixers.svg)](https://packagist.org/packages/kubawerlos/php-cs-fixer-custom-fixers)
88

99
[![CI Status](https://github.com/kubawerlos/php-cs-fixer-custom-fixers/workflows/CI/badge.svg?branch=main&event=push)](https://github.com/kubawerlos/php-cs-fixer-custom-fixers/actions)

dev-tools/psalm.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
<errorLevel type='suppress'>
3232
<file name='./src/Fixer/OrderedClassElementsInternalFixer.php' />
3333
<file name='../src/Analyzer/Analysis/ArrayElementAnalysis.php' />
34+
<file name='../src/Analyzer/Analysis/ConstructorAnalysis.php' />
3435
<file name='../src/Analyzer/Analysis/SwitchAnalysis.php' />
3536
</errorLevel>
3637
</PossiblyUnusedMethod>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
/*
4+
* This file is part of PHP CS Fixer: custom fixers.
5+
*
6+
* (c) 2018 Kuba Werłos
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace PhpCsFixerCustomFixers\Analyzer;
15+
16+
use PhpCsFixer\Tokenizer\Tokens;
17+
use PhpCsFixer\Tokenizer\TokensAnalyzer;
18+
use PhpCsFixerCustomFixers\Analyzer\Analysis\ConstructorAnalysis;
19+
20+
/**
21+
* @internal
22+
*/
23+
final class ConstructorAnalyzer
24+
{
25+
public function findNonAbstractConstructor(Tokens $tokens, int $classIndex): ?ConstructorAnalysis
26+
{
27+
if (!$tokens[$classIndex]->isGivenKind(\T_CLASS)) {
28+
throw new \InvalidArgumentException(\sprintf('Index %d is not a class.', $classIndex));
29+
}
30+
31+
$tokensAnalyzer = new TokensAnalyzer($tokens);
32+
33+
/** @var int $index */
34+
foreach ($tokensAnalyzer->getClassyElements() as $index => $element) {
35+
if ($element['classIndex'] !== $classIndex) {
36+
continue;
37+
}
38+
if ($element['type'] !== 'method') {
39+
continue;
40+
}
41+
42+
/** @var int $functionNameIndex */
43+
$functionNameIndex = $tokens->getNextMeaningfulToken($index);
44+
45+
if ($tokens[$functionNameIndex]->equals([\T_STRING, '__construct'], false)) {
46+
$constructorData = $tokensAnalyzer->getMethodAttributes($index);
47+
if ($constructorData['abstract']) {
48+
return null;
49+
}
50+
51+
return new ConstructorAnalysis($tokens, $index);
52+
}
53+
}
54+
55+
return null;
56+
}
57+
}

tests/Analyzer/Analysis/ConstructorAnalysisTest.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
* @internal
2222
*
2323
* @covers \PhpCsFixerCustomFixers\Analyzer\Analysis\ConstructorAnalysis
24-
*
25-
* @requires PHP 8.0
2624
*/
2725
final class ConstructorAnalysisTest extends TestCase
2826
{
@@ -65,12 +63,14 @@ public function __construct(array $a, bool $b, callable $c1, CALLABLE $c1, int $
6563
}',
6664
];
6765

68-
yield 'some already promoted' => [
69-
[22 => 'b', 39 => 's'],
70-
'<?php class Foo {
66+
if (\PHP_VERSION_ID >= 80000) {
67+
yield 'some already promoted' => [
68+
[22 => 'b', 39 => 's'],
69+
'<?php class Foo {
7170
public function __construct(public array $a, bool $b, protected ?Bar\Baz\Qux $q, string $s, private OtherType $t) {}
7271
}',
73-
];
72+
];
73+
}
7474
}
7575

7676
/**
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
3+
/*
4+
* This file is part of PHP CS Fixer: custom fixers.
5+
*
6+
* (c) 2018 Kuba Werłos
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Tests\Analyzer;
15+
16+
use PhpCsFixer\Tokenizer\Tokens;
17+
use PhpCsFixerCustomFixers\Analyzer\Analysis\ConstructorAnalysis;
18+
use PhpCsFixerCustomFixers\Analyzer\ConstructorAnalyzer;
19+
use PHPUnit\Framework\TestCase;
20+
21+
/**
22+
* @internal
23+
*
24+
* @covers \PhpCsFixerCustomFixers\Analyzer\ConstructorAnalyzer
25+
*/
26+
final class ConstructorAnalyzerTest extends TestCase
27+
{
28+
public function testFindingNonAbstractConstructorWhenNotForClass(): void
29+
{
30+
$this->expectException(\InvalidArgumentException::class);
31+
$this->expectExceptionMessage('Index 2 is not a class.');
32+
33+
$tokens = Tokens::fromCode('<?php $no . $class . $here;');
34+
$analyzer = new ConstructorAnalyzer();
35+
36+
$analyzer->findNonAbstractConstructor($tokens, 2);
37+
}
38+
39+
/**
40+
* @param array<int, null|int> $expected
41+
*
42+
* @dataProvider provideFindingNonAbstractConstructorCases
43+
*/
44+
public function testFindingNonAbstractConstructor(array $expected, string $code): void
45+
{
46+
$tokens = Tokens::fromCode($code);
47+
$analyzer = new ConstructorAnalyzer();
48+
49+
foreach ($expected as $classIndex => $nonAbstractConstructorIndex) {
50+
$constructorAnalysis = $analyzer->findNonAbstractConstructor($tokens, $classIndex);
51+
52+
if ($nonAbstractConstructorIndex === null) {
53+
self::assertNull($constructorAnalysis);
54+
} else {
55+
self::assertInstanceOf(ConstructorAnalysis::class, $constructorAnalysis);
56+
self::assertSame(
57+
\serialize(new ConstructorAnalysis($tokens, $nonAbstractConstructorIndex)),
58+
\serialize($constructorAnalysis)
59+
);
60+
}
61+
}
62+
}
63+
64+
/**
65+
* @return iterable<array{array<int, null|int>, string}>
66+
*/
67+
public static function provideFindingNonAbstractConstructorCases(): iterable
68+
{
69+
yield 'no constructor' => [
70+
[3 => null],
71+
'<?php abstract class Foo {
72+
public function notConstructor() {}
73+
}',
74+
];
75+
76+
yield 'abstract constructor' => [
77+
[3 => null],
78+
'<?php abstract class Foo {
79+
abstract public function __construct() {}
80+
}',
81+
];
82+
83+
yield 'public constructor' => [
84+
[3 => 11],
85+
'<?php abstract class Foo {
86+
public function __construct() {}
87+
}',
88+
];
89+
90+
yield 'uppercase constructor' => [
91+
[3 => 11],
92+
'<?php abstract class Foo {
93+
public function __CONSTRUCT() {}
94+
}',
95+
];
96+
97+
yield 'class with other elements' => [
98+
[3 => 29],
99+
'<?php abstract class Foo {
100+
public $a;
101+
public static function create() {}
102+
public function __construct() {}
103+
public function bar() {}
104+
}',
105+
];
106+
107+
yield 'multiple classes' => [
108+
[2 => 10, 21 => null, 29 => 37],
109+
'<?php
110+
class Foo {
111+
public function __construct() {}
112+
}
113+
class Bar {
114+
}
115+
class Baz {
116+
public function __construct() {}
117+
}',
118+
];
119+
}
120+
}

0 commit comments

Comments
 (0)