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

Add a linter rule for abstract methods containing a body

Summary:
`abstract` methods cannot contain a body.

```
PHP Fatal error:  Abstract function X::Y() cannot contain body in /home/josh/workspace/github.com/phacility/arcanist/test.php on line 4
```

Test Plan: Added unit tests.

Reviewers: #blessed_reviewers, epriestley

Reviewed By: #blessed_reviewers, epriestley

Subscribers: Korvin

Differential Revision: https://secure.phabricator.com/D14560
This commit is contained in:
Joshua Spence 2015-11-24 08:19:35 +11:00
parent 37571d8839
commit 72d9013d29
8 changed files with 88 additions and 28 deletions

View file

@ -9,6 +9,8 @@
phutil_register_library_map(array( phutil_register_library_map(array(
'__library_version__' => 2, '__library_version__' => 2,
'class' => array( 'class' => array(
'ArcanistAbstractMethodBodyXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistAbstractMethodBodyXHPASTLinterRule.php',
'ArcanistAbstractMethodBodyXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistAbstractMethodBodyXHPASTLinterRuleTestCase.php',
'ArcanistAbstractPrivateMethodXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistAbstractPrivateMethodXHPASTLinterRule.php', 'ArcanistAbstractPrivateMethodXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistAbstractPrivateMethodXHPASTLinterRule.php',
'ArcanistAbstractPrivateMethodXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistAbstractPrivateMethodXHPASTLinterRuleTestCase.php', 'ArcanistAbstractPrivateMethodXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistAbstractPrivateMethodXHPASTLinterRuleTestCase.php',
'ArcanistAliasFunctionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistAliasFunctionXHPASTLinterRule.php', 'ArcanistAliasFunctionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistAliasFunctionXHPASTLinterRule.php',
@ -395,6 +397,8 @@ phutil_register_library_map(array(
), ),
'function' => array(), 'function' => array(),
'xmap' => array( 'xmap' => array(
'ArcanistAbstractMethodBodyXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistAbstractMethodBodyXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistAbstractPrivateMethodXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistAbstractPrivateMethodXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistAbstractPrivateMethodXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistAbstractPrivateMethodXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistAliasFunctionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistAliasFunctionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',

View file

@ -225,6 +225,34 @@ abstract class ArcanistXHPASTLinterRule extends Phobject {
return AASTNodeList::newFromTreeAndNodes($root->getTree(), $nodes); return AASTNodeList::newFromTreeAndNodes($root->getTree(), $nodes);
} }
/**
* Get class/method modifiers.
*
* @param XHPASTNode A node of type `n_CLASS_DECLARATION` or
* `n_METHOD_DECLARATION`.
* @return map<string, bool> Class/method modifiers.
*/
final protected 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;
}
/** /**
* Get PHP superglobals. * Get PHP superglobals.
* *

View file

@ -0,0 +1,30 @@
<?php
final class ArcanistAbstractMethodBodyXHPASTLinterRule
extends ArcanistXHPASTLinterRule {
const ID = 108;
public function getLintName() {
return pht('`%s` Method Cannot Contain Body', 'abstract');
}
public function process(XHPASTNode $root) {
$methods = $root->selectDescendantsOfType('n_METHOD_DECLARATION');
foreach ($methods as $method) {
$modifiers = $this->getModifiers($method);
$body = $method->getChildByIndex(5);
if (idx($modifiers, 'abstract') && $body->getTypeName() != 'n_EMPTY') {
$this->raiseLintAtNode(
$body,
pht(
'`%s` methods cannot contain a body. This construct will '.
'cause a fatal error.',
'abstract'));
}
}
}
}

View file

@ -43,32 +43,4 @@ final class ArcanistClassMustBeDeclaredAbstractXHPASTLinterRule
} }
} }
/**
* 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;
}
} }

View file

@ -0,0 +1,10 @@
<?php
final class ArcanistAbstractMethodBodyXHPASTLinterRuleTestCase
extends ArcanistXHPASTLinterRuleTestCase {
public function testLinter() {
$this->executeTestsInDirectory(dirname(__FILE__).'/abstract-method-body/');
}
}

View file

@ -0,0 +1,6 @@
<?php
abstract class SomeClass {
abstract public function someMethod() {}
}
~~~~~~~~~~
error:3:41

View file

@ -0,0 +1,5 @@
<?php
abstract class SomeClass {
abstract public function someMethod();
}
~~~~~~~~~~

View file

@ -0,0 +1,5 @@
<?php
abstract class SomeClass {
public function someMethod() {}
}
~~~~~~~~~~