1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-25 00:02: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:
epriestley 2021-02-03 13:46:39 -08:00
parent e2b6439a73
commit c51a996fb0
3 changed files with 90 additions and 25 deletions

View file

@ -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.
}
}
} }
} }

View file

@ -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() {}
}

View file

@ -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());
} }