/** * @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; }
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; }
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; }