Skip to content

Commit 33f0bcb

Browse files
committed
feature #3907 Add ImplodeCallFixer (kubawerlos)
This PR was squashed before being merged into the 2.13-dev branch (closes #3907). Discussion ---------- Add ImplodeCallFixer Resolves #3904 Adding to `@Symfony` rule set was suggested by @keradus. Commits ------- f063eae Add ImplodeCallFixer
2 parents 791a415 + f063eae commit 33f0bcb

18 files changed

+365
-18
lines changed

README.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,13 @@ Choose from the list of available rules:
733733

734734
Convert ``heredoc`` to ``nowdoc`` where possible.
735735

736+
* **implode_call** [@Symfony:risky]
737+
738+
Function ``implode`` must be called with 2 arguments in the documented
739+
order.
740+
741+
*Risky rule: risky when the function ``implode`` is overridden.*
742+
736743
* **include** [@Symfony]
737744

738745
Include/Require and file path should be divided with a single space.

src/DocBlock/Annotation.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ public function remove()
261261
*/
262262
public function getContent()
263263
{
264-
return implode($this->lines);
264+
return implode('', $this->lines);
265265
}
266266

267267
public function supportTypes()

src/DocBlock/DocBlock.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ public function getAnnotationsOfType($types)
159159
*/
160160
public function getContent()
161161
{
162-
return implode($this->lines);
162+
return implode('', $this->lines);
163163
}
164164

165165
private function findAnnotationLength($start)
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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\AbstractFixer;
16+
use PhpCsFixer\FixerDefinition\CodeSample;
17+
use PhpCsFixer\FixerDefinition\FixerDefinition;
18+
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
19+
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
20+
use PhpCsFixer\Tokenizer\Token;
21+
use PhpCsFixer\Tokenizer\Tokens;
22+
23+
/**
24+
* @author Kuba Werłos <werlos@gmail.com>
25+
*/
26+
final class ImplodeCallFixer extends AbstractFixer
27+
{
28+
/**
29+
* {@inheritdoc}
30+
*/
31+
public function getDefinition()
32+
{
33+
return new FixerDefinition(
34+
'Function `implode` must be called with 2 arguments in the documented order.',
35+
[
36+
new CodeSample("<?php\nimplode(\$pieces, '');\n"),
37+
new CodeSample("<?php\nimplode(\$pieces);\n"),
38+
],
39+
null,
40+
'Risky when the function `implode` is overridden.'
41+
);
42+
}
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
public function isRisky()
48+
{
49+
return true;
50+
}
51+
52+
/**
53+
* {@inheritdoc}
54+
*/
55+
public function isCandidate(Tokens $tokens)
56+
{
57+
return $tokens->isTokenKindFound(T_STRING);
58+
}
59+
60+
/**
61+
* {@inheritdoc}
62+
*/
63+
public function getPriority()
64+
{
65+
// must be run after NoAliasFunctionsFixer
66+
// must be run before MethodArgumentSpaceFixer
67+
return -1;
68+
}
69+
70+
/**
71+
* {@inheritdoc}
72+
*/
73+
protected function applyFix(\SplFileInfo $file, Tokens $tokens)
74+
{
75+
$functionsAnalyzer = new FunctionsAnalyzer();
76+
77+
foreach ($tokens as $index => $token) {
78+
if (!$tokens[$index]->equals([T_STRING, 'implode'], false)) {
79+
continue;
80+
}
81+
82+
if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) {
83+
continue;
84+
}
85+
86+
$argumentsIndices = $this->getArgumentIndices($tokens, $index);
87+
88+
if (1 === \count($argumentsIndices)) {
89+
$firstArgumentIndex = \key($argumentsIndices);
90+
$tokens->insertAt($firstArgumentIndex, [
91+
new Token([T_CONSTANT_ENCAPSED_STRING, "''"]),
92+
new Token(','),
93+
new Token([T_WHITESPACE, ' ']),
94+
]);
95+
96+
continue;
97+
}
98+
99+
if (2 === \count($argumentsIndices)) {
100+
list($firstArgumentIndex, $secondArgumentIndex) = \array_keys($argumentsIndices);
101+
102+
// If the first argument is string we have nothing to do
103+
if ($tokens[$firstArgumentIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) {
104+
continue;
105+
}
106+
// If the second argument is not string we cannot make a swap
107+
if (!$tokens[$secondArgumentIndex]->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) {
108+
continue;
109+
}
110+
111+
// collect tokens from first argument
112+
$firstArgumenteEndIndex = $argumentsIndices[\key($argumentsIndices)];
113+
$newSecondArgumentTokens = [];
114+
for ($i = \key($argumentsIndices); $i <= $firstArgumenteEndIndex; ++$i) {
115+
$newSecondArgumentTokens[] = clone $tokens[$i];
116+
$tokens->clearAt($i);
117+
}
118+
119+
$tokens->insertAt($firstArgumentIndex, clone $tokens[$secondArgumentIndex]);
120+
121+
// insert above increased the second argument index
122+
++$secondArgumentIndex;
123+
$tokens->clearAt($secondArgumentIndex);
124+
$tokens->insertAt($secondArgumentIndex, $newSecondArgumentTokens);
125+
}
126+
}
127+
}
128+
129+
/**
130+
* @param Tokens $tokens
131+
* @param int $functionNameIndex
132+
*
133+
* @return array<int, int> In the format: startIndex => endIndex
134+
*/
135+
private function getArgumentIndices(Tokens $tokens, $functionNameIndex)
136+
{
137+
$argumentsAnalyzer = new ArgumentsAnalyzer();
138+
139+
$openParenthesis = $tokens->getNextTokenOfKind($functionNameIndex, ['(']);
140+
$closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis);
141+
142+
$indices = [];
143+
144+
foreach ($argumentsAnalyzer->getArguments($tokens, $openParenthesis, $closeParenthesis) as $startIndexCandidate => $endIndex) {
145+
$indices[$tokens->getNextMeaningfulToken($startIndexCandidate - 1)] = $tokens->getPrevMeaningfulToken($endIndex + 1);
146+
}
147+
148+
return $indices;
149+
}
150+
}

src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,15 @@ public function configure(array $configuration = null)
106106
}
107107
}
108108

109+
/**
110+
* {@inheritdoc}
111+
*/
112+
public function getPriority()
113+
{
114+
// must be run after ImplodeCallFixer
115+
return -2;
116+
}
117+
109118
/**
110119
* {@inheritdoc}
111120
*/

src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ private function updateDocBlockIfNeeded(Tokens $tokens, $docBlockIndex)
147147
}
148148
$doc = $this->makeDocBlockMultiLineIfNeeded($doc, $tokens, $docBlockIndex);
149149
$lines = $this->addInternalAnnotation($doc, $tokens, $docBlockIndex);
150-
$lines = implode($lines);
150+
$lines = implode('', $lines);
151151

152152
$tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]);
153153
}
@@ -227,7 +227,7 @@ private function makeDocBlockMultiLineIfNeeded(DocBlock $doc, Tokens $tokens, $d
227227
if (1 === \count($lines) && empty($doc->getAnnotationsOfType('internal'))) {
228228
$lines = $this->splitUpDocBlock($lines, $tokens, $docBlockIndex);
229229

230-
return new DocBlock(implode($lines));
230+
return new DocBlock(implode('', $lines));
231231
}
232232

233233
return $doc;
@@ -275,6 +275,6 @@ private function getSingleLineDocBlockEntry($line)
275275
}
276276
$line = \array_slice($line, $i);
277277

278-
return implode($line);
278+
return implode('', $line);
279279
}
280280
}

src/Fixer/PhpUnit/PhpUnitMethodCasingFixer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ private function updateDocBlock(Tokens $tokens, $docBlockIndex)
267267
}
268268

269269
if ($docBlockNeesUpdate) {
270-
$lines = implode($lines);
270+
$lines = implode('', $lines);
271271
$tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]);
272272
}
273273
}

src/Fixer/PhpUnit/PhpUnitNoExpectationAnnotationFixer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ private function fixPhpUnitClass(Tokens $tokens, $startIndex, $endIndex)
233233
$newMethodsCode = '<?php $this->'
234234
.(isset($annotations['expectedExceptionMessageRegExp']) ? 'setExpectedExceptionRegExp' : 'setExpectedException')
235235
.'('
236-
.implode($paramList, ', ')
236+
.implode(', ', $paramList)
237237
.');';
238238
$newMethods = Tokens::fromCode($newMethodsCode);
239239
$newMethods[0] = new Token([

src/Fixer/PhpUnit/PhpUnitOrderedCoversFixer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens)
100100
);
101101
}
102102

103-
$tokens[$index] = new Token([T_DOC_COMMENT, implode($lines)]);
103+
$tokens[$index] = new Token([T_DOC_COMMENT, implode('', $lines)]);
104104
}
105105
}
106106
}

src/Fixer/PhpUnit/PhpUnitTestAnnotationFixer.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ private function applyTestAnnotation(Tokens $tokens, $startIndex, $endIndex)
145145

146146
$lines = $this->addTestAnnotation($lines, $tokens, $docBlockIndex);
147147

148-
$lines = implode($lines);
148+
$lines = implode('', $lines);
149149
$tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]);
150150
}
151151
}
@@ -167,7 +167,7 @@ private function applyTestPrefix(Tokens $tokens, $startIndex, $endIndex)
167167

168168
$lines = $this->updateDocBlock($tokens, $docBlockIndex);
169169

170-
$lines = implode($lines);
170+
$lines = implode('', $lines);
171171
$tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]);
172172

173173
$functionNameIndex = $tokens->getNextMeaningfulToken($i);
@@ -451,7 +451,7 @@ private function getSingleLineDocBlockEntry($line)
451451
}
452452
$line = \array_slice($line, $i);
453453

454-
return implode($line);
454+
return implode('', $line);
455455
}
456456

457457
/**
@@ -480,14 +480,14 @@ private function removeTestPrefixFromDependsAnnotation(Line $line)
480480
$line = \str_split($line->getContent());
481481

482482
$dependsIndex = $this->findWhereDependsFunctionNameStarts($line);
483-
$dependsFunctionName = implode(\array_slice($line, $dependsIndex));
483+
$dependsFunctionName = implode('', \array_slice($line, $dependsIndex));
484484

485485
if ($this->startsWith('test', $dependsFunctionName)) {
486486
$dependsFunctionName = $this->removeTestPrefix($dependsFunctionName);
487487
}
488488
array_splice($line, $dependsIndex);
489489

490-
return new Line(implode($line).$dependsFunctionName);
490+
return new Line(implode('', $line).$dependsFunctionName);
491491
}
492492

493493
/**
@@ -499,15 +499,15 @@ private function addTestPrefixToDependsAnnotation(Line $line)
499499
{
500500
$line = \str_split($line->getContent());
501501
$dependsIndex = $this->findWhereDependsFunctionNameStarts($line);
502-
$dependsFunctionName = implode(\array_slice($line, $dependsIndex));
502+
$dependsFunctionName = implode('', \array_slice($line, $dependsIndex));
503503

504504
if (!$this->startsWith('test', $dependsFunctionName)) {
505505
$dependsFunctionName = $this->addTestPrefix($dependsFunctionName);
506506
}
507507

508508
array_splice($line, $dependsIndex);
509509

510-
return new Line(implode($line).$dependsFunctionName);
510+
return new Line(implode('', $line).$dependsFunctionName);
511511
}
512512

513513
/**

src/Fixer/Phpdoc/PhpdocAlignFixer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ private function fixDocBlock($content)
328328
}
329329
}
330330

331-
return implode($lines);
331+
return implode('', $lines);
332332
}
333333

334334
/**

src/Fixer/Whitespace/NoExtraBlankLinesFixer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ private function removeMultipleBlankLines($index)
383383
$count = \count($parts);
384384

385385
if ($count > $expected) {
386-
$this->tokens[$index] = new Token([T_WHITESPACE, implode(\array_slice($parts, 0, $expected)).rtrim($parts[$count - 1], "\r\n")]);
386+
$this->tokens[$index] = new Token([T_WHITESPACE, implode('', \array_slice($parts, 0, $expected)).rtrim($parts[$count - 1], "\r\n")]);
387387
}
388388
}
389389

src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens)
101101
}
102102
}
103103

104-
$content = implode($lines);
104+
$content = implode('', $lines);
105105
if ('' !== $content) {
106106
$tokens[$index] = new Token([$token->getId(), $content]);
107107
} else {

src/RuleSet.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ final class RuleSet implements RuleSetInterface
170170
'fopen_flag_order' => true,
171171
'fopen_flags' => true,
172172
'function_to_constant' => true,
173+
'implode_call' => true,
173174
'is_null' => true,
174175
'modernize_types_casting' => true,
175176
'native_constant_invocation' => [

tests/AutoReview/FixerFactoryTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,13 @@ public function provideFixersPriorityCases()
9898
[$fixers['general_phpdoc_annotation_remove'], $fixers['phpdoc_trim']],
9999
[$fixers['general_phpdoc_annotation_remove'], $fixers['no_empty_phpdoc']],
100100
[$fixers['indentation_type'], $fixers['phpdoc_indent']],
101+
[$fixers['implode_call'], $fixers['method_argument_space']],
101102
[$fixers['is_null'], $fixers['yoda_style']],
102103
[$fixers['list_syntax'], $fixers['binary_operator_spaces']],
103104
[$fixers['list_syntax'], $fixers['ternary_operator_spaces']],
104105
[$fixers['method_separation'], $fixers['braces']],
105106
[$fixers['method_separation'], $fixers['indentation_type']],
107+
[$fixers['no_alias_functions'], $fixers['implode_call']],
106108
[$fixers['no_alias_functions'], $fixers['php_unit_dedicate_assert']],
107109
[$fixers['no_blank_lines_after_phpdoc'], $fixers['single_blank_line_before_namespace']],
108110
[$fixers['no_empty_comment'], $fixers['no_extra_blank_lines']],

0 commit comments

Comments
 (0)