• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

keradus / PHP-CS-Fixer / 17253322895

26 Aug 2025 11:52PM UTC coverage: 94.753% (+0.008%) from 94.745%
17253322895

push

github

keradus
add to git-blame-ignore-revs

28316 of 29884 relevant lines covered (94.75%)

45.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.55
/src/Fixer/FunctionNotation/DateTimeCreateFromFormatCallFixer.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of PHP CS Fixer.
7
 *
8
 * (c) Fabien Potencier <fabien@symfony.com>
9
 *     Dariusz RumiƄski <dariusz.ruminski@gmail.com>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14

15
namespace PhpCsFixer\Fixer\FunctionNotation;
16

17
use PhpCsFixer\AbstractFixer;
18
use PhpCsFixer\FixerDefinition\CodeSample;
19
use PhpCsFixer\FixerDefinition\FixerDefinition;
20
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
21
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
22
use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer;
23
use PhpCsFixer\Tokenizer\Token;
24
use PhpCsFixer\Tokenizer\Tokens;
25

26
/**
27
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
28
 */
29
final class DateTimeCreateFromFormatCallFixer extends AbstractFixer
30
{
31
    public function getDefinition(): FixerDefinitionInterface
32
    {
33
        return new FixerDefinition(
3✔
34
            'The first argument of `DateTime::createFromFormat` method must start with `!`.',
3✔
35
            [
3✔
36
                new CodeSample("<?php \\DateTime::createFromFormat('Y-m-d', '2022-02-11');\n"),
3✔
37
            ],
3✔
38
            "Consider this code:
3✔
39
    `DateTime::createFromFormat('Y-m-d', '2022-02-11')`.
40
    What value will be returned? '2022-02-11 00:00:00.0'?
41
    No, actual return value has 'H:i:s' section like '2022-02-11 16:55:37.0'.
42
    Change 'Y-m-d' to '!Y-m-d', return value will be '2022-02-11 00:00:00.0'.
43
    So, adding `!` to format string will make return value more intuitive.",
3✔
44
            'Risky when depending on the actual timings being used even when not explicit set in format.'
3✔
45
        );
3✔
46
    }
47

48
    /**
49
     * {@inheritdoc}
50
     *
51
     * Must run after NoUselessConcatOperatorFixer.
52
     */
53
    public function getPriority(): int
54
    {
55
        return 0;
1✔
56
    }
57

58
    public function isCandidate(Tokens $tokens): bool
59
    {
60
        return $tokens->isTokenKindFound(\T_DOUBLE_COLON);
39✔
61
    }
62

63
    public function isRisky(): bool
64
    {
65
        return true;
1✔
66
    }
67

68
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
69
    {
70
        $argumentsAnalyzer = new ArgumentsAnalyzer();
39✔
71
        $namespaceUsesAnalyzer = new NamespaceUsesAnalyzer();
39✔
72

73
        foreach ($tokens->getNamespaceDeclarations() as $namespace) {
39✔
74
            $scopeStartIndex = $namespace->getScopeStartIndex();
39✔
75
            $useDeclarations = $namespaceUsesAnalyzer->getDeclarationsInNamespace($tokens, $namespace);
39✔
76

77
            for ($index = $namespace->getScopeEndIndex(); $index > $scopeStartIndex; --$index) {
39✔
78
                if (!$tokens[$index]->isGivenKind(\T_DOUBLE_COLON)) {
39✔
79
                    continue;
39✔
80
                }
81

82
                $functionNameIndex = $tokens->getNextMeaningfulToken($index);
39✔
83

84
                if (!$tokens[$functionNameIndex]->equals([\T_STRING, 'createFromFormat'], false)) {
39✔
85
                    continue;
×
86
                }
87

88
                if (!$tokens[$tokens->getNextMeaningfulToken($functionNameIndex)]->equals('(')) {
39✔
89
                    continue;
×
90
                }
91

92
                $classNameIndex = $tokens->getPrevMeaningfulToken($index);
39✔
93

94
                if (!$tokens[$classNameIndex]->equalsAny([[\T_STRING, \DateTime::class], [\T_STRING, \DateTimeImmutable::class]], false)) {
39✔
95
                    continue;
×
96
                }
97

98
                $preClassNameIndex = $tokens->getPrevMeaningfulToken($classNameIndex);
39✔
99

100
                if ($tokens[$preClassNameIndex]->isGivenKind(\T_NS_SEPARATOR)) {
39✔
101
                    if ($tokens[$tokens->getPrevMeaningfulToken($preClassNameIndex)]->isGivenKind(\T_STRING)) {
29✔
102
                        continue;
4✔
103
                    }
104
                } elseif (!$namespace->isGlobalNamespace()) {
12✔
105
                    continue;
2✔
106
                } else {
107
                    foreach ($useDeclarations as $useDeclaration) {
10✔
108
                        foreach (['datetime', 'datetimeimmutable'] as $name) {
6✔
109
                            if ($name === strtolower($useDeclaration->getShortName()) && $name !== strtolower($useDeclaration->getFullName())) {
6✔
110
                                continue 3;
4✔
111
                            }
112
                        }
113
                    }
114
                }
115

116
                $openIndex = $tokens->getNextTokenOfKind($functionNameIndex, ['(']);
31✔
117
                $closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex);
31✔
118

119
                $argumentIndex = $this->getFirstArgumentTokenIndex($tokens, $argumentsAnalyzer->getArguments($tokens, $openIndex, $closeIndex));
31✔
120

121
                if (null === $argumentIndex) {
31✔
122
                    continue;
10✔
123
                }
124

125
                $format = $tokens[$argumentIndex]->getContent();
21✔
126

127
                if (\strlen($format) < 3) {
21✔
128
                    continue;
2✔
129
                }
130

131
                $offset = 'b' === $format[0] || 'B' === $format[0] ? 2 : 1;
19✔
132

133
                if ('!' === $format[$offset]) {
19✔
134
                    continue;
18✔
135
                }
136

137
                $tokens->clearAt($argumentIndex);
19✔
138
                $tokens->insertAt($argumentIndex, new Token([\T_CONSTANT_ENCAPSED_STRING, substr_replace($format, '!', $offset, 0)]));
19✔
139
            }
140
        }
141
    }
142

143
    /**
144
     * @param array<int, int> $arguments
145
     */
146
    private function getFirstArgumentTokenIndex(Tokens $tokens, array $arguments): ?int
147
    {
148
        if (2 !== \count($arguments)) {
31✔
149
            return null;
4✔
150
        }
151

152
        $argumentStartIndex = array_key_first($arguments);
27✔
153
        $argumentEndIndex = $arguments[$argumentStartIndex];
27✔
154
        $argumentStartIndex = $tokens->getNextMeaningfulToken($argumentStartIndex - 1);
27✔
155

156
        if (
157
            $argumentStartIndex !== $argumentEndIndex
27✔
158
            && $tokens->getNextMeaningfulToken($argumentStartIndex) <= $argumentEndIndex
27✔
159
        ) {
160
            return null; // argument is not a simple single string
×
161
        }
162

163
        return !$tokens[$argumentStartIndex]->isGivenKind(\T_CONSTANT_ENCAPSED_STRING)
27✔
164
            ? null // first argument is not a string
6✔
165
            : $argumentStartIndex;
27✔
166
    }
167
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc