Viewing file: CodeCleaner.php (6.19 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php
/* * This file is part of Psy Shell. * * (c) 2012-2015 Justin Hileman * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */
namespace Psy;
use PhpParser\NodeTraverser; use PhpParser\Parser; use PhpParser\PrettyPrinter\Standard as Printer; use Psy\CodeCleaner\AbstractClassPass; use Psy\CodeCleaner\AssignThisVariablePass; use Psy\CodeCleaner\CalledClassPass; use Psy\CodeCleaner\CallTimePassByReferencePass; use Psy\CodeCleaner\ExitPass; use Psy\CodeCleaner\FunctionReturnInWriteContextPass; use Psy\CodeCleaner\ImplicitReturnPass; use Psy\CodeCleaner\InstanceOfPass; use Psy\CodeCleaner\LeavePsyshAlonePass; use Psy\CodeCleaner\LegacyEmptyPass; use Psy\CodeCleaner\MagicConstantsPass; use Psy\CodeCleaner\NamespacePass; use Psy\CodeCleaner\StaticConstructorPass; use Psy\CodeCleaner\StrictTypesPass; use Psy\CodeCleaner\UseStatementPass; use Psy\CodeCleaner\ValidClassNamePass; use Psy\CodeCleaner\ValidConstantPass; use Psy\CodeCleaner\ValidFunctionNamePass; use Psy\Exception\ParseErrorException;
/** * A service to clean up user input, detect parse errors before they happen, * and generally work around issues with the PHP code evaluation experience. */ class CodeCleaner { private $parser; private $printer; private $traverser; private $namespace;
/** * CodeCleaner constructor. * * @param Parser $parser A PhpParser Parser instance. One will be created if not explicitly supplied. * @param Printer $printer A PhpParser Printer instance. One will be created if not explicitly supplied. * @param NodeTraverser $traverser A PhpParser NodeTraverser instance. One will be created if not explicitly supplied. */ public function __construct(Parser $parser = null, Printer $printer = null, NodeTraverser $traverser = null) { if ($parser === null) { $parserFactory = new ParserFactory(); $parser = $parserFactory->createParser(); }
$this->parser = $parser; $this->printer = $printer ?: new Printer(); $this->traverser = $traverser ?: new NodeTraverser();
foreach ($this->getDefaultPasses() as $pass) { $this->traverser->addVisitor($pass); } }
/** * Get default CodeCleaner passes. * * @return array */ private function getDefaultPasses() { return array( new AbstractClassPass(), new AssignThisVariablePass(), new FunctionReturnInWriteContextPass(), new CallTimePassByReferencePass(), new CalledClassPass(), new InstanceOfPass(), new LeavePsyshAlonePass(), new LegacyEmptyPass(), new ImplicitReturnPass(), new UseStatementPass(), // must run before namespace and validation passes new NamespacePass($this), // must run after the implicit return pass new StrictTypesPass(), new StaticConstructorPass(), new ValidFunctionNamePass(), new ValidClassNamePass(), new ValidConstantPass(), new MagicConstantsPass(), new ExitPass(), ); }
/** * Clean the given array of code. * * @throws ParseErrorException if the code is invalid PHP, and cannot be coerced into valid PHP. * * @param array $codeLines * @param bool $requireSemicolons * * @return string|false Cleaned PHP code, False if the input is incomplete. */ public function clean(array $codeLines, $requireSemicolons = false) { $stmts = $this->parse('<?php ' . implode(PHP_EOL, $codeLines) . PHP_EOL, $requireSemicolons); if ($stmts === false) { return false; }
// Catch fatal errors before they happen $stmts = $this->traverser->traverse($stmts);
return $this->printer->prettyPrint($stmts); }
/** * Set the current local namespace. * * @param null|array $namespace (default: null) * * @return null|array */ public function setNamespace(array $namespace = null) { $this->namespace = $namespace; }
/** * Get the current local namespace. * * @return null|array */ public function getNamespace() { return $this->namespace; }
/** * Lex and parse a block of code. * * @see Parser::parse * * @param string $code * @param bool $requireSemicolons * * @return array A set of statements */ protected function parse($code, $requireSemicolons = false) { try { return $this->parser->parse($code); } catch (\PhpParser\Error $e) { if ($this->parseErrorIsUnclosedString($e, $code)) { return false; }
if (!$this->parseErrorIsEOF($e)) { throw ParseErrorException::fromParseError($e); }
if ($requireSemicolons) { return false; }
try { // Unexpected EOF, try again with an implicit semicolon return $this->parser->parse($code . ';'); } catch (\PhpParser\Error $e) { return false; } } }
private function parseErrorIsEOF(\PhpParser\Error $e) { $msg = $e->getRawMessage();
return ($msg === 'Unexpected token EOF') || (strpos($msg, 'Syntax error, unexpected EOF') !== false); }
/** * A special test for unclosed single-quoted strings. * * Unlike (all?) other unclosed statements, single quoted strings have * their own special beautiful snowflake syntax error just for * themselves. * * @param \PhpParser\Error $e * @param string $code * * @return bool */ private function parseErrorIsUnclosedString(\PhpParser\Error $e, $code) { if ($e->getRawMessage() !== 'Syntax error, unexpected T_ENCAPSED_AND_WHITESPACE') { return false; }
try { $this->parser->parse($code . "';"); } catch (\Exception $e) { return false; }
return true; } }
|