Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F22947935
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
13 KB
Subscribers
None
View Options
diff --git a/src/lint/linter/xhpast/rules/ArcanistPHPCompatibilityXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistPHPCompatibilityXHPASTLinterRule.php
index 5ee6f3fe..e2f627a3 100644
--- a/src/lint/linter/xhpast/rules/ArcanistPHPCompatibilityXHPASTLinterRule.php
+++ b/src/lint/linter/xhpast/rules/ArcanistPHPCompatibilityXHPASTLinterRule.php
@@ -1,450 +1,450 @@
<?php
final class ArcanistPHPCompatibilityXHPASTLinterRule
extends ArcanistXHPASTLinterRule {
const ID = 45;
public function getLintName() {
return pht('PHP Compatibility');
}
public function process(XHPASTNode $root) {
static $compat_info;
if (!$this->version) {
return;
}
if ($compat_info === null) {
$target = phutil_get_library_root('phutil').
'/../resources/php_compat_info.json';
$compat_info = phutil_json_decode(Filesystem::readFile($target));
}
// Create a whitelist for symbols which are being used conditionally.
$whitelist = array(
'class' => array(),
'function' => array(),
);
$conditionals = $root->selectDescendantsOfType('n_IF');
foreach ($conditionals as $conditional) {
$condition = $conditional->getChildOfType(0, 'n_CONTROL_CONDITION');
$function = $condition->getChildByIndex(0);
if ($function->getTypeName() != 'n_FUNCTION_CALL') {
continue;
}
$function_token = $function
->getChildByIndex(0);
if ($function_token->getTypeName() != 'n_SYMBOL_NAME') {
// This may be `Class::method(...)` or `$var(...)`.
continue;
}
$function_name = $function_token->getConcreteString();
switch ($function_name) {
case 'class_exists':
case 'function_exists':
case 'interface_exists':
$type = null;
switch ($function_name) {
case 'class_exists':
$type = 'class';
break;
case 'function_exists':
$type = 'function';
break;
case 'interface_exists':
$type = 'interface';
break;
}
$params = $function->getChildOfType(1, 'n_CALL_PARAMETER_LIST');
$symbol = $params->getChildByIndex(0);
if (!$symbol->isStaticScalar()) {
continue;
}
$symbol_name = $symbol->evalStatic();
if (!idx($whitelist[$type], $symbol_name)) {
$whitelist[$type][$symbol_name] = array();
}
$span = $conditional
->getChildByIndex(1)
->getTokens();
$whitelist[$type][$symbol_name][] = range(
head_key($span),
last_key($span));
break;
}
}
$calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
foreach ($calls as $call) {
$node = $call->getChildByIndex(0);
$name = $node->getConcreteString();
$version = idx($compat_info['functions'], $name, array());
$min = idx($version, 'php.min');
$max = idx($version, 'php.max');
// Check if whitelisted.
$whitelisted = false;
foreach (idx($whitelist['function'], $name, array()) as $range) {
if (array_intersect($range, array_keys($node->getTokens()))) {
$whitelisted = true;
break;
}
}
if ($whitelisted) {
continue;
}
if ($min && version_compare($min, $this->version, '>')) {
$this->raiseLintAtNode(
$node,
pht(
'This codebase targets PHP %s, but `%s()` was not '.
'introduced until PHP %s.',
$this->version,
$name,
$min));
} else if ($max && version_compare($max, $this->version, '<')) {
$this->raiseLintAtNode(
$node,
pht(
'This codebase targets PHP %s, but `%s()` was '.
'removed in PHP %s.',
$this->version,
$name,
$max));
} else if (array_key_exists($name, $compat_info['params'])) {
$params = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST');
foreach (array_values($params->getChildren()) as $i => $param) {
$version = idx($compat_info['params'][$name], $i);
if ($version && version_compare($version, $this->version, '>')) {
$this->raiseLintAtNode(
$param,
pht(
'This codebase targets PHP %s, but parameter %d '.
'of `%s()` was not introduced until PHP %s.',
$this->version,
$i + 1,
$name,
$version));
}
}
}
if ($this->windowsVersion) {
$windows = idx($compat_info['functions_windows'], $name);
if ($windows === false) {
$this->raiseLintAtNode(
$node,
pht(
'This codebase targets PHP %s on Windows, '.
'but `%s()` is not available there.',
$this->windowsVersion,
$name));
} else if (version_compare($windows, $this->windowsVersion, '>')) {
$this->raiseLintAtNode(
$node,
pht(
'This codebase targets PHP %s on Windows, '.
'but `%s()` is not available there until PHP %s.',
$this->windowsVersion,
$name,
$windows));
}
}
}
$classes = $root->selectDescendantsOfType('n_CLASS_NAME');
foreach ($classes as $node) {
$name = $node->getConcreteString();
$version = idx($compat_info['interfaces'], $name, array());
$version = idx($compat_info['classes'], $name, $version);
$min = idx($version, 'php.min');
$max = idx($version, 'php.max');
// Check if whitelisted.
$whitelisted = false;
foreach (idx($whitelist['class'], $name, array()) as $range) {
if (array_intersect($range, array_keys($node->getTokens()))) {
$whitelisted = true;
break;
}
}
if ($whitelisted) {
continue;
}
if ($min && version_compare($min, $this->version, '>')) {
$this->raiseLintAtNode(
$node,
pht(
'This codebase targets PHP %s, but `%s` was not '.
'introduced until PHP %s.',
$this->version,
$name,
$min));
} else if ($max && version_compare($max, $this->version, '<')) {
$this->raiseLintAtNode(
$node,
pht(
'This codebase targets PHP %s, but `%s` was '.
'removed in PHP %s.',
$this->version,
$name,
$max));
}
}
// TODO: Technically, this will include function names. This is unlikely to
// cause any issues (unless, of course, there existed a function that had
// the same name as some constant).
$constants = $root->selectDescendantsOfTypes(array(
'n_SYMBOL_NAME',
'n_MAGIC_SCALAR',
));
foreach ($constants as $node) {
$name = $node->getConcreteString();
$version = idx($compat_info['constants'], $name, array());
$min = idx($version, 'php.min');
$max = idx($version, 'php.max');
if ($min && version_compare($min, $this->version, '>')) {
$this->raiseLintAtNode(
$node,
pht(
'This codebase targets PHP %s, but `%s` was not '.
'introduced until PHP %s.',
$this->version,
$name,
$min));
} else if ($max && version_compare($max, $this->version, '<')) {
$this->raiseLintAtNode(
$node,
pht(
'This codebase targets PHP %s, but `%s` was '.
'removed in PHP %s.',
$this->version,
$name,
$max));
}
}
if (version_compare($this->version, '5.3.0') < 0) {
$this->lintPHP53Features($root);
} else {
$this->lintPHP53Incompatibilities($root);
}
if (version_compare($this->version, '5.4.0') < 0) {
$this->lintPHP54Features($root);
} else {
$this->lintPHP54Incompatibilities($root);
}
}
private function lintPHP53Features(XHPASTNode $root) {
$functions = $root->selectTokensOfType('T_FUNCTION');
foreach ($functions as $function) {
$next = $function->getNextToken();
while ($next) {
if ($next->isSemantic()) {
break;
}
$next = $next->getNextToken();
}
if ($next) {
if ($next->getTypeName() === '(') {
$this->raiseLintAtToken(
$function,
pht(
'This codebase targets PHP %s, but anonymous '.
'functions were not introduced until PHP 5.3.',
$this->version));
}
}
}
$namespaces = $root->selectTokensOfType('T_NAMESPACE');
foreach ($namespaces as $namespace) {
$this->raiseLintAtToken(
$namespace,
pht(
'This codebase targets PHP %s, but namespaces were not '.
'introduced until PHP 5.3.',
$this->version));
}
// NOTE: This is only "use x;", in anonymous functions the node type is
// n_LEXICAL_VARIABLE_LIST even though both tokens are T_USE.
// TODO: We parse n_USE in a slightly crazy way right now; that would be
// a better selector once it's fixed.
$uses = $root->selectDescendantsOfType('n_USE_LIST');
foreach ($uses as $use) {
$this->raiseLintAtNode(
$use,
pht(
'This codebase targets PHP %s, but namespaces were not '.
'introduced until PHP 5.3.',
$this->version));
}
$statics = $root->selectDescendantsOfType('n_CLASS_STATIC_ACCESS');
foreach ($statics as $static) {
$name = $static->getChildByIndex(0);
if ($name->getTypeName() != 'n_CLASS_NAME') {
continue;
}
if ($name->getConcreteString() === 'static') {
$this->raiseLintAtNode(
$name,
pht(
'This codebase targets PHP %s, but `%s` was not '.
'introduced until PHP 5.3.',
$this->version,
'static::'));
}
}
$ternaries = $root->selectDescendantsOfType('n_TERNARY_EXPRESSION');
foreach ($ternaries as $ternary) {
- $yes = $ternary->getChildByIndex(1);
+ $yes = $ternary->getChildByIndex(2);
if ($yes->getTypeName() === 'n_EMPTY') {
$this->raiseLintAtNode(
$ternary,
pht(
'This codebase targets PHP %s, but short ternary was '.
'not introduced until PHP 5.3.',
$this->version));
}
}
$heredocs = $root->selectDescendantsOfType('n_HEREDOC');
foreach ($heredocs as $heredoc) {
if (preg_match('/^<<<[\'"]/', $heredoc->getConcreteString())) {
$this->raiseLintAtNode(
$heredoc,
pht(
'This codebase targets PHP %s, but nowdoc was not '.
'introduced until PHP 5.3.',
$this->version));
}
}
}
private function lintPHP53Incompatibilities(XHPASTNode $root) {}
private function lintPHP54Features(XHPASTNode $root) {
$indexes = $root->selectDescendantsOfType('n_INDEX_ACCESS');
foreach ($indexes as $index) {
switch ($index->getChildByIndex(0)->getTypeName()) {
case 'n_FUNCTION_CALL':
case 'n_METHOD_CALL':
$this->raiseLintAtNode(
$index->getChildByIndex(1),
pht(
'The `%s` syntax was not introduced until PHP 5.4, but this '.
'codebase targets an earlier version of PHP. You can rewrite '.
'this expression using `%s`.',
'f()[...]',
'idx()'));
break;
}
}
$closures = $this->getAnonymousClosures($root);
foreach ($closures as $closure) {
$static_accesses = $closure
->selectDescendantsOfType('n_CLASS_STATIC_ACCESS');
foreach ($static_accesses as $static_access) {
$class = $static_access->getChildByIndex(0);
if ($class->getTypeName() != 'n_CLASS_NAME') {
continue;
}
if (strtolower($class->getConcreteString()) != 'self') {
continue;
}
$this->raiseLintAtNode(
$class,
pht(
'The use of `%s` in an anonymous closure is not '.
'available before PHP 5.4.',
'self'));
}
$property_accesses = $closure
->selectDescendantsOfType('n_OBJECT_PROPERTY_ACCESS');
foreach ($property_accesses as $property_access) {
$variable = $property_access->getChildByIndex(0);
if ($variable->getTypeName() != 'n_VARIABLE') {
continue;
}
if ($variable->getConcreteString() != '$this') {
continue;
}
$this->raiseLintAtNode(
$variable,
pht(
'The use of `%s` in an anonymous closure is not '.
'available before PHP 5.4.',
'$this'));
}
}
}
private function lintPHP54Incompatibilities(XHPASTNode $root) {
$breaks = $root->selectDescendantsOfTypes(array('n_BREAK', 'n_CONTINUE'));
foreach ($breaks as $break) {
$arg = $break->getChildByIndex(0);
switch ($arg->getTypeName()) {
case 'n_EMPTY':
break;
case 'n_NUMERIC_SCALAR':
if ($arg->getConcreteString() != '0') {
break;
}
default:
$this->raiseLintAtNode(
$break->getChildByIndex(0),
pht(
'The `%s` and `%s` statements no longer accept '.
'variable arguments.',
'break',
'continue'));
break;
}
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, May 10, 8:42 AM (1 d, 16 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
32/9f/67cc6bf3a86061d551e918930036
Attached To
rARC Arcanist
Event Timeline
Log In to Comment