Skip to content

Commit d506db6

Browse files
authored
Add DeclareAfterOpeningTagFixer (#656)
1 parent cda28ee commit d506db6

File tree

7 files changed

+213
-1
lines changed

7 files changed

+213
-1
lines changed

.php-cs-fixer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
unset($rules['modernize_strpos']); // TODO: remove when dropping support to PHP <8.0
3434
unset($rules['use_arrow_functions']); // TODO: remove when dropping support to PHP <7.4
3535
unset($rules[PhpCsFixerCustomFixers\Fixer\PromotedConstructorPropertyFixer::name()]); // TODO: remove when dropping support to PHP <8.0
36+
unset($rules[PhpCsFixerCustomFixers\Fixer\DeclareAfterOpeningTagFixer::name()]); // Only to have PR with this fixer smaller
3637

3738
foreach (new PhpCsFixerCustomFixersDev\Fixers() as $fixer) {
3839
$rules[$fixer->getName()] = true;

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# CHANGELOG for PHP CS Fixer: custom fixers
22

3+
## v3.4.0
4+
- Add DeclareAfterOpeningTagFixer
5+
36
## v3.3.0
47
- Add ConstructorEmptyBracesFixer
58
- PhpdocNoIncorrectVarAnnotationFixer - remove more incorrect annotations

README.md

Lines changed: 11 additions & 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-3029-brightgreen.svg)
6+
![Tests](https://img.shields.io/badge/tests-3058-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)
@@ -117,6 +117,16 @@ Data providers must be static.
117117
}
118118
```
119119

120+
#### DeclareAfterOpeningTagFixer
121+
Declare statement must be placed in the same line, after opening tag.
122+
```diff
123+
-<?php
124+
+<?php declare(strict_types=1);
125+
$foo;
126+
-declare(strict_types=1);
127+
$bar;
128+
```
129+
120130
#### InternalClassCasingFixer
121131
Classes defined internally by extension or core must be referenced with the correct case.
122132
```diff
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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\Fixer;
15+
16+
use PhpCsFixer\FixerDefinition\CodeSample;
17+
use PhpCsFixer\FixerDefinition\FixerDefinition;
18+
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
19+
use PhpCsFixer\Preg;
20+
use PhpCsFixer\Tokenizer\Token;
21+
use PhpCsFixer\Tokenizer\Tokens;
22+
use PhpCsFixerCustomFixers\TokenRemover;
23+
24+
final class DeclareAfterOpeningTagFixer extends AbstractFixer
25+
{
26+
public function getDefinition(): FixerDefinitionInterface
27+
{
28+
return new FixerDefinition(
29+
'Declare statement must be placed in the same line, after opening tag.',
30+
[new CodeSample("<?php\n\$foo;\ndeclare(strict_types=1);\n\$bar;\n")]
31+
);
32+
}
33+
34+
/**
35+
* Must run after BlankLineAfterOpeningTagFixer, HeaderCommentFixer.
36+
*/
37+
public function getPriority(): int
38+
{
39+
return -31;
40+
}
41+
42+
public function isCandidate(Tokens $tokens): bool
43+
{
44+
return $tokens->isTokenKindFound(\T_DECLARE);
45+
}
46+
47+
public function isRisky(): bool
48+
{
49+
return false;
50+
}
51+
52+
public function fix(\SplFileInfo $file, Tokens $tokens): void
53+
{
54+
if (!$tokens[0]->isGivenKind(\T_OPEN_TAG)) {
55+
return;
56+
}
57+
58+
$openingTagTokenContent = $tokens[0]->getContent();
59+
if ($openingTagTokenContent === '<?php ' && $tokens[1]->isGivenKind(\T_DECLARE)) {
60+
return;
61+
}
62+
63+
$tokens[0] = new Token([\T_OPEN_TAG, \substr($openingTagTokenContent, 0, 5) . ' ']);
64+
if ($tokens[1]->isGivenKind(\T_WHITESPACE)) {
65+
$tokens[1] = new Token([\T_WHITESPACE, \substr($openingTagTokenContent, 5) . $tokens[1]->getContent()]);
66+
} else {
67+
$tokens->insertAt(1, new Token([\T_WHITESPACE, \substr($openingTagTokenContent, 5)]));
68+
}
69+
70+
/** @var int $declareIndex */
71+
$declareIndex = $tokens->getNextTokenOfKind(0, [[\T_DECLARE]]);
72+
73+
/** @var int $openParenthesisIndex */
74+
$openParenthesisIndex = $tokens->getNextMeaningfulToken($declareIndex);
75+
$closeParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesisIndex);
76+
77+
/** @var int $semicolonIndex */
78+
$semicolonIndex = $tokens->getNextMeaningfulToken($closeParenthesisIndex);
79+
80+
$tokensToInsert = [];
81+
for ($index = $declareIndex; $index <= $semicolonIndex; $index++) {
82+
$tokensToInsert[] = $tokens[$index];
83+
}
84+
85+
if ($tokens[$semicolonIndex + 1]->isGivenKind(\T_WHITESPACE)) {
86+
/** @var string $content */
87+
$content = Preg::replace('/^(\R?)(?=\R)/', '', $tokens[$semicolonIndex + 1]->getContent());
88+
89+
$tokens->ensureWhitespaceAtIndex($semicolonIndex + 1, 0, $content);
90+
}
91+
92+
$tokens->clearRange($declareIndex + 1, $semicolonIndex);
93+
TokenRemover::removeWithLinesIfPossible($tokens, $declareIndex);
94+
95+
$tokens->insertAt(1, $tokensToInsert);
96+
}
97+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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\Fixer;
15+
16+
/**
17+
* @internal
18+
*
19+
* @covers \PhpCsFixerCustomFixers\Fixer\DeclareAfterOpeningTagFixer
20+
*/
21+
final class DeclareAfterOpeningTagFixerTest extends AbstractFixerTestCase
22+
{
23+
public function testIsRisky(): void
24+
{
25+
self::assertFalse($this->fixer->isRisky());
26+
}
27+
28+
/**
29+
* @dataProvider provideFixCases
30+
*/
31+
public function testFix(string $expected, ?string $input = null): void
32+
{
33+
$this->doTest($expected, $input);
34+
}
35+
36+
/**
37+
* @return iterable<array{0: string, 1?: string}>
38+
*/
39+
public static function provideFixCases(): iterable
40+
{
41+
yield 'skip files not starting with PHP opening tag' => ['<html></html>'];
42+
43+
yield 'fix inside comments' => [
44+
'<?php declare(strict_types=1);
45+
// Foo
46+
// Bar
47+
',
48+
'<?php
49+
// Foo
50+
declare(strict_types=1);
51+
// Bar
52+
',
53+
];
54+
55+
yield 'fix and clean up empty lines' => [
56+
'<?php declare(strict_types=1);
57+
58+
/*
59+
* Header comment
60+
*/
61+
62+
// code starts here
63+
',
64+
'<?php
65+
66+
/*
67+
* Header comment
68+
*/
69+
70+
declare(strict_types=1);
71+
72+
// code starts here
73+
',
74+
];
75+
}
76+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--CONFIGURATION--
2+
{ "blank_line_after_opening_tag": true, "PhpCsFixerCustomFixers/declare_after_opening_tag": true }
3+
--EXPECTED--
4+
<?php declare(strict_types=1);
5+
6+
class Foo {}
7+
8+
--INPUT--
9+
<?php
10+
declare(strict_types=1);
11+
class Foo {}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--CONFIGURATION--
2+
{ "header_comment": { "header": "The header", "location": "after_open" }, "PhpCsFixerCustomFixers/declare_after_opening_tag": true }
3+
--EXPECTED--
4+
<?php declare(strict_types=1);
5+
6+
/*
7+
* The header
8+
*/
9+
10+
11+
--INPUT--
12+
<?php
13+
14+
declare(strict_types=1);

0 commit comments

Comments
 (0)