1
0
Fork 0
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:
Joshua Spence 2015-08-11 06:49:54 +10:00
parent 6c759ae343
commit 587f7440e3
6 changed files with 121 additions and 40 deletions

View file

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

View file

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

View file

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

View file

@ -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).
*

View file

@ -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;

View file

@ -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(