public function process(XHPASTNode $root)
 {
     $calls = $root->selectDescendantsOfTypes(array('n_FUNCTION_CALL', 'n_METHOD_CALL'));
     foreach ($calls as $call) {
         $params = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST');
         $tokens = $params->getTokens();
         $first = head($tokens);
         $leading = $first->getNonsemanticTokensBefore();
         $leading_text = implode('', mpull($leading, 'getValue'));
         if (preg_match('/^\\s+$/', $leading_text)) {
             $this->raiseLintAtOffset($first->getOffset() - strlen($leading_text), pht('Convention: no spaces before opening parenthesis in calls.'), $leading_text, '');
         }
     }
     foreach ($calls as $call) {
         // If the last parameter of a call is a HEREDOC, don't apply this rule.
         $params = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST')->getChildren();
         if ($params) {
             $last_param = last($params);
             if ($last_param->getTypeName() === 'n_HEREDOC') {
                 continue;
             }
         }
         $tokens = $call->getTokens();
         $last = array_pop($tokens);
         $trailing = $last->getNonsemanticTokensBefore();
         $trailing_text = implode('', mpull($trailing, 'getValue'));
         if (preg_match('/^\\s+$/', $trailing_text)) {
             $this->raiseLintAtOffset($last->getOffset() - strlen($trailing_text), pht('Convention: no spaces before closing parenthesis in calls.'), $trailing_text, '');
         }
     }
 }
 public function process(XHPASTNode $root)
 {
     $all_paren_groups = $root->selectDescendantsOfTypes(array('n_ARRAY_VALUE_LIST', 'n_ASSIGNMENT_LIST', 'n_CALL_PARAMETER_LIST', 'n_DECLARATION_PARAMETER_LIST', 'n_CONTROL_CONDITION', 'n_FOR_EXPRESSION', 'n_FOREACH_EXPRESSION'));
     foreach ($all_paren_groups as $group) {
         $tokens = $group->getTokens();
         $token_o = array_shift($tokens);
         $token_c = array_pop($tokens);
         $nonsem_o = $token_o->getNonsemanticTokensAfter();
         $nonsem_c = $token_c->getNonsemanticTokensBefore();
         if (!$nonsem_o) {
             continue;
         }
         $raise = array();
         $string_o = implode('', mpull($nonsem_o, 'getValue'));
         if (preg_match('/^[ ]+$/', $string_o)) {
             $raise[] = array($nonsem_o, $string_o);
         }
         if ($nonsem_o !== $nonsem_c) {
             $string_c = implode('', mpull($nonsem_c, 'getValue'));
             if (preg_match('/^[ ]+$/', $string_c)) {
                 $raise[] = array($nonsem_c, $string_c);
             }
         }
         foreach ($raise as $warning) {
             list($tokens, $string) = $warning;
             $this->raiseLintAtOffset(reset($tokens)->getOffset(), pht('Parentheses should hug their contents.'), $string, '');
         }
     }
 }
 public function process(XHPASTNode $root)
 {
     foreach ($root->selectDescendantsOfType('n_STATEMENT_LIST') as $list) {
         $tokens = $list->getTokens();
         if (!$tokens || head($tokens)->getValue() != '{') {
             continue;
         }
         list($before, $after) = $list->getSurroundingNonsemanticTokens();
         if (!$before) {
             $first = head($tokens);
             // Only insert the space if we're after a closing parenthesis. If
             // we're in a construct like "else{}", other rules will insert space
             // after the 'else' correctly.
             $prev = $first->getPrevToken();
             if (!$prev || $prev->getValue() !== ')') {
                 continue;
             }
             $this->raiseLintAtToken($first, pht('Put opening braces on the same line as control statements and ' . 'declarations, with a single space before them.'), ' ' . $first->getValue());
         } else {
             if (count($before) === 1) {
                 $before = reset($before);
                 if ($before->getValue() !== ' ') {
                     $this->raiseLintAtToken($before, pht('Put opening braces on the same line as control statements and ' . 'declarations, with a single space before them.'), ' ');
                 }
             }
         }
     }
     $nodes = $root->selectDescendantsOfType('n_STATEMENT');
     foreach ($nodes as $node) {
         $parent = $node->getParentNode();
         if (!$parent) {
             continue;
         }
         $type = $parent->getTypeName();
         if ($type != 'n_STATEMENT_LIST' && $type != 'n_DECLARE') {
             $this->raiseLintAtNode($node, pht('Use braces to surround a statement block.'));
         }
     }
     $nodes = $root->selectDescendantsOfTypes(array('n_DO_WHILE', 'n_ELSE', 'n_ELSEIF'));
     foreach ($nodes as $list) {
         $tokens = $list->getTokens();
         if (!$tokens || last($tokens)->getValue() != '}') {
             continue;
         }
         list($before, $after) = $list->getSurroundingNonsemanticTokens();
         if (!$before) {
             $first = last($tokens);
             $this->raiseLintAtToken($first, pht('Put opening braces on the same line as control statements and ' . 'declarations, with a single space before them.'), ' ' . $first->getValue());
         } else {
             if (count($before) === 1) {
                 $before = reset($before);
                 if ($before->getValue() !== ' ') {
                     $this->raiseLintAtToken($before, pht('Put opening braces on the same line as control statements and ' . 'declarations, with a single space before them.'), ' ');
                 }
             }
         }
     }
 }
 public function process(XHPASTNode $root)
 {
     $nodes = $root->selectDescendantsOfTypes(array('n_CONCATENATION_LIST', 'n_STRING_SCALAR'));
     foreach ($nodes as $node) {
         $strings = array();
         if ($node->getTypeName() === 'n_CONCATENATION_LIST') {
             $strings = $node->selectDescendantsOfType('n_STRING_SCALAR');
         } else {
             if ($node->getTypeName() === 'n_STRING_SCALAR') {
                 $strings = array($node);
                 if ($node->getParentNode()->getTypeName() === 'n_CONCATENATION_LIST') {
                     continue;
                 }
             }
         }
         $valid = false;
         $invalid_nodes = array();
         $fixes = array();
         foreach ($strings as $string) {
             $concrete_string = $string->getConcreteString();
             $single_quoted = $concrete_string[0] === "'";
             $contents = substr($concrete_string, 1, -1);
             // Double quoted strings are allowed when the string contains the
             // following characters.
             static $allowed_chars = array('\\n', '\\r', '\\t', '\\v', '\\e', '\\f', '\'', '\\0', '\\1', '\\2', '\\3', '\\4', '\\5', '\\6', '\\7', '\\x');
             $contains_special_chars = false;
             foreach ($allowed_chars as $allowed_char) {
                 if (strpos($contents, $allowed_char) !== false) {
                     $contains_special_chars = true;
                 }
             }
             if (!$string->isConstantString()) {
                 $valid = true;
             } else {
                 if ($contains_special_chars && !$single_quoted) {
                     $valid = true;
                 } else {
                     if (!$contains_special_chars && !$single_quoted) {
                         $invalid_nodes[] = $string;
                         $fixes[$string->getID()] = "'" . str_replace('\\"', '"', $contents) . "'";
                     }
                 }
             }
         }
         if (!$valid) {
             foreach ($invalid_nodes as $invalid_node) {
                 $this->raiseLintAtNode($invalid_node, pht('String does not require double quotes. For consistency, ' . 'prefer single quotes.'), $fixes[$invalid_node->getID()]);
             }
         }
     }
 }
 public function process(XHPASTNode $root)
 {
     $decs = $root->selectDescendantsOfTypes(array('n_FUNCTION_DECLARATION', 'n_METHOD_DECLARATION'));
     foreach ($decs as $dec) {
         $params = $dec->getChildOfType(3, 'n_DECLARATION_PARAMETER_LIST');
         $tokens = $params->getTokens();
         $last = array_pop($tokens);
         $trailing = $last->getNonsemanticTokensBefore();
         $trailing_text = implode('', mpull($trailing, 'getValue'));
         if (preg_match('/^\\s+$/', $trailing_text)) {
             $this->raiseLintAtOffset($last->getOffset() - strlen($trailing_text), pht('Convention: no spaces before closing parenthesis in ' . 'function and method declarations.'), $trailing_text, '');
         }
     }
 }
 public function process(XHPASTNode $root)
 {
     $methods = $root->selectDescendantsOfTypes(array('n_CLASS_MEMBER_MODIFIER_LIST', 'n_METHOD_MODIFIER_LIST'));
     foreach ($methods as $method) {
         $modifiers = $method->getChildren();
         $is_abstract = false;
         $is_final = false;
         $is_static = false;
         $visibility = null;
         foreach ($modifiers as $modifier) {
             switch ($modifier->getConcreteString()) {
                 case 'abstract':
                     if ($method->getTypeName() == 'n_CLASS_MEMBER_MODIFIER_LIST') {
                         $this->raiseLintAtNode($modifier, pht('Properties cannot be declared %s.', 'abstract'));
                     }
                     if ($is_abstract) {
                         $this->raiseLintAtNode($modifier, pht('Multiple %s modifiers are not allowed.', 'abstract'));
                     }
                     if ($is_final) {
                         $this->raiseLintAtNode($modifier, pht('Cannot use the %s modifier on an %s class member', 'final', 'abstract'));
                     }
                     $is_abstract = true;
                     break;
                 case 'final':
                     if ($is_abstract) {
                         $this->raiseLintAtNode($modifier, pht('Cannot use the %s modifier on an %s class member', 'final', 'abstract'));
                     }
                     if ($is_final) {
                         $this->raiseLintAtNode($modifier, pht('Multiple %s modifiers are not allowed.', 'final'));
                     }
                     $is_final = true;
                     break;
                 case 'public':
                 case 'protected':
                 case 'private':
                     if ($visibility) {
                         $this->raiseLintAtNode($modifier, pht('Multiple access type modifiers are not allowed.'));
                     }
                     $visibility = $modifier->getConcreteString();
                     break;
                 case 'static':
                     if ($is_static) {
                         $this->raiseLintAtNode($modifier, pht('Multiple %s modifiers are not allowed.', 'static'));
                     }
                     break;
             }
         }
     }
 }
 public function process(XHPASTNode $root)
 {
     $nodes = $root->selectDescendantsOfTypes(array('n_ARRAY_LITERAL', 'n_FUNCTION_CALL', 'n_METHOD_CALL', 'n_LIST'));
     foreach ($nodes as $node) {
         switch ($node->getTypeName()) {
             case 'n_ARRAY_LITERAL':
                 if (head($node->getTokens())->getTypeName() == '[') {
                     // Short array syntax.
                     continue 2;
                 }
                 $params = $node->getChildOfType(0, 'n_ARRAY_VALUE_LIST');
                 break;
             case 'n_FUNCTION_CALL':
             case 'n_METHOD_CALL':
                 $params = $node->getChildOfType(1, 'n_CALL_PARAMETER_LIST');
                 break;
             case 'n_LIST':
                 $params = $node->getChildOfType(0, 'n_ASSIGNMENT_LIST');
                 break;
             default:
                 throw new Exception(pht("Unexpected node of type '%s'!", $node->getTypeName()));
         }
         $tokens = $params->getTokens();
         $first = head($tokens);
         $leading = $first->getNonsemanticTokensBefore();
         $leading_text = implode('', mpull($leading, 'getValue'));
         if (preg_match('/^\\s+$/', $leading_text)) {
             $this->raiseLintAtOffset($first->getOffset() - strlen($leading_text), pht('Convention: no spaces before opening parentheses.'), $leading_text, '');
         }
         // If the last parameter of a call is a HEREDOC, don't apply this rule.
         $params = $params->getChildren();
         if ($params) {
             $last_param = last($params);
             if ($last_param->getTypeName() === 'n_HEREDOC') {
                 continue;
             }
         }
         $tokens = $node->getTokens();
         $last = array_pop($tokens);
         if ($node->getTypeName() == 'n_ARRAY_LITERAL') {
             continue;
         }
         $trailing = $last->getNonsemanticTokensBefore();
         $trailing_text = implode('', mpull($trailing, 'getValue'));
         if (preg_match('/^\\s+$/', $trailing_text)) {
             $this->raiseLintAtOffset($last->getOffset() - strlen($trailing_text), pht('Convention: no spaces before closing parentheses.'), $trailing_text, '');
         }
     }
 }
 public function process(XHPASTNode $root)
 {
     $nodes = $root->selectDescendantsOfTypes(array('n_INCLUDE_FILE', 'n_ECHO_LIST'));
     foreach ($nodes as $node) {
         $child = head($node->getChildren());
         if ($child->getTypeName() === 'n_PARENTHETICAL_EXPRESSION') {
             list($before, $after) = $child->getSurroundingNonsemanticTokens();
             $replace = preg_replace('/^\\((.*)\\)$/', '$1', $child->getConcreteString());
             if (!$before) {
                 $replace = ' ' . $replace;
             }
             $this->raiseLintAtNode($child, pht('Language constructs do not require parentheses.'), $replace);
         }
     }
 }
 public function process(XHPASTNode $root)
 {
     $decs = $root->selectDescendantsOfTypes(array('n_FUNCTION_DECLARATION', 'n_METHOD_DECLARATION'));
     foreach ($decs as $dec) {
         $params = $dec->getChildOfType(3, 'n_DECLARATION_PARAMETER_LIST');
         $tokens = $params->getTokens();
         $first = head($tokens);
         $last = last($tokens);
         $leading = $first->getNonsemanticTokensBefore();
         $leading_text = implode('', mpull($leading, 'getValue'));
         $trailing = $last->getNonsemanticTokensBefore();
         $trailing_text = implode('', mpull($trailing, 'getValue'));
         if ($dec->getChildByIndex(2)->getTypeName() == 'n_EMPTY') {
             // Anonymous functions.
             if ($leading_text != ' ') {
                 $this->raiseLintAtOffset($first->getOffset() - strlen($leading_text), pht('Convention: space before opening parenthesis in ' . 'anonymous function declarations.'), $leading_text, ' ');
             }
         } else {
             if (preg_match('/^\\s+$/', $leading_text)) {
                 $this->raiseLintAtOffset($first->getOffset() - strlen($leading_text), pht('Convention: no spaces before opening parenthesis in ' . 'function and method declarations.'), $leading_text, '');
             }
         }
         if (preg_match('/^\\s+$/', $trailing_text)) {
             $this->raiseLintAtOffset($last->getOffset() - strlen($trailing_text), pht('Convention: no spaces before closing parenthesis in ' . 'function and method declarations.'), $trailing_text, '');
         }
         $use_list = $dec->getChildByIndex(4);
         if ($use_list->getTypeName() == 'n_EMPTY') {
             continue;
         }
         $use_token = $use_list->selectTokensOfType('T_USE');
         foreach ($use_token as $use) {
             $before = $use->getNonsemanticTokensBefore();
             $after = $use->getNonsemanticTokensAfter();
             if (!$before) {
                 $this->raiseLintAtOffset($use->getOffset(), pht('Convention: space before `%s` token.', 'use'), '', ' ');
             }
             if (!$after) {
                 $this->raiseLintAtOffset($use->getOffset() + strlen($use->getValue()), pht('Convention: space after `%s` token.', 'use'), '', ' ');
             }
         }
     }
 }
 public function process(XHPASTNode $root)
 {
     // These things declare variables in a function:
     //    Explicit parameters
     //    Assignment
     //    Assignment via list()
     //    Static
     //    Global
     //    Lexical vars
     //    Builtins ($this)
     //    foreach()
     //    catch
     //
     // These things make lexical scope unknowable:
     //    Use of extract()
     //    Assignment to variable variables ($$x)
     //    Global with variable variables
     //
     // These things don't count as "using" a variable:
     //    isset()
     //    empty()
     //    Static class variables
     //
     // The general approach here is to find each function/method declaration,
     // then:
     //
     //  1. Identify all the variable declarations, and where they first occur
     //     in the function/method declaration.
     //  2. Identify all the uses that don't really count (as above).
     //  3. Everything else must be a use of a variable.
     //  4. For each variable, check if any uses occur before the declaration
     //     and warn about them.
     //
     // We also keep track of where lexical scope becomes unknowable (e.g.,
     // because the function calls extract() or uses dynamic variables,
     // preventing us from keeping track of which variables are defined) so we
     // can stop issuing warnings after that.
     //
     // TODO: Support functions defined inside other functions which is commonly
     // used with anonymous functions.
     $defs = $root->selectDescendantsOfTypes(array('n_FUNCTION_DECLARATION', 'n_METHOD_DECLARATION'));
     foreach ($defs as $def) {
         // We keep track of the first offset where scope becomes unknowable, and
         // silence any warnings after that. Default it to INT_MAX so we can min()
         // it later to keep track of the first problem we encounter.
         $scope_destroyed_at = PHP_INT_MAX;
         $declarations = array('$this' => 0) + array_fill_keys($this->getSuperGlobalNames(), 0);
         $declaration_tokens = array();
         $exclude_tokens = array();
         $vars = array();
         // First up, find all the different kinds of declarations, as explained
         // above. Put the tokens into the $vars array.
         $param_list = $def->getChildOfType(3, 'n_DECLARATION_PARAMETER_LIST');
         $param_vars = $param_list->selectDescendantsOfType('n_VARIABLE');
         foreach ($param_vars as $var) {
             $vars[] = $var;
         }
         // This is PHP5.3 closure syntax: function () use ($x) {};
         $lexical_vars = $def->getChildByIndex(4)->selectDescendantsOfType('n_VARIABLE');
         foreach ($lexical_vars as $var) {
             $vars[] = $var;
         }
         $body = $def->getChildByIndex(5);
         if ($body->getTypeName() === 'n_EMPTY') {
             // Abstract method declaration.
             continue;
         }
         $static_vars = $body->selectDescendantsOfType('n_STATIC_DECLARATION')->selectDescendantsOfType('n_VARIABLE');
         foreach ($static_vars as $var) {
             $vars[] = $var;
         }
         $global_vars = $body->selectDescendantsOfType('n_GLOBAL_DECLARATION_LIST');
         foreach ($global_vars as $var_list) {
             foreach ($var_list->getChildren() as $var) {
                 if ($var->getTypeName() === 'n_VARIABLE') {
                     $vars[] = $var;
                 } else {
                     // Dynamic global variable, i.e. "global $$x;".
                     $scope_destroyed_at = min($scope_destroyed_at, $var->getOffset());
                     // An error is raised elsewhere, no need to raise here.
                 }
             }
         }
         // Include "catch (Exception $ex)", but not variables in the body of the
         // catch block.
         $catches = $body->selectDescendantsOfType('n_CATCH');
         foreach ($catches as $catch) {
             $vars[] = $catch->getChildOfType(1, 'n_VARIABLE');
         }
         $binary = $body->selectDescendantsOfType('n_BINARY_EXPRESSION');
         foreach ($binary as $expr) {
             if ($expr->getChildByIndex(1)->getConcreteString() !== '=') {
                 continue;
             }
             $lval = $expr->getChildByIndex(0);
             if ($lval->getTypeName() === 'n_VARIABLE') {
                 $vars[] = $lval;
             } else {
                 if ($lval->getTypeName() === 'n_LIST') {
                     // Recursively grab everything out of list(), since the grammar
                     // permits list() to be nested. Also note that list() is ONLY valid
                     // as an lval assignments, so we could safely lift this out of the
                     // n_BINARY_EXPRESSION branch.
                     $assign_vars = $lval->selectDescendantsOfType('n_VARIABLE');
                     foreach ($assign_vars as $var) {
                         $vars[] = $var;
                     }
                 }
             }
             if ($lval->getTypeName() === 'n_VARIABLE_VARIABLE') {
                 $scope_destroyed_at = min($scope_destroyed_at, $lval->getOffset());
                 // No need to raise here since we raise an error elsewhere.
             }
         }
         $calls = $body->selectDescendantsOfType('n_FUNCTION_CALL');
         foreach ($calls as $call) {
             $name = strtolower($call->getChildByIndex(0)->getConcreteString());
             if ($name === 'empty' || $name === 'isset') {
                 $params = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST')->selectDescendantsOfType('n_VARIABLE');
                 foreach ($params as $var) {
                     $exclude_tokens[$var->getID()] = true;
                 }
                 continue;
             }
             if ($name !== 'extract') {
                 continue;
             }
             $scope_destroyed_at = min($scope_destroyed_at, $call->getOffset());
         }
         // Now we have every declaration except foreach(), handled below. Build
         // two maps, one which just keeps track of which tokens are part of
         // declarations ($declaration_tokens) and one which has the first offset
         // where a variable is declared ($declarations).
         foreach ($vars as $var) {
             $concrete = $this->getConcreteVariableString($var);
             $declarations[$concrete] = min(idx($declarations, $concrete, PHP_INT_MAX), $var->getOffset());
             $declaration_tokens[$var->getID()] = true;
         }
         // Excluded tokens are ones we don't "count" as being used, described
         // above. Put them into $exclude_tokens.
         $class_statics = $body->selectDescendantsOfType('n_CLASS_STATIC_ACCESS');
         $class_static_vars = $class_statics->selectDescendantsOfType('n_VARIABLE');
         foreach ($class_static_vars as $var) {
             $exclude_tokens[$var->getID()] = true;
         }
         // Find all the variables in scope, and figure out where they are used.
         // We want to find foreach() iterators which are both declared before and
         // used after the foreach() loop.
         $uses = array();
         $all_vars = $body->selectDescendantsOfType('n_VARIABLE');
         $all = array();
         // NOTE: $all_vars is not a real array so we can't unset() it.
         foreach ($all_vars as $var) {
             // Be strict since it's easier; we don't let you reuse an iterator you
             // declared before a loop after the loop, even if you're just assigning
             // to it.
             $concrete = $this->getConcreteVariableString($var);
             $uses[$concrete][$var->getID()] = $var->getOffset();
             if (isset($declaration_tokens[$var->getID()])) {
                 // We know this is part of a declaration, so it's fine.
                 continue;
             }
             if (isset($exclude_tokens[$var->getID()])) {
                 // We know this is part of isset() or similar, so it's fine.
                 continue;
             }
             $all[$var->getOffset()] = $concrete;
         }
         // Do foreach() last, we want to handle implicit redeclaration of a
         // variable already in scope since this probably means we're ovewriting a
         // local.
         // NOTE: Processing foreach expressions in order allows programs which
         // reuse iterator variables in other foreach() loops -- this is fine. We
         // have a separate warning to prevent nested loops from reusing the same
         // iterators.
         $foreaches = $body->selectDescendantsOfType('n_FOREACH');
         $all_foreach_vars = array();
         foreach ($foreaches as $foreach) {
             $foreach_expr = $foreach->getChildOfType(0, 'n_FOREACH_EXPRESSION');
             $foreach_vars = array();
             // Determine the end of the foreach() loop.
             $foreach_tokens = $foreach->getTokens();
             $last_token = end($foreach_tokens);
             $foreach_end = $last_token->getOffset();
             $key_var = $foreach_expr->getChildByIndex(1);
             if ($key_var->getTypeName() === 'n_VARIABLE') {
                 $foreach_vars[] = $key_var;
             }
             $value_var = $foreach_expr->getChildByIndex(2);
             if ($value_var->getTypeName() === 'n_VARIABLE') {
                 $foreach_vars[] = $value_var;
             } else {
                 // The root-level token may be a reference, as in:
                 //    foreach ($a as $b => &$c) { ... }
                 // Reach into the n_VARIABLE_REFERENCE node to grab the n_VARIABLE
                 // node.
                 $var = $value_var->getChildByIndex(0);
                 if ($var->getTypeName() === 'n_VARIABLE_VARIABLE') {
                     $var = $var->getChildByIndex(0);
                 }
                 $foreach_vars[] = $var;
             }
             // Remove all uses of the iterators inside of the foreach() loop from
             // the $uses map.
             foreach ($foreach_vars as $var) {
                 $concrete = $this->getConcreteVariableString($var);
                 $offset = $var->getOffset();
                 foreach ($uses[$concrete] as $id => $use_offset) {
                     if ($use_offset >= $offset && $use_offset < $foreach_end) {
                         unset($uses[$concrete][$id]);
                     }
                 }
                 $all_foreach_vars[] = $var;
             }
         }
         foreach ($all_foreach_vars as $var) {
             $concrete = $this->getConcreteVariableString($var);
             $offset = $var->getOffset();
             // This is a declaration, exclude it from the "declare variables prior
             // to use" check below.
             unset($all[$var->getOffset()]);
             $vars[] = $var;
         }
         // Now rebuild declarations to include foreach().
         foreach ($vars as $var) {
             $concrete = $this->getConcreteVariableString($var);
             $declarations[$concrete] = min(idx($declarations, $concrete, PHP_INT_MAX), $var->getOffset());
             $declaration_tokens[$var->getID()] = true;
         }
         foreach (array('n_STRING_SCALAR', 'n_HEREDOC') as $type) {
             foreach ($body->selectDescendantsOfType($type) as $string) {
                 foreach ($string->getStringVariables() as $offset => $var) {
                     $all[$string->getOffset() + $offset - 1] = '$' . $var;
                 }
             }
         }
         // Issue a warning for every variable token, unless it appears in a
         // declaration, we know about a prior declaration, we have explicitly
         // excluded it, or scope has been made unknowable before it appears.
         $issued_warnings = array();
         foreach ($all as $offset => $concrete) {
             if ($offset >= $scope_destroyed_at) {
                 // This appears after an extract() or $$var so we have no idea
                 // whether it's legitimate or not. We raised a harshly-worded warning
                 // when scope was made unknowable, so just ignore anything we can't
                 // figure out.
                 continue;
             }
             if ($offset >= idx($declarations, $concrete, PHP_INT_MAX)) {
                 // The use appears after the variable is declared, so it's fine.
                 continue;
             }
             if (!empty($issued_warnings[$concrete])) {
                 // We've already issued a warning for this variable so we don't need
                 // to issue another one.
                 continue;
             }
             $this->raiseLintAtOffset($offset, pht('Declare variables prior to use (even if you are passing them ' . 'as reference parameters). You may have misspelled this ' . 'variable name.'), $concrete);
             $issued_warnings[$concrete] = true;
         }
     }
 }
 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;
         }
     }
 }
 public function process(XHPASTNode $root)
 {
     $defs = $root->selectDescendantsOfTypes(array('n_FUNCTION_DECLARATION', 'n_METHOD_DECLARATION'));
     foreach ($defs as $def) {
         $body = $def->getChildByIndex(6);
         if ($body->getTypeName() === 'n_EMPTY') {
             // Abstract method declaration.
             continue;
         }
         $exclude = array();
         // Exclude uses of variables, unsets, and foreach loops
         // within closures - they are checked on their own
         $func_defs = $body->selectDescendantsOfType('n_FUNCTION_DECLARATION');
         foreach ($func_defs as $func_def) {
             $vars = $func_def->selectDescendantsOfType('n_VARIABLE');
             foreach ($vars as $var) {
                 $exclude[$var->getID()] = true;
             }
             $unset_lists = $func_def->selectDescendantsOfType('n_UNSET_LIST');
             foreach ($unset_lists as $unset_list) {
                 $exclude[$unset_list->getID()] = true;
             }
             $foreaches = $func_def->selectDescendantsOfType('n_FOREACH');
             foreach ($foreaches as $foreach) {
                 $exclude[$foreach->getID()] = true;
             }
         }
         // Find all variables that are unset within the scope
         $unset_vars = array();
         $unset_lists = $body->selectDescendantsOfType('n_UNSET_LIST');
         foreach ($unset_lists as $unset_list) {
             if (isset($exclude[$unset_list->getID()])) {
                 continue;
             }
             $unset_list_vars = $unset_list->selectDescendantsOfType('n_VARIABLE');
             foreach ($unset_list_vars as $var) {
                 $concrete = $this->getConcreteVariableString($var);
                 $unset_vars[$concrete][] = $var->getOffset();
                 $exclude[$var->getID()] = true;
             }
         }
         // Find all reference variables in foreach expressions
         $reference_vars = array();
         $foreaches = $body->selectDescendantsOfType('n_FOREACH');
         foreach ($foreaches as $foreach) {
             if (isset($exclude[$foreach->getID()])) {
                 continue;
             }
             $foreach_expr = $foreach->getChildOfType(0, 'n_FOREACH_EXPRESSION');
             $var = $foreach_expr->getChildByIndex(2);
             if ($var->getTypeName() !== 'n_VARIABLE_REFERENCE') {
                 continue;
             }
             $reference = $var->getChildByIndex(0);
             if ($reference->getTypeName() !== 'n_VARIABLE') {
                 continue;
             }
             $reference_name = $this->getConcreteVariableString($reference);
             $reference_vars[$reference_name][] = $reference->getOffset();
             $exclude[$reference->getID()] = true;
             // Exclude uses of the reference variable within the foreach loop
             $foreach_vars = $foreach->selectDescendantsOfType('n_VARIABLE');
             foreach ($foreach_vars as $var) {
                 $name = $this->getConcreteVariableString($var);
                 if ($name === $reference_name) {
                     $exclude[$var->getID()] = true;
                 }
             }
         }
         // Allow usage if the reference variable is assigned to another
         // reference variable
         $binary = $body->selectDescendantsOfType('n_BINARY_EXPRESSION');
         foreach ($binary as $expr) {
             if ($expr->getChildByIndex(1)->getConcreteString() !== '=') {
                 continue;
             }
             $lval = $expr->getChildByIndex(0);
             if ($lval->getTypeName() !== 'n_VARIABLE') {
                 continue;
             }
             $rval = $expr->getChildByIndex(2);
             if ($rval->getTypeName() !== 'n_VARIABLE_REFERENCE') {
                 continue;
             }
             // Counts as unsetting a variable
             $concrete = $this->getConcreteVariableString($lval);
             $unset_vars[$concrete][] = $lval->getOffset();
             $exclude[$lval->getID()] = true;
         }
         $all_vars = array();
         $all = $body->selectDescendantsOfType('n_VARIABLE');
         foreach ($all as $var) {
             if (isset($exclude[$var->getID()])) {
                 continue;
             }
             $name = $this->getConcreteVariableString($var);
             if (!isset($reference_vars[$name])) {
                 continue;
             }
             // Find the closest reference offset to this variable
             $reference_offset = null;
             foreach ($reference_vars[$name] as $offset) {
                 if ($offset < $var->getOffset()) {
                     $reference_offset = $offset;
                 } else {
                     break;
                 }
             }
             if (!$reference_offset) {
                 continue;
             }
             // Check if an unset exists between reference and usage of this
             // variable
             $warn = true;
             if (isset($unset_vars[$name])) {
                 foreach ($unset_vars[$name] as $unset_offset) {
                     if ($unset_offset > $reference_offset && $unset_offset < $var->getOffset()) {
                         $warn = false;
                         break;
                     }
                 }
             }
             if ($warn) {
                 $this->raiseLintAtNode($var, pht('This variable was used already as a by-reference iterator ' . 'variable. Such variables survive outside the `%s` loop, ' . 'do not reuse.', 'foreach'));
             }
         }
     }
 }
 public function process(XHPASTNode $root)
 {
     // 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
     // override us.
     $names = array();
     $classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
     foreach ($classes as $class) {
         $name_token = $class->getChildByIndex(1);
         $name_string = $name_token->getConcreteString();
         $names[] = array('class', $name_string, $name_token, ArcanistXHPASTLintNamingHook::isUpperCamelCase($name_string) ? null : pht('Follow naming conventions: classes should be named using `%s`.', 'UpperCamelCase'));
     }
     $ifaces = $root->selectDescendantsOfType('n_INTERFACE_DECLARATION');
     foreach ($ifaces as $iface) {
         $name_token = $iface->getChildByIndex(1);
         $name_string = $name_token->getConcreteString();
         $names[] = array('interface', $name_string, $name_token, ArcanistXHPASTLintNamingHook::isUpperCamelCase($name_string) ? null : pht('Follow naming conventions: interfaces should be named using `%s`.', 'UpperCamelCase'));
     }
     $functions = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION');
     foreach ($functions as $function) {
         $name_token = $function->getChildByIndex(2);
         if ($name_token->getTypeName() === 'n_EMPTY') {
             // Unnamed closure.
             continue;
         }
         $name_string = $name_token->getConcreteString();
         $names[] = array('function', $name_string, $name_token, ArcanistXHPASTLintNamingHook::isLowercaseWithUnderscores(ArcanistXHPASTLintNamingHook::stripPHPFunction($name_string)) ? null : pht('Follow naming conventions: functions should be named using `%s`.', 'lowercase_with_underscores'));
     }
     $methods = $root->selectDescendantsOfType('n_METHOD_DECLARATION');
     foreach ($methods as $method) {
         $name_token = $method->getChildByIndex(2);
         $name_string = $name_token->getConcreteString();
         $names[] = array('method', $name_string, $name_token, ArcanistXHPASTLintNamingHook::isLowerCamelCase(ArcanistXHPASTLintNamingHook::stripPHPFunction($name_string)) ? null : pht('Follow naming conventions: methods should be named using `%s`.', 'lowerCamelCase'));
     }
     $param_tokens = array();
     $params = $root->selectDescendantsOfType('n_DECLARATION_PARAMETER_LIST');
     foreach ($params as $param_list) {
         foreach ($param_list->getChildren() as $param) {
             $name_token = $param->getChildByIndex(1);
             if ($name_token->getTypeName() === 'n_VARIABLE_REFERENCE') {
                 $name_token = $name_token->getChildOfType(0, 'n_VARIABLE');
             }
             $param_tokens[$name_token->getID()] = true;
             $name_string = $name_token->getConcreteString();
             $names[] = array('parameter', $name_string, $name_token, ArcanistXHPASTLintNamingHook::isLowercaseWithUnderscores(ArcanistXHPASTLintNamingHook::stripPHPVariable($name_string)) ? null : pht('Follow naming conventions: parameters ' . 'should be named using `%s`', 'lowercase_with_underscores'));
         }
     }
     $constants = $root->selectDescendantsOfType('n_CLASS_CONSTANT_DECLARATION_LIST');
     foreach ($constants as $constant_list) {
         foreach ($constant_list->getChildren() as $constant) {
             $name_token = $constant->getChildByIndex(0);
             $name_string = $name_token->getConcreteString();
             $names[] = array('constant', $name_string, $name_token, ArcanistXHPASTLintNamingHook::isUppercaseWithUnderscores($name_string) ? null : pht('Follow naming conventions: class constants ' . 'should be named using `%s`', 'UPPERCASE_WITH_UNDERSCORES'));
         }
     }
     $member_tokens = array();
     $props = $root->selectDescendantsOfType('n_CLASS_MEMBER_DECLARATION_LIST');
     foreach ($props as $prop_list) {
         foreach ($prop_list->getChildren() as $token_id => $prop) {
             if ($prop->getTypeName() === 'n_CLASS_MEMBER_MODIFIER_LIST') {
                 continue;
             }
             $name_token = $prop->getChildByIndex(0);
             $member_tokens[$name_token->getID()] = true;
             $name_string = $name_token->getConcreteString();
             $names[] = array('member', $name_string, $name_token, ArcanistXHPASTLintNamingHook::isLowerCamelCase(ArcanistXHPASTLintNamingHook::stripPHPVariable($name_string)) ? null : pht('Follow naming conventions: class properties ' . 'should be named using `%s`.', 'lowerCamelCase'));
         }
     }
     $superglobal_map = array_fill_keys($this->getSuperGlobalNames(), true);
     $defs = $root->selectDescendantsOfTypes(array('n_FUNCTION_DECLARATION', 'n_METHOD_DECLARATION'));
     foreach ($defs as $def) {
         $globals = $def->selectDescendantsOfType('n_GLOBAL_DECLARATION_LIST');
         $globals = $globals->selectDescendantsOfType('n_VARIABLE');
         $globals_map = array();
         foreach ($globals as $global) {
             $global_string = $global->getConcreteString();
             $globals_map[$global_string] = true;
             $names[] = array('user', $global_string, $global, null);
         }
         // Exclude access of static properties, since lint will be raised at
         // their declaration if they're invalid and they may not conform to
         // variable rules. This is slightly overbroad (includes the entire
         // RHS of a "Class::..." token) to cover cases like "Class:$x[0]". These
         // variables are simply made exempt from naming conventions.
         $exclude_tokens = array();
         $statics = $def->selectDescendantsOfType('n_CLASS_STATIC_ACCESS');
         foreach ($statics as $static) {
             $rhs = $static->getChildByIndex(1);
             if ($rhs->getTypeName() == 'n_VARIABLE') {
                 $exclude_tokens[$rhs->getID()] = true;
             } else {
                 $rhs_vars = $rhs->selectDescendantsOfType('n_VARIABLE');
                 foreach ($rhs_vars as $var) {
                     $exclude_tokens[$var->getID()] = true;
                 }
             }
         }
         $vars = $def->selectDescendantsOfType('n_VARIABLE');
         foreach ($vars as $token_id => $var) {
             if (isset($member_tokens[$token_id])) {
                 continue;
             }
             if (isset($param_tokens[$token_id])) {
                 continue;
             }
             if (isset($exclude_tokens[$token_id])) {
                 continue;
             }
             $var_string = $var->getConcreteString();
             // Awkward artifact of "$o->{$x}".
             $var_string = trim($var_string, '{}');
             if (isset($superglobal_map[$var_string])) {
                 continue;
             }
             if (isset($globals_map[$var_string])) {
                 continue;
             }
             $names[] = array('variable', $var_string, $var, ArcanistXHPASTLintNamingHook::isLowercaseWithUnderscores(ArcanistXHPASTLintNamingHook::stripPHPVariable($var_string)) ? null : pht('Follow naming conventions: variables ' . 'should be named using `%s`.', 'lowercase_with_underscores'));
         }
     }
     // If a naming hook is configured, give it a chance to override the
     // default results for all the symbol names.
     $hook_class = $this->naminghook;
     if ($hook_class) {
         $hook_obj = newv($hook_class, array());
         foreach ($names as $k => $name_attrs) {
             list($type, $name, $token, $default) = $name_attrs;
             $result = $hook_obj->lintSymbolName($type, $name, $default);
             $names[$k][3] = $result;
         }
     }
     // Raise anything we're left with.
     foreach ($names as $k => $name_attrs) {
         list($type, $name, $token, $result) = $name_attrs;
         if ($result) {
             $this->raiseLintAtNode($token, $result);
         }
     }
     // Lint constant declarations.
     $defines = $this->getFunctionCalls($root, array('define'))->add($root->selectDescendantsOfTypes(array('n_CLASS_CONSTANT_DECLARATION', 'n_CONSTANT_DECLARATION')));
     foreach ($defines as $define) {
         switch ($define->getTypeName()) {
             case 'n_CLASS_CONSTANT_DECLARATION':
             case 'n_CONSTANT_DECLARATION':
                 $constant = $define->getChildByIndex(0);
                 if ($constant->getTypeName() !== 'n_STRING') {
                     $constant = null;
                 }
                 break;
             case 'n_FUNCTION_CALL':
                 $constant = $define->getChildOfType(1, 'n_CALL_PARAMETER_LIST')->getChildByIndex(0);
                 if ($constant->getTypeName() !== 'n_STRING_SCALAR') {
                     $constant = null;
                 }
                 break;
             default:
                 $constant = null;
                 break;
         }
         if (!$constant) {
             continue;
         }
         $constant_name = $constant->getConcreteString();
         if ($constant_name !== strtoupper($constant_name)) {
             $this->raiseLintAtNode($constant, pht('Constants should be uppercase.'));
         }
     }
 }
 public function process(XHPASTNode $root)
 {
     $defs = $root->selectDescendantsOfTypes(array('n_FUNCTION_DECLARATION', 'n_METHOD_DECLARATION'));
     foreach ($defs as $def) {
         // We keep track of the first offset where scope becomes unknowable, and
         // silence any warnings after that. Default it to INT_MAX so we can min()
         // it later to keep track of the first problem we encounter.
         $scope_destroyed_at = PHP_INT_MAX;
         $declarations = array('$this' => 0) + array_fill_keys($this->getSuperGlobalNames(), 0);
         $declaration_tokens = array();
         $exclude_tokens = array();
         $vars = array();
         // First up, find all the different kinds of declarations, as explained
         // above. Put the tokens into the $vars array.
         $param_list = $def->getChildOfType(3, 'n_DECLARATION_PARAMETER_LIST');
         $param_vars = $param_list->selectDescendantsOfType('n_VARIABLE');
         foreach ($param_vars as $var) {
             $vars[] = $var;
         }
         // This is PHP5.3 closure syntax: function () use ($x) {};
         $lexical_vars = $def->getChildByIndex(4)->selectDescendantsOfType('n_VARIABLE');
         foreach ($lexical_vars as $var) {
             $vars[] = $var;
         }
         $body = $def->getChildByIndex(5);
         if ($body->getTypeName() === 'n_EMPTY') {
             // Abstract method declaration.
             continue;
         }
         $static_vars = $body->selectDescendantsOfType('n_STATIC_DECLARATION')->selectDescendantsOfType('n_VARIABLE');
         foreach ($static_vars as $var) {
             $vars[] = $var;
         }
         $global_vars = $body->selectDescendantsOfType('n_GLOBAL_DECLARATION_LIST');
         foreach ($global_vars as $var_list) {
             foreach ($var_list->getChildren() as $var) {
                 if ($var->getTypeName() === 'n_VARIABLE') {
                     $vars[] = $var;
                 } else {
                     // Dynamic global variable, i.e. "global $$x;".
                     $scope_destroyed_at = min($scope_destroyed_at, $var->getOffset());
                     // An error is raised elsewhere, no need to raise here.
                 }
             }
         }
         // Include "catch (Exception $ex)", but not variables in the body of the
         // catch block.
         $catches = $body->selectDescendantsOfType('n_CATCH');
         foreach ($catches as $catch) {
             $vars[] = $catch->getChildOfType(1, 'n_VARIABLE');
         }
         $binary = $body->selectDescendantsOfType('n_BINARY_EXPRESSION');
         foreach ($binary as $expr) {
             if ($expr->getChildByIndex(1)->getConcreteString() !== '=') {
                 continue;
             }
             $lval = $expr->getChildByIndex(0);
             if ($lval->getTypeName() === 'n_VARIABLE') {
                 $vars[] = $lval;
             } else {
                 if ($lval->getTypeName() === 'n_LIST') {
                     // Recursivey grab everything out of list(), since the grammar
                     // permits list() to be nested. Also note that list() is ONLY valid
                     // as an lval assignments, so we could safely lift this out of the
                     // n_BINARY_EXPRESSION branch.
                     $assign_vars = $lval->selectDescendantsOfType('n_VARIABLE');
                     foreach ($assign_vars as $var) {
                         $vars[] = $var;
                     }
                 }
             }
             if ($lval->getTypeName() === 'n_VARIABLE_VARIABLE') {
                 $scope_destroyed_at = min($scope_destroyed_at, $lval->getOffset());
                 // No need to raise here since we raise an error elsewhere.
             }
         }
         $calls = $body->selectDescendantsOfType('n_FUNCTION_CALL');
         foreach ($calls as $call) {
             $name = strtolower($call->getChildByIndex(0)->getConcreteString());
             if ($name === 'empty' || $name === 'isset') {
                 $params = $call->getChildOfType(1, 'n_CALL_PARAMETER_LIST')->selectDescendantsOfType('n_VARIABLE');
                 foreach ($params as $var) {
                     $exclude_tokens[$var->getID()] = true;
                 }
                 continue;
             }
             if ($name !== 'extract') {
                 continue;
             }
             $scope_destroyed_at = min($scope_destroyed_at, $call->getOffset());
         }
         // Now we have every declaration except foreach(), handled below. Build
         // two maps, one which just keeps track of which tokens are part of
         // declarations ($declaration_tokens) and one which has the first offset
         // where a variable is declared ($declarations).
         foreach ($vars as $var) {
             $concrete = $this->getConcreteVariableString($var);
             $declarations[$concrete] = min(idx($declarations, $concrete, PHP_INT_MAX), $var->getOffset());
             $declaration_tokens[$var->getID()] = true;
         }
         // Excluded tokens are ones we don't "count" as being used, described
         // above. Put them into $exclude_tokens.
         $class_statics = $body->selectDescendantsOfType('n_CLASS_STATIC_ACCESS');
         $class_static_vars = $class_statics->selectDescendantsOfType('n_VARIABLE');
         foreach ($class_static_vars as $var) {
             $exclude_tokens[$var->getID()] = true;
         }
         // Find all the variables in scope, and figure out where they are used.
         // We want to find foreach() iterators which are both declared before and
         // used after the foreach() loop.
         $uses = array();
         $all_vars = $body->selectDescendantsOfType('n_VARIABLE');
         $all = array();
         // NOTE: $all_vars is not a real array so we can't unset() it.
         foreach ($all_vars as $var) {
             // Be strict since it's easier; we don't let you reuse an iterator you
             // declared before a loop after the loop, even if you're just assigning
             // to it.
             $concrete = $this->getConcreteVariableString($var);
             $uses[$concrete][$var->getID()] = $var->getOffset();
             if (isset($declaration_tokens[$var->getID()])) {
                 // We know this is part of a declaration, so it's fine.
                 continue;
             }
             if (isset($exclude_tokens[$var->getID()])) {
                 // We know this is part of isset() or similar, so it's fine.
                 continue;
             }
             $all[$var->getOffset()] = $concrete;
         }
         // Do foreach() last, we want to handle implicit redeclaration of a
         // variable already in scope since this probably means we're ovewriting a
         // local.
         // NOTE: Processing foreach expressions in order allows programs which
         // reuse iterator variables in other foreach() loops -- this is fine. We
         // have a separate warning to prevent nested loops from reusing the same
         // iterators.
         $foreaches = $body->selectDescendantsOfType('n_FOREACH');
         $all_foreach_vars = array();
         foreach ($foreaches as $foreach) {
             $foreach_expr = $foreach->getChildOfType(0, 'n_FOREACH_EXPRESSION');
             $foreach_vars = array();
             // Determine the end of the foreach() loop.
             $foreach_tokens = $foreach->getTokens();
             $last_token = end($foreach_tokens);
             $foreach_end = $last_token->getOffset();
             $key_var = $foreach_expr->getChildByIndex(1);
             if ($key_var->getTypeName() === 'n_VARIABLE') {
                 $foreach_vars[] = $key_var;
             }
             $value_var = $foreach_expr->getChildByIndex(2);
             if ($value_var->getTypeName() === 'n_VARIABLE') {
                 $foreach_vars[] = $value_var;
             } else {
                 // The root-level token may be a reference, as in:
                 //    foreach ($a as $b => &$c) { ... }
                 // Reach into the n_VARIABLE_REFERENCE node to grab the n_VARIABLE
                 // node.
                 $var = $value_var->getChildByIndex(0);
                 if ($var->getTypeName() === 'n_VARIABLE_VARIABLE') {
                     $var = $var->getChildByIndex(0);
                 }
                 $foreach_vars[] = $var;
             }
             // Remove all uses of the iterators inside of the foreach() loop from
             // the $uses map.
             foreach ($foreach_vars as $var) {
                 $concrete = $this->getConcreteVariableString($var);
                 $offset = $var->getOffset();
                 foreach ($uses[$concrete] as $id => $use_offset) {
                     if ($use_offset >= $offset && $use_offset < $foreach_end) {
                         unset($uses[$concrete][$id]);
                     }
                 }
                 $all_foreach_vars[] = $var;
             }
         }
         foreach ($all_foreach_vars as $var) {
             $concrete = $this->getConcreteVariableString($var);
             $offset = $var->getOffset();
             // If a variable was declared before a foreach() and is used after
             // it, raise a message.
             if (isset($declarations[$concrete])) {
                 if ($declarations[$concrete] < $offset) {
                     if (!empty($uses[$concrete]) && max($uses[$concrete]) > $offset) {
                         $message = $this->raiseLintAtNode($var, pht('This iterator variable is a previously declared local ' . 'variable. To avoid overwriting locals, do not reuse them ' . 'as iterator variables.'));
                         $message->setOtherLocations(array($this->getOtherLocation($declarations[$concrete]), $this->getOtherLocation(max($uses[$concrete]))));
                     }
                 }
             }
             // This is a declaration, exclude it from the "declare variables prior
             // to use" check below.
             unset($all[$var->getOffset()]);
             $vars[] = $var;
         }
     }
 }