mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-12-23 14:00:55 +01:00
Fix self member reference rule for PHP 5.3
Summary: Fixes T8674. Currently, `ArcanistSelfMemberReferenceXHPASTLinterRule` warns if a fully-qualified class name is used in place of `self`. This is fine in most cases, but in some specific scenarios fails for PHP 5.3 because `self` (and also `$this`) cannot be used in an anonymous closure (see [[http://php.net/manual/en/functions.anonymous.php | anonymous functions]] and [[https://wiki.php.net/rfc/closures/removal-of-this | removal of `$this` in closures]]). In order to do this, I also had to modify the manner in which configuration was passed to individual linter rule. Previously, it was only possible or an individual linter rule to be set with a configuration value. Once the linter rule had set this value, there was no means by which to allow this value to be shared amongst linter rules. Depends on D13819. Test Plan: Added unit tests. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin Maniphest Tasks: T8674 Differential Revision: https://secure.phabricator.com/D13820
This commit is contained in:
parent
6c759ae343
commit
587f7440e3
6 changed files with 121 additions and 40 deletions
|
@ -67,14 +67,21 @@ final class ArcanistXHPASTLinter extends ArcanistBaseXHPASTLinter {
|
|||
}
|
||||
|
||||
public function setLinterConfigurationValue($key, $value) {
|
||||
$matched = false;
|
||||
|
||||
foreach ($this->rules as $rule) {
|
||||
foreach ($rule->getLinterConfigurationOptions() as $k => $spec) {
|
||||
if ($k == $key) {
|
||||
return $rule->setLinterConfigurationValue($key, $value);
|
||||
$matched = true;
|
||||
$rule->setLinterConfigurationValue($key, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($matched) {
|
||||
return;
|
||||
}
|
||||
|
||||
return parent::setLinterConfigurationValue($key, $value);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
final class SomeClass extends Phobject {
|
||||
public static function someMethod() {
|
||||
$closure = function () {
|
||||
SomeClass::someOtherMethod();
|
||||
};
|
||||
$closure();
|
||||
}
|
||||
}
|
||||
~~~~~~~~~~
|
||||
error:3:13
|
||||
~~~~~~~~~~
|
||||
~~~~~~~~~~
|
||||
{
|
||||
"config": {
|
||||
"xhpast.php-version": "5.3.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
final class SomeClass extends Phobject {
|
||||
public static function someMethod() {
|
||||
$closure = function () {
|
||||
SomeClass::someOtherMethod();
|
||||
};
|
||||
$closure();
|
||||
}
|
||||
}
|
||||
~~~~~~~~~~
|
||||
error:3:13
|
||||
advice:6:7
|
||||
~~~~~~~~~~
|
||||
<?php
|
||||
|
||||
final class SomeClass extends Phobject {
|
||||
public static function someMethod() {
|
||||
$closure = function () {
|
||||
self::someOtherMethod();
|
||||
};
|
||||
$closure();
|
||||
}
|
||||
}
|
||||
~~~~~~~~~~
|
||||
{
|
||||
"config": {
|
||||
"xhpast.php-version": "5.4.0"
|
||||
}
|
||||
}
|
|
@ -5,6 +5,9 @@ abstract class ArcanistXHPASTLinterRule extends Phobject {
|
|||
private $linter = null;
|
||||
private $lintID = null;
|
||||
|
||||
protected $version;
|
||||
protected $windowsVersion;
|
||||
|
||||
final public static function loadAllRules() {
|
||||
return id(new PhutilClassMapQuery())
|
||||
->setAncestorClass(__CLASS__)
|
||||
|
@ -47,10 +50,29 @@ abstract class ArcanistXHPASTLinterRule extends Phobject {
|
|||
}
|
||||
|
||||
public function getLinterConfigurationOptions() {
|
||||
return array();
|
||||
return array(
|
||||
'xhpast.php-version' => array(
|
||||
'type' => 'optional string',
|
||||
'help' => pht('PHP version to target.'),
|
||||
),
|
||||
'xhpast.php-version.windows' => array(
|
||||
'type' => 'optional string',
|
||||
'help' => pht('PHP version to target on Windows.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function setLinterConfigurationValue($key, $value) {}
|
||||
public function setLinterConfigurationValue($key, $value) {
|
||||
switch ($key) {
|
||||
case 'xhpast.php-version':
|
||||
$this->version = $value;
|
||||
return;
|
||||
|
||||
case 'xhpast.php-version.windows':
|
||||
$this->windowsVersion = $value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
abstract public function process(XHPASTNode $root);
|
||||
|
||||
|
@ -138,6 +160,28 @@ abstract class ArcanistXHPASTLinterRule extends Phobject {
|
|||
|
||||
/* -( Utility )------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Retrieve all anonymous closure(s).
|
||||
*
|
||||
* Returns all descendant nodes which represent an anonymous function
|
||||
* declaration.
|
||||
*
|
||||
* @param XHPASTNode Root node.
|
||||
* @return AASTNodeList
|
||||
*/
|
||||
protected function getAnonymousClosures(XHPASTNode $root) {
|
||||
$func_decls = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION');
|
||||
$nodes = array();
|
||||
|
||||
foreach ($func_decls as $func_decl) {
|
||||
if ($func_decl->getChildByIndex(2)->getTypeName() == 'n_EMPTY') {
|
||||
$nodes[] = $func_decl;
|
||||
}
|
||||
}
|
||||
|
||||
return AASTNodeList::newFromTreeAndNodes($root->getTree(), $nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all calls to some specified function(s).
|
||||
*
|
||||
|
|
|
@ -5,41 +5,10 @@ final class ArcanistPHPCompatibilityXHPASTLinterRule
|
|||
|
||||
const ID = 45;
|
||||
|
||||
private $version;
|
||||
private $windowsVersion;
|
||||
|
||||
public function getLintName() {
|
||||
return pht('PHP Compatibility');
|
||||
}
|
||||
|
||||
public function getLinterConfigurationOptions() {
|
||||
return parent::getLinterConfigurationOptions() + array(
|
||||
'xhpast.php-version' => array(
|
||||
'type' => 'optional string',
|
||||
'help' => pht('PHP version to target.'),
|
||||
),
|
||||
'xhpast.php-version.windows' => array(
|
||||
'type' => 'optional string',
|
||||
'help' => pht('PHP version to target on Windows.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function setLinterConfigurationValue($key, $value) {
|
||||
switch ($key) {
|
||||
case 'xhpast.php-version':
|
||||
$this->version = $value;
|
||||
return;
|
||||
|
||||
case 'xhpast.php-version.windows':
|
||||
$this->windowsVersion = $value;
|
||||
return;
|
||||
|
||||
default:
|
||||
return parent::setLinterConfigurationValue($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function process(XHPASTNode $root) {
|
||||
static $compat_info;
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ final class ArcanistSelfMemberReferenceXHPASTLinterRule
|
|||
|
||||
$class_static_accesses = $class_declaration
|
||||
->selectDescendantsOfType('n_CLASS_STATIC_ACCESS');
|
||||
$closures = $this->getAnonymousClosures($class_declaration);
|
||||
|
||||
foreach ($class_static_accesses as $class_static_access) {
|
||||
$double_colons = $class_static_access
|
||||
|
@ -35,12 +36,23 @@ final class ArcanistSelfMemberReferenceXHPASTLinterRule
|
|||
$class_ref_name = $class_ref->getConcreteString();
|
||||
|
||||
if (strtolower($class_name) == strtolower($class_ref_name)) {
|
||||
$this->raiseLintAtNode(
|
||||
$class_ref,
|
||||
pht(
|
||||
'Use `%s` for local static member references.',
|
||||
'self::'),
|
||||
'self');
|
||||
$in_closure = false;
|
||||
|
||||
foreach ($closures as $closure) {
|
||||
if ($class_ref->isDescendantOf($closure)) {
|
||||
$in_closure = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (version_compare($this->version, '5.4.0', '>=') || !$in_closure) {
|
||||
$this->raiseLintAtNode(
|
||||
$class_ref,
|
||||
pht(
|
||||
'Use `%s` for local static member references.',
|
||||
'self::'),
|
||||
'self');
|
||||
}
|
||||
}
|
||||
|
||||
static $self_refs = array(
|
||||
|
|
Loading…
Reference in a new issue