mirror of
https://we.phorge.it/source/arcanist.git
synced 2025-01-10 23:01:04 +01:00
Add a linter rule to determine whether a class should be marked as abstract
Summary: A class containing `abstract` methods must itself be marked as `abstract`. ``` PHP Fatal error: Class X contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (X::Y) in /home/josh/workspace/github.com/phacility/arcanist/test.php on line 5 ``` Test Plan: Added unit tests. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D14559
This commit is contained in:
parent
20e99acf0a
commit
37571d8839
6 changed files with 105 additions and 0 deletions
|
@ -57,6 +57,8 @@ phutil_register_library_map(array(
|
|||
'ArcanistClassExtendsObjectXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistClassExtendsObjectXHPASTLinterRule.php',
|
||||
'ArcanistClassExtendsObjectXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistClassExtendsObjectXHPASTLinterRuleTestCase.php',
|
||||
'ArcanistClassFilenameMismatchXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistClassFilenameMismatchXHPASTLinterRule.php',
|
||||
'ArcanistClassMustBeDeclaredAbstractXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistClassMustBeDeclaredAbstractXHPASTLinterRule.php',
|
||||
'ArcanistClassMustBeDeclaredAbstractXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistClassMustBeDeclaredAbstractXHPASTLinterRuleTestCase.php',
|
||||
'ArcanistClassNameLiteralXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistClassNameLiteralXHPASTLinterRule.php',
|
||||
'ArcanistClassNameLiteralXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistClassNameLiteralXHPASTLinterRuleTestCase.php',
|
||||
'ArcanistCloseRevisionWorkflow' => 'workflow/ArcanistCloseRevisionWorkflow.php',
|
||||
|
@ -441,6 +443,8 @@ phutil_register_library_map(array(
|
|||
'ArcanistClassExtendsObjectXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistClassExtendsObjectXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||
'ArcanistClassFilenameMismatchXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistClassMustBeDeclaredAbstractXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistClassMustBeDeclaredAbstractXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||
'ArcanistClassNameLiteralXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistClassNameLiteralXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||
'ArcanistCloseRevisionWorkflow' => 'ArcanistWorkflow',
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistClassMustBeDeclaredAbstractXHPASTLinterRule
|
||||
extends ArcanistXHPASTLinterRule {
|
||||
|
||||
const ID = 113;
|
||||
|
||||
public function getLintName() {
|
||||
return pht(
|
||||
'`%s` Containing `%s` Methods Must Be Declared `%s`',
|
||||
'class',
|
||||
'abstract',
|
||||
'abstract');
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
|
||||
|
||||
foreach ($classes as $class) {
|
||||
$class_modifiers = $this->getModifiers($class);
|
||||
|
||||
$abstract_methods = array();
|
||||
$methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION');
|
||||
|
||||
foreach ($methods as $method) {
|
||||
$method_modifiers = $this->getModifiers($method);
|
||||
|
||||
if (idx($method_modifiers, 'abstract')) {
|
||||
$abstract_methods[] = $method;
|
||||
}
|
||||
}
|
||||
|
||||
if (!idx($class_modifiers, 'abstract') && $abstract_methods) {
|
||||
$this->raiseLintAtNode(
|
||||
$class,
|
||||
pht(
|
||||
'Class contains %s %s method(s) and must therefore '.
|
||||
'be declared `%s`.',
|
||||
phutil_count($abstract_methods),
|
||||
'abstract',
|
||||
'abstract'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get class/method modifiers.
|
||||
*
|
||||
* @param XHPASTNode A node of type `n_CLASS_DECLARATION` or
|
||||
* `n_METHOD_DECLARATION`.
|
||||
* @return map<string, bool> Class/method modifiers.
|
||||
*/
|
||||
private function getModifiers(XHPASTNode $node) {
|
||||
$modifier_list = $node->getChildByIndex(0);
|
||||
|
||||
switch ($modifier_list->getTypeName()) {
|
||||
case 'n_CLASS_ATTRIBUTES':
|
||||
case 'n_METHOD_MODIFIER_LIST':
|
||||
break;
|
||||
|
||||
default:
|
||||
return array();
|
||||
}
|
||||
|
||||
$modifiers = array();
|
||||
|
||||
foreach ($modifier_list->selectDescendantsOfType('n_STRING') as $modifier) {
|
||||
$modifiers[strtolower($modifier->getConcreteString())] = true;
|
||||
}
|
||||
|
||||
return $modifiers;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistClassMustBeDeclaredAbstractXHPASTLinterRuleTestCase
|
||||
extends ArcanistXHPASTLinterRuleTestCase {
|
||||
|
||||
public function testLinter() {
|
||||
$this->executeTestsInDirectory(
|
||||
dirname(__FILE__).'/class-must-be-declared-abstract/');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
abstract class SomeClass {
|
||||
abstract public function someMethod();
|
||||
}
|
||||
~~~~~~~~~~
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
class SomeClass {
|
||||
public function someMethod();
|
||||
}
|
||||
~~~~~~~~~~
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
class SomeClass {
|
||||
abstract public function someMethod();
|
||||
}
|
||||
~~~~~~~~~~
|
||||
error:2:1
|
Loading…
Reference in a new issue