mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-22 14:52:40 +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:
parent
49b83927b8
commit
465ad3fa44
2 changed files with 152 additions and 0 deletions
|
@ -53,6 +53,7 @@ final class ArcanistXHPASTLinter extends ArcanistLinter {
|
||||||
const LINT_BINARY_EXPRESSION_SPACING = 27;
|
const LINT_BINARY_EXPRESSION_SPACING = 27;
|
||||||
const LINT_ARRAY_INDEX_SPACING = 28;
|
const LINT_ARRAY_INDEX_SPACING = 28;
|
||||||
const LINT_RAGGED_CLASSTREE_EDGE = 29;
|
const LINT_RAGGED_CLASSTREE_EDGE = 29;
|
||||||
|
const LINT_IMPLICIT_FALLTHROUGH = 30;
|
||||||
|
|
||||||
|
|
||||||
public function getLintNameMap() {
|
public function getLintNameMap() {
|
||||||
|
@ -85,6 +86,7 @@ final class ArcanistXHPASTLinter extends ArcanistLinter {
|
||||||
self::LINT_BINARY_EXPRESSION_SPACING => 'Space Around Binary Operator',
|
self::LINT_BINARY_EXPRESSION_SPACING => 'Space Around Binary Operator',
|
||||||
self::LINT_ARRAY_INDEX_SPACING => 'Spacing Before Array Index',
|
self::LINT_ARRAY_INDEX_SPACING => 'Spacing Before Array Index',
|
||||||
self::LINT_RAGGED_CLASSTREE_EDGE => 'Class Not abstract Or final',
|
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,
|
=> ArcanistLintSeverity::SEVERITY_WARNING,
|
||||||
self::LINT_ARRAY_INDEX_SPACING
|
self::LINT_ARRAY_INDEX_SPACING
|
||||||
=> ArcanistLintSeverity::SEVERITY_WARNING,
|
=> ArcanistLintSeverity::SEVERITY_WARNING,
|
||||||
|
self::LINT_IMPLICIT_FALLTHROUGH
|
||||||
|
=> ArcanistLintSeverity::SEVERITY_WARNING,
|
||||||
|
|
||||||
// This is disabled by default because it implies a very strict policy
|
// This is disabled by default because it implies a very strict policy
|
||||||
// which isn't necessary in the general case.
|
// which isn't necessary in the general case.
|
||||||
|
@ -182,6 +186,101 @@ final class ArcanistXHPASTLinter extends ArcanistLinter {
|
||||||
$this->lintReusedIterators($root);
|
$this->lintReusedIterators($root);
|
||||||
$this->lintBraceFormatting($root);
|
$this->lintBraceFormatting($root);
|
||||||
$this->lintRaggedClasstreeEdges($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) {
|
private function lintBraceFormatting($root) {
|
||||||
|
|
53
src/lint/linter/xhpast/__tests__/data/switches.lint-test
Normal file
53
src/lint/linter/xhpast/__tests__/data/switches.lint-test
Normal 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
|
Loading…
Reference in a new issue