diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index 973c7a12..875287f8 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -7,8 +7,8 @@ jobs: name: Nette Code Checker runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: shivammathur/setup-php@v1 + - uses: actions/checkout@v3 + - uses: shivammathur/setup-php@v2 with: php-version: 8.0 coverage: none @@ -21,8 +21,8 @@ jobs: name: Nette Coding Standard runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: shivammathur/setup-php@v1 + - uses: actions/checkout@v3 + - uses: shivammathur/setup-php@v2 with: php-version: 8.0 coverage: none diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 94b3ec65..25e44dd0 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -7,7 +7,7 @@ jobs: name: PHPStan runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: php-version: 8.0 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 54206dba..239bdc4d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: name: PHP ${{ matrix.php }} tests steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -22,7 +22,7 @@ jobs: - run: composer install --no-progress --prefer-dist - run: vendor/bin/tester tests -s -C - if: failure() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: output path: tests/**/output @@ -32,7 +32,7 @@ jobs: name: Lowest Dependencies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: php-version: 8.0 @@ -46,7 +46,7 @@ jobs: name: Code Coverage runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 with: php-version: 8.0 diff --git a/composer.json b/composer.json index 431cbc9b..fc193a79 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "nette/php-generator", - "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.1 features.", + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.2 features.", "keywords": ["nette", "php", "code", "scaffolding"], "homepage": "https://nette.org", "license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"], @@ -20,7 +20,7 @@ }, "require-dev": { "nette/tester": "^2.4", - "nikic/php-parser": "^4.13", + "nikic/php-parser": "^4.15", "tracy/tracy": "^2.8", "phpstan/phpstan": "^1.0" }, diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index dd03b4fa..c826bc8e 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,10 +1,5 @@ parameters: ignoreErrors: - - - message: '#^Match expression does not handle remaining value\: true$#' - count: 1 - path: src/PhpGenerator/ClassLike.php - - message: '#^Method Nette\\PhpGenerator\\ClassType\:\:addTrait\(\) has parameter \$deprecatedParam with no value type specified in iterable type array\.$#' count: 1 @@ -15,16 +10,6 @@ parameters: count: 1 path: src/PhpGenerator/EnumType.php - - - message: '#^Access to an undefined property PhpParser\\Node\:\:\$attrGroups\.$#' - count: 1 - path: src/PhpGenerator/Extractor.php - - - - message: '#^Access to an undefined property PhpParser\\Node\\Expr\:\:\$value\.$#' - count: 1 - path: src/PhpGenerator/Extractor.php - - message: '#^Call to an undefined method Nette\\PhpGenerator\\ClassLike\:\:addConstant\(\)\.$#' count: 1 @@ -45,13 +30,18 @@ parameters: count: 1 path: src/PhpGenerator/Extractor.php + - + message: '#^Call to an undefined method Nette\\PhpGenerator\\FunctionLike\:\:addPromotedParameter\(\)\.$#' + count: 1 + path: src/PhpGenerator/Extractor.php + - message: '#^Method Nette\\PhpGenerator\\Extractor\:\:addCommentAndAttributes\(\) has parameter \$element with no type specified\.$#' count: 1 path: src/PhpGenerator/Extractor.php - - message: '#^Property class@anonymous/PhpGenerator/Extractor\.php\:176\:\:\$callback has no type specified\.$#' + message: '#^Property class@anonymous/PhpGenerator/Extractor\.php\:175\:\:\$callback has no type specified\.$#' count: 1 path: src/PhpGenerator/Extractor.php @@ -65,6 +55,11 @@ parameters: count: 1 path: src/PhpGenerator/Factory.php + - + message: '#^Call to an undefined method ReflectionClass\\:\:isReadOnly\(\)\.$#' + count: 1 + path: src/PhpGenerator/Factory.php + - message: '#^Elseif branch is unreachable because previous condition is always true\.$#' count: 1 @@ -90,11 +85,6 @@ parameters: count: 1 path: src/PhpGenerator/Factory.php - - - message: '#^Parameter \#1 \$name of method Nette\\PhpGenerator\\ClassType\:\:setExtends\(\) expects string\|null, array\ given\.$#' - count: 1 - path: src/PhpGenerator/Factory.php - - message: '#^Unreachable statement \- code above always terminates\.$#' count: 1 @@ -115,6 +105,11 @@ parameters: count: 1 path: src/PhpGenerator/Method.php + - + message: '#^Method Nette\\PhpGenerator\\Printer\:\:printDocComment\(\) has parameter \$commentable with no type specified\.$#' + count: 1 + path: src/PhpGenerator/Printer.php + - message: '#^Method Nette\\PhpGenerator\\TraitType\:\:addTrait\(\) has parameter \$deprecatedParam with no value type specified in iterable type array\.$#' count: 1 diff --git a/readme.md b/readme.md index 8cd138d3..01f74159 100644 --- a/readme.md +++ b/readme.md @@ -141,7 +141,7 @@ public function __construct( } ``` -Readonly properties introduced by PHP 8.1 can be marked via `setReadOnly()`. +Readonly properties and classes can be marked via `setReadOnly()`. ------ @@ -502,7 +502,7 @@ Each type or union/intersection type can be passed as a string, you can also use ```php use Nette\PhpGenerator\Type; -$member->setType('array'); // or Type::ARRAY; +$member->setType('array'); // or Type::Array; $member->setType('array|string'); // or Type::union('array', 'string') $member->setType('Foo&Bar'); // or Type::intersection(Foo::class, Bar::class) $member->setType(null); // removes type diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index a78a1d8b..1877d163 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -36,6 +36,7 @@ final class ClassType extends ClassLike private bool $final = false; private bool $abstract = false; private ?string $extends = null; + private bool $readOnly = false; /** @var string[] */ private array $implements = []; @@ -172,6 +173,19 @@ public function isAbstract(): bool } + public function setReadOnly(bool $state = true): static + { + $this->readOnly = $state; + return $this; + } + + + public function isReadOnly(): bool + { + return $this->readOnly; + } + + public function setExtends(?string $name): static { if ($name) { diff --git a/src/PhpGenerator/Closure.php b/src/PhpGenerator/Closure.php index 398e1491..d019fbd6 100644 --- a/src/PhpGenerator/Closure.php +++ b/src/PhpGenerator/Closure.php @@ -14,13 +14,10 @@ /** * Closure. - * - * @property-deprecated string $body */ -final class Closure +final class Closure extends FunctionLike { use Nette\SmartObject; - use Traits\FunctionLike; use Traits\AttributeAware; /** @var Parameter[] */ diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index d5569f4c..148bf3a4 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -108,7 +108,7 @@ private function getReformattedContents(array $nodes, int $level): string */ private function prepareReplacements(array $nodes): array { - $start = $nodes[0]->getStartFilePos(); + $start = $this->getNodeStartPos($nodes[0]); $replacements = []; (new NodeFinder)->find($nodes, function (Node $node) use (&$replacements, $start) { if ($node instanceof Node\Name\FullyQualified) { @@ -246,6 +246,7 @@ private function addClassToFile(PhpFile $phpFile, Node\Stmt\Class_ $node): Class $class->setFinal($node->isFinal()); $class->setAbstract($node->isAbstract()); + $class->setReadOnly(method_exists($node, 'isReadonly') && $node->isReadonly()); $this->addCommentAndAttributes($class, $node); return $class; } @@ -384,7 +385,7 @@ private function addCommentAndAttributes($element, Node $node): void } - private function setupFunction(GlobalFunction|Method $function, Node\FunctionLike $node): void + private function setupFunction(FunctionLike $function, Node\FunctionLike $node): void { $function->setReturnReference($node->returnsByRef()); $function->setReturnType($node->getReturnType() ? $this->toPhp($node->getReturnType()) : null); @@ -430,7 +431,15 @@ private function toPhp(mixed $value): string private function getNodeContents(Node ...$nodes): string { - $start = $nodes[0]->getStartFilePos(); + $start = $this->getNodeStartPos($nodes[0]); return substr($this->code, $start, end($nodes)->getEndFilePos() - $start + 1); } + + + private function getNodeStartPos(Node $node): int + { + return ($comments = $node->getComments()) + ? $comments[0]->getStartFilePos() + : $node->getStartFilePos(); + } } diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 979ca1c7..c8193a59 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -32,7 +32,8 @@ public function fromClassReflection( \ReflectionClass $from, bool $withBodies = false, ?bool $materializeTraits = null, - ): ClassLike { + ): ClassLike + { if ($materializeTraits !== null) { trigger_error(__METHOD__ . '() parameter $materializeTraits has been removed (is always false).', E_USER_DEPRECATED); } @@ -55,6 +56,7 @@ public function fromClassReflection( $class = new ClassType($from->getShortName(), new PhpNamespace($from->getNamespaceName())); $class->setFinal($from->isFinal() && $class->isClass()); $class->setAbstract($from->isAbstract() && $class->isClass()); + $class->setReadOnly(PHP_VERSION_ID >= 80200 && $from->isReadOnly()); } $ifaces = $from->getInterfaceNames(); @@ -70,7 +72,7 @@ public function fromClassReflection( } $class->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); - $class->setAttributes(self::getAttributes($from)); + $class->setAttributes($this->getAttributes($from)); if ($from->getParentClass()) { $class->setExtends($from->getParentClass()->name); $class->setImplements(array_diff($class->getImplements(), $from->getParentClass()->getInterfaceNames())); @@ -160,7 +162,7 @@ public function fromMethodReflection(\ReflectionMethod $from): Method $method->setReturnReference($from->returnsReference()); $method->setVariadic($from->isVariadic()); $method->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); - $method->setAttributes(self::getAttributes($from)); + $method->setAttributes($this->getAttributes($from)); $method->setReturnType((string) $from->getReturnType()); return $method; @@ -177,7 +179,7 @@ public function fromFunctionReflection(\ReflectionFunction $from, bool $withBody $function->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); } - $function->setAttributes(self::getAttributes($from)); + $function->setAttributes($this->getAttributes($from)); $function->setReturnType((string) $from->getReturnType()); if ($withBody) { @@ -192,12 +194,12 @@ public function fromFunctionReflection(\ReflectionFunction $from, bool $withBody } - public function fromCallable(callable $from): Method|GlobalFunction|Closure + public function fromCallable(callable $from): FunctionLike { $ref = Nette\Utils\Callback::toReflection($from); return $ref instanceof \ReflectionMethod - ? self::fromMethodReflection($ref) - : self::fromFunctionReflection($ref); + ? $this->fromMethodReflection($ref) + : $this->fromFunctionReflection($ref); } @@ -226,7 +228,7 @@ public function fromParameterReflection(\ReflectionParameter $from): Parameter $param->setNullable($param->isNullable() && $param->getDefaultValue() !== null); } - $param->setAttributes(self::getAttributes($from)); + $param->setAttributes($this->getAttributes($from)); return $param; } @@ -238,7 +240,7 @@ public function fromConstantReflection(\ReflectionClassConstant $from): Constant $const->setVisibility($this->getVisibility($from)); $const->setFinal(PHP_VERSION_ID >= 80100 ? $from->isFinal() : false); $const->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); - $const->setAttributes(self::getAttributes($from)); + $const->setAttributes($this->getAttributes($from)); return $const; } @@ -248,7 +250,7 @@ public function fromCaseReflection(\ReflectionClassConstant $from): EnumCase $const = new EnumCase($from->name); $const->setValue($from->getValue()->value ?? null); $const->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); - $const->setAttributes(self::getAttributes($from)); + $const->setAttributes($this->getAttributes($from)); return $const; } @@ -265,7 +267,7 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property $prop->setInitialized($from->hasType() && array_key_exists($prop->getName(), $defaults)); $prop->setReadOnly(PHP_VERSION_ID >= 80100 ? $from->isReadOnly() : false); $prop->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); - $prop->setAttributes(self::getAttributes($from)); + $prop->setAttributes($this->getAttributes($from)); return $prop; } diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/FunctionLike.php similarity index 95% rename from src/PhpGenerator/Traits/FunctionLike.php rename to src/PhpGenerator/FunctionLike.php index 15bb8e57..ace264ce 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/FunctionLike.php @@ -7,18 +7,18 @@ declare(strict_types=1); -namespace Nette\PhpGenerator\Traits; +namespace Nette\PhpGenerator; use Nette; -use Nette\PhpGenerator\Dumper; -use Nette\PhpGenerator\Parameter; use Nette\Utils\Type; /** - * @internal + * GlobalFunction/Closure/Method description. + * + * @property-deprecated string $body */ -trait FunctionLike +abstract class FunctionLike { private string $body = ''; diff --git a/src/PhpGenerator/GlobalFunction.php b/src/PhpGenerator/GlobalFunction.php index ae1e4f88..26665332 100644 --- a/src/PhpGenerator/GlobalFunction.php +++ b/src/PhpGenerator/GlobalFunction.php @@ -14,13 +14,10 @@ /** * Global function. - * - * @property-deprecated string $body */ -final class GlobalFunction +final class GlobalFunction extends FunctionLike { use Nette\SmartObject; - use Traits\FunctionLike; use Traits\NameAware; use Traits\CommentAware; use Traits\AttributeAware; diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index 71e7de6f..12823b4c 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -170,14 +170,19 @@ public static function createObject(string $class, array $props): object public static function validateType(?string $type, bool &$nullable): ?string { + $nullable = false; if ($type === '' || $type === null) { return null; } - if (!preg_match('#(?: - \?[\w\\\\]+| - [\w\\\\]+ (?: (&[\w\\\\]+)* | (\|[\w\\\\]+)* ) - )()$#xAD', $type)) { + if (!preg_match(<<<'XX' + ~(?n) + ( + \?? (? [\w\\]+)| + (? (?&type) (& (?&type))+ )| + (? (?&type) | \( (?&intersection) \) ) (\| (?&upart) )+ + )$~xAD + XX, $type)) { throw new Nette\InvalidArgumentException("Value '$type' is not valid type."); } diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index 0596658c..fbffab1f 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -14,13 +14,10 @@ /** * Class method. - * - * @property-deprecated string|null $body */ -final class Method +final class Method extends FunctionLike { use Nette\SmartObject; - use Traits\FunctionLike; use Traits\NameAware; use Traits\VisibilityAware; use Traits\CommentAware; @@ -92,7 +89,11 @@ public function addPromotedParameter(string $name, mixed $defaultValue = null): $param->setDefaultValue($defaultValue); } - return $this->parameters[$name] = $param; + $params = $this->getParameters(); + $params[$name] = $param; + $this->setParameters($params); + + return $param; } diff --git a/src/PhpGenerator/PhpNamespace.php b/src/PhpGenerator/PhpNamespace.php index 890fc8bf..cf9c005b 100644 --- a/src/PhpGenerator/PhpNamespace.php +++ b/src/PhpGenerator/PhpNamespace.php @@ -162,7 +162,7 @@ public function addUseConstant(string $name, ?string $alias = null): static /** @return string[] */ public function getUses(string $of = self::NameNormal): array { - asort($this->aliases[$of]); + uasort($this->aliases[$of], fn(string $a, string $b): int => strtolower(strtr($a, '\\', ' ')) <=> strtolower(strtr($b, '\\', ' '))); return array_filter( $this->aliases[$of], fn($name, $alias) => strcasecmp(($this->name ? $this->name . '\\' : '') . $alias, $name), diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 48128454..4e0f3ee3 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -49,7 +49,7 @@ public function printFunction(GlobalFunction $function, ?PhpNamespace $namespace $body = ltrim(rtrim(Strings::normalize($body)) . "\n"); return $this->printDocComment($function) - . self::printAttributes($function->getAttributes()) + . $this->printAttributes($function->getAttributes()) . $line . $this->printParameters($function, strlen($line) + strlen($returnType) + 2) // 2 = parentheses . $returnType @@ -72,7 +72,7 @@ public function printClosure(Closure $closure, ?PhpNamespace $namespace = null): $body = Helpers::simplifyTaggedNames($closure->getBody(), $this->namespace); $body = ltrim(rtrim(Strings::normalize($body)) . "\n"); - return self::printAttributes($closure->getAttributes(), inline: true) + return $this->printAttributes($closure->getAttributes(), inline: true) . 'function ' . ($closure->getReturnReference() ? '&' : '') . $this->printParameters($closure) @@ -93,7 +93,7 @@ public function printArrowFunction(Closure $closure, ?PhpNamespace $namespace = $body = Helpers::simplifyTaggedNames($closure->getBody(), $this->namespace); - return self::printAttributes($closure->getAttributes()) + return $this->printAttributes($closure->getAttributes()) . 'fn' . ($closure->getReturnReference() ? '&' : '') . $this->printParameters($closure) @@ -120,7 +120,7 @@ public function printMethod(Method $method, ?PhpNamespace $namespace = null, boo $braceOnNextLine = $this->bracesOnNextLine && !str_contains($params, "\n"); return $this->printDocComment($method) - . self::printAttributes($method->getAttributes()) + . $this->printAttributes($method->getAttributes()) . $line . $params . $returnType @@ -133,7 +133,8 @@ public function printMethod(Method $method, ?PhpNamespace $namespace = null, boo public function printClass( ClassType|InterfaceType|TraitType|EnumType $class, ?PhpNamespace $namespace = null, - ): string { + ): string + { $this->namespace = $this->resolveTypes ? $namespace : null; $class->validate(); $resolver = $this->namespace @@ -159,7 +160,7 @@ public function printClass( foreach ($class->getCases() as $case) { $enumType ??= is_scalar($case->getValue()) ? get_debug_type($case->getValue()) : null; $cases[] = $this->printDocComment($case) - . self::printAttributes($case->getAttributes()) + . $this->printAttributes($case->getAttributes()) . 'case ' . $case->getName() . ($case->getValue() === null ? '' : ' = ' . $this->dump($case->getValue())) . ";\n"; @@ -167,26 +168,24 @@ public function printClass( } $consts = []; - if ($class instanceof ClassType || $class instanceof InterfaceType || $class instanceof EnumType) { + $methods = []; + if ( + $class instanceof ClassType + || $class instanceof InterfaceType + || $class instanceof TraitType + || $class instanceof EnumType + ) { foreach ($class->getConstants() as $const) { $def = ($const->isFinal() ? 'final ' : '') . ($const->getVisibility() ? $const->getVisibility() . ' ' : '') . 'const ' . $const->getName() . ' = '; $consts[] = $this->printDocComment($const) - . self::printAttributes($const->getAttributes()) + . $this->printAttributes($const->getAttributes()) . $def . $this->dump($const->getValue(), strlen($def)) . ";\n"; } - } - $methods = []; - if ( - $class instanceof ClassType - || $class instanceof InterfaceType - || $class instanceof EnumType - || $class instanceof TraitType - ) { foreach ($class->getMethods() as $method) { $methods[] = $this->printMethod($method, $namespace, $class->isInterface()); } @@ -205,7 +204,7 @@ public function printClass( . '$' . $property->getName()); $properties[] = $this->printDocComment($property) - . self::printAttributes($property->getAttributes()) + . $this->printAttributes($property->getAttributes()) . $def . ($property->getValue() === null && !$property->isInitialized() ? '' @@ -226,6 +225,7 @@ public function printClass( if ($class instanceof ClassType) { $line[] = $class->isAbstract() ? 'abstract' : null; $line[] = $class->isFinal() ? 'final' : null; + $line[] = $class->isReadOnly() ? 'readonly' : null; } $line[] = match (true) { @@ -243,7 +243,7 @@ public function printClass( $line[] = $class->getName() ? null : '{'; return $this->printDocComment($class) - . self::printAttributes($class->getAttributes()) + . $this->printAttributes($class->getAttributes()) . implode(' ', array_filter($line)) . ($class->getName() ? "\n{\n" : "\n") . ($members ? $this->indent(implode("\n", $members)) : '') @@ -333,7 +333,7 @@ protected function printParameters(Closure|GlobalFunction|Method $function, int $promoted = $param instanceof PromotedParameter ? $param : null; $params[] = ($promoted ? $this->printDocComment($promoted) : '') - . ($attrs = self::printAttributes($param->getAttributes(), inline: true)) + . ($attrs = $this->printAttributes($param->getAttributes(), inline: true)) . ($promoted ? ($promoted->getVisibility() ?: 'public') . ($promoted->isReadOnly() && $type ? ' readonly' : '') @@ -385,7 +385,7 @@ protected function printDocComment(/*Traits\CommentAware*/ $commentable): string } - private function printReturnType(Closure|GlobalFunction|Method $function): string + private function printReturnType(FunctionLike $function): string { return ($tmp = $this->printType($function->getReturnType(), $function->isReturnNullable())) ? $this->returnTypeColon . $tmp diff --git a/src/PhpGenerator/TraitType.php b/src/PhpGenerator/TraitType.php index 4dbf42df..57621ad4 100644 --- a/src/PhpGenerator/TraitType.php +++ b/src/PhpGenerator/TraitType.php @@ -20,6 +20,7 @@ */ final class TraitType extends ClassLike { + use Traits\ConstantsAware; use Traits\MethodsAware; use Traits\PropertiesAware; use Traits\TraitsAware; @@ -28,6 +29,7 @@ public function addMember(Method|Property|Constant|TraitUse $member): static { $name = $member->getName(); [$type, $n] = match (true) { + $member instanceof Constant => ['consts', $name], $member instanceof Method => ['methods', strtolower($name)], $member instanceof Property => ['properties', $name], $member instanceof TraitUse => ['traits', $name], @@ -43,6 +45,7 @@ public function addMember(Method|Property|Constant|TraitUse $member): static public function __clone() { $clone = fn($item) => clone $item; + $this->consts = array_map($clone, $this->consts); $this->methods = array_map($clone, $this->methods); $this->properties = array_map($clone, $this->properties); $this->traits = array_map($clone, $this->traits); diff --git a/src/PhpGenerator/Type.php b/src/PhpGenerator/Type.php index be77199d..be3242a4 100644 --- a/src/PhpGenerator/Type.php +++ b/src/PhpGenerator/Type.php @@ -16,22 +16,42 @@ class Type { public const - STRING = 'string', - INT = 'int', - FLOAT = 'float', - BOOL = 'bool', - ARRAY = 'array', - OBJECT = 'object', - CALLABLE = 'callable', - ITERABLE = 'iterable', - VOID = 'void', - NEVER = 'never', - MIXED = 'mixed', - FALSE = 'false', - NULL = 'null', - SELF = 'self', - PARENT = 'parent', - STATIC = 'static'; + String = 'string', + Int = 'int', + Float = 'float', + Bool = 'bool', + Array = 'array', + Object = 'object', + Callable = 'callable', + Iterable = 'iterable', + Void = 'void', + Never = 'never', + Mixed = 'mixed', + True = 'true', + False = 'false', + Null = 'null', + Self = 'self', + Parent = 'parent', + Static = 'static'; + + /** @deprecated */ + public const + STRING = self::String, + INT = self::Int, + FLOAT = self::Float, + BOOL = self::Bool, + ARRAY = self::Array, + OBJECT = self::Object, + CALLABLE = self::Callable, + ITERABLE = self::Iterable, + VOID = self::Void, + NEVER = self::Never, + MIXED = self::Mixed, + FALSE = self::False, + NULL = self::Null, + SELF = self::Self, + PARENT = self::Parent, + STATIC = self::Static; public static function nullable(string $type, bool $state = true): string diff --git a/tests/PhpGenerator/ClassType.from.82.phpt b/tests/PhpGenerator/ClassType.from.82.phpt new file mode 100644 index 00000000..db001ac7 --- /dev/null +++ b/tests/PhpGenerator/ClassType.from.82.phpt @@ -0,0 +1,18 @@ +addProperty('order') ->setValue(new Literal('RecursiveIteratorIterator::SELF_FIRST')); $class->addProperty('typed1') - ->setType(Type::ARRAY) + ->setType(Type::Array) ->setReadOnly(); $class->addProperty('typed2') - ->setType(Type::ARRAY) + ->setType(Type::Array) ->setNullable() ->setInitialized(); $class->addProperty('typed3') - ->setType(Type::ARRAY) + ->setType(Type::Array) ->setValue(null); $p = $class->addProperty('sections', ['first' => true]) @@ -128,7 +128,7 @@ $method->addParameter('item'); $method->addParameter('res', null) ->setReference(true) - ->setType(Type::union(Type::ARRAY, 'null')); + ->setType(Type::union(Type::Array, 'null')); $method->addParameter('bar', null) ->setType('stdClass|string') diff --git a/tests/PhpGenerator/Extractor.extractAll.phpt b/tests/PhpGenerator/Extractor.extractAll.phpt index 3eccfdd8..e57556c1 100644 --- a/tests/PhpGenerator/Extractor.extractAll.phpt +++ b/tests/PhpGenerator/Extractor.extractAll.phpt @@ -15,6 +15,9 @@ sameFile(__DIR__ . '/expected/Extractor.classes.expect', (string) $file); $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.81.php')))->extractAll(); sameFile(__DIR__ . '/expected/Extractor.classes.81.expect', (string) $file); +$file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.82.php')))->extractAll(); +sameFile(__DIR__ . '/expected/Extractor.classes.82.expect', (string) $file); + $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/enum.php')))->extractAll(); sameFile(__DIR__ . '/expected/Extractor.enum.expect', (string) $file); @@ -24,56 +27,8 @@ sameFile(__DIR__ . '/expected/Extractor.traits.expect', (string) $file); $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/bodies.php')))->extractAll(); sameFile(__DIR__ . '/expected/Extractor.bodies.expect', (string) $file); -$file = (new Extractor(<<<'XX' - extractAll(); -Assert::type(Nette\PhpGenerator\PhpFile::class, $file); -Assert::match(<<<'XX' - extractAll(); +sameFile(__DIR__ . '/expected/Extractor.expect', (string) $file); Assert::exception(function () { (new Extractor('')); diff --git a/tests/PhpGenerator/Helpers.validateType.phpt b/tests/PhpGenerator/Helpers.validateType.phpt new file mode 100644 index 00000000..1277ca8f --- /dev/null +++ b/tests/PhpGenerator/Helpers.validateType.phpt @@ -0,0 +1,43 @@ + Helpers::validateType('-', $foo), + Nette\InvalidArgumentException::class, +); + +Assert::exception( + fn() => Helpers::validateType('?Foo|Bar', $foo), + Nette\InvalidArgumentException::class, +); + +Assert::exception( + fn() => Helpers::validateType('(Foo)', $foo), + Nette\InvalidArgumentException::class, +); + +Assert::exception( + fn() => Helpers::validateType('(Foo&Bar)', $foo), + Nette\InvalidArgumentException::class, +); diff --git a/tests/PhpGenerator/Method.returnTypes.phpt b/tests/PhpGenerator/Method.returnTypes.phpt index 10b67c68..bdde645b 100644 --- a/tests/PhpGenerator/Method.returnTypes.phpt +++ b/tests/PhpGenerator/Method.returnTypes.phpt @@ -13,8 +13,7 @@ namespace A } } -namespace -{ +namespace { use Nette\PhpGenerator\Method; use Tester\Assert; diff --git a/tests/PhpGenerator/Method.scalarParameters.phpt b/tests/PhpGenerator/Method.scalarParameters.phpt index 0b11c876..2cb36a86 100644 --- a/tests/PhpGenerator/Method.scalarParameters.phpt +++ b/tests/PhpGenerator/Method.scalarParameters.phpt @@ -39,8 +39,8 @@ Assert::same('float', $method->getParameters()['d']->getType()); $method = (new Method('create')) ->setBody('return null;'); -$method->addParameter('a')->setType(Type::STRING); -$method->addParameter('b')->setType(Type::BOOL); +$method->addParameter('a')->setType(Type::String); +$method->addParameter('b')->setType(Type::Bool); same( 'function create(string $a, bool $b) diff --git a/tests/PhpGenerator/Printer.use-order.phpt b/tests/PhpGenerator/Printer.use-order.phpt new file mode 100644 index 00000000..f7e642eb --- /dev/null +++ b/tests/PhpGenerator/Printer.use-order.phpt @@ -0,0 +1,28 @@ +addUse('Example\Foo\EmailAlias\Bar'); +$namespace->addUse('Example\Foo\Email\Test'); +$namespace->addUse('Example\Foo\MyClass'); + +Assert::match( + <<<'XX' + namespace Foo; + + use Example\Foo\Email\Test; + use Example\Foo\EmailAlias\Bar; + use Example\Foo\MyClass; + + XX, + $printer->printNamespace($namespace), +); diff --git a/tests/PhpGenerator/Type.phpt b/tests/PhpGenerator/Type.phpt index 84626f77..f889865a 100644 --- a/tests/PhpGenerator/Type.phpt +++ b/tests/PhpGenerator/Type.phpt @@ -8,7 +8,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -Assert::same('A|string', Type::union(A::class, Type::STRING)); +Assert::same('A|string', Type::union(A::class, Type::String)); Assert::same('?A', Type::nullable(A::class)); Assert::same('?A', Type::nullable(A::class, true)); diff --git a/tests/PhpGenerator/expected/ClassType.from.82.expect b/tests/PhpGenerator/expected/ClassType.from.82.expect new file mode 100644 index 00000000..6ea6378a --- /dev/null +++ b/tests/PhpGenerator/expected/ClassType.from.82.expect @@ -0,0 +1,11 @@ +readonly class Class13 +{ + public function func(C|(X&D)|null $foo): (A&B)|null + { + } +} + +trait Trait13 +{ + public const FOO = 123; +} diff --git a/tests/PhpGenerator/expected/ClassType.from.bodies.expect b/tests/PhpGenerator/expected/ClassType.from.bodies.expect index 1ce8fe8f..a251549e 100644 --- a/tests/PhpGenerator/expected/ClassType.from.bodies.expect +++ b/tests/PhpGenerator/expected/ClassType.from.bodies.expect @@ -27,6 +27,7 @@ abstract class Class7 public function long() { + // comment if ($member instanceof Method) { $s = [1, 2, 3]; } @@ -39,6 +40,7 @@ abstract class Class7 public function resolving($a = Abc\a\FOO, self $b = null, $c = self::FOO) { + // constants echo FOO; echo \FOO; echo a\FOO; diff --git a/tests/PhpGenerator/expected/Extractor.bodies.expect b/tests/PhpGenerator/expected/Extractor.bodies.expect index 21d22313..6e321ade 100644 --- a/tests/PhpGenerator/expected/Extractor.bodies.expect +++ b/tests/PhpGenerator/expected/Extractor.bodies.expect @@ -37,6 +37,7 @@ abstract class Class7 function long() { + // comment if ($member instanceof Method) { $s = [1, 2, 3]; } @@ -49,6 +50,7 @@ abstract class Class7 function resolving($a = a\FOO, self $b = null, $c = self::FOO) { + // constants echo FOO; echo \FOO; echo a\FOO; diff --git a/tests/PhpGenerator/expected/Extractor.bodies.resolving.expect b/tests/PhpGenerator/expected/Extractor.bodies.resolving.expect index efe51b99..cb64aca7 100644 --- a/tests/PhpGenerator/expected/Extractor.bodies.resolving.expect +++ b/tests/PhpGenerator/expected/Extractor.bodies.resolving.expect @@ -32,6 +32,7 @@ abstract class Class7 function long() { + // comment if ($member instanceof \Abc\Method) { $s = [1, 2, 3]; } @@ -44,6 +45,7 @@ abstract class Class7 function resolving($a = \Abc\a\FOO, self $b = null, $c = self::FOO) { + // constants echo FOO; echo \FOO; echo \Abc\a\FOO; diff --git a/tests/PhpGenerator/expected/Extractor.bodies.unresolving.expect b/tests/PhpGenerator/expected/Extractor.bodies.unresolving.expect index 8d8a774c..6ae6f85c 100644 --- a/tests/PhpGenerator/expected/Extractor.bodies.unresolving.expect +++ b/tests/PhpGenerator/expected/Extractor.bodies.unresolving.expect @@ -32,6 +32,7 @@ abstract class Class7 function long() { + // comment if ($member instanceof \Abc\Method) { $s = [1, 2, 3]; } @@ -44,6 +45,7 @@ abstract class Class7 function resolving($a = \Abc\a\FOO, self $b = null, $c = self::FOO) { + // constants echo FOO; echo \FOO; echo \Abc\a\FOO; diff --git a/tests/PhpGenerator/expected/Extractor.classes.82.expect b/tests/PhpGenerator/expected/Extractor.classes.82.expect new file mode 100644 index 00000000..54f5c525 --- /dev/null +++ b/tests/PhpGenerator/expected/Extractor.classes.82.expect @@ -0,0 +1,17 @@ +