Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 95aec52

Browse files
julienfalquekeradus
authored andcommitted
Add NoBreakCommentFixer
1 parent b3ddc0a commit 95aec52

6 files changed

Lines changed: 1357 additions & 0 deletions

File tree

README.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,16 @@ Choose from the list of available rules:
638638

639639
There should be no blank lines before a namespace declaration.
640640

641+
* **no_break_comment** [@PSR2, @Symfony]
642+
643+
There must be a comment when fall-through is intentional in a non-empty
644+
case body.
645+
646+
Configuration options:
647+
648+
- ``comment_text`` (``string``): the text to use in the added comment and to
649+
detect it; defaults to ``'no break'``
650+
641651
* **no_closing_tag** [@PSR2, @Symfony]
642652

643653
The closing ``?>`` tag MUST be omitted from files containing only PHP.
Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
<?php
2+
3+
/*
4+
* This file is part of PHP CS Fixer.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
* Dariusz Rumiński <[email protected]>
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\ControlStructure;
14+
15+
use PhpCsFixer\AbstractFixer;
16+
use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface;
17+
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
18+
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
19+
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
20+
use PhpCsFixer\FixerDefinition\CodeSample;
21+
use PhpCsFixer\FixerDefinition\FixerDefinition;
22+
use PhpCsFixer\Tokenizer\Token;
23+
use PhpCsFixer\Tokenizer\Tokens;
24+
25+
/**
26+
* Fixer for rule defined in PSR2 ¶5.2.
27+
*/
28+
final class NoBreakCommentFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface
29+
{
30+
/**
31+
* {@inheritdoc}
32+
*/
33+
public function getDefinition()
34+
{
35+
return new FixerDefinition(
36+
'There must be a comment when fall-through is intentional in a non-empty case body.',
37+
[
38+
new CodeSample("<?php\nswitch (\$foo)\n{ case 1:\n foo();\n case 2:\n bar();\n}"),
39+
new CodeSample("<?php\nswitch (\$foo)\n{ case 1:\n foo();\n // no break\n break;\n case 2:\n bar();\n}"),
40+
],
41+
'Adds a "no break" comment before fall-through cases, and removes it if there is no fall-through.'
42+
);
43+
}
44+
45+
/**
46+
* {@inheritdoc}
47+
*/
48+
public function isCandidate(Tokens $tokens)
49+
{
50+
return $tokens->isAnyTokenKindsFound([T_CASE, T_DEFAULT]);
51+
}
52+
53+
/**
54+
* {@inheritdoc}
55+
*/
56+
protected function createConfigurationDefinition()
57+
{
58+
return new FixerConfigurationResolver([
59+
(new FixerOptionBuilder('comment_text', 'The text to use in the added comment and to detect it.'))
60+
->setAllowedTypes(['string'])
61+
->setDefault('no break')
62+
->getOption(),
63+
]);
64+
}
65+
66+
/**
67+
* {@inheritdoc}
68+
*/
69+
protected function applyFix(\SplFileInfo $file, Tokens $tokens)
70+
{
71+
for ($position = count($tokens) - 1; $position >= 0; --$position) {
72+
if ($tokens[$position]->isGivenKind([T_CASE, T_DEFAULT])) {
73+
$this->fixCase($tokens, $position);
74+
}
75+
}
76+
}
77+
78+
/**
79+
* @param Tokens $tokens
80+
* @param int $casePosition
81+
*/
82+
private function fixCase(Tokens $tokens, $casePosition)
83+
{
84+
$empty = true;
85+
$fallThrough = true;
86+
$commentPosition = null;
87+
for ($i = $tokens->getNextTokenOfKind($casePosition, [':', ';']) + 1, $max = count($tokens); $i < $max; ++$i) {
88+
if ($tokens[$i]->isGivenKind([T_SWITCH, T_IF, T_ELSE, T_ELSEIF, T_FOR, T_FOREACH, T_WHILE, T_DO, T_FUNCTION, T_CLASS])) {
89+
$empty = false;
90+
$i = $this->getStructureEnd($tokens, $i);
91+
92+
continue;
93+
}
94+
95+
if ($tokens[$i]->isGivenKind([T_BREAK, T_CONTINUE, T_RETURN, T_EXIT, T_THROW, T_GOTO])) {
96+
$fallThrough = false;
97+
98+
continue;
99+
}
100+
101+
if ($tokens[$i]->equals('}') || $tokens[$i]->isGivenKind(T_ENDSWITCH)) {
102+
if (null !== $commentPosition) {
103+
$this->removeComment($tokens, $commentPosition);
104+
}
105+
106+
break;
107+
}
108+
109+
if ($this->isNoBreakComment($tokens[$i])) {
110+
$commentPosition = $i;
111+
112+
continue;
113+
}
114+
115+
if ($tokens[$i]->isGivenKind([T_CASE, T_DEFAULT])) {
116+
if (!$empty && $fallThrough) {
117+
if (null !== $commentPosition && $tokens->getPrevNonWhitespace($i) !== $commentPosition) {
118+
$this->removeComment($tokens, $commentPosition);
119+
$commentPosition = null;
120+
}
121+
122+
if (null === $commentPosition) {
123+
$this->insertCommentAt($tokens, $i);
124+
} else {
125+
$this->ensureNewLineAt($tokens, $commentPosition);
126+
}
127+
} elseif (null !== $commentPosition) {
128+
$this->removeComment($tokens, $commentPosition);
129+
}
130+
131+
break;
132+
}
133+
134+
if (!$tokens[$i]->isGivenKind([T_COMMENT, T_WHITESPACE])) {
135+
$empty = false;
136+
}
137+
}
138+
}
139+
140+
/**
141+
* @param Token $token
142+
*
143+
* @return bool
144+
*/
145+
private function isNoBreakComment(Token $token)
146+
{
147+
if (!$token->isComment()) {
148+
return false;
149+
}
150+
151+
$text = $this->configuration['comment_text'];
152+
153+
return preg_match("~^((//|#)\s*$text\s*)|(/\*\*?\s*$text\s*\*/)$~", $token->getContent());
154+
}
155+
156+
/**
157+
* @param Tokens $tokens
158+
* @param int $casePosition
159+
*/
160+
private function insertCommentAt(Tokens $tokens, $casePosition)
161+
{
162+
$lineEnding = $this->whitespacesConfig->getLineEnding();
163+
164+
$newlinePosition = $this->ensureNewLineAt($tokens, $casePosition);
165+
166+
$newlineToken = $tokens[$newlinePosition];
167+
168+
$nbNewlines = substr_count($newlineToken->getContent(), $lineEnding);
169+
if ($tokens[$newlinePosition - 1]->isGivenKind(T_OPEN_TAG) && false !== strpos($tokens[$newlinePosition - 1]->getContent(), $lineEnding)) {
170+
++$nbNewlines;
171+
172+
if (false === strpos($newlineToken->getContent(), $lineEnding)) {
173+
$tokens[$newlinePosition] = new Token([$newlineToken->getId(), $lineEnding.$newlineToken->getContent()]);
174+
}
175+
}
176+
177+
if ($nbNewlines > 1) {
178+
preg_match('/^(.*?)(\R[ \t]*)$/s', $newlineToken->getContent(), $matches);
179+
180+
$indent = $this->getIndentAt($tokens, $newlinePosition - 1);
181+
$tokens[$newlinePosition] = new Token([$newlineToken->getId(), $matches[1].$lineEnding.$indent]);
182+
$tokens->insertAt(++$newlinePosition, new Token([T_WHITESPACE, $matches[2]]));
183+
}
184+
185+
$tokens->insertAt($newlinePosition, new Token([T_COMMENT, '// '.$this->configuration['comment_text']]));
186+
187+
if (!$tokens[$newlinePosition - 1]->isGivenKind(T_OPEN_TAG)) {
188+
$this->ensureNewLineAt($tokens, $newlinePosition);
189+
} else {
190+
$tokens->insertAt(
191+
$newlinePosition,
192+
new Token([T_WHITESPACE, $this->getIndentAt($tokens, $newlinePosition - 1)])
193+
);
194+
}
195+
}
196+
197+
/**
198+
* @param Tokens $tokens
199+
* @param int $position
200+
*
201+
* @return int The newline token position
202+
*/
203+
private function ensureNewLineAt(Tokens $tokens, $position)
204+
{
205+
$lineEnding = $this->whitespacesConfig->getLineEnding();
206+
$content = $lineEnding.$this->getIndentAt($tokens, $position);
207+
208+
$whitespaceToken = $tokens[$position - 1];
209+
if (!$whitespaceToken->isGivenKind(T_WHITESPACE)) {
210+
$tokens->insertAt($position, new Token([T_WHITESPACE, $content]));
211+
212+
return $position;
213+
}
214+
215+
if ($tokens[$position - 2]->isGivenKind(T_OPEN_TAG) && false !== strpos($tokens[$position - 2]->getContent(), $lineEnding)) {
216+
$content = preg_replace('/^\R/', '', $content);
217+
}
218+
219+
if (false === strpos($whitespaceToken->getContent(), $lineEnding)) {
220+
$tokens[$position - 1] = new Token([T_WHITESPACE, $content]);
221+
}
222+
223+
return $position - 1;
224+
}
225+
226+
/**
227+
* @param Tokens $tokens
228+
* @param int $commentPosition
229+
*/
230+
private function removeComment(Tokens $tokens, $commentPosition)
231+
{
232+
if ($tokens[$tokens->getPrevNonWhitespace($commentPosition)]->isGivenKind(T_OPEN_TAG)) {
233+
$whitespacePosition = $commentPosition + 1;
234+
$regex = '/^\R[ \t]*/';
235+
} else {
236+
$whitespacePosition = $commentPosition - 1;
237+
$regex = '/\R[ \t]*$/';
238+
}
239+
240+
$whitespaceToken = $tokens[$whitespacePosition];
241+
if ($whitespaceToken->isGivenKind(T_WHITESPACE)) {
242+
$content = preg_replace($regex, '', $whitespaceToken->getContent());
243+
if ('' !== $content) {
244+
$tokens[$whitespacePosition] = new Token([T_WHITESPACE, $content]);
245+
} else {
246+
$tokens->clearAt($whitespacePosition);
247+
}
248+
}
249+
250+
$tokens->clearTokenAndMergeSurroundingWhitespace($commentPosition);
251+
}
252+
253+
/**
254+
* @param Tokens $tokens
255+
* @param int $position
256+
*
257+
* @return string
258+
*/
259+
private function getIndentAt(Tokens $tokens, $position)
260+
{
261+
while (true) {
262+
$position = $tokens->getPrevTokenOfKind($position, [[T_WHITESPACE]]);
263+
264+
if (null === $position) {
265+
break;
266+
}
267+
268+
$content = $tokens[$position]->getContent();
269+
270+
$prevToken = $tokens[$position - 1];
271+
if ($prevToken->isGivenKind(T_OPEN_TAG) && preg_match('/\R$/', $prevToken->getContent())) {
272+
$content = $this->whitespacesConfig->getLineEnding().$content;
273+
}
274+
275+
if (preg_match('/\R([ \t]*)$/', $content, $matches)) {
276+
return $matches[1];
277+
}
278+
}
279+
280+
return '';
281+
}
282+
283+
/**
284+
* @param Tokens $tokens
285+
* @param int $position
286+
*
287+
* @return int
288+
*/
289+
private function getStructureEnd(Tokens $tokens, $position)
290+
{
291+
$initialToken = $tokens[$position];
292+
293+
if ($initialToken->isGivenKind([T_FOR, T_FOREACH, T_WHILE, T_IF, T_ELSE, T_ELSEIF, T_SWITCH, T_FUNCTION])) {
294+
$position = $tokens->findBlockEnd(
295+
Tokens::BLOCK_TYPE_PARENTHESIS_BRACE,
296+
$tokens->getNextTokenOfKind($position, ['('])
297+
);
298+
} elseif ($initialToken->isGivenKind([T_CLASS])) {
299+
$openParenthesisPosition = $tokens->getNextMeaningfulToken($position);
300+
if ('(' === $tokens[$openParenthesisPosition]->getContent()) {
301+
$position = $tokens->findBlockEnd(
302+
Tokens::BLOCK_TYPE_PARENTHESIS_BRACE,
303+
$openParenthesisPosition
304+
);
305+
}
306+
}
307+
308+
$position = $tokens->getNextMeaningfulToken($position);
309+
if ('{' !== $tokens[$position]->getContent()) {
310+
return $tokens->getNextTokenOfKind($position, [';']);
311+
}
312+
313+
$position = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $position);
314+
315+
if ($initialToken->isGivenKind([T_DO])) {
316+
$position = $tokens->findBlockEnd(
317+
Tokens::BLOCK_TYPE_PARENTHESIS_BRACE,
318+
$tokens->getNextTokenOfKind($position, ['('])
319+
);
320+
321+
return $tokens->getNextTokenOfKind($position, [';']);
322+
}
323+
324+
return $position;
325+
}
326+
}

src/RuleSet.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ final class RuleSet implements RuleSetInterface
3939
'lowercase_constants' => true,
4040
'lowercase_keywords' => true,
4141
'method_argument_space' => ['ensure_fully_multiline' => true],
42+
'no_break_comment' => true,
4243
'no_closing_tag' => true,
4344
'no_spaces_after_function_name' => true,
4445
'no_spaces_inside_parenthesis' => true,

0 commit comments

Comments
 (0)