diff --git a/src/lint/linter/ArcanistXHPASTLinter.php b/src/lint/linter/ArcanistXHPASTLinter.php index b43edad6..f75082cb 100644 --- a/src/lint/linter/ArcanistXHPASTLinter.php +++ b/src/lint/linter/ArcanistXHPASTLinter.php @@ -49,6 +49,7 @@ final class ArcanistXHPASTLinter extends ArcanistBaseXHPASTLinter { const LINT_EMPTY_STATEMENT = 47; const LINT_ARRAY_SEPARATOR = 48; const LINT_CONSTRUCTOR_PARENTHESES = 49; + const LINT_DUPLICATE_SWITCH_CASE = 50; private $naminghook; private $switchhook; @@ -109,6 +110,7 @@ final class ArcanistXHPASTLinter extends ArcanistBaseXHPASTLinter { self::LINT_EMPTY_STATEMENT => 'Empty Block Statement', self::LINT_ARRAY_SEPARATOR => 'Array Separator', self::LINT_CONSTRUCTOR_PARENTHESES => 'Constructor Parentheses', + self::LINT_DUPLICATE_SWITCH_CASE => 'Duplicate Case Statements', ); } @@ -199,7 +201,7 @@ final class ArcanistXHPASTLinter extends ArcanistBaseXHPASTLinter { public function getVersion() { // The version number should be incremented whenever a new rule is added. - return '10'; + return '11'; } protected function resolveFuture($path, Future $future) { @@ -271,6 +273,7 @@ final class ArcanistXHPASTLinter extends ArcanistBaseXHPASTLinter { 'lintEmptyBlockStatements' => self::LINT_EMPTY_STATEMENT, 'lintArraySeparator' => self::LINT_ARRAY_SEPARATOR, 'lintConstructorParentheses' => self::LINT_CONSTRUCTOR_PARENTHESES, + 'lintSwitchStatements' => self::LINT_DUPLICATE_SWITCH_CASE, ); foreach ($method_codes as $method => $codes) { @@ -2868,6 +2871,44 @@ final class ArcanistXHPASTLinter extends ArcanistBaseXHPASTLinter { } } + private function lintSwitchStatements(XHPASTNode $root) { + $switch_statements = $root->selectDescendantsOfType('n_SWITCH'); + + foreach ($switch_statements as $switch_statement) { + $case_statements = $switch_statement + ->getChildOfType(1, 'n_STATEMENT_LIST') + ->getChildrenOfType('n_CASE'); + $nodes_by_case = array(); + + foreach ($case_statements as $case_statement) { + $case = $case_statement + ->getChildByIndex(0) + ->getSemanticString(); + $nodes_by_case[$case][] = $case_statement; + } + + foreach ($nodes_by_case as $case => $nodes) { + if (count($nodes) <= 1) { + continue; + } + + $node = array_pop($nodes_by_case[$case]); + $message = $this->raiseLintAtNode( + $node, + self::LINT_DUPLICATE_SWITCH_CASE, + pht( + 'Duplicate case in switch statement. PHP will ignore all '. + 'but the first case.')); + + $locations = array(); + foreach ($nodes_by_case[$case] as $node) { + $locations[] = $this->getOtherLocation($node->getOffset()); + } + $message->setOtherLocations($locations); + } + } + } + public function getSuperGlobalNames() { return array( '$GLOBALS', diff --git a/src/lint/linter/__tests__/xhpast/duplicate-switch-case.lint-test b/src/lint/linter/__tests__/xhpast/duplicate-switch-case.lint-test new file mode 100644 index 00000000..309e04d6 --- /dev/null +++ b/src/lint/linter/__tests__/xhpast/duplicate-switch-case.lint-test @@ -0,0 +1,28 @@ +