Add readme

This commit is contained in:
Daniil Gentili 2020-09-05 22:35:30 +02:00
parent 8dda97c514
commit 546c6dbaca
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
51 changed files with 243 additions and 36 deletions

56
README.md Normal file
View File

@ -0,0 +1,56 @@
# Phabel
**Write and deploy modern PHP 8 code, today.**
Created by [Daniil Gentili](https://daniil.it)
## WIP :)
This is a transpiler that will allow native usage of php8 features and especially syntax in projects and libraries, while allowing maintainers to publish a version targeting 7.0 or even lower versions of php.
The transpiler will seamlessly hook into composer to transpile the package (and all dependencies down the current branch of the dependency tree!) on installation, on the user's machine, targeting the user's specific php version (or another one specified in the config).
This project is mostly ready, but I would love some feedback on design and APIs.
## Design
After [hooking into composer](https://github.com/danog/phabel/tree/master/src/Composer) by specifying a custom repository, the transpilation process begins.
### 1. Composer dependency graph
All dependencies of a package with a phabel configuration are processed with phabel with the same configuration, to allow transpiling towards an arbitrary version of PHP; composer takes care of choosing the best package, according to the current version of PHP.
### 2. Phabel plugin graph
All plugins specified in the configuration of each package are added to the [phabel plugin graph](https://github.com/danog/phabel/blob/master/src/PluginGraph/GraphInternal.php).
The [plugin graph](https://github.com/danog/phabel/blob/master/src/PluginGraph/GraphInternal.php) takes care of properly trickling configuration values and plugins down the dependency graph, as well as plugin graph optimization by merging multiple transforms (if allowed) in a single AST traversal.
It will also detect circular references in [plugin dependencies](#3-2-plugin-dependencies).
### 3. Plugins
#### 3.1 AST traversal
Each [phabel plugin](https://github.com/danog/phabel/blob/master/src/PluginInterface.php) can specify multiple `leave*` and `enter*` methods, called when traversing down or up the dependency graph.
These methods will be called only for nodes matching the typehint of the parameter.
A second, optional parameter can be provided, to allow the [Traverser](https://github.com/danog/phabel/blob/master/src/Traverser.php) to pass a [Context](https://github.com/danog/phabel/blob/master/src/Context.php) object with helper methods for replacing nodes in arbitrary positions of the AST stack.
#### 3.2 Plugin dependencies
Plugins can also specify other plugins as "dependencies" or "reverse dependencies", with the `runBefore` , `runAfter` , `runWithBefore` , `runWithAfter` methods, to force some transforms to run before others.
By using the `*with*` methods, additional plugin graph optimization is allowed by merging multiple transforms in a single AST traversal.
Each [phabel plugin](https://github.com/danog/phabel/blob/master/src/PluginInterface.php) can also accept a configuration: this a simple way to reuse code, by specifying a single plugin for a class of transforms, and then requiring it from other plugins, specifying a specific configuration to trigger only certain transforms.
#### 3.3 Plugin configuration
Configuration arrays are coupled with plugins when resolving the plugin graph.
When possible, the plugin graph will try to merge a plugin with multiple configs into a single (or fewer) plugins using the [ `mergeConfigs` method](https://github.com/danog/phabel/blob/master/src/PluginInterface.php) of the plugin.
This merge method will be called automatically during plugin graph flattening, if requirement links allow it.
### 4. Transforms
[Multiple transforms are available](https://github.com/danog/phabel/tree/master/src/Target), covering all PHP 7 features.
More complex and generic transforms like typehint and nested expression polyfilling can be found in the [plugin folder](https://github.com/danog/phabel/tree/master/src/Plugin).

View File

@ -26,8 +26,8 @@ use PhpParser\Node\Stmt\If_;
use SplStack;
/**
* AST Context
*
* AST Context.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
@ -261,8 +261,7 @@ class Context
]
);
$curNode = $result;
} else if ($subNode instanceof FuncCall) {
} elseif ($subNode instanceof FuncCall) {
}
$this->insertBefore($parent, $nodes);
}

View File

@ -17,9 +17,10 @@ use PhpParser\ParserFactory;
use ReflectionClass;
/**
* Plugin
*
* Plugin.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
abstract class Plugin implements PluginInterface
{

View File

@ -6,7 +6,6 @@ use Phabel\Plugin;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\BinaryOp\Concat;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\Isset_;
use PhpParser\Node\Expr\PropertyFetch;
@ -18,12 +17,17 @@ use PhpParser\Node\VarLikeIdentifier;
use ReflectionClass;
use ReflectionClassConstant;
use ReflectionException;
use ReflectionProperty;
/**
* Replace nested expressions in isset.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class IssetExpressionFixer extends Plugin
{
/**
* Recursively extract bottom ArrayDimFetch
* Recursively extract bottom ArrayDimFetch.
*
* @param Node $var
* @return Node
@ -134,41 +138,41 @@ class IssetExpressionFixer extends Plugin
}
/**
* Check if static property is set
* Check if static property is set.
*
* @param class-string|object $class Class
* @param string $property Property name
* @param boolean $propertyOrConstant Whether to fetch the property or the constant
*
*
* @return boolean
*/
public static function staticExists($class, string $property, bool $propertyOrConstant): bool
{
$reflectionClass = new ReflectionClass($class);
$class = new self::getClass($class);
$class = self::getClass($class);
if ($propertyOrConstant) {
try {
$reflection = $reflectionClass->getProperty($property);
} catch (ReflectionException $e) {
return false;
}
} else if (PHP_VERSION_ID >= 70100) {
} elseif (PHP_VERSION_ID >= 70100) {
try {
$reflection = new ReflectionClassConstant($class, $property);
} catch (ReflectionException $e) {
return false;
}
} else {
return isset($reflectionClass->getConstants()[$property];
return isset($reflectionClass->getConstants()[$property]);
}
$classCaller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['class'] ?? '';
$classCaller = \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['class'] ?? '';
$allowProtected = false;
$allowPrivate = false;
if ($classCaller) {
if ($class === $classCaller) {
$allowProtected = $allowPrivate = true;
} else if ($reflectionClass->isSubclassOf($classCaller) || (new ReflectionClass($classCaller))->isSubclassOf($class)) {
} elseif ($reflectionClass->isSubclassOf($classCaller) || (new ReflectionClass($classCaller))->isSubclassOf($class)) {
$allowProtected = true;
}
}

View File

@ -22,6 +22,7 @@ use SplStack;
* Enable memoization of results based on a parameter.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class Memoization
{

View File

@ -8,7 +8,6 @@ use Phabel\Traverser;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
use PhpParser\Node\Expr\ClassConstFetch;
@ -25,6 +24,12 @@ use PhpParser\Node\Expr\Ternary;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Stmt\Return_;
/**
* Fix nested expressions.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class NestedExpressionFixer extends Plugin
{
/**
@ -108,7 +113,7 @@ class NestedExpressionFixer extends Plugin
"returnMe",
new Closure([
'byRef' => true,
'uses' => array_keys($this->finderPlugin->getFound()),
'uses' => \array_keys($this->finderPlugin->getFound()),
'stmts' => [
new Assign($value = $context->getVariable(), $valueCopy),
new Return_($expr)

View File

@ -6,6 +6,12 @@ use Phabel\Plugin;
use Phabel\Traverser;
use PhpParser\Builder\FunctionLike;
/**
* Regenerator transformer.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class ReGenerator extends Plugin
{
const SHOULD_ATTRIBUTE = 'shouldRegenerate';
@ -25,7 +31,7 @@ class ReGenerator extends Plugin
}
$this->traverser->traverseAst($function);
}
public function runAfter(): array
public static function runAfter(): array
{
return [ArrowClosure::class];
}

View File

@ -4,6 +4,9 @@ namespace Phabel\Plugin\ReGenerator;
/**
* Regenerator class.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class ReGenerator implements \Iterator
{

View File

@ -9,6 +9,9 @@ use SplQueue;
/**
* Internal regenerator traversor.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class ReGeneratorInternal extends Plugin
{

View File

@ -11,6 +11,9 @@ use SplQueue;
/**
* Optimizes concatenation of multiple strings.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class StringConcatOptimizer extends Plugin
{

View File

@ -43,6 +43,7 @@ use SplStack;
* Replace all usages of a certain type in typehints.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class TypeHintStripper extends Plugin
{

View File

@ -8,6 +8,7 @@ use ReflectionMethod;
* Caches plugin information.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class PluginCache
{

View File

@ -5,7 +5,10 @@ namespace Phabel\PluginGraph;
use Phabel\PluginInterface;
/**
* Circular reference in plugin graph.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class CircularException extends \Exception
{

View File

@ -2,10 +2,14 @@
namespace Phabel\PluginGraph;
use Phabel\Plugin;
use Phabel\PluginInterface;
/**
* Graph API wrapper.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class Graph
{

View File

@ -7,7 +7,10 @@ use SplObjectStorage;
use SplQueue;
/**
* Internal graph class.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class GraphInternal
{

View File

@ -11,6 +11,7 @@ use SplQueue;
* Represents a plugin with a certain configuration.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class Node
{

View File

@ -6,6 +6,7 @@ namespace Phabel\PluginGraph;
* List of packages associated with plugin.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class PackageContext
{

View File

@ -9,6 +9,7 @@ use SplQueue;
* Representation of multiple plugins+configs.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class Plugins
{

View File

@ -5,9 +5,10 @@ namespace Phabel;
use Phabel\PluginGraph\PackageContext;
/**
* Plugin interface
*
* Plugin interface.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
interface PluginInterface
{

View File

@ -7,8 +7,9 @@ use PhpParser\NodeAbstract;
/**
* Root node.
*
* @author Daniil Gentili <email@email.com>
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class RootNode extends NodeAbstract
{

View File

@ -8,6 +8,11 @@ use Phabel\Plugin\ReGenerator;
use PhpParser\Node\Expr\Yield_;
use PhpParser\Node\FunctionLike;
/**
* Detect usages of yield.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class YieldDetector extends Plugin
{
public function enterYield(Yield_ $node, Context $ctx): void
@ -19,11 +24,11 @@ class YieldDetector extends Plugin
}
}
}
public function runBefore(): array
public static function runBefore(): array
{
return [ReGenerator::class];
}
public function runAfter(): array
public static function runAfter(): array
{
return [ArrowClosure::class];
}

View File

@ -10,6 +10,10 @@ use PhpParser\Node\Expr\New_;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt\Namespace_;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class AnonymousClassReplacer extends Plugin
{
/**

View File

@ -9,6 +9,10 @@ use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class ClosureCallReplacer extends Plugin
{
/**

View File

@ -8,8 +8,8 @@ use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Const_;
/*
* Converts define() arrays into const arrays
/**
* Converts define() arrays into const arrays.
*/
class DefineArrayReplacer extends Plugin
{

View File

@ -8,6 +8,10 @@ use PhpParser\Node\Stmt\GroupUse;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\UseUse;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class GroupUseReplacer extends Plugin
{
/**

View File

@ -5,6 +5,10 @@ namespace Phabel\Target\Php70;
use Phabel\Plugin;
use Phabel\Plugin\IssetExpressionFixer as fixer;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class IssetExpressionFixer extends Plugin
{
/**

View File

@ -5,6 +5,10 @@ namespace Phabel\Target\Php70;
use Phabel\Plugin;
use Phabel\Plugin\NestedExpressionFixer as fixer;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class NestedExpressionFixer extends Plugin
{
/**

View File

@ -7,6 +7,10 @@ use PhpParser\Node;
use PhpParser\Node\Expr\BinaryOp\Coalesce;
use PhpParser\Node\Expr\StaticCall;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class NullCoalesceReplacer extends Plugin
{
/**

View File

@ -5,6 +5,10 @@ namespace Phabel\Target\Php70;
use Phabel\Plugin;
use PhpParser\Node;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class ReservedNameReplacer extends Plugin
{
/**

View File

@ -5,6 +5,10 @@ namespace Phabel\Target\Php70;
use Phabel\Plugin;
use Phabel\Plugin\TypeHintStripper;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class ScalarTypeHintsRemover extends Plugin
{
/**

View File

@ -7,6 +7,10 @@ use PhpParser\Node\Stmt\DeclareDeclare;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class StrictTypesDeclareStatementRemover extends NodeVisitorAbstract
{
/**

View File

@ -10,6 +10,10 @@ use PhpParser\Node\Expr\YieldFrom;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\Return_;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class YieldFromReturnDetector extends Plugin
{
public function enterYieldFrom(YieldFrom $node, Context $ctx): void
@ -48,11 +52,11 @@ class YieldFromReturnDetector extends Plugin
}
}
}
public function runBefore(): array
public static function runBefore(): array
{
return [ReGenerator::class];
}
public function runAfter(): array
public static function runAfter(): array
{
return [ArrowClosure::class];
}

View File

@ -4,6 +4,10 @@ namespace Phabel\Target\Php71;
use Phabel\Plugin;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class IssetExpressionFixer extends Plugin
{
}

View File

@ -4,6 +4,10 @@ namespace Phabel\Target\Php71;
use Phabel\Plugin;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class NestedExpressionFixer extends Plugin
{
}

View File

@ -5,6 +5,10 @@ namespace Phabel\Target\Php71;
use Phabel\Plugin;
use Phabel\Plugin\TypeHintStripper;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class NullableType extends Plugin
{
/**

View File

@ -5,6 +5,10 @@ namespace Phabel\Target\Php71;
use Phabel\Plugin;
use Phabel\Plugin\TypeHintStripper;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class VoidReturnType extends Plugin
{
/**

View File

@ -4,6 +4,10 @@ namespace Phabel\Target\Php72;
use Phabel\Plugin;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class IssetExpressionFixer extends Plugin
{
}

View File

@ -4,6 +4,10 @@ namespace Phabel\Target\Php72;
use Phabel\Plugin;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class NestedExpressionFixer extends Plugin
{
}

View File

@ -5,6 +5,10 @@ namespace Phabel\Target\Php72;
use Phabel\Plugin;
use Phabel\Plugin\TypeHintStripper;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class ObjectTypeHintReplacer extends Plugin
{
/**

View File

@ -4,6 +4,10 @@ namespace Phabel\Target\Php73;
use Phabel\Plugin;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class IssetExpressionFixer extends Plugin
{
}

View File

@ -5,6 +5,10 @@ namespace Phabel\Target\Php73;
use Phabel\Plugin;
use Phabel\Plugin\NestedExpressionFixer as fixer;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class NestedExpressionFixer extends Plugin
{
/**

View File

@ -7,6 +7,10 @@ use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\FuncCall;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class ArrayUnpack extends Plugin
{
public function enter(Array_ $array): ?FuncCall

View File

@ -8,8 +8,6 @@ use Phabel\Plugin\ArrowClosureVariableFinder;
use Phabel\Traverser;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\ClosureUse;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Param;
/**

View File

@ -6,6 +6,10 @@ use Phabel\Plugin;
use PhpParser\Node\Expr\ClosureUse;
use PhpParser\Node\Expr\Variable;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class ArrowClosureVariableFinder extends Plugin
{
private array $found = [];

View File

@ -4,6 +4,10 @@ namespace Phabel\Target\Php74;
use Phabel\Plugin;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class IssetExpressionFixer extends Plugin
{
}

View File

@ -4,6 +4,10 @@ namespace Phabel\Target\Php74;
use Phabel\Plugin;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class NestedExpressionFixer extends Plugin
{
}

View File

@ -7,6 +7,10 @@ use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\AssignOp\Coalesce;
use PhpParser\Node\Expr\BinaryOp\Coalesce as BinaryOpCoalesce;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class NullCoalesceAssignment extends Plugin
{
public function enter(Coalesce $coalesce): Assign

View File

@ -5,6 +5,10 @@ namespace Phabel\Target\Php80;
use Phabel\Plugin;
use Phabel\Plugin\IssetExpressionFixer as fixer;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class IssetExpressionFixer extends Plugin
{
/**

View File

@ -5,6 +5,10 @@ namespace Phabel\Target\Php80;
use Phabel\Plugin;
use Phabel\Plugin\NestedExpressionFixer as fixer;
/**
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class NestedExpressionFixer extends Plugin
{
/**

View File

@ -8,9 +8,10 @@ use PhpParser\ParserFactory;
use SplQueue;
/**
* AST traverser
*
* AST traverser.
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class Traverser
{

View File

@ -4,8 +4,9 @@ namespace Phabel;
/**
* Represent variables currently in scope.
*
*
* @author Daniil Gentili <daniil@daniil.it>
* @license MIT
*/
class VariableContext
{
@ -75,13 +76,13 @@ class VariableContext
public function getVar(): string
{
do {
$var = 'phabel'.bin2hex(random_bytes(8));
$var = 'phabel'.\bin2hex(\random_bytes(8));
} while (isset($this->variables[$var]));
$this->variables[$var] = true;
return $var;
}
/**
* Get all variables currently defined
* Get all variables currently defined.
*
* @return array
*/