Skip to content

Commit dce63b1

Browse files
committed
feature #3812 Add FopenFlagOrderFixer & FopenFlagsFixer (SpacePossum)
This PR was merged into the 2.13-dev branch. Discussion ---------- Add FopenFlagOrderFixer & FopenFlagsFixer closes #3417 ping @ntzm , hope you can help me out with: - define the order of all flags - ~do we want to have an option to remove flags when found, like `t`~ - ~do we want to have an option to add flags when not found, like `b`~ Commits ------- 8682d86 Add FopenFlagOrderFixer & FopenFlagsFixer
2 parents 1311e2b + 8682d86 commit dce63b1

File tree

7 files changed

+560
-0
lines changed

7 files changed

+560
-0
lines changed

README.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,18 @@ Choose from the list of available rules:
647647
set in order to fix the class. (case insensitive); defaults to
648648
``['@internal']``
649649

650+
* **fopen_flag_order** [@Symfony:risky]
651+
652+
Order the flags in ``fopen`` calls, ``b`` and ``t`` must be last.
653+
654+
*Risky rule: risky when the function ``fopen`` is overridden.*
655+
656+
* **fopen_flags** [@Symfony:risky]
657+
658+
The flags in ``fopen`` calls must contain ``b`` and must omit ``t``.
659+
660+
*Risky rule: risky when the function ``fopen`` is overridden.*
661+
650662
* **full_opening_tag** [@PSR1, @PSR2, @Symfony]
651663

652664
PHP code must use the long ``<?php`` tags or short-echo ``<?=`` tags and not

src/AbstractFopenFlagFixer.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
/*
4+
* This file is part of PHP CS Fixer.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
8+
*
9+
* This source file is subject to the MIT license that is bundled
10+
* with this source code in the file LICENSE.
11+
*/
12+
13+
namespace PhpCsFixer;
14+
15+
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
16+
use PhpCsFixer\Tokenizer\Tokens;
17+
18+
/**
19+
* @internal
20+
*
21+
* @author SpacePossum
22+
*/
23+
abstract class AbstractFopenFlagFixer extends AbstractFunctionReferenceFixer
24+
{
25+
/**
26+
* {@inheritdoc}
27+
*/
28+
public function isCandidate(Tokens $tokens)
29+
{
30+
return $tokens->isAllTokenKindsFound([T_STRING, T_CONSTANT_ENCAPSED_STRING]);
31+
}
32+
33+
/**
34+
* {@inheritdoc}
35+
*/
36+
protected function applyFix(\SplFileInfo $file, Tokens $tokens)
37+
{
38+
$argumentsAnalyzer = new ArgumentsAnalyzer();
39+
40+
$index = 0;
41+
$end = $tokens->count() - 1;
42+
while (true) {
43+
$candidate = $this->find('fopen', $tokens, $index, $end);
44+
45+
if (null === $candidate) {
46+
break;
47+
}
48+
49+
$index = $candidate[1]; // proceed to '(' of `fopen`
50+
51+
// fetch arguments
52+
$arguments = $argumentsAnalyzer->getArguments(
53+
$tokens,
54+
$index,
55+
$candidate[2]
56+
);
57+
58+
$argumentsCount = \count($arguments); // argument count sanity check
59+
60+
if ($argumentsCount < 2 || $argumentsCount > 4) {
61+
continue;
62+
}
63+
64+
$argumentStartIndex = array_keys($arguments)[1]; // get second argument index
65+
66+
$this->fixFopenFlagToken(
67+
$tokens,
68+
$argumentStartIndex,
69+
$arguments[$argumentStartIndex]
70+
);
71+
}
72+
}
73+
74+
abstract protected function fixFopenFlagToken(Tokens $tokens, $argumentStartIndex, $argumentEndIndex);
75+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
3+
/*
4+
* This file is part of PHP CS Fixer.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
8+
*
9+
* This source file is subject to the MIT license that is bundled
10+
* with this source code in the file LICENSE.
11+
*/
12+
13+
namespace PhpCsFixer\Fixer\FunctionNotation;
14+
15+
use PhpCsFixer\AbstractFopenFlagFixer;
16+
use PhpCsFixer\FixerDefinition\CodeSample;
17+
use PhpCsFixer\FixerDefinition\FixerDefinition;
18+
use PhpCsFixer\Preg;
19+
use PhpCsFixer\Tokenizer\Token;
20+
use PhpCsFixer\Tokenizer\Tokens;
21+
22+
/**
23+
* @author SpacePossum
24+
*/
25+
final class FopenFlagOrderFixer extends AbstractFopenFlagFixer
26+
{
27+
/**
28+
* {@inheritdoc}
29+
*/
30+
public function getDefinition()
31+
{
32+
return new FixerDefinition(
33+
'Order the flags in `fopen` calls, `b` and `t` must be last.',
34+
[new CodeSample("<?php\n\$a = fopen(\$foo, 'br+');\n")],
35+
null,
36+
'Risky when the function `fopen` is overridden.'
37+
);
38+
}
39+
40+
/**
41+
* @param Tokens $tokens
42+
* @param int $argumentStartIndex
43+
* @param int $argumentEndIndex
44+
*/
45+
protected function fixFopenFlagToken(Tokens $tokens, $argumentStartIndex, $argumentEndIndex)
46+
{
47+
$argumentFlagIndex = null;
48+
49+
for ($i = $argumentStartIndex; $i <= $argumentEndIndex; ++$i) {
50+
if ($tokens[$i]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
51+
continue;
52+
}
53+
54+
if (null !== $argumentFlagIndex) {
55+
return; // multiple meaningful tokens found, no candidate for fixing
56+
}
57+
58+
$argumentFlagIndex = $i;
59+
}
60+
61+
// check if second argument is candidate
62+
if (null === $argumentFlagIndex || !$tokens[$argumentFlagIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) {
63+
return;
64+
}
65+
66+
$content = $tokens[$argumentFlagIndex]->getContent();
67+
$contentQuote = $content[0]; // `'`, `"`, `b` or `B`
68+
69+
if ('b' === $contentQuote || 'B' === $contentQuote) {
70+
$binPrefix = $contentQuote;
71+
$contentQuote = $content[1]; // `'` or `"`
72+
$mode = substr($content, 2, -1);
73+
} else {
74+
$binPrefix = '';
75+
$mode = substr($content, 1, -1);
76+
}
77+
78+
$modeLength = \strlen($mode);
79+
if ($modeLength < 2 || $modeLength > 13) { // 13 === length 'r+w+a+x+c+etb'
80+
return; // sanity check to be less risky
81+
}
82+
83+
$split = $this->sortFlags(Preg::split('#([^\+]\+?)#', $mode, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE));
84+
$newContent = $binPrefix.$contentQuote.implode('', $split).$contentQuote;
85+
86+
if ($content !== $newContent) {
87+
$tokens[$argumentFlagIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, $newContent]);
88+
}
89+
}
90+
91+
/**
92+
* @param string[] $flags
93+
*
94+
* @return string[]
95+
*/
96+
private function sortFlags(array $flags)
97+
{
98+
usort(
99+
$flags,
100+
static function ($flag1, $flag2) {
101+
if ($flag1 === $flag2) {
102+
return 0;
103+
}
104+
105+
if ('b' === $flag1) {
106+
return 1;
107+
}
108+
109+
if ('b' === $flag2) {
110+
return -1;
111+
}
112+
113+
if ('t' === $flag1) {
114+
return 1;
115+
}
116+
117+
if ('t' === $flag2) {
118+
return -1;
119+
}
120+
121+
return $flag1 < $flag2 ? -1 : 1;
122+
}
123+
);
124+
125+
return $flags;
126+
}
127+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
/*
4+
* This file is part of PHP CS Fixer.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
8+
*
9+
* This source file is subject to the MIT license that is bundled
10+
* with this source code in the file LICENSE.
11+
*/
12+
13+
namespace PhpCsFixer\Fixer\FunctionNotation;
14+
15+
use PhpCsFixer\AbstractFopenFlagFixer;
16+
use PhpCsFixer\FixerDefinition\CodeSample;
17+
use PhpCsFixer\FixerDefinition\FixerDefinition;
18+
use PhpCsFixer\Tokenizer\Token;
19+
use PhpCsFixer\Tokenizer\Tokens;
20+
21+
/**
22+
* @author SpacePossum
23+
*/
24+
final class FopenFlagsFixer extends AbstractFopenFlagFixer
25+
{
26+
/**
27+
* {@inheritdoc}
28+
*/
29+
public function getDefinition()
30+
{
31+
return new FixerDefinition(
32+
'The flags in `fopen` calls must contain `b` and must omit `t`.',
33+
[new CodeSample("<?php\n\$a = fopen(\$foo, 'rwt');\n")],
34+
null,
35+
'Risky when the function `fopen` is overridden.'
36+
);
37+
}
38+
39+
/**
40+
* @param Tokens $tokens
41+
* @param int $argumentStartIndex
42+
* @param int $argumentEndIndex
43+
*/
44+
protected function fixFopenFlagToken(Tokens $tokens, $argumentStartIndex, $argumentEndIndex)
45+
{
46+
$argumentFlagIndex = null;
47+
48+
for ($i = $argumentStartIndex; $i <= $argumentEndIndex; ++$i) {
49+
if ($tokens[$i]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
50+
continue;
51+
}
52+
53+
if (null !== $argumentFlagIndex) {
54+
return; // multiple meaningful tokens found, no candidate for fixing
55+
}
56+
57+
$argumentFlagIndex = $i;
58+
}
59+
60+
// check if second argument is candidate
61+
if (null === $argumentFlagIndex || !$tokens[$argumentFlagIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) {
62+
return;
63+
}
64+
65+
$content = $tokens[$argumentFlagIndex]->getContent();
66+
$contentQuote = $content[0]; // `'`, `"`, `b` or `B`
67+
68+
if ('b' === $contentQuote || 'B' === $contentQuote) {
69+
$binPrefix = $contentQuote;
70+
$contentQuote = $content[1]; // `'` or `"`
71+
$mode = substr($content, 2, -1);
72+
} else {
73+
$binPrefix = '';
74+
$mode = substr($content, 1, -1);
75+
}
76+
77+
$modeLength = \strlen($mode);
78+
if ($modeLength < 1 || $modeLength > 13) { // 13 === length 'r+w+a+x+c+etb'
79+
return; // sanity check to be less risky
80+
}
81+
82+
$mode = str_replace('t', '', $mode);
83+
84+
if (false === strpos($mode, 'b')) {
85+
$mode .= 'b';
86+
}
87+
88+
$newContent = $binPrefix.$contentQuote.$mode.$contentQuote;
89+
90+
if ($content !== $newContent) {
91+
$tokens[$argumentFlagIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, $newContent]);
92+
}
93+
}
94+
}

src/RuleSet.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ final class RuleSet implements RuleSetInterface
166166
'dir_constant' => true,
167167
'ereg_to_preg' => true,
168168
'error_suppression' => true,
169+
'fopen_flag_order' => true,
170+
'fopen_flags' => true,
169171
'function_to_constant' => true,
170172
'is_null' => true,
171173
'modernize_types_casting' => true,

0 commit comments

Comments
 (0)