Skip to content

Commit

Permalink
feature #5892 NewWithBracesFixer - option to remove braces (jrmajor)
Browse files Browse the repository at this point in the history
This PR was merged into the master branch.

Discussion
----------

NewWithBracesFixer - option to remove braces

~Although PSR-12 states that “when instantiating a new class, parentheses MUST always be present”, this rule clearly does not apply to anonymous classes, as there are three examples of anonymous classes in PSR-12 and none of them has parentheses.~

This PR adds `named_class` and `anonymous_class`  boolean options to `NewWithBracesFixer`. By default, both of them are set to true, which matches current behavior. ~However, I've changed the configuration in PSR-12 to `['anonymous_class' => false]`, as this is the code style used in all examples.~

Commits
-------

877624a NewWithBracesFixer - add no braces support
  • Loading branch information
SpacePossum committed Feb 17, 2022
2 parents 0999168 + 877624a commit 51c8146
Show file tree
Hide file tree
Showing 5 changed files with 508 additions and 33 deletions.
14 changes: 13 additions & 1 deletion doc/list.rst
Expand Up @@ -1181,7 +1181,19 @@ List of Available Rules
`Source PhpCsFixer\\Fixer\\Casing\\NativeFunctionTypeDeclarationCasingFixer <./../src/Fixer/Casing/NativeFunctionTypeDeclarationCasingFixer.php>`_
- `new_with_braces <./rules/operator/new_with_braces.rst>`_

All instances created with new keyword must be followed by braces.
All instances created with ``new`` keyword must (not) be followed by braces.

Configuration options:

- | ``named_class``
| Whether named classes should be followed by parentheses.
| Allowed types: ``bool``
| Default value: ``true``
- | ``anonymous_class``
| Whether anonymous classes should be followed by parentheses.
| Allowed types: ``bool``
| Default value: ``true``

Part of rule sets `@PSR12 <./ruleSets/PSR12.rst>`_ `@PhpCsFixer <./ruleSets/PhpCsFixer.rst>`_ `@Symfony <./ruleSets/Symfony.rst>`_

Expand Down
2 changes: 1 addition & 1 deletion doc/rules/index.rst
Expand Up @@ -504,7 +504,7 @@ Operator
Use ``&&`` and ``||`` logical operators instead of ``and`` and ``or``.
- `new_with_braces <./operator/new_with_braces.rst>`_

All instances created with new keyword must be followed by braces.
All instances created with ``new`` keyword must (not) be followed by braces.
- `no_space_around_double_colon <./operator/no_space_around_double_colon.rst>`_

There must be no space around double colons (also called Scope Resolution Operator or Paamayim Nekudotayim).
Expand Down
67 changes: 61 additions & 6 deletions doc/rules/operator/new_with_braces.rst
Expand Up @@ -2,31 +2,86 @@
Rule ``new_with_braces``
========================

All instances created with new keyword must be followed by braces.
All instances created with ``new`` keyword must (not) be followed by braces.

Configuration
-------------

``named_class``
~~~~~~~~~~~~~~~

Whether named classes should be followed by parentheses.

Allowed types: ``bool``

Default value: ``true``

``anonymous_class``
~~~~~~~~~~~~~~~~~~~

Whether anonymous classes should be followed by parentheses.

Allowed types: ``bool``

Default value: ``true``

Examples
--------

Example #1
~~~~~~~~~~

*Default* configuration.

.. code-block:: diff
--- Original
+++ New
-<?php $x = new X;
+<?php $x = new X();
<?php
-$x = new X;
-$y = new class {};
+$x = new X();
+$y = new class() {};
Example #2
~~~~~~~~~~

With configuration: ``['anonymous_class' => false]``.

.. code-block:: diff
--- Original
+++ New
<?php
-$y = new class() {};
+$y = new class {};
Example #3
~~~~~~~~~~

With configuration: ``['named_class' => false]``.

.. code-block:: diff
--- Original
+++ New
<?php
-$x = new X();
+$x = new X;
Rule sets
---------

The rule is part of the following rule sets:

@PSR12
Using the `@PSR12 <./../../ruleSets/PSR12.rst>`_ rule set will enable the ``new_with_braces`` rule.
Using the `@PSR12 <./../../ruleSets/PSR12.rst>`_ rule set will enable the ``new_with_braces`` rule with the default config.

@PhpCsFixer
Using the `@PhpCsFixer <./../../ruleSets/PhpCsFixer.rst>`_ rule set will enable the ``new_with_braces`` rule.
Using the `@PhpCsFixer <./../../ruleSets/PhpCsFixer.rst>`_ rule set will enable the ``new_with_braces`` rule with the default config.

@Symfony
Using the `@Symfony <./../../ruleSets/Symfony.rst>`_ rule set will enable the ``new_with_braces`` rule.
Using the `@Symfony <./../../ruleSets/Symfony.rst>`_ rule set will enable the ``new_with_braces`` rule with the default config.
95 changes: 74 additions & 21 deletions src/Fixer/Operator/NewWithBracesFixer.php
Expand Up @@ -15,6 +15,10 @@
namespace PhpCsFixer\Fixer\Operator;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
Expand All @@ -25,16 +29,26 @@
/**
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*/
final class NewWithBracesFixer extends AbstractFixer
final class NewWithBracesFixer extends AbstractFixer implements ConfigurableFixerInterface
{
/**
* {@inheritdoc}
*/
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'All instances created with new keyword must be followed by braces.',
[new CodeSample("<?php \$x = new X;\n")]
'All instances created with `new` keyword must (not) be followed by braces.',
[
new CodeSample("<?php\n\n\$x = new X;\n\$y = new class {};\n"),
new CodeSample(
"<?php\n\n\$y = new class() {};\n",
['anonymous_class' => false]
),
new CodeSample(
"<?php\n\n\$x = new X();\n",
['named_class' => false]
),
]
);
}

Expand Down Expand Up @@ -108,6 +122,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
[CT::T_BRACE_CLASS_INSTANTIATION_OPEN],
[CT::T_BRACE_CLASS_INSTANTIATION_CLOSE],
];

if (\defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG')) { // @TODO: drop condition when PHP 8.1+ is required
$nextTokenKinds[] = [T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG];
$nextTokenKinds[] = [T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG];
Expand All @@ -120,40 +135,78 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
}

$nextIndex = $tokens->getNextTokenOfKind($index, $nextTokenKinds);
$nextToken = $tokens[$nextIndex];

// new anonymous class definition
if ($nextToken->isGivenKind(T_CLASS)) {
if (!$tokens[$tokens->getNextMeaningfulToken($nextIndex)]->equals('(')) {
$this->insertBracesAfter($tokens, $nextIndex);
if ($tokens[$nextIndex]->isGivenKind(T_CLASS)) {
$nextIndex = $tokens->getNextMeaningfulToken($nextIndex);

if ($this->configuration['anonymous_class']) {
$this->ensureBracesAt($tokens, $nextIndex);
} else {
$this->ensureNoBracesAt($tokens, $nextIndex);
}

continue;
}

// entrance into array index syntax - need to look for exit
while ($nextToken->equals('[') || $nextToken->isGivenKind(CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN)) {
$nextIndex = $tokens->findBlockEnd(Tokens::detectBlockType($nextToken)['type'], $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];
while ($tokens[$nextIndex]->equals('[') || $tokens[$nextIndex]->isGivenKind(CT::T_ARRAY_INDEX_CURLY_BRACE_OPEN)) {
$nextIndex = $tokens->findBlockEnd(Tokens::detectBlockType($tokens[$nextIndex])['type'], $nextIndex);
$nextIndex = $tokens->getNextMeaningfulToken($nextIndex);
}

// new statement with () - nothing to do
if ($nextToken->equals('(') || $nextToken->isObjectOperator()) {
continue;
if ($this->configuration['named_class']) {
$this->ensureBracesAt($tokens, $nextIndex);
} else {
$this->ensureNoBracesAt($tokens, $nextIndex);
}
}
}

$this->insertBracesAfter($tokens, $tokens->getPrevMeaningfulToken($nextIndex));
/**
* {@inheritdoc}
*/
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
{
return new FixerConfigurationResolver([
(new FixerOptionBuilder('named_class', 'Whether named classes should be followed by parentheses.'))
->setAllowedTypes(['bool'])
->setDefault(true)
->getOption(),
(new FixerOptionBuilder('anonymous_class', 'Whether anonymous classes should be followed by parentheses.'))
->setAllowedTypes(['bool'])
->setDefault(true)
->getOption(),
]);
}

private function ensureBracesAt(Tokens $tokens, int $index): void
{
$token = $tokens[$index];

if (!$token->equals('(') && !$token->isObjectOperator()) {
$tokens->insertAt(
$tokens->getPrevMeaningfulToken($index) + 1,
[new Token('('), new Token(')')]
);
}
}

private function insertBracesAfter(Tokens $tokens, int $index): void
private function ensureNoBracesAt(Tokens $tokens, int $index): void
{
$tokens->insertAt(++$index, [new Token('('), new Token(')')]);
if (!$tokens[$index]->equals('(')) {
return;
}

$closingIndex = $tokens->getNextMeaningfulToken($index);

// constructor has arguments - braces can not be removed
if (!$tokens[$closingIndex]->equals(')')) {
return;
}

$tokens->clearTokenAndMergeSurroundingWhitespace($closingIndex);
$tokens->clearTokenAndMergeSurroundingWhitespace($index);
}
}

0 comments on commit 51c8146

Please sign in to comment.