Skip to content

Commit 2463eff

Browse files
committed
bug #4895 Fix conflict between header_comment and declare_strict_types (BackEndTea, julienfalque)
This PR was merged into the 2.15 branch. Discussion ---------- Fix conflict between header_comment and declare_strict_types Fixes #3467. /cc @BackEndTea Commits ------- ee98411 Fix header_comment/declare_strict_types conflict db3d415 Add test cases for header comment and declare strict
2 parents adbb12e + ee98411 commit 2463eff

File tree

6 files changed

+271
-102
lines changed

6 files changed

+271
-102
lines changed

src/Fixer/Comment/HeaderCommentFixer.php

Lines changed: 117 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public function isCandidate(Tokens $tokens)
113113
/**
114114
* {@inheritdoc}
115115
*
116-
* Must run after NoBlankLinesAfterPhpdocFixer.
116+
* Must run after DeclareStrictTypesFixer, NoBlankLinesAfterPhpdocFixer.
117117
*/
118118
public function getPriority()
119119
{
@@ -128,30 +128,46 @@ public function getPriority()
128128
*/
129129
protected function applyFix(\SplFileInfo $file, Tokens $tokens)
130130
{
131-
// figure out where the comment should be placed
132-
$headerNewIndex = $this->findHeaderCommentInsertionIndex($tokens);
131+
$location = $this->configuration['location'];
133132

134-
// check if there is already a comment
135-
$headerCurrentIndex = $this->findHeaderCommentCurrentIndex($tokens, $headerNewIndex - 1);
133+
$locationIndexes = [];
134+
foreach (['after_open', 'after_declare_strict'] as $possibleLocation) {
135+
$locationIndex = $this->findHeaderCommentInsertionIndex($tokens, $possibleLocation);
136136

137-
if (null === $headerCurrentIndex) {
138-
if ('' === $this->configuration['header']) {
139-
return; // header not found and none should be set, return
140-
}
137+
if (!isset($locationIndexes[$locationIndex]) || $possibleLocation === $location) {
138+
$locationIndexes[$locationIndex] = $possibleLocation;
141139

142-
$this->insertHeader($tokens, $headerNewIndex);
143-
} elseif ($this->getHeaderAsComment() !== $tokens[$headerCurrentIndex]->getContent()) {
144-
$tokens->clearTokenAndMergeSurroundingWhitespace($headerCurrentIndex);
145-
if ('' === $this->configuration['header']) {
146-
return; // header found and cleared, none should be set, return
140+
continue;
147141
}
148-
149-
$this->insertHeader($tokens, $headerNewIndex);
150-
} else {
151-
$headerNewIndex = $headerCurrentIndex;
152142
}
153143

154-
$this->fixWhiteSpaceAroundHeader($tokens, $headerNewIndex);
144+
foreach (array_values($locationIndexes) as $possibleLocation) {
145+
// figure out where the comment should be placed
146+
$headerNewIndex = $this->findHeaderCommentInsertionIndex($tokens, $possibleLocation);
147+
148+
// check if there is already a comment
149+
$headerCurrentIndex = $this->findHeaderCommentCurrentIndex($tokens, $headerNewIndex - 1);
150+
151+
if (null === $headerCurrentIndex) {
152+
if ('' === $this->configuration['header'] || $possibleLocation !== $location) {
153+
continue;
154+
}
155+
156+
$this->insertHeader($tokens, $headerNewIndex);
157+
} elseif ($this->getHeaderAsComment() !== $tokens[$headerCurrentIndex]->getContent() || $possibleLocation !== $location) {
158+
$this->removeHeader($tokens, $headerCurrentIndex);
159+
160+
if ('' === $this->configuration['header']) {
161+
continue;
162+
}
163+
164+
if ($possibleLocation === $location) {
165+
$this->insertHeader($tokens, $headerNewIndex);
166+
}
167+
} else {
168+
$this->fixWhiteSpaceAroundHeader($tokens, $headerCurrentIndex);
169+
}
170+
}
155171
}
156172

157173
/**
@@ -228,11 +244,13 @@ private function findHeaderCommentCurrentIndex(Tokens $tokens, $headerNewIndex)
228244
/**
229245
* Find the index where the header comment must be inserted.
230246
*
247+
* @param string $location
248+
*
231249
* @return int
232250
*/
233-
private function findHeaderCommentInsertionIndex(Tokens $tokens)
251+
private function findHeaderCommentInsertionIndex(Tokens $tokens, $location)
234252
{
235-
if ('after_open' === $this->configuration['location']) {
253+
if ('after_open' === $location) {
236254
return 1;
237255
}
238256

@@ -287,24 +305,31 @@ private function fixWhiteSpaceAroundHeader(Tokens $tokens, $headerIndex)
287305
$lineEnding = $this->whitespacesConfig->getLineEnding();
288306

289307
// fix lines after header comment
290-
$expectedLineCount = 'both' === $this->configuration['separate'] || 'bottom' === $this->configuration['separate'] ? 2 : 1;
308+
if (
309+
('both' === $this->configuration['separate'] || 'bottom' === $this->configuration['separate'])
310+
&& null !== $tokens->getNextMeaningfulToken($headerIndex)
311+
) {
312+
$expectedLineCount = 2;
313+
} else {
314+
$expectedLineCount = 1;
315+
}
291316
if ($headerIndex === \count($tokens) - 1) {
292317
$tokens->insertAt($headerIndex + 1, new Token([T_WHITESPACE, str_repeat($lineEnding, $expectedLineCount)]));
293318
} else {
294-
$afterCommentIndex = $tokens->getNextNonWhitespace($headerIndex);
295-
$lineBreakCount = $this->getLineBreakCount($tokens, $headerIndex + 1, null === $afterCommentIndex ? \count($tokens) : $afterCommentIndex);
319+
$lineBreakCount = $this->getLineBreakCount($tokens, $headerIndex, 1);
296320
if ($lineBreakCount < $expectedLineCount) {
297321
$missing = str_repeat($lineEnding, $expectedLineCount - $lineBreakCount);
298322
if ($tokens[$headerIndex + 1]->isWhitespace()) {
299323
$tokens[$headerIndex + 1] = new Token([T_WHITESPACE, $missing.$tokens[$headerIndex + 1]->getContent()]);
300324
} else {
301325
$tokens->insertAt($headerIndex + 1, new Token([T_WHITESPACE, $missing]));
302326
}
303-
} elseif ($lineBreakCount > 2) {
304-
// remove extra line endings
305-
if ($tokens[$headerIndex + 1]->isWhitespace()) {
306-
$tokens[$headerIndex + 1] = new Token([T_WHITESPACE, $lineEnding.$lineEnding]);
307-
}
327+
} elseif ($lineBreakCount > $expectedLineCount && $tokens[$headerIndex + 1]->isWhitespace()) {
328+
$newLinesToRemove = $lineBreakCount - $expectedLineCount;
329+
$tokens[$headerIndex + 1] = new Token([
330+
T_WHITESPACE,
331+
Preg::replace("/^\\R{{$newLinesToRemove}}/", '', $tokens[$headerIndex + 1]->getContent()),
332+
]);
308333
}
309334
}
310335

@@ -317,27 +342,80 @@ private function fixWhiteSpaceAroundHeader(Tokens $tokens, $headerIndex)
317342
$tokens[$prev] = new Token([T_OPEN_TAG, Preg::replace($regex, $lineEnding, $tokens[$prev]->getContent())]);
318343
}
319344

320-
$lineBreakCount = $this->getLineBreakCount($tokens, $prev, $headerIndex);
345+
$lineBreakCount = $this->getLineBreakCount($tokens, $headerIndex, -1);
321346
if ($lineBreakCount < $expectedLineCount) {
322347
// because of the way the insert index was determined for header comment there cannot be an empty token here
323348
$tokens->insertAt($headerIndex, new Token([T_WHITESPACE, str_repeat($lineEnding, $expectedLineCount - $lineBreakCount)]));
324349
}
325350
}
326351

327352
/**
328-
* @param int $indexStart
329-
* @param int $indexEnd
353+
* @param int $index
354+
* @param int $direction
330355
*
331356
* @return int
332357
*/
333-
private function getLineBreakCount(Tokens $tokens, $indexStart, $indexEnd)
358+
private function getLineBreakCount(Tokens $tokens, $index, $direction)
359+
{
360+
$whitespace = '';
361+
362+
for ($index += $direction; isset($tokens[$index]); $index += $direction) {
363+
$token = $tokens[$index];
364+
365+
if ($token->isWhitespace()) {
366+
$whitespace .= $token->getContent();
367+
368+
continue;
369+
}
370+
371+
if (-1 === $direction && $token->isGivenKind(T_OPEN_TAG)) {
372+
$whitespace .= $token->getContent();
373+
}
374+
375+
if ('' !== $token->getContent()) {
376+
break;
377+
}
378+
}
379+
380+
return substr_count($whitespace, "\n");
381+
}
382+
383+
private function removeHeader(Tokens $tokens, $index)
334384
{
335-
$lineCount = 0;
336-
for ($i = $indexStart; $i < $indexEnd; ++$i) {
337-
$lineCount += substr_count($tokens[$i]->getContent(), "\n");
385+
$prevIndex = $index - 1;
386+
$prevToken = $tokens[$prevIndex];
387+
$newlineRemoved = false;
388+
389+
if ($prevToken->isWhitespace()) {
390+
$content = $prevToken->getContent();
391+
392+
if (Preg::match('/\R/', $content)) {
393+
$newlineRemoved = true;
394+
}
395+
396+
$content = Preg::replace('/\R?\h*$/', '', $content);
397+
398+
if ('' !== $content) {
399+
$tokens[$prevIndex] = new Token([T_WHITESPACE, $content]);
400+
} else {
401+
$tokens->clearAt($prevIndex);
402+
}
338403
}
339404

340-
return $lineCount;
405+
$nextIndex = $index + 1;
406+
$nextToken = isset($tokens[$nextIndex]) ? $tokens[$nextIndex] : null;
407+
408+
if (!$newlineRemoved && null !== $nextToken && $nextToken->isWhitespace()) {
409+
$content = Preg::replace('/^\R/', '', $nextToken->getContent());
410+
411+
if ('' !== $content) {
412+
$tokens[$nextIndex] = new Token([T_WHITESPACE, $content]);
413+
} else {
414+
$tokens->clearAt($nextIndex);
415+
}
416+
}
417+
418+
$tokens->clearTokenAndMergeSurroundingWhitespace($index);
341419
}
342420

343421
/**
@@ -346,5 +424,7 @@ private function getLineBreakCount(Tokens $tokens, $indexStart, $indexEnd)
346424
private function insertHeader(Tokens $tokens, $index)
347425
{
348426
$tokens->insertAt($index, new Token([self::HEADER_COMMENT === $this->configuration['comment_type'] ? T_COMMENT : T_DOC_COMMENT, $this->getHeaderAsComment()]));
427+
428+
$this->fixWhiteSpaceAroundHeader($tokens, $index);
349429
}
350430
}

src/Fixer/Strict/DeclareStrictTypesFixer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public function getDefinition()
4747
/**
4848
* {@inheritdoc}
4949
*
50-
* Must run before BlankLineAfterOpeningTagFixer, DeclareEqualNormalizeFixer.
50+
* Must run before BlankLineAfterOpeningTagFixer, DeclareEqualNormalizeFixer, HeaderCommentFixer.
5151
*/
5252
public function getPriority()
5353
{

tests/AutoReview/FixerFactoryTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public function provideFixersPriorityCases()
8787
[$fixers['combine_nested_dirname'], $fixers['no_spaces_inside_parenthesis']],
8888
[$fixers['declare_strict_types'], $fixers['blank_line_after_opening_tag']],
8989
[$fixers['declare_strict_types'], $fixers['declare_equal_normalize']],
90+
[$fixers['declare_strict_types'], $fixers['header_comment']],
9091
[$fixers['dir_constant'], $fixers['combine_nested_dirname']],
9192
[$fixers['doctrine_annotation_array_assignment'], $fixers['doctrine_annotation_spaces']],
9293
[$fixers['elseif'], $fixers['braces']],

0 commit comments

Comments
 (0)