1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-22 06:42:41 +01:00

Add a lint warning for implicit fallthrough in switch statements

Summary: If a case does not end with break, continue, throw, exit or return and does not have a "fallthrough" comment, raise a warning.

Test Plan: Ran test case; ran linter against all of Phabricator (no hits).

Reviewers: btrahan

Reviewed By: btrahan

CC: aran, epriestley

Differential Revision: https://secure.phabricator.com/D1824
This commit is contained in:
epriestley 2012-03-08 12:20:19 -08:00
parent 49b83927b8
commit 465ad3fa44
2 changed files with 152 additions and 0 deletions

View file

@ -53,6 +53,7 @@ final class ArcanistXHPASTLinter extends ArcanistLinter {
const LINT_BINARY_EXPRESSION_SPACING = 27;
const LINT_ARRAY_INDEX_SPACING = 28;
const LINT_RAGGED_CLASSTREE_EDGE = 29;
const LINT_IMPLICIT_FALLTHROUGH = 30;
public function getLintNameMap() {
@ -85,6 +86,7 @@ final class ArcanistXHPASTLinter extends ArcanistLinter {
self::LINT_BINARY_EXPRESSION_SPACING => 'Space Around Binary Operator',
self::LINT_ARRAY_INDEX_SPACING => 'Spacing Before Array Index',
self::LINT_RAGGED_CLASSTREE_EDGE => 'Class Not abstract Or final',
self::LINT_IMPLICIT_FALLTHROUGH => 'Implicit Fallthrough',
);
}
@ -111,6 +113,8 @@ final class ArcanistXHPASTLinter extends ArcanistLinter {
=> ArcanistLintSeverity::SEVERITY_WARNING,
self::LINT_ARRAY_INDEX_SPACING
=> ArcanistLintSeverity::SEVERITY_WARNING,
self::LINT_IMPLICIT_FALLTHROUGH
=> ArcanistLintSeverity::SEVERITY_WARNING,
// This is disabled by default because it implies a very strict policy
// which isn't necessary in the general case.
@ -182,6 +186,101 @@ final class ArcanistXHPASTLinter extends ArcanistLinter {
$this->lintReusedIterators($root);
$this->lintBraceFormatting($root);
$this->lintRaggedClasstreeEdges($root);
$this->lintImplicitFallthrough($root);
}
private function lintImplicitFallthrough($root) {
$switches = $root->selectDescendantsOfType('n_SWITCH');
foreach ($switches as $switch) {
$blocks = array();
$cases = $switch->selectDescendantsOfType('n_CASE');
foreach ($cases as $case) {
$blocks[] = $case;
}
$defaults = $switch->selectDescendantsOfType('n_DEFAULT');
foreach ($defaults as $default) {
$blocks[] = $default;
}
foreach ($blocks as $key => $block) {
$tokens = $block->getTokens();
// Get all the trailing nonsemantic tokens, since we need to look for
// "fallthrough" comments past the end of the semantic block.
$last = end($tokens);
while ($last && $last = $last->getNextToken()) {
if (!$last->isSemantic()) {
$tokens[] = $last;
}
}
$blocks[$key] = $tokens;
}
foreach ($blocks as $tokens) {
// Test each block (case or default statement) to see if it's OK. It's
// OK if:
//
// - it is empty; or
// - it ends in break, return, throw, continue or exit; or
// - it has a comment with "fallthrough" in its text.
// Empty blocks are OK, so we start this at `true` and only set it to
// false if we find a statement.
$block_ok = true;
// Keeps track of whether the current statement is one that validates
// the block (break, return, throw, continue) or something else.
$statement_ok = false;
foreach ($tokens as $token) {
if (!$token->isSemantic()) {
// Liberally match "fall" in the comment text so that comments like
// "fallthru", "fall through", "fallthrough", etc., are accepted.
if (preg_match('/fall/i', $token->getValue())) {
$block_ok = true;
break;
}
continue;
}
$tok_type = $token->getTypeName();
if ($tok_type == ';') {
if ($statement_ok) {
$statment_ok = false;
} else {
$block_ok = false;
}
continue;
}
if ($tok_type == 'T_RETURN' ||
$tok_type == 'T_BREAK' ||
$tok_type == 'T_CONTINUE' ||
$tok_type == 'T_THROW' ||
$tok_type == 'T_EXIT') {
$statement_ok = true;
$block_ok = true;
}
}
if (!$block_ok) {
$this->raiseLintAtToken(
head($tokens),
self::LINT_IMPLICIT_FALLTHROUGH,
"This 'case' or 'default' has a nonempty block which does not ".
"end with 'break', 'continue', 'return', 'throw' or 'exit'. Did ".
"you forget to add one of those? If you intend to fall through, ".
"add a '// fallthrough' comment to silence this warning.");
}
}
}
}
private function lintBraceFormatting($root) {

View file

@ -0,0 +1,53 @@
<?php
$x = 0;
$y = 0;
switch ($x) {
case 1:
$x++;
continue;
case 2:
$x++;
break;
case 3:
$x++;
return;
case 4:
$x++;
throw new Exception("!");
case 5:
break 2;
case 6:
continue 2;
case 7:
case 8:
$x++;
break;
case 9:
$x++;
/* fallthrough */
case 10:
$x++;
break;
case 11:
$x++;
exit(1);
default:
break;
}
switch ($x) {
case 1:
$x++;
case 2:
break;
}
switch ($x) {
default:
$x++;
}
~~~~~~~~~~
warning:41:3
warning:48:3