In some occasions (like during anonymous classes), we actually traverse a part of the subtree manually so we need to "store" the original global variables, and use new global variables for that traversal. Once completed, we return to the original global variables.
 /**
  * @param array $nodes
  * @return \PhpParser\Node[]
  */
 public function traverse(array $nodes)
 {
     NodeStateStack::getInstance()->pushVars();
     $ret = parent::traverse($nodes);
     NodeStateStack::getInstance()->popVars();
     return $ret;
 }
 public function leaveNode(Node $node)
 {
     if ($node instanceof ClassLike) {
         NodeStateStack::getInstance()->pop('currentClass');
     }
     if (!$node instanceof Node\FunctionLike) {
         return;
     }
     $functionNode = NodeStateStack::getInstance()->end('currentFunction');
     $functionNode = $functionNode['node'];
     NodeStateStack::getInstance()->pop('currentFunction');
     // Remove return type if set
     if ($node->returnType) {
         $node->returnType = null;
     }
     // Remove scalar types and store for later
     $params = array();
     foreach ($node->params as $param) {
         if (in_array($param->type, array('string', 'int', 'float', 'bool'))) {
             $canBeNull = false;
             if ($param->default != null) {
                 if ($param->default instanceof Node\Expr\ConstFetch) {
                     if ($param->default->name->parts[0] == "null") {
                         $canBeNull = true;
                     }
                 }
             }
             $params[] = array('type' => $param->type, 'arg' => $param->name, 'func' => $node->name, 'nullable' => $canBeNull);
             $param->type = null;
         }
     }
     // Don't add checks when we don't enforce strict
     if (!NodeStateStack::getInstance()->get('isStrict')) {
         return null;
     }
     // No typehinting on abstract
     if ($functionNode instanceof Node\Stmt\ClassMethod && $functionNode->isAbstract()) {
         return null;
     }
     // No typehinting on interfaces
     if (NodeStateStack::getInstance()->end('currentClass') instanceof Interface_) {
         return null;
     }
     // Add code for checking scalar types
     foreach (array_reverse($params) as $param) {
         $code = sprintf('<?php if (! is_%s($%s) %s) { throw new \\InvalidArgumentException("Argument \\$%s passed to %s() must be of the type %s, ".(gettype($%s) == "object" ? get_class($%s) : gettype($%s))." given"); }', $param['type'], $param['arg'], $param['nullable'] ? 'and ! is_null($' . $param['arg'] . ')' : "", $param['arg'], $param['func'], $param['type'], $param['arg'], $param['arg'], $param['arg'], $param['arg']);
         $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
         $stmts = $parser->parse($code);
         if (!$node->stmts) {
             $node->stmts = $stmts;
         } else {
             $node->stmts = array_merge($stmts, $node->stmts);
         }
     }
 }
 public function leaveNode(Node $node)
 {
     if ($node instanceof Node\Expr\Yield_) {
         $functionNode = NodeStateStack::getInstance()->pop('currentFunction');
         $functionNode['generator'] = true;
         NodeStateStack::getInstance()->push('currentFunction', $functionNode);
         return null;
     }
     if ($node instanceof Node\Stmt\Return_) {
         $functionNode = NodeStateStack::getInstance()->end('currentFunction');
         if ($functionNode['generator']) {
             throw new TranspileException("Cannot transpile return statements from generators");
         }
     }
     return null;
 }
Beispiel #4
0
 public function leaveNode(Node $node)
 {
     if (!$node instanceof Node\Stmt\Declare_) {
         return null;
     }
     foreach ($node->declares as $idx => $declare) {
         if ($declare->key != 'strict_types') {
             continue;
         }
         // Set global strict value so others know what to do with scalar typehinting and return types.
         NodeStateStack::getInstance()->set('isStrict', $declare->value->value == 1);
         // Remove the strict_type declare
         return NodeTraverser::REMOVE_NODE;
     }
     return null;
 }
Beispiel #5
0
 public function leaveNode(Node $node)
 {
     if (!$node instanceof Node\Stmt\Return_) {
         return null;
     }
     $functionNode = NodeStateStack::getInstance()->end('currentFunction');
     // No functionNode means we are doing a return in the global scope
     if (!$functionNode) {
         return null;
     }
     $functionNode = $functionNode['node'];
     // Check return type of current function
     if ($functionNode->returnType == null) {
         return null;
     }
     // Not strict, so no need to check return type;
     if (!NodeStateStack::getInstance()->get('isStrict')) {
         return null;
     }
     // Define uniq retvar for returning, most likely not needed but done to make sure we don't
     // hit any existing variables or multiple return vars
     $retVar = 'ret' . uniqid(true);
     // Generate code for "$retVar = <originalExpression>"
     $retNode = new Node\Expr\Assign(new Node\Expr\Variable($retVar), $node->expr);
     // Generate remainder code
     $returnType = (string) $functionNode->returnType;
     // Manually add starting namespace separator for FQCN
     if ($functionNode->returnType instanceof Node\Name\FullyQualified && $returnType[0] != '\\') {
         $returnType = '\\' . $returnType;
     }
     // @TODO: It might be easier to read when we generate ALL code directly from Nodes instead of generating it
     if (in_array($returnType, array('string', 'bool', 'int', 'float', 'array'))) {
         // Scalars are treated a bit different
         $code = sprintf('<?php ' . "\n" . '  if (! is_%s($' . $retVar . ')) { ' . "\n" . '    throw new \\InvalidArgumentException("Argument returned must be of the type %s, ".gettype($' . $retVar . ')." given"); ' . "\n" . '  } ' . "\n" . '  return $' . $retVar . '; ', $returnType, $returnType);
     } else {
         // Otherwise use is_a for check against classes
         $code = sprintf('<?php ' . "\n" . '  if (! $' . $retVar . ' instanceof %s) { ' . "\n" . '    throw new \\InvalidArgumentException("Argument returned must be of the type %s, ".(gettype($' . $retVar . ') == "object" ? get_class($' . $retVar . ') : gettype($' . $retVar . '))." given"); ' . "\n" . '  } ' . "\n" . '  return $' . $retVar . '; ', $returnType, $returnType);
     }
     $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
     $stmts = $parser->parse($code);
     // Merge $retVar = <expr> with remainder code
     $stmts = array_merge(array($retNode), $stmts);
     return $stmts;
 }
 /**
  * Add final anonymous classes to the statements
  *
  * @param array $nodes
  * @return array
  */
 public function afterTraverse(array $nodes)
 {
     // Nothing to do when there are no anonymous classes
     if (NodeStateStack::getInstance()->count('anonClasses') == 0) {
         return $nodes;
     }
     // We must transpile anonymous classes first, as we haven't done this yet
     $traverser = Transpile::getTraverser();
     $anonClassStmts = $traverser->traverse(NodeStateStack::getInstance()->get('anonClasses'));
     // Find hook point for anonymous classes, which must be after declare, namespaces and use-statements, and can
     // before anything else. This might cause issues when anonymous classes implement interfaces that are defined
     // later on.
     $idx = $this->getAnonymousClassHookIndex($nodes);
     // Array_merge the anonymous class statements on the correct position
     $preStmts = array_slice($nodes, 0, $idx);
     $postStmts = array_slice($nodes, $idx);
     $nodes = array_merge($preStmts, $anonClassStmts, $postStmts);
     return $nodes;
 }