Page MenuHomePhabricator

D14559.id.diff
No OneTemporary

D14559.id.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -57,6 +57,8 @@
'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 @@
'ArcanistClassExtendsObjectXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistClassExtendsObjectXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistClassFilenameMismatchXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
+ 'ArcanistClassMustBeDeclaredAbstractXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
+ 'ArcanistClassMustBeDeclaredAbstractXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistClassNameLiteralXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistClassNameLiteralXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistCloseRevisionWorkflow' => 'ArcanistWorkflow',
diff --git a/src/lint/linter/xhpast/rules/ArcanistClassMustBeDeclaredAbstractXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistClassMustBeDeclaredAbstractXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistClassMustBeDeclaredAbstractXHPASTLinterRule.php
@@ -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;
+ }
+
+}
diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistClassMustBeDeclaredAbstractXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistClassMustBeDeclaredAbstractXHPASTLinterRuleTestCase.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistClassMustBeDeclaredAbstractXHPASTLinterRuleTestCase.php
@@ -0,0 +1,11 @@
+<?php
+
+final class ArcanistClassMustBeDeclaredAbstractXHPASTLinterRuleTestCase
+ extends ArcanistXHPASTLinterRuleTestCase {
+
+ public function testLinter() {
+ $this->executeTestsInDirectory(
+ dirname(__FILE__).'/class-must-be-declared-abstract/');
+ }
+
+}
diff --git a/src/lint/linter/xhpast/rules/__tests__/class-must-be-declared-abstract/is-abstract.lint-test b/src/lint/linter/xhpast/rules/__tests__/class-must-be-declared-abstract/is-abstract.lint-test
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/__tests__/class-must-be-declared-abstract/is-abstract.lint-test
@@ -0,0 +1,5 @@
+<?php
+abstract class SomeClass {
+ abstract public function someMethod();
+}
+~~~~~~~~~~
diff --git a/src/lint/linter/xhpast/rules/__tests__/class-must-be-declared-abstract/is-not-abstract.lint-test b/src/lint/linter/xhpast/rules/__tests__/class-must-be-declared-abstract/is-not-abstract.lint-test
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/__tests__/class-must-be-declared-abstract/is-not-abstract.lint-test
@@ -0,0 +1,5 @@
+<?php
+class SomeClass {
+ public function someMethod();
+}
+~~~~~~~~~~
diff --git a/src/lint/linter/xhpast/rules/__tests__/class-must-be-declared-abstract/should-be-abstract.lint-test b/src/lint/linter/xhpast/rules/__tests__/class-must-be-declared-abstract/should-be-abstract.lint-test
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/__tests__/class-must-be-declared-abstract/should-be-abstract.lint-test
@@ -0,0 +1,6 @@
+<?php
+class SomeClass {
+ abstract public function someMethod();
+}
+~~~~~~~~~~
+error:2:1

File Metadata

Mime Type
text/plain
Expires
Sun, Oct 20, 10:15 AM (3 w, 2 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6735988
Default Alt Text
D14559.id.diff (6 KB)

Event Timeline