Skip to content

Commit

Permalink
Add a fixer used to add ! prefix to the first argument of DateTime:…
Browse files Browse the repository at this point in the history
…:createFromFormat
  • Loading branch information
liquid207 committed Feb 28, 2022
1 parent f45c5a0 commit e6c8569
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 0 deletions.
14 changes: 14 additions & 0 deletions doc/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,20 @@ List of Available Rules

`Source PhpCsFixer\\Fixer\\ControlStructure\\ControlStructureContinuationPositionFixer <./../src/Fixer/ControlStructure/ControlStructureContinuationPositionFixer.php>`_
- `create_from_format_call <./rules/function_notation/create_from_format_call.rst>`_

The first argument of ``DateTime::createFromFormat`` method must start with ``!``.

Consider this code:
``DateTime::createFromFormat('Y-m-d', '2022-02-11')``.
What value will be return? '2022-01-11 00:00:00.0'? No,
actual return value has 'H:i:s' section like '2022-02-11 16:55:37.0'.
Change 'Y-m-d' to '!Y-m-d', return value will be '2022-01-11
00:00:00.0'.
So add ``!`` to format string will make return value more
intuitive.

`Source PhpCsFixer\\Fixer\\FunctionNotation\\CreateFromFormatCallFixer <./../src/Fixer/FunctionNotation/CreateFromFormatCallFixer.php>`_
- `date_time_immutable <./rules/class_usage/date_time_immutable.rst>`_

Class ``DateTimeImmutable`` should be used instead of ``DateTime``.
Expand Down
31 changes: 31 additions & 0 deletions doc/rules/function_notation/create_from_format_call.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
================================
Rule ``create_from_format_call``
================================

The first argument of ``DateTime::createFromFormat`` method must start with
``!``.

Description
-----------

Consider this code:
``DateTime::createFromFormat('Y-m-d', '2022-02-11')``.
What value will be return? '2022-01-11 00:00:00.0'? No, actual
return value has 'H:i:s' section like '2022-02-11 16:55:37.0'.
Change 'Y-m-d' to '!Y-m-d', return value will be '2022-01-11
00:00:00.0'.
So add ``!`` to format string will make return value more
intuitive.

Examples
--------

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

.. code-block:: diff
--- Original
+++ New
-<?php \DateTime::createFromFormat('Y-m-d', '2022-02-11');
+<?php \DateTime::createFromFormat('!Y-m-d', '2022-02-11');
3 changes: 3 additions & 0 deletions doc/rules/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,9 @@ Function Notation
- `combine_nested_dirname <./function_notation/combine_nested_dirname.rst>`_ *(risky)*

Replace multiple nested calls of ``dirname`` by only one call with second ``$level`` parameter. Requires PHP >= 7.0.
- `create_from_format_call <./function_notation/create_from_format_call.rst>`_

The first argument of ``DateTime::createFromFormat`` method must start with ``!``.
- `fopen_flag_order <./function_notation/fopen_flag_order.rst>`_ *(risky)*

Order the flags in ``fopen`` calls, ``b`` and ``t`` must be last.
Expand Down
103 changes: 103 additions & 0 deletions src/Fixer/FunctionNotation/CreateFromFormatCallFixer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

declare(strict_types=1);

/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace PhpCsFixer\Fixer\FunctionNotation;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;

final class CreateFromFormatCallFixer extends AbstractFixer
{
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'The first argument of `DateTime::createFromFormat` method must start with `!`.',
[
new CodeSample("<?php \\DateTime::createFromFormat('Y-m-d', '2022-02-11');\n"),
],
"Consider this code:
`DateTime::createFromFormat('Y-m-d', '2022-02-11')`.
What value will be return? '2022-01-11 00:00:00.0'? No, actual return value has 'H:i:s' section like '2022-02-11 16:55:37.0'.
Change 'Y-m-d' to '!Y-m-d', return value will be '2022-01-11 00:00:00.0'.
So add `!` to format string will make return value more intuitive."
);
}

public function isCandidate(Tokens $tokens): bool
{
return $tokens->isTokenKindFound(T_DOUBLE_COLON);
}

protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
$useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens);
$argumentsAnalyzer = new ArgumentsAnalyzer();

for ($index = 0; $index < \count($tokens); ++$index) {
if (!$tokens[$index]->isGivenKind(T_DOUBLE_COLON)) {
continue;
}

$functionNameIndex = $index + 1;

if (!$tokens[$functionNameIndex]->equals([T_STRING, 'createFromFormat'], false)) {
continue;
}

if (!$tokens[$functionNameIndex + 1]->equals('(')) {
continue;
}

$classNamePreviousIndex = $tokens->getTokenNotOfKindsSibling($functionNameIndex, -1, [T_DOUBLE_COLON, T_NS_SEPARATOR, T_STRING]);
$classNameIndex = $index - 1;
$className = $tokens->generatePartialCode($classNamePreviousIndex + 1, $classNameIndex);

foreach ($useDeclarations as $useDeclaration) {
if ($useDeclaration->getShortName() === $className) {
$className = $useDeclaration->getFullName();

break;
}
}

if (\DateTime::class !== str_replace('\\', '', $className)) {
continue;
}

$openIndex = $tokens->getNextTokenOfKind($functionNameIndex, ['(']);
$closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex);
$arguments = $argumentsAnalyzer->getArguments($tokens, $openIndex, $closeIndex);

if (2 !== \count($arguments)) {
continue;
}

$formatArgumentIndex = array_values($arguments)[0];
$format = $tokens[$formatArgumentIndex]->getContent();

if (!\in_array(substr($format, 0, 1), ['\'', '"'], true) || '!' === substr($format, 1, 1)) {
continue;
}

$tokens->clearAt($formatArgumentIndex);
$tokens->insertAt($formatArgumentIndex, new Token([T_CONSTANT_ENCAPSED_STRING, substr_replace($format, '!', 1, 0)]));
}
}
}
73 changes: 73 additions & 0 deletions tests/Fixer/FunctionNotation/CreateFromFormatCallFixerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace PhpCsFixer\Tests\Fixer\FunctionNotation;

use PhpCsFixer\Tests\Test\AbstractFixerTestCase;

/**
* @internal
* @covers \PhpCsFixer\Fixer\FunctionNotation\CreateFromFormatCallFixer
*/
final class CreateFromFormatCallFixerTest extends AbstractFixerTestCase
{
/**
* @dataProvider provideFixCases
*/
public function testFix(string $expected, ?string $input = null): void
{
$this->doTest($expected, $input);
}

public function provideFixCases(): \Generator
{
yield [
'<?php \DateTime::createFromFormat(\'!Y-m-d\', \'2022-02-11\');',
'<?php \DateTime::createFromFormat(\'Y-m-d\', \'2022-02-11\');',
];

yield [
'<?php use DateTime; DateTime::createFromFormat(\'!Y-m-d\', \'2022-02-11\');',
'<?php use DateTime; DateTime::createFromFormat(\'Y-m-d\', \'2022-02-11\');',
];

yield [
'<?php DateTime::createFromFormat(\'!Y-m-d\', \'2022-02-11\');',
'<?php DateTime::createFromFormat(\'Y-m-d\', \'2022-02-11\');',
];

yield [
'<?php use \Example\DateTime; DateTime::createFromFormat(\'Y-m-d\', \'2022-02-11\');',
];

yield [
'<?php \DateTime::createFromFormat("!Y-m-d", \'2022-02-11\');',
'<?php \DateTime::createFromFormat("Y-m-d", \'2022-02-11\');',
];

yield [
'<?php \DateTime::createFromFormat($foo, \'2022-02-11\');',
];

yield [
'<?php \DateTime::createFromFormat( "!Y-m-d", \'2022-02-11\');',
'<?php \DateTime::createFromFormat( "Y-m-d", \'2022-02-11\');',
];

yield [
'<?php \DateTime::createFromFormat(/* aaa */ "!Y-m-d", \'2022-02-11\');',
'<?php \DateTime::createFromFormat(/* aaa */ "Y-m-d", \'2022-02-11\');',
];
}
}

0 comments on commit e6c8569

Please sign in to comment.