Skip to content

Commit e1b9595

Browse files
authored
Generic/InlineControlStructure: bail early for control structures without body (#880)
* Generic/InlineControlStructure: bail early for control structures without body The sniff now consistently handles all supported control structures without a body by bailing early. Extending the existing behavior for `while` and `for` to also include `do while`, `else`, `elseif`, `if`, and `foreach`. Previously, the sniff would incorrectly flag these empty control structures as inline control structures that needed curly braces. For `else`, `elseif`, `if`, and `foreach`, the fixer would remove the semicolon and add the curly braces. For `do while`, the fixer would add the curly braces and keep the semicolon in between the braces. In all the cases, the resulting code was syntactically correct. Consider the following example: ``` do ; while ($foo < 5); ``` Previously, PHPCS would flag this as an inline control structure and PHPCBF would fix it to: ``` do { ; } while ($foo < 5); ``` Now an empty `do while` is ignored by the sniff (no warnings and no fixes). Here is a link showing that control structures without a body are valid in PHP: https://3v4l.org/slnYL And here is a link showing that the way that they were being fixed by PHPCBF was resulting in valid code (`while` and `for` are not included below as they were already ignored before this commit): https://3v4l.org/8k1N3 * Generic/InlineControlStructure: removed unnecessary code block 29d0be3 changed the sniff to bail early for all control structures without body, so the code will never reach the fixer if `$closer + 1` is `T_SEMICOLON` and thus the removed condition is not necessary anymore. * Generic/InlineControlStructure: removed another unnecessary code block The original version of this part of the code that is now being removed was added in the early days by the commit that enabled this sniff to fix errors: squizlabs/PHP_CodeSniffer@a54c619#diff-4b3945c2100b0a92a56509de1b797bf58ad804cf36233c95c492479b665655dcR148-R154 The only two tests that were added with the commit mentioned above that trigger the removed condition are tests using `while` loops without body: squizlabs/PHP_CodeSniffer@a54c619#diff-116c49a7b0b31f724fc25409e31ba119d7f023146818bcb63edbe8f4071422e2R42-R43 Control structures without a body are the only cases where `$next` would be equal to `$end`. Thus, these are the only cases where the removed condition would be executed. But two previous commits, changed the sniff to bail early and not get to the fixer part when handling control structures without a body. 13c803b changed the sniff to ignore `while`/`for` without a body and updated the existing tests (squizlabs/PHP_CodeSniffer@13c803b#diff-2f069f3fe33bacdfc80485b97303aec66c98c451d07e6d86e41982b81ab1a294L49-R50). 29d0be3 expanded the same approach for `do while`/`else`/`elseif`/`if`/`foreach` control structures. After the removal of the `$next !== $end` check, the `$next` variable became unused allowing for further simplification of the code by removing the place where it was being defined. Note for reviewers: this commit is easier to evaluate when ignoring whitespaces.
1 parent 0782b55 commit e1b9595

File tree

4 files changed

+110
-97
lines changed

4 files changed

+110
-97
lines changed

src/Standards/Generic/Sniffs/ControlStructures/InlineControlStructureSniff.php

Lines changed: 66 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -70,21 +70,25 @@ public function process(File $phpcsFile, $stackPtr)
7070
}
7171
}
7272

73-
if ($tokens[$stackPtr]['code'] === T_WHILE || $tokens[$stackPtr]['code'] === T_FOR) {
74-
// This could be from a DO WHILE, which doesn't have an opening brace or a while/for without body.
75-
if (isset($tokens[$stackPtr]['parenthesis_closer']) === true) {
76-
$afterParensCloser = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($tokens[$stackPtr]['parenthesis_closer'] + 1), null, true);
77-
if ($afterParensCloser === false) {
78-
// Live coding.
79-
return;
80-
}
73+
if (isset($tokens[$stackPtr]['parenthesis_closer']) === true) {
74+
$nextTokenIndex = ($tokens[$stackPtr]['parenthesis_closer'] + 1);
75+
} else if (in_array($tokens[$stackPtr]['code'], [T_ELSE, T_DO], true) === true) {
76+
$nextTokenIndex = ($stackPtr + 1);
77+
}
8178

82-
if ($tokens[$afterParensCloser]['code'] === T_SEMICOLON) {
83-
$phpcsFile->recordMetric($stackPtr, 'Control structure defined inline', 'no');
84-
return;
85-
}
79+
if (isset($nextTokenIndex) === true) {
80+
$firstNonEmptyToken = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, $nextTokenIndex, null, true);
81+
if ($firstNonEmptyToken === false) {
82+
// Live coding.
83+
return;
8684
}
87-
}//end if
85+
86+
if ($tokens[$firstNonEmptyToken]['code'] === T_SEMICOLON) {
87+
// This is a control structure without a body. Bow out.
88+
$phpcsFile->recordMetric($stackPtr, 'Control structure defined inline', 'no');
89+
return;
90+
}
91+
}
8892

8993
if (isset($tokens[$stackPtr]['parenthesis_opener'], $tokens[$stackPtr]['parenthesis_closer']) === false
9094
&& $tokens[$stackPtr]['code'] !== T_ELSE
@@ -151,9 +155,7 @@ public function process(File $phpcsFile, $stackPtr)
151155
$closer = $stackPtr;
152156
}
153157

154-
if ($tokens[($closer + 1)]['code'] === T_WHITESPACE
155-
|| $tokens[($closer + 1)]['code'] === T_SEMICOLON
156-
) {
158+
if ($tokens[($closer + 1)]['code'] === T_WHITESPACE) {
157159
$phpcsFile->fixer->addContent($closer, ' {');
158160
} else {
159161
$phpcsFile->fixer->addContent($closer, ' { ');
@@ -249,102 +251,69 @@ public function process(File $phpcsFile, $stackPtr)
249251
}
250252

251253
$nextContent = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($end + 1), null, true);
252-
if ($nextContent === false || $tokens[$nextContent]['line'] !== $tokens[$end]['line']) {
253-
// Looks for completely empty statements.
254-
$next = $phpcsFile->findNext(T_WHITESPACE, ($closer + 1), ($end + 1), true);
255-
} else {
256-
$next = ($end + 1);
257-
$endLine = $end;
258-
}
259254

260-
if ($next !== $end) {
261-
if ($nextContent === false || $tokens[$nextContent]['line'] !== $tokens[$end]['line']) {
262-
// Account for a comment on the end of the line.
263-
for ($endLine = $end; $endLine < $phpcsFile->numTokens; $endLine++) {
264-
if (isset($tokens[($endLine + 1)]) === false
265-
|| $tokens[$endLine]['line'] !== $tokens[($endLine + 1)]['line']
266-
) {
267-
break;
268-
}
269-
}
270-
271-
if (isset(Tokens::COMMENT_TOKENS[$tokens[$endLine]['code']]) === false
272-
&& ($tokens[$endLine]['code'] !== T_WHITESPACE
273-
|| isset(Tokens::COMMENT_TOKENS[$tokens[($endLine - 1)]['code']]) === false)
274-
) {
275-
$endLine = $end;
276-
}
277-
}
278-
279-
if ($endLine !== $end) {
280-
$endToken = $endLine;
281-
$addedContent = '';
282-
} else {
283-
$endToken = $end;
284-
$addedContent = $phpcsFile->eolChar;
285-
286-
if ($tokens[$end]['code'] !== T_SEMICOLON
287-
&& $tokens[$end]['code'] !== T_CLOSE_CURLY_BRACKET
255+
if ($nextContent === false || $tokens[$nextContent]['line'] !== $tokens[$end]['line']) {
256+
// Account for a comment on the end of the line.
257+
for ($endLine = $end; $endLine < $phpcsFile->numTokens; $endLine++) {
258+
if (isset($tokens[($endLine + 1)]) === false
259+
|| $tokens[$endLine]['line'] !== $tokens[($endLine + 1)]['line']
288260
) {
289-
$phpcsFile->fixer->addContent($end, '; ');
261+
break;
290262
}
291263
}
292264

293-
$next = $phpcsFile->findNext(T_WHITESPACE, ($endToken + 1), null, true);
294-
if ($next !== false
295-
&& ($tokens[$next]['code'] === T_ELSE
296-
|| $tokens[$next]['code'] === T_ELSEIF)
265+
if (isset(Tokens::COMMENT_TOKENS[$tokens[$endLine]['code']]) === false
266+
&& ($tokens[$endLine]['code'] !== T_WHITESPACE
267+
|| isset(Tokens::COMMENT_TOKENS[$tokens[($endLine - 1)]['code']]) === false)
297268
) {
298-
$phpcsFile->fixer->addContentBefore($next, '} ');
299-
} else {
300-
$indent = '';
301-
for ($first = $stackPtr; $first > 0; $first--) {
302-
if ($tokens[$first]['column'] === 1) {
303-
break;
304-
}
305-
}
269+
$endLine = $end;
270+
}
271+
} else {
272+
$endLine = $end;
273+
}
306274

307-
if ($tokens[$first]['code'] === T_WHITESPACE) {
308-
$indent = $tokens[$first]['content'];
309-
} else if ($tokens[$first]['code'] === T_INLINE_HTML
310-
|| $tokens[$first]['code'] === T_OPEN_TAG
311-
) {
312-
$addedContent = '';
313-
}
275+
if ($endLine !== $end) {
276+
$endToken = $endLine;
277+
$addedContent = '';
278+
} else {
279+
$endToken = $end;
280+
$addedContent = $phpcsFile->eolChar;
314281

315-
$addedContent .= $indent.'}';
316-
if ($next !== false && $tokens[$endToken]['code'] === T_COMMENT) {
317-
$addedContent .= $phpcsFile->eolChar;
318-
}
282+
if ($tokens[$end]['code'] !== T_SEMICOLON
283+
&& $tokens[$end]['code'] !== T_CLOSE_CURLY_BRACKET
284+
) {
285+
$phpcsFile->fixer->addContent($end, '; ');
286+
}
287+
}
319288

320-
$phpcsFile->fixer->addContent($endToken, $addedContent);
321-
}//end if
289+
$next = $phpcsFile->findNext(T_WHITESPACE, ($endToken + 1), null, true);
290+
if ($next !== false
291+
&& ($tokens[$next]['code'] === T_ELSE
292+
|| $tokens[$next]['code'] === T_ELSEIF)
293+
) {
294+
$phpcsFile->fixer->addContentBefore($next, '} ');
322295
} else {
323-
if ($nextContent === false || $tokens[$nextContent]['line'] !== $tokens[$end]['line']) {
324-
// Account for a comment on the end of the line.
325-
for ($endLine = $end; $endLine < $phpcsFile->numTokens; $endLine++) {
326-
if (isset($tokens[($endLine + 1)]) === false
327-
|| $tokens[$endLine]['line'] !== $tokens[($endLine + 1)]['line']
328-
) {
329-
break;
330-
}
296+
$indent = '';
297+
for ($first = $stackPtr; $first > 0; $first--) {
298+
if ($tokens[$first]['column'] === 1) {
299+
break;
331300
}
301+
}
332302

333-
if ($tokens[$endLine]['code'] !== T_COMMENT
334-
&& ($tokens[$endLine]['code'] !== T_WHITESPACE
335-
|| $tokens[($endLine - 1)]['code'] !== T_COMMENT)
336-
) {
337-
$endLine = $end;
338-
}
303+
if ($tokens[$first]['code'] === T_WHITESPACE) {
304+
$indent = $tokens[$first]['content'];
305+
} else if ($tokens[$first]['code'] === T_INLINE_HTML
306+
|| $tokens[$first]['code'] === T_OPEN_TAG
307+
) {
308+
$addedContent = '';
339309
}
340310

341-
if ($endLine !== $end) {
342-
$phpcsFile->fixer->replaceToken($end, '');
343-
$phpcsFile->fixer->addNewlineBefore($endLine);
344-
$phpcsFile->fixer->addContent($endLine, '}');
345-
} else {
346-
$phpcsFile->fixer->replaceToken($end, '}');
311+
$addedContent .= $indent.'}';
312+
if ($next !== false && $tokens[$endToken]['code'] === T_COMMENT) {
313+
$addedContent .= $phpcsFile->eolChar;
347314
}
315+
316+
$phpcsFile->fixer->addContent($endToken, $addedContent);
348317
}//end if
349318

350319
$phpcsFile->fixer->endChangeset();

src/Standards/Generic/Tests/ControlStructures/InlineControlStructureUnitTest.1.inc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,3 +276,24 @@ function testFinally()
276276
if ($something) {
277277
echo 'hello';
278278
} else /* comment */ if ($somethingElse) echo 'hi';
279+
280+
if ($sniffShouldBailEarly);
281+
282+
if (false) {
283+
} elseif ($sniffShouldBailEarly);
284+
285+
if (false) {
286+
} else if ($sniffShouldBailEarly);
287+
288+
if (false) {
289+
} else ($sniffShouldGenerateError);
290+
291+
if (false) {
292+
} else; // Sniff should bail early.
293+
294+
foreach ($array as $sniffShouldBailEarly);
295+
296+
foreach ($array as $sniffShouldBailEarly)
297+
/* some comment */;
298+
299+
do ; while ($sniffShouldBailEarly > 5);

src/Standards/Generic/Tests/ControlStructures/InlineControlStructureUnitTest.1.inc.fixed

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,3 +314,25 @@ if ($something) {
314314
echo 'hello';
315315
} else /* comment */ if ($somethingElse) { echo 'hi';
316316
}
317+
318+
if ($sniffShouldBailEarly);
319+
320+
if (false) {
321+
} elseif ($sniffShouldBailEarly);
322+
323+
if (false) {
324+
} else if ($sniffShouldBailEarly);
325+
326+
if (false) {
327+
} else { ($sniffShouldGenerateError);
328+
}
329+
330+
if (false) {
331+
} else; // Sniff should bail early.
332+
333+
foreach ($array as $sniffShouldBailEarly);
334+
335+
foreach ($array as $sniffShouldBailEarly)
336+
/* some comment */;
337+
338+
do ; while ($sniffShouldBailEarly > 5);

src/Standards/Generic/Tests/ControlStructures/InlineControlStructureUnitTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public function getErrorList($testFile='')
8181
260 => 1,
8282
269 => 1,
8383
278 => 1,
84+
289 => 1,
8485
];
8586

8687
default:

0 commit comments

Comments
 (0)