Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NewWithBracesFixer - option to remove braces #5892

Merged
merged 1 commit into from Feb 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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``
SpacePossum marked this conversation as resolved.
Show resolved Hide resolved
~~~~~~~~~~~~~~~~~~~

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);
}
}