├── .gitattributes ├── LICENSE.md ├── README.md ├── bin ├── php2zephir └── php2zephir.php ├── box.json ├── composer.json ├── docker-compose.yml ├── phpcs.xml.dist ├── src ├── Console │ └── Command │ │ ├── AbstractCommand.php │ │ ├── Prototype.php │ │ └── Zep.php ├── Exception │ ├── CouldNotCreateDirectoryException.php │ ├── CouldNotWriteFileException.php │ ├── FileNotExistsException.php │ ├── PhpToZephirException.php │ └── RuntimeException.php ├── PhpParser │ └── NodeVisitor │ │ ├── ArrayDestructuring.php │ │ ├── InitLocalVariable.php │ │ ├── IssetSplitter.php │ │ ├── RemoveUseFunction.php │ │ ├── SortNamespaceClasses.php │ │ └── UnsetSplitter.php └── ZephirPrinter.php └── test ├── Mock ├── ClassConstants.php ├── ClassMethods.php ├── ClassMethodsReturnTypes.php ├── ClassProperty.php ├── ClassReturnArray.php └── MyClass.php ├── PhpParser └── NodeVisitor │ └── SortNamespaceClassesTest.php ├── Zephir ├── ClassConstants.zep ├── ClassMethods.zep ├── ClassMethodsReturnTypes.zep ├── ClassProperty.zep └── ClassReturnArray.zep └── ZephirPrinterTest.php /.gitattributes: -------------------------------------------------------------------------------- 1 | /docs export-ignore 2 | /examples export-ignore 3 | /tests export-ignore 4 | .coveralls.yml export-ignore 5 | .docheader export-ignore 6 | .gitignore export-ignore 7 | .php_cs export-ignore 8 | .travis.yml export-ignore 9 | phpunit.xml.dist export-ignore 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | New BSD License 2 | =============== 3 | 4 | Copyright (c) 2018, Sandro Keil 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | * Neither the names of the copyright holders nor the names of its 16 | contributors may be used to endorse or promote products derived from this 17 | software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP to Zephir 2 | 3 | [![Build Status](https://travis-ci.org/sandrokeil/php-to-zephir.svg?branch=master)](https://travis-ci.org/sandrokeil/php-to-zephir) 4 | [![Coverage Status](https://coveralls.io/repos/sandrokeil/php-to-zephir/badge.svg?branch=master&service=github)](https://coveralls.io/github/sandrokeil/php-to-zephir?branch=master) 5 | 6 | Converts PHP 7 files to [Zephir](https://zephir-lang.com/en) zep files 7 | and can create Zephir prototype files of external used libraries. 8 | 9 | ## Requirements 10 | 11 | - PHP >= 7.1 12 | 13 | ## Installation 14 | 15 | ``` 16 | $ composer require --dev sandrokeil/php-to-zephir 17 | ``` 18 | 19 | ## Usage 20 | To create Zephir zep files of your PHP files run: 21 | 22 | ``` 23 | $ bin/php2zephir php2zephir:zep:create [source path/file] [destination path/file] 24 | ``` 25 | 26 | To create Zephir prototypes for external libraries run: 27 | 28 | ``` 29 | $ bin/php2zephir php2zephir:prototype:create [source path/file] [destination file prototype.php] 30 | ``` 31 | 32 | ## Create PHAR 33 | A PHAR file can be generated with [box](https://github.com/humbug/box). 34 | 35 | ``` 36 | $ php box.phar compile 37 | ``` 38 | 39 | ## Unit Tests 40 | 41 | ``` 42 | $ docker-compose run --rm php vendor/bin/phpunit 43 | ``` 44 | 45 | ## Zephir Docker Image 46 | 47 | If you want to compile your Zephir zep files for PHP Alpine 3.8 Docker images try my [Zephir Docker images](https://github.com/sandrokeil/docker-files/tree/master/zephir). 48 | 49 | ## Limitations 50 | 51 | - Can not handle reserved variable names like `$resource`, it would result in compilation error -------------------------------------------------------------------------------- /bin/php2zephir: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | ')) { 13 | fwrite( 14 | STDERR, 15 | 'This version of php2zephir requires PHP >= 7.1; using the latest version of PHP is highly recommended.' . PHP_EOL 16 | ); 17 | 18 | die(1); 19 | } 20 | 21 | if (!ini_get('date.timezone')) { 22 | ini_set('date.timezone', 'UTC'); 23 | } 24 | 25 | foreach ( 26 | [ 27 | __DIR__ . '/../../../autoload.php', 28 | __DIR__ . '/../../autoload.php', 29 | __DIR__ . '/../vendor/autoload.php', 30 | __DIR__ . '/vendor/autoload.php', 31 | ] as $file 32 | ) { 33 | if (file_exists($file)) { 34 | define('PHP2ZEHPIR_COMPOSER_INSTALL', $file); 35 | break; 36 | } 37 | } 38 | 39 | unset($file); 40 | 41 | if (!defined('PHP2ZEHPIR_COMPOSER_INSTALL')) { 42 | fwrite(STDERR, 43 | 'You need to set up the project dependencies using the following commands:' . PHP_EOL . 44 | 'wget http://getcomposer.org/composer.phar' . PHP_EOL . 45 | 'php composer.phar install' . PHP_EOL 46 | ); 47 | 48 | die(1); 49 | } 50 | 51 | require PHP2ZEHPIR_COMPOSER_INSTALL; 52 | 53 | use Symfony\Component\Console\Application; 54 | use PhpToZephir\Console\Command; 55 | 56 | $description = <<addCommands( 68 | [ 69 | new Command\Zep(), 70 | new Command\Prototype(), 71 | ] 72 | ); 73 | 74 | $application->run(); -------------------------------------------------------------------------------- /box.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "bin/php2zephir.php", 3 | "compression": "GZ", 4 | "compactors": [ 5 | "KevinGH\\Box\\Compactor\\Php" 6 | ], 7 | "directories": [ 8 | "src/" 9 | ], 10 | "files": [ 11 | "LICENSE.md", 12 | "bin/php2zephir.php" 13 | ], 14 | "finder": [ 15 | { 16 | "notName": "/LICENSE|.*\\.md|.*\\.dist|Makefile|composer\\.json|composer\\.lock/", 17 | "exclude": [ 18 | "doc", 19 | "test", 20 | "test_old", 21 | "tests", 22 | "Tests", 23 | "vendor-bin" 24 | ], 25 | "in": "vendor" 26 | }, 27 | { 28 | "name": "composer.json", 29 | "in": "." 30 | } 31 | ], 32 | "intercept": true, 33 | "metadata": "Converts PHP 7 files to Zephir zep files", 34 | "output": "build/php2zephir.phar" 35 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sandrokeil/php-to-zephir", 3 | "description": "Convert PHP 7 files to Zephir zep files", 4 | "type": "library", 5 | "license": "BSD-3-Clause", 6 | "keywords": [ 7 | "php", 8 | "extension", 9 | "converter", 10 | "zephir", 11 | "zep" 12 | ], 13 | "authors": [ 14 | { 15 | "name": "Sandro Keil", 16 | "homepage": "https://sandro-keil.de", 17 | "role": "maintainer" 18 | } 19 | ], 20 | "minimum-stability": "dev", 21 | "prefer-stable": true, 22 | "config": { 23 | "sort-packages": true 24 | }, 25 | "require": { 26 | "php": "^7.1", 27 | "nikic/php-parser": "^4.1", 28 | "symfony/console": "^2.7 || ^3.0 || ^4.0" 29 | }, 30 | "require-dev": { 31 | "malukenho/docheader": "^0.1.7", 32 | "phpunit/phpunit": "^7.4", 33 | "roave/security-advisories": "dev-master", 34 | "squizlabs/php_codesniffer": "^2.9.1" 35 | }, 36 | "autoload": { 37 | "psr-4": { 38 | "PhpToZephir\\": "src/" 39 | } 40 | }, 41 | "autoload-dev": { 42 | "psr-4": { 43 | "PhpToZephirTest\\": "test/" 44 | } 45 | }, 46 | "scripts": { 47 | "check": [ 48 | "@cs-check", 49 | "@docheader", 50 | "@test" 51 | ], 52 | "cs-check": "phpcs", 53 | "cs-fix": "phpcbf", 54 | "test": "phpunit --colors=always", 55 | "test-coverage": "phpunit --colors=always --coverage-clover clover.xml", 56 | "docheader": "docheader check src/ test/" 57 | }, 58 | "bin": [ 59 | "bin/php2zephir" 60 | ], 61 | "archive": { 62 | "exclude": [ 63 | ".coveralls.yml", 64 | ".travis.yml", 65 | "build", 66 | "phpunit.xml*", 67 | "test" 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | # To run tests docker-compose run --rm php vendor/bin/phpunit 4 | php: 5 | image: prooph/php:7.2-cli-xdebug 6 | environment: 7 | PHP_IDE_CONFIG: "serverName=application" 8 | XDEBUG_CONFIG: "remote_host=phpstorm" 9 | volumes: 10 | - "./:/app" 11 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | PHP to Zephir coding standard 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | src 28 | 29 | -------------------------------------------------------------------------------- /src/Console/Command/AbstractCommand.php: -------------------------------------------------------------------------------- 1 | from = $input->getArgument('from'); 44 | $this->to = $input->getArgument('to'); 45 | $this->isDir = is_dir($this->from); 46 | 47 | if (! file_exists($this->from)) { 48 | throw FileNotExistsException::forFile($this->from); 49 | } 50 | $this->from = realpath($this->from); 51 | 52 | $progressBar = new ProgressBar($output, $this->countFiles($this->from)); 53 | $i = 0; 54 | 55 | foreach ($this->getFileContent($this->from) as $fileInfo) { 56 | $this->processFileContent($fileInfo[0], $fileInfo[1]); 57 | $i++; 58 | $progressBar->setProgress($i); 59 | } 60 | $progressBar->finish(); 61 | $this->finished(); 62 | $output->writeln(''); 63 | } 64 | 65 | abstract protected function processFileContent(string $file, string $fileContent): void; 66 | 67 | abstract protected function finished(): void; 68 | 69 | private function getFileContent(string $from): \Generator 70 | { 71 | if (is_file($from)) { 72 | yield [$from, file_get_contents($from)]; 73 | return; 74 | } 75 | 76 | $directory = new RecursiveDirectoryIterator($from); 77 | $iterator = new RecursiveIteratorIterator($directory); 78 | $regex = new RegexIterator($iterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH); 79 | 80 | $regex->rewind(); 81 | 82 | foreach ($regex as $file) { 83 | yield [$file[0], file_get_contents($file[0])]; 84 | } 85 | } 86 | 87 | private function countFiles(string $from) 88 | { 89 | if (is_file($from)) { 90 | return 1; 91 | } 92 | 93 | $directory = new RecursiveDirectoryIterator($from); 94 | $iterator = new RecursiveIteratorIterator($directory); 95 | $regex = new RegexIterator($iterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH); 96 | 97 | return iterator_count($regex); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Console/Command/Prototype.php: -------------------------------------------------------------------------------- 1 | parser = (new ParserFactory())->create(ParserFactory::ONLY_PHP7); 58 | $this->sortNamespaceClasses = new SortNamespaceClasses(); 59 | 60 | $this->traverser = new NodeTraverser(); 61 | $this->traverser->addVisitor(new class extends NodeVisitorAbstract 62 | { 63 | public function leaveNode(Node $node) 64 | { 65 | if ($node instanceof Node\Stmt\Declare_) { 66 | return NodeTraverser::REMOVE_NODE; 67 | } 68 | if ($node instanceof Node\Stmt\Use_) { 69 | return NodeTraverser::REMOVE_NODE; 70 | } 71 | } 72 | 73 | public function enterNode(Node $node) 74 | { 75 | if ($node instanceof Node\Stmt\ClassMethod) { 76 | // Clean out the function body 77 | if ($node->stmts !== null) { 78 | $node->stmts = []; 79 | } 80 | $node->setAttribute('comments', null); 81 | } 82 | if ($node instanceof Node\Stmt\Class_) { 83 | $node->setAttribute('comments', null); 84 | } 85 | if ($node instanceof Node\Stmt\Interface_) { 86 | $node->setAttribute('comments', null); 87 | } 88 | } 89 | }); 90 | $this->traverser->addVisitor(new NameResolver()); 91 | $this->traverser->addVisitor($this->sortNamespaceClasses); 92 | 93 | $this->printer = new Standard(); 94 | } 95 | 96 | protected function configure() 97 | { 98 | $this 99 | ->setName('php2zephir:prototype:create') 100 | ->setDescription('Creates prototypes of given file or directory') 101 | ->addArgument('from', InputArgument::REQUIRED, 'Source path or file') 102 | ->addArgument('to', InputArgument::REQUIRED, 'Destination path of Zephir prototype file'); 103 | } 104 | 105 | protected function processFileContent(string $file, string $fileContent): void 106 | { 107 | $ast = $this->parser->parse($fileContent); 108 | $ast = $this->traverser->traverse($ast); 109 | $this->prototypeCode .= $this->printer->prettyPrint($ast) . PHP_EOL; 110 | } 111 | 112 | protected function finished(): void 113 | { 114 | $to = realpath($this->to); 115 | if ($to && is_dir($to)) { 116 | throw new RuntimeException(sprintf('To must be a file and not a directory, "%s" given', $this->to)); 117 | } 118 | 119 | $dir = dirname($this->to); 120 | 121 | if (! @mkdir($dir, 0755, true) && ! is_dir($dir)) { 122 | throw CouldNotCreateDirectoryException::forDir($dir); 123 | } 124 | $code = "sortNamespaceClasses->printSortedNamespaces($this->printer); 125 | 126 | if (false === file_put_contents($this->to, $code)) { 127 | throw CouldNotWriteFileException::forFile($to); 128 | } 129 | 130 | $this->prototypeCode = ''; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/Console/Command/Zep.php: -------------------------------------------------------------------------------- 1 | parser = (new ParserFactory())->create(ParserFactory::ONLY_PHP7); 48 | $this->traverser = new NodeTraverser(); 49 | $this->traverser->addVisitor(new InitLocalVariable()); 50 | $this->traverser->addVisitor(new RemoveUseFunction()); 51 | $this->traverser->addVisitor(new UnsetSplitter()); 52 | $this->traverser->addVisitor(new IssetSplitter()); 53 | $this->traverser->addVisitor(new ArrayDestructuring()); 54 | $this->printer = new ZephirPrinter(); 55 | } 56 | 57 | protected function configure() 58 | { 59 | $this 60 | ->setName('php2zephir:zep:create') 61 | ->setDescription('Creates zep file(s) of given file or directory') 62 | ->addArgument('from', InputArgument::REQUIRED, 'Source path or file to convert to zep file') 63 | ->addArgument('to', InputArgument::REQUIRED, 'Destination path or file for converted zep file'); 64 | } 65 | 66 | protected function processFileContent(string $file, string $fileContent): void 67 | { 68 | $to = $this->to; 69 | 70 | if ($this->isDir === true) { 71 | $base = dirname(substr($file, strlen($this->from))); 72 | 73 | if ($base !== '/') { 74 | $base .= '/'; 75 | } 76 | $to = $this->to . $base . basename($file); 77 | } 78 | $dir = dirname($to); 79 | $to = preg_replace('/.php$/i', '.zep', $to); 80 | 81 | if (! @mkdir($dir, 0755, true) && ! is_dir($dir)) { 82 | throw CouldNotCreateDirectoryException::forDir($dir); 83 | } 84 | 85 | $ast = $this->parser->parse($fileContent); 86 | $ast = $this->traverser->traverse($ast); 87 | 88 | if (false === file_put_contents($to, $this->printer->prettyPrintFile($ast))) { 89 | throw CouldNotWriteFileException::forFile($to); 90 | } 91 | } 92 | 93 | protected function finished(): void 94 | { 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Exception/CouldNotCreateDirectoryException.php: -------------------------------------------------------------------------------- 1 | expr instanceof Node\Expr\Assign 22 | && ($node->expr->expr instanceof Node\Expr\MethodCall 23 | || $node->expr->expr instanceof Node\Expr\FuncCall 24 | || $node->expr->expr instanceof Node\Expr\StaticCall 25 | || $node->expr->expr instanceof Node\Expr\Array_ 26 | ) 27 | && $node->expr->var instanceof Node\Expr\Array_ 28 | ) { 29 | $stmts = array_map(function (Node\Expr\ArrayItem $item) { 30 | return new Node\Expr\Variable($item->value->name); 31 | }, $node->expr->var->items); 32 | 33 | $arrayName = 'a' . bin2hex(random_bytes(6)); 34 | $key = -1; 35 | $destructuring = array_map(function (Node\Expr\ArrayItem $item) use ($stmts, $arrayName, &$key) { 36 | $key++; 37 | $name = clone $stmts[$key]; 38 | $name->setAttribute('init', false); 39 | return new Node\Stmt\Expression( 40 | new Node\Expr\Assign( 41 | $name, 42 | new Node\Expr\ArrayDimFetch( 43 | new Node\Expr\Variable($arrayName, $item->getAttributes()), 44 | new Node\Scalar\LNumber($key) 45 | ) 46 | ) 47 | ); 48 | }, $node->expr->var->items); 49 | 50 | $node->expr->var = new Node\Expr\Variable($arrayName, $node->getAttributes()); 51 | 52 | $stmts[] = $node; 53 | 54 | $stmts = array_merge($stmts, $destructuring); 55 | 56 | return $stmts; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/PhpParser/NodeVisitor/InitLocalVariable.php: -------------------------------------------------------------------------------- 1 | var instanceof Node\Expr\Variable) { 29 | $name = $node->var->name; 30 | $this->initialized[$name] = $name; 31 | } 32 | } 33 | 34 | public function leaveNode(Node $node) 35 | { 36 | if ($node instanceof Node\Stmt\ClassMethod) { 37 | $stmts = $node->stmts; 38 | 39 | foreach ($this->initialized as $name) { 40 | array_unshift($stmts, new Node\Expr\Variable($name)); 41 | } 42 | $node->stmts = $stmts; 43 | $this->initialized = []; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/PhpParser/NodeVisitor/IssetSplitter.php: -------------------------------------------------------------------------------- 1 | cond instanceof Node\Expr\Isset_ 23 | ) { 24 | if ($and = $this->issetCondition($node->cond)) { 25 | return new Node\Stmt\If_($and[0]); 26 | } 27 | } 28 | if ($node instanceof Node\Stmt\If_ 29 | && $node->cond instanceof Node\Expr\BinaryOp 30 | && $node->cond->right instanceof Node\Expr\Isset_ 31 | ) { 32 | if ($and = $this->issetCondition($node->cond->right)) { 33 | $node->cond->right = $and[0]; 34 | } 35 | } 36 | } 37 | 38 | private function issetCondition(Node\Expr\Isset_ $node): ?array 39 | { 40 | if (count($node->vars) > 1) { 41 | $issetConditions = array_map(function (Node\Expr $expr) { 42 | return new Node\Expr\Isset_([$expr]); 43 | }, $node->vars); 44 | $number = count($issetConditions); 45 | 46 | if ($number > 2) { 47 | throw new RuntimeException('Only two variables are supported for isset conversion.'); 48 | } 49 | 50 | $and = []; 51 | for ($i = 0; $i < $number; $i += 2) { 52 | $and[] = new Node\Expr\BinaryOp\BooleanAnd($issetConditions[$i], $issetConditions[$i + 1]); 53 | } 54 | return $and; 55 | } 56 | return null; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/PhpParser/NodeVisitor/RemoveUseFunction.php: -------------------------------------------------------------------------------- 1 | type === Node\Stmt\Use_::TYPE_FUNCTION || $node->type === Node\Stmt\Use_::TYPE_CONSTANT) 23 | ) { 24 | return NodeTraverser::REMOVE_NODE; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/PhpParser/NodeVisitor/SortNamespaceClasses.php: -------------------------------------------------------------------------------- 1 | namespaces[$node->name->toString()]) 27 | ) { 28 | $this->namespaces[$node->name->toString()] = []; 29 | $this->currentNamespace = $node->name->toString(); 30 | } 31 | 32 | if ($node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Interface_) { 33 | $this->namespaces[$this->currentNamespace][$node->namespacedName->toString()] = $node; 34 | 35 | return NodeTraverser::DONT_TRAVERSE_CHILDREN; 36 | } 37 | } 38 | 39 | public function getNamespaces(): array 40 | { 41 | return $this->namespaces; 42 | } 43 | 44 | public function printSortedNamespaces(PrettyPrinterAbstract $printer): string 45 | { 46 | $code = ''; 47 | 48 | foreach ($this->namespaces as $namespace => &$cls) { 49 | usort($cls, [self::class, 'sortNamespaces']); 50 | $namespaceNode = new Node\Stmt\Namespace_(new Node\Name($namespace), $cls); 51 | $code .= $printer->prettyPrint([$namespaceNode]) . PHP_EOL; 52 | } 53 | 54 | return $code; 55 | } 56 | 57 | public static function sortNamespaces(Node\Stmt\ClassLike $a, Node\Stmt\ClassLike $b): int 58 | { 59 | $aExtends = empty($a->extends); 60 | $bExtends = empty($b->extends); 61 | 62 | if ($aExtends && $bExtends) { 63 | return 0; 64 | } 65 | if ($aExtends && ! $bExtends) { 66 | return -1; 67 | } 68 | if (! $aExtends && $bExtends) { 69 | return 1; 70 | } 71 | $aNamespace = $a->namespacedName->toString(); 72 | $bNamespace = $b->namespacedName->toString(); 73 | 74 | if (! empty(array_filter($a->extends, function (Node\Name\FullyQualified $node) use ($bNamespace) { 75 | return $node->toString() === $bNamespace; 76 | }))) { 77 | return 1; 78 | } 79 | 80 | if (! empty(array_filter($b->extends, function (Node\Name\FullyQualified $node) use ($aNamespace) { 81 | return $node->toString() === $aNamespace; 82 | }))) { 83 | return -1; 84 | } 85 | 86 | return 0; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/PhpParser/NodeVisitor/UnsetSplitter.php: -------------------------------------------------------------------------------- 1 | vars) > 1 22 | ) { 23 | return array_map(function (Node\Expr $expr) { 24 | return new Node\Stmt\Unset_([$expr]); 25 | }, $node->vars); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ZephirPrinter.php: -------------------------------------------------------------------------------- 1 | prettyPrint($stmts); 41 | 42 | if ($stmts[0] instanceof Stmt\InlineHTML) { 43 | $p = preg_replace('/^<\?php\s+\?>\n?/', '', $p); 44 | } 45 | if ($stmts[count($stmts) - 1] instanceof Stmt\InlineHTML) { 46 | $p = preg_replace('/<\?php$/', '', rtrim($p)); 47 | } 48 | 49 | return $p; 50 | } 51 | 52 | protected function pStmt_Declare(Stmt\Declare_ $node) 53 | { 54 | $declares = $this->pCommaSeparated($node->declares); 55 | 56 | if (false !== strpos($declares, 'strict_types')) { 57 | return ''; 58 | } 59 | 60 | return 'declare (' . $declares . ')' 61 | . (null !== $node->stmts ? ' {' . $this->pStmts($node->stmts) . $this->nl . '}' : ';'); 62 | } 63 | 64 | protected function pExpr_Variable(Expr\Variable $node) 65 | { 66 | if ($node->name instanceof Expr) { 67 | return '${' . $this->p($node->name) . '}'; 68 | } else { 69 | $code = $node->name; 70 | 71 | if (0 === count($node->getAttributes())) { 72 | $code = 'var ' . $code . ';'; 73 | } 74 | 75 | return $code; 76 | } 77 | } 78 | 79 | protected function pExpr_ArrayItem(Expr\ArrayItem $node) 80 | { 81 | return (null !== $node->key ? $this->p($node->key) . ': ' : '') 82 | . ($node->byRef ? '&' : '') . $this->p($node->value); 83 | } 84 | 85 | protected function pExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $node) 86 | { 87 | return $this->pDereferenceLhs($node->class) . '::$' . $this->pObjectProperty($node->name); 88 | } 89 | 90 | protected function pVarLikeIdentifier(Node\VarLikeIdentifier $node) 91 | { 92 | return $node->name; 93 | } 94 | 95 | protected function pStmt_PropertyProperty(Stmt\PropertyProperty $node) 96 | { 97 | return $node->name 98 | . (null !== $node->default ? ' = ' . $this->p($node->default) : ''); 99 | } 100 | 101 | protected function pStmt_Foreach(Stmt\Foreach_ $node) 102 | { 103 | $code = ''; 104 | $key = null !== $node->keyVar ? $this->p($node->keyVar) : ''; 105 | $value = $this->p($node->valueVar); 106 | 107 | if ($key !== '') { 108 | $code .= 'var ' . $key . ';' . PHP_EOL; 109 | } 110 | $code .= 'var ' . $value . ';' . PHP_EOL; 111 | 112 | return $code . 'for ' . (null !== $node->keyVar ? $this->p($node->keyVar) . ', ' : '') 113 | . ($node->byRef ? '&' : '') . $value . ' in ' . $this->p($node->expr) . ' {' 114 | . $this->pStmts($node->stmts) . $this->nl . '}'; 115 | } 116 | 117 | protected function pStmt_ClassConst(Stmt\ClassConst $node) 118 | { 119 | return 'const ' . $this->pCommaSeparated($node->consts) . ';'; 120 | } 121 | 122 | protected function pImplode(array $nodes, string $glue = '') : string 123 | { 124 | $pNodes = []; 125 | foreach ($nodes as $node) { 126 | if (null === $node) { 127 | $pNodes[] = ''; 128 | } elseif ($node instanceof Node\Param) { 129 | $param = $this->p($node); 130 | 131 | if ($node->type instanceof Node\Name) { 132 | $param = explode(' ', $param); 133 | $param = '<' . $param[0] .'> ' . $param[1]; 134 | } 135 | $pNodes[] = $param; 136 | } else { 137 | $pNodes[] = $this->p($node); 138 | } 139 | } 140 | 141 | return implode($glue, $pNodes); 142 | } 143 | 144 | protected function pStmt_ClassMethod(Stmt\ClassMethod $node) 145 | { 146 | $returnType = null; 147 | 148 | if (null !== $node->returnType) { 149 | $returnType = $this->p($node->returnType); 150 | } 151 | 152 | if ($returnType !== null && 0 === strpos($returnType, '?')) { 153 | $returnType = substr($returnType, 1) . '|null'; 154 | } 155 | 156 | if (null !== $node->returnType && $node->returnType->getType() === 'Name') { 157 | $returnType = '<' . $this->p($node->returnType) . '>'; 158 | } 159 | 160 | if ($returnType === null) { 161 | foreach ($node->getComments() as $comment) { 162 | $matches = []; 163 | if (1 === preg_match('/(@return +)([\|\\a-z]+\n)/i', $comment->getText(), $matches)) { 164 | $returnType = trim($matches[2]); 165 | $split = strpos($returnType, ' '); 166 | 167 | if (false !== $split) { 168 | $returnType = substr($returnType, 0, $split); 169 | } 170 | 171 | if ($returnType === 'mixed') { 172 | $returnType = null; 173 | } 174 | } 175 | } 176 | } 177 | 178 | $params = $node->getParams(); 179 | 180 | foreach ($params as &$param) { 181 | if ($param->type === null) { 182 | $param->type = new Node\Identifier('var'); 183 | } 184 | } 185 | 186 | return $this->pModifiers($node->flags) 187 | . 'function ' . ($node->byRef ? '&' : '') . $node->name 188 | . '(' . $this->pCommaSeparated($params) . ')' 189 | . (null !== $returnType ? ' -> ' . $returnType : '') 190 | . (null !== $node->stmts 191 | ? $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}' 192 | : ';') . "\n"; 193 | } 194 | 195 | protected function pStmt_Function(Stmt\Function_ $node) 196 | { 197 | $returnType = ''; 198 | 199 | if (null !== $node->returnType) { 200 | $returnType = $this->p($node->returnType); 201 | } 202 | 203 | if (null !== $node->returnType && $node->returnType->getType() === 'Name') { 204 | $returnType = '<' . $this->p($node->returnType) . '>'; 205 | } 206 | 207 | return 'function ' . ($node->byRef ? '&' : '') . $node->name 208 | . '(' . $this->pCommaSeparated($node->params) . ')' 209 | . (null !== $node->returnType ? ' -> ' . $returnType : '') 210 | . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; 211 | } 212 | 213 | protected function pExpr_Assign(Expr\Assign $node) 214 | { 215 | return 'let ' . parent::pExpr_Assign($node); 216 | } 217 | 218 | protected function pExpr_AssignRef(Expr\AssignRef $node) 219 | { 220 | return 'let ' . parent::pExpr_AssignRef($node); 221 | } 222 | 223 | protected function pExpr_AssignOp_Plus(AssignOp\Plus $node) 224 | { 225 | return 'let ' . parent::pExpr_AssignOp_Plus($node); 226 | } 227 | 228 | protected function pExpr_AssignOp_Minus(AssignOp\Minus $node) 229 | { 230 | return 'let ' . parent::pExpr_AssignOp_Minus($node); 231 | } 232 | 233 | protected function pExpr_AssignOp_Mul(AssignOp\Mul $node) 234 | { 235 | return 'let ' . parent::pExpr_AssignOp_Mul($node); 236 | } 237 | 238 | protected function pExpr_AssignOp_Div(AssignOp\Div $node) 239 | { 240 | return 'let ' . parent::pExpr_AssignOp_Div($node); 241 | } 242 | 243 | protected function pExpr_AssignOp_Concat(AssignOp\Concat $node) 244 | { 245 | return 'let ' . parent::pExpr_AssignOp_Concat($node); 246 | } 247 | 248 | protected function pExpr_AssignOp_Mod(AssignOp\Mod $node) 249 | { 250 | return 'let ' . parent::pExpr_AssignOp_Mod($node); 251 | } 252 | 253 | protected function pExpr_AssignOp_BitwiseAnd(AssignOp\BitwiseAnd $node) 254 | { 255 | return 'let ' . parent::pExpr_AssignOp_BitwiseAnd($node); 256 | } 257 | 258 | protected function pExpr_AssignOp_BitwiseOr(AssignOp\BitwiseOr $node) 259 | { 260 | return 'let ' . parent::pExpr_AssignOp_BitwiseOr($node); 261 | } 262 | 263 | protected function pExpr_AssignOp_BitwiseXor(AssignOp\BitwiseXor $node) 264 | { 265 | return 'let ' . parent::pExpr_AssignOp_BitwiseXor($node); 266 | } 267 | 268 | protected function pExpr_AssignOp_ShiftLeft(AssignOp\ShiftLeft $node) 269 | { 270 | return 'let ' . parent::pExpr_AssignOp_ShiftLeft($node); 271 | } 272 | 273 | protected function pExpr_AssignOp_ShiftRight(AssignOp\ShiftRight $node) 274 | { 275 | return 'let ' . parent::pExpr_AssignOp_ShiftRight($node); 276 | } 277 | 278 | protected function pExpr_AssignOp_Pow(AssignOp\Pow $node) 279 | { 280 | return 'let ' . parent::pExpr_AssignOp_Pow($node); 281 | } 282 | 283 | protected function pExpr_PostInc(Expr\PostInc $node) 284 | { 285 | return 'let ' . parent::pExpr_PostInc($node); 286 | } 287 | 288 | protected function pExpr_PostDec(Expr\PostDec $node) 289 | { 290 | return 'let ' . parent::pExpr_PostDec($node); 291 | } 292 | 293 | protected function pExpr_BinaryOp_Coalesce(BinaryOp\Coalesce $node) 294 | { 295 | [$precedence, $associativity] = $this->precedenceMap[BinaryOp\Coalesce::class]; 296 | 297 | $left = $this->pPrec($node->left, $precedence, $associativity, -1); 298 | $right = $this->pPrec($node->right, $precedence, $associativity, 1); 299 | 300 | return 'isset(' . $left . ') ? ' . $left . ' : ' . $right; 301 | } 302 | 303 | protected function pExpr_BinaryOp_Concat(BinaryOp\Concat $node) 304 | { 305 | [$precedence, $associativity] = $this->precedenceMap[BinaryOp\Concat::class]; 306 | 307 | $left = $this->pPrec($node->left, $precedence, $associativity, -1); 308 | $right = $this->pPrec($node->right, $precedence, $associativity, 1); 309 | 310 | return $this->convertToDoubleQuotes($left) . ' . ' . $this->convertToDoubleQuotes($right); 311 | } 312 | 313 | protected function pSingleQuotedString(string $string) 314 | { 315 | if (strlen($string) === 1) { 316 | return '\'' . addcslashes($string, '\'\\') . '\''; 317 | } 318 | return '"' . addcslashes($string, '"\\') . '"'; 319 | } 320 | 321 | private function convertToDoubleQuotes(string $value) 322 | { 323 | if (strlen($value) === 3 && strpos($value, "'") === 0 && $value[2] === "'") { 324 | return '"' . $value[1] . '"'; 325 | } 326 | return $value; 327 | } 328 | 329 | protected function pStmt_TryCatch(Stmt\TryCatch $node) 330 | { 331 | $stmt = parent::pStmt_TryCatch($node); 332 | 333 | return 'var ' . $this->p($node->catches[0]->var) . ";\n" . $stmt; 334 | } 335 | 336 | protected function pStmt_Catch(Stmt\Catch_ $node) 337 | { 338 | return 'catch ' . $this->pImplode($node->types, '|') . ', ' 339 | . $this->p($node->var) 340 | . ' {' . $this->pStmts($node->stmts) . $this->nl . '}'; 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /test/Mock/ClassConstants.php: -------------------------------------------------------------------------------- 1 | myProperty = $myProperty; 26 | } 27 | 28 | public function setMyClass(MyClass $myClass): void 29 | { 30 | $this->myClass = $myClass; 31 | } 32 | 33 | public function getMyProperty() 34 | { 35 | return $this->myProperty; 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /test/Mock/ClassReturnArray.php: -------------------------------------------------------------------------------- 1 | true, 20 | 'first' => 'ok', 21 | 3 => 123, 22 | ]; 23 | } 24 | 25 | public function index(): array 26 | { 27 | return [ 28 | 'first', 29 | 2, 30 | true 31 | ]; 32 | } 33 | } -------------------------------------------------------------------------------- /test/Mock/MyClass.php: -------------------------------------------------------------------------------- 1 | sortNamespaceClasses = new SortNamespaceClasses(); 45 | $this->parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7); 46 | $this->traverser = new NodeTraverser(); 47 | $this->traverser->addVisitor(new NameResolver()); 48 | $this->traverser->addVisitor($this->sortNamespaceClasses); 49 | $this->printer = new Standard(); 50 | } 51 | 52 | /** 53 | * @test 54 | */ 55 | public function it_sorts_interfaces(): void 56 | { 57 | $code = <<<'CODE' 58 | parser->parse($code); 105 | $this->traverser->traverse($ast); 106 | 107 | $current = $this->sortNamespaceClasses->printSortedNamespaces($this->printer); 108 | 109 | $this->assertEquals( 110 | $exptectedCode, 111 | $current, 112 | $current 113 | ); 114 | } 115 | } -------------------------------------------------------------------------------- /test/Zephir/ClassConstants.zep: -------------------------------------------------------------------------------- 1 | /** 2 | * Sandro Keil (https://sandro-keil.de) 3 | * 4 | * @link http://github.com/sandrokeil/php-to-zephir for the canonical source repository 5 | * @copyright Copyright (c) 2018 Sandro Keil 6 | * @license http://github.com/sandrokeil/php-to-zephir/blob/master/LICENSE.md New BSD License 7 | */ 8 | 9 | namespace PhpToZephirTest\Mock; 10 | 11 | class ClassConstants 12 | { 13 | const FIRST = "testing"; 14 | const SECOND = 2; 15 | const THIRD = true; 16 | } -------------------------------------------------------------------------------- /test/Zephir/ClassMethods.zep: -------------------------------------------------------------------------------- 1 | /** 2 | * Sandro Keil (https://sandro-keil.de) 3 | * 4 | * @link http://github.com/sandrokeil/php-to-zephir for the canonical source repository 5 | * @copyright Copyright (c) 2018 Sandro Keil 6 | * @license http://github.com/sandrokeil/php-to-zephir/blob/master/LICENSE.md New BSD License 7 | */ 8 | 9 | namespace PhpToZephirTest\Mock; 10 | 11 | class ClassMethods 12 | { 13 | public function doSum1(int a, int b) -> int 14 | { 15 | return a + b; 16 | } 17 | 18 | public function doSum2(int a, int b = 3) -> int 19 | { 20 | return a + b; 21 | } 22 | 23 | public function doSum3(int a = 1, int b = 2) -> int 24 | { 25 | return a + b; 26 | } 27 | 28 | public function doSum4(int a, int b) -> int 29 | { 30 | return a + b; 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /test/Zephir/ClassMethodsReturnTypes.zep: -------------------------------------------------------------------------------- 1 | /** 2 | * Sandro Keil (https://sandro-keil.de) 3 | * 4 | * @link http://github.com/sandrokeil/php-to-zephir for the canonical source repository 5 | * @copyright Copyright (c) 2018 Sandro Keil 6 | * @license http://github.com/sandrokeil/php-to-zephir/blob/master/LICENSE.md New BSD License 7 | */ 8 | 9 | namespace PhpToZephirTest\Mock; 10 | 11 | class ClassMethodsReturnTypes 12 | { 13 | /** 14 | * @param $a 15 | * @return bool|string 16 | */ 17 | public function getSomeData(var a) -> bool|string 18 | { 19 | } 20 | 21 | /** 22 | * @param $a 23 | * @return mixed 24 | */ 25 | public function mixed(var a) 26 | { 27 | } 28 | 29 | /** 30 | * @param $a 31 | * @return string|array|object awesome Data 32 | * @throws \RuntimeException if possible 33 | */ 34 | public function data(var a) -> string|array|object 35 | { 36 | } 37 | 38 | public function getId() -> string|null 39 | { 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /test/Zephir/ClassProperty.zep: -------------------------------------------------------------------------------- 1 | /** 2 | * Sandro Keil (https://sandro-keil.de) 3 | * 4 | * @link http://github.com/sandrokeil/php-to-zephir for the canonical source repository 5 | * @copyright Copyright (c) 2018 Sandro Keil 6 | * @license http://github.com/sandrokeil/php-to-zephir/blob/master/LICENSE.md New BSD License 7 | */ 8 | 9 | namespace PhpToZephirTest\Mock; 10 | 11 | class ClassProperty 12 | { 13 | protected myProperty; 14 | /** 15 | * @var MyClass 16 | */ 17 | protected myClass; 18 | public function setMyProperty(var myProperty) -> void 19 | { 20 | let this->myProperty = myProperty; 21 | } 22 | 23 | public function setMyClass( myClass) -> void 24 | { 25 | let this->myClass = myClass; 26 | } 27 | 28 | public function getMyProperty() 29 | { 30 | return this->myProperty; 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /test/Zephir/ClassReturnArray.zep: -------------------------------------------------------------------------------- 1 | /** 2 | * Sandro Keil (https://sandro-keil.de) 3 | * 4 | * @link http://github.com/sandrokeil/php-to-zephir for the canonical source repository 5 | * @copyright Copyright (c) 2018 Sandro Keil 6 | * @license http://github.com/sandrokeil/php-to-zephir/blob/master/LICENSE.md New BSD License 7 | */ 8 | 9 | namespace PhpToZephirTest\Mock; 10 | 11 | class ClassReturnArray 12 | { 13 | public function assoc() -> array 14 | { 15 | return ["test": true, "first": "ok", 3: 123]; 16 | } 17 | 18 | public function index() -> array 19 | { 20 | return ["first", 2, true]; 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /test/ZephirPrinterTest.php: -------------------------------------------------------------------------------- 1 | parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7); 43 | $this->traverser = new NodeTraverser(); 44 | $this->traverser->addVisitor(new InitLocalVariable()); 45 | $this->traverser->addVisitor(new RemoveUseFunction()); 46 | $this->traverser->addVisitor(new UnsetSplitter()); 47 | $this->traverser->addVisitor(new IssetSplitter()); 48 | $this->traverser->addVisitor(new ArrayDestructuring()); 49 | $this->zephirPrinter = new ZephirPrinter(); 50 | } 51 | 52 | /** 53 | * @test 54 | */ 55 | public function it_converts_return_type_string(): void 56 | { 57 | $code = <<<'CODE' 58 | string 67 | { 68 | } 69 | CODE; 70 | 71 | $ast = $this->parser->parse($code); 72 | $ast = $this->traverser->traverse($ast); 73 | 74 | $current = $this->zephirPrinter->prettyPrintFile($ast); 75 | 76 | $this->assertEquals($expectedCode, $current, $current); 77 | } 78 | 79 | /** 80 | * @test 81 | */ 82 | public function it_converts_function_types(): void 83 | { 84 | $code = <<<'CODE' 85 | string 94 | { 95 | } 96 | CODE; 97 | 98 | $ast = $this->parser->parse($code); 99 | $ast = $this->traverser->traverse($ast); 100 | 101 | $current = $this->zephirPrinter->prettyPrintFile($ast); 102 | 103 | $this->assertEquals($expectedCode, $current, $current); 104 | } 105 | 106 | /** 107 | * @test 108 | */ 109 | public function it_converts_return_type_class(): void 110 | { 111 | $code = <<<'CODE' 112 | 124 | { 125 | } 126 | CODE; 127 | 128 | $ast = $this->parser->parse($code); 129 | $ast = $this->traverser->traverse($ast); 130 | 131 | $this->assertEquals($expectedCode, $this->zephirPrinter->prettyPrintFile($ast)); 132 | } 133 | 134 | /** 135 | * @test 136 | */ 137 | public function it_converts_class_methods(): void 138 | { 139 | $code = file_get_contents(__DIR__ . '/Mock/ClassMethods.php'); 140 | $expectedCode = file_get_contents(__DIR__ . '/Zephir/ClassMethods.zep'); 141 | 142 | $ast = $this->parser->parse($code); 143 | $ast = $this->traverser->traverse($ast); 144 | 145 | $current = $this->zephirPrinter->prettyPrintFile($ast); 146 | 147 | $this->assertEquals($expectedCode, $current, $current); 148 | } 149 | 150 | /** 151 | * @test 152 | */ 153 | public function it_converts_class_property(): void 154 | { 155 | $code = file_get_contents(__DIR__ . '/Mock/ClassProperty.php'); 156 | $expectedCode = file_get_contents(__DIR__ . '/Zephir/ClassProperty.zep'); 157 | 158 | $ast = $this->parser->parse($code); 159 | $ast = $this->traverser->traverse($ast); 160 | $current = $this->zephirPrinter->prettyPrintFile($ast); 161 | 162 | $this->assertEquals($expectedCode, $current, $current); 163 | } 164 | 165 | /** 166 | * @test 167 | */ 168 | public function it_converts_class_methods_return_types(): void 169 | { 170 | $code = file_get_contents(__DIR__ . '/Mock/ClassMethodsReturnTypes.php'); 171 | $expectedCode = file_get_contents(__DIR__ . '/Zephir/ClassMethodsReturnTypes.zep'); 172 | 173 | $ast = $this->parser->parse($code); 174 | $ast = $this->traverser->traverse($ast); 175 | $current = $this->zephirPrinter->prettyPrintFile($ast); 176 | 177 | $this->assertEquals($expectedCode, $current, $current); 178 | } 179 | 180 | /** 181 | * @test 182 | */ 183 | public function it_converts_class_return_array(): void 184 | { 185 | $code = file_get_contents(__DIR__ . '/Mock/ClassReturnArray.php'); 186 | $expectedCode = file_get_contents(__DIR__ . '/Zephir/ClassReturnArray.zep'); 187 | 188 | $ast = $this->parser->parse($code); 189 | $ast = $this->traverser->traverse($ast); 190 | $current = $this->zephirPrinter->prettyPrintFile($ast); 191 | 192 | $this->assertEquals($expectedCode, $current, $current); 193 | } 194 | 195 | /** 196 | * @test 197 | */ 198 | public function it_converts_class_constants(): void 199 | { 200 | $code = file_get_contents(__DIR__ . '/Mock/ClassConstants.php'); 201 | $expectedCode = file_get_contents(__DIR__ . '/Zephir/ClassConstants.zep'); 202 | 203 | $ast = $this->parser->parse($code); 204 | $ast = $this->traverser->traverse($ast); 205 | $current = $this->zephirPrinter->prettyPrintFile($ast); 206 | 207 | $this->assertEquals($expectedCode, $current, $current); 208 | } 209 | 210 | /** 211 | * @test 212 | */ 213 | public function it_converts_foreach(): void 214 | { 215 | $code = <<<'CODE' 216 | $type) { 220 | } 221 | CODE; 222 | 223 | $expectedCode = <<<'CODE' 224 | let types = ["one", "two", "three"]; 225 | var key; 226 | var type; 227 | for key, type in types { 228 | } 229 | CODE; 230 | 231 | $ast = $this->parser->parse($code); 232 | $ast = $this->traverser->traverse($ast); 233 | $current = $this->zephirPrinter->prettyPrintFile($ast); 234 | 235 | $this->assertEquals($expectedCode, $current, $current); 236 | } 237 | 238 | /** 239 | * @test 240 | */ 241 | public function it_converts_string_concat(): void 242 | { 243 | $code = <<<'CODE' 244 | parser->parse($code); 258 | $ast = $this->traverser->traverse($ast); 259 | $current = $this->zephirPrinter->prettyPrintFile($ast); 260 | 261 | $this->assertEquals($expectedCode, $current, $current); 262 | } 263 | 264 | /** 265 | * @test 266 | */ 267 | public function it_converts_assign_plus(): void 268 | { 269 | $code = <<<'CODE' 270 | parser->parse($code); 282 | $ast = $this->traverser->traverse($ast); 283 | $current = $this->zephirPrinter->prettyPrintFile($ast); 284 | 285 | $this->assertEquals($expectedCode, $current, $current); 286 | } 287 | 288 | /** 289 | * @test 290 | */ 291 | public function it_converts_post_increment(): void 292 | { 293 | $code = <<<'CODE' 294 | parser->parse($code); 303 | $ast = $this->traverser->traverse($ast); 304 | $current = $this->zephirPrinter->prettyPrintFile($ast); 305 | 306 | $this->assertEquals($expectedCode, $current, $current); 307 | } 308 | 309 | /** 310 | * @test 311 | */ 312 | public function it_converts_coalesce(): void 313 | { 314 | $code = <<<'CODE' 315 | parser->parse($code); 324 | $ast = $this->traverser->traverse($ast); 325 | $current = $this->zephirPrinter->prettyPrintFile($ast); 326 | 327 | $this->assertEquals($expectedCode, $current, $current); 328 | } 329 | 330 | /** 331 | * @test 332 | */ 333 | public function it_initializes_local_variables(): void 334 | { 335 | $code = <<<'CODE' 336 | testingFunc(); 350 | $newVariable = $this->testingFunc(); 351 | } 352 | } 353 | CODE; 354 | 355 | $expectedCode = <<<'CODE' 356 | class TestClass 357 | { 358 | public function testingFunc() 359 | { 360 | var otherVariable; 361 | var variable; 362 | let variable = 0; 363 | let otherVariable = 123; 364 | return otherVariable; 365 | } 366 | 367 | public function otherFunc() 368 | { 369 | var newVariable; 370 | var variable; 371 | let variable = 123; 372 | let variable = 50; 373 | let variable = this->testingFunc(); 374 | let newVariable = this->testingFunc(); 375 | } 376 | 377 | } 378 | CODE; 379 | 380 | $ast = $this->parser->parse($code); 381 | $ast = $this->traverser->traverse($ast); 382 | $current = $this->zephirPrinter->prettyPrintFile($ast); 383 | 384 | $this->assertEquals($expectedCode, $current, $current); 385 | } 386 | 387 | /** 388 | * @test 389 | */ 390 | public function it_removes_use_function_imports(): void 391 | { 392 | $code = <<<'CODE' 393 | parser->parse($code); 407 | $ast = $this->traverser->traverse($ast); 408 | $current = $this->zephirPrinter->prettyPrintFile($ast); 409 | 410 | $this->assertEquals($expectedCode, $current, $current); 411 | } 412 | 413 | /** 414 | * @test 415 | */ 416 | public function it_converts_try_catch(): void 417 | { 418 | $code = <<<'CODE' 419 | parser->parse($code); 433 | $ast = $this->traverser->traverse($ast); 434 | $current = $this->zephirPrinter->prettyPrintFile($ast); 435 | 436 | $this->assertEquals($expectedCode, $current, $current); 437 | } 438 | 439 | /** 440 | * @test 441 | */ 442 | public function it_converts_unset(): void 443 | { 444 | $code = <<<'CODE' 445 | parser->parse($code); 455 | $ast = $this->traverser->traverse($ast); 456 | $current = $this->zephirPrinter->prettyPrintFile($ast); 457 | 458 | $this->assertEquals($expectedCode, $current, $current); 459 | } 460 | 461 | /** 462 | * @test 463 | */ 464 | public function it_converts_isset(): void 465 | { 466 | $code = <<<'CODE' 467 | parser->parse($code); 482 | $ast = $this->traverser->traverse($ast); 483 | $current = $this->zephirPrinter->prettyPrintFile($ast); 484 | 485 | $this->assertEquals($expectedCode, $current, $current); 486 | } 487 | 488 | /** 489 | * @test 490 | */ 491 | public function it_converts_array_destructuring(): void 492 | { 493 | $code = <<<'CODE' 494 | parser->parse($code); 499 | $ast = $this->traverser->traverse($ast); 500 | $current = $this->zephirPrinter->prettyPrintFile($ast); 501 | 502 | /** 503 | * var firstName; 504 | * var lastName; 505 | * let adb5910704831 = ["John", "Doe"]; 506 | * let firstName = db5910704831[0]; 507 | * let lastName = db5910704831[1]; 508 | */ 509 | $this->assertNotFalse(strpos($current, 'var firstName;'), $current); 510 | $this->assertNotFalse(strpos($current, 'var lastName;'), $current); 511 | $this->assertNotFalse(strpos($current, 'let firstName = '), $current); 512 | $this->assertNotFalse(strpos($current, 'let lastName = '), $current); 513 | $this->assertNotFalse(strpos($current, '[0];'), $current); 514 | $this->assertNotFalse(strpos($current, '[1];'), $current); 515 | } 516 | 517 | /** 518 | * @test 519 | */ 520 | public function it_converts_array_destructuring_with_method_call(): void 521 | { 522 | $code = <<<'CODE' 523 | methodCall($data); 525 | CODE; 526 | 527 | $ast = $this->parser->parse($code); 528 | $ast = $this->traverser->traverse($ast); 529 | $current = $this->zephirPrinter->prettyPrintFile($ast); 530 | 531 | /** 532 | * var firstName; 533 | * var lastName; 534 | * let adb5910704831 = $this->methodCall($data); 535 | * let firstName = db5910704831[0]; 536 | * let lastName = db5910704831[1]; 537 | */ 538 | $this->assertNotFalse(strpos($current, 'var firstName;'), $current); 539 | $this->assertNotFalse(strpos($current, 'var lastName;'), $current); 540 | $this->assertNotFalse(strpos($current, 'let firstName = '), $current); 541 | $this->assertNotFalse(strpos($current, 'let lastName = '), $current); 542 | $this->assertNotFalse(strpos($current, '[0];'), $current); 543 | $this->assertNotFalse(strpos($current, '[1];'), $current); 544 | } 545 | 546 | /** 547 | * @test 548 | */ 549 | public function it_converts_array_destructuring_with_function_call(): void 550 | { 551 | $code = <<<'CODE' 552 | parser->parse($code); 557 | $ast = $this->traverser->traverse($ast); 558 | $current = $this->zephirPrinter->prettyPrintFile($ast); 559 | 560 | /** 561 | * var firstName; 562 | * var lastName; 563 | * let adb5910704831 = explode(' ', $data, 2); 564 | * let firstName = db5910704831[0]; 565 | * let lastName = db5910704831[1]; 566 | */ 567 | $this->assertNotFalse(strpos($current, 'var firstName;'), $current); 568 | $this->assertNotFalse(strpos($current, 'var lastName;'), $current); 569 | $this->assertNotFalse(strpos($current, 'let firstName = '), $current); 570 | $this->assertNotFalse(strpos($current, 'let lastName = '), $current); 571 | $this->assertNotFalse(strpos($current, '[0];'), $current); 572 | $this->assertNotFalse(strpos($current, '[1];'), $current); 573 | } 574 | } 575 | --------------------------------------------------------------------------------