From a67be7c2a0a0e7355486a1a838d744b912235810 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 30 Jan 2019 11:45:45 +0100 Subject: [PATCH 1/7] Printer::setTypeResolving() adds ability to print uresolved code [Closes #31] --- readme.md | 13 ++++++++++-- src/PhpGenerator/Printer.php | 19 ++++++++++++++--- tests/PhpGenerator/PhpNamespace.fqn.phpt | 3 +++ tests/PhpGenerator/Printer.namespace.phpt | 4 ++++ .../Printer.namespace.unresolved.expect | 21 +++++++++++++++++++ 5 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 tests/PhpGenerator/expected/Printer.namespace.unresolved.expect diff --git a/readme.md b/readme.md index 3c4b8b0f..ddeaba70 100644 --- a/readme.md +++ b/readme.md @@ -413,8 +413,9 @@ $namespace->addUse('Http\Request', 'HttpReq'); // use Http\Request as HttpReq; ``` **IMPORTANT NOTE:** when the class is part of the namespace, it is rendered slightly differently: all types (ie. type hints, return types, parent class name, -implemented interfaces and used traits) are automatically *resolved*. It means that you have to **use full class names** in definitions -and they will be replaced with aliases (according to the use-statements) or fully qualified names in the resulting code: +implemented interfaces and used traits) are automatically *resolved* (unless you turn it off, see below). +It means that you have to **use full class names** in definitions and they will be replaced +with aliases (according to the use-statements) or fully qualified names in the resulting code: ```php $namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); @@ -455,6 +456,14 @@ class Demo implements A } ``` +Auto-resolving can be turned off this way: + +```php +$printer = new Nette\PhpGenerator\Printer; // or PsrPrinter +$printer->setTypeResolving(false); +echo $printer->printNamespace($namespace); +``` + PHP Files --------- diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 580a3f10..657ac250 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -26,6 +26,9 @@ class Printer /** @var int */ protected $linesBetweenMethods = 2; + /** @var bool */ + private $resolveTypes = true; + public function printFunction(GlobalFunction $function, PhpNamespace $namespace = null): string { @@ -83,7 +86,7 @@ public function printMethod(Method $method, PhpNamespace $namespace = null): str public function printClass(ClassType $class, PhpNamespace $namespace = null): string { $class->validate(); - $resolver = $namespace ? [$namespace, 'unresolveName'] : function ($s) { return $s; }; + $resolver = $this->resolveTypes && $namespace ? [$namespace, 'unresolveName'] : function ($s) { return $s; }; $traits = []; foreach ($class->getTraitResolutions() as $trait => $resolutions) { @@ -185,6 +188,16 @@ public function printFile(PhpFile $file): string } + /** + * @return static + */ + public function setTypeResolving(bool $state = true): self + { + $this->resolveTypes = $state; + return $this; + } + + protected function indent(string $s): string { return Strings::indent($s, 1, $this->indentation); @@ -201,7 +214,7 @@ protected function printParameters($function, ?PhpNamespace $namespace): string foreach ($list as $param) { $variadic = $function->isVariadic() && $param === end($list); $hint = $param->getTypeHint(); - $params[] = ($hint ? ($param->isNullable() ? '?' : '') . ($namespace ? $namespace->unresolveName($hint) : $hint) . ' ' : '') + $params[] = ($hint ? ($param->isNullable() ? '?' : '') . ($this->resolveTypes && $namespace ? $namespace->unresolveName($hint) : $hint) . ' ' : '') . ($param->isReference() ? '&' : '') . ($variadic ? '...' : '') . '$' . $param->getName() @@ -220,7 +233,7 @@ protected function printParameters($function, ?PhpNamespace $namespace): string protected function printReturnType($function, ?PhpNamespace $namespace): string { return $function->getReturnType() - ? ': ' . ($function->getReturnNullable() ? '?' : '') . ($namespace ? $namespace->unresolveName($function->getReturnType()) : $function->getReturnType()) + ? ': ' . ($function->getReturnNullable() ? '?' : '') . ($this->resolveTypes && $namespace ? $namespace->unresolveName($function->getReturnType()) : $function->getReturnType()) : ''; } } diff --git a/tests/PhpGenerator/PhpNamespace.fqn.phpt b/tests/PhpGenerator/PhpNamespace.fqn.phpt index 890b40fb..de460410 100644 --- a/tests/PhpGenerator/PhpNamespace.fqn.phpt +++ b/tests/PhpGenerator/PhpNamespace.fqn.phpt @@ -64,3 +64,6 @@ $method->addParameter('two') sameFile(__DIR__ . '/expected/PhpNamespace.fqn2.expect', (string) $class); sameFile(__DIR__ . '/expected/PhpNamespace.fqn2.expect', (new Printer)->printClass($class, new PhpNamespace(''))); + +// no resolve +sameFile(__DIR__ . '/expected/PhpNamespace.fqn1.expect', (new Printer)->printClass($class)); diff --git a/tests/PhpGenerator/Printer.namespace.phpt b/tests/PhpGenerator/Printer.namespace.phpt index 062efef2..271bbee9 100644 --- a/tests/PhpGenerator/Printer.namespace.phpt +++ b/tests/PhpGenerator/Printer.namespace.phpt @@ -38,6 +38,10 @@ sameFile(__DIR__ . '/expected/Printer.namespace.class.expect', $printer->printCl sameFile(__DIR__ . '/expected/Printer.namespace.class2.expect', $printer->printClass($class)); sameFile(__DIR__ . '/expected/Printer.namespace.method.expect', $printer->printMethod($class->getMethod('first'))); +$printer2 = new Printer; +$printer2->setTypeResolving(false); +sameFile(__DIR__ . '/expected/Printer.namespace.unresolved.expect', $printer2->printNamespace($namespace)); + $function = new \Nette\PhpGenerator\GlobalFunction('func'); $function diff --git a/tests/PhpGenerator/expected/Printer.namespace.unresolved.expect b/tests/PhpGenerator/expected/Printer.namespace.unresolved.expect new file mode 100644 index 00000000..f671ea04 --- /dev/null +++ b/tests/PhpGenerator/expected/Printer.namespace.unresolved.expect @@ -0,0 +1,21 @@ +namespace Foo; + +use Bar\C; +use Foo\D as E; + +/** + * Description of class. + * This is example + */ +final class A extends ParentClass implements IExample, Foo\IOne +{ + use Foo\ObjectTrait; + + /** + * @return resource + */ + final public function first(Bar\C $var): stdClass + { + return $this->handle; + } +} From 461b6a46ca876dd6561955bc6f685100e3fb13c8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 5 Feb 2019 18:35:48 +0100 Subject: [PATCH 2/7] travis: finely segmented matrix, added PhpStan --- .travis.yml | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index c201a8ca..d2ec5115 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,24 +20,34 @@ after_failure: jobs: include: - - env: title="Lowest Dependencies" + - name: Lowest Dependencies install: - travis_retry composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable - - stage: Code Standard Checker - php: 7.1 + - name: Nette Code Checker install: - # Install Nette Code Checker - travis_retry composer create-project nette/code-checker temp/code-checker ^3 --no-progress - # Install Nette Coding Standard - - travis_retry composer create-project nette/coding-standard temp/coding-standard ^2 --no-progress - script: - php temp/code-checker/code-checker --strict-types + + + - name: Nette Coding Standard + install: + - travis_retry composer create-project nette/coding-standard temp/coding-standard ^2 --no-progress + script: - php temp/coding-standard/ecs check src tests --config temp/coding-standard/coding-standard-php71.yml + - stage: Static Analysis (informative) + install: + # Install PHPStan + - travis_retry composer create-project phpstan/phpstan-shim temp/phpstan --no-progress + - travis_retry composer install --no-progress --prefer-dist + script: + - php temp/phpstan/phpstan.phar analyse --autoload-file vendor/autoload.php --level 5 src + + - stage: Code Coverage script: - vendor/bin/tester -p phpdbg tests -s --coverage ./coverage.xml --coverage-src ./src @@ -47,6 +57,7 @@ jobs: allow_failures: + - stage: Static Analysis (informative) - stage: Code Coverage From b03cf2dfcd7e5226b5459e1daf9650129f228fc3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 12 Mar 2019 00:24:25 +0100 Subject: [PATCH 3/7] composer: removed conflict with nette/nette < 2.2 --- composer.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/composer.json b/composer.json index 8cd70bbe..a78fe22a 100644 --- a/composer.json +++ b/composer.json @@ -22,9 +22,6 @@ "nette/tester": "^2.0", "tracy/tracy": "^2.3" }, - "conflict": { - "nette/nette": "<2.2" - }, "autoload": { "classmap": ["src/"] }, From ee1376daae0dffd8f58486e134a942699101cc35 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 2 Mar 2019 20:55:14 +0100 Subject: [PATCH 4/7] removed PHP workaround --- src/PhpGenerator/Helpers.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index e8976dfd..b265e7b7 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -43,11 +43,7 @@ private static function _dump(&$var, int $level = 0) return (string) $var; } elseif (is_float($var)) { - if (is_finite($var)) { - $var = var_export($var, true); - return strpos($var, '.') === false ? $var . '.0' : $var; // workaround for PHP < 7.0.2 - } - return str_replace('.0', '', var_export($var, true)); // workaround for PHP 7.0.2 + return var_export($var, true); } elseif ($var === null) { return 'null'; From 546c901edfc9ed118d4b90e28088dfb12f4ce4e6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 15 Mar 2019 00:00:18 +0100 Subject: [PATCH 5/7] PhpLiteral is not final --- src/PhpGenerator/PhpLiteral.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpGenerator/PhpLiteral.php b/src/PhpGenerator/PhpLiteral.php index 816a5290..b7dd7d91 100644 --- a/src/PhpGenerator/PhpLiteral.php +++ b/src/PhpGenerator/PhpLiteral.php @@ -13,7 +13,7 @@ /** * PHP literal value. */ -final class PhpLiteral +class PhpLiteral { /** @var string */ private $value; From 0b79d86ab00e00389c7cea5f6772beed9bdac819 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 2 Mar 2019 20:29:51 +0100 Subject: [PATCH 6/7] Helpers::format() is preferred over formatArgs() because we have variadics :) --- src/PhpGenerator/Helpers.php | 2 +- src/PhpGenerator/Method.php | 2 +- src/PhpGenerator/Traits/FunctionLike.php | 4 ++-- tests/PhpGenerator/Helpers.format.phpt | 22 +++++++++++----------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/PhpGenerator/Helpers.php b/src/PhpGenerator/Helpers.php index b265e7b7..1303a494 100644 --- a/src/PhpGenerator/Helpers.php +++ b/src/PhpGenerator/Helpers.php @@ -110,7 +110,7 @@ private static function _dump(&$var, int $level = 0) throw new Nette\InvalidArgumentException('Cannot dump anonymous class.'); } elseif (in_array($class, ['DateTime', 'DateTimeImmutable'], true)) { - return self::formatArgs("new $class(?, new DateTimeZone(?))", [$var->format('Y-m-d H:i:s.u'), $var->getTimeZone()->getName()]); + return self::format("new $class(?, new DateTimeZone(?))", $var->format('Y-m-d H:i:s.u'), $var->getTimeZone()->getName()); } $arr = (array) $var; diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index 124240ce..bf2ab538 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -63,7 +63,7 @@ public function __toString(): string */ public function setBody(?string $code, array $args = null): self { - $this->body = $args === null || $code === null ? $code : Helpers::formatArgs($code, $args); + $this->body = $args === null || $code === null ? $code : Helpers::format($code, ...$args); return $this; } diff --git a/src/PhpGenerator/Traits/FunctionLike.php b/src/PhpGenerator/Traits/FunctionLike.php index 4c368df2..3d2b2343 100644 --- a/src/PhpGenerator/Traits/FunctionLike.php +++ b/src/PhpGenerator/Traits/FunctionLike.php @@ -43,7 +43,7 @@ trait FunctionLike */ public function setBody(string $code, array $args = null): self { - $this->body = $args === null ? $code : Helpers::formatArgs($code, $args); + $this->body = $args === null ? $code : Helpers::format($code, ...$args); return $this; } @@ -59,7 +59,7 @@ public function getBody(): string */ public function addBody(string $code, array $args = null): self { - $this->body .= ($args === null ? $code : Helpers::formatArgs($code, $args)) . "\n"; + $this->body .= ($args === null ? $code : Helpers::format($code, ...$args)) . "\n"; return $this; } diff --git a/tests/PhpGenerator/Helpers.format.phpt b/tests/PhpGenerator/Helpers.format.phpt index 627b896c..91245abc 100644 --- a/tests/PhpGenerator/Helpers.format.phpt +++ b/tests/PhpGenerator/Helpers.format.phpt @@ -17,10 +17,10 @@ Assert::same('func', Helpers::format('func')); Assert::same('func(1)', Helpers::format('func(?)', 1)); Assert::same('func', Helpers::formatArgs('func', [])); Assert::same('func(1)', Helpers::formatArgs('func(?)', [1])); -Assert::same('func(1 ? 2 : 3)', Helpers::formatArgs('func(1 \? 2 : 3)', [])); -Assert::same('func([1, 2])', Helpers::formatArgs('func(?)', [[1, 2]])); -Assert::same('func(1, 2)', Helpers::formatArgs('func(...?)', [[1, 2]])); -Assert::same('func(1, 2)', Helpers::formatArgs('func(?*)', [[1, 2]])); // old way +Assert::same('func(1 ? 2 : 3)', Helpers::format('func(1 \? 2 : 3)')); +Assert::same('func([1, 2])', Helpers::format('func(?)', [1, 2])); +Assert::same('func(1, 2)', Helpers::format('func(...?)', [1, 2])); +Assert::same('func(1, 2)', Helpers::format('func(?*)', [1, 2])); // old way same( 'func( 10, @@ -51,11 +51,11 @@ same( 35, 36 )', - Helpers::formatArgs('func(?*)', [range(10, 36)]) + Helpers::format('func(?*)', range(10, 36)) ); Assert::exception(function () { - Helpers::formatArgs('func(...?)', [1, 2]); + Helpers::format('func(...?)', 1, 2); }, Nette\InvalidArgumentException::class, 'Argument must be an array.'); Assert::exception(function () { @@ -63,13 +63,13 @@ Assert::exception(function () { }, Nette\InvalidArgumentException::class, 'Insufficient number of placeholders.'); Assert::exception(function () { - Helpers::formatArgs('func(?, ?, ?)', [1, 2]); + Helpers::format('func(?, ?, ?)', [1, 2]); }, Nette\InvalidArgumentException::class, 'Insufficient number of arguments.'); -Assert::same('$a = 2', Helpers::formatArgs('$? = ?', ['a', 2])); -Assert::same('$obj->a = 2', Helpers::formatArgs('$obj->? = ?', ['a', 2])); -Assert::same('$obj->{1} = 2', Helpers::formatArgs('$obj->? = ?', [1, 2])); -Assert::same('$obj->{\' \'} = 2', Helpers::formatArgs('$obj->? = ?', [' ', 2])); +Assert::same('$a = 2', Helpers::format('$? = ?', 'a', 2)); +Assert::same('$obj->a = 2', Helpers::format('$obj->? = ?', 'a', 2)); +Assert::same('$obj->{1} = 2', Helpers::format('$obj->? = ?', 1, 2)); +Assert::same('$obj->{\' \'} = 2', Helpers::format('$obj->? = ?', ' ', 2)); Assert::same('Item', Helpers::formatMember('Item')); Assert::same("{'0Item'}", Helpers::formatMember('0Item')); From acff8b136fad84b860a626d133e791f95781f9f5 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 15 Mar 2019 04:41:06 +0100 Subject: [PATCH 7/7] Factory: uses ReflectionNamedType --- src/PhpGenerator/Factory.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 88ad0c2b..97a12f9c 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -76,7 +76,7 @@ public function fromMethodReflection(\ReflectionMethod $from): Method $method->setVariadic($from->isVariadic()); $method->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); if ($from->hasReturnType()) { - $method->setReturnType((string) $from->getReturnType()); + $method->setReturnType($from->getReturnType()->getName()); $method->setReturnNullable($from->getReturnType()->allowsNull()); } return $method; @@ -96,7 +96,7 @@ public function fromFunctionReflection(\ReflectionFunction $from) $function->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); } if ($from->hasReturnType()) { - $function->setReturnType((string) $from->getReturnType()); + $function->setReturnType($from->getReturnType()->getName()); $function->setReturnNullable($from->getReturnType()->allowsNull()); } return $function; @@ -107,7 +107,7 @@ public function fromParameterReflection(\ReflectionParameter $from): Parameter { $param = new Parameter($from->getName()); $param->setReference($from->isPassedByReference()); - $param->setTypeHint($from->hasType() ? (string) $from->getType() : null); + $param->setTypeHint($from->hasType() ? $from->getType()->getName() : null); $param->setNullable($from->hasType() && $from->getType()->allowsNull()); if ($from->isDefaultValueAvailable()) { $param->setDefaultValue($from->isDefaultValueConstant()