mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-24 15:52:40 +01:00
Detect and correct "final private" methods in lint
Summary: Ref T13588. Marking a method "final private" has never been meaningful, and is an error in PHP8. Add static analysis to detect (and correct) this issue. Test Plan: Added unit tests, will lint "phabricator/". Maniphest Tasks: T13588 Differential Revision: https://secure.phabricator.com/D21539
This commit is contained in:
parent
e2b6439a73
commit
c51a996fb0
3 changed files with 90 additions and 25 deletions
|
@ -22,68 +22,65 @@ final class ArcanistInvalidModifiersXHPASTLinterRule
|
||||||
$is_final = false;
|
$is_final = false;
|
||||||
$is_static = false;
|
$is_static = false;
|
||||||
$visibility = null;
|
$visibility = null;
|
||||||
|
$is_property = ($method->getTypeName() == 'n_CLASS_MEMBER_MODIFIER_LIST');
|
||||||
|
$is_method = !$is_property;
|
||||||
|
|
||||||
|
$final_modifier = null;
|
||||||
|
$visibility_modifier = null;
|
||||||
|
$abstract_modifier = null;
|
||||||
|
|
||||||
foreach ($modifiers as $modifier) {
|
foreach ($modifiers as $modifier) {
|
||||||
switch ($modifier->getConcreteString()) {
|
switch ($modifier->getConcreteString()) {
|
||||||
case 'abstract':
|
case 'abstract':
|
||||||
if ($method->getTypeName() == 'n_CLASS_MEMBER_MODIFIER_LIST') {
|
if ($is_property) {
|
||||||
$this->raiseLintAtNode(
|
$this->raiseLintAtNode(
|
||||||
$modifier,
|
$modifier,
|
||||||
pht(
|
pht(
|
||||||
'Properties cannot be declared `%s`.',
|
'Properties cannot be declared "abstract".'));
|
||||||
'abstract'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($is_abstract) {
|
if ($is_abstract) {
|
||||||
$this->raiseLintAtNode(
|
$this->raiseLintAtNode(
|
||||||
$modifier,
|
$modifier,
|
||||||
pht(
|
pht(
|
||||||
'Multiple `%s` modifiers are not allowed.',
|
'Multiple "abstract" modifiers are not allowed.'));
|
||||||
'abstract'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($is_final) {
|
|
||||||
$this->raiseLintAtNode(
|
|
||||||
$modifier,
|
|
||||||
pht(
|
|
||||||
'Cannot use the `%s` modifier on an `%s` class member',
|
|
||||||
'final',
|
|
||||||
'abstract'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$abstract_modifier = $modifier;
|
||||||
$is_abstract = true;
|
$is_abstract = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'final':
|
case 'final':
|
||||||
if ($is_abstract) {
|
if ($is_property) {
|
||||||
$this->raiseLintAtNode(
|
$this->raiseLintAtNode(
|
||||||
$modifier,
|
$modifier,
|
||||||
pht(
|
pht(
|
||||||
'Cannot use the `%s` modifier on an `%s` class member',
|
'Properties can not be declared "final".'));
|
||||||
'final',
|
|
||||||
'abstract'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($is_final) {
|
if ($is_final) {
|
||||||
$this->raiseLintAtNode(
|
$this->raiseLintAtNode(
|
||||||
$modifier,
|
$modifier,
|
||||||
pht(
|
pht(
|
||||||
'Multiple `%s` modifiers are not allowed.',
|
'Multiple "final" modifiers are not allowed.'));
|
||||||
'final'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$final_modifier = $modifier;
|
||||||
$is_final = true;
|
$is_final = true;
|
||||||
break;
|
break;
|
||||||
case 'public':
|
case 'public':
|
||||||
case 'protected':
|
case 'protected':
|
||||||
case 'private':
|
case 'private':
|
||||||
if ($visibility) {
|
if ($visibility !== null) {
|
||||||
$this->raiseLintAtNode(
|
$this->raiseLintAtNode(
|
||||||
$modifier,
|
$modifier,
|
||||||
pht('Multiple access type modifiers are not allowed.'));
|
pht('Multiple access type modifiers are not allowed.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$visibility_modifier = $modifier;
|
||||||
|
|
||||||
$visibility = $modifier->getConcreteString();
|
$visibility = $modifier->getConcreteString();
|
||||||
|
$visibility = phutil_utf8_strtolower($visibility);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'static':
|
case 'static':
|
||||||
|
@ -91,12 +88,47 @@ final class ArcanistInvalidModifiersXHPASTLinterRule
|
||||||
$this->raiseLintAtNode(
|
$this->raiseLintAtNode(
|
||||||
$modifier,
|
$modifier,
|
||||||
pht(
|
pht(
|
||||||
'Multiple `%s` modifiers are not allowed.',
|
'Multiple "static" modifiers are not allowed.'));
|
||||||
'static'));
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$is_private = ($visibility === 'private');
|
||||||
|
|
||||||
|
if ($is_final && $is_abstract) {
|
||||||
|
if ($is_method) {
|
||||||
|
$this->raiseLintAtNode(
|
||||||
|
$final_modifier,
|
||||||
|
pht('Methods may not be both "abstract" and "final".'));
|
||||||
|
} else {
|
||||||
|
// Properties can't be "abstract" and "final" either, but they can't
|
||||||
|
// ever be "abstract" at all, and we've already raise a message about
|
||||||
|
// that earlier.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_private && $is_final) {
|
||||||
|
if ($is_method) {
|
||||||
|
$final_tokens = $final_modifier->getTokens();
|
||||||
|
$space_tokens = last($final_tokens)->getWhitespaceTokensAfter();
|
||||||
|
|
||||||
|
$final_offset = head($final_tokens)->getOffset();
|
||||||
|
|
||||||
|
$final_string = array_merge($final_tokens, $space_tokens);
|
||||||
|
$final_string = mpull($final_string, 'getValue');
|
||||||
|
$final_string = implode('', $final_string);
|
||||||
|
|
||||||
|
$this->raiseLintAtOffset(
|
||||||
|
$final_offset,
|
||||||
|
pht('Methods may not be both "private" and "final".'),
|
||||||
|
$final_string,
|
||||||
|
'');
|
||||||
|
} else {
|
||||||
|
// Properties can't be "final" at all, and we already raised a
|
||||||
|
// message about this.
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,14 +5,36 @@ class SomeClass {
|
||||||
public public $b;
|
public public $b;
|
||||||
public protected $c;
|
public protected $c;
|
||||||
private abstract $d;
|
private abstract $d;
|
||||||
|
private final $e;
|
||||||
|
|
||||||
public function foo() {}
|
public function foo() {}
|
||||||
public protected function bar() {}
|
public protected function bar() {}
|
||||||
abstract final public function baz() {}
|
abstract final public function baz() {}
|
||||||
|
final private function wuff() {}
|
||||||
|
private final function fizz() {}
|
||||||
}
|
}
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
error:5:10:XHP72:Invalid Modifiers
|
error:5:10:XHP72:Invalid Modifiers
|
||||||
error:6:10:XHP72:Invalid Modifiers
|
error:6:10:XHP72:Invalid Modifiers
|
||||||
error:7:11:XHP72:Invalid Modifiers
|
error:7:11:XHP72:Invalid Modifiers
|
||||||
error:10:10:XHP72:Invalid Modifiers
|
error:8:11:XHP72:Invalid Modifiers
|
||||||
error:11:12:XHP72:Invalid Modifiers
|
error:11:10:XHP72:Invalid Modifiers
|
||||||
|
error:12:12:XHP72:Invalid Modifiers
|
||||||
|
error:13:3:XHP72:Invalid Modifiers
|
||||||
|
error:14:11:XHP72:Invalid Modifiers
|
||||||
|
~~~~~~~~~~
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class SomeClass {
|
||||||
|
public $a;
|
||||||
|
public public $b;
|
||||||
|
public protected $c;
|
||||||
|
private abstract $d;
|
||||||
|
private final $e;
|
||||||
|
|
||||||
|
public function foo() {}
|
||||||
|
public protected function bar() {}
|
||||||
|
abstract final public function baz() {}
|
||||||
|
private function wuff() {}
|
||||||
|
private function fizz() {}
|
||||||
|
}
|
||||||
|
|
|
@ -84,6 +84,17 @@ abstract class AASTToken extends Phobject {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getWhitespaceTokensAfter() {
|
||||||
|
$tokens = $this->tree->getRawTokenStream();
|
||||||
|
$result = array();
|
||||||
|
$ii = $this->id + 1;
|
||||||
|
while ($ii < count($tokens) && $tokens[$ii]->isAnyWhitespace()) {
|
||||||
|
$result[$ii] = $tokens[$ii];
|
||||||
|
++$ii;
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
final public function getLineNumber() {
|
final public function getLineNumber() {
|
||||||
return idx($this->tree->getOffsetToLineNumberMap(), $this->getOffset());
|
return idx($this->tree->getOffsetToLineNumberMap(), $this->getOffset());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue