Changeset View
Changeset View
Standalone View
Standalone View
src/lint/linter/ArcanistXHPASTLinter.php
Show All 27 Lines | final class ArcanistXHPASTLinter extends ArcanistBaseXHPASTLinter { | ||||
const LINT_DUPLICATE_KEYS_IN_ARRAY = 22; | const LINT_DUPLICATE_KEYS_IN_ARRAY = 22; | ||||
const LINT_REUSED_ITERATORS = 23; | const LINT_REUSED_ITERATORS = 23; | ||||
const LINT_BRACE_FORMATTING = 24; | const LINT_BRACE_FORMATTING = 24; | ||||
const LINT_PARENTHESES_SPACING = 25; | const LINT_PARENTHESES_SPACING = 25; | ||||
const LINT_CONTROL_STATEMENT_SPACING = 26; | const LINT_CONTROL_STATEMENT_SPACING = 26; | ||||
const LINT_BINARY_EXPRESSION_SPACING = 27; | const LINT_BINARY_EXPRESSION_SPACING = 27; | ||||
const LINT_ARRAY_INDEX_SPACING = 28; | const LINT_ARRAY_INDEX_SPACING = 28; | ||||
const LINT_IMPLICIT_FALLTHROUGH = 30; | const LINT_IMPLICIT_FALLTHROUGH = 30; | ||||
const LINT_PHP_53_FEATURES = 31; | const LINT_PHP_53_FEATURES = 31; // Deprecated | ||||
const LINT_REUSED_AS_ITERATOR = 32; | const LINT_REUSED_AS_ITERATOR = 32; | ||||
const LINT_COMMENT_SPACING = 34; | const LINT_COMMENT_SPACING = 34; | ||||
const LINT_PHP_54_FEATURES = 35; | const LINT_PHP_54_FEATURES = 35; // Deprecated | ||||
const LINT_SLOWNESS = 36; | const LINT_SLOWNESS = 36; | ||||
const LINT_CLOSING_CALL_PAREN = 37; | const LINT_CLOSING_CALL_PAREN = 37; | ||||
const LINT_CLOSING_DECL_PAREN = 38; | const LINT_CLOSING_DECL_PAREN = 38; | ||||
const LINT_REUSED_ITERATOR_REFERENCE = 39; | const LINT_REUSED_ITERATOR_REFERENCE = 39; | ||||
const LINT_KEYWORD_CASING = 40; | const LINT_KEYWORD_CASING = 40; | ||||
const LINT_DOUBLE_QUOTE = 41; | const LINT_DOUBLE_QUOTE = 41; | ||||
const LINT_ELSEIF_USAGE = 42; | const LINT_ELSEIF_USAGE = 42; | ||||
const LINT_SEMICOLON_SPACING = 43; | const LINT_SEMICOLON_SPACING = 43; | ||||
const LINT_CONCATENATION_OPERATOR = 44; | const LINT_CONCATENATION_OPERATOR = 44; | ||||
const LINT_PHP_COMPATIBILITY = 45; | |||||
private $naminghook; | private $naminghook; | ||||
private $switchhook; | private $switchhook; | ||||
private $version; | |||||
private $windowsVersion; | |||||
public function getInfoName() { | public function getInfoName() { | ||||
return 'XHPAST Lint'; | return 'XHPAST Lint'; | ||||
} | } | ||||
public function getInfoDescription() { | public function getInfoDescription() { | ||||
return pht( | return pht( | ||||
'Use XHPAST to enforce Phabricator coding conventions on PHP source '. | 'Use XHPAST to enforce Phabricator coding conventions on PHP source '. | ||||
Show All 40 Lines | return array( | ||||
self::LINT_CLOSING_CALL_PAREN => 'Call Formatting', | self::LINT_CLOSING_CALL_PAREN => 'Call Formatting', | ||||
self::LINT_CLOSING_DECL_PAREN => 'Declaration Formatting', | self::LINT_CLOSING_DECL_PAREN => 'Declaration Formatting', | ||||
self::LINT_REUSED_ITERATOR_REFERENCE => 'Reuse of Iterator References', | self::LINT_REUSED_ITERATOR_REFERENCE => 'Reuse of Iterator References', | ||||
self::LINT_KEYWORD_CASING => 'Keyword Conventions', | self::LINT_KEYWORD_CASING => 'Keyword Conventions', | ||||
self::LINT_DOUBLE_QUOTE => 'Unnecessary Double Quotes', | self::LINT_DOUBLE_QUOTE => 'Unnecessary Double Quotes', | ||||
self::LINT_ELSEIF_USAGE => 'ElseIf Usage', | self::LINT_ELSEIF_USAGE => 'ElseIf Usage', | ||||
self::LINT_SEMICOLON_SPACING => 'Semicolon Spacing', | self::LINT_SEMICOLON_SPACING => 'Semicolon Spacing', | ||||
self::LINT_CONCATENATION_OPERATOR => 'Concatenation Spacing', | self::LINT_CONCATENATION_OPERATOR => 'Concatenation Spacing', | ||||
self::LINT_PHP_COMPATIBILITY => 'PHP Compatibility', | |||||
); | ); | ||||
} | } | ||||
public function getLinterName() { | public function getLinterName() { | ||||
return 'XHP'; | return 'XHP'; | ||||
} | } | ||||
public function getLinterConfigurationName() { | public function getLinterConfigurationName() { | ||||
▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Lines | return parent::getLinterConfigurationOptions() + array( | ||||
'enforces more granular naming convention rules for symbols.'), | 'enforces more granular naming convention rules for symbols.'), | ||||
), | ), | ||||
'xhpast.switchhook' => array( | 'xhpast.switchhook' => array( | ||||
'type' => 'optional string', | 'type' => 'optional string', | ||||
'help' => pht( | 'help' => pht( | ||||
'Name of a concrete subclass of ArcanistXHPASTLintSwitchHook which '. | 'Name of a concrete subclass of ArcanistXHPASTLintSwitchHook which '. | ||||
'tunes the analysis of switch() statements for this linter.'), | 'tunes the analysis of switch() statements for this linter.'), | ||||
), | ), | ||||
'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) { | switch ($key) { | ||||
case 'xhpast.naminghook': | case 'xhpast.naminghook': | ||||
$this->naminghook = $value; | $this->naminghook = $value; | ||||
return; | return; | ||||
case 'xhpast.switchhook': | case 'xhpast.switchhook': | ||||
$this->switchhook = $value; | $this->switchhook = $value; | ||||
return; | return; | ||||
case 'xhpast.php-version': | |||||
$this->version = $value; | |||||
joshuaspence: Should //maybe// attempt to validate if `$value` looks like a valid PHP version. | |||||
return; | |||||
case 'xhpast.php-version.windows': | |||||
$this->windowsVersion = $value; | |||||
return; | |||||
} | } | ||||
return parent::setLinterConfigurationValue($key, $value); | return parent::setLinterConfigurationValue($key, $value); | ||||
} | } | ||||
public function getVersion() { | public function getVersion() { | ||||
// The version number should be incremented whenever a new rule is added. | // The version number should be incremented whenever a new rule is added. | ||||
return '7'; | return '7'; | ||||
Show All 15 Lines | if (!$tree) { | ||||
return; | return; | ||||
} | } | ||||
$root = $tree->getRootNode(); | $root = $tree->getRootNode(); | ||||
$method_codes = array( | $method_codes = array( | ||||
'lintStrstrUsedForCheck' => self::LINT_SLOWNESS, | 'lintStrstrUsedForCheck' => self::LINT_SLOWNESS, | ||||
'lintStrposUsedForStart' => self::LINT_SLOWNESS, | 'lintStrposUsedForStart' => self::LINT_SLOWNESS, | ||||
'lintPHP53Features' => self::LINT_PHP_53_FEATURES, | |||||
'lintPHP54Features' => self::LINT_PHP_54_FEATURES, | |||||
'lintImplicitFallthrough' => self::LINT_IMPLICIT_FALLTHROUGH, | 'lintImplicitFallthrough' => self::LINT_IMPLICIT_FALLTHROUGH, | ||||
'lintBraceFormatting' => self::LINT_BRACE_FORMATTING, | 'lintBraceFormatting' => self::LINT_BRACE_FORMATTING, | ||||
'lintTautologicalExpressions' => self::LINT_TAUTOLOGICAL_EXPRESSION, | 'lintTautologicalExpressions' => self::LINT_TAUTOLOGICAL_EXPRESSION, | ||||
'lintCommentSpaces' => self::LINT_COMMENT_SPACING, | 'lintCommentSpaces' => self::LINT_COMMENT_SPACING, | ||||
'lintHashComments' => self::LINT_COMMENT_STYLE, | 'lintHashComments' => self::LINT_COMMENT_STYLE, | ||||
'lintReusedIterators' => self::LINT_REUSED_ITERATORS, | 'lintReusedIterators' => self::LINT_REUSED_ITERATORS, | ||||
'lintReusedIteratorReferences' => self::LINT_REUSED_ITERATOR_REFERENCE, | 'lintReusedIteratorReferences' => self::LINT_REUSED_ITERATOR_REFERENCE, | ||||
'lintVariableVariables' => self::LINT_VARIABLE_VARIABLE, | 'lintVariableVariables' => self::LINT_VARIABLE_VARIABLE, | ||||
Show All 27 Lines | $method_codes = array( | ||||
'lintClosingCallParen' => self::LINT_CLOSING_CALL_PAREN, | 'lintClosingCallParen' => self::LINT_CLOSING_CALL_PAREN, | ||||
'lintClosingDeclarationParen' => self::LINT_CLOSING_DECL_PAREN, | 'lintClosingDeclarationParen' => self::LINT_CLOSING_DECL_PAREN, | ||||
'lintKeywordCasing' => self::LINT_KEYWORD_CASING, | 'lintKeywordCasing' => self::LINT_KEYWORD_CASING, | ||||
'lintStrings' => self::LINT_DOUBLE_QUOTE, | 'lintStrings' => self::LINT_DOUBLE_QUOTE, | ||||
'lintElseIfStatements' => self::LINT_ELSEIF_USAGE, | 'lintElseIfStatements' => self::LINT_ELSEIF_USAGE, | ||||
'lintSemicolons' => self::LINT_SEMICOLON_SPACING, | 'lintSemicolons' => self::LINT_SEMICOLON_SPACING, | ||||
'lintSpaceAroundConcatenationOperators' => | 'lintSpaceAroundConcatenationOperators' => | ||||
self::LINT_CONCATENATION_OPERATOR, | self::LINT_CONCATENATION_OPERATOR, | ||||
'lintPHPCompatibility' => self::LINT_PHP_COMPATIBILITY, | |||||
); | ); | ||||
foreach ($method_codes as $method => $codes) { | foreach ($method_codes as $method => $codes) { | ||||
foreach ((array)$codes as $code) { | foreach ((array)$codes as $code) { | ||||
if ($this->isCodeEnabled($code)) { | if ($this->isCodeEnabled($code)) { | ||||
call_user_func(array($this, $method), $root); | call_user_func(array($this, $method), $root); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
public function lintStrstrUsedForCheck(XHPASTNode $root) { | private function lintStrstrUsedForCheck(XHPASTNode $root) { | ||||
$expressions = $root->selectDescendantsOfType('n_BINARY_EXPRESSION'); | $expressions = $root->selectDescendantsOfType('n_BINARY_EXPRESSION'); | ||||
foreach ($expressions as $expression) { | foreach ($expressions as $expression) { | ||||
$operator = $expression->getChildOfType(1, 'n_OPERATOR'); | $operator = $expression->getChildOfType(1, 'n_OPERATOR'); | ||||
$operator = $operator->getConcreteString(); | $operator = $operator->getConcreteString(); | ||||
if ($operator != '===' && $operator != '!==') { | if ($operator != '===' && $operator != '!==') { | ||||
continue; | continue; | ||||
} | } | ||||
Show All 25 Lines | foreach ($expressions as $expression) { | ||||
$this->raiseLintAtNode( | $this->raiseLintAtNode( | ||||
$strstr, | $strstr, | ||||
self::LINT_SLOWNESS, | self::LINT_SLOWNESS, | ||||
'Use stripos() for checking if the string contains something.'); | 'Use stripos() for checking if the string contains something.'); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
public function lintStrposUsedForStart(XHPASTNode $root) { | private function lintStrposUsedForStart(XHPASTNode $root) { | ||||
$expressions = $root->selectDescendantsOfType('n_BINARY_EXPRESSION'); | $expressions = $root->selectDescendantsOfType('n_BINARY_EXPRESSION'); | ||||
foreach ($expressions as $expression) { | foreach ($expressions as $expression) { | ||||
$operator = $expression->getChildOfType(1, 'n_OPERATOR'); | $operator = $expression->getChildOfType(1, 'n_OPERATOR'); | ||||
$operator = $operator->getConcreteString(); | $operator = $operator->getConcreteString(); | ||||
if ($operator != '===' && $operator != '!==') { | if ($operator != '===' && $operator != '!==') { | ||||
continue; | continue; | ||||
} | } | ||||
Show All 26 Lines | foreach ($expressions as $expression) { | ||||
$strpos, | $strpos, | ||||
self::LINT_SLOWNESS, | self::LINT_SLOWNESS, | ||||
'Use strncasecmp() for checking if the string starts with '. | 'Use strncasecmp() for checking if the string starts with '. | ||||
'something.'); | 'something.'); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
public function lintPHP53Features(XHPASTNode $root) { | private function lintPHPCompatibility(XHPASTNode $root) { | ||||
$php53 = self::LINT_PHP_53_FEATURES; | |||||
$php54 = self::LINT_PHP_54_FEATURES; | |||||
$disabled = ArcanistLintSeverity::SEVERITY_DISABLED; | |||||
Not Done Inline ActionsShould probably look at whether LINT_PHP_53_FEATURES and/or LINT_PHP_54_FEATURES are being used (i.e. is a non-disabled severity set) and then:
joshuaspence: Should probably look at whether `LINT_PHP_53_FEATURES` and/or `LINT_PHP_54_FEATURES` are being… | |||||
if ($this->getLintMessageSeverity($php53) !== $disabled) { | |||||
phutil_deprecated( | |||||
'`LINT_PHP_53_FEATURES` is deprecated.', | |||||
"You should set 'xhpast.php-version' instead."); | |||||
if (!$this->version) { | |||||
$this->version = '5.2.3'; | |||||
} | |||||
} | |||||
if ($this->getLintMessageSeverity($php54) !== $disabled) { | |||||
phutil_deprecated( | |||||
'`LINT_PHP_54_FEATURES` is deprecated.', | |||||
"You should set 'xhpast.php-version' instead."); | |||||
if (!$this->version) { | |||||
$this->version = '5.3.0'; | |||||
} | |||||
} | |||||
Not Done Inline ActionsI think it's fine to get rid of these entirely, I strongly suspect only Phabricator is using them. epriestley: I think it's fine to get rid of these entirely, I strongly suspect only Phabricator is using… | |||||
Not Done Inline ActionsWe use them where I work. I think leave them there for a short period of time. joshuaspence: We use them where I work. I think leave them there for a short period of time. | |||||
if (!$this->version) { | |||||
return; | |||||
} | |||||
$target = phutil_get_library_root('arcanist'). | |||||
'/../resources/php_compat_info.json'; | |||||
$compat_info = json_decode(file_get_contents($target), true); | |||||
Not Done Inline ActionsPrefer Filesystem::readFile() to get a more useful, explicit exception on failure. epriestley: Prefer Filesystem::readFile() to get a more useful, explicit exception on failure. | |||||
$calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); | |||||
foreach ($calls as $call) { | |||||
$node = $call->getChildByIndex(0); | |||||
$name = $node->getConcreteString(); | |||||
$version = idx($compat_info['functions'], $name); | |||||
if ($version && version_compare($version['min'], $this->version, '>')) { | |||||
$this->raiseLintAtNode( | |||||
$node, | |||||
self::LINT_PHP_COMPATIBILITY, | |||||
"This codebase targets PHP {$this->version}, but `{$name}()` was ". | |||||
"not introduced until PHP {$version['min']}."); | |||||
} 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, | |||||
self::LINT_PHP_COMPATIBILITY, | |||||
"This codebase targets PHP {$this->version}, but parameter ". | |||||
($i + 1)." of `{$name}()` was not introduced until PHP ". | |||||
"{$version}."); | |||||
} | |||||
} | |||||
} | |||||
if ($this->windowsVersion) { | |||||
$windows = idx($compat_info['functions_windows'], $name); | |||||
if ($windows === false) { | |||||
$this->raiseLintAtNode( | |||||
$node, | |||||
self::LINT_PHP_COMPATIBILITY, | |||||
"This codebase targets PHP {$this->windowsVersion} on Windows, ". | |||||
"but `{$name}()` is not available there."); | |||||
} else if (version_compare($windows, $this->windowsVersion, '>')) { | |||||
$this->raiseLintAtNode( | |||||
$node, | |||||
self::LINT_PHP_COMPATIBILITY, | |||||
"This codebase targets PHP {$this->windowsVersion} on Windows, ". | |||||
"but `{$name}()` is not available there until PHP ". | |||||
"{$this->windowsVersion}."); | |||||
} | |||||
} | |||||
} | |||||
$classes = $root->selectDescendantsOfType('n_CLASS_NAME'); | |||||
foreach ($classes as $node) { | |||||
$name = $node->getConcreteString(); | |||||
$version = idx($compat_info['interfaces'], $name); | |||||
$version = idx($compat_info['classes'], $name, $version); | |||||
if ($version && version_compare($version['min'], $this->version, '>')) { | |||||
$this->raiseLintAtNode( | |||||
$node, | |||||
self::LINT_PHP_COMPATIBILITY, | |||||
"This codebase targets PHP {$this->version}, but `{$name}` was not ". | |||||
"introduced until PHP {$version['min']}."); | |||||
} | |||||
} | |||||
// 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->selectDescendantsOfType('n_SYMBOL_NAME'); | |||||
foreach ($constants as $node) { | |||||
$name = $node->getConcreteString(); | |||||
$version = idx($compat_info['constants'], $name); | |||||
if ($version && version_compare($version['min'], $this->version, '>')) { | |||||
$this->raiseLintAtNode( | |||||
$node, | |||||
self::LINT_PHP_53_FEATURES, | |||||
"This codebase targets PHP {$this->version}, but `{$name}` was not ". | |||||
"introduced until PHP {$version['min']}."); | |||||
} | |||||
} | |||||
if (version_compare($this->version, '5.3.0') < 0) { | |||||
$this->lintPHP53Features($root); | |||||
} | |||||
if (version_compare($this->version, '5.4.0') < 0) { | |||||
$this->lintPHP54Features($root); | |||||
} | |||||
} | |||||
private function lintPHP53Features(XHPASTNode $root) { | |||||
$functions = $root->selectTokensOfType('T_FUNCTION'); | $functions = $root->selectTokensOfType('T_FUNCTION'); | ||||
foreach ($functions as $function) { | foreach ($functions as $function) { | ||||
$next = $function->getNextToken(); | $next = $function->getNextToken(); | ||||
while ($next) { | while ($next) { | ||||
if ($next->isSemantic()) { | if ($next->isSemantic()) { | ||||
break; | break; | ||||
} | } | ||||
$next = $next->getNextToken(); | $next = $next->getNextToken(); | ||||
} | } | ||||
if ($next) { | if ($next) { | ||||
if ($next->getTypeName() == '(') { | if ($next->getTypeName() == '(') { | ||||
$this->raiseLintAtToken( | $this->raiseLintAtToken( | ||||
$function, | $function, | ||||
self::LINT_PHP_53_FEATURES, | self::LINT_PHP_COMPATIBILITY, | ||||
'This codebase targets PHP 5.2, but anonymous functions were '. | "This codebase targets PHP {$this->version}, but anonymous ". | ||||
'not introduced until PHP 5.3.'); | "functions were not introduced until PHP 5.3."); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
$namespaces = $root->selectTokensOfType('T_NAMESPACE'); | $namespaces = $root->selectTokensOfType('T_NAMESPACE'); | ||||
foreach ($namespaces as $namespace) { | foreach ($namespaces as $namespace) { | ||||
$this->raiseLintAtToken( | $this->raiseLintAtToken( | ||||
$namespace, | $namespace, | ||||
self::LINT_PHP_53_FEATURES, | self::LINT_PHP_COMPATIBILITY, | ||||
'This codebase targets PHP 5.2, but namespaces were not introduced '. | "This codebase targets PHP {$this->version}, but namespaces were not ". | ||||
'until PHP 5.3.'); | "introduced until PHP 5.3."); | ||||
} | } | ||||
// NOTE: This is only "use x;", in anonymous functions the node type is | // NOTE: This is only "use x;", in anonymous functions the node type is | ||||
Not Done Inline ActionsThis is probably wrong now and needs to be given some more thought. joshuaspence: This is probably wrong now and needs to be given some more thought. | |||||
// n_LEXICAL_VARIABLE_LIST even though both tokens are T_USE. | // 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 | // TODO: We parse n_USE in a slightly crazy way right now; that would be | ||||
// a better selector once it's fixed. | // a better selector once it's fixed. | ||||
$uses = $root->selectDescendantsOfType('n_USE_LIST'); | $uses = $root->selectDescendantsOfType('n_USE_LIST'); | ||||
foreach ($uses as $use) { | foreach ($uses as $use) { | ||||
$this->raiseLintAtNode( | $this->raiseLintAtNode( | ||||
$use, | $use, | ||||
self::LINT_PHP_53_FEATURES, | self::LINT_PHP_COMPATIBILITY, | ||||
'This codebase targets PHP 5.2, but namespaces were not introduced '. | "This codebase targets PHP {$this->version}, but namespaces were not ". | ||||
'until PHP 5.3.'); | "introduced until PHP 5.3."); | ||||
} | } | ||||
$statics = $root->selectDescendantsOfType('n_CLASS_STATIC_ACCESS'); | $statics = $root->selectDescendantsOfType('n_CLASS_STATIC_ACCESS'); | ||||
foreach ($statics as $static) { | foreach ($statics as $static) { | ||||
$name = $static->getChildByIndex(0); | $name = $static->getChildByIndex(0); | ||||
if ($name->getTypeName() != 'n_CLASS_NAME') { | if ($name->getTypeName() != 'n_CLASS_NAME') { | ||||
continue; | continue; | ||||
} | } | ||||
if ($name->getConcreteString() == 'static') { | if ($name->getConcreteString() == 'static') { | ||||
$this->raiseLintAtNode( | $this->raiseLintAtNode( | ||||
$name, | $name, | ||||
self::LINT_PHP_53_FEATURES, | self::LINT_PHP_COMPATIBILITY, | ||||
'This codebase targets PHP 5.2, but `static::` was not introduced '. | "This codebase targets PHP {$this->version}, but `static::` was not ". | ||||
'until PHP 5.3.'); | "introduced until PHP 5.3."); | ||||
} | } | ||||
} | } | ||||
$ternaries = $root->selectDescendantsOfType('n_TERNARY_EXPRESSION'); | $ternaries = $root->selectDescendantsOfType('n_TERNARY_EXPRESSION'); | ||||
foreach ($ternaries as $ternary) { | foreach ($ternaries as $ternary) { | ||||
$yes = $ternary->getChildByIndex(1); | $yes = $ternary->getChildByIndex(1); | ||||
if ($yes->getTypeName() == 'n_EMPTY') { | if ($yes->getTypeName() == 'n_EMPTY') { | ||||
$this->raiseLintAtNode( | $this->raiseLintAtNode( | ||||
$ternary, | $ternary, | ||||
self::LINT_PHP_53_FEATURES, | self::LINT_PHP_COMPATIBILITY, | ||||
'This codebase targets PHP 5.2, but short ternary was not '. | "This codebase targets PHP {$this->version}, but short ternary was ". | ||||
'introduced until PHP 5.3.'); | "not introduced until PHP 5.3."); | ||||
} | } | ||||
} | } | ||||
$heredocs = $root->selectDescendantsOfType('n_HEREDOC'); | $heredocs = $root->selectDescendantsOfType('n_HEREDOC'); | ||||
foreach ($heredocs as $heredoc) { | foreach ($heredocs as $heredoc) { | ||||
if (preg_match('/^<<<[\'"]/', $heredoc->getConcreteString())) { | if (preg_match('/^<<<[\'"]/', $heredoc->getConcreteString())) { | ||||
$this->raiseLintAtNode( | $this->raiseLintAtNode( | ||||
$heredoc, | $heredoc, | ||||
self::LINT_PHP_53_FEATURES, | self::LINT_PHP_COMPATIBILITY, | ||||
'This codebase targets PHP 5.2, but nowdoc was not introduced until '. | "This codebase targets PHP {$this->version}, but nowdoc was not ". | ||||
'PHP 5.3.'); | "introduced until PHP 5.3."); | ||||
} | |||||
} | |||||
$this->lintPHP53Functions($root); | |||||
} | |||||
private function lintPHP53Functions(XHPASTNode $root) { | |||||
$target = phutil_get_library_root('arcanist'). | |||||
'/../resources/php_compat_info.json'; | |||||
$compat_info = json_decode(file_get_contents($target), true); | |||||
$required = '5.2.3'; | |||||
$calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); | |||||
foreach ($calls as $call) { | |||||
$node = $call->getChildByIndex(0); | |||||
$name = $node->getConcreteString(); | |||||
$version = idx($compat_info['functions'], $name); | |||||
$windows = idx($compat_info['functions_windows'], $name); | |||||
if ($version && version_compare($version['min'], $required, '>')) { | |||||
$this->raiseLintAtNode( | |||||
$node, | |||||
self::LINT_PHP_53_FEATURES, | |||||
"This codebase targets PHP 5.2.3, but `{$name}()` was not ". | |||||
"introduced until PHP {$version['min']}."); | |||||
} 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, $required, '>')) { | |||||
$this->raiseLintAtNode( | |||||
$param, | |||||
self::LINT_PHP_53_FEATURES, | |||||
"This codebase targets PHP 5.2.3, but parameter ".($i + 1)." ". | |||||
"of `{$name}()` was not introduced until PHP {$version}."); | |||||
} | } | ||||
} | } | ||||
} else if ($windows === '' || version_compare($windows, '5.3.0') > 0) { | |||||
$this->raiseLintAtNode( | |||||
$node, | |||||
self::LINT_PHP_53_FEATURES, | |||||
"This codebase targets PHP 5.3.0 on Windows, but `{$name}()` is not ". | |||||
"available there". | |||||
($windows ? " until PHP {$windows}" : '')."."); | |||||
} | |||||
} | } | ||||
$classes = $root->selectDescendantsOfType('n_CLASS_NAME'); | private function lintPHP54Features(XHPASTNode $root) { | ||||
foreach ($classes as $node) { | |||||
$name = $node->getConcreteString(); | |||||
$version = idx($compat_info['interfaces'], $name); | |||||
$version = idx($compat_info['classes'], $name, $version); | |||||
if ($version && version_compare($version['min'], $required, '>')) { | |||||
$this->raiseLintAtNode( | |||||
$node, | |||||
self::LINT_PHP_53_FEATURES, | |||||
"This codebase targets PHP 5.2.3, but `{$name}` was not ". | |||||
"introduced until PHP {$version['min']}."); | |||||
} | |||||
} | |||||
// 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->selectDescendantsOfType('n_SYMBOL_NAME'); | |||||
foreach ($constants as $node) { | |||||
$name = $node->getConcreteString(); | |||||
$version = idx($compat_info['constants'], $name); | |||||
if ($version && version_compare($version['min'], $required, '>')) { | |||||
$this->raiseLintAtNode( | |||||
$node, | |||||
self::LINT_PHP_53_FEATURES, | |||||
"This codebase targets PHP 5.2.3, but `{$name}` was not ". | |||||
"introduced until PHP {$version['min']}."); | |||||
} | |||||
} | |||||
} | |||||
public function lintPHP54Features(XHPASTNode $root) { | |||||
$indexes = $root->selectDescendantsOfType('n_INDEX_ACCESS'); | $indexes = $root->selectDescendantsOfType('n_INDEX_ACCESS'); | ||||
foreach ($indexes as $index) { | foreach ($indexes as $index) { | ||||
$left = $index->getChildByIndex(0); | $left = $index->getChildByIndex(0); | ||||
switch ($left->getTypeName()) { | switch ($left->getTypeName()) { | ||||
case 'n_FUNCTION_CALL': | case 'n_FUNCTION_CALL': | ||||
case 'n_METHOD_CALL': | case 'n_METHOD_CALL': | ||||
$this->raiseLintAtNode( | $this->raiseLintAtNode( | ||||
$index->getChildByIndex(1), | $index->getChildByIndex(1), | ||||
self::LINT_PHP_54_FEATURES, | self::LINT_PHP_COMPATIBILITY, | ||||
'The f()[...] syntax was not introduced until PHP 5.4, but this '. | 'The f()[...] syntax was not introduced until PHP 5.4, but this '. | ||||
'codebase targets an earlier version of PHP. You can rewrite '. | 'codebase targets an earlier version of PHP. You can rewrite '. | ||||
'this expression using idx().'); | 'this expression using idx().'); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
▲ Show 20 Lines • Show All 152 Lines • ▼ Show 20 Lines | foreach ($switches as $switch) { | ||||
"you forget to add one of those? If you intend to fall through, ". | "you forget to add one of those? If you intend to fall through, ". | ||||
"add a '// fallthrough' comment to silence this warning."); | "add a '// fallthrough' comment to silence this warning."); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
private function lintBraceFormatting(XHPASTNode $root) { | private function lintBraceFormatting(XHPASTNode $root) { | ||||
foreach ($root->selectDescendantsOfType('n_STATEMENT_LIST') as $list) { | foreach ($root->selectDescendantsOfType('n_STATEMENT_LIST') as $list) { | ||||
$tokens = $list->getTokens(); | $tokens = $list->getTokens(); | ||||
if (!$tokens || head($tokens)->getValue() != '{') { | if (!$tokens || head($tokens)->getValue() != '{') { | ||||
continue; | continue; | ||||
} | } | ||||
list($before, $after) = $list->getSurroundingNonsemanticTokens(); | list($before, $after) = $list->getSurroundingNonsemanticTokens(); | ||||
if (!$before) { | if (!$before) { | ||||
$first = head($tokens); | $first = head($tokens); | ||||
Show All 19 Lines | foreach ($root->selectDescendantsOfType('n_STATEMENT_LIST') as $list) { | ||||
$before, | $before, | ||||
self::LINT_BRACE_FORMATTING, | self::LINT_BRACE_FORMATTING, | ||||
'Put opening braces on the same line as control statements and '. | 'Put opening braces on the same line as control statements and '. | ||||
'declarations, with a single space before them.', | 'declarations, with a single space before them.', | ||||
' '); | ' '); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
private function lintTautologicalExpressions(XHPASTNode $root) { | private function lintTautologicalExpressions(XHPASTNode $root) { | ||||
$expressions = $root->selectDescendantsOfType('n_BINARY_EXPRESSION'); | $expressions = $root->selectDescendantsOfType('n_BINARY_EXPRESSION'); | ||||
static $operators = array( | static $operators = array( | ||||
'-' => true, | '-' => true, | ||||
'/' => true, | '/' => true, | ||||
▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | foreach ($expressions as $expr) { | ||||
self::LINT_TAUTOLOGICAL_EXPRESSION, | self::LINT_TAUTOLOGICAL_EXPRESSION, | ||||
'The logical value of this expression is static. Did you forget '. | 'The logical value of this expression is static. Did you forget '. | ||||
'to remove some debugging code?'); | 'to remove some debugging code?'); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Statically evaluate a boolean value from an XHP tree. | * Statically evaluate a boolean value from an XHP tree. | ||||
* | * | ||||
* TODO: Improve this and move it to XHPAST proper? | * TODO: Improve this and move it to XHPAST proper? | ||||
* | * | ||||
* @param string The "semantic string" of a single value. | * @param string The "semantic string" of a single value. | ||||
* @return mixed ##true## or ##false## if the value could be evaluated | * @return mixed ##true## or ##false## if the value could be evaluated | ||||
* statically; ##null## if static evaluation was not possible. | * statically; ##null## if static evaluation was not possible. | ||||
▲ Show 20 Lines • Show All 308 Lines • ▼ Show 20 Lines | foreach ($vvars as $vvar) { | ||||
$this->raiseLintAtNode( | $this->raiseLintAtNode( | ||||
$vvar, | $vvar, | ||||
self::LINT_VARIABLE_VARIABLE, | self::LINT_VARIABLE_VARIABLE, | ||||
'Rewrite this code to use an array. Variable variables are unclear '. | 'Rewrite this code to use an array. Variable variables are unclear '. | ||||
'and hinder static analysis.'); | 'and hinder static analysis.'); | ||||
} | } | ||||
} | } | ||||
protected function lintUndeclaredVariables(XHPASTNode $root) { | private function lintUndeclaredVariables(XHPASTNode $root) { | ||||
// These things declare variables in a function: | // These things declare variables in a function: | ||||
// Explicit parameters | // Explicit parameters | ||||
// Assignment | // Assignment | ||||
// Assignment via list() | // Assignment via list() | ||||
// Static | // Static | ||||
// Global | // Global | ||||
// Lexical vars | // Lexical vars | ||||
// Builtins ($this) | // Builtins ($this) | ||||
▲ Show 20 Lines • Show All 347 Lines • ▼ Show 20 Lines | |||||
private function getConcreteVariableString(XHPASTNode $var) { | private function getConcreteVariableString(XHPASTNode $var) { | ||||
$concrete = $var->getConcreteString(); | $concrete = $var->getConcreteString(); | ||||
// Strip off curly braces as in $obj->{$property}. | // Strip off curly braces as in $obj->{$property}. | ||||
$concrete = trim($concrete, '{}'); | $concrete = trim($concrete, '{}'); | ||||
return $concrete; | return $concrete; | ||||
} | } | ||||
protected function lintPHPTagUse(XHPASTNode $root) { | private function lintPHPTagUse(XHPASTNode $root) { | ||||
$tokens = $root->getTokens(); | $tokens = $root->getTokens(); | ||||
foreach ($tokens as $token) { | foreach ($tokens as $token) { | ||||
if ($token->getTypeName() == 'T_OPEN_TAG') { | if ($token->getTypeName() == 'T_OPEN_TAG') { | ||||
if (trim($token->getValue()) == '<?') { | if (trim($token->getValue()) == '<?') { | ||||
$this->raiseLintAtToken( | $this->raiseLintAtToken( | ||||
$token, | $token, | ||||
self::LINT_PHP_SHORT_TAG, | self::LINT_PHP_SHORT_TAG, | ||||
'Use the full form of the PHP open tag, "<?php".', | 'Use the full form of the PHP open tag, "<?php".', | ||||
Show All 21 Lines | private function lintPHPTagUse(XHPASTNode $root) { | ||||
foreach ($root->selectTokensOfType('T_CLOSE_TAG') as $token) { | foreach ($root->selectTokensOfType('T_CLOSE_TAG') as $token) { | ||||
$this->raiseLintAtToken( | $this->raiseLintAtToken( | ||||
$token, | $token, | ||||
self::LINT_PHP_CLOSE_TAG, | self::LINT_PHP_CLOSE_TAG, | ||||
'Do not use the PHP closing tag, "?>".'); | 'Do not use the PHP closing tag, "?>".'); | ||||
} | } | ||||
} | } | ||||
protected function lintNamingConventions(XHPASTNode $root) { | private function lintNamingConventions(XHPASTNode $root) { | ||||
// We're going to build up a list of <type, name, token, error> tuples | // We're going to build up a list of <type, name, token, error> tuples | ||||
// and then try to instantiate a hook class which has the opportunity to | // and then try to instantiate a hook class which has the opportunity to | ||||
// override us. | // override us. | ||||
$names = array(); | $names = array(); | ||||
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION'); | $classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION'); | ||||
foreach ($classes as $class) { | foreach ($classes as $class) { | ||||
$name_token = $class->getChildByIndex(1); | $name_token = $class->getChildByIndex(1); | ||||
▲ Show 20 Lines • Show All 237 Lines • ▼ Show 20 Lines | foreach ($names as $k => $name_attrs) { | ||||
$this->raiseLintAtNode( | $this->raiseLintAtNode( | ||||
$token, | $token, | ||||
self::LINT_NAMING_CONVENTIONS, | self::LINT_NAMING_CONVENTIONS, | ||||
$result); | $result); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
protected function lintSurpriseConstructors(XHPASTNode $root) { | private function lintSurpriseConstructors(XHPASTNode $root) { | ||||
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION'); | $classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION'); | ||||
foreach ($classes as $class) { | foreach ($classes as $class) { | ||||
$class_name = $class->getChildByIndex(1)->getConcreteString(); | $class_name = $class->getChildByIndex(1)->getConcreteString(); | ||||
$methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION'); | $methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION'); | ||||
foreach ($methods as $method) { | foreach ($methods as $method) { | ||||
$method_name_token = $method->getChildByIndex(2); | $method_name_token = $method->getChildByIndex(2); | ||||
$method_name = $method_name_token->getConcreteString(); | $method_name = $method_name_token->getConcreteString(); | ||||
if (strtolower($class_name) == strtolower($method_name)) { | if (strtolower($class_name) == strtolower($method_name)) { | ||||
$this->raiseLintAtNode( | $this->raiseLintAtNode( | ||||
$method_name_token, | $method_name_token, | ||||
self::LINT_IMPLICIT_CONSTRUCTOR, | self::LINT_IMPLICIT_CONSTRUCTOR, | ||||
'Name constructors __construct() explicitly. This method is a '. | 'Name constructors __construct() explicitly. This method is a '. | ||||
'constructor because it has the same name as the class it is '. | 'constructor because it has the same name as the class it is '. | ||||
'defined in.'); | 'defined in.'); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
protected function lintParenthesesShouldHugExpressions(XHPASTNode $root) { | private function lintParenthesesShouldHugExpressions(XHPASTNode $root) { | ||||
$calls = $root->selectDescendantsOfType('n_CALL_PARAMETER_LIST'); | $calls = $root->selectDescendantsOfType('n_CALL_PARAMETER_LIST'); | ||||
$controls = $root->selectDescendantsOfType('n_CONTROL_CONDITION'); | $controls = $root->selectDescendantsOfType('n_CONTROL_CONDITION'); | ||||
$fors = $root->selectDescendantsOfType('n_FOR_EXPRESSION'); | $fors = $root->selectDescendantsOfType('n_FOR_EXPRESSION'); | ||||
$foreach = $root->selectDescendantsOfType('n_FOREACH_EXPRESSION'); | $foreach = $root->selectDescendantsOfType('n_FOREACH_EXPRESSION'); | ||||
$decl = $root->selectDescendantsOfType('n_DECLARATION_PARAMETER_LIST'); | $decl = $root->selectDescendantsOfType('n_DECLARATION_PARAMETER_LIST'); | ||||
$all_paren_groups = $calls | $all_paren_groups = $calls | ||||
->add($controls) | ->add($controls) | ||||
Show All 40 Lines | foreach ($all_paren_groups as $group) { | ||||
self::LINT_PARENTHESES_SPACING, | self::LINT_PARENTHESES_SPACING, | ||||
'Parentheses should hug their contents.', | 'Parentheses should hug their contents.', | ||||
$string, | $string, | ||||
''); | ''); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
protected function lintSpaceAfterControlStatementKeywords(XHPASTNode $root) { | private function lintSpaceAfterControlStatementKeywords(XHPASTNode $root) { | ||||
foreach ($root->getTokens() as $id => $token) { | foreach ($root->getTokens() as $id => $token) { | ||||
switch ($token->getTypeName()) { | switch ($token->getTypeName()) { | ||||
case 'T_IF': | case 'T_IF': | ||||
case 'T_ELSE': | case 'T_ELSE': | ||||
case 'T_FOR': | case 'T_FOR': | ||||
case 'T_FOREACH': | case 'T_FOREACH': | ||||
case 'T_WHILE': | case 'T_WHILE': | ||||
case 'T_DO': | case 'T_DO': | ||||
Show All 30 Lines | foreach ($root->getTokens() as $id => $token) { | ||||
' '); | ' '); | ||||
} | } | ||||
} | } | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
protected function lintSpaceAroundBinaryOperators(XHPASTNode $root) { | private function lintSpaceAroundBinaryOperators(XHPASTNode $root) { | ||||
$expressions = $root->selectDescendantsOfType('n_BINARY_EXPRESSION'); | $expressions = $root->selectDescendantsOfType('n_BINARY_EXPRESSION'); | ||||
foreach ($expressions as $expression) { | foreach ($expressions as $expression) { | ||||
$operator = $expression->getChildByIndex(1); | $operator = $expression->getChildByIndex(1); | ||||
$operator_value = $operator->getConcreteString(); | $operator_value = $operator->getConcreteString(); | ||||
list($before, $after) = $operator->getSurroundingNonsemanticTokens(); | list($before, $after) = $operator->getSurroundingNonsemanticTokens(); | ||||
$replace = null; | $replace = null; | ||||
if (empty($before) && empty($after)) { | if (empty($before) && empty($after)) { | ||||
▲ Show 20 Lines • Show All 59 Lines • ▼ Show 20 Lines | foreach ($tokens as $token) { | ||||
$replace); | $replace); | ||||
} | } | ||||
} | } | ||||
// TODO: Spacing around default parameter assignment in function/method | // TODO: Spacing around default parameter assignment in function/method | ||||
// declarations (which is not n_BINARY_EXPRESSION). | // declarations (which is not n_BINARY_EXPRESSION). | ||||
} | } | ||||
protected function lintSpaceAroundConcatenationOperators(XHPASTNode $root) { | private function lintSpaceAroundConcatenationOperators(XHPASTNode $root) { | ||||
$tokens = $root->selectTokensOfType('.'); | $tokens = $root->selectTokensOfType('.'); | ||||
foreach ($tokens as $token) { | foreach ($tokens as $token) { | ||||
$prev = $token->getPrevToken(); | $prev = $token->getPrevToken(); | ||||
$next = $token->getNextToken(); | $next = $token->getNextToken(); | ||||
foreach (array('prev' => $prev, 'next' => $next) as $wtoken) { | foreach (array('prev' => $prev, 'next' => $next) as $wtoken) { | ||||
if ($wtoken->getTypeName() != 'T_WHITESPACE') { | if ($wtoken->getTypeName() != 'T_WHITESPACE') { | ||||
continue; | continue; | ||||
Show All 14 Lines | foreach ($tokens as $token) { | ||||
$wtoken, | $wtoken, | ||||
self::LINT_BINARY_EXPRESSION_SPACING, | self::LINT_BINARY_EXPRESSION_SPACING, | ||||
'Convention: no spaces around "." (string concatenation) operator.', | 'Convention: no spaces around "." (string concatenation) operator.', | ||||
''); | ''); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
protected function lintDynamicDefines(XHPASTNode $root) { | private function lintDynamicDefines(XHPASTNode $root) { | ||||
$calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); | $calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); | ||||
foreach ($calls as $call) { | foreach ($calls as $call) { | ||||
$name = $call->getChildByIndex(0)->getConcreteString(); | $name = $call->getChildByIndex(0)->getConcreteString(); | ||||
if (strtolower($name) == 'define') { | if (strtolower($name) == 'define') { | ||||
$parameter_list = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST'); | $parameter_list = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST'); | ||||
$defined = $parameter_list->getChildByIndex(0); | $defined = $parameter_list->getChildByIndex(0); | ||||
if (!$defined->isStaticScalar()) { | if (!$defined->isStaticScalar()) { | ||||
$this->raiseLintAtNode( | $this->raiseLintAtNode( | ||||
$defined, | $defined, | ||||
self::LINT_DYNAMIC_DEFINE, | self::LINT_DYNAMIC_DEFINE, | ||||
'First argument to define() must be a string literal.'); | 'First argument to define() must be a string literal.'); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
protected function lintUseOfThisInStaticMethods(XHPASTNode $root) { | private function lintUseOfThisInStaticMethods(XHPASTNode $root) { | ||||
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION'); | $classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION'); | ||||
foreach ($classes as $class) { | foreach ($classes as $class) { | ||||
$methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION'); | $methods = $class->selectDescendantsOfType('n_METHOD_DECLARATION'); | ||||
foreach ($methods as $method) { | foreach ($methods as $method) { | ||||
$attributes = $method | $attributes = $method | ||||
->getChildByIndex(0, 'n_METHOD_MODIFIER_LIST') | ->getChildByIndex(0, 'n_METHOD_MODIFIER_LIST') | ||||
->selectDescendantsOfType('n_STRING'); | ->selectDescendantsOfType('n_STRING'); | ||||
Show All 34 Lines | |||||
} | } | ||||
/** | /** | ||||
* preg_quote() takes two arguments, but the second one is optional because | * preg_quote() takes two arguments, but the second one is optional because | ||||
* it is possible to use (), [] or {} as regular expression delimiters. If | * it is possible to use (), [] or {} as regular expression delimiters. If | ||||
* you don't pass a second argument, you're probably going to get something | * you don't pass a second argument, you're probably going to get something | ||||
* wrong. | * wrong. | ||||
*/ | */ | ||||
protected function lintPregQuote(XHPASTNode $root) { | private function lintPregQuote(XHPASTNode $root) { | ||||
$function_calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); | $function_calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); | ||||
foreach ($function_calls as $call) { | foreach ($function_calls as $call) { | ||||
$name = $call->getChildByIndex(0)->getConcreteString(); | $name = $call->getChildByIndex(0)->getConcreteString(); | ||||
if (strtolower($name) === 'preg_quote') { | if (strtolower($name) === 'preg_quote') { | ||||
$parameter_list = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST'); | $parameter_list = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST'); | ||||
if (count($parameter_list->getChildren()) !== 2) { | if (count($parameter_list->getChildren()) !== 2) { | ||||
$this->raiseLintAtNode( | $this->raiseLintAtNode( | ||||
$call, | $call, | ||||
Show All 16 Lines | |||||
* exit code 0. This is likely not what is intended; these statements have | * exit code 0. This is likely not what is intended; these statements have | ||||
* different effects: | * different effects: | ||||
* | * | ||||
* exit(-1); | * exit(-1); | ||||
* exit -1; | * exit -1; | ||||
* | * | ||||
* The former exits with a failure code, the latter with a success code! | * The former exits with a failure code, the latter with a success code! | ||||
*/ | */ | ||||
protected function lintExitExpressions(XHPASTNode $root) { | private function lintExitExpressions(XHPASTNode $root) { | ||||
$unaries = $root->selectDescendantsOfType('n_UNARY_PREFIX_EXPRESSION'); | $unaries = $root->selectDescendantsOfType('n_UNARY_PREFIX_EXPRESSION'); | ||||
foreach ($unaries as $unary) { | foreach ($unaries as $unary) { | ||||
$operator = $unary->getChildByIndex(0)->getConcreteString(); | $operator = $unary->getChildByIndex(0)->getConcreteString(); | ||||
if (strtolower($operator) == 'exit') { | if (strtolower($operator) == 'exit') { | ||||
if ($unary->getParentNode()->getTypeName() != 'n_STATEMENT') { | if ($unary->getParentNode()->getTypeName() != 'n_STATEMENT') { | ||||
$this->raiseLintAtNode( | $this->raiseLintAtNode( | ||||
$unary, | $unary, | ||||
self::LINT_EXIT_EXPRESSION, | self::LINT_EXIT_EXPRESSION, | ||||
Show All 16 Lines | foreach ($indexes as $index) { | ||||
self::LINT_ARRAY_INDEX_SPACING, | self::LINT_ARRAY_INDEX_SPACING, | ||||
'Convention: no spaces before index access.', | 'Convention: no spaces before index access.', | ||||
$trailing_text, | $trailing_text, | ||||
''); | ''); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
protected function lintTODOComments(XHPASTNode $root) { | private function lintTODOComments(XHPASTNode $root) { | ||||
$comments = $root->selectTokensOfType('T_COMMENT') + | $comments = $root->selectTokensOfType('T_COMMENT') + | ||||
$root->selectTokensOfType('T_DOC_COMMENT'); | $root->selectTokensOfType('T_DOC_COMMENT'); | ||||
foreach ($comments as $token) { | foreach ($comments as $token) { | ||||
$value = $token->getValue(); | $value = $token->getValue(); | ||||
if ($token->getTypeName() === 'T_DOC_COMMENT') { | if ($token->getTypeName() === 'T_DOC_COMMENT') { | ||||
$regex = '/(TODO|@todo)/'; | $regex = '/(TODO|@todo)/'; | ||||
} else { | } else { | ||||
▲ Show 20 Lines • Show All 140 Lines • ▼ Show 20 Lines | private function lintDuplicateKeysInArray(XHPASTNode $root) { | ||||
} | } | ||||
} | } | ||||
private function lintClosingCallParen(XHPASTNode $root) { | private function lintClosingCallParen(XHPASTNode $root) { | ||||
$calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); | $calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); | ||||
$calls = $calls->add($root->selectDescendantsOfType('n_METHOD_CALL')); | $calls = $calls->add($root->selectDescendantsOfType('n_METHOD_CALL')); | ||||
foreach ($calls as $call) { | foreach ($calls as $call) { | ||||
// If the last parameter of a call is a HEREDOC, don't apply this rule. | // If the last parameter of a call is a HEREDOC, don't apply this rule. | ||||
$params = $call | $params = $call | ||||
->getChildOfType(1, 'n_CALL_PARAMETER_LIST') | ->getChildOfType(1, 'n_CALL_PARAMETER_LIST') | ||||
->getChildren(); | ->getChildren(); | ||||
if ($params) { | if ($params) { | ||||
$last_param = last($params); | $last_param = last($params); | ||||
if ($last_param->getTypeName() == 'n_HEREDOC') { | if ($last_param->getTypeName() == 'n_HEREDOC') { | ||||
▲ Show 20 Lines • Show All 93 Lines • ▼ Show 20 Lines | foreach ($keywords as $keyword) { | ||||
$expected_spelling); | $expected_spelling); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
private function lintStrings(XHPASTNode $root) { | private function lintStrings(XHPASTNode $root) { | ||||
$nodes = $root->selectDescendantsOfTypes(array( | $nodes = $root->selectDescendantsOfTypes(array( | ||||
'n_CONCATENATION_LIST', | 'n_CONCATENATION_LIST', | ||||
'n_STRING_SCALAR')); | 'n_STRING_SCALAR', | ||||
)); | |||||
foreach ($nodes as $node) { | foreach ($nodes as $node) { | ||||
$strings = array(); | $strings = array(); | ||||
if ($node->getTypeName() === 'n_CONCATENATION_LIST') { | if ($node->getTypeName() === 'n_CONCATENATION_LIST') { | ||||
$strings = $node->selectDescendantsOfType('n_STRING_SCALAR'); | $strings = $node->selectDescendantsOfType('n_STRING_SCALAR'); | ||||
} else if ($node->getTypeName() === 'n_STRING_SCALAR') { | } else if ($node->getTypeName() === 'n_STRING_SCALAR') { | ||||
$strings = array($node); | $strings = array($node); | ||||
▲ Show 20 Lines • Show All 110 Lines • Show Last 20 Lines |
Should maybe attempt to validate if $value looks like a valid PHP version.